From 0f0625c79b6efaf9cc331a4551092bb053a8284b Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 10 Sep 2025 13:08:36 -0700 Subject: [PATCH 001/986] Initial curvature work. --- src/axom/primal/CMakeLists.txt | 1 + src/axom/primal/geometry/NURBSCurve.hpp | 41 +++++- src/axom/primal/operators/curvature.hpp | 147 +++++++++++++++++++ src/axom/primal/tests/primal_nurbs_curve.cpp | 33 ++++- 4 files changed, 220 insertions(+), 2 deletions(-) create mode 100644 src/axom/primal/operators/curvature.hpp diff --git a/src/axom/primal/CMakeLists.txt b/src/axom/primal/CMakeLists.txt index 05d09ad70d..3ed9609a24 100644 --- a/src/axom/primal/CMakeLists.txt +++ b/src/axom/primal/CMakeLists.txt @@ -48,6 +48,7 @@ set( primal_headers ## operators operators/clip.hpp operators/closest_point.hpp + operators/curvature.hpp operators/intersect.hpp operators/intersection_volume.hpp operators/orientation.hpp diff --git a/src/axom/primal/geometry/NURBSCurve.hpp b/src/axom/primal/geometry/NURBSCurve.hpp index 8742394ace..3f860c918d 100644 --- a/src/axom/primal/geometry/NURBSCurve.hpp +++ b/src/axom/primal/geometry/NURBSCurve.hpp @@ -23,6 +23,7 @@ #include "axom/primal/geometry/BoundingBox.hpp" #include "axom/primal/geometry/OrientedBoundingBox.hpp" +#include "axom/primal/operators/curvature.hpp" #include "axom/primal/operators/squared_distance.hpp" #include @@ -387,7 +388,7 @@ class NURBSCurve std::swap(theta_0, theta_1); } - SLIC_ASSERT(theta_1 - theta_0 <= 2.0 * M_PI); + SLIC_ASSERT(static_cast(theta_1 - theta_0) <= static_cast(2.0 * M_PI)); T pi23 = 2.0 * M_PI / 3.0; int n_segments = std::ceil((theta_1 - theta_0) / pi23); @@ -1372,6 +1373,44 @@ class NURBSCurve ///@} + ///@{ + /// \name Functions dealing with curvature + + /*! + * \brief Evaluates the curvature at parameter value \a t. + * + * \param t The parameter value. + * + * \return The curvature value at u. + */ + double curvature(T t) const + { + PointType eval; + VectorType Dt, DtDt; + evaluateSecondDerivative(t, eval, Dt, DtDt); + return axom::primal::curvature(Dt, DtDt); + } + + /*! + * \brief Evaluates the curvature derivatives evaluated at \a t. + * + * \param t The parameter value. + * \param d The number of derivatives to compute (must be 1 or 2). + * \param[out] ders An array that will contain the curvature derivatives evaluated at \a t. + * + * \return The curvature derivative value(s) evaluated at \a t. + */ + void curvatureDerivatives(T t, int d, axom::Array &ders) const + { + SLIC_ASSERT(d == 1 || d == 2); + PointType eval; + axom::Array curveDers; + // Evaluate d+1 curve derivatives at u. + evaluateDerivatives(t, d + 1, eval, curveDers); + axom::primal::curvatureDerivatives(d, curveDers, ders); + } + ///@} + /*! * \brief Equality operator for NURBS Curves * diff --git a/src/axom/primal/operators/curvature.hpp b/src/axom/primal/operators/curvature.hpp new file mode 100644 index 0000000000..729a98e4c0 --- /dev/null +++ b/src/axom/primal/operators/curvature.hpp @@ -0,0 +1,147 @@ +// 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) + +/*! + * \file curvature.hpp + * + * \brief Consists of a set of templated routines used to calculate + * the curvature, given a curve's derivatives evaluated at a point. + * + */ + +#ifndef AXOM_PRIMAL_CURVATURE_HPP_ +#define AXOM_PRIMAL_CURVATURE_HPP_ + +#include "axom/config.hpp" +#include "axom/core/Array.hpp" +#include "axom/primal/geometry/Point.hpp" +#include "axom/primal/geometry/Vector.hpp" + +#include "axom/slic/interface/slic.hpp" + +namespace axom +{ +namespace primal +{ + +/*! + * \brief Evaluates the curvature, given the derivatives. + * + * \param[in] Dt The 1st derivative of the curve. + * \param[in] DtDt The 2nd derivative of the curve. + * + * \return The curvature evaluated at \a u. + */ +template +T curvature(const VectorType &Dt, const VectorType &DtDt) +{ + if constexpr (VectorType::dimension() == 2) + { + const T xp = Dt[0]; // x' + const T yp = Dt[1]; // y' + + const T xpp = DtDt[0]; // x'' + const T ypp = DtDt[1]; // y'' + + // This is signed curvature as formulated at: + // https://en.wikipedia.org/wiki/Curvature#Curvature_of_a_graph + // k = (x'y'' - y'x'') / pow(x'x' + y'y', 3./2.) + const T xp2_plus_yp2 = xp * xp + yp * yp; + return (xp * ypp - yp * xpp) / pow(xp2_plus_yp2, 3. / 2.); + } + else + { + return VectorType::cross_product(Dt, DtDt).norm() / pow(Dt.norm(), 3.); + } +} + +/*! + * \brief Evaluates the curvature derivatives using supplied curve derivatives. + * + * \param[in] d The number of derivatives to compute (1=1st deriv, 2=1st & 2nd derivs) + * \param[in] curveDerivs The derivatives, up to order \a d, of the curve. + * \param[out] ders An array that will contain the curvature derivatives. + */ +template +void curvatureDerivatives(int d, + const axom::Array &curveDerivs, + axom::Array &ders) +{ + SLIC_ASSERT(d == 1 || d == 2); + SLIC_ASSERT(curveDerivs.size() == d + 1); + + ders.resize(d); + + const VectorType& D1 = curveDerivs[0]; + const VectorType& D2 = curveDerivs[1]; + const VectorType& D3 = curveDerivs[2]; + +#if 0 + // Original 2D only + const T xp = D1[0]; // x' + const T xpp = D2[0]; // x'' + const T xppp = D3[0]; // x''' + + const T yp = D1[1]; // y' + const T ypp = D2[1]; // y'' + const T yppp = D3[1]; // y''' + + // 1st derivative of curvature. + const T xp2_plus_yp2 = xp * xp + yp * yp; + const T A = -3. * (xp * ypp - yp * xpp) * 2. * (xp * xpp + yp * ypp); + const T B = 2. * pow(xp2_plus_yp2, 5. / 2.); + const T C = xp * yppp - yp * xppp; + const T D = pow(xp2_plus_yp2, 3. / 2.); + ders[0] = A / B + C / D; + + if(d >= 2) + { + // 2nd derivative of curvature. + const T E = 15. * (-yp * xpp + xp * ypp) * + pow(2. * xp * xpp + 2. * yp * ypp, 2.) / + (4. * pow(xp2_plus_yp2, 7. / 2.)); + const T F = 3. * (2. * xp * xpp + 2. * yp * ypp) * + (-yp * xppp + xp * yppp) / pow(xp2_plus_yp2, 5. / 2.); + const T G = 3. * (-yp * xpp + xp * ypp) * + (2. * (xpp * xpp) + 2. * (ypp * ypp) + 2. * xp * xppp + 2. * yp * yppp) / + (2. * pow(xp2_plus_yp2, 5. / 2.)); + const T H = (-ypp * xppp + xpp * yppp) / pow(xp2_plus_yp2, 3. / 2.); + + ders[1] = E - F - G + H; + } +#else + const T D1Norm = D1.norm(); + const T D1Norm3 = pow(D1Norm, 3.); + const T D1Norm5 = pow(D1Norm, 5.); + const T D1D2Norm = VectorType::cross_product(D1, D2).norm(); + const T D1D3Norm = VectorType::cross_product(D1, D3).norm(); + + // 1st derivative of curvature. + const T A = -3. * D1D2Norm * 2. * (D1 * D2); + const T B = 2. * D1Norm5; + const T C = D1D3Norm; + const T D = D1Norm3; + ders[0] = A / B + C / D; + + if(d >= 2) + { + // 2nd derivative of curvature. + const T E = 15. * D1D2Norm * pow(2. * (D1 * D2), 2.) / (4. * pow(D1Norm, 7.)); + + const T F = 3. * (2. * (D1 * D2)) * D1D3Norm / D1Norm5; + + const T G = 3. * D1D2Norm * (D2.squared_norm() * (D1 * D3)) / D1Norm5; + + const T H = VectorType::cross_product(D2, D3).norm() / D1Norm3; + + ders[1] = E - F - G + H; + } +#endif +} + +} // namespace primal +} // namespace axom + +#endif // AXOM_PRIMAL_CURVATURE_HPP_ diff --git a/src/axom/primal/tests/primal_nurbs_curve.cpp b/src/axom/primal/tests/primal_nurbs_curve.cpp index 3f382b6617..e24010f48b 100644 --- a/src/axom/primal/tests/primal_nurbs_curve.cpp +++ b/src/axom/primal/tests/primal_nurbs_curve.cpp @@ -16,7 +16,7 @@ #include namespace primal = axom::primal; - +#if 0 //------------------------------------------------------------------------------ TEST(primal_nurbscurve, default_constructor) { @@ -1121,7 +1121,38 @@ TEST(primal_nurbscurve, linear_segment_constructor) } } } +#endif +//------------------------------------------------------------------------------ +template +void curvature_test(T tol) +{ + using NURBSCurve2D = axom::primal::NURBSCurve; + + const T cx = 0.; + const T cy = 0.; + const T R = 4.; + const T theta_0 = 0.; + const T theta_1 = 2. * M_PI; + const NURBSCurve2D curve = NURBSCurve2D::make_circular_arc_nurbs(theta_0, theta_1, cx, cy, R); + const int N = 100; + for(int i = 0; i < N; i++) + { + const T t = static_cast(i) / static_cast(N - 1); + const double c = curve.curvature(t); + + // The reciprocal of its radius (R), expressed as k = 1/R + EXPECT_NEAR(c , 1. / R, tol); + } +} + +TEST(primal_nurbscurve, curvature) +{ + curvature_test(1.e-7); + curvature_test(1.e-7); +} + +//------------------------------------------------------------------------------ int main(int argc, char* argv[]) { int result = 0; From 650b1447bd7bd2169d3c071c15bf511d445ca2f5 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 30 Sep 2025 16:05:37 -0700 Subject: [PATCH 002/986] Curvature derivative changes --- src/axom/primal/geometry/NURBSCurve.hpp | 2 +- src/axom/primal/operators/curvature.hpp | 10 +- src/axom/primal/tests/primal_nurbs_curve.cpp | 215 ++++++++++++++++++- 3 files changed, 214 insertions(+), 13 deletions(-) diff --git a/src/axom/primal/geometry/NURBSCurve.hpp b/src/axom/primal/geometry/NURBSCurve.hpp index 3f860c918d..ba481de9db 100644 --- a/src/axom/primal/geometry/NURBSCurve.hpp +++ b/src/axom/primal/geometry/NURBSCurve.hpp @@ -1406,7 +1406,7 @@ class NURBSCurve PointType eval; axom::Array curveDers; // Evaluate d+1 curve derivatives at u. - evaluateDerivatives(t, d + 1, eval, curveDers); + evaluateDerivatives(t, 3, eval, curveDers); axom::primal::curvatureDerivatives(d, curveDers, ders); } ///@} diff --git a/src/axom/primal/operators/curvature.hpp b/src/axom/primal/operators/curvature.hpp index 729a98e4c0..05f594627f 100644 --- a/src/axom/primal/operators/curvature.hpp +++ b/src/axom/primal/operators/curvature.hpp @@ -70,7 +70,7 @@ void curvatureDerivatives(int d, axom::Array &ders) { SLIC_ASSERT(d == 1 || d == 2); - SLIC_ASSERT(curveDerivs.size() == d + 1); + SLIC_ASSERT(curveDerivs.size() == 3); ders.resize(d); @@ -119,7 +119,7 @@ void curvatureDerivatives(int d, const T D1D3Norm = VectorType::cross_product(D1, D3).norm(); // 1st derivative of curvature. - const T A = -3. * D1D2Norm * 2. * (D1 * D2); + const T A = -3. * D1D2Norm * 2. * D1.dot(D2); const T B = 2. * D1Norm5; const T C = D1D3Norm; const T D = D1Norm3; @@ -128,11 +128,11 @@ void curvatureDerivatives(int d, if(d >= 2) { // 2nd derivative of curvature. - const T E = 15. * D1D2Norm * pow(2. * (D1 * D2), 2.) / (4. * pow(D1Norm, 7.)); + const T E = 15. * D1D2Norm * pow(2. * D1.dot(D2), 2.) / (4. * pow(D1Norm, 7.)); - const T F = 3. * (2. * (D1 * D2)) * D1D3Norm / D1Norm5; + const T F = 3. * (2. * D1.dot(D2)) * D1D3Norm / D1Norm5; - const T G = 3. * D1D2Norm * (D2.squared_norm() * (D1 * D3)) / D1Norm5; + const T G = 3. * D1D2Norm * (D2.squared_norm() * D1.dot(D3)) / D1Norm5; const T H = VectorType::cross_product(D2, D3).norm() / D1Norm3; diff --git a/src/axom/primal/tests/primal_nurbs_curve.cpp b/src/axom/primal/tests/primal_nurbs_curve.cpp index e24010f48b..91f8218a30 100644 --- a/src/axom/primal/tests/primal_nurbs_curve.cpp +++ b/src/axom/primal/tests/primal_nurbs_curve.cpp @@ -8,11 +8,12 @@ * \brief This file tests primal's NURBS curve functionality */ -#include "gtest/gtest.h" - +#include "axom/config.hpp" #include "axom/slic.hpp" - #include "axom/primal/geometry/NURBSCurve.hpp" + +#include "gtest/gtest.h" + #include namespace primal = axom::primal; @@ -1123,10 +1124,172 @@ TEST(primal_nurbscurve, linear_segment_constructor) } #endif //------------------------------------------------------------------------------ + +/*! + * \brief Promote a 2D curve to a 3D curve defined using 2 vectors as the XY axes. + * + * \param input The input 2D curve. + * \param xvec The X axis of the 3D coordinate system. + * \param yvec The Y axis of the 3D coordinate system. + */ +template +NURBS3D promoteTo3D(const NURBS2D &input, const VectorType &xvec, const VectorType &yvec) +{ + SLIC_ASSERT(NURBS2D::PointType::DIMENSION == 2); + SLIC_ASSERT(NURBS3D::PointType::DIMENSION == 3); + using PointType = typename NURBS3D::PointType; + + NURBS3D output(input.getNumControlPoints(), input.getDegree()); + for(int i = 0; i < input.getNumControlPoints(); i++) + { + const auto &p2 = input[i]; + output[i] = PointType((xvec * p2[0] + yvec * p2[1]).data(), NURBS3D::PointType::DIMENSION); + } + output.setKnots(input.getKnots()); + + if(input.isRational()) + { + output.makeRational(); + for(int i = 0; i < input.getNumControlPoints(); i++) + { + output.setWeight(i, input.getWeight(i)); + } + } + + return output; +} + +template +NURBSCurveType makeCurve() +{ + using PointType = typename NURBSCurveType::PointType; + using T = typename PointType::CoordType; +#if 0 + PointType data[] = {PointType {1.4, 0.8, 0.5}, + PointType {0.6, 1.2, 1.0}, + PointType {0.8/*1.3*/, 1.6, 1.8}, + PointType {2.9, 2.4, 2.3}, + PointType {2., 3., 2.}, + PointType {1.2, 3.3, 1.4}};//3.2, 3.5, 3.0}}; + + T weights[] = {1.0, 1.0, 1.0, 1.0, 1.0, 1.0}; + + return NURBSCurveType(data, weights, 6, 3); +#endif + const int N = 10; + axom::Array data; + axom::Array weights; + data.resize(N); + weights.resize(N, T{1}); + T x0 = 0.; + T x1 = 2 * M_PI; + + for(int i = 0; i < N; i++) + { + const double t = static_cast(i) / static_cast(N - 1); + const T angle = x0 + t * (x1 - x0); + PointType &p = data[i]; + p[0] = angle; + p[1] = sin(angle); + } + return NURBSCurveType(data.data(), weights.data(), N, 3); +} + +/// Write a NURBSCurve to a Point3D file for VisIt. +template +void writeNURBS(const std::string &filename, const NURBSCurveType &curve, bool writeCurvature = false, int N = 100) +{ + FILE *f = fopen(filename.c_str(), "wt"); + fprintf(f, "X Y Z t\n"); + for(int i = 0; i < N; i++) + { + const double t = static_cast(i) / static_cast(N - 1); + const auto p = curve.evaluate(t); + double data = writeCurvature ? curve.curvature(t) : t; + if constexpr(NURBSCurveType::PointType::DIMENSION == 2) + { + fprintf(f, "%lg %lg 0. %lg\n", p[0], p[1], data); + } + else + { + fprintf(f, "%lg %lg %lg %lg\n", p[0], p[1], p[2], data); + } + } + fclose(f); +} + +template +void writeNURBSCurve(const std::string &filename, const NURBSCurveType &curve, int N = 100) +{ + using PointType = typename NURBSCurveType::PointType; + using T = typename PointType::CoordType; + + FILE *f = fopen(filename.c_str(), "wt"); + const char *axes[] = {"X", "Y", "Z"}; + for(int d = 0; d < NURBSCurveType::PointType::DIMENSION; d++) + { + fprintf(f, "# %s\n", axes[d]); + for(int i = 0; i < N; i++) + { + const double t = static_cast(i) / static_cast(N - 1); + const auto p = curve.evaluate(t); + fprintf(f, "%lg %lg\n", p[0], p[d]); + } + } + fprintf(f, "# dt\n"); + for(int i = 0; i < N; i++) + { + const T t = static_cast(i) / static_cast(N - 1); + const auto p = curve.evaluate(t); + const auto data = curve.dt(t); + fprintf(f, "%lg %lg\n", p[0], data[1] / data[0]); + } + fprintf(f, "# dtdt\n"); + for(int i = 0; i < N; i++) + { + const T t = static_cast(i) / static_cast(N - 1); + const auto p = curve.evaluate(t); + const auto dt = curve.dt(t); + const auto data = curve.dtdt(t); + T dtdt = dt[0] * data[1] / data[0]; + if(dtdt > 200) dtdt = 200; + if(dtdt < -200) dtdt = -200; + fprintf(f, "%lg %lg\n", p[0], dtdt); + } + fprintf(f, "# curvature\n"); + for(int i = 0; i < N; i++) + { + const double t = static_cast(i) / static_cast(N - 1); + const auto p = curve.evaluate(t); + double data = curve.curvature(t); + fprintf(f, "%lg %lg\n", p[0], data); + } + fprintf(f, "# Dcurvature\n"); + for(int i = 0; i < N; i++) + { + const double t = static_cast(i) / static_cast(N - 1); + axom::Array ders; + const auto p = curve.evaluate(t); + curve.curvatureDerivatives(t, 1, ders); + fprintf(f, "%lg %lg\n", p[0], ders[0]); + } + fprintf(f, "# D2curvature\n"); + for(int i = 0; i < N; i++) + { + const double t = static_cast(i) / static_cast(N - 1); + axom::Array ders; + const auto p = curve.evaluate(t); + curve.curvatureDerivatives(t, 2, ders); + fprintf(f, "%lg %lg\n", p[0], ders[1]); + } + fclose(f); +} + template -void curvature_test(T tol) +void curvature2d_test(T tol) { using NURBSCurve2D = axom::primal::NURBSCurve; + using VectorType = typename NURBSCurve2D::VectorType; const T cx = 0.; const T cy = 0.; @@ -1144,12 +1307,50 @@ void curvature_test(T tol) // The reciprocal of its radius (R), expressed as k = 1/R EXPECT_NEAR(c , 1. / R, tol); } + + const auto ecurve = makeCurve(); + writeNURBS("expCurve.3D", ecurve, true); + writeNURBSCurve("nurbs.curve", ecurve); +} + +template +void curvature3d_test(T tol) +{ + using NURBSCurve2D = axom::primal::NURBSCurve; + using NURBSCurve3D = axom::primal::NURBSCurve; + using Vector3D = axom::primal::Vector; + + const T R = 4.; + const T theta_0 = 0.; + const T theta_1 = 2. * M_PI; + const NURBSCurve2D curve2d = NURBSCurve2D::make_circular_arc_nurbs(theta_0, theta_1, 0., 0., R); + // Make a 3D version of the arc + const T angle = M_PI / 4.; + Vector3D uvec{cos(angle), 0., -sin(angle)}; + Vector3D vvec{0., 1., 0.}; + auto curve3d = promoteTo3D(curve2d, uvec, vvec); + + const int N = 100; + for(int i = 0; i < N; i++) + { + const T t = static_cast(i) / static_cast(N - 1); + const double c = curve3d.curvature(t); + + // The reciprocal of its radius (R), expressed as k = 1/R + EXPECT_NEAR(c , 1. / R, tol); + } +} + +TEST(primal_nurbscurve, curvature2d) +{ + curvature2d_test(1.e-7); + curvature2d_test(1.e-7); } -TEST(primal_nurbscurve, curvature) +TEST(primal_nurbscurve, curvature3d) { - curvature_test(1.e-7); - curvature_test(1.e-7); + curvature3d_test(1.e-7); + curvature3d_test(1.e-7); } //------------------------------------------------------------------------------ From b6661af9039bd928eb5ed1de3cb9f3beb2033cf1 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 8 Oct 2025 17:15:48 -0700 Subject: [PATCH 003/986] Temp debugging changes --- src/axom/primal/tests/primal_nurbs_curve.cpp | 66 +++++++++++--------- 1 file changed, 37 insertions(+), 29 deletions(-) diff --git a/src/axom/primal/tests/primal_nurbs_curve.cpp b/src/axom/primal/tests/primal_nurbs_curve.cpp index 91f8218a30..6d72147387 100644 --- a/src/axom/primal/tests/primal_nurbs_curve.cpp +++ b/src/axom/primal/tests/primal_nurbs_curve.cpp @@ -1164,26 +1164,23 @@ NURBSCurveType makeCurve() { using PointType = typename NURBSCurveType::PointType; using T = typename PointType::CoordType; -#if 0 - PointType data[] = {PointType {1.4, 0.8, 0.5}, - PointType {0.6, 1.2, 1.0}, - PointType {0.8/*1.3*/, 1.6, 1.8}, - PointType {2.9, 2.4, 2.3}, - PointType {2., 3., 2.}, - PointType {1.2, 3.3, 1.4}};//3.2, 3.5, 3.0}}; - - T weights[] = {1.0, 1.0, 1.0, 1.0, 1.0, 1.0}; - - return NURBSCurveType(data, weights, 6, 3); -#endif - const int N = 10; +#if 1 + const T cx = 0.; + const T cy = 0.; + const T R = 4.; + const T eps = 0.0975; + const T theta_0 = 0. + eps; + const T theta_1 = M_PI - eps; + return NURBSCurveType::make_circular_arc_nurbs(theta_0, theta_1, cx, cy, R); +#else + // A lot of samples are needed to closely fit the curve. + const int N = 128 + 1; axom::Array data; axom::Array weights; data.resize(N); weights.resize(N, T{1}); T x0 = 0.; T x1 = 2 * M_PI; - for(int i = 0; i < N; i++) { const double t = static_cast(i) / static_cast(N - 1); @@ -1193,6 +1190,7 @@ NURBSCurveType makeCurve() p[1] = sin(angle); } return NURBSCurveType(data.data(), weights.data(), N, 3); +#endif } /// Write a NURBSCurve to a Point3D file for VisIt. @@ -1251,11 +1249,18 @@ void writeNURBSCurve(const std::string &filename, const NURBSCurveType &curve, i const auto p = curve.evaluate(t); const auto dt = curve.dt(t); const auto data = curve.dtdt(t); +#if 0 T dtdt = dt[0] * data[1] / data[0]; - if(dtdt > 200) dtdt = 200; - if(dtdt < -200) dtdt = -200; +#else + T dtdt = data[1] / data[0]; +#endif + if(dtdt > 20) dtdt = 20; + if(dtdt < -20) dtdt = -20; fprintf(f, "%lg %lg\n", p[0], dtdt); +std::cout << "\t!!data=" << data << std::endl; +std::cout << "\t!!dtdt=" << dtdt << std::endl; } +#if 0 fprintf(f, "# curvature\n"); for(int i = 0; i < N; i++) { @@ -1282,6 +1287,7 @@ void writeNURBSCurve(const std::string &filename, const NURBSCurveType &curve, i curve.curvatureDerivatives(t, 2, ders); fprintf(f, "%lg %lg\n", p[0], ders[1]); } +#endif fclose(f); } @@ -1289,8 +1295,7 @@ template void curvature2d_test(T tol) { using NURBSCurve2D = axom::primal::NURBSCurve; - using VectorType = typename NURBSCurve2D::VectorType; - +#if 0 const T cx = 0.; const T cy = 0.; const T R = 4.; @@ -1307,9 +1312,9 @@ void curvature2d_test(T tol) // The reciprocal of its radius (R), expressed as k = 1/R EXPECT_NEAR(c , 1. / R, tol); } - +#endif const auto ecurve = makeCurve(); - writeNURBS("expCurve.3D", ecurve, true); + writeNURBS("nurbs.3D", ecurve, true); writeNURBSCurve("nurbs.curve", ecurve); } @@ -1320,14 +1325,17 @@ void curvature3d_test(T tol) using NURBSCurve3D = axom::primal::NURBSCurve; using Vector3D = axom::primal::Vector; + const T cx = 0.; + const T cy = 0.; const T R = 4.; const T theta_0 = 0.; const T theta_1 = 2. * M_PI; - const NURBSCurve2D curve2d = NURBSCurve2D::make_circular_arc_nurbs(theta_0, theta_1, 0., 0., R); + const NURBSCurve2D curve2d = NURBSCurve2D::make_circular_arc_nurbs(theta_0, theta_1, cx, cy, R); // Make a 3D version of the arc - const T angle = M_PI / 4.; - Vector3D uvec{cos(angle), 0., -sin(angle)}; - Vector3D vvec{0., 1., 0.}; + const T a0 = M_PI / 4.; + const T a1 = a0 + M_PI / 2.; + Vector3D uvec{cos(a0), 0., -sin(a0)}; + Vector3D vvec{cos(a1), 0., -sin(a1)}; auto curve3d = promoteTo3D(curve2d, uvec, vvec); const int N = 100; @@ -1344,15 +1352,15 @@ void curvature3d_test(T tol) TEST(primal_nurbscurve, curvature2d) { curvature2d_test(1.e-7); - curvature2d_test(1.e-7); +// curvature2d_test(1.e-7); } - +#if 0 TEST(primal_nurbscurve, curvature3d) { - curvature3d_test(1.e-7); - curvature3d_test(1.e-7); + curvature3d_test(1.5e-7); + curvature3d_test(1.5e-7); } - +#endif //------------------------------------------------------------------------------ int main(int argc, char* argv[]) { From eb047cdc1d73e63d8edd54f85dad62bbf119afa8 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 8 Oct 2025 17:16:05 -0700 Subject: [PATCH 004/986] Approaching something more workable for 2nd derivative. --- src/axom/primal/geometry/NURBSCurve.hpp | 78 ++++++++++++++++++++----- 1 file changed, 65 insertions(+), 13 deletions(-) diff --git a/src/axom/primal/geometry/NURBSCurve.hpp b/src/axom/primal/geometry/NURBSCurve.hpp index b59ee3b6e1..12268d971d 100644 --- a/src/axom/primal/geometry/NURBSCurve.hpp +++ b/src/axom/primal/geometry/NURBSCurve.hpp @@ -1059,6 +1059,7 @@ class NURBSCurve void evaluateDerivatives(T t, int d, PointType& eval, axom::Array& ders) const { SLIC_ASSERT(m_knotvec.isValidParameter(t)); + SLIC_ASSERT(d >= 1); t = axom::utilities::clampVal(t, getMinKnot(), getMaxKnot()); const int p = m_knotvec.getDegree(); @@ -1069,11 +1070,17 @@ class NURBSCurve int du = std::min(d, p); const auto span = m_knotvec.findSpan(t); const auto N_evals = m_knotvec.derivativeBasisFunctionsBySpan(span, t, du); +std::cout << "dtdt(" << t << "): d=" << d << ", du=" << du << ", span=" << span << ", N_evals={"; +for(const auto &val : N_evals) +{ + std::cout << val << ", "; +} +std::cout << "}\n"; - // Store w(u) in Awders[NDIMS][0], w'(u) in Awders[NDIMS][1], ... + // Store w(u) in Awders[0][NDIMS], w'(u) in Awders[1][NDIMS], ... axom::Array> Awders(d + 1); - // Compute the homogenous point and its d derivatives + // Compute the homogeneous point and its d derivatives for(int k = 0; k <= du; k++) { Point Pw(0.0); @@ -1097,29 +1104,75 @@ class NURBSCurve // and Awders[0][NDIMS] is w(u). // Zero out the points - for(int i = 0; i < NDIMS; ++i) + for(int k = 0; k < d; k++) { - eval[i] = 0.0; - for(int k = 0; k < d; k++) + for(int j = 0; j < NDIMS; ++j) { - ders[k][i] = 0.0; + ders[k][j] = 0.0; } } // Separate k = 0 case - Point v = Awders[0]; - for(int i = 0; i < NDIMS; ++i) + const T w = Awders[0][NDIMS]; + for(int j = 0; j < NDIMS; ++j) { - eval[i] = v[i] / Awders[0][NDIMS]; + eval[j] = Awders[0][j] / w; } // Separate k = 1 case - v = Awders[1]; for(int j = 0; j < NDIMS; ++j) { - ders[0][j] = (v[j] - Awders[1][NDIMS] * eval[j]) / Awders[0][NDIMS]; + // C'(t) = (A'(t) - w'(t) * C(t)) / w(t) + ders[0][j] = (Awders[1][j] - Awders[1][NDIMS] * eval[j]) / w; } +#if 1 +#pragma message "Got my newer version." + for(int k = 2; k <= d; k++) + { +std::cout << "\tk=" << k << ", eval=" << eval << ", Awders[" << k << "]=" << Awders[k] << std::endl; + // Eq. 4.8 on page 125. + // + // k + // (k) (k) ---- (i) (k-i) + // C (t) = A (t) - \ ( k ) w (t)C (t) + // / ( i ) + // ---- + // i=1 + // ------------------------------------ + // w(t) + // + // (shown here for 2nd derivative) + // C''(t) = (A''(t) - 2w'(t)C'(t) - w''(t)C(t)) / w(t) + // + // + // w(t) = Awders[0][NDIMS] + // w'(t) = Awders[1][NDIMS] + // w''(t) = Awders[2][NDIMS] + // ... + // A(t) = Awders[0] + // A'(t) = Awders[1] + // A''(t) = Awders[2] + // ... + // C(t) = eval + // C'(t) = ders[0] + // C''(t) = ders[1] + // ... + auto Ck = VectorType(Awders[k].data(), NDIMS); + for(int i = 1; i <= k; i++) + { + const auto bin = axom::utilities::binomialCoefficient(k, i); + const auto k_i = k - i; + const auto wi = Awders[i][NDIMS]; + for(int j = 0; j < NDIMS; ++j) + { + Ck[j] -= bin * wi * ders[k_i][j]; + } + } +std::cout << "\tk=" << k << ", v=" << v << std::endl; + ders[k - 1] = Ck / w; + } +#else // Recursive formula for k >= 2 for(int k = 2; k <= d; k++) { @@ -1128,7 +1181,6 @@ class NURBSCurve { v[j] = v[j] - Awders[k][NDIMS] * eval[j]; } - for(int i = 1; i < k; i++) { auto bin = axom::utilities::binomialCoefficient(k, i); @@ -1143,8 +1195,8 @@ class NURBSCurve ders[k - 1][j] = v[j] / Awders[0][NDIMS]; } } +#endif } - ///@} ///@{ From 356d790d781d515c6cd6e9f47f7a0be6f4a54a72 Mon Sep 17 00:00:00 2001 From: Brian Manh Hien Han Date: Tue, 17 Mar 2026 15:43:16 -0700 Subject: [PATCH 005/986] Reduce code duplication across slic macros --- src/axom/slic/interface/slic_macros.hpp | 172 +++--------------------- 1 file changed, 21 insertions(+), 151 deletions(-) diff --git a/src/axom/slic/interface/slic_macros.hpp b/src/axom/slic/interface/slic_macros.hpp index 7975c95226..a084d6f467 100644 --- a/src/axom/slic/interface/slic_macros.hpp +++ b/src/axom/slic/interface/slic_macros.hpp @@ -42,17 +42,7 @@ * \endcode * */ -#define SLIC_ERROR(msg) \ - do \ - { \ - std::ostringstream __oss; \ - __oss << msg; \ - axom::slic::logErrorMessage(__oss.str(), __FILE__, __LINE__); \ - if(axom::slic::isAbortOnErrorsEnabled()) \ - { \ - axom::slic::abort(); \ - } \ - } while(axom::slic::detail::false_value) +#define SLIC_ERROR(msg) SLIC_ERROR_IF(true, msg) /*! * \def SLIC_ERROR_IF( EXP, msg ) @@ -70,20 +60,17 @@ * \endcode * */ -#define SLIC_ERROR_IF(EXP, msg) \ - do \ - { \ - if(EXP) \ - { \ - std::ostringstream __oss; \ - __oss << msg; \ - axom::slic::logErrorMessage(__oss.str(), __FILE__, __LINE__); \ - if(axom::slic::isAbortOnErrorsEnabled()) \ - { \ - axom::slic::abort(); \ - } \ - } \ - } while(axom::slic::detail::false_value) +#define SLIC_ERROR_IF(EXP, msg) \ + if(EXP) \ + { \ + std::ostringstream __oss; \ + __oss << msg; \ + axom::slic::logErrorMessage(__oss.str(), __FILE__, __LINE__); \ + if(axom::slic::isAbortOnErrorsEnabled()) \ + { \ + axom::slic::abort(); \ + } \ + } /*! * \def SLIC_ERROR_ROOT( msg ) @@ -103,20 +90,7 @@ * \endcode * */ -#define SLIC_ERROR_ROOT(msg) \ - do \ - { \ - if(axom::slic::isRoot()) \ - { \ - std::ostringstream __oss; \ - __oss << msg; \ - axom::slic::logErrorMessage(__oss.str(), __FILE__, __LINE__); \ - if(axom::slic::isAbortOnErrorsEnabled()) \ - { \ - axom::slic::abort(); \ - } \ - } \ - } while(axom::slic::detail::false_value) +#define SLIC_ERROR_ROOT(msg) SLIC_ERROR_IF(axom::slic::isRoot(), msg) /*! * \def SLIC_ERROR_ROOT_IF( EXP, msg ) @@ -136,23 +110,7 @@ * \endcode * */ -#define SLIC_ERROR_ROOT_IF(EXP, msg) \ - do \ - { \ - if(EXP) \ - { \ - if(axom::slic::isRoot()) \ - { \ - std::ostringstream __oss; \ - __oss << msg; \ - axom::slic::logErrorMessage(__oss.str(), __FILE__, __LINE__); \ - if(axom::slic::isAbortOnErrorsEnabled()) \ - { \ - axom::slic::abort(); \ - } \ - } \ - } \ - } while(axom::slic::detail::false_value) +#define SLIC_ERROR_ROOT_IF(EXP, msg) SLIC_ERROR_IF((EXP) && (axom::slic::isRoot()), msg) ///@} @@ -187,17 +145,7 @@ * \endcode * */ -#define SLIC_WARNING(msg) \ - do \ - { \ - std::ostringstream __oss; \ - __oss << msg; \ - axom::slic::logWarningMessage(__oss.str(), __FILE__, __LINE__); \ - if(axom::slic::isAbortOnWarningsEnabled()) \ - { \ - axom::slic::abort(); \ - } \ - } while(axom::slic::detail::false_value) +#define SLIC_WARNING(msg) SLIC_WARNING_IF(true, msg) /*! * \def SLIC_WARNING_IF( EXP, msg ) @@ -247,20 +195,7 @@ * \endcode * */ -#define SLIC_WARNING_ROOT(msg) \ - do \ - { \ - if(axom::slic::isRoot()) \ - { \ - std::ostringstream __oss; \ - __oss << msg; \ - axom::slic::logWarningMessage(__oss.str(), __FILE__, __LINE__); \ - if(axom::slic::isAbortOnWarningsEnabled()) \ - { \ - axom::slic::abort(); \ - } \ - } \ - } while(axom::slic::detail::false_value) +#define SLIC_WARNING_ROOT(msg) SLIC_WARNING_IF(axom::slic::isRoot(), msg) /*! * \def SLIC_WARNING_ROOT_IF( EXP, msg ) @@ -280,23 +215,7 @@ * \endcode * */ -#define SLIC_WARNING_ROOT_IF(EXP, msg) \ - do \ - { \ - if(EXP) \ - { \ - if(axom::slic::isRoot()) \ - { \ - std::ostringstream __oss; \ - __oss << msg; \ - axom::slic::logWarningMessage(__oss.str(), __FILE__, __LINE__); \ - if(axom::slic::isAbortOnWarningsEnabled()) \ - { \ - axom::slic::abort(); \ - } \ - } \ - } \ - } while(axom::slic::detail::false_value) +#define SLIC_WARNING_ROOT_IF(EXP, msg) SLIC_WARNING_IF((EXP) && (axom::slic::isRoot()), msg) ///@} @@ -334,20 +253,7 @@ * \endcode * */ - #define SLIC_ASSERT(EXP) \ - do \ - { \ - if(!(EXP)) \ - { \ - std::ostringstream __oss; \ - __oss << "Failed Assert: " << #EXP; \ - axom::slic::logErrorMessage(__oss.str(), __FILE__, __LINE__); \ - if(axom::slic::isAbortOnErrorsEnabled()) \ - { \ - axom::slic::abort(); \ - } \ - } \ - } while(axom::slic::detail::false_value) + #define SLIC_ASSERT(EXP) SLIC_ASSERT_MSG(EXP, "") /*! * \def SLIC_ASSERT_MSG( EXP, msg ) @@ -422,31 +328,7 @@ * \endcode * */ - #define SLIC_CHECK(EXP) \ - do \ - { \ - if(!(EXP)) \ - { \ - std::ostringstream __oss; \ - __oss << "Failed Check: " << #EXP; \ - if(axom::slic::debug::checksAreErrors) \ - { \ - axom::slic::logErrorMessage(__oss.str(), __FILE__, __LINE__); \ - if(axom::slic::isAbortOnErrorsEnabled()) \ - { \ - axom::slic::abort(); \ - } \ - } \ - else \ - { \ - axom::slic::logWarningMessage(__oss.str(), __FILE__, __LINE__); \ - if(axom::slic::isAbortOnWarningsEnabled()) \ - { \ - axom::slic::abort(); \ - } \ - } \ - } \ - } while(axom::slic::detail::false_value) + #define SLIC_CHECK(EXP) SLIC_CHECK_MSG(EXP, "") /*! * \def SLIC_CHECK_MSG( EXP, msg ) @@ -524,13 +406,7 @@ * \endcode * */ -#define SLIC_INFO(msg) \ - do \ - { \ - std::ostringstream __oss; \ - __oss << msg; \ - axom::slic::logMessage(axom::slic::message::Info, __oss.str(), __FILE__, __LINE__); \ - } while(axom::slic::detail::false_value) +#define SLIC_INFO(msg) SLIC_INFO_IF(true, msg) /*! * \def SLIC_INFO_TAGGED( msg, tag ) @@ -630,13 +506,7 @@ * \endcode * */ - #define SLIC_DEBUG(msg) \ - do \ - { \ - std::ostringstream __oss; \ - __oss << msg; \ - axom::slic::logMessage(axom::slic::message::Debug, __oss.str(), __FILE__, __LINE__); \ - } while(axom::slic::detail::false_value) + #define SLIC_DEBUG(msg) SLIC_DEBUG_IF(true, msg) /*! * \def SLIC_DEBUG_IF( EXP, msg ) From 695690d7c78bd71bc334cd50a9bb3459fdaf79ff Mon Sep 17 00:00:00 2001 From: Brian Manh Hien Han Date: Wed, 18 Mar 2026 10:13:37 -0700 Subject: [PATCH 006/986] Reduce code duplication for slic_macros test --- src/axom/slic/tests/slic_macros.cpp | 167 +++++++++------------------- 1 file changed, 50 insertions(+), 117 deletions(-) diff --git a/src/axom/slic/tests/slic_macros.cpp b/src/axom/slic/tests/slic_macros.cpp index 747390f538..6defe449d8 100644 --- a/src/axom/slic/tests/slic_macros.cpp +++ b/src/axom/slic/tests/slic_macros.cpp @@ -118,6 +118,27 @@ void check_tag(const std::string& msg, const std::string& expected_tag) EXPECT_EQ(tag, expected_tag); } +//------------------------------------------------------------------------------ +void check_level_msg_line_file(const std::string& level, const std::string& message, int expected_line) +{ + EXPECT_FALSE(slic::internal::is_stream_empty()); + const std::string str = slic::internal::test_stream.str(); + check_level(str, level); + check_msg(str, message); + check_line(str, expected_line); + check_file(str); + slic::internal::clear(); +} + +// Convenience test macro that checks slic macro has logged a message +#define EXPECT_SLIC_LOG(macro_call, level, message) \ + do \ + { \ + const int expected_line = __LINE__; \ + macro_call; \ + check_level_msg_line_file(level, message, expected_line); \ + } while(false) + } // end anonymous namespace //------------------------------------------------------------------------------ @@ -125,29 +146,15 @@ void check_tag(const std::string& msg, const std::string& expected_tag) //------------------------------------------------------------------------------ TEST(slic_macros, test_error_macros) { - int expected_line_number; - EXPECT_TRUE(slic::internal::is_stream_empty()); - SLIC_ERROR("test error message"); - expected_line_number = __LINE__ - 1; - EXPECT_FALSE(slic::internal::is_stream_empty()); - check_level(slic::internal::test_stream.str(), "ERROR"); - check_msg(slic::internal::test_stream.str(), "test error message"); - check_file(slic::internal::test_stream.str()); - check_line(slic::internal::test_stream.str(), expected_line_number); - slic::internal::clear(); + EXPECT_SLIC_LOG(SLIC_ERROR("test error message"), "ERROR", "test error message"); SLIC_ERROR_IF(false, "this message should not be logged!"); EXPECT_TRUE(slic::internal::is_stream_empty()); - SLIC_ERROR_IF(true, "this message is logged!"); - expected_line_number = __LINE__ - 1; - EXPECT_FALSE(slic::internal::is_stream_empty()); - check_level(slic::internal::test_stream.str(), "ERROR"); - check_msg(slic::internal::test_stream.str(), "this message is logged!"); - check_file(slic::internal::test_stream.str()); - check_line(slic::internal::test_stream.str(), expected_line_number); - slic::internal::clear(); + EXPECT_SLIC_LOG(SLIC_ERROR_IF(true, "this message is logged!"), + "ERROR", + "this message is logged!"); // Check selective filtering based on root == false axom::slic::setIsRoot(false); @@ -157,13 +164,9 @@ TEST(slic_macros, test_error_macros) // Check selective filter based on root == true axom::slic::setIsRoot(true); SLIC_ERROR_ROOT_IF(true, "this message is logged!"); - expected_line_number = __LINE__ - 1; - EXPECT_FALSE(slic::internal::is_stream_empty()); - check_level(slic::internal::test_stream.str(), "ERROR"); - check_msg(slic::internal::test_stream.str(), "this message is logged!"); - check_file(slic::internal::test_stream.str()); - check_line(slic::internal::test_stream.str(), expected_line_number); - slic::internal::clear(); + EXPECT_SLIC_LOG(SLIC_ERROR_ROOT_IF(true, "this message is logged!"), + "ERROR", + "this message is logged!"); // is root, but conditional is false -> no message axom::slic::setIsRoot(true); @@ -179,29 +182,15 @@ TEST(slic_macros, test_error_macros) //------------------------------------------------------------------------------ TEST(slic_macros, test_warning_macros) { - int expected_line_number; - EXPECT_TRUE(slic::internal::is_stream_empty()); - SLIC_WARNING("test warning message"); - expected_line_number = __LINE__ - 1; - EXPECT_FALSE(slic::internal::is_stream_empty()); - check_level(slic::internal::test_stream.str(), "WARNING"); - check_msg(slic::internal::test_stream.str(), "test warning message"); - check_file(slic::internal::test_stream.str()); - check_line(slic::internal::test_stream.str(), expected_line_number); - slic::internal::clear(); + EXPECT_SLIC_LOG(SLIC_WARNING("test warning message"), "WARNING", "test warning message"); SLIC_WARNING_IF(false, "this message should not be logged!"); EXPECT_TRUE(slic::internal::is_stream_empty()); - SLIC_WARNING_IF(true, "this message is logged!"); - expected_line_number = __LINE__ - 1; - EXPECT_FALSE(slic::internal::is_stream_empty()); - check_level(slic::internal::test_stream.str(), "WARNING"); - check_msg(slic::internal::test_stream.str(), "this message is logged!"); - check_file(slic::internal::test_stream.str()); - check_line(slic::internal::test_stream.str(), expected_line_number); - slic::internal::clear(); + EXPECT_SLIC_LOG(SLIC_WARNING_IF(true, "this message is logged!"), + "WARNING", + "this message is logged!"); // Check selective filtering based on root == false axom::slic::setIsRoot(false); @@ -210,14 +199,9 @@ TEST(slic_macros, test_warning_macros) // Check selective filter based on root == true axom::slic::setIsRoot(true); - SLIC_WARNING_ROOT_IF(true, "this message is logged!"); - expected_line_number = __LINE__ - 1; - EXPECT_FALSE(slic::internal::is_stream_empty()); - check_level(slic::internal::test_stream.str(), "WARNING"); - check_msg(slic::internal::test_stream.str(), "this message is logged!"); - check_file(slic::internal::test_stream.str()); - check_line(slic::internal::test_stream.str(), expected_line_number); - slic::internal::clear(); + EXPECT_SLIC_LOG(SLIC_WARNING_ROOT_IF(true, "this message is logged!"), + "WARNING", + "this message is logged!"); // is root, but conditional is false -> no message axom::slic::setIsRoot(true); @@ -233,29 +217,13 @@ TEST(slic_macros, test_warning_macros) //------------------------------------------------------------------------------ TEST(slic_macros, test_info_macros) { - int expected_line_number; - EXPECT_TRUE(slic::internal::is_stream_empty()); - SLIC_INFO("test info message"); - expected_line_number = __LINE__ - 1; - EXPECT_FALSE(slic::internal::is_stream_empty()); - check_level(slic::internal::test_stream.str(), "INFO"); - check_msg(slic::internal::test_stream.str(), "test info message"); - check_file(slic::internal::test_stream.str()); - check_line(slic::internal::test_stream.str(), expected_line_number); - slic::internal::clear(); + EXPECT_SLIC_LOG(SLIC_INFO("test info message"), "INFO", "test info message"); SLIC_INFO_IF(false, "this message should not be logged!"); EXPECT_TRUE(slic::internal::is_stream_empty()); - SLIC_INFO_IF(true, "this message is logged!"); - expected_line_number = __LINE__ - 1; - EXPECT_FALSE(slic::internal::is_stream_empty()); - check_level(slic::internal::test_stream.str(), "INFO"); - check_msg(slic::internal::test_stream.str(), "this message is logged!"); - check_file(slic::internal::test_stream.str()); - check_line(slic::internal::test_stream.str(), expected_line_number); - slic::internal::clear(); + EXPECT_SLIC_LOG(SLIC_INFO_IF(true, "this message is logged!"), "INFO", "this message is logged!"); // is root, but conditional is false -> no message axom::slic::setIsRoot(true); @@ -277,12 +245,7 @@ TEST(slic_macros, test_debug_macros) SLIC_DEBUG("test debug message"); expected_line_number = __LINE__ - 1; #ifdef AXOM_DEBUG - EXPECT_FALSE(slic::internal::is_stream_empty()); - check_level(slic::internal::test_stream.str(), "DEBUG"); - check_msg(slic::internal::test_stream.str(), "test debug message"); - check_file(slic::internal::test_stream.str()); - check_line(slic::internal::test_stream.str(), expected_line_number); - slic::internal::clear(); + check_level_msg_line_file("DEBUG", "test debug message", expected_line_number); #else // SLIC_DEBUG macros only log messages when AXOM_DEBUG is defined EXPECT_TRUE(slic::internal::is_stream_empty()); @@ -294,12 +257,7 @@ TEST(slic_macros, test_debug_macros) SLIC_DEBUG_IF(true, "this message is logged!"); expected_line_number = __LINE__ - 1; #ifdef AXOM_DEBUG - EXPECT_FALSE(slic::internal::is_stream_empty()); - check_level(slic::internal::test_stream.str(), "DEBUG"); - check_msg(slic::internal::test_stream.str(), "this message is logged!"); - check_file(slic::internal::test_stream.str()); - check_line(slic::internal::test_stream.str(), expected_line_number); - slic::internal::clear(); + check_level_msg_line_file("DEBUG", "this message is logged!", expected_line_number); #else // SLIC_DEBUG macros only log messages when AXOM_DEBUG is defined EXPECT_TRUE(slic::internal::is_stream_empty()); @@ -315,12 +273,7 @@ TEST(slic_macros, test_debug_macros) SLIC_DEBUG_ROOT_IF(true, "this message is logged!"); expected_line_number = __LINE__ - 1; #ifdef AXOM_DEBUG - EXPECT_FALSE(slic::internal::is_stream_empty()); - check_level(slic::internal::test_stream.str(), "DEBUG"); - check_msg(slic::internal::test_stream.str(), "this message is logged!"); - check_file(slic::internal::test_stream.str()); - check_line(slic::internal::test_stream.str(), expected_line_number); - slic::internal::clear(); + check_level_msg_line_file("DEBUG", "this message is logged!", expected_line_number); #else // SLIC_DEBUG macros only log messages when AXOM_DEBUG is defined EXPECT_TRUE(slic::internal::is_stream_empty()); @@ -348,12 +301,7 @@ TEST(slic_macros, test_assert_macros) SLIC_ASSERT(val < 0); expected_line_number = __LINE__ - 1; #if defined(AXOM_DEBUG) && !defined(AXOM_DEVICE_CODE) - EXPECT_FALSE(slic::internal::is_stream_empty()); - check_level(slic::internal::test_stream.str(), "ERROR"); - check_msg(slic::internal::test_stream.str(), "Failed Assert: val < 0"); - check_file(slic::internal::test_stream.str()); - check_line(slic::internal::test_stream.str(), expected_line_number); - slic::internal::clear(); + check_level_msg_line_file("ERROR", "Failed Assert: val < 0", expected_line_number); #else // SLIC_ASSERT macros only log messages when AXOM_DEBUG is defined AXOM_UNUSED_VAR(val); @@ -367,12 +315,9 @@ TEST(slic_macros, test_assert_macros) SLIC_ASSERT_MSG(val < 0, "val should be negative!"); expected_line_number = __LINE__ - 1; #if defined(AXOM_DEBUG) && !defined(AXOM_DEVICE_CODE) - EXPECT_FALSE(slic::internal::is_stream_empty()); - check_level(slic::internal::test_stream.str(), "ERROR"); - check_msg(slic::internal::test_stream.str(), "Failed Assert: val < 0\nval should be negative!"); - check_file(slic::internal::test_stream.str()); - check_line(slic::internal::test_stream.str(), expected_line_number); - slic::internal::clear(); + check_level_msg_line_file("ERROR", + "Failed Assert: val < 0\nval should be negative!", + expected_line_number); #else // SLIC_ASSERT macros only log messages when AXOM_DEBUG is defined AXOM_UNUSED_VAR(val); @@ -391,12 +336,7 @@ TEST(slic_macros, test_check_macros) SLIC_CHECK(val < 0); expected_line_number = __LINE__ - 1; #if defined(AXOM_DEBUG) && !defined(AXOM_DEVICE_CODE) - EXPECT_FALSE(slic::internal::is_stream_empty()); - check_level(slic::internal::test_stream.str(), "WARNING"); - check_msg(slic::internal::test_stream.str(), "Failed Check: val < 0"); - check_file(slic::internal::test_stream.str()); - check_line(slic::internal::test_stream.str(), expected_line_number); - slic::internal::clear(); + check_level_msg_line_file("WARNING", "Failed Check: val < 0", expected_line_number); #else // SLIC_CHECK macros only log messages when AXOM_DEBUG is defined AXOM_UNUSED_VAR(val); @@ -410,12 +350,9 @@ TEST(slic_macros, test_check_macros) SLIC_CHECK_MSG(val < 0, "val should be negative!"); expected_line_number = __LINE__ - 1; #if defined(AXOM_DEBUG) && !defined(AXOM_DEVICE_CODE) - EXPECT_FALSE(slic::internal::is_stream_empty()); - check_level(slic::internal::test_stream.str(), "WARNING"); - check_msg(slic::internal::test_stream.str(), "Failed Check: val < 0\nval should be negative!"); - check_file(slic::internal::test_stream.str()); - check_line(slic::internal::test_stream.str(), expected_line_number); - slic::internal::clear(); + check_level_msg_line_file("WARNING", + "Failed Check: val < 0\nval should be negative!", + expected_line_number); #else // SLIC_CHECK macros only log messages when AXOM_DEBUG is defined AXOM_UNUSED_VAR(val); @@ -432,13 +369,9 @@ TEST(slic_macros, test_tagged_macros) EXPECT_TRUE(slic::internal::is_stream_empty()); SLIC_INFO_TAGGED("test tagged info message", "myTag"); expected_line_number = __LINE__ - 1; - EXPECT_FALSE(slic::internal::is_stream_empty()); - check_level(slic::internal::test_stream.str(), "INFO"); - check_msg(slic::internal::test_stream.str(), "test tagged info message"); - check_file(slic::internal::test_stream.str()); - check_line(slic::internal::test_stream.str(), expected_line_number); + check_tag(slic::internal::test_stream.str(), "myTag"); - slic::internal::clear(); + check_level_msg_line_file("INFO", "test tagged info message", expected_line_number); SLIC_INFO_TAGGED("this message should not be logged (no tag given)!", ""); EXPECT_TRUE(slic::internal::is_stream_empty()); From 6a02d6875e663749780da6b308e10e1bc236d390 Mon Sep 17 00:00:00 2001 From: Brian Manh Hien Han Date: Wed, 18 Mar 2026 11:59:49 -0700 Subject: [PATCH 007/986] Add SLIC_WARNING_*_ONCE macros --- src/axom/slic/interface/slic_macros.hpp | 97 +++++++++++++++++++++++++ src/axom/slic/tests/slic_macros.cpp | 60 +++++++++++++++ 2 files changed, 157 insertions(+) diff --git a/src/axom/slic/interface/slic_macros.hpp b/src/axom/slic/interface/slic_macros.hpp index a084d6f467..6fc5532256 100644 --- a/src/axom/slic/interface/slic_macros.hpp +++ b/src/axom/slic/interface/slic_macros.hpp @@ -147,6 +147,23 @@ */ #define SLIC_WARNING(msg) SLIC_WARNING_IF(true, msg) +/*! + * \def SLIC_WARNING_ONCE( msg ) + * \brief Logs a warning message only once per call site. + * + * \param [in] msg user-supplied message + * + * \note The SLIC_WARNING_ONCE macro is always active. + * \note Aborts the application when `slic::enableAbortOnWarning()` + * + * Usage: + * \code + * SLIC_WARNING_ONCE( "my_val should always be positive" ); + * \endcode + * + */ +#define SLIC_WARNING_ONCE(msg) SLIC_DETAIL_LOG_IF_ONCE(SLIC_WARNING_IF, true, msg) + /*! * \def SLIC_WARNING_IF( EXP, msg ) * \brief Logs a warning iff EXP is true @@ -178,6 +195,24 @@ } \ } while(axom::slic::detail::false_value) +/*! + * \def SLIC_WARNING_IF_ONCE( EXP, msg ) + * \brief Logs a warning iff EXP is true, and only once per call site. + * + * \param [in] EXP user-supplied boolean expression. + * \param [in] msg user-supplied message. + * + * \note The SLIC_WARNING_IF_ONCE macro is always active. + * \note Aborts the application when `slic::enableAbortOnWarning()` + * + * Usage: + * \code + * SLIC_WARNING_IF_ONCE( (val < 0), "my_val should always be positive" ); + * \endcode + * + */ +#define SLIC_WARNING_IF_ONCE(EXP, msg) SLIC_DETAIL_LOG_IF_ONCE(SLIC_WARNING_IF, EXP, msg) + /*! * \def SLIC_WARNING_ROOT( msg ) * \brief Macro that logs given warning message only on root. @@ -197,6 +232,26 @@ */ #define SLIC_WARNING_ROOT(msg) SLIC_WARNING_IF(axom::slic::isRoot(), msg) +/*! + * \def SLIC_WARNING_ROOT_ONCE( msg ) + * \brief Macro that logs given warning message only on root, and only once per call site. + * + * \param [in] msg user-supplied message. + * + * \note The SLIC_WARNING_ROOT_ONCE macro is always active. + * \note By default, all ranks are considered to be root. + * Must call `axom::slic::initialize(is_root={true|false})` + * or set via `axom::slic::setIsRoot({true|false})` to filter based on root. + * + * Usage: + * \code + * SLIC_WARNING_ROOT_ONCE( "A warning has occurred!" ); + * \endcode + * + */ +#define SLIC_WARNING_ROOT_ONCE(msg) \ + SLIC_DETAIL_LOG_IF_ONCE(SLIC_WARNING_IF, axom::slic::isRoot(), msg) + /*! * \def SLIC_WARNING_ROOT_IF( EXP, msg ) * \brief Macro that logs given warning message only on root iff EXP is true. @@ -217,6 +272,28 @@ */ #define SLIC_WARNING_ROOT_IF(EXP, msg) SLIC_WARNING_IF((EXP) && (axom::slic::isRoot()), msg) +/*! + * \def SLIC_WARNING_ROOT_IF_ONCE( EXP, msg ) + * \brief Macro that logs given warning message only on root iff EXP is true, + * and only once per call site. + * + * \param [in] EXP user-supplied boolean expression. + * \param [in] msg user-supplied message. + * + * \note The SLIC_WARNING_ROOT_IF_ONCE macro is always active. + * \note By default, all ranks are considered to be root. + * Must call `axom::slic::initialize(is_root={true|false})` + * or set via `axom::slic::setIsRoot({true|false})` to filter based on root. + * + * Usage: + * \code + * SLIC_WARNING_ROOT_IF_ONCE( (val < 0), "my_val should always be positive" ); + * \endcode + * + */ +#define SLIC_WARNING_ROOT_IF_ONCE(EXP, msg) \ + SLIC_DETAIL_LOG_IF_ONCE(SLIC_WARNING_ROOT_IF, (EXP) && (axom::slic::isRoot()), msg) + ///@} // Use complete debug macros when not on device @@ -602,6 +679,26 @@ #endif +/*! + * \brief Helper macro to define SLIC_*_ONCE macros that log only the first + * time they are used. + * + * \param [in] macro the macro to call. + * \param [in] EXP user-supplied boolean expression. + * \param [in] msg user-supplied message. + * + */ +#define SLIC_DETAIL_LOG_IF_ONCE(macro, EXP, msg) \ + do \ + { \ + static bool once = true; \ + if(once) \ + { \ + macro(EXP, msg); \ + once = false; \ + } \ + } while(axom::slic::detail::false_value) + namespace axom { namespace slic diff --git a/src/axom/slic/tests/slic_macros.cpp b/src/axom/slic/tests/slic_macros.cpp index 6defe449d8..b2e2c54d7d 100644 --- a/src/axom/slic/tests/slic_macros.cpp +++ b/src/axom/slic/tests/slic_macros.cpp @@ -119,6 +119,22 @@ void check_tag(const std::string& msg, const std::string& expected_tag) } //------------------------------------------------------------------------------ +int check_count(const std::string& msg, const std::string& expected_level) +{ + EXPECT_FALSE(msg.empty()); + + int count = 0; + for(size_t pos = msg.find(expected_level); pos != std::string::npos; + pos = msg.find(expected_level, pos + expected_level.size())) + { + ++count; + } + return count; +} + +//------------------------------------------------------------------------------ +// Checks level, message, line number, and file location. +// Clears stream when finished. void check_level_msg_line_file(const std::string& level, const std::string& message, int expected_line) { EXPECT_FALSE(slic::internal::is_stream_empty()); @@ -139,6 +155,19 @@ void check_level_msg_line_file(const std::string& level, const std::string& mess check_level_msg_line_file(level, message, expected_line); \ } while(false) +// Convenience test macro that checks SLIC_*_ONCE macro has logged one message +#define EXPECT_SLIC_ONCE(macro_call, level, message) \ + do \ + { \ + const int expected_line = __LINE__; \ + for(int i = 0; i < 2; i++) \ + { \ + macro_call; \ + } \ + EXPECT_EQ(check_count(slic::internal::test_stream.str(), level), 1); \ + check_level_msg_line_file(level, message, expected_line); \ + } while(false) + } // end anonymous namespace //------------------------------------------------------------------------------ @@ -185,33 +214,64 @@ TEST(slic_macros, test_warning_macros) EXPECT_TRUE(slic::internal::is_stream_empty()); EXPECT_SLIC_LOG(SLIC_WARNING("test warning message"), "WARNING", "test warning message"); + // Called once per call site + EXPECT_SLIC_ONCE(SLIC_WARNING_ONCE("test warning message once"), + "WARNING", + "test warning message once"); + + // Two different call sites, will have two messages + SLIC_WARNING_ONCE("test warning message #1"); + SLIC_WARNING_ONCE("test warning message #2"); + EXPECT_EQ(check_count(slic::internal::test_stream.str(), "WARNING"), 2); + slic::internal::clear(); + SLIC_WARNING_IF(false, "this message should not be logged!"); EXPECT_TRUE(slic::internal::is_stream_empty()); + SLIC_WARNING_IF_ONCE(false, "this message should not be logged!"); + EXPECT_TRUE(slic::internal::is_stream_empty()); + EXPECT_SLIC_LOG(SLIC_WARNING_IF(true, "this message is logged!"), "WARNING", "this message is logged!"); + EXPECT_SLIC_ONCE(SLIC_WARNING_IF_ONCE(true, "this message is logged once!"), + "WARNING", + "this message is logged once!"); + // Check selective filtering based on root == false axom::slic::setIsRoot(false); SLIC_WARNING_ROOT_IF(false, "this message should not be logged!"); EXPECT_TRUE(slic::internal::is_stream_empty()); + SLIC_WARNING_ROOT_IF_ONCE(false, "this message should not be logged!"); + EXPECT_TRUE(slic::internal::is_stream_empty()); + // Check selective filter based on root == true axom::slic::setIsRoot(true); EXPECT_SLIC_LOG(SLIC_WARNING_ROOT_IF(true, "this message is logged!"), "WARNING", "this message is logged!"); + EXPECT_SLIC_ONCE(SLIC_WARNING_ROOT_IF_ONCE(true, "this message is logged once!"), + "WARNING", + "this message is logged once!"); + // is root, but conditional is false -> no message axom::slic::setIsRoot(true); SLIC_WARNING_ROOT_IF(false, "this message should not be logged!"); EXPECT_TRUE(slic::internal::is_stream_empty()); + SLIC_WARNING_ROOT_IF_ONCE(false, "this message should not be logged!"); + EXPECT_TRUE(slic::internal::is_stream_empty()); + // is not root, and conditional is true -> no message axom::slic::setIsRoot(false); SLIC_WARNING_ROOT_IF(true, "this message should not be logged!"); EXPECT_TRUE(slic::internal::is_stream_empty()); + + SLIC_WARNING_ROOT_IF_ONCE(true, "this message should not be logged!"); + EXPECT_TRUE(slic::internal::is_stream_empty()); } //------------------------------------------------------------------------------ From c26ff2be0fe70d33ae7a3ad9ba27cb83f81f54e6 Mon Sep 17 00:00:00 2001 From: Brian Manh Hien Han Date: Wed, 18 Mar 2026 13:35:57 -0700 Subject: [PATCH 008/986] Add SLIC_INFO_*_ONCE macros --- src/axom/slic/interface/slic_macros.hpp | 93 +++++++++++++++++++++++++ src/axom/slic/tests/slic_macros.cpp | 29 +++++++- 2 files changed, 121 insertions(+), 1 deletion(-) diff --git a/src/axom/slic/interface/slic_macros.hpp b/src/axom/slic/interface/slic_macros.hpp index 6fc5532256..0aed87396f 100644 --- a/src/axom/slic/interface/slic_macros.hpp +++ b/src/axom/slic/interface/slic_macros.hpp @@ -485,6 +485,22 @@ */ #define SLIC_INFO(msg) SLIC_INFO_IF(true, msg) +/*! + * \def SLIC_INFO_ONCE( msg ) + * \brief Logs an Info message only once per call site. + * + * \param [in] msg user-supplied message + * + * \note The SLIC_INFO_ONCE macro is always active. + * + * Usage: + * \code + * SLIC_INFO_ONCE( "informative text goes here" ); + * \endcode + * + */ +#define SLIC_INFO_ONCE(msg) SLIC_DETAIL_LOG_IF_ONCE(SLIC_INFO_IF, true, msg) + /*! * \def SLIC_INFO_TAGGED( msg, tag ) * \brief Logs an Info message to a tagged stream @@ -508,6 +524,32 @@ axom::slic::logMessage(axom::slic::message::Info, __oss.str(), tag, __FILE__, __LINE__, false, true); \ } while(axom::slic::detail::false_value) +/*! + * \def SLIC_INFO_TAGGED_ONCE( msg, tag ) + * \brief Logs an Info message to a tagged stream only once per call site. + * + * \param [in] msg user-supplied message + * \param [in] tag user-supplied tag + * + * \note The SLIC_INFO_TAGGED_ONCE macro is always active. + * + * Usage: + * \code + * SLIC_INFO_TAGGED_ONCE("informative text goes here", "tag"); + * \endcode + * + */ +#define SLIC_INFO_TAGGED_ONCE(msg, tag) \ + do \ + { \ + static bool once = true; \ + if(once) \ + { \ + SLIC_INFO_TAGGED(msg, tag); \ + once = false; \ + } \ + } while(axom::slic::detail::false_value) + /*! * \def SLIC_INFO_IF( EXP, msg ) * \brief Logs an Info message iff EXP is true @@ -534,6 +576,23 @@ } \ } while(axom::slic::detail::false_value) +/*! + * \def SLIC_INFO_IF_ONCE( EXP, msg ) + * \brief Logs an Info message iff EXP is true, only once per call site. + * + * \param [in] EXP user-supplied boolean expression. + * \param [in] msg user-supplied message. + * + * \note The SLIC_INFO_IF_ONCE macro is always active. + * + * Usage: + * \code + * SLIC_INFO_IF_ONCE( (val < 0), "my_val should always be positive" ); + * \endcode + * + */ +#define SLIC_INFO_IF_ONCE(EXP, msg) SLIC_DETAIL_LOG_IF_ONCE(SLIC_INFO_IF, EXP, msg) + /*! * \def SLIC_INFO_ROOT( msg ) * \brief Logs an Info message if on root @@ -550,6 +609,22 @@ */ #define SLIC_INFO_ROOT(msg) SLIC_INFO_IF(axom::slic::isRoot(), msg) +/*! + * \def SLIC_INFO_ROOT_ONCE( msg ) + * \brief Logs an Info message if on root, only once per call site. + * + * \param [in] msg user-supplied message. + * + * \note The SLIC_INFO_ROOT_ONCE macro is always active. + * + * Usage: + * \code + * SLIC_INFO_ROOT_ONCE( "informative text goes here" ); + * \endcode + * + */ +#define SLIC_INFO_ROOT_ONCE(msg) SLIC_DETAIL_LOG_IF_ONCE(SLIC_INFO_ROOT, axom::slic::isRoot(), msg) + /*! * \def SLIC_INFO_ROOT_IF( EXP, msg ) * \brief Logs an Info message if on root and iff EXP is true @@ -567,6 +642,24 @@ */ #define SLIC_INFO_ROOT_IF(EXP, msg) SLIC_INFO_IF((EXP) && (axom::slic::isRoot()), msg) +/*! + * \def SLIC_INFO_ROOT_IF_ONCE( EXP, msg ) + * \brief Logs an Info message if on root and iff EXP is true, only once per call site. + * + * \param [in] EXP user-supplied boolean expression. + * \param [in] msg user-supplied message. + * + * \note The SLIC_INFO_ROOT_IF_ONCE macro is always active. + * + * Usage: + * \code + * SLIC_INFO_ROOT_IF_ONCE( (val < 0), "my_val should always be positive" ); + * \endcode + * + */ +#define SLIC_INFO_ROOT_IF_ONCE(EXP, msg) \ + SLIC_DETAIL_LOG_IF_ONCE(SLIC_INFO_ROOT_IF, (EXP) && (axom::slic::isRoot()), msg) + #ifdef AXOM_DEBUG /*! diff --git a/src/axom/slic/tests/slic_macros.cpp b/src/axom/slic/tests/slic_macros.cpp index b2e2c54d7d..5c8c100016 100644 --- a/src/axom/slic/tests/slic_macros.cpp +++ b/src/axom/slic/tests/slic_macros.cpp @@ -155,7 +155,7 @@ void check_level_msg_line_file(const std::string& level, const std::string& mess check_level_msg_line_file(level, message, expected_line); \ } while(false) -// Convenience test macro that checks SLIC_*_ONCE macro has logged one message +// Convenience test macro that checks SLIC_*_ONCE macro logs one message #define EXPECT_SLIC_ONCE(macro_call, level, message) \ do \ { \ @@ -280,20 +280,35 @@ TEST(slic_macros, test_info_macros) EXPECT_TRUE(slic::internal::is_stream_empty()); EXPECT_SLIC_LOG(SLIC_INFO("test info message"), "INFO", "test info message"); + EXPECT_SLIC_ONCE(SLIC_INFO_ONCE("this info message once"), "INFO", "this info message once"); + SLIC_INFO_IF(false, "this message should not be logged!"); EXPECT_TRUE(slic::internal::is_stream_empty()); + SLIC_INFO_IF_ONCE(false, "this message should not be logged!"); + EXPECT_TRUE(slic::internal::is_stream_empty()); + EXPECT_SLIC_LOG(SLIC_INFO_IF(true, "this message is logged!"), "INFO", "this message is logged!"); + EXPECT_SLIC_ONCE(SLIC_INFO_IF_ONCE(true, "this message is logged once!"), + "INFO", + "this message is logged once!"); + // is root, but conditional is false -> no message axom::slic::setIsRoot(true); SLIC_INFO_ROOT_IF(false, "this message should not be logged!"); EXPECT_TRUE(slic::internal::is_stream_empty()); + SLIC_INFO_ROOT_IF_ONCE(false, "this message should not be logged!"); + EXPECT_TRUE(slic::internal::is_stream_empty()); + // is not root, and conditional is true -> no message axom::slic::setIsRoot(false); SLIC_INFO_ROOT_IF(true, "this message should not be logged!"); EXPECT_TRUE(slic::internal::is_stream_empty()); + + SLIC_INFO_ROOT_IF_ONCE(true, "this message should not be logged!"); + EXPECT_TRUE(slic::internal::is_stream_empty()); } //------------------------------------------------------------------------------ @@ -433,10 +448,22 @@ TEST(slic_macros, test_tagged_macros) check_tag(slic::internal::test_stream.str(), "myTag"); check_level_msg_line_file("INFO", "test tagged info message", expected_line_number); + for(int i = 0; i < 2; i++) + { + SLIC_INFO_TAGGED_ONCE("test tagged info message once", "myTag"); + } + expected_line_number = __LINE__ - 2; + + EXPECT_EQ(check_count(slic::internal::test_stream.str(), "INFO"), 1); + check_tag(slic::internal::test_stream.str(), "myTag"); + check_level_msg_line_file("INFO", "test tagged info message once", expected_line_number); + SLIC_INFO_TAGGED("this message should not be logged (no tag given)!", ""); + SLIC_INFO_TAGGED_ONCE("this message should not be logged (no tag given)!", ""); EXPECT_TRUE(slic::internal::is_stream_empty()); SLIC_INFO_TAGGED("this message should not be logged (tag DNE)!", "tag404"); + SLIC_INFO_TAGGED_ONCE("this message should not be logged (tag DNE)!", "tag404"); EXPECT_TRUE(slic::internal::is_stream_empty()); } From 34d861b14cb432dc044e051cdcc6900a43e29e09 Mon Sep 17 00:00:00 2001 From: Brian Manh Hien Han Date: Wed, 18 Mar 2026 14:44:08 -0700 Subject: [PATCH 009/986] Add SLIC_DEBUG_*_ONCE macros; additional slic_macros test refactoring --- src/axom/slic/interface/slic_macros.hpp | 107 +++++++++++++++++++++++- src/axom/slic/tests/slic_macros.cpp | 89 ++++++++++++++------ 2 files changed, 169 insertions(+), 27 deletions(-) diff --git a/src/axom/slic/interface/slic_macros.hpp b/src/axom/slic/interface/slic_macros.hpp index 0aed87396f..213eda7b7c 100644 --- a/src/axom/slic/interface/slic_macros.hpp +++ b/src/axom/slic/interface/slic_macros.hpp @@ -292,7 +292,7 @@ * */ #define SLIC_WARNING_ROOT_IF_ONCE(EXP, msg) \ - SLIC_DETAIL_LOG_IF_ONCE(SLIC_WARNING_ROOT_IF, (EXP) && (axom::slic::isRoot()), msg) + SLIC_DETAIL_LOG_IF_ONCE(SLIC_WARNING_IF, (EXP) && (axom::slic::isRoot()), msg) ///@} @@ -623,7 +623,7 @@ * \endcode * */ -#define SLIC_INFO_ROOT_ONCE(msg) SLIC_DETAIL_LOG_IF_ONCE(SLIC_INFO_ROOT, axom::slic::isRoot(), msg) +#define SLIC_INFO_ROOT_ONCE(msg) SLIC_DETAIL_LOG_IF_ONCE(SLIC_INFO_IF, axom::slic::isRoot(), msg) /*! * \def SLIC_INFO_ROOT_IF( EXP, msg ) @@ -658,7 +658,7 @@ * */ #define SLIC_INFO_ROOT_IF_ONCE(EXP, msg) \ - SLIC_DETAIL_LOG_IF_ONCE(SLIC_INFO_ROOT_IF, (EXP) && (axom::slic::isRoot()), msg) + SLIC_DETAIL_LOG_IF_ONCE(SLIC_INFO_IF, (EXP) && (axom::slic::isRoot()), msg) #ifdef AXOM_DEBUG @@ -678,6 +678,22 @@ */ #define SLIC_DEBUG(msg) SLIC_DEBUG_IF(true, msg) + /*! + * \def SLIC_DEBUG_ONCE( msg ) + * \brief Logs a Debug message only once per call site. + * + * \param [in] msg user-supplied message + * + * \note The SLIC_DEBUG_ONCE macro is active when AXOM_DEBUG is defined. + * + * Usage: + * \code + * SLIC_DEBUG_ONCE( "debug message goes here" ); + * \endcode + * + */ + #define SLIC_DEBUG_ONCE(msg) SLIC_DETAIL_LOG_IF_ONCE(SLIC_DEBUG_IF, true, msg) + /*! * \def SLIC_DEBUG_IF( EXP, msg ) * \brief Logs an Debug message iff EXP is true @@ -704,6 +720,23 @@ } \ } while(axom::slic::detail::false_value) + /*! + * \def SLIC_DEBUG_IF_ONCE( EXP, msg ) + * \brief Logs an Debug message iff EXP is true only once per call site. + * + * \param [in] EXP user-supplied boolean expression. + * \param [in] msg user-supplied message. + * + * \note The SLIC_DEBUG_IF_ONCE macro is active when AXOM_DEBUG is defined. + * + * Usage: + * \code + * SLIC_DEBUG_IF_ONCE( (val < 0), "my_val should always be positive" ); + * \endcode + * + */ + #define SLIC_DEBUG_IF_ONCE(EXP, msg) SLIC_DETAIL_LOG_IF_ONCE(SLIC_DEBUG_IF, EXP, msg) + /*! * \def SLIC_DEBUG_ROOT( msg ) * \brief Logs a Debug message if on root @@ -720,6 +753,23 @@ */ #define SLIC_DEBUG_ROOT(msg) SLIC_DEBUG_IF(axom::slic::isRoot(), msg) + /*! + * \def SLIC_DEBUG_ROOT_ONCE( msg ) + * \brief Logs a Debug message if on root only once per call site. + * + * \param [in] msg user-supplied message. + * + * \note The SLIC_DEBUG_ROOT_ONCE macro is active when AXOM_DEBUG is defined. + * + * Usage: + * \code + * SLIC_DEBUG_ROOT_ONCE( "informative text goes here" ); + * \endcode + * + */ + #define SLIC_DEBUG_ROOT_ONCE(msg) \ + SLIC_DETAIL_LOG_IF_ONCE(SLIC_DEBUG_IF, axom::slic::isRoot(), msg) + /*! * \def SLIC_DEBUG_ROOT_IF( EXP, msg ) * \brief Logs a Debug message if on root and iff EXP is true @@ -737,6 +787,24 @@ */ #define SLIC_DEBUG_ROOT_IF(EXP, msg) SLIC_DEBUG_IF((EXP) && (axom::slic::isRoot()), msg) + /*! + * \def SLIC_DEBUG_ROOT_IF_ONCE( EXP, msg ) + * \brief Logs a Debug message if on root and iff EXP is true, only once per call site. + * + * \param [in] EXP user-supplied boolean expression. + * \param [in] msg user-supplied message. + * + * \note The SLIC_DEBUG_ROOT_IF_ONCE macro is active when AXOM_DEBUG is defined. + * + * Usage: + * \code + * SLIC_DEBUG_ROOT_IF_ONCE( (val < 0), "my_val should always be positive" ); + * \endcode + * + */ + #define SLIC_DEBUG_ROOT_IF_ONCE(EXP, msg) \ + SLIC_DETAIL_LOG_IF_ONCE(SLIC_DEBUG_IF, (EXP) && (axom::slic::isRoot()), msg) + /*! * \def SLIC_DEBUG_PRINT_CONTAINER( name, container ) * \brief Logs a Debug message containing the contents of the container, moving @@ -762,13 +830,46 @@ axom::slic::logMessage(axom::slic::message::Debug, __oss.str(), __FILE__, __LINE__); \ } while(axom::slic::detail::false_value) + /*! + * \def SLIC_DEBUG_PRINT_CONTAINER_ONCE( name, container ) + * \brief Logs a Debug message containing the contents of the container, moving + * the contents to the host if needed. Called only once per call site. + * + * \param [in] name The name of the container in the printed message. + * \param [in] container The container (array, vector, view). + * + * \note The SLIC_DEBUG_PRINT_CONTAINER_ONCE macro is active when AXOM_DEBUG is defined. + * + * Usage: + * \code + * axom::ArrayView dataView; + * SLIC_DEBUG_PRINT_CONTAINER_ONCE( "dataView", dataView ); + * \endcode + * + */ + #define SLIC_DEBUG_PRINT_CONTAINER_ONCE(name, container) \ + do \ + { \ + static bool once = true; \ + if(once) \ + { \ + SLIC_DEBUG_PRINT_CONTAINER(name, container); \ + once = false; \ + } \ + } while(axom::slic::detail::false_value) + #else // turn off debug macros #define SLIC_DEBUG(ignore_EXP) ((void)0) + #define SLIC_DEBUG_ONCE(ignore_EXP) ((void)0) #define SLIC_DEBUG_IF(ignore_EXP, ignore_msg) ((void)0) + #define SLIC_DEBUG_IF_ONCE(ignore_EXP, ignore_msg) ((void)0) #define SLIC_DEBUG_ROOT(ignore_EXP) ((void)0) + #define SLIC_DEBUG_ROOT_ONCE(ignore_EXP) ((void)0) #define SLIC_DEBUG_ROOT_IF(ignore_EXP, ignore_msg) ((void)0) + #define SLIC_DEBUG_ROOT_IF_ONCE(ignore_EXP, ignore_msg) ((void)0) #define SLIC_DEBUG_PRINT_CONTAINER(ignore_name, ignore_container) ((void)0) + #define SLIC_DEBUG_PRINT_CONTAINER_ONCE(ignore_name, ignore_container) ((void)0) #endif diff --git a/src/axom/slic/tests/slic_macros.cpp b/src/axom/slic/tests/slic_macros.cpp index 5c8c100016..837b43905a 100644 --- a/src/axom/slic/tests/slic_macros.cpp +++ b/src/axom/slic/tests/slic_macros.cpp @@ -247,8 +247,16 @@ TEST(slic_macros, test_warning_macros) SLIC_WARNING_ROOT_IF_ONCE(false, "this message should not be logged!"); EXPECT_TRUE(slic::internal::is_stream_empty()); - // Check selective filter based on root == true axom::slic::setIsRoot(true); + EXPECT_SLIC_LOG(SLIC_WARNING_ROOT("this message is logged on root!"), + "WARNING", + "this message is logged on root!"); + + EXPECT_SLIC_ONCE(SLIC_WARNING_ROOT_ONCE("this message is logged on root once!"), + "WARNING", + "this message is logged on root once!"); + + // Check selective filter based on root == true EXPECT_SLIC_LOG(SLIC_WARNING_ROOT_IF(true, "this message is logged!"), "WARNING", "this message is logged!"); @@ -294,8 +302,16 @@ TEST(slic_macros, test_info_macros) "INFO", "this message is logged once!"); - // is root, but conditional is false -> no message axom::slic::setIsRoot(true); + EXPECT_SLIC_LOG(SLIC_INFO_ROOT("this message is logged on root!"), + "INFO", + "this message is logged on root!"); + + EXPECT_SLIC_ONCE(SLIC_INFO_ROOT_ONCE("this message is logged on root once!"), + "INFO", + "this message is logged on root once!"); + + // is root, but conditional is false -> no message SLIC_INFO_ROOT_IF(false, "this message should not be logged!"); EXPECT_TRUE(slic::internal::is_stream_empty()); @@ -314,56 +330,81 @@ TEST(slic_macros, test_info_macros) //------------------------------------------------------------------------------ TEST(slic_macros, test_debug_macros) { - int expected_line_number; - EXPECT_TRUE(slic::internal::is_stream_empty()); - SLIC_DEBUG("test debug message"); - expected_line_number = __LINE__ - 1; #ifdef AXOM_DEBUG - check_level_msg_line_file("DEBUG", "test debug message", expected_line_number); + EXPECT_SLIC_LOG(SLIC_DEBUG("test debug message"), "DEBUG", "test debug message"); + + EXPECT_SLIC_ONCE(SLIC_DEBUG_ONCE("test debug message once"), "DEBUG", "test debug message once"); + + EXPECT_SLIC_LOG(SLIC_DEBUG_IF(true, "this message is logged!"), + "DEBUG", + "this message is logged!"); + + EXPECT_SLIC_ONCE(SLIC_DEBUG_IF_ONCE(true, "this message is logged once!"), + "DEBUG", + "this message is logged once!"); + + axom::slic::setIsRoot(true); + EXPECT_SLIC_LOG(SLIC_DEBUG_ROOT("this message is logged!"), "DEBUG", "this message is logged!"); + + EXPECT_SLIC_ONCE(SLIC_DEBUG_ROOT_ONCE("this message is logged once!"), + "DEBUG", + "this message is logged once!"); + + // Check selective filter based on root == true + EXPECT_SLIC_LOG(SLIC_DEBUG_ROOT_IF(true, "this message is logged!"), + "DEBUG", + "this message is logged!"); + + EXPECT_SLIC_ONCE(SLIC_DEBUG_ROOT_IF_ONCE(true, "this message is logged once!"), + "DEBUG", + "this message is logged once!"); + #else // SLIC_DEBUG macros only log messages when AXOM_DEBUG is defined + + SLIC_DEBUG("test debug message"); + SLIC_DEBUG_ONCE("test debug message"); + + SLIC_DEBUG_IF(true, "this message is logged!"); + SLIC_DEBUG_IF_ONCE(true, "this message is logged!"); + + axom::slic::setIsRoot(true); + SLIC_DEBUG_ROOT_IF(true, "this message is logged!"); + SLIC_DEBUG_ROOT_IF_ONCE(true, "this message is logged!"); + EXPECT_TRUE(slic::internal::is_stream_empty()); #endif SLIC_DEBUG_IF(false, "this message should not be logged!"); EXPECT_TRUE(slic::internal::is_stream_empty()); - SLIC_DEBUG_IF(true, "this message is logged!"); - expected_line_number = __LINE__ - 1; -#ifdef AXOM_DEBUG - check_level_msg_line_file("DEBUG", "this message is logged!", expected_line_number); -#else - // SLIC_DEBUG macros only log messages when AXOM_DEBUG is defined + SLIC_DEBUG_IF_ONCE(false, "this message should not be logged!"); EXPECT_TRUE(slic::internal::is_stream_empty()); -#endif // Check selective filtering based on root == false axom::slic::setIsRoot(false); SLIC_DEBUG_ROOT_IF(false, "this message should not be logged!"); EXPECT_TRUE(slic::internal::is_stream_empty()); - // Check selective filter based on root == true - axom::slic::setIsRoot(true); - SLIC_DEBUG_ROOT_IF(true, "this message is logged!"); - expected_line_number = __LINE__ - 1; -#ifdef AXOM_DEBUG - check_level_msg_line_file("DEBUG", "this message is logged!", expected_line_number); -#else - // SLIC_DEBUG macros only log messages when AXOM_DEBUG is defined + SLIC_DEBUG_ROOT_IF_ONCE(false, "this message should not be logged!"); EXPECT_TRUE(slic::internal::is_stream_empty()); - AXOM_UNUSED_VAR(expected_line_number); -#endif // is root, but conditional is false -> no message axom::slic::setIsRoot(true); SLIC_DEBUG_ROOT_IF(false, "this message should not be logged!"); EXPECT_TRUE(slic::internal::is_stream_empty()); + SLIC_DEBUG_ROOT_IF_ONCE(false, "this message should not be logged!"); + EXPECT_TRUE(slic::internal::is_stream_empty()); + // is not root, and conditional is true -> no message axom::slic::setIsRoot(false); SLIC_DEBUG_ROOT_IF(true, "this message should not be logged!"); EXPECT_TRUE(slic::internal::is_stream_empty()); + + SLIC_DEBUG_ROOT_IF_ONCE(true, "this message should not be logged!"); + EXPECT_TRUE(slic::internal::is_stream_empty()); } //------------------------------------------------------------------------------ From d0fce6792f9be8c90c06ef020b1356f813945d07 Mon Sep 17 00:00:00 2001 From: Brian Manh Hien Han Date: Wed, 18 Mar 2026 16:42:46 -0700 Subject: [PATCH 010/986] Add additional test --- src/axom/slic/tests/slic_macros.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/axom/slic/tests/slic_macros.cpp b/src/axom/slic/tests/slic_macros.cpp index 837b43905a..158997cd46 100644 --- a/src/axom/slic/tests/slic_macros.cpp +++ b/src/axom/slic/tests/slic_macros.cpp @@ -370,6 +370,9 @@ TEST(slic_macros, test_debug_macros) SLIC_DEBUG_IF_ONCE(true, "this message is logged!"); axom::slic::setIsRoot(true); + SLIC_DEBUG_ROOT(true, "this message is logged!"); + SLIC_DEBUG_ROOT_ONCE(true, "this message is logged!"); + SLIC_DEBUG_ROOT_IF(true, "this message is logged!"); SLIC_DEBUG_ROOT_IF_ONCE(true, "this message is logged!"); From d599b9de56ee73d1747cf0a3ba43b1ba9e0e8b1d Mon Sep 17 00:00:00 2001 From: Brian Manh Hien Han Date: Thu, 19 Mar 2026 08:36:24 -0700 Subject: [PATCH 011/986] Fix slic once logic, add more tests to verify behavior --- src/axom/slic/interface/slic_macros.hpp | 9 ++++-- src/axom/slic/tests/slic_macros.cpp | 42 +++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/src/axom/slic/interface/slic_macros.hpp b/src/axom/slic/interface/slic_macros.hpp index 213eda7b7c..b1022ec3b1 100644 --- a/src/axom/slic/interface/slic_macros.hpp +++ b/src/axom/slic/interface/slic_macros.hpp @@ -886,10 +886,13 @@ do \ { \ static bool once = true; \ - if(once) \ + if(EXP) \ { \ - macro(EXP, msg); \ - once = false; \ + if(once) \ + { \ + macro(true, msg); \ + once = false; \ + } \ } \ } while(axom::slic::detail::false_value) diff --git a/src/axom/slic/tests/slic_macros.cpp b/src/axom/slic/tests/slic_macros.cpp index 158997cd46..71fab20326 100644 --- a/src/axom/slic/tests/slic_macros.cpp +++ b/src/axom/slic/tests/slic_macros.cpp @@ -511,6 +511,48 @@ TEST(slic_macros, test_tagged_macros) EXPECT_TRUE(slic::internal::is_stream_empty()); } +//------------------------------------------------------------------------------ +TEST(slic_macros, test_if_once_macros) +{ + // Check that message is logged when condition is satisfied only once + for(int i = 0; i < 3; i++) + { + SLIC_INFO_IF_ONCE(i > 0, i << "th message is logged!"); + } + int expected_line_number = __LINE__ - 2; + EXPECT_EQ(check_count(slic::internal::test_stream.str(), "INFO"), 1); + check_level_msg_line_file("INFO", "1th message is logged", expected_line_number); + + axom::slic::setIsRoot(true); + for(int i = 0; i < 3; i++) + { + SLIC_INFO_ROOT_IF_ONCE(i > 0, i << "th message is logged!"); + } + expected_line_number = __LINE__ - 2; + EXPECT_EQ(check_count(slic::internal::test_stream.str(), "INFO"), 1); + check_level_msg_line_file("INFO", "1th message is logged", expected_line_number); + + // Two call-sites have a single message each + for(int i = 0; i < 3; i++) + { + SLIC_INFO_IF_ONCE(i == 0, "message 1 logs " << i); + SLIC_INFO_IF_ONCE(i > 0, "message 2 logs " << i); + } + int msg_1_line = __LINE__ - 2; + int msg_2_line = __LINE__ - 3; + + EXPECT_FALSE(slic::internal::is_stream_empty()); + const std::string str = slic::internal::test_stream.str(); + EXPECT_EQ(check_count(str, "INFO"), 2); + check_level(str, "INFO"); + check_msg(str, "message 1 logs 0"); + check_msg(str.substr(str.size() / 2), "message 2 logs 1"); + check_line(str, msg_1_line); + check_line(str, msg_2_line); + check_file(str); + slic::internal::clear(); +} + //------------------------------------------------------------------------------ TEST(slic_macros, test_macros_file_output) { From 2a3887f1be24bbaddc0eb20dca8cb599b0de0e4a Mon Sep 17 00:00:00 2001 From: Brian Manh Hien Han Date: Thu, 19 Mar 2026 11:16:12 -0700 Subject: [PATCH 012/986] Reduce code duplication for slic_macros_parallel; SCOPED_TRACE for faster debugging --- src/axom/slic/tests/slic_macros_parallel.cpp | 967 ++++++------------- 1 file changed, 311 insertions(+), 656 deletions(-) diff --git a/src/axom/slic/tests/slic_macros_parallel.cpp b/src/axom/slic/tests/slic_macros_parallel.cpp index a716895940..6b7519e24c 100644 --- a/src/axom/slic/tests/slic_macros_parallel.cpp +++ b/src/axom/slic/tests/slic_macros_parallel.cpp @@ -144,6 +144,7 @@ void check_tag(const std::string& msg, const std::string& expected_tag) } //------------------------------------------------------------------------------ +// For SynchronizedStream where each message has one associated rank void check_rank(const std::string& msg, int expected_rank) { EXPECT_FALSE(msg.empty()); @@ -157,6 +158,7 @@ void check_rank(const std::string& msg, int expected_rank) } //------------------------------------------------------------------------------ +// For LumberjackStream where message from multiple ranks are at the root void check_ranks(const std::string& msg, int expected_ranks) { // Check all ranks from [0, expected_ranks) are in message @@ -167,12 +169,12 @@ void check_ranks(const std::string& msg, int expected_ranks) } //------------------------------------------------------------------------------ -void check_rank_count(const std::string& msg, const std::string& streamType, int expected_rank_count) +void check_rank_count(const std::string& msg, const std::string& stream_type, int expected_rank_count) { EXPECT_FALSE(msg.empty()); // Always 1 for SynchronizedStream - if(streamType == "Synchronized") + if(stream_type == "Synchronized") { expected_rank_count = 1; } @@ -184,6 +186,147 @@ void check_rank_count(const std::string& msg, const std::string& streamType, int EXPECT_EQ(rank_count, expected_rank_count); } +//------------------------------------------------------------------------------ +// Checks message logged on all ranks +void check_all_ranks(const std::string& stream_type, + const std::string& level, + const std::string& message, + int expected_line, + int rank, + int nranks) +{ + if(stream_type == "Synchronized" || (stream_type == "Lumberjack" && rank == 0)) + { + EXPECT_FALSE(slic::internal::are_all_streams_empty()); + const std::string str = slic::internal::test_stream.str(); + + SCOPED_TRACE(std::string("SLIC trace (all ranks): Failed line was ") + + std::to_string(expected_line)); + + check_level(str, level); + check_msg(str, message); + check_line(str, expected_line); + if(stream_type == "Synchronized") + { + check_rank(str, rank); + } + else + { + check_ranks(str, nranks); + } + check_rank_count(str, stream_type, nranks); + + check_file(str); + } +} + +// Convenience test macro that calls given slic macro and checks +// message logged on all ranks +#define EXPECT_SLIC_LOG_ALL_RANKS(macro_call, level, message) \ + do \ + { \ + const int expected_line = __LINE__; \ + macro_call; \ + slic::flushStreams(); \ + check_all_ranks(GetParam(), level, message, expected_line, rank, nranks); \ + slic::internal::clear_streams(); \ + } while(false) + +//------------------------------------------------------------------------------ +// Checks message logged only on root +void check_root(const std::string& stream_type, + const std::string& level, + const std::string& message, + int expected_line, + int rank) +{ + if(rank == 0) + { + EXPECT_FALSE(slic::internal::are_all_streams_empty()); + const std::string str = slic::internal::test_stream.str(); + + SCOPED_TRACE(std::string("SLIC trace (root): Failed line was ") + std::to_string(expected_line)); + + check_level(str, level); + check_msg(str, message); + check_line(str, expected_line); + check_rank(str, rank); + + // Only one rank has logged a message + check_rank_count(str, stream_type, 1); + + check_file(str); + } + else + { + EXPECT_TRUE(slic::internal::are_all_streams_empty()); + } +} + +// Convenience test macro that calls given slic macro and checks +// message logged only on root +#define EXPECT_SLIC_LOG_ROOT(macro_call, level, message) \ + do \ + { \ + axom::slic::setIsRoot(rank == 0); \ + const int expected_line = __LINE__; \ + macro_call; \ + slic::flushStreams(); \ + check_root(GetParam(), level, message, expected_line, rank); \ + slic::internal::clear_streams(); \ + } while(false) + +//------------------------------------------------------------------------------ +// Checks message logged only on even ranks +void check_even(const std::string& stream_type, + const std::string& level, + const std::string& message, + int expected_line, + int rank, + int nranks) +{ + if(((rank % 2) == 0 && stream_type == "Synchronized") || (rank == 0 && stream_type == "Lumberjack")) + { + EXPECT_FALSE(slic::internal::are_all_streams_empty()); + const std::string str = slic::internal::test_stream.str(); + + SCOPED_TRACE(std::string("SLIC trace (even): Failed line was ") + std::to_string(expected_line)); + + check_level(str, level); + check_msg(str, message); + check_line(str, expected_line); + if(stream_type == "Synchronized") + { + check_rank(str, rank); + } + else + { + for(int i = 0; i < nranks; i += 2) + { + check_rank(str, i); + } + } + check_rank_count(str, stream_type, (nranks / 2) + (nranks % 2)); + check_file(str); + } + else + { + EXPECT_TRUE(slic::internal::are_all_streams_empty()); + } +} + +// Convenience test macro that calls given slic macro and checks +// message logged only on even ranks +#define EXPECT_SLIC_LOG_EVEN(macro_call, level, message) \ + do \ + { \ + const int expected_line = __LINE__; \ + macro_call; \ + slic::flushStreams(); \ + check_even(GetParam(), level, message, expected_line, rank, nranks); \ + slic::internal::clear_streams(); \ + } while(false) + //------------------------------------------------------------------------------ bool has_aborted = false; void custom_abort_function() { has_aborted = true; } @@ -256,56 +399,17 @@ class SlicMacrosParallel : public ::testing::TestWithParam //------------------------------------------------------------------------------ TEST_P(SlicMacrosParallel, test_error_macros) { - int expected_line_number; - EXPECT_TRUE(slic::internal::are_all_streams_empty()); - SLIC_ERROR("test error message"); - expected_line_number = __LINE__ - 1; - slic::flushStreams(); - if(GetParam() == "Synchronized" || (GetParam() == "Lumberjack" && rank == 0)) - { - EXPECT_FALSE(slic::internal::are_all_streams_empty()); - check_level(slic::internal::test_stream.str(), "ERROR"); - check_msg(slic::internal::test_stream.str(), "test error message"); - check_file(slic::internal::test_stream.str()); - check_line(slic::internal::test_stream.str(), expected_line_number); - if(GetParam() == "Synchronized") - { - check_rank(slic::internal::test_stream.str(), rank); - } - else - { - check_ranks(slic::internal::test_stream.str(), nranks); - } - check_rank_count(slic::internal::test_stream.str(), GetParam(), nranks); - } - slic::internal::clear_streams(); + + EXPECT_SLIC_LOG_ALL_RANKS(SLIC_ERROR("test error message"), "ERROR", "test error message"); SLIC_ERROR_IF(false, "this message should not be logged!"); slic::flushStreams(); EXPECT_TRUE(slic::internal::are_all_streams_empty()); - SLIC_ERROR_IF(true, "this message is logged!"); - expected_line_number = __LINE__ - 1; - slic::flushStreams(); - if(GetParam() == "Synchronized" || (GetParam() == "Lumberjack" && rank == 0)) - { - EXPECT_FALSE(slic::internal::are_all_streams_empty()); - check_level(slic::internal::test_stream.str(), "ERROR"); - check_msg(slic::internal::test_stream.str(), "this message is logged!"); - check_file(slic::internal::test_stream.str()); - check_line(slic::internal::test_stream.str(), expected_line_number); - if(GetParam() == "Synchronized") - { - check_rank(slic::internal::test_stream.str(), rank); - } - else - { - check_ranks(slic::internal::test_stream.str(), nranks); - } - check_rank_count(slic::internal::test_stream.str(), GetParam(), nranks); - } - slic::internal::clear_streams(); + EXPECT_SLIC_LOG_ALL_RANKS(SLIC_ERROR_IF(true, "this message is logged!"), + "ERROR", + "this message is logged!"); // Check selective filtering based on root == false axom::slic::setIsRoot(false); @@ -315,27 +419,9 @@ TEST_P(SlicMacrosParallel, test_error_macros) // Check selective filter based on root == true axom::slic::setIsRoot(true); - SLIC_ERROR_ROOT_IF(true, "this message is logged!"); - expected_line_number = __LINE__ - 1; - slic::flushStreams(); - if(GetParam() == "Synchronized" || (GetParam() == "Lumberjack" && rank == 0)) - { - EXPECT_FALSE(slic::internal::are_all_streams_empty()); - check_level(slic::internal::test_stream.str(), "ERROR"); - check_msg(slic::internal::test_stream.str(), "this message is logged!"); - check_file(slic::internal::test_stream.str()); - check_line(slic::internal::test_stream.str(), expected_line_number); - if(GetParam() == "Synchronized") - { - check_rank(slic::internal::test_stream.str(), rank); - } - else - { - check_ranks(slic::internal::test_stream.str(), nranks); - } - check_rank_count(slic::internal::test_stream.str(), GetParam(), nranks); - } - slic::internal::clear_streams(); + EXPECT_SLIC_LOG_ALL_RANKS(SLIC_ERROR_ROOT_IF(true, "this message is logged!"), + "ERROR", + "this message is logged!"); // is root, but conditional is false -> no message axom::slic::setIsRoot(true); @@ -349,112 +435,31 @@ TEST_P(SlicMacrosParallel, test_error_macros) slic::flushStreams(); EXPECT_TRUE(slic::internal::are_all_streams_empty()); - // Check for one rank being root - axom::slic::setIsRoot(rank == 0); - SLIC_ERROR_ROOT_IF(true, "this message is logged!"); - expected_line_number = __LINE__ - 1; - slic::flushStreams(); - if(rank == 0) - { - EXPECT_FALSE(slic::internal::are_all_streams_empty()); - check_level(slic::internal::test_stream.str(), "ERROR"); - check_msg(slic::internal::test_stream.str(), "this message is logged!"); - check_file(slic::internal::test_stream.str()); - check_line(slic::internal::test_stream.str(), expected_line_number); - check_rank(slic::internal::test_stream.str(), rank); - check_rank_count(slic::internal::test_stream.str(), GetParam(), 1); - } - else - { - EXPECT_TRUE(slic::internal::are_all_streams_empty()); - } - slic::internal::clear_streams(); + // Check for message only on root + EXPECT_SLIC_LOG_ROOT(SLIC_ERROR_ROOT_IF(true, "this message is logged!"), + "ERROR", + "this message is logged!"); - // Check for more than one rank being root for SynchronizedStream - axom::slic::setIsRoot((rank % 2) == 0); - SLIC_ERROR_ROOT_IF(true, "this message is logged!"); - expected_line_number = __LINE__ - 1; - slic::flushStreams(); - if(((rank % 2) == 0 && GetParam() == "Synchronized") || (rank == 0 && GetParam() == "Lumberjack")) - { - EXPECT_FALSE(slic::internal::are_all_streams_empty()); - check_level(slic::internal::test_stream.str(), "ERROR"); - check_msg(slic::internal::test_stream.str(), "this message is logged!"); - check_file(slic::internal::test_stream.str()); - check_line(slic::internal::test_stream.str(), expected_line_number); - if(GetParam() == "Synchronized") - { - check_rank(slic::internal::test_stream.str(), rank); - } - else - { - for(int i = 0; i < nranks; i += 2) - { - check_rank(slic::internal::test_stream.str(), i); - } - } - check_rank_count(slic::internal::test_stream.str(), GetParam(), (nranks / 2) + (nranks % 2)); - } - else - { - EXPECT_TRUE(slic::internal::are_all_streams_empty()); - } - slic::internal::clear_streams(); + // Check for message on every even rank only + EXPECT_SLIC_LOG_EVEN(SLIC_ERROR_IF((rank % 2) == 0, "this message is logged!"), + "ERROR", + "this message is logged!"); } //------------------------------------------------------------------------------ TEST_P(SlicMacrosParallel, test_warning_macros) { - int expected_line_number; - EXPECT_TRUE(slic::internal::are_all_streams_empty()); - SLIC_WARNING("test warning message"); - expected_line_number = __LINE__ - 1; - slic::flushStreams(); - if(GetParam() == "Synchronized" || (GetParam() == "Lumberjack" && rank == 0)) - { - EXPECT_FALSE(slic::internal::are_all_streams_empty()); - check_level(slic::internal::test_stream.str(), "WARNING"); - check_msg(slic::internal::test_stream.str(), "test warning message"); - check_file(slic::internal::test_stream.str()); - check_line(slic::internal::test_stream.str(), expected_line_number); - if(GetParam() == "Synchronized") - { - check_rank(slic::internal::test_stream.str(), rank); - } - else - { - check_ranks(slic::internal::test_stream.str(), nranks); - } - check_rank_count(slic::internal::test_stream.str(), GetParam(), nranks); - } - slic::internal::clear_streams(); + + EXPECT_SLIC_LOG_ALL_RANKS(SLIC_WARNING("test warning message"), "WARNING", "test warning message"); SLIC_WARNING_IF(false, "this message should not be logged!"); slic::flushStreams(); EXPECT_TRUE(slic::internal::are_all_streams_empty()); - SLIC_WARNING_IF(true, "this message is logged!"); - expected_line_number = __LINE__ - 1; - slic::flushStreams(); - if(GetParam() == "Synchronized" || (GetParam() == "Lumberjack" && rank == 0)) - { - EXPECT_FALSE(slic::internal::are_all_streams_empty()); - check_level(slic::internal::test_stream.str(), "WARNING"); - check_msg(slic::internal::test_stream.str(), "this message is logged!"); - check_file(slic::internal::test_stream.str()); - check_line(slic::internal::test_stream.str(), expected_line_number); - if(GetParam() == "Synchronized") - { - check_rank(slic::internal::test_stream.str(), rank); - } - else - { - check_ranks(slic::internal::test_stream.str(), nranks); - } - check_rank_count(slic::internal::test_stream.str(), GetParam(), nranks); - } - slic::internal::clear_streams(); + EXPECT_SLIC_LOG_ALL_RANKS(SLIC_WARNING_IF(true, "this message is logged!"), + "WARNING", + "this message is logged!"); // Check selective filtering based on root == false axom::slic::setIsRoot(false); @@ -464,27 +469,9 @@ TEST_P(SlicMacrosParallel, test_warning_macros) // Check selective filter based on root == true axom::slic::setIsRoot(true); - SLIC_WARNING_ROOT_IF(true, "this message is logged!"); - expected_line_number = __LINE__ - 1; - slic::flushStreams(); - if(GetParam() == "Synchronized" || (GetParam() == "Lumberjack" && rank == 0)) - { - EXPECT_FALSE(slic::internal::are_all_streams_empty()); - check_level(slic::internal::test_stream.str(), "WARNING"); - check_msg(slic::internal::test_stream.str(), "this message is logged!"); - check_file(slic::internal::test_stream.str()); - check_line(slic::internal::test_stream.str(), expected_line_number); - if(GetParam() == "Synchronized") - { - check_rank(slic::internal::test_stream.str(), rank); - } - else - { - check_ranks(slic::internal::test_stream.str(), nranks); - } - check_rank_count(slic::internal::test_stream.str(), GetParam(), nranks); - } - slic::internal::clear_streams(); + EXPECT_SLIC_LOG_ALL_RANKS(SLIC_WARNING_ROOT_IF(true, "this message is logged!"), + "WARNING", + "this message is logged!"); // is root, but conditional is false -> no message axom::slic::setIsRoot(true); @@ -498,57 +485,15 @@ TEST_P(SlicMacrosParallel, test_warning_macros) slic::flushStreams(); EXPECT_TRUE(slic::internal::are_all_streams_empty()); - // Check for one rank being root - axom::slic::setIsRoot(rank == 0); - SLIC_WARNING_ROOT_IF(true, "this message is logged!"); - expected_line_number = __LINE__ - 1; - slic::flushStreams(); - if(rank == 0) - { - EXPECT_FALSE(slic::internal::are_all_streams_empty()); - check_level(slic::internal::test_stream.str(), "WARNING"); - check_msg(slic::internal::test_stream.str(), "this message is logged!"); - check_file(slic::internal::test_stream.str()); - check_line(slic::internal::test_stream.str(), expected_line_number); - check_rank(slic::internal::test_stream.str(), rank); - check_rank_count(slic::internal::test_stream.str(), GetParam(), 1); - } - else - { - EXPECT_TRUE(slic::internal::are_all_streams_empty()); - } - slic::internal::clear_streams(); + // Check for message only on root + EXPECT_SLIC_LOG_ROOT(SLIC_WARNING_ROOT_IF(true, "this message is logged!"), + "WARNING", + "this message is logged!"); - // Check for more than one rank being root for SynchronizedStream - axom::slic::setIsRoot((rank % 2) == 0); - SLIC_WARNING_ROOT_IF(true, "this message is logged!"); - expected_line_number = __LINE__ - 1; - slic::flushStreams(); - if(((rank % 2) == 0 && GetParam() == "Synchronized") || (rank == 0 && GetParam() == "Lumberjack")) - { - EXPECT_FALSE(slic::internal::are_all_streams_empty()); - check_level(slic::internal::test_stream.str(), "WARNING"); - check_msg(slic::internal::test_stream.str(), "this message is logged!"); - check_file(slic::internal::test_stream.str()); - check_line(slic::internal::test_stream.str(), expected_line_number); - if(GetParam() == "Synchronized") - { - check_rank(slic::internal::test_stream.str(), rank); - } - else - { - for(int i = 0; i < nranks; i += 2) - { - check_rank(slic::internal::test_stream.str(), i); - } - } - check_rank_count(slic::internal::test_stream.str(), GetParam(), (nranks / 2) + (nranks % 2)); - } - else - { - EXPECT_TRUE(slic::internal::are_all_streams_empty()); - } - slic::internal::clear_streams(); + // Check for message on every even rank only + EXPECT_SLIC_LOG_EVEN(SLIC_WARNING_IF((rank % 2) == 0, "this message is logged!"), + "WARNING", + "this message is logged!"); } //------------------------------------------------------------------------------ @@ -558,27 +503,7 @@ TEST_P(SlicMacrosParallel, test_info_macros) int expected_tag_number; EXPECT_TRUE(slic::internal::are_all_streams_empty()); - SLIC_INFO("test info message"); - expected_line_number = __LINE__ - 1; - slic::flushStreams(); - if(GetParam() == "Synchronized" || (GetParam() == "Lumberjack" && rank == 0)) - { - EXPECT_FALSE(slic::internal::are_all_streams_empty()); - check_level(slic::internal::test_stream.str(), "INFO"); - check_msg(slic::internal::test_stream.str(), "test info message"); - check_file(slic::internal::test_stream.str()); - check_line(slic::internal::test_stream.str(), expected_line_number); - if(GetParam() == "Synchronized") - { - check_rank(slic::internal::test_stream.str(), rank); - } - else - { - check_ranks(slic::internal::test_stream.str(), nranks); - } - check_rank_count(slic::internal::test_stream.str(), GetParam(), nranks); - } - slic::internal::clear_streams(); + EXPECT_SLIC_LOG_ALL_RANKS(SLIC_INFO("test info message"), "INFO", "test info message"); SLIC_INFO_TAGGED("test tagged info message", "myTag"); expected_tag_number = __LINE__ - 1; @@ -613,214 +538,8 @@ TEST_P(SlicMacrosParallel, test_info_macros) { EXPECT_FALSE(slic::internal::is_stream_empty()); check_level(slic::internal::test_stream.str(), "INFO"); - check_msg(slic::internal::test_stream.str(), - "test info message only for normal message-level stream"); - check_file(slic::internal::test_stream.str()); - check_line(slic::internal::test_stream.str(), expected_line_number); - if(GetParam() == "Synchronized") - { - check_rank(slic::internal::test_stream.str(), rank); - } - else - { - check_ranks(slic::internal::test_stream.str(), nranks); - } - check_rank_count(slic::internal::test_stream.str(), GetParam(), nranks); - - EXPECT_FALSE(slic::internal::is_tag_stream_empty()); - check_level(slic::internal::test_tag_stream.str(), "INFO"); - check_msg(slic::internal::test_tag_stream.str(), - "test tagged info message only for tagged stream"); - check_file(slic::internal::test_tag_stream.str()); - check_line(slic::internal::test_tag_stream.str(), expected_tag_number); - check_tag(slic::internal::test_tag_stream.str(), "myTag"); - if(GetParam() == "Synchronized") - { - check_rank(slic::internal::test_tag_stream.str(), rank); - } - else - { - check_ranks(slic::internal::test_tag_stream.str(), nranks); - } - check_rank_count(slic::internal::test_tag_stream.str(), GetParam(), nranks); - } - slic::internal::clear_streams(); - - SLIC_INFO_TAGGED("this message should not be logged (no tag given)!", ""); - slic::flushStreams(); - EXPECT_TRUE(slic::internal::are_all_streams_empty()); - - SLIC_INFO_TAGGED("this message should not be logged (tag DNE)!", "tag404"); - slic::flushStreams(); - EXPECT_TRUE(slic::internal::are_all_streams_empty()); - - SLIC_INFO_IF(false, "this message should not be logged!"); - slic::flushStreams(); - EXPECT_TRUE(slic::internal::are_all_streams_empty()); - - SLIC_INFO_IF(true, "this message is logged!"); - expected_line_number = __LINE__ - 1; - slic::flushStreams(); - if(GetParam() == "Synchronized" || (GetParam() == "Lumberjack" && rank == 0)) - { - EXPECT_FALSE(slic::internal::are_all_streams_empty()); - check_level(slic::internal::test_stream.str(), "INFO"); - check_msg(slic::internal::test_stream.str(), "this message is logged!"); - check_file(slic::internal::test_stream.str()); - check_line(slic::internal::test_stream.str(), expected_line_number); - if(GetParam() == "Synchronized") - { - check_rank(slic::internal::test_stream.str(), rank); - } - else - { - check_ranks(slic::internal::test_stream.str(), nranks); - } - check_rank_count(slic::internal::test_stream.str(), GetParam(), nranks); - } - slic::internal::clear_streams(); - - // Check selective filtering based on root == false - axom::slic::setIsRoot(false); - SLIC_INFO_ROOT_IF(false, "this message should not be logged!"); - slic::flushStreams(); - EXPECT_TRUE(slic::internal::are_all_streams_empty()); - - // Check selective filter based on root == true - axom::slic::setIsRoot(true); - SLIC_INFO_ROOT_IF(true, "this message is logged!"); - expected_line_number = __LINE__ - 1; - slic::flushStreams(); - if(GetParam() == "Synchronized" || (GetParam() == "Lumberjack" && rank == 0)) - { - EXPECT_FALSE(slic::internal::are_all_streams_empty()); - check_level(slic::internal::test_stream.str(), "INFO"); - check_msg(slic::internal::test_stream.str(), "this message is logged!"); - check_file(slic::internal::test_stream.str()); - check_line(slic::internal::test_stream.str(), expected_line_number); - if(GetParam() == "Synchronized") - { - check_rank(slic::internal::test_stream.str(), rank); - } - else - { - check_ranks(slic::internal::test_stream.str(), nranks); - } - check_rank_count(slic::internal::test_stream.str(), GetParam(), nranks); - } - slic::internal::clear_streams(); - - // is root, but conditional is false -> no message - axom::slic::setIsRoot(true); - SLIC_INFO_ROOT_IF(false, "this message should not be logged!"); - slic::flushStreams(); - EXPECT_TRUE(slic::internal::are_all_streams_empty()); - - // is not root, and conditional is true -> no message - axom::slic::setIsRoot(false); - SLIC_INFO_ROOT_IF(true, "this message should not be logged!"); - slic::flushStreams(); - EXPECT_TRUE(slic::internal::are_all_streams_empty()); - - // Check for one rank being root - axom::slic::setIsRoot(rank == 0); - SLIC_INFO_ROOT_IF(true, "this message is logged!"); - expected_line_number = __LINE__ - 1; - slic::flushStreams(); - if(rank == 0) - { - EXPECT_FALSE(slic::internal::are_all_streams_empty()); - check_level(slic::internal::test_stream.str(), "INFO"); - check_msg(slic::internal::test_stream.str(), "this message is logged!"); - check_file(slic::internal::test_stream.str()); - check_line(slic::internal::test_stream.str(), expected_line_number); - check_rank(slic::internal::test_stream.str(), rank); - check_rank_count(slic::internal::test_stream.str(), GetParam(), 1); - } - else - { - EXPECT_TRUE(slic::internal::are_all_streams_empty()); - } - slic::internal::clear_streams(); - - // Check for more than one rank being root - axom::slic::setIsRoot((rank % 2) == 0); - SLIC_INFO_ROOT_IF(true, "this message is logged!"); - expected_line_number = __LINE__ - 1; - slic::flushStreams(); - if(((rank % 2) == 0 && GetParam() == "Synchronized") || (rank == 0 && GetParam() == "Lumberjack")) - { - EXPECT_FALSE(slic::internal::are_all_streams_empty()); - check_level(slic::internal::test_stream.str(), "INFO"); - check_msg(slic::internal::test_stream.str(), "this message is logged!"); - check_file(slic::internal::test_stream.str()); - check_line(slic::internal::test_stream.str(), expected_line_number); - if(GetParam() == "Synchronized") - { - check_rank(slic::internal::test_stream.str(), rank); - } - else - { - for(int i = 0; i < nranks; i += 2) - { - check_rank(slic::internal::test_stream.str(), i); - } - } - check_rank_count(slic::internal::test_stream.str(), GetParam(), (nranks / 2) + (nranks % 2)); - } - else - { - EXPECT_TRUE(slic::internal::are_all_streams_empty()); - } - slic::internal::clear_streams(); -} - -//------------------------------------------------------------------------------ -TEST_P(SlicMacrosParallel, test_debug_macros) -{ - int expected_line_number; - - EXPECT_TRUE(slic::internal::are_all_streams_empty()); - SLIC_DEBUG("test debug message"); - expected_line_number = __LINE__ - 1; - slic::flushStreams(); -#ifdef AXOM_DEBUG - if(GetParam() == "Synchronized" || (GetParam() == "Lumberjack" && rank == 0)) - { - EXPECT_FALSE(slic::internal::are_all_streams_empty()); - check_level(slic::internal::test_stream.str(), "DEBUG"); - check_msg(slic::internal::test_stream.str(), "test debug message"); - check_file(slic::internal::test_stream.str()); - check_line(slic::internal::test_stream.str(), expected_line_number); - if(GetParam() == "Synchronized") - { - check_rank(slic::internal::test_stream.str(), rank); - } - else - { - check_ranks(slic::internal::test_stream.str(), nranks); - } - check_rank_count(slic::internal::test_stream.str(), GetParam(), nranks); - } - slic::internal::clear_streams(); -#else - // SLIC_DEBUG macros only log messages when AXOM_DEBUG is defined - EXPECT_TRUE(slic::internal::are_all_streams_empty()); -#endif - - SLIC_DEBUG_IF(false, "this message should not be logged!"); - slic::flushStreams(); - EXPECT_TRUE(slic::internal::are_all_streams_empty()); - - SLIC_DEBUG_IF(true, "this message is logged!"); - expected_line_number = __LINE__ - 1; - slic::flushStreams(); -#ifdef AXOM_DEBUG - if(GetParam() == "Synchronized" || (GetParam() == "Lumberjack" && rank == 0)) - { - EXPECT_FALSE(slic::internal::are_all_streams_empty()); - check_level(slic::internal::test_stream.str(), "DEBUG"); - check_msg(slic::internal::test_stream.str(), "this message is logged!"); + check_msg(slic::internal::test_stream.str(), + "test info message only for normal message-level stream"); check_file(slic::internal::test_stream.str()); check_line(slic::internal::test_stream.str(), expected_line_number); if(GetParam() == "Synchronized") @@ -832,122 +551,142 @@ TEST_P(SlicMacrosParallel, test_debug_macros) check_ranks(slic::internal::test_stream.str(), nranks); } check_rank_count(slic::internal::test_stream.str(), GetParam(), nranks); + + EXPECT_FALSE(slic::internal::is_tag_stream_empty()); + check_level(slic::internal::test_tag_stream.str(), "INFO"); + check_msg(slic::internal::test_tag_stream.str(), + "test tagged info message only for tagged stream"); + check_file(slic::internal::test_tag_stream.str()); + check_line(slic::internal::test_tag_stream.str(), expected_tag_number); + check_tag(slic::internal::test_tag_stream.str(), "myTag"); + if(GetParam() == "Synchronized") + { + check_rank(slic::internal::test_tag_stream.str(), rank); + } + else + { + check_ranks(slic::internal::test_tag_stream.str(), nranks); + } + check_rank_count(slic::internal::test_tag_stream.str(), GetParam(), nranks); } slic::internal::clear_streams(); -#else - // SLIC_DEBUG macros only log messages when AXOM_DEBUG is defined + + SLIC_INFO_TAGGED("this message should not be logged (no tag given)!", ""); + slic::flushStreams(); EXPECT_TRUE(slic::internal::are_all_streams_empty()); -#endif + + SLIC_INFO_TAGGED("this message should not be logged (tag DNE)!", "tag404"); + slic::flushStreams(); + EXPECT_TRUE(slic::internal::are_all_streams_empty()); + + SLIC_INFO_IF(false, "this message should not be logged!"); + slic::flushStreams(); + EXPECT_TRUE(slic::internal::are_all_streams_empty()); + + EXPECT_SLIC_LOG_ALL_RANKS(SLIC_INFO_IF(true, "this message is logged!"), + "INFO", + "this message is logged!"); // Check selective filtering based on root == false axom::slic::setIsRoot(false); - SLIC_DEBUG_ROOT_IF(false, "this message should not be logged!"); + SLIC_INFO_ROOT_IF(false, "this message should not be logged!"); slic::flushStreams(); EXPECT_TRUE(slic::internal::are_all_streams_empty()); // Check selective filter based on root == true axom::slic::setIsRoot(true); - SLIC_DEBUG_ROOT_IF(true, "this message is logged!"); - expected_line_number = __LINE__ - 1; - slic::flushStreams(); -#ifdef AXOM_DEBUG - if(GetParam() == "Synchronized" || (GetParam() == "Lumberjack" && rank == 0)) - { - EXPECT_FALSE(slic::internal::are_all_streams_empty()); - check_level(slic::internal::test_stream.str(), "DEBUG"); - check_msg(slic::internal::test_stream.str(), "this message is logged!"); - check_file(slic::internal::test_stream.str()); - check_line(slic::internal::test_stream.str(), expected_line_number); - if(GetParam() == "Synchronized") - { - check_rank(slic::internal::test_stream.str(), rank); - } - else - { - check_ranks(slic::internal::test_stream.str(), nranks); - } - check_rank_count(slic::internal::test_stream.str(), GetParam(), nranks); - } - slic::internal::clear_streams(); -#else - // SLIC_DEBUG macros only log messages when AXOM_DEBUG is defined - EXPECT_TRUE(slic::internal::are_all_streams_empty()); -#endif + EXPECT_SLIC_LOG_ALL_RANKS(SLIC_INFO_ROOT_IF(true, "this message is logged!"), + "INFO", + "this message is logged!"); // is root, but conditional is false -> no message axom::slic::setIsRoot(true); - SLIC_DEBUG_ROOT_IF(false, "this message should not be logged!"); + SLIC_INFO_ROOT_IF(false, "this message should not be logged!"); slic::flushStreams(); EXPECT_TRUE(slic::internal::are_all_streams_empty()); // is not root, and conditional is true -> no message axom::slic::setIsRoot(false); - SLIC_DEBUG_ROOT_IF(true, "this message should not be logged!"); + SLIC_INFO_ROOT_IF(true, "this message should not be logged!"); slic::flushStreams(); EXPECT_TRUE(slic::internal::are_all_streams_empty()); - // Check for one rank being root - axom::slic::setIsRoot(rank == 0); - SLIC_DEBUG_ROOT_IF(true, "this message is logged!"); - expected_line_number = __LINE__ - 1; - slic::flushStreams(); + // Check for message only on root + EXPECT_SLIC_LOG_ROOT(SLIC_INFO_ROOT_IF(true, "this message is logged!"), + "INFO", + "this message is logged!"); + + // Check for message on every even rank only + EXPECT_SLIC_LOG_EVEN(SLIC_INFO_IF((rank % 2) == 0, "this message is logged!"), + "INFO", + "this message is logged!"); +} + +//------------------------------------------------------------------------------ +TEST_P(SlicMacrosParallel, test_debug_macros) +{ + EXPECT_TRUE(slic::internal::are_all_streams_empty()); + #ifdef AXOM_DEBUG - if(rank == 0) - { - EXPECT_FALSE(slic::internal::are_all_streams_empty()); - check_level(slic::internal::test_stream.str(), "DEBUG"); - check_msg(slic::internal::test_stream.str(), "this message is logged!"); - check_file(slic::internal::test_stream.str()); - check_line(slic::internal::test_stream.str(), expected_line_number); - check_rank(slic::internal::test_stream.str(), rank); - check_rank_count(slic::internal::test_stream.str(), GetParam(), 1); - } - else - { - EXPECT_TRUE(slic::internal::are_all_streams_empty()); - } - slic::internal::clear_streams(); + + EXPECT_SLIC_LOG_ALL_RANKS(SLIC_DEBUG("test debug message"), "DEBUG", "test debug message"); + + EXPECT_SLIC_LOG_ALL_RANKS(SLIC_DEBUG_IF(true, "this message is logged!"), + "DEBUG", + "this message is logged!"); + + // Check selective filter based on root == true + axom::slic::setIsRoot(true); + EXPECT_SLIC_LOG_ALL_RANKS(SLIC_DEBUG_ROOT_IF(true, "this message is logged!"), + "DEBUG", + "this message is logged!"); + + // Check for message only on root + EXPECT_SLIC_LOG_ROOT(SLIC_DEBUG_ROOT_IF(true, "this message is logged!"), + "DEBUG", + "this message is logged!"); + + // Check for message on every even rank only + EXPECT_SLIC_LOG_EVEN(SLIC_DEBUG_IF((rank % 2) == 0, "this message is logged!"), + "DEBUG", + "this message is logged!"); + #else // SLIC_DEBUG macros only log messages when AXOM_DEBUG is defined - EXPECT_TRUE(slic::internal::are_all_streams_empty()); -#endif + SLIC_DEBUG("test debug message"); + + SLIC_DEBUG_IF(true, "this message is logged!"); - // Check for more than one rank being root - axom::slic::setIsRoot((rank % 2) == 0); + axom::slic::setIsRoot(true); SLIC_DEBUG_ROOT_IF(true, "this message is logged!"); - expected_line_number = __LINE__ - 1; + + SLIC_DEBUG_IF((rank % 2) == 0, "this message is logged!"); + slic::flushStreams(); -#ifdef AXOM_DEBUG - if(((rank % 2) == 0 && GetParam() == "Synchronized") || (rank == 0 && GetParam() == "Lumberjack")) - { - EXPECT_FALSE(slic::internal::are_all_streams_empty()); - check_level(slic::internal::test_stream.str(), "DEBUG"); - check_msg(slic::internal::test_stream.str(), "this message is logged!"); - check_file(slic::internal::test_stream.str()); - check_line(slic::internal::test_stream.str(), expected_line_number); - if(GetParam() == "Synchronized") - { - check_rank(slic::internal::test_stream.str(), rank); - } - else - { - for(int i = 0; i < nranks; i += 2) - { - check_rank(slic::internal::test_stream.str(), i); - } - } - check_rank_count(slic::internal::test_stream.str(), GetParam(), (nranks / 2) + (nranks % 2)); - } - else - { - EXPECT_TRUE(slic::internal::are_all_streams_empty()); - } - slic::internal::clear_streams(); -#else - // SLIC_DEBUG macros only log messages when AXOM_DEBUG is defined EXPECT_TRUE(slic::internal::are_all_streams_empty()); - AXOM_UNUSED_VAR(expected_line_number); #endif + + SLIC_DEBUG_IF(false, "this message should not be logged!"); + slic::flushStreams(); + EXPECT_TRUE(slic::internal::are_all_streams_empty()); + + // Check selective filtering based on root == false + axom::slic::setIsRoot(false); + SLIC_DEBUG_ROOT_IF(false, "this message should not be logged!"); + slic::flushStreams(); + EXPECT_TRUE(slic::internal::are_all_streams_empty()); + + // is root, but conditional is false -> no message + axom::slic::setIsRoot(true); + SLIC_DEBUG_ROOT_IF(false, "this message should not be logged!"); + slic::flushStreams(); + EXPECT_TRUE(slic::internal::are_all_streams_empty()); + + // is not root, and conditional is true -> no message + axom::slic::setIsRoot(false); + SLIC_DEBUG_ROOT_IF(true, "this message should not be logged!"); + slic::flushStreams(); + EXPECT_TRUE(slic::internal::are_all_streams_empty()); } TEST_P(SlicMacrosParallel, test_abort_error_macros) @@ -1216,144 +955,60 @@ TEST_P(SlicMacrosParallel, test_abort_warning_macros) //------------------------------------------------------------------------------ TEST_P(SlicMacrosParallel, test_assert_macros) { - [[maybe_unused]] int expected_line_number; - slic::internal::clear_streams(); EXPECT_TRUE(slic::internal::are_all_streams_empty()); - constexpr int val = 42; - SLIC_ASSERT(val < 0); - expected_line_number = __LINE__ - 1; - slic::flushStreams(); + #if defined(AXOM_DEBUG) && !defined(AXOM_DEVICE_CODE) - if(GetParam() == "Synchronized" || (GetParam() == "Lumberjack" && rank == 0)) - { - EXPECT_FALSE(slic::internal::are_all_streams_empty()); - check_level(slic::internal::test_stream.str(), "ERROR"); - check_msg(slic::internal::test_stream.str(), "Failed Assert: val < 0"); - check_file(slic::internal::test_stream.str()); - check_line(slic::internal::test_stream.str(), expected_line_number); - if(GetParam() == "Synchronized") - { - check_rank(slic::internal::test_stream.str(), rank); - } - else - { - check_ranks(slic::internal::test_stream.str(), nranks); - } - check_rank_count(slic::internal::test_stream.str(), GetParam(), nranks); - } - slic::internal::clear_streams(); + + EXPECT_SLIC_LOG_ALL_RANKS(SLIC_ASSERT(val < 0), "ERROR", "Failed Assert: val < 0"); + + EXPECT_SLIC_LOG_ALL_RANKS(SLIC_ASSERT_MSG(val < 0, "val should be negative!"), + "ERROR", + "Failed Assert: val < 0\nval should be negative!"); + #else // SLIC_ASSERT macros only log messages when AXOM_DEBUG is defined - AXOM_UNUSED_VAR(val); - AXOM_UNUSED_VAR(expected_line_number); - EXPECT_TRUE(slic::internal::are_all_streams_empty()); -#endif + SLIC_ASSERT(val < 0); + + SLIC_ASSERT_MSG(val < 0, "val should be negative!"); - SLIC_ASSERT(val > 0); slic::flushStreams(); EXPECT_TRUE(slic::internal::are_all_streams_empty()); +#endif - SLIC_ASSERT_MSG(val < 0, "val should be negative!"); - expected_line_number = __LINE__ - 1; + SLIC_ASSERT(val > 0); slic::flushStreams(); -#if defined(AXOM_DEBUG) && !defined(AXOM_DEVICE_CODE) - if(GetParam() == "Synchronized" || (GetParam() == "Lumberjack" && rank == 0)) - { - EXPECT_FALSE(slic::internal::are_all_streams_empty()); - check_level(slic::internal::test_stream.str(), "ERROR"); - check_msg(slic::internal::test_stream.str(), "Failed Assert: val < 0\nval should be negative!"); - check_file(slic::internal::test_stream.str()); - check_line(slic::internal::test_stream.str(), expected_line_number); - if(GetParam() == "Synchronized") - { - check_rank(slic::internal::test_stream.str(), rank); - } - else - { - check_ranks(slic::internal::test_stream.str(), nranks); - } - check_rank_count(slic::internal::test_stream.str(), GetParam(), nranks); - } - slic::internal::clear_streams(); -#else - // SLIC_ASSERT macros only log messages when AXOM_DEBUG is defined - AXOM_UNUSED_VAR(val); - AXOM_UNUSED_VAR(expected_line_number); EXPECT_TRUE(slic::internal::are_all_streams_empty()); -#endif } // ------------------------------------------------------------------------------ TEST_P(SlicMacrosParallel, test_check_macros) { - [[maybe_unused]] int expected_line_number; - EXPECT_TRUE(slic::internal::are_all_streams_empty()); constexpr int val = 42; - SLIC_CHECK(val < 0); - expected_line_number = __LINE__ - 1; - slic::flushStreams(); + #if defined(AXOM_DEBUG) && !defined(AXOM_DEVICE_CODE) - if(GetParam() == "Synchronized" || (GetParam() == "Lumberjack" && rank == 0)) - { - EXPECT_FALSE(slic::internal::are_all_streams_empty()); - check_level(slic::internal::test_stream.str(), "WARNING"); - check_msg(slic::internal::test_stream.str(), "Failed Check: val < 0"); - check_file(slic::internal::test_stream.str()); - check_line(slic::internal::test_stream.str(), expected_line_number); - if(GetParam() == "Synchronized") - { - check_rank(slic::internal::test_stream.str(), rank); - } - else - { - check_ranks(slic::internal::test_stream.str(), nranks); - } - check_rank_count(slic::internal::test_stream.str(), GetParam(), nranks); - } - slic::internal::clear_streams(); + EXPECT_SLIC_LOG_ALL_RANKS(SLIC_CHECK(val < 0), "WARNING", "Failed Check: val < 0"); + + EXPECT_SLIC_LOG_ALL_RANKS(SLIC_CHECK_MSG(val < 0, "val should be negative!"), + "WARNING", + "Failed Check: val < 0\nval should be negative!"); + #else // SLIC_CHECK macros only log messages when AXOM_DEBUG is defined - AXOM_UNUSED_VAR(val); - AXOM_UNUSED_VAR(expected_line_number); - EXPECT_TRUE(slic::internal::are_all_streams_empty()); -#endif + SLIC_CHECK(val < 0); + + SLIC_CHECK_MSG(val < 0, "val should be negative!"); - SLIC_CHECK(val > 0); slic::flushStreams(); EXPECT_TRUE(slic::internal::are_all_streams_empty()); +#endif - SLIC_CHECK_MSG(val < 0, "val should be negative!"); - expected_line_number = __LINE__ - 1; + SLIC_CHECK(val > 0); slic::flushStreams(); -#if defined(AXOM_DEBUG) && !defined(AXOM_DEVICE_CODE) - if(GetParam() == "Synchronized" || (GetParam() == "Lumberjack" && rank == 0)) - { - EXPECT_FALSE(slic::internal::are_all_streams_empty()); - check_level(slic::internal::test_stream.str(), "WARNING"); - check_msg(slic::internal::test_stream.str(), "Failed Check: val < 0\nval should be negative!"); - check_file(slic::internal::test_stream.str()); - check_line(slic::internal::test_stream.str(), expected_line_number); - if(GetParam() == "Synchronized") - { - check_rank(slic::internal::test_stream.str(), rank); - } - else - { - check_ranks(slic::internal::test_stream.str(), nranks); - } - check_rank_count(slic::internal::test_stream.str(), GetParam(), nranks); - } - slic::internal::clear_streams(); -#else - // SLIC_CHECK macros only log messages when AXOM_DEBUG is defined - AXOM_UNUSED_VAR(val); - AXOM_UNUSED_VAR(expected_line_number); EXPECT_TRUE(slic::internal::are_all_streams_empty()); -#endif } //------------------------------------------------------------------------------ From 625ea7bc94832b60f79b585a070b5590d9647eb9 Mon Sep 17 00:00:00 2001 From: Brian Manh Hien Han Date: Thu, 19 Mar 2026 12:55:40 -0700 Subject: [PATCH 013/986] release fixes --- src/axom/slic/tests/slic_macros.cpp | 5 +++-- src/axom/slic/tests/slic_macros_parallel.cpp | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/axom/slic/tests/slic_macros.cpp b/src/axom/slic/tests/slic_macros.cpp index 71fab20326..b4d1bff9c8 100644 --- a/src/axom/slic/tests/slic_macros.cpp +++ b/src/axom/slic/tests/slic_macros.cpp @@ -139,6 +139,7 @@ void check_level_msg_line_file(const std::string& level, const std::string& mess { EXPECT_FALSE(slic::internal::is_stream_empty()); const std::string str = slic::internal::test_stream.str(); + check_level(str, level); check_msg(str, message); check_line(str, expected_line); @@ -370,8 +371,8 @@ TEST(slic_macros, test_debug_macros) SLIC_DEBUG_IF_ONCE(true, "this message is logged!"); axom::slic::setIsRoot(true); - SLIC_DEBUG_ROOT(true, "this message is logged!"); - SLIC_DEBUG_ROOT_ONCE(true, "this message is logged!"); + SLIC_DEBUG_ROOT("this message is logged!"); + SLIC_DEBUG_ROOT_ONCE("this message is logged!"); SLIC_DEBUG_ROOT_IF(true, "this message is logged!"); SLIC_DEBUG_ROOT_IF_ONCE(true, "this message is logged!"); diff --git a/src/axom/slic/tests/slic_macros_parallel.cpp b/src/axom/slic/tests/slic_macros_parallel.cpp index 6b7519e24c..8cf91d3bfc 100644 --- a/src/axom/slic/tests/slic_macros_parallel.cpp +++ b/src/axom/slic/tests/slic_macros_parallel.cpp @@ -969,6 +969,8 @@ TEST_P(SlicMacrosParallel, test_assert_macros) #else // SLIC_ASSERT macros only log messages when AXOM_DEBUG is defined + AXOM_UNUSED_VAR(val); + SLIC_ASSERT(val < 0); SLIC_ASSERT_MSG(val < 0, "val should be negative!"); @@ -998,6 +1000,8 @@ TEST_P(SlicMacrosParallel, test_check_macros) #else // SLIC_CHECK macros only log messages when AXOM_DEBUG is defined + AXOM_UNUSED_VAR(val); + SLIC_CHECK(val < 0); SLIC_CHECK_MSG(val < 0, "val should be negative!"); From 4ad8d8c4a96e41a5f574886bfb644c274ec8aa63 Mon Sep 17 00:00:00 2001 From: Brian Manh Hien Han Date: Thu, 19 Mar 2026 13:50:53 -0700 Subject: [PATCH 014/986] not pretty - turn clang-format on/off to keep __LINE__ as expected --- src/axom/slic/tests/slic_macros.cpp | 91 ++++++++--------- src/axom/slic/tests/slic_macros_parallel.cpp | 102 +++++++++---------- 2 files changed, 88 insertions(+), 105 deletions(-) diff --git a/src/axom/slic/tests/slic_macros.cpp b/src/axom/slic/tests/slic_macros.cpp index b4d1bff9c8..2e94ffce97 100644 --- a/src/axom/slic/tests/slic_macros.cpp +++ b/src/axom/slic/tests/slic_macros.cpp @@ -182,9 +182,10 @@ TEST(slic_macros, test_error_macros) SLIC_ERROR_IF(false, "this message should not be logged!"); EXPECT_TRUE(slic::internal::is_stream_empty()); - EXPECT_SLIC_LOG(SLIC_ERROR_IF(true, "this message is logged!"), - "ERROR", - "this message is logged!"); + // Single line - Placement of ")" matters for __LINE__ for slic call and checking + // clang-format off + EXPECT_SLIC_LOG(SLIC_ERROR_IF(true, "this message is logged!"), "ERROR", "this message is logged!"); + // clang-format on // Check selective filtering based on root == false axom::slic::setIsRoot(false); @@ -194,9 +195,10 @@ TEST(slic_macros, test_error_macros) // Check selective filter based on root == true axom::slic::setIsRoot(true); SLIC_ERROR_ROOT_IF(true, "this message is logged!"); - EXPECT_SLIC_LOG(SLIC_ERROR_ROOT_IF(true, "this message is logged!"), - "ERROR", - "this message is logged!"); + + // clang-format off + EXPECT_SLIC_LOG(SLIC_ERROR_ROOT_IF(true, "this message is logged!"), "ERROR", "this message is logged!"); + // clang-format on // is root, but conditional is false -> no message axom::slic::setIsRoot(true); @@ -216,9 +218,10 @@ TEST(slic_macros, test_warning_macros) EXPECT_SLIC_LOG(SLIC_WARNING("test warning message"), "WARNING", "test warning message"); // Called once per call site - EXPECT_SLIC_ONCE(SLIC_WARNING_ONCE("test warning message once"), - "WARNING", - "test warning message once"); + // Single line - Placement of ")" matters for __LINE__ for slic call and checking + // clang-format off + EXPECT_SLIC_ONCE(SLIC_WARNING_ONCE("test warning message once"), "WARNING", "test warning message once"); + // clang-format on // Two different call sites, will have two messages SLIC_WARNING_ONCE("test warning message #1"); @@ -232,13 +235,11 @@ TEST(slic_macros, test_warning_macros) SLIC_WARNING_IF_ONCE(false, "this message should not be logged!"); EXPECT_TRUE(slic::internal::is_stream_empty()); - EXPECT_SLIC_LOG(SLIC_WARNING_IF(true, "this message is logged!"), - "WARNING", - "this message is logged!"); + // clang-format off + EXPECT_SLIC_LOG(SLIC_WARNING_IF(true, "this message is logged!"), "WARNING", "this message is logged!"); - EXPECT_SLIC_ONCE(SLIC_WARNING_IF_ONCE(true, "this message is logged once!"), - "WARNING", - "this message is logged once!"); + EXPECT_SLIC_ONCE(SLIC_WARNING_IF_ONCE(true, "this message is logged once!"), "WARNING", "this message is logged once!"); + // clang-format on // Check selective filtering based on root == false axom::slic::setIsRoot(false); @@ -249,22 +250,16 @@ TEST(slic_macros, test_warning_macros) EXPECT_TRUE(slic::internal::is_stream_empty()); axom::slic::setIsRoot(true); - EXPECT_SLIC_LOG(SLIC_WARNING_ROOT("this message is logged on root!"), - "WARNING", - "this message is logged on root!"); + // clang-format off + EXPECT_SLIC_LOG(SLIC_WARNING_ROOT("this message is logged on root!"), "WARNING", "this message is logged on root!"); - EXPECT_SLIC_ONCE(SLIC_WARNING_ROOT_ONCE("this message is logged on root once!"), - "WARNING", - "this message is logged on root once!"); + EXPECT_SLIC_ONCE(SLIC_WARNING_ROOT_ONCE("this message is logged on root once!"), "WARNING", "this message is logged on root once!"); // Check selective filter based on root == true - EXPECT_SLIC_LOG(SLIC_WARNING_ROOT_IF(true, "this message is logged!"), - "WARNING", - "this message is logged!"); + EXPECT_SLIC_LOG(SLIC_WARNING_ROOT_IF(true, "this message is logged!"), "WARNING", "this message is logged!"); - EXPECT_SLIC_ONCE(SLIC_WARNING_ROOT_IF_ONCE(true, "this message is logged once!"), - "WARNING", - "this message is logged once!"); + EXPECT_SLIC_ONCE(SLIC_WARNING_ROOT_IF_ONCE(true, "this message is logged once!"), "WARNING", "this message is logged once!"); + // clang-format on // is root, but conditional is false -> no message axom::slic::setIsRoot(true); @@ -299,18 +294,17 @@ TEST(slic_macros, test_info_macros) EXPECT_SLIC_LOG(SLIC_INFO_IF(true, "this message is logged!"), "INFO", "this message is logged!"); - EXPECT_SLIC_ONCE(SLIC_INFO_IF_ONCE(true, "this message is logged once!"), - "INFO", - "this message is logged once!"); + // Single line - Placement of ")" matters for __LINE__ for slic call and checking + // clang-format off + EXPECT_SLIC_ONCE(SLIC_INFO_IF_ONCE(true, "this message is logged once!"), "INFO", "this message is logged once!"); + // clang-format on axom::slic::setIsRoot(true); - EXPECT_SLIC_LOG(SLIC_INFO_ROOT("this message is logged on root!"), - "INFO", - "this message is logged on root!"); + // clang-format off + EXPECT_SLIC_LOG(SLIC_INFO_ROOT("this message is logged on root!"), "INFO", "this message is logged on root!"); - EXPECT_SLIC_ONCE(SLIC_INFO_ROOT_ONCE("this message is logged on root once!"), - "INFO", - "this message is logged on root once!"); + EXPECT_SLIC_ONCE(SLIC_INFO_ROOT_ONCE("this message is logged on root once!"), "INFO", "this message is logged on root once!"); + // clang-format on // is root, but conditional is false -> no message SLIC_INFO_ROOT_IF(false, "this message should not be logged!"); @@ -337,29 +331,24 @@ TEST(slic_macros, test_debug_macros) EXPECT_SLIC_ONCE(SLIC_DEBUG_ONCE("test debug message once"), "DEBUG", "test debug message once"); - EXPECT_SLIC_LOG(SLIC_DEBUG_IF(true, "this message is logged!"), - "DEBUG", - "this message is logged!"); + // Single line - Placement of ")" matters for __LINE__ for slic call and checking + // clang-format off + EXPECT_SLIC_LOG(SLIC_DEBUG_IF(true, "this message is logged!"), "DEBUG", "this message is logged!"); - EXPECT_SLIC_ONCE(SLIC_DEBUG_IF_ONCE(true, "this message is logged once!"), - "DEBUG", - "this message is logged once!"); + EXPECT_SLIC_ONCE(SLIC_DEBUG_IF_ONCE(true, "this message is logged once!"), "DEBUG", "this message is logged once!"); + // clang-format on axom::slic::setIsRoot(true); EXPECT_SLIC_LOG(SLIC_DEBUG_ROOT("this message is logged!"), "DEBUG", "this message is logged!"); - EXPECT_SLIC_ONCE(SLIC_DEBUG_ROOT_ONCE("this message is logged once!"), - "DEBUG", - "this message is logged once!"); + // clang-format off + EXPECT_SLIC_ONCE(SLIC_DEBUG_ROOT_ONCE("this message is logged once!"), "DEBUG", "this message is logged once!"); // Check selective filter based on root == true - EXPECT_SLIC_LOG(SLIC_DEBUG_ROOT_IF(true, "this message is logged!"), - "DEBUG", - "this message is logged!"); + EXPECT_SLIC_LOG(SLIC_DEBUG_ROOT_IF(true, "this message is logged!"), "DEBUG", "this message is logged!"); - EXPECT_SLIC_ONCE(SLIC_DEBUG_ROOT_IF_ONCE(true, "this message is logged once!"), - "DEBUG", - "this message is logged once!"); + EXPECT_SLIC_ONCE(SLIC_DEBUG_ROOT_IF_ONCE(true, "this message is logged once!"), "DEBUG", "this message is logged once!"); + // clang-format on #else // SLIC_DEBUG macros only log messages when AXOM_DEBUG is defined diff --git a/src/axom/slic/tests/slic_macros_parallel.cpp b/src/axom/slic/tests/slic_macros_parallel.cpp index 8cf91d3bfc..163f60e52c 100644 --- a/src/axom/slic/tests/slic_macros_parallel.cpp +++ b/src/axom/slic/tests/slic_macros_parallel.cpp @@ -407,9 +407,10 @@ TEST_P(SlicMacrosParallel, test_error_macros) slic::flushStreams(); EXPECT_TRUE(slic::internal::are_all_streams_empty()); - EXPECT_SLIC_LOG_ALL_RANKS(SLIC_ERROR_IF(true, "this message is logged!"), - "ERROR", - "this message is logged!"); + // Single line - Placement of ")" matters for __LINE__ for slic call and checking + // clang-format off + EXPECT_SLIC_LOG_ALL_RANKS(SLIC_ERROR_IF(true, "this message is logged!"), "ERROR", "this message is logged!"); + // clang-format on // Check selective filtering based on root == false axom::slic::setIsRoot(false); @@ -419,9 +420,9 @@ TEST_P(SlicMacrosParallel, test_error_macros) // Check selective filter based on root == true axom::slic::setIsRoot(true); - EXPECT_SLIC_LOG_ALL_RANKS(SLIC_ERROR_ROOT_IF(true, "this message is logged!"), - "ERROR", - "this message is logged!"); + // clang-format off + EXPECT_SLIC_LOG_ALL_RANKS(SLIC_ERROR_ROOT_IF(true, "this message is logged!"), "ERROR", "this message is logged!"); + // clang-format on // is root, but conditional is false -> no message axom::slic::setIsRoot(true); @@ -435,15 +436,13 @@ TEST_P(SlicMacrosParallel, test_error_macros) slic::flushStreams(); EXPECT_TRUE(slic::internal::are_all_streams_empty()); + // clang-format off // Check for message only on root - EXPECT_SLIC_LOG_ROOT(SLIC_ERROR_ROOT_IF(true, "this message is logged!"), - "ERROR", - "this message is logged!"); + EXPECT_SLIC_LOG_ROOT(SLIC_ERROR_ROOT_IF(true, "this message is logged!"), "ERROR", "this message is logged!"); // Check for message on every even rank only - EXPECT_SLIC_LOG_EVEN(SLIC_ERROR_IF((rank % 2) == 0, "this message is logged!"), - "ERROR", - "this message is logged!"); + EXPECT_SLIC_LOG_EVEN(SLIC_ERROR_IF((rank % 2) == 0, "this message is logged!"), "ERROR", "this message is logged!"); + // clang-format on } //------------------------------------------------------------------------------ @@ -457,9 +456,10 @@ TEST_P(SlicMacrosParallel, test_warning_macros) slic::flushStreams(); EXPECT_TRUE(slic::internal::are_all_streams_empty()); - EXPECT_SLIC_LOG_ALL_RANKS(SLIC_WARNING_IF(true, "this message is logged!"), - "WARNING", - "this message is logged!"); + // Single line - Placement of ")" matters for __LINE__ for slic call and checking + // clang-format off + EXPECT_SLIC_LOG_ALL_RANKS(SLIC_WARNING_IF(true, "this message is logged!"), "WARNING", "this message is logged!"); + // clang-format on // Check selective filtering based on root == false axom::slic::setIsRoot(false); @@ -469,9 +469,9 @@ TEST_P(SlicMacrosParallel, test_warning_macros) // Check selective filter based on root == true axom::slic::setIsRoot(true); - EXPECT_SLIC_LOG_ALL_RANKS(SLIC_WARNING_ROOT_IF(true, "this message is logged!"), - "WARNING", - "this message is logged!"); + // clang-format off + EXPECT_SLIC_LOG_ALL_RANKS(SLIC_WARNING_ROOT_IF(true, "this message is logged!"), "WARNING", "this message is logged!"); + // clang-format on // is root, but conditional is false -> no message axom::slic::setIsRoot(true); @@ -485,15 +485,13 @@ TEST_P(SlicMacrosParallel, test_warning_macros) slic::flushStreams(); EXPECT_TRUE(slic::internal::are_all_streams_empty()); + // clang-format off // Check for message only on root - EXPECT_SLIC_LOG_ROOT(SLIC_WARNING_ROOT_IF(true, "this message is logged!"), - "WARNING", - "this message is logged!"); + EXPECT_SLIC_LOG_ROOT(SLIC_WARNING_ROOT_IF(true, "this message is logged!"), "WARNING", "this message is logged!"); // Check for message on every even rank only - EXPECT_SLIC_LOG_EVEN(SLIC_WARNING_IF((rank % 2) == 0, "this message is logged!"), - "WARNING", - "this message is logged!"); + EXPECT_SLIC_LOG_EVEN(SLIC_WARNING_IF((rank % 2) == 0, "this message is logged!"), "WARNING", "this message is logged!"); + // clang-format on } //------------------------------------------------------------------------------ @@ -583,9 +581,10 @@ TEST_P(SlicMacrosParallel, test_info_macros) slic::flushStreams(); EXPECT_TRUE(slic::internal::are_all_streams_empty()); - EXPECT_SLIC_LOG_ALL_RANKS(SLIC_INFO_IF(true, "this message is logged!"), - "INFO", - "this message is logged!"); + // Single line - Placement of ")" matters for __LINE__ for slic call and checking + // clang-format off + EXPECT_SLIC_LOG_ALL_RANKS(SLIC_INFO_IF(true, "this message is logged!"), "INFO", "this message is logged!"); + // clang-format on // Check selective filtering based on root == false axom::slic::setIsRoot(false); @@ -595,9 +594,9 @@ TEST_P(SlicMacrosParallel, test_info_macros) // Check selective filter based on root == true axom::slic::setIsRoot(true); - EXPECT_SLIC_LOG_ALL_RANKS(SLIC_INFO_ROOT_IF(true, "this message is logged!"), - "INFO", - "this message is logged!"); + // clang-format off + EXPECT_SLIC_LOG_ALL_RANKS(SLIC_INFO_ROOT_IF(true, "this message is logged!"), "INFO", "this message is logged!"); + // clang-format on // is root, but conditional is false -> no message axom::slic::setIsRoot(true); @@ -611,15 +610,13 @@ TEST_P(SlicMacrosParallel, test_info_macros) slic::flushStreams(); EXPECT_TRUE(slic::internal::are_all_streams_empty()); + // clang-format off // Check for message only on root - EXPECT_SLIC_LOG_ROOT(SLIC_INFO_ROOT_IF(true, "this message is logged!"), - "INFO", - "this message is logged!"); + EXPECT_SLIC_LOG_ROOT(SLIC_INFO_ROOT_IF(true, "this message is logged!"), "INFO", "this message is logged!"); // Check for message on every even rank only - EXPECT_SLIC_LOG_EVEN(SLIC_INFO_IF((rank % 2) == 0, "this message is logged!"), - "INFO", - "this message is logged!"); + EXPECT_SLIC_LOG_EVEN(SLIC_INFO_IF((rank % 2) == 0, "this message is logged!"), "INFO", "this message is logged!"); + // clang-format on } //------------------------------------------------------------------------------ @@ -631,25 +628,20 @@ TEST_P(SlicMacrosParallel, test_debug_macros) EXPECT_SLIC_LOG_ALL_RANKS(SLIC_DEBUG("test debug message"), "DEBUG", "test debug message"); - EXPECT_SLIC_LOG_ALL_RANKS(SLIC_DEBUG_IF(true, "this message is logged!"), - "DEBUG", - "this message is logged!"); + // Single line - Placement of ")" matters for __LINE__ for slic call and checking + // clang-format off + EXPECT_SLIC_LOG_ALL_RANKS(SLIC_DEBUG_IF(true, "this message is logged!"), "DEBUG", "this message is logged!"); // Check selective filter based on root == true axom::slic::setIsRoot(true); - EXPECT_SLIC_LOG_ALL_RANKS(SLIC_DEBUG_ROOT_IF(true, "this message is logged!"), - "DEBUG", - "this message is logged!"); + EXPECT_SLIC_LOG_ALL_RANKS(SLIC_DEBUG_ROOT_IF(true, "this message is logged!"), "DEBUG", "this message is logged!"); // Check for message only on root - EXPECT_SLIC_LOG_ROOT(SLIC_DEBUG_ROOT_IF(true, "this message is logged!"), - "DEBUG", - "this message is logged!"); + EXPECT_SLIC_LOG_ROOT(SLIC_DEBUG_ROOT_IF(true, "this message is logged!"), "DEBUG", "this message is logged!"); // Check for message on every even rank only - EXPECT_SLIC_LOG_EVEN(SLIC_DEBUG_IF((rank % 2) == 0, "this message is logged!"), - "DEBUG", - "this message is logged!"); + EXPECT_SLIC_LOG_EVEN(SLIC_DEBUG_IF((rank % 2) == 0, "this message is logged!"), "DEBUG", "this message is logged!"); + // clang-format on #else // SLIC_DEBUG macros only log messages when AXOM_DEBUG is defined @@ -963,9 +955,10 @@ TEST_P(SlicMacrosParallel, test_assert_macros) EXPECT_SLIC_LOG_ALL_RANKS(SLIC_ASSERT(val < 0), "ERROR", "Failed Assert: val < 0"); - EXPECT_SLIC_LOG_ALL_RANKS(SLIC_ASSERT_MSG(val < 0, "val should be negative!"), - "ERROR", - "Failed Assert: val < 0\nval should be negative!"); + // Single line - Placement of ")" matters for __LINE__ for slic call and checking + // clang-format off + EXPECT_SLIC_LOG_ALL_RANKS(SLIC_ASSERT_MSG(val < 0, "val should be negative!"), "ERROR", "Failed Assert: val < 0\nval should be negative!"); + // clang-format on #else // SLIC_ASSERT macros only log messages when AXOM_DEBUG is defined @@ -994,9 +987,10 @@ TEST_P(SlicMacrosParallel, test_check_macros) #if defined(AXOM_DEBUG) && !defined(AXOM_DEVICE_CODE) EXPECT_SLIC_LOG_ALL_RANKS(SLIC_CHECK(val < 0), "WARNING", "Failed Check: val < 0"); - EXPECT_SLIC_LOG_ALL_RANKS(SLIC_CHECK_MSG(val < 0, "val should be negative!"), - "WARNING", - "Failed Check: val < 0\nval should be negative!"); + // Single line - Placement of ")" matters for __LINE__ for slic call and checking + // clang-format off + EXPECT_SLIC_LOG_ALL_RANKS(SLIC_CHECK_MSG(val < 0, "val should be negative!"), "WARNING", "Failed Check: val < 0\nval should be negative!"); + // clang-format on #else // SLIC_CHECK macros only log messages when AXOM_DEBUG is defined From fe4dbb5f9dfe09317b2b8bf416552b2af0d8c97f Mon Sep 17 00:00:00 2001 From: Brian Manh Hien Han Date: Thu, 19 Mar 2026 16:16:52 -0700 Subject: [PATCH 015/986] Got a ONCE unit test working for slic_macros_parallel --- src/axom/slic/tests/slic_macros_parallel.cpp | 42 ++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/axom/slic/tests/slic_macros_parallel.cpp b/src/axom/slic/tests/slic_macros_parallel.cpp index 163f60e52c..2f1002b605 100644 --- a/src/axom/slic/tests/slic_macros_parallel.cpp +++ b/src/axom/slic/tests/slic_macros_parallel.cpp @@ -186,6 +186,21 @@ void check_rank_count(const std::string& msg, const std::string& stream_type, in EXPECT_EQ(rank_count, expected_rank_count); } +//------------------------------------------------------------------------------ +// Use level to determine number of messages - used for SLIC_*_ONCE macros +int check_msg_count(const std::string& msg, const std::string& expected_level) +{ + EXPECT_FALSE(msg.empty()); + + int count = 0; + for(size_t pos = msg.find(expected_level); pos != std::string::npos; + pos = msg.find(expected_level, pos + expected_level.size())) + { + ++count; + } + return count; +} + //------------------------------------------------------------------------------ // Checks message logged on all ranks void check_all_ranks(const std::string& stream_type, @@ -448,6 +463,8 @@ TEST_P(SlicMacrosParallel, test_error_macros) //------------------------------------------------------------------------------ TEST_P(SlicMacrosParallel, test_warning_macros) { + int expected_line_number; + EXPECT_TRUE(slic::internal::are_all_streams_empty()); EXPECT_SLIC_LOG_ALL_RANKS(SLIC_WARNING("test warning message"), "WARNING", "test warning message"); @@ -456,6 +473,31 @@ TEST_P(SlicMacrosParallel, test_warning_macros) slic::flushStreams(); EXPECT_TRUE(slic::internal::are_all_streams_empty()); + // Called once per call site; since this test is ran for parameters + // Synchronized and Lumberjack, a separate call site is needed for each parameter, + // otherwise ONCE macro logs nothing when its Lumberjack's turn to run. + for(int i = 0; i < 3; i++) + { + if(GetParam() == "Synchronized") + { + SLIC_WARNING_ONCE("test warning message " << i); + expected_line_number = __LINE__ - 1; + } + else + { + SLIC_WARNING_ONCE("test warning message " << i); + expected_line_number = __LINE__ - 1; + } + } + slic::flushStreams(); + if(GetParam() == "Synchronized" || (GetParam() == "Lumberjack" && rank == 0)) + { + EXPECT_FALSE(slic::internal::are_all_streams_empty()); + EXPECT_EQ(check_msg_count(slic::internal::test_stream.str(), "WARNING"), 1); + } + check_all_ranks(GetParam(), "WARNING", "test warning message 0", expected_line_number, rank, nranks); + slic::internal::clear_streams(); + // Single line - Placement of ")" matters for __LINE__ for slic call and checking // clang-format off EXPECT_SLIC_LOG_ALL_RANKS(SLIC_WARNING_IF(true, "this message is logged!"), "WARNING", "this message is logged!"); From ada5e6fa669a6f5fa1bc33c58d2b6997563fcdec Mon Sep 17 00:00:00 2001 From: Brian Manh Hien Han Date: Fri, 20 Mar 2026 13:04:02 -0700 Subject: [PATCH 016/986] Add SLIC_*_ONCE tests to slic_macros_parallel; correct two call-site test in slic_macros serial --- src/axom/slic/tests/slic_macros.cpp | 20 +- src/axom/slic/tests/slic_macros_parallel.cpp | 452 +++++++++++++++++++ 2 files changed, 465 insertions(+), 7 deletions(-) diff --git a/src/axom/slic/tests/slic_macros.cpp b/src/axom/slic/tests/slic_macros.cpp index 2e94ffce97..b2d0576369 100644 --- a/src/axom/slic/tests/slic_macros.cpp +++ b/src/axom/slic/tests/slic_macros.cpp @@ -528,18 +528,24 @@ TEST(slic_macros, test_if_once_macros) SLIC_INFO_IF_ONCE(i == 0, "message 1 logs " << i); SLIC_INFO_IF_ONCE(i > 0, "message 2 logs " << i); } - int msg_1_line = __LINE__ - 2; + int msg_1_line = __LINE__ - 3; int msg_2_line = __LINE__ - 3; EXPECT_FALSE(slic::internal::is_stream_empty()); const std::string str = slic::internal::test_stream.str(); EXPECT_EQ(check_count(str, "INFO"), 2); - check_level(str, "INFO"); - check_msg(str, "message 1 logs 0"); - check_msg(str.substr(str.size() / 2), "message 2 logs 1"); - check_line(str, msg_1_line); - check_line(str, msg_2_line); - check_file(str); + + std::string msg_1 = str.substr(0, str.size() / 2); + std::string msg_2 = str.substr(str.size() / 2); + + check_level(msg_1, "INFO"); + check_level(msg_2, "INFO"); + check_msg(msg_1, "message 1 logs 0"); + check_msg(msg_2, "message 2 logs 1"); + check_line(msg_1, msg_1_line); + check_line(msg_2, msg_2_line); + check_file(msg_1); + check_file(msg_2); slic::internal::clear(); } diff --git a/src/axom/slic/tests/slic_macros_parallel.cpp b/src/axom/slic/tests/slic_macros_parallel.cpp index 2f1002b605..98ec942fc1 100644 --- a/src/axom/slic/tests/slic_macros_parallel.cpp +++ b/src/axom/slic/tests/slic_macros_parallel.cpp @@ -470,6 +470,7 @@ TEST_P(SlicMacrosParallel, test_warning_macros) EXPECT_SLIC_LOG_ALL_RANKS(SLIC_WARNING("test warning message"), "WARNING", "test warning message"); SLIC_WARNING_IF(false, "this message should not be logged!"); + SLIC_WARNING_IF_ONCE(false, "this message should not be logged!"); slic::flushStreams(); EXPECT_TRUE(slic::internal::are_all_streams_empty()); @@ -503,9 +504,32 @@ TEST_P(SlicMacrosParallel, test_warning_macros) EXPECT_SLIC_LOG_ALL_RANKS(SLIC_WARNING_IF(true, "this message is logged!"), "WARNING", "this message is logged!"); // clang-format on + for(int i = 0; i < 3; i++) + { + if(GetParam() == "Synchronized") + { + SLIC_WARNING_IF_ONCE(true, "this message is logged " << i); + expected_line_number = __LINE__ - 1; + } + else + { + SLIC_WARNING_IF_ONCE(true, "this message is logged " << i); + expected_line_number = __LINE__ - 1; + } + } + slic::flushStreams(); + if(GetParam() == "Synchronized" || (GetParam() == "Lumberjack" && rank == 0)) + { + EXPECT_FALSE(slic::internal::are_all_streams_empty()); + EXPECT_EQ(check_msg_count(slic::internal::test_stream.str(), "WARNING"), 1); + } + check_all_ranks(GetParam(), "WARNING", "this message is logged 0", expected_line_number, rank, nranks); + slic::internal::clear_streams(); + // Check selective filtering based on root == false axom::slic::setIsRoot(false); SLIC_WARNING_ROOT_IF(false, "this message should not be logged!"); + SLIC_WARNING_ROOT_IF_ONCE(false, "this message should not be logged!"); slic::flushStreams(); EXPECT_TRUE(slic::internal::are_all_streams_empty()); @@ -515,15 +539,39 @@ TEST_P(SlicMacrosParallel, test_warning_macros) EXPECT_SLIC_LOG_ALL_RANKS(SLIC_WARNING_ROOT_IF(true, "this message is logged!"), "WARNING", "this message is logged!"); // clang-format on + for(int i = 0; i < 3; i++) + { + if(GetParam() == "Synchronized") + { + SLIC_WARNING_ROOT_IF_ONCE(true, "this message is logged " << i); + expected_line_number = __LINE__ - 1; + } + else + { + SLIC_WARNING_ROOT_IF_ONCE(true, "this message is logged " << i); + expected_line_number = __LINE__ - 1; + } + } + slic::flushStreams(); + if(GetParam() == "Synchronized" || (GetParam() == "Lumberjack" && rank == 0)) + { + EXPECT_FALSE(slic::internal::are_all_streams_empty()); + EXPECT_EQ(check_msg_count(slic::internal::test_stream.str(), "WARNING"), 1); + } + check_all_ranks(GetParam(), "WARNING", "this message is logged 0", expected_line_number, rank, nranks); + slic::internal::clear_streams(); + // is root, but conditional is false -> no message axom::slic::setIsRoot(true); SLIC_WARNING_ROOT_IF(false, "this message should not be logged!"); + SLIC_WARNING_ROOT_IF_ONCE(false, "this message should not be logged!"); slic::flushStreams(); EXPECT_TRUE(slic::internal::are_all_streams_empty()); // is not root, and conditional is true -> no message axom::slic::setIsRoot(false); SLIC_WARNING_ROOT_IF(true, "this message should not be logged!"); + SLIC_WARNING_ROOT_IF_ONCE(true, "this message should not be logged!"); slic::flushStreams(); EXPECT_TRUE(slic::internal::are_all_streams_empty()); @@ -531,9 +579,54 @@ TEST_P(SlicMacrosParallel, test_warning_macros) // Check for message only on root EXPECT_SLIC_LOG_ROOT(SLIC_WARNING_ROOT_IF(true, "this message is logged!"), "WARNING", "this message is logged!"); + axom::slic::setIsRoot(rank == 0); + for(int i = 0; i < 3; i++) + { + if(GetParam() == "Synchronized") + { + SLIC_WARNING_ROOT_IF_ONCE(true, "this message is logged " << i); + expected_line_number = __LINE__ - 1; + } + else + { + SLIC_WARNING_ROOT_IF_ONCE(true, "this message is logged " << i); + expected_line_number = __LINE__ - 1; + } + } + slic::flushStreams(); + if(rank == 0) + { + EXPECT_FALSE(slic::internal::are_all_streams_empty()); + EXPECT_EQ(check_msg_count(slic::internal::test_stream.str(), "WARNING"), 1); + } + check_root(GetParam(), "WARNING", "this message is logged 0", expected_line_number, rank); + slic::internal::clear_streams(); + // Check for message on every even rank only EXPECT_SLIC_LOG_EVEN(SLIC_WARNING_IF((rank % 2) == 0, "this message is logged!"), "WARNING", "this message is logged!"); // clang-format on + + for(int i = 0; i < 3; i++) + { + if(GetParam() == "Synchronized") + { + SLIC_WARNING_IF_ONCE((rank % 2) == 0, "this message is logged " << i); + expected_line_number = __LINE__ - 1; + } + else + { + SLIC_WARNING_IF_ONCE((rank % 2) == 0, "this message is logged " << i); + expected_line_number = __LINE__ - 1; + } + } + slic::flushStreams(); + if(((rank % 2) == 0 && stream_type == "Synchronized") || (rank == 0 && stream_type == "Lumberjack")) + { + EXPECT_FALSE(slic::internal::are_all_streams_empty()); + EXPECT_EQ(check_msg_count(slic::internal::test_stream.str(), "WARNING"), 1); + } + check_even(GetParam(), "WARNING", "this message is logged 0", expected_line_number, rank, nranks); + slic::internal::clear_streams(); } //------------------------------------------------------------------------------ @@ -545,6 +638,31 @@ TEST_P(SlicMacrosParallel, test_info_macros) EXPECT_TRUE(slic::internal::are_all_streams_empty()); EXPECT_SLIC_LOG_ALL_RANKS(SLIC_INFO("test info message"), "INFO", "test info message"); + // Called once per call site; since this test is ran for parameters + // Synchronized and Lumberjack, a separate call site is needed for each parameter, + // otherwise ONCE macro logs nothing when its Lumberjack's turn to run. + for(int i = 0; i < 3; i++) + { + if(GetParam() == "Synchronized") + { + SLIC_INFO_ONCE("test info message " << i); + expected_line_number = __LINE__ - 1; + } + else + { + SLIC_INFO_ONCE("test info message " << i); + expected_line_number = __LINE__ - 1; + } + } + slic::flushStreams(); + if(GetParam() == "Synchronized" || (GetParam() == "Lumberjack" && rank == 0)) + { + EXPECT_FALSE(slic::internal::are_all_streams_empty()); + EXPECT_EQ(check_msg_count(slic::internal::test_stream.str(), "INFO"), 1); + } + check_all_ranks(GetParam(), "INFO", "test info message 0", expected_line_number, rank, nranks); + slic::internal::clear_streams(); + SLIC_INFO_TAGGED("test tagged info message", "myTag"); expected_tag_number = __LINE__ - 1; slic::flushStreams(); @@ -612,14 +730,17 @@ TEST_P(SlicMacrosParallel, test_info_macros) slic::internal::clear_streams(); SLIC_INFO_TAGGED("this message should not be logged (no tag given)!", ""); + SLIC_INFO_TAGGED_ONCE("this message should not be logged (no tag given)!", ""); slic::flushStreams(); EXPECT_TRUE(slic::internal::are_all_streams_empty()); SLIC_INFO_TAGGED("this message should not be logged (tag DNE)!", "tag404"); + SLIC_INFO_TAGGED_ONCE("this message should not be logged (tag DNE)!", "tag404"); slic::flushStreams(); EXPECT_TRUE(slic::internal::are_all_streams_empty()); SLIC_INFO_IF(false, "this message should not be logged!"); + SLIC_INFO_IF_ONCE(false, "this message should not be logged!"); slic::flushStreams(); EXPECT_TRUE(slic::internal::are_all_streams_empty()); @@ -628,9 +749,32 @@ TEST_P(SlicMacrosParallel, test_info_macros) EXPECT_SLIC_LOG_ALL_RANKS(SLIC_INFO_IF(true, "this message is logged!"), "INFO", "this message is logged!"); // clang-format on + for(int i = 0; i < 3; i++) + { + if(GetParam() == "Synchronized") + { + SLIC_INFO_IF_ONCE(true, "this message is logged " << i); + expected_line_number = __LINE__ - 1; + } + else + { + SLIC_INFO_IF_ONCE(true, "this message is logged " << i); + expected_line_number = __LINE__ - 1; + } + } + slic::flushStreams(); + if(GetParam() == "Synchronized" || (GetParam() == "Lumberjack" && rank == 0)) + { + EXPECT_FALSE(slic::internal::are_all_streams_empty()); + EXPECT_EQ(check_msg_count(slic::internal::test_stream.str(), "INFO"), 1); + } + check_all_ranks(GetParam(), "INFO", "this message is logged 0", expected_line_number, rank, nranks); + slic::internal::clear_streams(); + // Check selective filtering based on root == false axom::slic::setIsRoot(false); SLIC_INFO_ROOT_IF(false, "this message should not be logged!"); + SLIC_INFO_ROOT_IF_ONCE(false, "this message should not be logged!"); slic::flushStreams(); EXPECT_TRUE(slic::internal::are_all_streams_empty()); @@ -640,15 +784,39 @@ TEST_P(SlicMacrosParallel, test_info_macros) EXPECT_SLIC_LOG_ALL_RANKS(SLIC_INFO_ROOT_IF(true, "this message is logged!"), "INFO", "this message is logged!"); // clang-format on + for(int i = 0; i < 3; i++) + { + if(GetParam() == "Synchronized") + { + SLIC_INFO_ROOT_IF_ONCE(true, "this message is logged " << i); + expected_line_number = __LINE__ - 1; + } + else + { + SLIC_INFO_ROOT_IF_ONCE(true, "this message is logged " << i); + expected_line_number = __LINE__ - 1; + } + } + slic::flushStreams(); + if(GetParam() == "Synchronized" || (GetParam() == "Lumberjack" && rank == 0)) + { + EXPECT_FALSE(slic::internal::are_all_streams_empty()); + EXPECT_EQ(check_msg_count(slic::internal::test_stream.str(), "INFO"), 1); + } + check_all_ranks(GetParam(), "INFO", "this message is logged 0", expected_line_number, rank, nranks); + slic::internal::clear_streams(); + // is root, but conditional is false -> no message axom::slic::setIsRoot(true); SLIC_INFO_ROOT_IF(false, "this message should not be logged!"); + SLIC_INFO_ROOT_IF_ONCE(false, "this message should not be logged!"); slic::flushStreams(); EXPECT_TRUE(slic::internal::are_all_streams_empty()); // is not root, and conditional is true -> no message axom::slic::setIsRoot(false); SLIC_INFO_ROOT_IF(true, "this message should not be logged!"); + SLIC_INFO_ROOT_IF_ONCE(true, "this message should not be logged!"); slic::flushStreams(); EXPECT_TRUE(slic::internal::are_all_streams_empty()); @@ -656,69 +824,240 @@ TEST_P(SlicMacrosParallel, test_info_macros) // Check for message only on root EXPECT_SLIC_LOG_ROOT(SLIC_INFO_ROOT_IF(true, "this message is logged!"), "INFO", "this message is logged!"); + axom::slic::setIsRoot(rank == 0); + for(int i = 0; i < 3; i++) + { + if(GetParam() == "Synchronized") + { + SLIC_INFO_ROOT_IF_ONCE(true, "this message is logged " << i); + expected_line_number = __LINE__ - 1; + } + else + { + SLIC_INFO_ROOT_IF_ONCE(true, "this message is logged " << i); + expected_line_number = __LINE__ - 1; + } + } + slic::flushStreams(); + if(rank == 0) + { + EXPECT_FALSE(slic::internal::are_all_streams_empty()); + EXPECT_EQ(check_msg_count(slic::internal::test_stream.str(), "INFO"), 1); + } + check_root(GetParam(), "INFO", "this message is logged 0", expected_line_number, rank); + slic::internal::clear_streams(); + // Check for message on every even rank only EXPECT_SLIC_LOG_EVEN(SLIC_INFO_IF((rank % 2) == 0, "this message is logged!"), "INFO", "this message is logged!"); // clang-format on + + for(int i = 0; i < 3; i++) + { + if(GetParam() == "Synchronized") + { + SLIC_INFO_IF_ONCE((rank % 2) == 0, "this message is logged " << i); + expected_line_number = __LINE__ - 1; + } + else + { + SLIC_INFO_IF_ONCE((rank % 2) == 0, "this message is logged " << i); + expected_line_number = __LINE__ - 1; + } + } + slic::flushStreams(); + if(((rank % 2) == 0 && stream_type == "Synchronized") || (rank == 0 && stream_type == "Lumberjack")) + { + EXPECT_FALSE(slic::internal::are_all_streams_empty()); + EXPECT_EQ(check_msg_count(slic::internal::test_stream.str(), "INFO"), 1); + } + check_even(GetParam(), "INFO", "this message is logged 0", expected_line_number, rank, nranks); + slic::internal::clear_streams(); } //------------------------------------------------------------------------------ TEST_P(SlicMacrosParallel, test_debug_macros) { + int expected_line_number; + EXPECT_TRUE(slic::internal::are_all_streams_empty()); #ifdef AXOM_DEBUG EXPECT_SLIC_LOG_ALL_RANKS(SLIC_DEBUG("test debug message"), "DEBUG", "test debug message"); + // Called once per call site; since this test is ran for parameters + // Synchronized and Lumberjack, a separate call site is needed for each parameter, + // otherwise ONCE macro logs nothing when its Lumberjack's turn to run. + for(int i = 0; i < 3; i++) + { + if(GetParam() == "Synchronized") + { + SLIC_DEBUG_ONCE("test debug message " << i); + expected_line_number = __LINE__ - 1; + } + else + { + SLIC_DEBUG_ONCE("test debug message " << i); + expected_line_number = __LINE__ - 1; + } + } + slic::flushStreams(); + if(GetParam() == "Synchronized" || (GetParam() == "Lumberjack" && rank == 0)) + { + EXPECT_FALSE(slic::internal::are_all_streams_empty()); + EXPECT_EQ(check_msg_count(slic::internal::test_stream.str(), "DEBUG"), 1); + } + check_all_ranks(GetParam(), "DEBUG", "test debug message 0", expected_line_number, rank, nranks); + slic::internal::clear_streams(); + // Single line - Placement of ")" matters for __LINE__ for slic call and checking // clang-format off EXPECT_SLIC_LOG_ALL_RANKS(SLIC_DEBUG_IF(true, "this message is logged!"), "DEBUG", "this message is logged!"); + for(int i = 0; i < 3; i++) + { + if(GetParam() == "Synchronized") + { + SLIC_DEBUG_IF_ONCE(true, "this message is logged " << i); + expected_line_number = __LINE__ - 1; + } + else + { + SLIC_DEBUG_IF_ONCE(true, "this message is logged " << i); + expected_line_number = __LINE__ - 1; + } + } + slic::flushStreams(); + if(GetParam() == "Synchronized" || (GetParam() == "Lumberjack" && rank == 0)) + { + EXPECT_FALSE(slic::internal::are_all_streams_empty()); + EXPECT_EQ(check_msg_count(slic::internal::test_stream.str(), "DEBUG"), 1); + } + check_all_ranks(GetParam(), "DEBUG", "this message is logged 0", expected_line_number, rank, nranks); + slic::internal::clear_streams(); + // Check selective filter based on root == true axom::slic::setIsRoot(true); EXPECT_SLIC_LOG_ALL_RANKS(SLIC_DEBUG_ROOT_IF(true, "this message is logged!"), "DEBUG", "this message is logged!"); + for(int i = 0; i < 3; i++) + { + if(GetParam() == "Synchronized") + { + SLIC_DEBUG_ROOT_IF_ONCE(true, "this message is logged " << i); + expected_line_number = __LINE__ - 1; + } + else + { + SLIC_DEBUG_ROOT_IF_ONCE(true, "this message is logged " << i); + expected_line_number = __LINE__ - 1; + } + } + slic::flushStreams(); + if(GetParam() == "Synchronized" || (GetParam() == "Lumberjack" && rank == 0)) + { + EXPECT_FALSE(slic::internal::are_all_streams_empty()); + EXPECT_EQ(check_msg_count(slic::internal::test_stream.str(), "DEBUG"), 1); + } + check_all_ranks(GetParam(), "DEBUG", "this message is logged 0", expected_line_number, rank, nranks); + slic::internal::clear_streams(); + // Check for message only on root EXPECT_SLIC_LOG_ROOT(SLIC_DEBUG_ROOT_IF(true, "this message is logged!"), "DEBUG", "this message is logged!"); + axom::slic::setIsRoot(rank == 0); + for(int i = 0; i < 3; i++) + { + if(GetParam() == "Synchronized") + { + SLIC_DEBUG_ROOT_IF_ONCE(true, "this message is logged " << i); + expected_line_number = __LINE__ - 1; + } + else + { + SLIC_DEBUG_ROOT_IF_ONCE(true, "this message is logged " << i); + expected_line_number = __LINE__ - 1; + } + } + slic::flushStreams(); + if(rank == 0) + { + EXPECT_FALSE(slic::internal::are_all_streams_empty()); + EXPECT_EQ(check_msg_count(slic::internal::test_stream.str(), "DEBUG"), 1); + } + check_root(GetParam(), "DEBUG", "this message is logged 0", expected_line_number, rank); + slic::internal::clear_streams(); + // Check for message on every even rank only EXPECT_SLIC_LOG_EVEN(SLIC_DEBUG_IF((rank % 2) == 0, "this message is logged!"), "DEBUG", "this message is logged!"); // clang-format on + for(int i = 0; i < 3; i++) + { + if(GetParam() == "Synchronized") + { + SLIC_DEBUG_IF_ONCE((rank % 2) == 0, "this message is logged " << i); + expected_line_number = __LINE__ - 1; + } + else + { + SLIC_DEBUG_IF_ONCE((rank % 2) == 0, "this message is logged " << i); + expected_line_number = __LINE__ - 1; + } + } + slic::flushStreams(); + if(((rank % 2) == 0 && stream_type == "Synchronized") || (rank == 0 && stream_type == "Lumberjack")) + { + EXPECT_FALSE(slic::internal::are_all_streams_empty()); + EXPECT_EQ(check_msg_count(slic::internal::test_stream.str(), "DEBUG"), 1); + } + check_even(GetParam(), "DEBUG", "this message is logged 0", expected_line_number, rank, nranks); + slic::internal::clear_streams(); + #else // SLIC_DEBUG macros only log messages when AXOM_DEBUG is defined + AXOM_UNUSED_VAR(expected_line_number); + SLIC_DEBUG("test debug message"); + SLIC_DEBUG_ONCE("test debug message"); SLIC_DEBUG_IF(true, "this message is logged!"); + SLIC_DEBUG_IF_ONCE(true, "this message is logged!"); axom::slic::setIsRoot(true); SLIC_DEBUG_ROOT_IF(true, "this message is logged!"); + SLIC_DEBUG_ROOT_IF_ONCE(true, "this message is logged!"); SLIC_DEBUG_IF((rank % 2) == 0, "this message is logged!"); + SLIC_DEBUG_IF_ONCE((rank % 2) == 0, "this message is logged!"); slic::flushStreams(); EXPECT_TRUE(slic::internal::are_all_streams_empty()); #endif SLIC_DEBUG_IF(false, "this message should not be logged!"); + SLIC_DEBUG_IF_ONCE(false, "this message should not be logged!"); slic::flushStreams(); EXPECT_TRUE(slic::internal::are_all_streams_empty()); // Check selective filtering based on root == false axom::slic::setIsRoot(false); SLIC_DEBUG_ROOT_IF(false, "this message should not be logged!"); + SLIC_DEBUG_ROOT_IF_ONCE(false, "this message should not be logged!"); slic::flushStreams(); EXPECT_TRUE(slic::internal::are_all_streams_empty()); // is root, but conditional is false -> no message axom::slic::setIsRoot(true); SLIC_DEBUG_ROOT_IF(false, "this message should not be logged!"); + SLIC_DEBUG_ROOT_IF_ONCE(false, "this message should not be logged!"); slic::flushStreams(); EXPECT_TRUE(slic::internal::are_all_streams_empty()); // is not root, and conditional is true -> no message axom::slic::setIsRoot(false); SLIC_DEBUG_ROOT_IF(true, "this message should not be logged!"); + SLIC_DEBUG_ROOT_IF_ONCE(true, "this message should not be logged!"); slic::flushStreams(); EXPECT_TRUE(slic::internal::are_all_streams_empty()); } @@ -1051,6 +1390,119 @@ TEST_P(SlicMacrosParallel, test_check_macros) EXPECT_TRUE(slic::internal::are_all_streams_empty()); } +//------------------------------------------------------------------------------ +TEST_P(SlicMacrosParallel, test_if_once_macros) +{ + int expected_line_number; + + // Check that message is logged when condition is satisfied only once + // Called once per call site; since this test is ran for parameters + // Synchronized and Lumberjack, a separate call site is needed for each parameter, + // otherwise ONCE macro logs nothing when its Lumberjack's turn to run. + for(int i = 0; i < 3; i++) + { + if(GetParam() == "Synchronized") + { + SLIC_INFO_IF_ONCE(i > 0, i << "th message is logged!"); + expected_line_number = __LINE__ - 1; + } + else + { + SLIC_INFO_IF_ONCE(i > 0, i << "th message is logged!"); + expected_line_number = __LINE__ - 1; + } + } + slic::flushStreams(); + if(GetParam() == "Synchronized" || (GetParam() == "Lumberjack" && rank == 0)) + { + EXPECT_FALSE(slic::internal::are_all_streams_empty()); + EXPECT_EQ(check_msg_count(slic::internal::test_stream.str(), "INFO"), 1); + } + check_all_ranks(GetParam(), "INFO", "1th message is logged", expected_line_number, rank, nranks); + slic::internal::clear_streams(); + + axom::slic::setIsRoot(true); + for(int i = 0; i < 3; i++) + { + if(GetParam() == "Synchronized") + { + SLIC_INFO_ROOT_IF_ONCE(i > 0, i << "th message is logged!"); + expected_line_number = __LINE__ - 1; + } + else + { + SLIC_INFO_ROOT_IF_ONCE(i > 0, i << "th message is logged!"); + expected_line_number = __LINE__ - 1; + } + } + slic::flushStreams(); + if(GetParam() == "Synchronized" || (GetParam() == "Lumberjack" && rank == 0)) + { + EXPECT_FALSE(slic::internal::are_all_streams_empty()); + EXPECT_EQ(check_msg_count(slic::internal::test_stream.str(), "INFO"), 1); + } + check_all_ranks(GetParam(), "INFO", "1th message is logged", expected_line_number, rank, nranks); + slic::internal::clear_streams(); + + // Two call-sites have a single message each + int msg_1_line; + int msg_2_line; + for(int i = 0; i < 3; i++) + { + if(GetParam() == "Synchronized") + { + SLIC_INFO_IF_ONCE(i == 0, "message 1 logs " << i); + msg_1_line = __LINE__ - 1; + SLIC_INFO_IF_ONCE(i > 0, "message 2 logs " << i); + msg_2_line = __LINE__ - 1; + } + else + { + SLIC_INFO_IF_ONCE(i == 0, "message 1 logs " << i); + msg_1_line = __LINE__ - 1; + SLIC_INFO_IF_ONCE(i > 0, "message 2 logs " << i); + msg_2_line = __LINE__ - 1; + } + } + slic::flushStreams(); + + if(GetParam() == "Synchronized" || (GetParam() == "Lumberjack" && rank == 0)) + { + EXPECT_FALSE(slic::internal::are_all_streams_empty()); + const std::string str = slic::internal::test_stream.str(); + EXPECT_EQ(check_msg_count(str, "INFO"), 2); + + check_level(str, "INFO"); + + std::string msg_1 = str.substr(0, str.size() / 2); + std::string msg_2 = str.substr(str.size() / 2); + + check_level(msg_1, "INFO"); + check_level(msg_2, "INFO"); + check_msg(msg_1, "message 1 logs 0"); + check_msg(msg_2, "message 2 logs 1"); + check_line(msg_1, msg_1_line); + check_line(msg_2, msg_2_line); + check_file(msg_1); + check_file(msg_2); + if(GetParam() == "Synchronized") + { + check_rank(msg_1, rank); + check_rank(msg_2, rank); + } + else + { + check_ranks(msg_1, nranks); + check_ranks(msg_2, nranks); + } + check_rank_count(msg_1, GetParam(), nranks); + check_rank_count(msg_2, GetParam(), nranks); + check_file(msg_1); + check_file(msg_2); + } + slic::internal::clear_streams(); +} + //------------------------------------------------------------------------------ TEST_P(SlicMacrosParallel, test_macros_file_output) { From 33bc5de65fa9d574f4856190462953c95c1a3c6a Mon Sep 17 00:00:00 2001 From: Brian Manh Hien Han Date: Fri, 20 Mar 2026 13:50:30 -0700 Subject: [PATCH 017/986] fix unused for release builds --- src/axom/quest/tests/quest_mesh_clipper.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/axom/quest/tests/quest_mesh_clipper.cpp b/src/axom/quest/tests/quest_mesh_clipper.cpp index c6f29506c3..0f9cb60d49 100644 --- a/src/axom/quest/tests/quest_mesh_clipper.cpp +++ b/src/axom/quest/tests/quest_mesh_clipper.cpp @@ -654,6 +654,7 @@ axom::klee::Geometry createGeom_CupMesh(sidre::DataStore& ds, const std::string& std::string proeFile = axom::utilities::filesystem::joinPath(AXOM_DATA_DIR, "quest/cup.proe"); reader.setFileName(proeFile); int readStatus = reader.read(); + AXOM_UNUSED_VAR(readStatus); SLIC_ASSERT(readStatus == 0); reader.getMesh(&tetMesh); const double extraScale = 1 / sqrt(3.0); // to ensure tetMesh remains inside mesh when rotated. From bcbb29c07cc64368d3f3948ff244fbe4df0272d7 Mon Sep 17 00:00:00 2001 From: Brian Manh Hien Han Date: Tue, 24 Mar 2026 10:02:44 -0700 Subject: [PATCH 018/986] Update docs --- .../sections/wrapping_slic_in_macros.rst | 101 ++++++++++++------ 1 file changed, 69 insertions(+), 32 deletions(-) diff --git a/src/axom/slic/docs/sphinx/sections/wrapping_slic_in_macros.rst b/src/axom/slic/docs/sphinx/sections/wrapping_slic_in_macros.rst index 19a2a327c7..f2138c32f5 100644 --- a/src/axom/slic/docs/sphinx/sections/wrapping_slic_in_macros.rst +++ b/src/axom/slic/docs/sphinx/sections/wrapping_slic_in_macros.rst @@ -111,38 +111,74 @@ functions are called. The table below details the built-in SLIC macros as well as some notes about when they are collective calls: -+----------------------------+------------------------------------------------+----------------------------------------------------------------------------+ -| Macro | Availability | Collective status | -+============================+================================================+============================================================================+ -| | ``SLIC_ASSERT`` | | Only available in debug configurations | | Collective by default. | -| | ``SLIC_ASSERT_MSG`` | | (i.e. when `AXOM_DEBUG` is defined). | | Collective after calling ``slic::enableAbortOnError()``. | -| | | | Not available in device code. | | No longer collective after calling ``slic::disableAbortOnError()``. | -+----------------------------+------------------------------------------------+----------------------------------------------------------------------------+ -| | ``SLIC_CHECK`` | | Only available in debug configurations | | Not collective by default. | -| | ``SLIC_CHECK_MSG`` | | (i.e. when `AXOM_DEBUG` is defined). | | Collective after ``slic::debug::checksAreErrors`` is set to ``true``, | -| | | | Not available in device code. | | defaults to ``false``. | -+----------------------------+------------------------------------------------+----------------------------------------------------------------------------+ -| | ``SLIC_DEBUG`` | | Only available in debug configurations | | Never | -| | ``SLIC_DEBUG_IF`` | | (i.e. when `AXOM_DEBUG` is defined) | | | -| | ``SLIC_DEBUG_ROOT`` | | | | | -| | ``SLIC_DEBUG_ROOT_IF`` | | | | | -+----------------------------+------------------------------------------------+----------------------------------------------------------------------------+ -| | ``SLIC_INFO`` | | Always | | Never | -| | ``SLIC_INFO_IF`` | | | | | -| | ``SLIC_INFO_ROOT`` | | | | | -| | ``SLIC_INFO_ROOT_IF`` | | | | | -| | ``SLIC_INFO_TAGGED`` | | | | | -+----------------------------+------------------------------------------------+----------------------------------------------------------------------------+ -| | ``SLIC_ERROR`` | | Always | | Collective by default. | -| | ``SLIC_ERROR_IF`` | | | | Collective after calling ``slic::enableAbortOnError()``. | -| | ``SLIC_ERROR_ROOT`` | | | | No longer collective after calling ``slic::disableAbortOnError()`` | -| | ``SLIC_ERROR_ROOT_IF`` | | | | | -+----------------------------+------------------------------------------------+----------------------------------------------------------------------------+ -| | ``SLIC_WARNING`` | | Always | | Not collective by default. | -| | ``SLIC_WARNING_IF`` | | | | Collective after calling ``slic::enableAbortOnWarning()``. | -| | ``SLIC_WARNING_ROOT`` | | | | No longer collective after calling ``slic::disableAbortOnWarning()`` | -| | ``SLIC_WARNING_ROOT_IF`` | | | | | -+----------------------------+------------------------------------------------+----------------------------------------------------------------------------+ +.. list-table:: SLIC macro availability and collective behavior + :header-rows: 1 + :widths: 28 34 38 + + * - Macro + - Availability + - Collective status + + * - ``SLIC_ASSERT`` + ``SLIC_ASSERT_MSG`` + - Only available in debug configurations (i.e. when ``AXOM_DEBUG`` is defined). + Not available in device code. + - Collective by default. + Collective after calling ``slic::enableAbortOnError()``. + No longer collective after calling ``slic::disableAbortOnError()``. + + * - ``SLIC_CHECK`` + ``SLIC_CHECK_MSG`` + - Only available in debug configurations (i.e. when ``AXOM_DEBUG`` is defined). + Not available in device code. + - Not collective by default. + Collective after ``slic::debug::checksAreErrors`` is set to ``true`` (defaults to ``false``). + + * - ``SLIC_DEBUG`` + ``SLIC_DEBUG_IF`` + ``SLIC_DEBUG_ROOT`` + ``SLIC_DEBUG_ROOT_IF`` + ``SLIC_DEBUG_ONCE`` + ``SLIC_DEBUG_IF_ONCE`` + ``SLIC_DEBUG_ROOT_ONCE`` + ``SLIC_DEBUG_ROOT_IF_ONCE`` + - Only available in debug configurations (i.e. when ``AXOM_DEBUG`` is defined). + - Never + + * - ``SLIC_INFO`` + ``SLIC_INFO_IF`` + ``SLIC_INFO_ROOT`` + ``SLIC_INFO_ROOT_IF`` + ``SLIC_INFO_TAGGED`` + ``SLIC_INFO_ONCE`` + ``SLIC_INFO_IF_ONCE`` + ``SLIC_INFO_ROOT_ONCE`` + ``SLIC_INFO_ROOT_IF_ONCE`` + ``SLIC_INFO_TAGGED_ONCE`` + - Always + - Never + + * - ``SLIC_ERROR`` + ``SLIC_ERROR_IF`` + ``SLIC_ERROR_ROOT`` + ``SLIC_ERROR_ROOT_IF`` + - Always + - Collective by default. + Collective after calling ``slic::enableAbortOnError()``. + No longer collective after calling ``slic::disableAbortOnError()``. + + * - ``SLIC_WARNING`` + ``SLIC_WARNING_IF`` + ``SLIC_WARNING_ROOT`` + ``SLIC_WARNING_ROOT_IF`` + ``SLIC_WARNING_ONCE`` + ``SLIC_WARNING_IF_ONCE`` + ``SLIC_WARNING_ROOT_ONCE`` + ``SLIC_WARNING_ROOT_IF_ONCE`` + - Always + - Not collective by default. + Collective after calling ``slic::enableAbortOnWarning()``. + No longer collective after calling ``slic::disableAbortOnWarning()``. Doxygen generated API documentation on Macros can be found here: `SLIC Macros <../../../../../doxygen/html/slic__macros_8hpp.html>`_ @@ -157,6 +193,7 @@ Consider the following rules of thumb when choosing from the above logging macro (i.e. their messages will not get logged), while `SLIC_INFO` macros are always available. * The `SLIC_*_ROOT` variants can help reduce logging verbosity when called in an MPI application, especially if all MPI ranks are expected to have the same data (for example, if a value was broadcast from one rank to all the other ranks). +* The `SLIC_*_ONCE` variants can help reduce logging verbosity when only the first invocation at a call-site is necessary. .. ############################################################################# From db812a9e1ab6eab7a4f1144f722a055f35f33129 Mon Sep 17 00:00:00 2001 From: Brian Manh Hien Han Date: Tue, 24 Mar 2026 10:04:50 -0700 Subject: [PATCH 019/986] update note --- RELEASE-NOTES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 6f30ebb228..7949cdca38 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -23,6 +23,8 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ - Sidre: Added Conduit Node to the Python interface. - Adds yapf as a Python formatter. - Quest: Adds fast GWN methods for STL/Triangulated STEP input and linearized NURBS Curve input which leverage error-controlled approximation and a spatial index (BVH). +- Slic: Adds new Slic macros that allow you to selectively print messages once per call-site. For example, + `SLIC_INFO_ONCE(msg)` and `SLIC_WARNING_ROOT_IF_ONCE(EXP, msg)`. ### Changed - Primal: Axom's polygon clipping was modified to handle some corner cases. From 530fbf32463b704795f3bd476c7805bf0b3f36b9 Mon Sep 17 00:00:00 2001 From: Brian Manh Hien Han Date: Tue, 24 Mar 2026 10:40:06 -0700 Subject: [PATCH 020/986] List of lists formatting --- .../sections/wrapping_slic_in_macros.rst | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/axom/slic/docs/sphinx/sections/wrapping_slic_in_macros.rst b/src/axom/slic/docs/sphinx/sections/wrapping_slic_in_macros.rst index f2138c32f5..0e15eb6680 100644 --- a/src/axom/slic/docs/sphinx/sections/wrapping_slic_in_macros.rst +++ b/src/axom/slic/docs/sphinx/sections/wrapping_slic_in_macros.rst @@ -121,18 +121,18 @@ The table below details the built-in SLIC macros as well as some notes about whe * - ``SLIC_ASSERT`` ``SLIC_ASSERT_MSG`` - - Only available in debug configurations (i.e. when ``AXOM_DEBUG`` is defined). - Not available in device code. - - Collective by default. - Collective after calling ``slic::enableAbortOnError()``. - No longer collective after calling ``slic::disableAbortOnError()``. + - - Only available in debug configurations (i.e. when ``AXOM_DEBUG`` is defined) + - Not available in device code + - - Collective by default + - Collective after calling ``slic::enableAbortOnError()`` + - No longer collective after calling ``slic::disableAbortOnError()`` * - ``SLIC_CHECK`` ``SLIC_CHECK_MSG`` - - Only available in debug configurations (i.e. when ``AXOM_DEBUG`` is defined). - Not available in device code. - - Not collective by default. - Collective after ``slic::debug::checksAreErrors`` is set to ``true`` (defaults to ``false``). + - - Only available in debug configurations (i.e. when ``AXOM_DEBUG`` is defined) + - Not available in device code + - - Not collective by default + - Collective after ``slic::debug::checksAreErrors`` is set to ``true``, defaults to ``false`` * - ``SLIC_DEBUG`` ``SLIC_DEBUG_IF`` @@ -142,8 +142,8 @@ The table below details the built-in SLIC macros as well as some notes about whe ``SLIC_DEBUG_IF_ONCE`` ``SLIC_DEBUG_ROOT_ONCE`` ``SLIC_DEBUG_ROOT_IF_ONCE`` - - Only available in debug configurations (i.e. when ``AXOM_DEBUG`` is defined). - - Never + - - Only available in debug configurations (i.e. when ``AXOM_DEBUG`` is defined) + - - Never * - ``SLIC_INFO`` ``SLIC_INFO_IF`` @@ -155,17 +155,17 @@ The table below details the built-in SLIC macros as well as some notes about whe ``SLIC_INFO_ROOT_ONCE`` ``SLIC_INFO_ROOT_IF_ONCE`` ``SLIC_INFO_TAGGED_ONCE`` - - Always - - Never + - - Always + - - Never * - ``SLIC_ERROR`` ``SLIC_ERROR_IF`` ``SLIC_ERROR_ROOT`` ``SLIC_ERROR_ROOT_IF`` - - Always - - Collective by default. - Collective after calling ``slic::enableAbortOnError()``. - No longer collective after calling ``slic::disableAbortOnError()``. + - - Always + - - Collective by default + - Collective after calling ``slic::enableAbortOnError()`` + - No longer collective after calling ``slic::disableAbortOnError()`` * - ``SLIC_WARNING`` ``SLIC_WARNING_IF`` @@ -175,10 +175,10 @@ The table below details the built-in SLIC macros as well as some notes about whe ``SLIC_WARNING_IF_ONCE`` ``SLIC_WARNING_ROOT_ONCE`` ``SLIC_WARNING_ROOT_IF_ONCE`` - - Always - - Not collective by default. - Collective after calling ``slic::enableAbortOnWarning()``. - No longer collective after calling ``slic::disableAbortOnWarning()``. + - - Always + - - Not collective by default + - Collective after calling ``slic::enableAbortOnWarning()`` + - No longer collective after calling ``slic::disableAbortOnWarning()`` Doxygen generated API documentation on Macros can be found here: `SLIC Macros <../../../../../doxygen/html/slic__macros_8hpp.html>`_ From d772001cae675cc324536da169d4c313f90caa07 Mon Sep 17 00:00:00 2001 From: Brian Manh Hien Han Date: Tue, 24 Mar 2026 11:13:03 -0700 Subject: [PATCH 021/986] Add SLIC_*_CONTAINER macro to table --- src/axom/slic/docs/sphinx/sections/wrapping_slic_in_macros.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/axom/slic/docs/sphinx/sections/wrapping_slic_in_macros.rst b/src/axom/slic/docs/sphinx/sections/wrapping_slic_in_macros.rst index 0e15eb6680..75a470b116 100644 --- a/src/axom/slic/docs/sphinx/sections/wrapping_slic_in_macros.rst +++ b/src/axom/slic/docs/sphinx/sections/wrapping_slic_in_macros.rst @@ -138,10 +138,12 @@ The table below details the built-in SLIC macros as well as some notes about whe ``SLIC_DEBUG_IF`` ``SLIC_DEBUG_ROOT`` ``SLIC_DEBUG_ROOT_IF`` + ``SLIC_DEBUG_PRINT_CONTAINER`` ``SLIC_DEBUG_ONCE`` ``SLIC_DEBUG_IF_ONCE`` ``SLIC_DEBUG_ROOT_ONCE`` ``SLIC_DEBUG_ROOT_IF_ONCE`` + ``SLIC_DEBUG_PRINT_CONTAINER_ONCE`` - - Only available in debug configurations (i.e. when ``AXOM_DEBUG`` is defined) - - Never From 2f48c1c7e0cefc4d425b5b29b8f1ca9452b47988 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Tue, 31 Mar 2026 09:37:50 -0700 Subject: [PATCH 022/986] Keep do-while --- src/axom/slic/interface/slic_macros.hpp | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/axom/slic/interface/slic_macros.hpp b/src/axom/slic/interface/slic_macros.hpp index b1022ec3b1..4cda60dc01 100644 --- a/src/axom/slic/interface/slic_macros.hpp +++ b/src/axom/slic/interface/slic_macros.hpp @@ -60,17 +60,20 @@ * \endcode * */ -#define SLIC_ERROR_IF(EXP, msg) \ - if(EXP) \ - { \ - std::ostringstream __oss; \ - __oss << msg; \ - axom::slic::logErrorMessage(__oss.str(), __FILE__, __LINE__); \ - if(axom::slic::isAbortOnErrorsEnabled()) \ - { \ - axom::slic::abort(); \ - } \ - } +#define SLIC_ERROR_IF(EXP, msg) \ + do \ + { \ + if(EXP) \ + { \ + std::ostringstream __oss; \ + __oss << msg; \ + axom::slic::logErrorMessage(__oss.str(), __FILE__, __LINE__); \ + if(axom::slic::isAbortOnErrorsEnabled()) \ + { \ + axom::slic::abort(); \ + } \ + } \ + } while(axom::slic::detail::false_value) /*! * \def SLIC_ERROR_ROOT( msg ) From 84f6dddb52cc37a04d29c3f07d5a1aa77565909c Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Tue, 24 Feb 2026 13:38:55 -0800 Subject: [PATCH 023/986] Add test to verify orientation of STEP readers --- src/axom/quest/tests/CMakeLists.txt | 4 + src/axom/quest/tests/quest_step_reader.cpp | 125 +++++++++++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 src/axom/quest/tests/quest_step_reader.cpp diff --git a/src/axom/quest/tests/CMakeLists.txt b/src/axom/quest/tests/CMakeLists.txt index 59bba00c24..95ad186df4 100644 --- a/src/axom/quest/tests/CMakeLists.txt +++ b/src/axom/quest/tests/CMakeLists.txt @@ -29,6 +29,10 @@ blt_list_append(TO quest_tests IF AXOM_DATA_DIR ELEMENTS quest_meshtester.cpp) +if(OPENCASCADE_FOUND AND AXOM_DATA_DIR) + list(APPEND quest_tests quest_step_reader.cpp) +endif() + set(quest_tests_depends quest gtest diff --git a/src/axom/quest/tests/quest_step_reader.cpp b/src/axom/quest/tests/quest_step_reader.cpp new file mode 100644 index 0000000000..3651faacd7 --- /dev/null +++ b/src/axom/quest/tests/quest_step_reader.cpp @@ -0,0 +1,125 @@ +// 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" + +#include "axom/core.hpp" +#include "axom/primal.hpp" +#include "axom/quest.hpp" + +#include "gtest/gtest.h" + +#include +#include + +namespace fs = axom::utilities::filesystem; +namespace primal = axom::primal; +namespace quest = axom::quest; + +//------------------------------------------------------------------------------ +std::string pjoin(const std::string& str) { return str; } + +std::string pjoin(const char* str) { return std::string(str); } + +template +std::string pjoin(const std::string& str, Args... args) +{ + return fs::joinPath(str, pjoin(args...)); +} + +template +std::string pjoin(const char* str, Args... args) +{ + return fs::joinPath(std::string(str), pjoin(args...)); +} + +//------------------------------------------------------------------------------ +bool isNearInteger(double value, double eps) +{ + const double nearest = std::round(value); + return std::abs(value - nearest) <= eps; +} + +//------------------------------------------------------------------------------ +void runStepFileTest(const std::string& stepFile) +{ + const std::string fileName = pjoin(AXOM_DATA_DIR, "quest", "step", stepFile); + SLIC_INFO(axom::fmt::format("Testing STEP file '{}'", fileName)); + + quest::STEPReader stepReader; + stepReader.setFileName(fileName); + + constexpr bool validate = false; + const int ret = stepReader.read(validate); + if(ret != 0) + { + SLIC_ERROR(axom::fmt::format("Failed to read STEP file '{}'", fileName)); + } + + const auto& patches = stepReader.getPatchArray(); + EXPECT_GT(patches.size(), 0) << "No NURBS patches were extracted from '" << fileName << "'"; + if(patches.empty()) + { + return; + } + + const auto shapeBbox = stepReader.getBRepBoundingBox(); + auto bboxMin = shapeBbox.getMin(); + auto bboxMax = shapeBbox.getMax(); + const auto bboxDiag = bboxMax.array() - bboxMin.array(); + + const primal::WindingTolerances tol; + constexpr double integer_eps = 1e-3; + + axom::Array> query_arr(0, 27); + for(const double fx : {0.25, 0.5, 0.75}) + { + for(const double fy : {0.25, 0.5, 0.75}) + { + for(const double fz : {0.25, 0.5, 0.75}) + { + query_arr.emplace_back(primal::Point({bboxMin[0] + fx * bboxDiag[0], + bboxMin[1] + fy * bboxDiag[1], + bboxMin[2] + fz * bboxDiag[2]})); + } + } + } + + const auto gwn_arr = primal::winding_number(query_arr, + patches, + tol.edge_tol, + tol.ls_tol, + tol.quad_tol, + tol.disk_size, + tol.EPS); + + EXPECT_EQ(gwn_arr.size(), query_arr.size()); + for(int i = 0; i < gwn_arr.size() && i < query_arr.size(); ++i) + { + const double wn = gwn_arr[i]; + EXPECT_NEAR(wn, std::round(wn), integer_eps); + } +} + +//------------------------------------------------------------------------------ +TEST(quest_step_reader, gwn_is_near_integer_on_bbox_grid) +{ + runStepFileTest("sliced_cylinder.step"); +} + +//------------------------------------------------------------------------------ +int main(int argc, char* argv[]) +{ + int result = 0; + + ::testing::InitGoogleTest(&argc, argv); + + axom::slic::SimpleLogger logger; + + result = RUN_ALL_TESTS(); + + return result; +} \ No newline at end of file From 3b11197aacb078695ddcc6c1d784da70802bf42e Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Tue, 24 Feb 2026 13:52:34 -0800 Subject: [PATCH 024/986] Add triangulator test --- src/axom/quest/tests/quest_step_reader.cpp | 104 ++++++++++++++++++++- 1 file changed, 99 insertions(+), 5 deletions(-) diff --git a/src/axom/quest/tests/quest_step_reader.cpp b/src/axom/quest/tests/quest_step_reader.cpp index 3651faacd7..846120a40e 100644 --- a/src/axom/quest/tests/quest_step_reader.cpp +++ b/src/axom/quest/tests/quest_step_reader.cpp @@ -7,6 +7,7 @@ #include "axom/config.hpp" #include "axom/core.hpp" +#include "axom/mint.hpp" #include "axom/primal.hpp" #include "axom/quest.hpp" @@ -16,6 +17,7 @@ #include namespace fs = axom::utilities::filesystem; +namespace mint = axom::mint; namespace primal = axom::primal; namespace quest = axom::quest; @@ -43,6 +45,83 @@ bool isNearInteger(double value, double eps) return std::abs(value - nearest) <= eps; } +namespace +{ +struct TriangleGwnEvaluator +{ + using Point3D = primal::Point; + using Triangle3D = primal::Triangle; + using BBox3D = primal::BoundingBox; + + void preprocess(const mint::UnstructuredMesh& mesh) + { + const auto ntris = mesh.getNumberOfCells(); + m_triangles.resize(ntris); + + BBox3D shapeBBox; + BBox3D* shapeBBoxPtr = &shapeBBox; + mint::for_all_nodes( + &mesh, + AXOM_LAMBDA(axom::IndexType /*nodeIdx*/, double x, double y, double z) { + shapeBBoxPtr->addPoint(Point3D {x, y, z}); + }); + + m_shapeCenter = shapeBBox.getCentroid(); + const auto longestDim = shapeBBox.getLongestDimension(); + m_scale = shapeBBox.getMax()[longestDim] - shapeBBox.getMin()[longestDim]; + if(m_scale <= 0.) + { + m_scale = 1.; + } + + const auto& ctr = m_shapeCenter; + const auto scl = m_scale; + auto trisView = m_triangles.view(); + mint::for_all_cells( + &mesh, + AXOM_LAMBDA(axom::IndexType cellIdx, + const axom::numerics::Matrix& coords, + [[maybe_unused]] const axom::IndexType* nodeIds) { + trisView[cellIdx] = + Triangle3D {Point3D {(coords(0, 0) - ctr[0]) / scl, + (coords(1, 0) - ctr[1]) / scl, + (coords(2, 0) - ctr[2]) / scl}, + Point3D {(coords(0, 1) - ctr[0]) / scl, + (coords(1, 1) - ctr[1]) / scl, + (coords(2, 1) - ctr[2]) / scl}, + Point3D {(coords(0, 2) - ctr[0]) / scl, + (coords(1, 2) - ctr[1]) / scl, + (coords(2, 2) - ctr[2]) / scl}}; + }); + } + + axom::Array evaluate(const axom::Array& queryArr, + const double edge_tol, + const double EPS) const + { + axom::Array gwnArr(queryArr.size()); + for(int qi = 0; qi < queryArr.size(); ++qi) + { + const Point3D qScaled((queryArr[qi].array() - m_shapeCenter.array()) / m_scale); + + double wn = 0.; + for(const auto& tri : m_triangles) + { + wn += primal::winding_number(qScaled, tri, edge_tol, EPS); + } + + gwnArr[qi] = wn; + } + + return gwnArr; + } + + Point3D m_shapeCenter; + double m_scale {1.0}; + axom::Array m_triangles; +}; +} // namespace + //------------------------------------------------------------------------------ void runStepFileTest(const std::string& stepFile) { @@ -71,9 +150,6 @@ void runStepFileTest(const std::string& stepFile) auto bboxMax = shapeBbox.getMax(); const auto bboxDiag = bboxMax.array() - bboxMin.array(); - const primal::WindingTolerances tol; - constexpr double integer_eps = 1e-3; - axom::Array> query_arr(0, 27); for(const double fx : {0.25, 0.5, 0.75}) { @@ -88,6 +164,7 @@ void runStepFileTest(const std::string& stepFile) } } + const primal::WindingTolerances tol; const auto gwn_arr = primal::winding_number(query_arr, patches, tol.edge_tol, @@ -97,16 +174,33 @@ void runStepFileTest(const std::string& stepFile) tol.EPS); EXPECT_EQ(gwn_arr.size(), query_arr.size()); + + constexpr double integer_eps = 1e-3; for(int i = 0; i < gwn_arr.size() && i < query_arr.size(); ++i) { const double wn = gwn_arr[i]; EXPECT_NEAR(wn, std::round(wn), integer_eps); } + + mint::UnstructuredMesh triMesh(3, mint::TRIANGLE); + stepReader.getTriangleMesh(&triMesh, 0.01, 0.5); + + TriangleGwnEvaluator triEval; + triEval.preprocess(triMesh); + + const auto tri_gwn_arr = triEval.evaluate(query_arr, tol.edge_tol, tol.EPS); + EXPECT_EQ(tri_gwn_arr.size(), query_arr.size()); + for(int i = 0; i < tri_gwn_arr.size() && i < query_arr.size(); ++i) + { + const double wn = tri_gwn_arr[i]; + EXPECT_NEAR(wn, std::round(wn), integer_eps); + } } //------------------------------------------------------------------------------ -TEST(quest_step_reader, gwn_is_near_integer_on_bbox_grid) +TEST(quest_step_reader, orientation_check) { + // If the STEP file is read properly, then the resulting GWN should be integer-valued runStepFileTest("sliced_cylinder.step"); } @@ -122,4 +216,4 @@ int main(int argc, char* argv[]) result = RUN_ALL_TESTS(); return result; -} \ No newline at end of file +} From 121abfbb0b9a8bc8a4752854281ae983e8ad60e5 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Tue, 24 Feb 2026 17:27:33 -0800 Subject: [PATCH 025/986] Remove existing STEP orientation fixes --- src/axom/quest/io/STEPReader.cpp | 34 -------------------------------- 1 file changed, 34 deletions(-) diff --git a/src/axom/quest/io/STEPReader.cpp b/src/axom/quest/io/STEPReader.cpp index 3487687156..0c11479265 100644 --- a/src/axom/quest/io/STEPReader.cpp +++ b/src/axom/quest/io/STEPReader.cpp @@ -683,12 +683,6 @@ class StepFileProcessor patches[patchIndex] = patchProcessor.nurbsPatchGeometry(); - // If the face is flipped in opencascade, we need to flip the primal primitive too - if(face.Orientation() == TopAbs_REVERSED) - { - patches[patchIndex].reverseOrientation_u(); - } - PatchData& patchData = m_patchData[patchIndex]; patchData.patchIndex = patchIndex; patchData.wasOriginallyPeriodic_u = patchProcessor.patchWasOriginallyPeriodic_u(); @@ -872,9 +866,6 @@ class StepFileProcessor const TopoDS_Edge& edge = TopoDS::Edge(edgeExp.Current()); const int curveIndex = patch.getNumTrimmingCurves(); - TopAbs_Orientation orientation = edge.Orientation(); - const bool isReversed = (orientation == TopAbs_REVERSED); - if(m_verbose) { BRepAdaptor_Curve curveAdaptor(edge); @@ -901,10 +892,6 @@ class StepFileProcessor patchData.trimmingCurves_originallyPeriodic.push_back( curveProcessor.curveWasOriginallyPeriodic()); - if(isReversed) // Ensure consistency of curve w.r.t. patch - { - curve.reverseOrientation(); - } SLIC_ASSERT(curve.isValidNURBS()); SLIC_ASSERT(curve.getDegree() == bsplineCurve->Degree()); @@ -938,13 +925,6 @@ class StepFileProcessor } } } - - // If the face is flipped, then the trimming curves all need to be reversed too - if(patch.isTrimmed() && - TopoDS::Face(faceExp.Current()).Orientation() == TopAbs_Orientation::TopAbs_REVERSED) - { - patch.reverseTrimmingCurves(); - } } } @@ -1142,8 +1122,6 @@ class PatchTriangulator { TopoDS_Face face = TopoDS::Face(faceExp.Current()); - const bool isReversed = (face.Orientation() == TopAbs_Orientation::TopAbs_REVERSED); - // Create a triangulation of this patch TopLoc_Location loc; opencascade::handle triangulation = BRep_Tool::Triangulation(face, loc); @@ -1164,11 +1142,6 @@ class PatchTriangulator int n1, n2, n3; triangle.Get(n1, n2, n3); - if(isReversed) - { - std::swap(n1, n3); - } - gp_Pnt p1 = triangulation->Node(n1).Transformed(trsf); gp_Pnt p2 = triangulation->Node(n2).Transformed(trsf); gp_Pnt p3 = triangulation->Node(n3).Transformed(trsf); @@ -1202,8 +1175,6 @@ class PatchTriangulator { TopoDS_Face face = TopoDS::Face(faceExp.Current()); - const bool isReversed = (face.Orientation() == TopAbs_Orientation::TopAbs_REVERSED); - // Get the underlying surface of the face opencascade::handle surface = BRep_Tool::Surface(face); @@ -1241,11 +1212,6 @@ class PatchTriangulator int n1, n2, n3; triangle.Get(n1, n2, n3); - if(isReversed) - { - std::swap(n1, n3); - } - gp_Pnt p1 = triangulation->Node(n1).Transformed(trsf); gp_Pnt p2 = triangulation->Node(n2).Transformed(trsf); gp_Pnt p3 = triangulation->Node(n3).Transformed(trsf); From 4ccb55ffc0bcf648780813f8373015cd676060f2 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Tue, 24 Feb 2026 17:31:53 -0800 Subject: [PATCH 026/986] Add better comments --- src/axom/quest/tests/quest_step_reader.cpp | 29 ++++++++-------------- 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/src/axom/quest/tests/quest_step_reader.cpp b/src/axom/quest/tests/quest_step_reader.cpp index 846120a40e..d8276d8d72 100644 --- a/src/axom/quest/tests/quest_step_reader.cpp +++ b/src/axom/quest/tests/quest_step_reader.cpp @@ -45,9 +45,10 @@ bool isNearInteger(double value, double eps) return std::abs(value - nearest) <= eps; } -namespace -{ -struct TriangleGwnEvaluator +//------------------------------------------------------------------------------ +// Use a pared-down version of the TriangleGWN3D method to directly evaluate +// the for a triangle mesh +struct MiniTriangleGWN3D { using Point3D = primal::Point; using Triangle3D = primal::Triangle; @@ -69,10 +70,6 @@ struct TriangleGwnEvaluator m_shapeCenter = shapeBBox.getCentroid(); const auto longestDim = shapeBBox.getLongestDimension(); m_scale = shapeBBox.getMax()[longestDim] - shapeBBox.getMin()[longestDim]; - if(m_scale <= 0.) - { - m_scale = 1.; - } const auto& ctr = m_shapeCenter; const auto scl = m_scale; @@ -82,16 +79,12 @@ struct TriangleGwnEvaluator AXOM_LAMBDA(axom::IndexType cellIdx, const axom::numerics::Matrix& coords, [[maybe_unused]] const axom::IndexType* nodeIds) { - trisView[cellIdx] = - Triangle3D {Point3D {(coords(0, 0) - ctr[0]) / scl, - (coords(1, 0) - ctr[1]) / scl, - (coords(2, 0) - ctr[2]) / scl}, - Point3D {(coords(0, 1) - ctr[0]) / scl, - (coords(1, 1) - ctr[1]) / scl, - (coords(2, 1) - ctr[2]) / scl}, - Point3D {(coords(0, 2) - ctr[0]) / scl, - (coords(1, 2) - ctr[1]) / scl, - (coords(2, 2) - ctr[2]) / scl}}; + // clang-format off + trisView[cellIdx] = + Triangle3D {Point3D {(coords(0, 0) - ctr[0]) / scl, (coords(1, 0) - ctr[1]) / scl, (coords(2, 0) - ctr[2]) / scl}, + Point3D {(coords(0, 1) - ctr[0]) / scl, (coords(1, 1) - ctr[1]) / scl, (coords(2, 1) - ctr[2]) / scl}, + Point3D {(coords(0, 2) - ctr[0]) / scl, (coords(1, 2) - ctr[1]) / scl, (coords(2, 2) - ctr[2]) / scl}}; + // clang-format on }); } @@ -185,7 +178,7 @@ void runStepFileTest(const std::string& stepFile) mint::UnstructuredMesh triMesh(3, mint::TRIANGLE); stepReader.getTriangleMesh(&triMesh, 0.01, 0.5); - TriangleGwnEvaluator triEval; + MiniTriangleGWN3D triEval; triEval.preprocess(triMesh); const auto tri_gwn_arr = triEval.evaluate(query_arr, tol.edge_tol, tol.EPS); From b85a89ed51bcd738552a81a77e09de30013c7fb4 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Thu, 26 Feb 2026 19:08:31 -0800 Subject: [PATCH 027/986] Initial commit --- src/axom/quest/io/STEPReader.cpp | 121 ++++++++++++++++++++- src/axom/quest/tests/quest_step_reader.cpp | 6 +- 2 files changed, 122 insertions(+), 5 deletions(-) diff --git a/src/axom/quest/io/STEPReader.cpp b/src/axom/quest/io/STEPReader.cpp index 0c11479265..f43679f118 100644 --- a/src/axom/quest/io/STEPReader.cpp +++ b/src/axom/quest/io/STEPReader.cpp @@ -26,6 +26,7 @@ #include "opencascade/BRepLib.hxx" #include "opencascade/BRepMesh_IncrementalMesh.hxx" #include "opencascade/BRepTools.hxx" +#include "opencascade/BRepTools_WireExplorer.hxx" #include "opencascade/BRepBndLib.hxx" #include "opencascade/Geom_BSplineSurface.hxx" #include "opencascade/Geom_RectangularTrimmedSurface.hxx" @@ -64,6 +65,7 @@ namespace internal struct PatchData { int patchIndex {-1}; + bool faceWasReversed {false}; bool wasOriginallyPeriodic_u {false}; bool wasOriginallyPeriodic_v {false}; axom::primal::BoundingBox parametricBBox; @@ -685,9 +687,20 @@ class StepFileProcessor PatchData& patchData = m_patchData[patchIndex]; patchData.patchIndex = patchIndex; + patchData.faceWasReversed = (face.Orientation() == TopAbs_REVERSED); patchData.wasOriginallyPeriodic_u = patchProcessor.patchWasOriginallyPeriodic_u(); patchData.wasOriginallyPeriodic_v = patchProcessor.patchWasOriginallyPeriodic_v(); patchData.parametricBBox = faceBoundingBox(face); + + // OpenCascade uses topological face orientation to indicate whether the face's + // outward normal is reversed relative to the underlying surface parameterization. + // Axom's winding-number routines require a consistently oriented surface, so + // we apply that reversal to the patch geometry itself. + if(patchData.faceWasReversed) + { + patches[patchIndex].reverseOrientation_u(); + } + patchData.physicalBBox = patches[patchIndex].boundingBox(); if(patchData.wasOriginallyPeriodic_u || patchData.wasOriginallyPeriodic_v) @@ -840,8 +853,13 @@ class StepFileProcessor int patchIndex = 0; for(TopExp_Explorer faceExp(m_shape, TopAbs_FACE); faceExp.More(); faceExp.Next(), ++patchIndex) { + const TopoDS_Face face = TopoDS::Face(faceExp.Current()); PatchData& patchData = m_patchData[patchIndex]; auto& patch = patches[patchIndex]; + const bool faceWasReversed = patchData.faceWasReversed; + const double umin = patch.getMinKnot_u(); + const double umax = patch.getMaxKnot_u(); + const TopoDS_Wire outerWire = BRepTools::OuterWire(face); // Get span of this patch in u and v directions BBox2D patchBbox = patchData.parametricBBox; @@ -860,11 +878,15 @@ class StepFileProcessor { const TopoDS_Wire& wire = TopoDS::Wire(wireExp.Current()); + const int wireCurveBegin = patch.getNumTrimmingCurves(); + axom::Array wirePolyline; + int edgeIndex = 0; - for(TopExp_Explorer edgeExp(wire, TopAbs_EDGE); edgeExp.More(); edgeExp.Next(), ++edgeIndex) + for(BRepTools_WireExplorer edgeExp(wire, face); edgeExp.More(); edgeExp.Next(), ++edgeIndex) { - const TopoDS_Edge& edge = TopoDS::Edge(edgeExp.Current()); + const TopoDS_Edge edge = TopoDS::Edge(edgeExp.Current()); const int curveIndex = patch.getNumTrimmingCurves(); + const bool isReversed = (edge.Orientation() == TopAbs_REVERSED); if(m_verbose) { @@ -879,7 +901,7 @@ class StepFileProcessor Standard_Real first, last; opencascade::handle parametricCurve = - BRep_Tool::CurveOnSurface(edge, TopoDS::Face(faceExp.Current()), first, last); + BRep_Tool::CurveOnSurface(edge, face, first, last); opencascade::handle bsplineCurve = Geom2dConvert::CurveToBSplineCurve(parametricCurve); @@ -892,6 +914,23 @@ class StepFileProcessor patchData.trimmingCurves_originallyPeriodic.push_back( curveProcessor.curveWasOriginallyPeriodic()); + // Ensure consistency of curve direction with the wire traversal + if(isReversed) + { + curve.reverseOrientation(); + } + + // If we reversed the patch geometry's u-orientation, apply the same + // parametric transform to the trimming curve control points. + if(faceWasReversed) + { + auto& cps = curve.getControlPoints(); + for(auto& pt : cps) + { + pt[0] = umin + umax - pt[0]; + } + } + SLIC_ASSERT(curve.isValidNURBS()); SLIC_ASSERT(curve.getDegree() == bsplineCurve->Degree()); @@ -923,6 +962,77 @@ class StepFileProcessor // TODO: Check that curve control points are within UV patch after adjusting periodicity } + + // Accumulate a polyline approximation of this edge in parametric space + if(!parametricCurve.IsNull()) + { + constexpr int numSamplesPerEdge = 11; + + const double t0 = isReversed ? static_cast(last) : static_cast(first); + const double t1 = isReversed ? static_cast(first) : static_cast(last); + + const int startSample = wirePolyline.empty() ? 0 : 1; + for(int s = startSample; s < numSamplesPerEdge; ++s) + { + const double alpha = (numSamplesPerEdge == 1) ? 0. : static_cast(s) / (numSamplesPerEdge - 1); + const double t = t0 + alpha * (t1 - t0); + const gp_Pnt2d uv = parametricCurve->Value(t); + double u = uv.X(); + double v = uv.Y(); + if(faceWasReversed) + { + u = umin + umax - u; + } + wirePolyline.emplace_back(PointType2D {u, v}); + } + } + } + + // Axom expects trimming loops to be oriented counterclockwise in (u,v). + // For Stokes' theorem on trimmed surfaces, inner trimming loops (holes) must have + // opposite orientation to the outer trimming loop. We use OpenCascade's notion of + // an outer wire to decide which orientation we expect, and then enforce it by + // measuring the loop orientation in parametric space. + if(wirePolyline.size() >= 3) + { + double signedArea2 = 0.; + for(int i = 0; i < wirePolyline.size(); ++i) + { + const auto& a = wirePolyline[i]; + const auto& b = wirePolyline[(i + 1) % wirePolyline.size()]; + signedArea2 += a[0] * b[1] - b[0] * a[1]; + } + + const bool isClockwise = (signedArea2 < 0.); + const bool shouldBeClockwise = !outerWire.IsNull() && !wire.IsSame(outerWire); + + if(isClockwise != shouldBeClockwise) + { + SLIC_INFO_IF(m_verbose, + axom::fmt::format("[Patch {} Wire {}] Detected {} trimming loop; reversing {} curves", + patchIndex, + wireIndex, + isClockwise ? "clockwise" : "counterclockwise", + patch.getNumTrimmingCurves() - wireCurveBegin)); + + auto& trimmingCurves = patch.getTrimmingCurves(); + const int wireCurveEnd = trimmingCurves.size(); + const int numWireCurves = wireCurveEnd - wireCurveBegin; + + // Reverse order (to preserve connectivity) and reverse direction + for(int ci = 0; ci < numWireCurves / 2; ++ci) + { + std::swap(trimmingCurves[wireCurveBegin + ci], + trimmingCurves[wireCurveEnd - 1 - ci]); + std::swap(patchData.trimmingCurves_originallyPeriodic[wireCurveBegin + ci], + patchData.trimmingCurves_originallyPeriodic[wireCurveEnd - 1 - ci]); + } + + for(int ci = wireCurveBegin; ci < wireCurveEnd; ++ci) + { + trimmingCurves[ci].reverseOrientation(); + } + } } } } @@ -1121,6 +1231,7 @@ class PatchTriangulator for(TopExp_Explorer faceExp(m_shape, TopAbs_FACE); faceExp.More(); faceExp.Next(), ++patchIndex) { TopoDS_Face face = TopoDS::Face(faceExp.Current()); + const bool flipTri = (face.Orientation() == TopAbs_REVERSED); // Create a triangulation of this patch TopLoc_Location loc; @@ -1141,6 +1252,10 @@ class PatchTriangulator Poly_Triangle triangle = triangulation->Triangle(i); int n1, n2, n3; triangle.Get(n1, n2, n3); + if(flipTri) + { + std::swap(n2, n3); + } gp_Pnt p1 = triangulation->Node(n1).Transformed(trsf); gp_Pnt p2 = triangulation->Node(n2).Transformed(trsf); diff --git a/src/axom/quest/tests/quest_step_reader.cpp b/src/axom/quest/tests/quest_step_reader.cpp index d8276d8d72..1b8abb3ef6 100644 --- a/src/axom/quest/tests/quest_step_reader.cpp +++ b/src/axom/quest/tests/quest_step_reader.cpp @@ -113,7 +113,6 @@ struct MiniTriangleGWN3D double m_scale {1.0}; axom::Array m_triangles; }; -} // namespace //------------------------------------------------------------------------------ void runStepFileTest(const std::string& stepFile) @@ -122,6 +121,7 @@ void runStepFileTest(const std::string& stepFile) SLIC_INFO(axom::fmt::format("Testing STEP file '{}'", fileName)); quest::STEPReader stepReader; + stepReader.setVerbosity(false); stepReader.setFileName(fileName); constexpr bool validate = false; @@ -176,7 +176,7 @@ void runStepFileTest(const std::string& stepFile) } mint::UnstructuredMesh triMesh(3, mint::TRIANGLE); - stepReader.getTriangleMesh(&triMesh, 0.01, 0.5); + stepReader.getTriangleMesh(&triMesh); MiniTriangleGWN3D triEval; triEval.preprocess(triMesh); @@ -195,6 +195,8 @@ TEST(quest_step_reader, orientation_check) { // If the STEP file is read properly, then the resulting GWN should be integer-valued runStepFileTest("sliced_cylinder.step"); + runStepFileTest("nut.step"); + runStepFileTest("boxed_sphere.step"); } //------------------------------------------------------------------------------ From a3c11e10b222a7bc86e0fdd1c2ef86aab0868a96 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Mon, 2 Mar 2026 11:40:30 -0800 Subject: [PATCH 028/986] Version which avoids coincident points --- src/axom/quest/io/STEPReader.cpp | 206 ++++++++++----------- src/axom/quest/tests/quest_step_reader.cpp | 19 +- 2 files changed, 105 insertions(+), 120 deletions(-) diff --git a/src/axom/quest/io/STEPReader.cpp b/src/axom/quest/io/STEPReader.cpp index f43679f118..40dd5deb0c 100644 --- a/src/axom/quest/io/STEPReader.cpp +++ b/src/axom/quest/io/STEPReader.cpp @@ -17,6 +17,7 @@ #include "opencascade/BRep_Tool.hxx" #include "opencascade/BRepAdaptor_Curve.hxx" #include "opencascade/BRepBuilderAPI_MakeFace.hxx" +#include "opencascade/BRepBuilderAPI_Sewing.hxx" #include "opencascade/BRepBuilderAPI_NurbsConvert.hxx" #include "opencascade/BRepCheck_Analyzer.hxx" #include "opencascade/BRepCheck_Edge.hxx" @@ -50,6 +51,7 @@ #include "opencascade/TopoDS_Edge.hxx" #include "opencascade/TopoDS_Face.hxx" #include "opencascade/TopoDS_Shape.hxx" +#include "opencascade/TopoDS_Solid.hxx" #include "opencascade/TopoDS_Wire.hxx" #include "opencascade/TopoDS.hxx" @@ -65,9 +67,9 @@ namespace internal struct PatchData { int patchIndex {-1}; - bool faceWasReversed {false}; bool wasOriginallyPeriodic_u {false}; bool wasOriginallyPeriodic_v {false}; + bool reversed_u {false}; axom::primal::BoundingBox parametricBBox; axom::primal::BoundingBox physicalBBox; axom::Array trimmingCurves_originallyPeriodic; @@ -112,6 +114,16 @@ class StepFileProcessor using PatchToTrimmingCurvesMap = std::map; private: + /// Reflect a trimming curve in u to match a u-reversed patch parameterization. + static void reflectCurve_u(NCurve& curve, double umin, double umax) + { + auto& ctrl = curve.getControlPoints(); + for(int i = 0; i < ctrl.size(); ++i) + { + ctrl[i][0] = umin + umax - ctrl[i][0]; + } + } + /// Returns a bounding box convering the patch's knot spans in 2D parametric space BBox2D faceBoundingBox(const TopoDS_Face& face) const { @@ -687,22 +699,19 @@ class StepFileProcessor PatchData& patchData = m_patchData[patchIndex]; patchData.patchIndex = patchIndex; - patchData.faceWasReversed = (face.Orientation() == TopAbs_REVERSED); patchData.wasOriginallyPeriodic_u = patchProcessor.patchWasOriginallyPeriodic_u(); patchData.wasOriginallyPeriodic_v = patchProcessor.patchWasOriginallyPeriodic_v(); + patchData.reversed_u = (face.Orientation() != TopAbs_FORWARD); patchData.parametricBBox = faceBoundingBox(face); + patchData.physicalBBox = patches[patchIndex].boundingBox(); - // OpenCascade uses topological face orientation to indicate whether the face's - // outward normal is reversed relative to the underlying surface parameterization. - // Axom's winding-number routines require a consistently oriented surface, so - // we apply that reversal to the patch geometry itself. - if(patchData.faceWasReversed) + // Apply the face orientation to the Axom patch so that normals are + // consistent with the original B-Rep. + if(patchData.reversed_u) { patches[patchIndex].reverseOrientation_u(); } - patchData.physicalBBox = patches[patchIndex].boundingBox(); - if(patchData.wasOriginallyPeriodic_u || patchData.wasOriginallyPeriodic_v) { opencascade::handle origSurface = @@ -853,13 +862,11 @@ class StepFileProcessor int patchIndex = 0; for(TopExp_Explorer faceExp(m_shape, TopAbs_FACE); faceExp.More(); faceExp.Next(), ++patchIndex) { - const TopoDS_Face face = TopoDS::Face(faceExp.Current()); PatchData& patchData = m_patchData[patchIndex]; auto& patch = patches[patchIndex]; - const bool faceWasReversed = patchData.faceWasReversed; - const double umin = patch.getMinKnot_u(); - const double umax = patch.getMaxKnot_u(); - const TopoDS_Wire outerWire = BRepTools::OuterWire(face); + + const double patch_umin = patch.getMinKnot_u(); + const double patch_umax = patch.getMaxKnot_u(); // Get span of this patch in u and v directions BBox2D patchBbox = patchData.parametricBBox; @@ -878,15 +885,12 @@ class StepFileProcessor { const TopoDS_Wire& wire = TopoDS::Wire(wireExp.Current()); - const int wireCurveBegin = patch.getNumTrimmingCurves(); - axom::Array wirePolyline; - int edgeIndex = 0; - for(BRepTools_WireExplorer edgeExp(wire, face); edgeExp.More(); edgeExp.Next(), ++edgeIndex) + BRepTools_WireExplorer edgeExp(wire, TopoDS::Face(faceExp.Current())); + for(; edgeExp.More(); edgeExp.Next(), ++edgeIndex) { - const TopoDS_Edge edge = TopoDS::Edge(edgeExp.Current()); + const TopoDS_Edge& edge = TopoDS::Edge(edgeExp.Current()); const int curveIndex = patch.getNumTrimmingCurves(); - const bool isReversed = (edge.Orientation() == TopAbs_REVERSED); if(m_verbose) { @@ -901,7 +905,7 @@ class StepFileProcessor Standard_Real first, last; opencascade::handle parametricCurve = - BRep_Tool::CurveOnSurface(edge, face, first, last); + BRep_Tool::CurveOnSurface(edge, TopoDS::Face(faceExp.Current()), first, last); opencascade::handle bsplineCurve = Geom2dConvert::CurveToBSplineCurve(parametricCurve); @@ -914,26 +918,19 @@ class StepFileProcessor patchData.trimmingCurves_originallyPeriodic.push_back( curveProcessor.curveWasOriginallyPeriodic()); - // Ensure consistency of curve direction with the wire traversal - if(isReversed) + SLIC_ASSERT(curve.isValidNURBS()); + SLIC_ASSERT(curve.getDegree() == bsplineCurve->Degree()); + + if(patchData.reversed_u) { - curve.reverseOrientation(); + reflectCurve_u(curve, patch_umin, patch_umax); } - // If we reversed the patch geometry's u-orientation, apply the same - // parametric transform to the trimming curve control points. - if(faceWasReversed) + if(edge.Orientation() == TopAbs_REVERSED) { - auto& cps = curve.getControlPoints(); - for(auto& pt : cps) - { - pt[0] = umin + umax - pt[0]; - } + curve.reverseOrientation(); } - SLIC_ASSERT(curve.isValidNURBS()); - SLIC_ASSERT(curve.getDegree() == bsplineCurve->Degree()); - patch.addTrimmingCurve(curve); SLIC_INFO_IF(m_verbose, @@ -962,77 +959,6 @@ class StepFileProcessor // TODO: Check that curve control points are within UV patch after adjusting periodicity } - - // Accumulate a polyline approximation of this edge in parametric space - if(!parametricCurve.IsNull()) - { - constexpr int numSamplesPerEdge = 11; - - const double t0 = isReversed ? static_cast(last) : static_cast(first); - const double t1 = isReversed ? static_cast(first) : static_cast(last); - - const int startSample = wirePolyline.empty() ? 0 : 1; - for(int s = startSample; s < numSamplesPerEdge; ++s) - { - const double alpha = (numSamplesPerEdge == 1) ? 0. : static_cast(s) / (numSamplesPerEdge - 1); - const double t = t0 + alpha * (t1 - t0); - const gp_Pnt2d uv = parametricCurve->Value(t); - double u = uv.X(); - double v = uv.Y(); - if(faceWasReversed) - { - u = umin + umax - u; - } - wirePolyline.emplace_back(PointType2D {u, v}); - } - } - } - - // Axom expects trimming loops to be oriented counterclockwise in (u,v). - // For Stokes' theorem on trimmed surfaces, inner trimming loops (holes) must have - // opposite orientation to the outer trimming loop. We use OpenCascade's notion of - // an outer wire to decide which orientation we expect, and then enforce it by - // measuring the loop orientation in parametric space. - if(wirePolyline.size() >= 3) - { - double signedArea2 = 0.; - for(int i = 0; i < wirePolyline.size(); ++i) - { - const auto& a = wirePolyline[i]; - const auto& b = wirePolyline[(i + 1) % wirePolyline.size()]; - signedArea2 += a[0] * b[1] - b[0] * a[1]; - } - - const bool isClockwise = (signedArea2 < 0.); - const bool shouldBeClockwise = !outerWire.IsNull() && !wire.IsSame(outerWire); - - if(isClockwise != shouldBeClockwise) - { - SLIC_INFO_IF(m_verbose, - axom::fmt::format("[Patch {} Wire {}] Detected {} trimming loop; reversing {} curves", - patchIndex, - wireIndex, - isClockwise ? "clockwise" : "counterclockwise", - patch.getNumTrimmingCurves() - wireCurveBegin)); - - auto& trimmingCurves = patch.getTrimmingCurves(); - const int wireCurveEnd = trimmingCurves.size(); - const int numWireCurves = wireCurveEnd - wireCurveBegin; - - // Reverse order (to preserve connectivity) and reverse direction - for(int ci = 0; ci < numWireCurves / 2; ++ci) - { - std::swap(trimmingCurves[wireCurveBegin + ci], - trimmingCurves[wireCurveEnd - 1 - ci]); - std::swap(patchData.trimmingCurves_originallyPeriodic[wireCurveBegin + ci], - patchData.trimmingCurves_originallyPeriodic[wireCurveEnd - 1 - ci]); - } - - for(int ci = wireCurveBegin; ci < wireCurveEnd; ++ci) - { - trimmingCurves[ci].reverseOrientation(); - } - } } } } @@ -1172,6 +1098,55 @@ class StepFileProcessor return TopoDS_Shape(); } + // After conversion to NURBS, orient closed solids. If the solid is not + // orientable (e.g. due to missing shared-edge connectivity), try sewing + // faces to restore topological adjacency and retry. + { + auto orientClosedSolids = [](TopoDS_Shape& inoutShape) { + ShapeBuild_ReShape reshape; + bool didOrient = false; + bool sawSolid = false; + bool orientFailed = false; + + for(TopExp_Explorer solidExp(inoutShape, TopAbs_SOLID); solidExp.More(); solidExp.Next()) + { + sawSolid = true; + TopoDS_Solid solid = TopoDS::Solid(solidExp.Current()); + if(BRepLib::OrientClosedSolid(solid)) + { + reshape.Replace(solidExp.Current(), solid); + didOrient = true; + } + else + { + orientFailed = true; + } + } + + if(didOrient) + { + inoutShape = reshape.Apply(inoutShape); + } + + return sawSolid && orientFailed; + }; + + const bool needsSewingRetry = orientClosedSolids(nurbsShape); + if(needsSewingRetry) + { + BRepBuilderAPI_Sewing sewing(Precision::Confusion()); + sewing.Add(nurbsShape); + sewing.Perform(); + + const TopoDS_Shape sewedShape = sewing.SewedShape(); + if(!sewedShape.IsNull()) + { + nurbsShape = sewedShape; + AXOM_MAYBE_UNUSED const bool _ = orientClosedSolids(nurbsShape); + } + } + } + m_loadStatus = LoadStatus::SUCCESS; SLIC_INFO_IF(m_verbose, axom::fmt::format("Successfully read the STEP file with {} roots", numRoots)); @@ -1231,7 +1206,7 @@ class PatchTriangulator for(TopExp_Explorer faceExp(m_shape, TopAbs_FACE); faceExp.More(); faceExp.Next(), ++patchIndex) { TopoDS_Face face = TopoDS::Face(faceExp.Current()); - const bool flipTri = (face.Orientation() == TopAbs_REVERSED); + const bool isReversed = (face.Orientation() != TopAbs_FORWARD); // Create a triangulation of this patch TopLoc_Location loc; @@ -1252,15 +1227,17 @@ class PatchTriangulator Poly_Triangle triangle = triangulation->Triangle(i); int n1, n2, n3; triangle.Get(n1, n2, n3); - if(flipTri) - { - std::swap(n2, n3); - } gp_Pnt p1 = triangulation->Node(n1).Transformed(trsf); gp_Pnt p2 = triangulation->Node(n2).Transformed(trsf); gp_Pnt p3 = triangulation->Node(n3).Transformed(trsf); + // Ensure triangle orientation matches the oriented face normal. + if(isReversed) + { + axom::utilities::swap(p2, p3); + } + axom::IndexType v1 = output_mesh.appendNode(p1.X(), p1.Y(), p1.Z()); axom::IndexType v2 = output_mesh.appendNode(p2.X(), p2.Y(), p2.Z()); axom::IndexType v3 = output_mesh.appendNode(p3.X(), p3.Y(), p3.Z()); @@ -1289,6 +1266,7 @@ class PatchTriangulator for(TopExp_Explorer faceExp(m_shape, TopAbs_FACE); faceExp.More(); faceExp.Next(), ++patchIndex) { TopoDS_Face face = TopoDS::Face(faceExp.Current()); + const bool isReversed = (face.Orientation() != TopAbs_FORWARD); // Get the underlying surface of the face opencascade::handle surface = BRep_Tool::Surface(face); @@ -1302,6 +1280,7 @@ class PatchTriangulator // Create a new face from the untrimmed surface TopoDS_Face newFace = BRepBuilderAPI_MakeFace(untrimmedSurface, Precision::Confusion()); + newFace.Orientation(face.Orientation()); // Mesh the new face BRepMesh_IncrementalMesh mesh(newFace, m_deflection, m_deflectionIsRelative, m_angularDeflection); @@ -1331,6 +1310,11 @@ class PatchTriangulator gp_Pnt p2 = triangulation->Node(n2).Transformed(trsf); gp_Pnt p3 = triangulation->Node(n3).Transformed(trsf); + if(isReversed) + { + axom::utilities::swap(p2, p3); + } + axom::IndexType v1 = output_mesh.appendNode(p1.X(), p1.Y(), p1.Z()); axom::IndexType v2 = output_mesh.appendNode(p2.X(), p2.Y(), p2.Z()); axom::IndexType v3 = output_mesh.appendNode(p3.X(), p3.Y(), p3.Z()); diff --git a/src/axom/quest/tests/quest_step_reader.cpp b/src/axom/quest/tests/quest_step_reader.cpp index 1b8abb3ef6..42d0cfed97 100644 --- a/src/axom/quest/tests/quest_step_reader.cpp +++ b/src/axom/quest/tests/quest_step_reader.cpp @@ -46,7 +46,7 @@ bool isNearInteger(double value, double eps) } //------------------------------------------------------------------------------ -// Use a pared-down version of the TriangleGWN3D method to directly evaluate +// Use a pared-down version of the TriangleGWN3D method to directly evaluate // the for a triangle mesh struct MiniTriangleGWN3D { @@ -138,7 +138,7 @@ void runStepFileTest(const std::string& stepFile) return; } - const auto shapeBbox = stepReader.getBRepBoundingBox(); + const auto shapeBbox = stepReader.getBRepBoundingBox().scale(1.1); auto bboxMin = shapeBbox.getMin(); auto bboxMax = shapeBbox.getMax(); const auto bboxDiag = bboxMax.array() - bboxMin.array(); @@ -168,6 +168,7 @@ void runStepFileTest(const std::string& stepFile) EXPECT_EQ(gwn_arr.size(), query_arr.size()); + SLIC_INFO("-- Testing NURBS read through GWN --"); constexpr double integer_eps = 1e-3; for(int i = 0; i < gwn_arr.size() && i < query_arr.size(); ++i) { @@ -181,6 +182,7 @@ void runStepFileTest(const std::string& stepFile) MiniTriangleGWN3D triEval; triEval.preprocess(triMesh); + SLIC_INFO("-- Testing triangulation through GWN --"); const auto tri_gwn_arr = triEval.evaluate(query_arr, tol.edge_tol, tol.EPS); EXPECT_EQ(tri_gwn_arr.size(), query_arr.size()); for(int i = 0; i < tri_gwn_arr.size() && i < query_arr.size(); ++i) @@ -191,13 +193,12 @@ void runStepFileTest(const std::string& stepFile) } //------------------------------------------------------------------------------ -TEST(quest_step_reader, orientation_check) -{ - // If the STEP file is read properly, then the resulting GWN should be integer-valued - runStepFileTest("sliced_cylinder.step"); - runStepFileTest("nut.step"); - runStepFileTest("boxed_sphere.step"); -} +// If the STEP file is read properly, then the resulting GWN should be integer-valued +TEST(quest_step_reader, orientation_check_cylinder) { runStepFileTest("sliced_cylinder.step"); } +TEST(quest_step_reader, orientation_check_nut) { runStepFileTest("nut.step"); } +TEST(quest_step_reader, orientation_check_boxed_sphere) { runStepFileTest("boxed_sphere.step"); } +TEST(quest_step_reader, orientation_check_tet) { runStepFileTest("tet.step"); } +TEST(quest_step_reader, orientation_check_bearings) { runStepFileTest("bearings.step"); } //------------------------------------------------------------------------------ int main(int argc, char* argv[]) From 26be45bcdf04d0ff626303ece48c2cd8f2fa6d4f Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Mon, 2 Mar 2026 14:59:03 -0800 Subject: [PATCH 029/986] Remove unnecessary topology check --- src/axom/quest/io/STEPReader.cpp | 49 -------------------------------- 1 file changed, 49 deletions(-) diff --git a/src/axom/quest/io/STEPReader.cpp b/src/axom/quest/io/STEPReader.cpp index 40dd5deb0c..5aa2d8a542 100644 --- a/src/axom/quest/io/STEPReader.cpp +++ b/src/axom/quest/io/STEPReader.cpp @@ -1098,55 +1098,6 @@ class StepFileProcessor return TopoDS_Shape(); } - // After conversion to NURBS, orient closed solids. If the solid is not - // orientable (e.g. due to missing shared-edge connectivity), try sewing - // faces to restore topological adjacency and retry. - { - auto orientClosedSolids = [](TopoDS_Shape& inoutShape) { - ShapeBuild_ReShape reshape; - bool didOrient = false; - bool sawSolid = false; - bool orientFailed = false; - - for(TopExp_Explorer solidExp(inoutShape, TopAbs_SOLID); solidExp.More(); solidExp.Next()) - { - sawSolid = true; - TopoDS_Solid solid = TopoDS::Solid(solidExp.Current()); - if(BRepLib::OrientClosedSolid(solid)) - { - reshape.Replace(solidExp.Current(), solid); - didOrient = true; - } - else - { - orientFailed = true; - } - } - - if(didOrient) - { - inoutShape = reshape.Apply(inoutShape); - } - - return sawSolid && orientFailed; - }; - - const bool needsSewingRetry = orientClosedSolids(nurbsShape); - if(needsSewingRetry) - { - BRepBuilderAPI_Sewing sewing(Precision::Confusion()); - sewing.Add(nurbsShape); - sewing.Perform(); - - const TopoDS_Shape sewedShape = sewing.SewedShape(); - if(!sewedShape.IsNull()) - { - nurbsShape = sewedShape; - AXOM_MAYBE_UNUSED const bool _ = orientClosedSolids(nurbsShape); - } - } - } - m_loadStatus = LoadStatus::SUCCESS; SLIC_INFO_IF(m_verbose, axom::fmt::format("Successfully read the STEP file with {} roots", numRoots)); From 284a0727ff305c1a888d86f7816ab96152d01d6b Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Tue, 3 Mar 2026 17:59:31 -0800 Subject: [PATCH 030/986] Fix the test --- src/axom/quest/tests/quest_step_reader.cpp | 23 +++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/axom/quest/tests/quest_step_reader.cpp b/src/axom/quest/tests/quest_step_reader.cpp index 42d0cfed97..3999c243f4 100644 --- a/src/axom/quest/tests/quest_step_reader.cpp +++ b/src/axom/quest/tests/quest_step_reader.cpp @@ -79,12 +79,12 @@ struct MiniTriangleGWN3D AXOM_LAMBDA(axom::IndexType cellIdx, const axom::numerics::Matrix& coords, [[maybe_unused]] const axom::IndexType* nodeIds) { - // clang-format off + // clang-format off trisView[cellIdx] = Triangle3D {Point3D {(coords(0, 0) - ctr[0]) / scl, (coords(1, 0) - ctr[1]) / scl, (coords(2, 0) - ctr[2]) / scl}, Point3D {(coords(0, 1) - ctr[0]) / scl, (coords(1, 1) - ctr[1]) / scl, (coords(2, 1) - ctr[2]) / scl}, Point3D {(coords(0, 2) - ctr[0]) / scl, (coords(1, 2) - ctr[1]) / scl, (coords(2, 2) - ctr[2]) / scl}}; - // clang-format on + // clang-format on }); } @@ -115,7 +115,7 @@ struct MiniTriangleGWN3D }; //------------------------------------------------------------------------------ -void runStepFileTest(const std::string& stepFile) +void runStepFileTest(const std::string& stepFile, double deflection = 0.1) { const std::string fileName = pjoin(AXOM_DATA_DIR, "quest", "step", stepFile); SLIC_INFO(axom::fmt::format("Testing STEP file '{}'", fileName)); @@ -177,7 +177,7 @@ void runStepFileTest(const std::string& stepFile) } mint::UnstructuredMesh triMesh(3, mint::TRIANGLE); - stepReader.getTriangleMesh(&triMesh); + stepReader.getTriangleMesh(&triMesh, deflection); MiniTriangleGWN3D triEval; triEval.preprocess(triMesh); @@ -194,11 +194,16 @@ void runStepFileTest(const std::string& stepFile) //------------------------------------------------------------------------------ // If the STEP file is read properly, then the resulting GWN should be integer-valued -TEST(quest_step_reader, orientation_check_cylinder) { runStepFileTest("sliced_cylinder.step"); } -TEST(quest_step_reader, orientation_check_nut) { runStepFileTest("nut.step"); } -TEST(quest_step_reader, orientation_check_boxed_sphere) { runStepFileTest("boxed_sphere.step"); } -TEST(quest_step_reader, orientation_check_tet) { runStepFileTest("tet.step"); } -TEST(quest_step_reader, orientation_check_bearings) { runStepFileTest("bearings.step"); } +TEST(quest_step_reader, test_tet) { runStepFileTest("tet.step"); } +TEST(quest_step_reader, test_cylinder) { runStepFileTest("sliced_cylinder.step"); } +TEST(quest_step_reader, test_nut) { runStepFileTest("nut.step"); } +TEST(quest_step_reader, test_bearings) { runStepFileTest("bearings.step", 0.05); } +TEST(quest_step_reader, test_half_bsphere) { runStepFileTest("half_boxed_sphere.step"); } + +// These tests are more expensive and therefore should not be run in the main testing loop, +// but should still be checked if something changes in STEPReader.cpp +//TEST(quest_step_reader, test_boxed_sphere) { runStepFileTest("boxed_sphere.step"); } +//TEST(quest_step_reader, test_brace) { runStepFileTest("plate.step", 0.01); } //------------------------------------------------------------------------------ int main(int argc, char* argv[]) From be1b9e6682378d0d8f5b8fb216fe3f36cd11dc18 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Fri, 13 Mar 2026 17:09:20 -0700 Subject: [PATCH 031/986] Increase test fidelity --- src/axom/quest/tests/quest_step_reader.cpp | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/axom/quest/tests/quest_step_reader.cpp b/src/axom/quest/tests/quest_step_reader.cpp index 3999c243f4..bcba8cbf78 100644 --- a/src/axom/quest/tests/quest_step_reader.cpp +++ b/src/axom/quest/tests/quest_step_reader.cpp @@ -143,16 +143,16 @@ void runStepFileTest(const std::string& stepFile, double deflection = 0.1) auto bboxMax = shapeBbox.getMax(); const auto bboxDiag = bboxMax.array() - bboxMin.array(); - axom::Array> query_arr(0, 27); - for(const double fx : {0.25, 0.5, 0.75}) + axom::Array> query_arr; + for(const double fx : {0.11, 0.251, 0.51, 0.751, 0.91}) { - for(const double fy : {0.25, 0.5, 0.75}) + for(const double fy : {0.11, 0.251, 0.51, 0.751, 0.91}) { - for(const double fz : {0.25, 0.5, 0.75}) + for(const double fz : {0.11, 0.251, 0.51, 0.751, 0.91}) { - query_arr.emplace_back(primal::Point({bboxMin[0] + fx * bboxDiag[0], - bboxMin[1] + fy * bboxDiag[1], - bboxMin[2] + fz * bboxDiag[2]})); + query_arr.push_back(primal::Point({bboxMin[0] + fx * bboxDiag[0], + bboxMin[1] + fy * bboxDiag[1], + bboxMin[2] + fz * bboxDiag[2]})); } } } @@ -198,12 +198,11 @@ TEST(quest_step_reader, test_tet) { runStepFileTest("tet.step"); } TEST(quest_step_reader, test_cylinder) { runStepFileTest("sliced_cylinder.step"); } TEST(quest_step_reader, test_nut) { runStepFileTest("nut.step"); } TEST(quest_step_reader, test_bearings) { runStepFileTest("bearings.step", 0.05); } -TEST(quest_step_reader, test_half_bsphere) { runStepFileTest("half_boxed_sphere.step"); } // These tests are more expensive and therefore should not be run in the main testing loop, // but should still be checked if something changes in STEPReader.cpp -//TEST(quest_step_reader, test_boxed_sphere) { runStepFileTest("boxed_sphere.step"); } -//TEST(quest_step_reader, test_brace) { runStepFileTest("plate.step", 0.01); } +TEST(quest_step_reader, test_boxed_sphere) { runStepFileTest("boxed_sphere.step"); } +TEST(quest_step_reader, test_brace) { runStepFileTest("plate.step", 0.01); } //------------------------------------------------------------------------------ int main(int argc, char* argv[]) From b90799532288527e5e330670dd70e9c4631b4875 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Mon, 16 Mar 2026 13:14:31 -0700 Subject: [PATCH 032/986] Fix 2D GWN calls --- src/axom/primal/operators/winding_number.hpp | 20 +++++--- src/axom/quest/tests/quest_step_reader.cpp | 52 ++++++++++++++++++-- 2 files changed, 61 insertions(+), 11 deletions(-) diff --git a/src/axom/primal/operators/winding_number.hpp b/src/axom/primal/operators/winding_number.hpp index 00faa15dcc..bfcbd5f384 100644 --- a/src/axom/primal/operators/winding_number.hpp +++ b/src/axom/primal/operators/winding_number.hpp @@ -373,7 +373,7 @@ double winding_number(const Point& query, */ template axom::Array winding_number(const axom::Array>& query_arr, - const axom::Array>& nurbs_curve_arr, + const axom::Array>& nurbs_cache_arr, double edge_tol = 1e-8, double EPS = 1e-8) { @@ -383,13 +383,19 @@ axom::Array winding_number(const axom::Array>& query_arr, { ret_val[n] = 0.0; - for(int i = 0; i < nurbs_curve_arr.size(); ++i) + for(int i = 0; i < nurbs_cache_arr.size(); ++i) { - ret_val[n] += detail::bezier_winding_number_memoized(query_arr[n], - nurbs_curve_arr[i], - dummy_isOnCurve, - edge_tol, - EPS); + for(int k = 0; k < nurbs_cache_arr[i].getNumKnotSpans(); ++k) + { + ret_val[n] += detail::bezier_winding_number_memoized(query_arr[n], + nurbs_cache_arr[i], + k, + 0, + 0, + dummy_isOnCurve, + edge_tol, + EPS); + } } } diff --git a/src/axom/quest/tests/quest_step_reader.cpp b/src/axom/quest/tests/quest_step_reader.cpp index bcba8cbf78..843bc1af66 100644 --- a/src/axom/quest/tests/quest_step_reader.cpp +++ b/src/axom/quest/tests/quest_step_reader.cpp @@ -114,6 +114,38 @@ struct MiniTriangleGWN3D axom::Array m_triangles; }; +//------------------------------------------------------------------------------ +void runSinglePatchTest(const axom::primal::NURBSPatch& patch, int idx) +{ + // This function evaluates the 2D GWN in the patch parameter space on a grid of points + // to verify that the value is near a nonnegative integer, i.e. the trimming curves + // form closed, CCW oriented loops + + constexpr int npts = 10; + double u_pts[npts], v_pts[npts]; + axom::numerics::linspace(patch.getMinKnot_u() - 0.11, patch.getMaxKnot_u() + 0.102, u_pts, npts); + axom::numerics::linspace(patch.getMinKnot_v() - 0.12, patch.getMaxKnot_v() + 0.101, v_pts, npts); + + axom::Array> query_arr; + for(auto u : u_pts) + { + for(auto v : v_pts) + { + query_arr.push_back(axom::primal::Point {u, v}); + } + } + + auto gwn_arr = axom::primal::winding_number(query_arr, patch.getTrimmingCurves()); + + constexpr double integer_eps = 1e-3; + for(auto wn : gwn_arr) + { + EXPECT_NEAR(wn, std::round(wn), integer_eps) + << axom::fmt::format("Patch {} has GWN not near integers\n", idx); + EXPECT_GE(std::round(wn), 0) << axom::fmt::format("Patch {} has GWN not near integers\n", idx); + } +} + //------------------------------------------------------------------------------ void runStepFileTest(const std::string& stepFile, double deflection = 0.1) { @@ -144,11 +176,11 @@ void runStepFileTest(const std::string& stepFile, double deflection = 0.1) const auto bboxDiag = bboxMax.array() - bboxMin.array(); axom::Array> query_arr; - for(const double fx : {0.11, 0.251, 0.51, 0.751, 0.91}) + for(const double fx : {0.11, 0.251, 0.52, 0.731, 0.91}) { - for(const double fy : {0.11, 0.251, 0.51, 0.751, 0.91}) + for(const double fy : {0.09, 0.249, 0.51, 0.75, 0.81}) { - for(const double fz : {0.11, 0.251, 0.51, 0.751, 0.91}) + for(const double fz : {0.105, 0.25, 0.5, 0.751, 0.901}) { query_arr.push_back(primal::Point({bboxMin[0] + fx * bboxDiag[0], bboxMin[1] + fy * bboxDiag[1], @@ -190,6 +222,18 @@ void runStepFileTest(const std::string& stepFile, double deflection = 0.1) const double wn = tri_gwn_arr[i]; EXPECT_NEAR(wn, std::round(wn), integer_eps); } + + // Applying this function uniformly to every patch in the surface can help point out + // some issues while debugging, but will also catch cases where trimming curves don't + // form closed loops, which the STEPReader isn't expected to correct. + + /* + SLIC_INFO("-- Testing NURBS read through Patch GWN --"); + for(int i = 0; i < patches.size(); ++i) + { + runSinglePatchTest(patches[i], i); + } + */ } //------------------------------------------------------------------------------ @@ -202,7 +246,7 @@ TEST(quest_step_reader, test_bearings) { runStepFileTest("bearings.step", 0.05); // These tests are more expensive and therefore should not be run in the main testing loop, // but should still be checked if something changes in STEPReader.cpp TEST(quest_step_reader, test_boxed_sphere) { runStepFileTest("boxed_sphere.step"); } -TEST(quest_step_reader, test_brace) { runStepFileTest("plate.step", 0.01); } +TEST(quest_step_reader, test_plate) { runStepFileTest("plate.step", 0.01); } //------------------------------------------------------------------------------ int main(int argc, char* argv[]) From 259d6d32093417e5aa308702c496e219b243763e Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Mon, 16 Mar 2026 13:34:27 -0700 Subject: [PATCH 033/986] Add a new example on a smaller shape --- src/axom/quest/tests/quest_step_reader.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/axom/quest/tests/quest_step_reader.cpp b/src/axom/quest/tests/quest_step_reader.cpp index 843bc1af66..56724cad08 100644 --- a/src/axom/quest/tests/quest_step_reader.cpp +++ b/src/axom/quest/tests/quest_step_reader.cpp @@ -117,10 +117,10 @@ struct MiniTriangleGWN3D //------------------------------------------------------------------------------ void runSinglePatchTest(const axom::primal::NURBSPatch& patch, int idx) { - // This function evaluates the 2D GWN in the patch parameter space on a grid of points + // This function evaluates the 2D GWN in the patch parameter space on a grid of points // to verify that the value is near a nonnegative integer, i.e. the trimming curves // form closed, CCW oriented loops - + constexpr int npts = 10; double u_pts[npts], v_pts[npts]; axom::numerics::linspace(patch.getMinKnot_u() - 0.11, patch.getMaxKnot_u() + 0.102, u_pts, npts); @@ -242,11 +242,12 @@ TEST(quest_step_reader, test_tet) { runStepFileTest("tet.step"); } TEST(quest_step_reader, test_cylinder) { runStepFileTest("sliced_cylinder.step"); } TEST(quest_step_reader, test_nut) { runStepFileTest("nut.step"); } TEST(quest_step_reader, test_bearings) { runStepFileTest("bearings.step", 0.05); } +TEST(quest_step_reader, test_bolt_clip) { runStepFileTest("bolt_clip.step"); } // These tests are more expensive and therefore should not be run in the main testing loop, // but should still be checked if something changes in STEPReader.cpp -TEST(quest_step_reader, test_boxed_sphere) { runStepFileTest("boxed_sphere.step"); } -TEST(quest_step_reader, test_plate) { runStepFileTest("plate.step", 0.01); } +//TEST(quest_step_reader, test_boxed_sphere) { runStepFileTest("boxed_sphere.step"); } +//TEST(quest_step_reader, test_plate) { runStepFileTest("plate.step", 0.01); } //------------------------------------------------------------------------------ int main(int argc, char* argv[]) From 4ce410dc5596351a2e787394fa0e1011e89f8767 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Mon, 16 Mar 2026 16:39:50 -0700 Subject: [PATCH 034/986] Update release notes, optional test macros --- RELEASE-NOTES.md | 1 + src/axom/quest/tests/quest_step_reader.cpp | 35 +++++++++++----------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index e11540da9b..1a7621fe8f 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -53,6 +53,7 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ allocator id via Umpire, if present. This prevents excessive calls to Umpire, which are not needed in all use cases. - Quest: A compilation problem with `-DAXOM_NO_INT64_T=1` was fixed. +- Quest: `STEPReader` now catches additional edge cases related to orientation of OpenCascade primitives. ## [Version 0.13.0] - Release date 2026-02-05 diff --git a/src/axom/quest/tests/quest_step_reader.cpp b/src/axom/quest/tests/quest_step_reader.cpp index 56724cad08..c2b82fe004 100644 --- a/src/axom/quest/tests/quest_step_reader.cpp +++ b/src/axom/quest/tests/quest_step_reader.cpp @@ -21,6 +21,11 @@ namespace mint = axom::mint; namespace primal = axom::primal; namespace quest = axom::quest; +// Enable code to apply test to larger files +// and check orientation of trimming curves +//#define AXOM_TEST_EXPENSIVE_STEP +//#define AXOM_TEST_PATCH_ORIENTATION + //------------------------------------------------------------------------------ std::string pjoin(const std::string& str) { return str; } @@ -38,13 +43,6 @@ std::string pjoin(const char* str, Args... args) return fs::joinPath(std::string(str), pjoin(args...)); } -//------------------------------------------------------------------------------ -bool isNearInteger(double value, double eps) -{ - const double nearest = std::round(value); - return std::abs(value - nearest) <= eps; -} - //------------------------------------------------------------------------------ // Use a pared-down version of the TriangleGWN3D method to directly evaluate // the for a triangle mesh @@ -80,10 +78,9 @@ struct MiniTriangleGWN3D const axom::numerics::Matrix& coords, [[maybe_unused]] const axom::IndexType* nodeIds) { // clang-format off - trisView[cellIdx] = - Triangle3D {Point3D {(coords(0, 0) - ctr[0]) / scl, (coords(1, 0) - ctr[1]) / scl, (coords(2, 0) - ctr[2]) / scl}, - Point3D {(coords(0, 1) - ctr[0]) / scl, (coords(1, 1) - ctr[1]) / scl, (coords(2, 1) - ctr[2]) / scl}, - Point3D {(coords(0, 2) - ctr[0]) / scl, (coords(1, 2) - ctr[1]) / scl, (coords(2, 2) - ctr[2]) / scl}}; + trisView[cellIdx] = Triangle3D {Point3D {(coords(0, 0) - ctr[0]) / scl, (coords(1, 0) - ctr[1]) / scl, (coords(2, 0) - ctr[2]) / scl}, + Point3D {(coords(0, 1) - ctr[0]) / scl, (coords(1, 1) - ctr[1]) / scl, (coords(2, 1) - ctr[2]) / scl}, + Point3D {(coords(0, 2) - ctr[0]) / scl, (coords(1, 2) - ctr[1]) / scl, (coords(2, 2) - ctr[2]) / scl}}; // clang-format on }); } @@ -115,7 +112,7 @@ struct MiniTriangleGWN3D }; //------------------------------------------------------------------------------ -void runSinglePatchTest(const axom::primal::NURBSPatch& patch, int idx) +void testSinglePatchOrientation(const axom::primal::NURBSPatch& patch, int idx) { // This function evaluates the 2D GWN in the patch parameter space on a grid of points // to verify that the value is near a nonnegative integer, i.e. the trimming curves @@ -223,17 +220,17 @@ void runStepFileTest(const std::string& stepFile, double deflection = 0.1) EXPECT_NEAR(wn, std::round(wn), integer_eps); } +#ifdef AXOM_TEST_PATCH_ORIENTATION // Applying this function uniformly to every patch in the surface can help point out // some issues while debugging, but will also catch cases where trimming curves don't - // form closed loops, which the STEPReader isn't expected to correct. + // form closed loops, which the STEPReader isn't expected to handle or correct. - /* SLIC_INFO("-- Testing NURBS read through Patch GWN --"); for(int i = 0; i < patches.size(); ++i) { - runSinglePatchTest(patches[i], i); + testSinglePatchOrientation(patches[i], i); } - */ +#endif } //------------------------------------------------------------------------------ @@ -244,10 +241,12 @@ TEST(quest_step_reader, test_nut) { runStepFileTest("nut.step"); } TEST(quest_step_reader, test_bearings) { runStepFileTest("bearings.step", 0.05); } TEST(quest_step_reader, test_bolt_clip) { runStepFileTest("bolt_clip.step"); } +#ifdef AXOM_TEST_EXPENSIVE_STEP // These tests are more expensive and therefore should not be run in the main testing loop, // but should still be checked if something changes in STEPReader.cpp -//TEST(quest_step_reader, test_boxed_sphere) { runStepFileTest("boxed_sphere.step"); } -//TEST(quest_step_reader, test_plate) { runStepFileTest("plate.step", 0.01); } +TEST(quest_step_reader, test_boxed_sphere) { runStepFileTest("boxed_sphere.step"); } +TEST(quest_step_reader, test_plate) { runStepFileTest("plate.step", 0.01); } +#endif //------------------------------------------------------------------------------ int main(int argc, char* argv[]) From 2b5fdafbc66702e39d4f3835692b47394a8800bf Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Tue, 31 Mar 2026 10:07:46 -0700 Subject: [PATCH 035/986] Add some range based iterators and other comment changes --- src/axom/primal/operators/winding_number.hpp | 26 ++++++++++---------- src/axom/quest/io/STEPReader.cpp | 8 +++--- src/axom/quest/tests/quest_step_reader.cpp | 6 ++--- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/axom/primal/operators/winding_number.hpp b/src/axom/primal/operators/winding_number.hpp index bfcbd5f384..388996b1ca 100644 --- a/src/axom/primal/operators/winding_number.hpp +++ b/src/axom/primal/operators/winding_number.hpp @@ -227,9 +227,9 @@ double winding_number(const Point& q, { bool dummy_isOnCurve = false; double ret_val = 0.0; - for(int i = 0; i < carray.size(); i++) + for(auto& curv : carray) { - ret_val += detail::bezier_winding_number(q, carray[i], dummy_isOnCurve, edge_tol, EPS); + ret_val += detail::bezier_winding_number(q, curv, dummy_isOnCurve, edge_tol, EPS); } return ret_val; @@ -321,7 +321,7 @@ double winding_number(const Point& q, * \brief Computes the GWN for a 2D point wrt an array of memoized data for 2D NURBS curves * * \param [in] query The query point to test - * \param [in] nurbs_curve_arr The array of memoized curve objects + * \param [in] nurbs_cache_arr The array of memoized curve objects * \param [out] isOnCurve Set to true is the query point is on the curve * \param [in] edge_tol The physical distance level at which objects are considered indistinguishable * \param [in] EPS Miscellaneous numerical tolerance level for nonphysical distances @@ -330,17 +330,17 @@ double winding_number(const Point& q, */ template double winding_number(const Point& query, - const axom::Array>& nurbs_curve_arr, + const axom::Array>& nurbs_cache_arr, bool& isOnCurve, double edge_tol = 1e-8, double EPS = 1e-8) { double gwn = 0; isOnCurve = false; - for(int i = 0; i < nurbs_curve_arr.size(); ++i) + for(auto& the_cache : nurbs_cache_arr) { bool isOnThisCurve = false; - gwn += winding_number(query, nurbs_curve_arr[i], isOnThisCurve, edge_tol, EPS); + gwn += winding_number(query, the_cache, isOnThisCurve, edge_tol, EPS); isOnCurve = isOnCurve || isOnThisCurve; } @@ -383,12 +383,12 @@ axom::Array winding_number(const axom::Array>& query_arr, { ret_val[n] = 0.0; - for(int i = 0; i < nurbs_cache_arr.size(); ++i) + for(auto& the_cache : nurbs_cache_arr) { - for(int k = 0; k < nurbs_cache_arr[i].getNumKnotSpans(); ++k) + for(int k = 0; k < the_cache.getNumKnotSpans(); ++k) { ret_val[n] += detail::bezier_winding_number_memoized(query_arr[n], - nurbs_cache_arr[i], + the_cache, k, 0, 0, @@ -424,9 +424,9 @@ axom::Array winding_number(const axom::Array>& query_arr, { axom::Array> cache_arr(0, curve_arr.size()); - for(int i = 0; i < curve_arr.size(); ++i) + for(auto& curv : curve_arr) { - cache_arr.emplace_back(detail::NURBSCurveGWNCache(curve_arr[i], edge_tol)); + cache_arr.emplace_back(detail::NURBSCurveGWNCache(curv, edge_tol)); } return winding_number(query_arr, cache_arr, edge_tol, EPS); @@ -902,9 +902,9 @@ axom::Array winding_number(const axom::Array>& query_arr, { // Precompute the expansions and cast directions for each patch axom::Array> nurbs_cache_arr(0, surf_arr.size()); - for(int i = 0; i < surf_arr.size(); ++i) + for(autp& surf : surf_arr) { - nurbs_cache_arr.emplace_back(detail::NURBSPatchGWNCache(surf_arr[i])); + nurbs_cache_arr.emplace_back(detail::NURBSPatchGWNCache(surf)); } return winding_number(query_arr, nurbs_cache_arr, edge_tol, ls_tol, quad_tol, disk_size, EPS); diff --git a/src/axom/quest/io/STEPReader.cpp b/src/axom/quest/io/STEPReader.cpp index 5aa2d8a542..be1cb649a6 100644 --- a/src/axom/quest/io/STEPReader.cpp +++ b/src/axom/quest/io/STEPReader.cpp @@ -69,7 +69,7 @@ struct PatchData int patchIndex {-1}; bool wasOriginallyPeriodic_u {false}; bool wasOriginallyPeriodic_v {false}; - bool reversed_u {false}; + bool was_reversed_u {false}; axom::primal::BoundingBox parametricBBox; axom::primal::BoundingBox physicalBBox; axom::Array trimmingCurves_originallyPeriodic; @@ -701,13 +701,13 @@ class StepFileProcessor patchData.patchIndex = patchIndex; patchData.wasOriginallyPeriodic_u = patchProcessor.patchWasOriginallyPeriodic_u(); patchData.wasOriginallyPeriodic_v = patchProcessor.patchWasOriginallyPeriodic_v(); - patchData.reversed_u = (face.Orientation() != TopAbs_FORWARD); + patchData.was_reversed_u = (face.Orientation() != TopAbs_FORWARD); patchData.parametricBBox = faceBoundingBox(face); patchData.physicalBBox = patches[patchIndex].boundingBox(); // Apply the face orientation to the Axom patch so that normals are // consistent with the original B-Rep. - if(patchData.reversed_u) + if(patchData.was_reversed_u) { patches[patchIndex].reverseOrientation_u(); } @@ -921,7 +921,7 @@ class StepFileProcessor SLIC_ASSERT(curve.isValidNURBS()); SLIC_ASSERT(curve.getDegree() == bsplineCurve->Degree()); - if(patchData.reversed_u) + if(patchData.was_reversed_u) { reflectCurve_u(curve, patch_umin, patch_umax); } diff --git a/src/axom/quest/tests/quest_step_reader.cpp b/src/axom/quest/tests/quest_step_reader.cpp index c2b82fe004..58e2e3c954 100644 --- a/src/axom/quest/tests/quest_step_reader.cpp +++ b/src/axom/quest/tests/quest_step_reader.cpp @@ -45,7 +45,7 @@ std::string pjoin(const char* str, Args... args) //------------------------------------------------------------------------------ // Use a pared-down version of the TriangleGWN3D method to directly evaluate -// the for a triangle mesh +// the winding number for a triangle mesh struct MiniTriangleGWN3D { using Point3D = primal::Point; @@ -76,7 +76,7 @@ struct MiniTriangleGWN3D &mesh, AXOM_LAMBDA(axom::IndexType cellIdx, const axom::numerics::Matrix& coords, - [[maybe_unused]] const axom::IndexType* nodeIds) { + const axom::IndexType* /*nodeIds*/) { // clang-format off trisView[cellIdx] = Triangle3D {Point3D {(coords(0, 0) - ctr[0]) / scl, (coords(1, 0) - ctr[1]) / scl, (coords(2, 0) - ctr[2]) / scl}, Point3D {(coords(0, 1) - ctr[0]) / scl, (coords(1, 1) - ctr[1]) / scl, (coords(2, 1) - ctr[2]) / scl}, @@ -170,7 +170,7 @@ void runStepFileTest(const std::string& stepFile, double deflection = 0.1) const auto shapeBbox = stepReader.getBRepBoundingBox().scale(1.1); auto bboxMin = shapeBbox.getMin(); auto bboxMax = shapeBbox.getMax(); - const auto bboxDiag = bboxMax.array() - bboxMin.array(); + const auto bboxDiag = bboxMax.range(); axom::Array> query_arr; for(const double fx : {0.11, 0.251, 0.52, 0.731, 0.91}) From 504ab57fb5e0d3aac2e66a45074217c7a5d5dab8 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Tue, 31 Mar 2026 10:24:20 -0700 Subject: [PATCH 036/986] Fix a silly typo --- src/axom/primal/operators/winding_number.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/axom/primal/operators/winding_number.hpp b/src/axom/primal/operators/winding_number.hpp index 388996b1ca..3d0d97b6a0 100644 --- a/src/axom/primal/operators/winding_number.hpp +++ b/src/axom/primal/operators/winding_number.hpp @@ -902,7 +902,7 @@ axom::Array winding_number(const axom::Array>& query_arr, { // Precompute the expansions and cast directions for each patch axom::Array> nurbs_cache_arr(0, surf_arr.size()); - for(autp& surf : surf_arr) + for(auto& surf : surf_arr) { nurbs_cache_arr.emplace_back(detail::NURBSPatchGWNCache(surf)); } From 6ff593d1d1e53c37ae42606c23f9c6295ac5e895 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Tue, 31 Mar 2026 15:44:20 -0700 Subject: [PATCH 037/986] Fix broken bbox call --- src/axom/quest/tests/quest_step_reader.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/axom/quest/tests/quest_step_reader.cpp b/src/axom/quest/tests/quest_step_reader.cpp index 58e2e3c954..46454d87f9 100644 --- a/src/axom/quest/tests/quest_step_reader.cpp +++ b/src/axom/quest/tests/quest_step_reader.cpp @@ -168,9 +168,7 @@ void runStepFileTest(const std::string& stepFile, double deflection = 0.1) } const auto shapeBbox = stepReader.getBRepBoundingBox().scale(1.1); - auto bboxMin = shapeBbox.getMin(); - auto bboxMax = shapeBbox.getMax(); - const auto bboxDiag = bboxMax.range(); + const auto bboxDiag = shapeBbox.range(); axom::Array> query_arr; for(const double fx : {0.11, 0.251, 0.52, 0.731, 0.91}) @@ -179,9 +177,9 @@ void runStepFileTest(const std::string& stepFile, double deflection = 0.1) { for(const double fz : {0.105, 0.25, 0.5, 0.751, 0.901}) { - query_arr.push_back(primal::Point({bboxMin[0] + fx * bboxDiag[0], - bboxMin[1] + fy * bboxDiag[1], - bboxMin[2] + fz * bboxDiag[2]})); + query_arr.push_back(primal::Point({shapeBbox.getMin()[0] + fx * bboxDiag[0], + shapeBbox.getMin()[1] + fy * bboxDiag[1], + shapeBbox.getMin()[2] + fz * bboxDiag[2]})); } } } From 39a5d3ebda8d37011dea1c1b066a3722872e4161 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Wed, 1 Apr 2026 15:10:29 -0700 Subject: [PATCH 038/986] Update data, unused params --- data | 2 +- src/axom/quest/GWNMethods.hpp | 4 ++-- src/axom/quest/tests/quest_step_reader.cpp | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/data b/data index 1619ab1f98..1a77bc5d81 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 1619ab1f98f3cc3b06f356c531c0b385ea42b0b7 +Subproject commit 1a77bc5d81ef1c3e007d7d634238b488cbdf60d4 diff --git a/src/axom/quest/GWNMethods.hpp b/src/axom/quest/GWNMethods.hpp index 18323b7621..0f6a7b052b 100644 --- a/src/axom/quest/GWNMethods.hpp +++ b/src/axom/quest/GWNMethods.hpp @@ -295,7 +295,7 @@ class PolylineGWN2D poly_mesh, AXOM_LAMBDA(axom::IndexType cellIdx, const axom::numerics::Matrix& coords, - const axom::IndexType* /*nodeIds*/) { + const axom::IndexType* AXOM_UNUSED_PARAM(nodeIds)) { segments_view[cellIdx] = SegmentType {Point2D {coords(0, 0), coords(1, 0)}, Point2D {coords(0, 1), coords(1, 1)}}; }); @@ -649,7 +649,7 @@ class TriangleGWN3D tri_mesh, AXOM_LAMBDA(axom::IndexType cellIdx, const axom::numerics::Matrix& coords, - const axom::IndexType* /*nodeIds*/) { + const axom::IndexType* AXOM_UNUSED_PARAM(nodeIds)) { triangles_view[cellIdx] = TriangleType {Point3D {(coords(0, 0) - shape_center[0]) / scale, (coords(1, 0) - shape_center[1]) / scale, diff --git a/src/axom/quest/tests/quest_step_reader.cpp b/src/axom/quest/tests/quest_step_reader.cpp index 46454d87f9..84cab98891 100644 --- a/src/axom/quest/tests/quest_step_reader.cpp +++ b/src/axom/quest/tests/quest_step_reader.cpp @@ -61,7 +61,7 @@ struct MiniTriangleGWN3D BBox3D* shapeBBoxPtr = &shapeBBox; mint::for_all_nodes( &mesh, - AXOM_LAMBDA(axom::IndexType /*nodeIdx*/, double x, double y, double z) { + AXOM_LAMBDA(axom::IndexType AXOM_UNUSED_PARAM(nodeIdx), double x, double y, double z) { shapeBBoxPtr->addPoint(Point3D {x, y, z}); }); @@ -76,7 +76,7 @@ struct MiniTriangleGWN3D &mesh, AXOM_LAMBDA(axom::IndexType cellIdx, const axom::numerics::Matrix& coords, - const axom::IndexType* /*nodeIds*/) { + const axom::IndexType* AXOM_UNUSED_PARAM(nodeIds)) { // clang-format off trisView[cellIdx] = Triangle3D {Point3D {(coords(0, 0) - ctr[0]) / scl, (coords(1, 0) - ctr[1]) / scl, (coords(2, 0) - ctr[2]) / scl}, Point3D {(coords(0, 1) - ctr[0]) / scl, (coords(1, 1) - ctr[1]) / scl, (coords(2, 1) - ctr[2]) / scl}, From 575d98be467623b11553e81bb308eb103f8e73ac Mon Sep 17 00:00:00 2001 From: Brian Han Date: Thu, 2 Apr 2026 15:05:50 -0700 Subject: [PATCH 039/986] fix note --- RELEASE-NOTES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 158675cb72..4b0daf21cc 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -38,6 +38,7 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ - Quest: Adds fast GWN methods for STL/Triangulated STEP input and linearized NURBS Curve input which leverage error-controlled approximation and a spatial index (BVH). - Slic: Adds new Slic macros that allow you to selectively print messages once per call-site. For example, + `SLIC_INFO_ONCE(msg)` and `SLIC_INFO_ROOT_IF_ONCE(EXP, msg)`. ### Changed - Primal: Axom's polygon clipping was modified to handle some corner cases. From 9b09ab9d4fc3267b6cce546e53445d51e7a19974 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Wed, 1 Apr 2026 15:10:05 -0700 Subject: [PATCH 040/986] Add back +python+mfem spec for intel compiler --- scripts/spack/specs.json | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/scripts/spack/specs.json b/scripts/spack/specs.json index 733749cc41..79ba88ff79 100644 --- a/scripts/spack/specs.json +++ b/scripts/spack/specs.json @@ -14,12 +14,11 @@ "__comment__":"# ", "__comment__":"##############################################################################", - "__comment__":"# mfem disabled for intel (icpx compiler error)", - "__comment__":"# python disabled for intel (mixed compiler conduit/caliper MPI spack compiler error)", + "__comment__":"# For intel compiler, mfem requires given compiler flag and ", + "__comment__":"# compiler preferences to prevent compiler mixing", "__comment__":"# Configs are for dane/rzwhippet", - "__comment__":"# Order matters - if ~python spec is built last this removes view of python", "toss_4_x86_64_ib": - [ "~python+devtools+hdf5~mfem+c2c+adiak+caliper %intel_25", + [ "+python+devtools+hdf5+mfem+c2c+adiak+caliper %%intel_25 ^mfem cxxflags=-fp-speculation=safe %intel_25", "+python+devtools+hdf5+mfem+c2c+scr+adiak+caliper %gcc_13", "+python+devtools+hdf5+mfem+c2c+scr+adiak+caliper+opencascade %clang_19"], From ecbf121e3cb3e114e0e1ed5ccd0d190d7a3416a4 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Wed, 1 Apr 2026 16:36:52 -0700 Subject: [PATCH 041/986] Relax tolerance for primal_integral unit test for intel-oneapi --- src/axom/primal/tests/primal_integral.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/axom/primal/tests/primal_integral.cpp b/src/axom/primal/tests/primal_integral.cpp index 1520d8dd44..8724c9c4a1 100644 --- a/src/axom/primal/tests/primal_integral.cpp +++ b/src/axom/primal/tests/primal_integral.cpp @@ -640,9 +640,11 @@ TEST(primal_integral, check_axom_mfem_quadrature_values) for(int j = 0; j < npts; ++j) { EXPECT_NEAR(axom_rule.node(j), mfem_rule.IntPoint(j).x, axom::numeric_limits::epsilon()); + + // Relax tolerance slightly for intel-oneapi EXPECT_NEAR(axom_rule.weight(j), mfem_rule.IntPoint(j).weight, - axom::numeric_limits::epsilon()); + 10 * axom::numeric_limits::epsilon()); } } } From 3c54d43914a7fd3ce76ec8e447928de471523910 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Wed, 1 Apr 2026 16:55:29 -0700 Subject: [PATCH 042/986] Add fp-model=precise flag to intel builds --- scripts/spack/packages/axom/package.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/scripts/spack/packages/axom/package.py b/scripts/spack/packages/axom/package.py index 13d65d0872..659590e30b 100644 --- a/scripts/spack/packages/axom/package.py +++ b/scripts/spack/packages/axom/package.py @@ -414,11 +414,14 @@ def initconfig_compiler_entries(self): if spec.satisfies("%cce"): entries.append(cmake_cache_string("CMAKE_CXX_FLAGS_DEBUG", "-O1 -g")) - # Disable intrusive warning: - # icpx: remark: note that use of '-g' without any optimization-level - # option will turn off most compiler optimizations similar to use of - # '-O0'; use '-Rno-debug-disables-optimization' to disable this remark if spec.satisfies("%oneapi"): + # Addresses floating point issues (default is fast) + entries.append(cmake_cache_string("CMAKE_CXX_FLAGS", "-fp-model=precise")) + + # Disable intrusive warning: + # icpx: remark: note that use of '-g' without any optimization-level + # option will turn off most compiler optimizations similar to use of + # '-O0'; use '-Rno-debug-disables-optimization' to disable this remark entries.append(cmake_cache_string("CMAKE_CXX_FLAGS_DEBUG", "-g -Rno-debug-disables-optimization")) return entries From 5ed1b568f0c0d620d434a47a64de1d41f62370ff Mon Sep 17 00:00:00 2001 From: Brian Han Date: Fri, 3 Apr 2026 09:11:36 -0700 Subject: [PATCH 043/986] Update RZ intel host-config --- ...4_ib-intel-oneapi-compilers@2025.2.0.cmake | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/host-configs/rzwhippet-toss_4_x86_64_ib-intel-oneapi-compilers@2025.2.0.cmake b/host-configs/rzwhippet-toss_4_x86_64_ib-intel-oneapi-compilers@2025.2.0.cmake index d0460445ec..532384bd0a 100644 --- a/host-configs/rzwhippet-toss_4_x86_64_ib-intel-oneapi-compilers@2025.2.0.cmake +++ b/host-configs/rzwhippet-toss_4_x86_64_ib-intel-oneapi-compilers@2025.2.0.cmake @@ -4,13 +4,13 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.26.3/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/intel-oneapi-compilers-2025.2.0/blt-0.7.1-2n76efajatydczwltuk6plxpgoyvu6jj;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/intel-oneapi-compilers-2025.2.0/c2c-1.8.0-rl42nhoa33aux3tluna3bftevqokiiqc;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/intel-oneapi-compilers-2025.2.0/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-ti2c3tm4pc6rma2cedok5hy5vnpxmsj7;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/intel-oneapi-compilers-2025.2.0/conduit-0.9.5-z4aapemtohmue4rikzbgjhmfyexwuym4;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/intel-oneapi-compilers-2025.2.0/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-jz235wsh3nhxjuzqxse57hcojia7x4mj;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/intel-oneapi-compilers-2025.2.0/umpire-2025.12.0-2eti2r3wjt4qs772djlcgf6axs44qmko;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/intel-oneapi-compilers-2025.2.0/adiak-0.4.0-rgjvxwzjod55ialozetvkjyi3hjczviy;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/intel-oneapi-compilers-2025.2.0/libunwind-1.8.3-h42c2h7kzkiaeq63dzvt32wlhcqbcrra;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/intel-oneapi-compilers-2025.2.0/hdf5-1.8.23-74fr7atqhw4jlzxt22xi5szeqpoxcwkh;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/intel-oneapi-compilers-2025.2.0/parmetis-4.0.3-g7bp5ds3u3laspneww3scyujjwebgdzh;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/intel-oneapi-compilers-2025.2.0/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-2gq3ddrhfxqlgzr6zxbvmwpzvtqxqj25;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/intel-oneapi-compilers-2025.2.0/fmt-11.0.2-orofhwltz3p5ckiw2t7gzhkgiifetwjt;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/intel-oneapi-compilers-2025.2.0/metis-5.1.0-zschch2gorb5k7t4rmrtc3aubm663i6u;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/none-none/intel-oneapi-runtime-2025.2.0-mfk4ezunnyprzpfps5pncx2nhttsa4z3;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/none-none/gcc-runtime-13.3.1-zsobnkwck2glzcbukupgvgbiwwhdonys;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.26.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66xzj4wm5kfqxyc4vgox7gb3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/doxygen-1.15.0;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/intel-oneapi-compilers-2025.2.0-ngszoacyp2i5d5nawitlyuh37jswjtie/compiler/2025.2;/usr/tce/packages/intel/intel-2025.2.0;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-intel-2025.2.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/intel-oneapi-compilers-2025.2.0/blt-0.7.1-2n76efajatydczwltuk6plxpgoyvu6jj;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/intel-oneapi-compilers-2025.2.0/c2c-1.8.0-rl42nhoa33aux3tluna3bftevqokiiqc;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/intel-oneapi-compilers-2025.2.0/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-ti2c3tm4pc6rma2cedok5hy5vnpxmsj7;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/intel-oneapi-compilers-2025.2.0/conduit-0.9.5-x6snw6rzqee7bhmsc6rezi3oobsb6tba;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/intel-oneapi-compilers-2025.2.0/mfem-4.9.0-exbr44mwmmbii764bvxrgct2ivffacjh;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/gcc-13.3.1/py-nanobind-2.7.0-zg2fpeevizaaykxxnp4butsrujlb54px;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/intel-oneapi-compilers-2025.2.0/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-jz235wsh3nhxjuzqxse57hcojia7x4mj;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/intel-oneapi-compilers-2025.2.0/umpire-2025.12.0-2eti2r3wjt4qs772djlcgf6axs44qmko;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/intel-oneapi-compilers-2025.2.0/adiak-0.4.0-rgjvxwzjod55ialozetvkjyi3hjczviy;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/intel-oneapi-compilers-2025.2.0/libunwind-1.8.3-h42c2h7kzkiaeq63dzvt32wlhcqbcrra;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/intel-oneapi-compilers-2025.2.0/hdf5-1.8.23-74fr7atqhw4jlzxt22xi5szeqpoxcwkh;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/intel-oneapi-compilers-2025.2.0/parmetis-4.0.3-g7bp5ds3u3laspneww3scyujjwebgdzh;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/gcc-13.3.1/py-numpy-2.4.2-z4yapdkn22w6adfrl7ibucqiokgrwb7f;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/intel-oneapi-compilers-2025.2.0/hypre-2.27.0-hgyyzv6j2qosdblkfnupzwgwmujcrtse;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/intel-oneapi-compilers-2025.2.0/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-2gq3ddrhfxqlgzr6zxbvmwpzvtqxqj25;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/intel-oneapi-compilers-2025.2.0/fmt-11.0.2-orofhwltz3p5ckiw2t7gzhkgiifetwjt;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/intel-oneapi-compilers-2025.2.0/metis-5.1.0-zschch2gorb5k7t4rmrtc3aubm663i6u;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/none-none/intel-oneapi-runtime-2025.2.0-mfk4ezunnyprzpfps5pncx2nhttsa4z3;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/none-none/gcc-runtime-13.3.1-zsobnkwck2glzcbukupgvgbiwwhdonys;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.26.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66xzj4wm5kfqxyc4vgox7gb3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/doxygen-1.15.0;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/intel-oneapi-compilers-2025.2.0-ngszoacyp2i5d5nawitlyuh37jswjtie/compiler/2025.2;/usr/tce/packages/intel/intel-2025.2.0;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-intel-2025.2.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/intel-oneapi-compilers-2025.2.0/axom-develop-bj7hmyynlaetqadetbi7owutac5umw3p/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/intel-oneapi-compilers-2025.2.0/axom-develop-bj7hmyynlaetqadetbi7owutac5umw3p/lib64;;/usr/tce/packages/intel/intel-2025.2.0/compiler/2025.2/lib;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/intel-oneapi-compilers-2025.2.0-ngszoacyp2i5d5nawitlyuh37jswjtie/compiler/2025.2/lib;/usr/tce/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/intel-oneapi-compilers-2025.2.0/axom-develop-3l4k45i3jli4otfuyze65smrd7vj43iz/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/intel-oneapi-compilers-2025.2.0/axom-develop-3l4k45i3jli4otfuyze65smrd7vj43iz/lib64;;/usr/tce/packages/intel/intel-2025.2.0/compiler/2025.2/lib;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/intel-oneapi-compilers-2025.2.0-ngszoacyp2i5d5nawitlyuh37jswjtie/compiler/2025.2/lib;/usr/tce/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/intel-oneapi-compilers-2025.2.0/axom-develop-bj7hmyynlaetqadetbi7owutac5umw3p/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/intel-oneapi-compilers-2025.2.0/axom-develop-bj7hmyynlaetqadetbi7owutac5umw3p/lib64;;/usr/tce/packages/intel/intel-2025.2.0/compiler/2025.2/lib;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/intel-oneapi-compilers-2025.2.0-ngszoacyp2i5d5nawitlyuh37jswjtie/compiler/2025.2/lib;/usr/tce/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/intel-oneapi-compilers-2025.2.0/axom-develop-3l4k45i3jli4otfuyze65smrd7vj43iz/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/intel-oneapi-compilers-2025.2.0/axom-develop-3l4k45i3jli4otfuyze65smrd7vj43iz/lib64;;/usr/tce/packages/intel/intel-2025.2.0/compiler/2025.2/lib;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/intel-oneapi-compilers-2025.2.0-ngszoacyp2i5d5nawitlyuh37jswjtie/compiler/2025.2/lib;/usr/tce/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") @@ -21,11 +21,11 @@ set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/oneapi/icx" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/oneapi/icx" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/oneapi/icpx" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/oneapi/icpx" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/oneapi/ifx" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/oneapi/ifx" CACHE PATH "") else() @@ -41,6 +41,8 @@ set(CMAKE_Fortran_FLAGS "-fPIC" CACHE STRING "") set(ENABLE_FORTRAN ON CACHE BOOL "") +set(CMAKE_CXX_FLAGS "-fp-model=precise" CACHE STRING "") + set(CMAKE_CXX_FLAGS_DEBUG "-g -Rno-debug-disables-optimization" CACHE STRING "") #------------------------------------------------------------------------------ @@ -75,13 +77,13 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/intel-oneapi-compilers-2025.2.0" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/intel-oneapi-compilers-2025.2.0" CACHE PATH "") -set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-z4aapemtohmue4rikzbgjhmfyexwuym4" CACHE PATH "") +set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-x6snw6rzqee7bhmsc6rezi3oobsb6tba" CACHE PATH "") set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-rl42nhoa33aux3tluna3bftevqokiiqc" CACHE PATH "") -# MFEM not built +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-exbr44mwmmbii764bvxrgct2ivffacjh" CACHE PATH "") set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-74fr7atqhw4jlzxt22xi5szeqpoxcwkh" CACHE PATH "") @@ -125,4 +127,14 @@ set(CPPCHECK_EXECUTABLE "${DEVTOOLS_ROOT}/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66x set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/kky5oskfhacihjb5gpy7qkeh6edlrtcb/doxygen-1.15.0/bin/doxygen" CACHE PATH "") +set(PY_NANOBIND_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/gcc-13.3.1/py-nanobind-2.7.0-zg2fpeevizaaykxxnp4butsrujlb54px/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_NUMPY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/gcc-13.3.1/py-numpy-2.4.2-z4yapdkn22w6adfrl7ibucqiokgrwb7f/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/none-none/py-pluggy-1.6.0-6zab4pck6v7johubbhalr2waowbwpjei/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/none-none/py-iniconfig-2.1.0-xwmbxuhk2oo3dcuizdp3z4hqbpqykkom/lib/python3.13/site-packages" CACHE PATH "") + From db394bcd7b6b837840f05f67f8cbf7ab43bf88fa Mon Sep 17 00:00:00 2001 From: Brian Han Date: Fri, 3 Apr 2026 10:51:50 -0700 Subject: [PATCH 044/986] update CZ host-config --- ...4_ib-intel-oneapi-compilers@2025.2.0.cmake | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/host-configs/dane-toss_4_x86_64_ib-intel-oneapi-compilers@2025.2.0.cmake b/host-configs/dane-toss_4_x86_64_ib-intel-oneapi-compilers@2025.2.0.cmake index 2c46309ced..941245ea7b 100644 --- a/host-configs/dane-toss_4_x86_64_ib-intel-oneapi-compilers@2025.2.0.cmake +++ b/host-configs/dane-toss_4_x86_64_ib-intel-oneapi-compilers@2025.2.0.cmake @@ -4,13 +4,13 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.26.3/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/intel-oneapi-compilers-2025.2.0/blt-0.7.1-2n76efajatydczwltuk6plxpgoyvu6jj;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/intel-oneapi-compilers-2025.2.0/c2c-1.8.0-rl42nhoa33aux3tluna3bftevqokiiqc;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/intel-oneapi-compilers-2025.2.0/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-ti2c3tm4pc6rma2cedok5hy5vnpxmsj7;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/intel-oneapi-compilers-2025.2.0/conduit-0.9.5-z4aapemtohmue4rikzbgjhmfyexwuym4;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/intel-oneapi-compilers-2025.2.0/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-jz235wsh3nhxjuzqxse57hcojia7x4mj;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/intel-oneapi-compilers-2025.2.0/umpire-2025.12.0-2eti2r3wjt4qs772djlcgf6axs44qmko;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/intel-oneapi-compilers-2025.2.0/adiak-0.4.0-rgjvxwzjod55ialozetvkjyi3hjczviy;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/intel-oneapi-compilers-2025.2.0/libunwind-1.8.3-h42c2h7kzkiaeq63dzvt32wlhcqbcrra;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/intel-oneapi-compilers-2025.2.0/hdf5-1.8.23-74fr7atqhw4jlzxt22xi5szeqpoxcwkh;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/intel-oneapi-compilers-2025.2.0/parmetis-4.0.3-g7bp5ds3u3laspneww3scyujjwebgdzh;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/intel-oneapi-compilers-2025.2.0/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-2gq3ddrhfxqlgzr6zxbvmwpzvtqxqj25;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/intel-oneapi-compilers-2025.2.0/fmt-11.0.2-orofhwltz3p5ckiw2t7gzhkgiifetwjt;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/intel-oneapi-compilers-2025.2.0/metis-5.1.0-zschch2gorb5k7t4rmrtc3aubm663i6u;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/none-none/intel-oneapi-runtime-2025.2.0-mfk4ezunnyprzpfps5pncx2nhttsa4z3;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/none-none/gcc-runtime-13.3.1-zsobnkwck2glzcbukupgvgbiwwhdonys;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.26.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66xzj4wm5kfqxyc4vgox7gb3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/doxygen-1.15.0;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/intel-oneapi-compilers-2025.2.0-ngszoacyp2i5d5nawitlyuh37jswjtie/compiler/2025.2;/usr/tce/packages/intel/intel-2025.2.0;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-intel-2025.2.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/intel-oneapi-compilers-2025.2.0/blt-0.7.1-2n76efajatydczwltuk6plxpgoyvu6jj;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/intel-oneapi-compilers-2025.2.0/c2c-1.8.0-rl42nhoa33aux3tluna3bftevqokiiqc;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/intel-oneapi-compilers-2025.2.0/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-ti2c3tm4pc6rma2cedok5hy5vnpxmsj7;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/intel-oneapi-compilers-2025.2.0/conduit-0.9.5-x6snw6rzqee7bhmsc6rezi3oobsb6tba;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/intel-oneapi-compilers-2025.2.0/mfem-4.9.0-exbr44mwmmbii764bvxrgct2ivffacjh;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/gcc-13.3.1/py-nanobind-2.7.0-zg2fpeevizaaykxxnp4butsrujlb54px;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/intel-oneapi-compilers-2025.2.0/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-jz235wsh3nhxjuzqxse57hcojia7x4mj;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/intel-oneapi-compilers-2025.2.0/umpire-2025.12.0-2eti2r3wjt4qs772djlcgf6axs44qmko;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/intel-oneapi-compilers-2025.2.0/adiak-0.4.0-rgjvxwzjod55ialozetvkjyi3hjczviy;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/intel-oneapi-compilers-2025.2.0/libunwind-1.8.3-h42c2h7kzkiaeq63dzvt32wlhcqbcrra;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/intel-oneapi-compilers-2025.2.0/hdf5-1.8.23-74fr7atqhw4jlzxt22xi5szeqpoxcwkh;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/intel-oneapi-compilers-2025.2.0/parmetis-4.0.3-g7bp5ds3u3laspneww3scyujjwebgdzh;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/gcc-13.3.1/py-numpy-2.4.2-z4yapdkn22w6adfrl7ibucqiokgrwb7f;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/intel-oneapi-compilers-2025.2.0/hypre-2.27.0-hgyyzv6j2qosdblkfnupzwgwmujcrtse;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/intel-oneapi-compilers-2025.2.0/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-2gq3ddrhfxqlgzr6zxbvmwpzvtqxqj25;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/intel-oneapi-compilers-2025.2.0/fmt-11.0.2-orofhwltz3p5ckiw2t7gzhkgiifetwjt;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/intel-oneapi-compilers-2025.2.0/metis-5.1.0-zschch2gorb5k7t4rmrtc3aubm663i6u;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/none-none/intel-oneapi-runtime-2025.2.0-mfk4ezunnyprzpfps5pncx2nhttsa4z3;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/none-none/gcc-runtime-13.3.1-zsobnkwck2glzcbukupgvgbiwwhdonys;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.26.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66xzj4wm5kfqxyc4vgox7gb3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/doxygen-1.15.0;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/intel-oneapi-compilers-2025.2.0-ngszoacyp2i5d5nawitlyuh37jswjtie/compiler/2025.2;/usr/tce/packages/intel/intel-2025.2.0;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-intel-2025.2.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/intel-oneapi-compilers-2025.2.0/axom-develop-5onyrt7zct4lirem7z34t2uqdtngl45l/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/intel-oneapi-compilers-2025.2.0/axom-develop-5onyrt7zct4lirem7z34t2uqdtngl45l/lib64;;/usr/tce/packages/intel/intel-2025.2.0/compiler/2025.2/lib;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/intel-oneapi-compilers-2025.2.0-ngszoacyp2i5d5nawitlyuh37jswjtie/compiler/2025.2/lib;/usr/tce/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/intel-oneapi-compilers-2025.2.0/axom-develop-3l4k45i3jli4otfuyze65smrd7vj43iz/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/intel-oneapi-compilers-2025.2.0/axom-develop-3l4k45i3jli4otfuyze65smrd7vj43iz/lib64;;/usr/tce/packages/intel/intel-2025.2.0/compiler/2025.2/lib;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/intel-oneapi-compilers-2025.2.0-ngszoacyp2i5d5nawitlyuh37jswjtie/compiler/2025.2/lib;/usr/tce/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/intel-oneapi-compilers-2025.2.0/axom-develop-5onyrt7zct4lirem7z34t2uqdtngl45l/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/intel-oneapi-compilers-2025.2.0/axom-develop-5onyrt7zct4lirem7z34t2uqdtngl45l/lib64;;/usr/tce/packages/intel/intel-2025.2.0/compiler/2025.2/lib;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/intel-oneapi-compilers-2025.2.0-ngszoacyp2i5d5nawitlyuh37jswjtie/compiler/2025.2/lib;/usr/tce/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/intel-oneapi-compilers-2025.2.0/axom-develop-3l4k45i3jli4otfuyze65smrd7vj43iz/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/intel-oneapi-compilers-2025.2.0/axom-develop-3l4k45i3jli4otfuyze65smrd7vj43iz/lib64;;/usr/tce/packages/intel/intel-2025.2.0/compiler/2025.2/lib;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/intel-oneapi-compilers-2025.2.0-ngszoacyp2i5d5nawitlyuh37jswjtie/compiler/2025.2/lib;/usr/tce/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") @@ -21,11 +21,11 @@ set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/oneapi/icx" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/oneapi/icx" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/oneapi/icpx" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/oneapi/icpx" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/oneapi/ifx" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/oneapi/ifx" CACHE PATH "") else() @@ -41,6 +41,8 @@ set(CMAKE_Fortran_FLAGS "-fPIC" CACHE STRING "") set(ENABLE_FORTRAN ON CACHE BOOL "") +set(CMAKE_CXX_FLAGS "-fp-model=precise" CACHE STRING "") + set(CMAKE_CXX_FLAGS_DEBUG "-g -Rno-debug-disables-optimization" CACHE STRING "") #------------------------------------------------------------------------------ @@ -75,13 +77,13 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/intel-oneapi-compilers-2025.2.0" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/intel-oneapi-compilers-2025.2.0" CACHE PATH "") -set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-z4aapemtohmue4rikzbgjhmfyexwuym4" CACHE PATH "") +set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-x6snw6rzqee7bhmsc6rezi3oobsb6tba" CACHE PATH "") set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-rl42nhoa33aux3tluna3bftevqokiiqc" CACHE PATH "") -# MFEM not built +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-exbr44mwmmbii764bvxrgct2ivffacjh" CACHE PATH "") set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-74fr7atqhw4jlzxt22xi5szeqpoxcwkh" CACHE PATH "") @@ -125,4 +127,14 @@ set(CPPCHECK_EXECUTABLE "${DEVTOOLS_ROOT}/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66x set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/kky5oskfhacihjb5gpy7qkeh6edlrtcb/doxygen-1.15.0/bin/doxygen" CACHE PATH "") +set(PY_NANOBIND_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/gcc-13.3.1/py-nanobind-2.7.0-zg2fpeevizaaykxxnp4butsrujlb54px/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_NUMPY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/gcc-13.3.1/py-numpy-2.4.2-z4yapdkn22w6adfrl7ibucqiokgrwb7f/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/none-none/py-pluggy-1.6.0-6zab4pck6v7johubbhalr2waowbwpjei/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/none-none/py-iniconfig-2.1.0-xwmbxuhk2oo3dcuizdp3z4hqbpqykkom/lib/python3.13/site-packages" CACHE PATH "") + From e8788359a8fa0bbfe59a8534b2d8aefd8f62b342 Mon Sep 17 00:00:00 2001 From: Chris White Date: Wed, 8 Apr 2026 10:57:02 -0700 Subject: [PATCH 045/986] allow sol to compile with C++20 --- src/thirdparty/axom/sol.hpp | 10 ++--- src/thirdparty/axom/sol_invoke_result.patch | 49 +++++++++++++++++++++ 2 files changed, 54 insertions(+), 5 deletions(-) create mode 100644 src/thirdparty/axom/sol_invoke_result.patch diff --git a/src/thirdparty/axom/sol.hpp b/src/thirdparty/axom/sol.hpp index 243ed4cf74..df11c0b92d 100644 --- a/src/thirdparty/axom/sol.hpp +++ b/src/thirdparty/axom/sol.hpp @@ -14425,7 +14425,7 @@ namespace sol { // constexpr is fine for not-clang namespace detail { - template (Args...)>> + template , Args...>> inline constexpr auto resolve_i(types, F &&) -> R (meta::unqualified_t::*)(Args...) { using Sig = R(Args...); typedef meta::unqualified_t Fu; @@ -14449,7 +14449,7 @@ namespace sol { return resolve_f(meta::has_deducible_signature{}, std::forward(f)); } - template > + template > inline constexpr auto resolve_i(types, F&& f) -> decltype(resolve_i(types(), std::forward(f))) { return resolve_i(types(), std::forward(f)); } @@ -14495,7 +14495,7 @@ namespace sol { // so don't use the constexpr versions inside of clang. namespace detail { - template (Args...)>> + template , Args...>> inline auto resolve_i(types, F &&) -> R (meta::unqualified_t::*)(Args...) { using Sig = R(Args...); typedef meta::unqualified_t Fu; @@ -14519,7 +14519,7 @@ namespace sol { return resolve_f(meta::has_deducible_signature{}, std::forward(f)); } - template > + template > inline auto resolve_i(types, F&& f) -> decltype(resolve_i(types(), std::forward(f))) { return resolve_i(types(), std::forward(f)); } @@ -20652,7 +20652,7 @@ namespace sol { } private: - template > + template > void set_fx(types, Key&& key, Fx&& fx) { set_resolved_function(std::forward(key), std::forward(fx)); } diff --git a/src/thirdparty/axom/sol_invoke_result.patch b/src/thirdparty/axom/sol_invoke_result.patch new file mode 100644 index 0000000000..bc296bfaaa --- /dev/null +++ b/src/thirdparty/axom/sol_invoke_result.patch @@ -0,0 +1,49 @@ +diff --git a/src/thirdparty/axom/sol.hpp b/src/thirdparty/axom/sol.hpp +index 243ed4cf7..df11c0b92 100644 +--- a/src/thirdparty/axom/sol.hpp ++++ b/src/thirdparty/axom/sol.hpp +@@ -14425,7 +14425,7 @@ namespace sol { + // constexpr is fine for not-clang + + namespace detail { +- template (Args...)>> ++ template , Args...>> + inline constexpr auto resolve_i(types, F &&) -> R (meta::unqualified_t::*)(Args...) { + using Sig = R(Args...); + typedef meta::unqualified_t Fu; +@@ -14449,7 +14449,7 @@ namespace sol { + return resolve_f(meta::has_deducible_signature{}, std::forward(f)); + } + +- template > ++ template > + inline constexpr auto resolve_i(types, F&& f) -> decltype(resolve_i(types(), std::forward(f))) { + return resolve_i(types(), std::forward(f)); + } +@@ -14495,7 +14495,7 @@ namespace sol { + // so don't use the constexpr versions inside of clang. + + namespace detail { +- template (Args...)>> ++ template , Args...>> + inline auto resolve_i(types, F &&) -> R (meta::unqualified_t::*)(Args...) { + using Sig = R(Args...); + typedef meta::unqualified_t Fu; +@@ -14519,7 +14519,7 @@ namespace sol { + return resolve_f(meta::has_deducible_signature{}, std::forward(f)); + } + +- template > ++ template > + inline auto resolve_i(types, F&& f) -> decltype(resolve_i(types(), std::forward(f))) { + return resolve_i(types(), std::forward(f)); + } +@@ -20652,7 +20652,7 @@ namespace sol { + } + + private: +- template > ++ template > + void set_fx(types, Key&& key, Fx&& fx) { + set_resolved_function(std::forward(key), std::forward(fx)); + } From d4df9740e4e935ae63c732dee1660103681c7115 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Mon, 16 Mar 2026 00:06:19 -0700 Subject: [PATCH 046/986] Adds quadrature functions to evaluate integrals over collections of BezierPatches and NURBSPatches --- .../detail/evaluate_integral_impl.hpp | 194 +++++++++++++++ .../primal/operators/evaluate_integral.hpp | 231 ++++++++++++++++++ src/axom/primal/tests/primal_integral.cpp | 62 +++++ src/axom/quest/tests/CMakeLists.txt | 1 + .../quest/tests/quest_step_quadrature.cpp | 40 +++ 5 files changed, 528 insertions(+) create mode 100644 src/axom/quest/tests/quest_step_quadrature.cpp diff --git a/src/axom/primal/operators/detail/evaluate_integral_impl.hpp b/src/axom/primal/operators/detail/evaluate_integral_impl.hpp index 253af353ba..debd511c6e 100644 --- a/src/axom/primal/operators/detail/evaluate_integral_impl.hpp +++ b/src/axom/primal/operators/detail/evaluate_integral_impl.hpp @@ -30,12 +30,15 @@ #include "axom/primal/geometry/Point.hpp" #include "axom/primal/geometry/Vector.hpp" #include "axom/primal/geometry/BezierCurve.hpp" +#include "axom/primal/geometry/BezierPatch.hpp" #include "axom/primal/geometry/NURBSCurve.hpp" +#include "axom/primal/geometry/NURBSPatch.hpp" #include "axom/primal/operators/detail/winding_number_2d_memoization.hpp" #include "axom/core/numerics/quadrature.hpp" // C++ includes +#include #include #include #include @@ -275,6 +278,8 @@ inline T evaluate_vector_line_integral_component(const NURBSCurveGWNCache& nc } //@} +//@} + ///@{ /// \name Evaluates scalar-field 2D area integrals for functions f : R^2 -> R^m @@ -403,6 +408,195 @@ inline RetType evaluate_area_integral_component(const NURBSCurveGWNCache& nc, return total_integral; } +///@{ +/// \name Helper routines for patch-based integrals in 3D + +template +inline typename CurveType::NumericType curve_array_lower_bound_y(const axom::Array& carray) +{ + using T = typename CurveType::NumericType; + + SLIC_ASSERT(!carray.empty()); + + T lower_bound_y = carray[0][0][1]; + for(int i = 0; i < carray.size(); ++i) + { + for(int j = 1; j < carray[i].getNumControlPoints(); ++j) + { + lower_bound_y = std::min(lower_bound_y, carray[i][j][1]); + } + } + + return lower_bound_y; +} + +template ::PointType>> +inline LambdaRetType evaluate_surface_integral_component(const primal::BezierPatch& b, + Lambda&& integrand, + const int npts) +{ + const axom::numerics::QuadratureRule& quad = axom::numerics::get_gauss_legendre(npts); + + LambdaRetType full_quadrature = LambdaRetType {}; + for(int qu = 0; qu < npts; ++qu) + { + for(int qv = 0; qv < npts; ++qv) + { + const auto x_q = b.evaluate(quad.node(qu), quad.node(qv)); + const auto n_q = b.normal(quad.node(qu), quad.node(qv)); + + full_quadrature += quad.weight(qu) * quad.weight(qv) * integrand(x_q) * n_q.norm(); + } + } + + return full_quadrature; +} + +template ::PointType>> +inline LambdaRetType evaluate_surface_integral_component(const primal::NURBSPatch& n, + Lambda&& integrand, + const int npts_Q, + const int npts_P) +{ + if(!n.isTrimmed()) + { + LambdaRetType total_integral = LambdaRetType {}; + for(const auto& bez : n.extractBezier()) + { + total_integral += detail::evaluate_surface_integral_component(bez, integrand, npts_Q); + } + + return total_integral; + } + + LambdaRetType total_integral = LambdaRetType {}; + for(const auto& split_patch : n.extractTrimmedBezier()) + { + const auto& curves = split_patch.getTrimmingCurves(); + if(curves.empty()) + { + continue; + } + + const auto lower_bound_y = detail::curve_array_lower_bound_y(curves); + for(int i = 0; i < curves.size(); ++i) + { + total_integral += detail::evaluate_area_integral_component( + curves[i], + [&split_patch, &integrand](Point uv) -> LambdaRetType { + const auto x_q = split_patch.evaluate(uv[0], uv[1]); + const auto n_q = split_patch.normal(uv[0], uv[1]); + return integrand(x_q) * n_q.norm(); + }, + lower_bound_y, + npts_Q, + npts_P); + } + } + + return total_integral; +} + +template ::PointType>> +inline LambdaRetType evaluate_volume_integral_component(const primal::BezierPatch& b, + Lambda&& integrand, + double int_lb, + const int npts_uv, + const int npts_z) +{ + const axom::numerics::QuadratureRule& quad_uv = axom::numerics::get_gauss_legendre(npts_uv); + const axom::numerics::QuadratureRule& quad_z = axom::numerics::get_gauss_legendre(npts_z); + + LambdaRetType full_quadrature = LambdaRetType {}; + for(int qu = 0; qu < npts_uv; ++qu) + { + for(int qv = 0; qv < npts_uv; ++qv) + { + const auto x_q = b.evaluate(quad_uv.node(qu), quad_uv.node(qv)); + const auto n_q = b.normal(quad_uv.node(qu), quad_uv.node(qv)); + + LambdaRetType antiderivative = LambdaRetType {}; + const T z_scale = x_q[2] - int_lb; + for(int qz = 0; qz < npts_z; ++qz) + { + const auto x_qz = Point({x_q[0], x_q[1], z_scale * quad_z.node(qz) + int_lb}); + antiderivative += quad_z.weight(qz) * z_scale * integrand(x_qz); + } + + full_quadrature += quad_uv.weight(qu) * quad_uv.weight(qv) * antiderivative * n_q[2]; + } + } + + return full_quadrature; +} + +template ::PointType>> +inline LambdaRetType evaluate_volume_integral_component(const primal::NURBSPatch& n, + Lambda&& integrand, + double int_lb, + const int npts_Q, + const int npts_P, + const int npts_Z) +{ + if(!n.isTrimmed()) + { + LambdaRetType total_integral = LambdaRetType {}; + for(const auto& bez : n.extractBezier()) + { + total_integral += + detail::evaluate_volume_integral_component(bez, integrand, int_lb, npts_Q, npts_Z); + } + + return total_integral; + } + + const axom::numerics::QuadratureRule& quad_z = axom::numerics::get_gauss_legendre(npts_Z); + + LambdaRetType total_integral = LambdaRetType {}; + for(const auto& split_patch : n.extractTrimmedBezier()) + { + const auto& curves = split_patch.getTrimmingCurves(); + if(curves.empty()) + { + continue; + } + + const auto lower_bound_y = detail::curve_array_lower_bound_y(curves); + for(int i = 0; i < curves.size(); ++i) + { + total_integral += detail::evaluate_area_integral_component( + curves[i], + [&split_patch, &integrand, &int_lb, &quad_z, &npts_Z](Point uv) -> LambdaRetType { + const auto x_q = split_patch.evaluate(uv[0], uv[1]); + const auto n_q = split_patch.normal(uv[0], uv[1]); + + LambdaRetType antiderivative = LambdaRetType {}; + const T z_scale = x_q[2] - int_lb; + for(int qz = 0; qz < npts_Z; ++qz) + { + const auto x_qz = Point({x_q[0], x_q[1], z_scale * quad_z.node(qz) + int_lb}); + antiderivative += quad_z.weight(qz) * z_scale * integrand(x_qz); + } + + return antiderivative * n_q[2]; + }, + lower_bound_y, + npts_Q, + npts_P); + } + } + + return total_integral; +} + //@} } // end namespace detail diff --git a/src/axom/primal/operators/evaluate_integral.hpp b/src/axom/primal/operators/evaluate_integral.hpp index e059d17fe8..ff03bf5a37 100644 --- a/src/axom/primal/operators/evaluate_integral.hpp +++ b/src/axom/primal/operators/evaluate_integral.hpp @@ -374,6 +374,237 @@ LambdaRetType evaluate_area_integral(const axom::Array& carray, } //@} +///@{ +/// \name Evaluates scalar-field surface integrals for functions f : R^3 -> R^m + +template ::PointType>> +LambdaRetType evaluate_surface_integral(const primal::BezierPatch& patch, + Lambda&& integrand, + int npts) +{ + static_assert(detail::internal::is_integrable_v, + "evaluate_integral methods require addition and scalar multiplication for lambda " + "function return type"); + + return detail::evaluate_surface_integral_component(patch, std::forward(integrand), npts); +} + +template ::PointType>> +LambdaRetType evaluate_surface_integral(const primal::NURBSPatch& patch, + Lambda&& integrand, + int npts_Q, + int npts_P = 0) +{ + static_assert(detail::internal::is_integrable_v, + "evaluate_integral methods require addition and scalar multiplication for lambda " + "function return type"); + + if(npts_P <= 0) + { + npts_P = npts_Q; + } + + return detail::evaluate_surface_integral_component(patch, + std::forward(integrand), + npts_Q, + npts_P); +} + +template ::PointType>> +LambdaRetType evaluate_surface_integral(const axom::Array>& patches, + Lambda&& integrand, + int npts) +{ + static_assert(detail::internal::is_integrable_v, + "evaluate_integral methods require addition and scalar multiplication for lambda " + "function return type"); + + LambdaRetType total_integral = LambdaRetType {}; + for(int i = 0; i < patches.size(); ++i) + { + total_integral += detail::evaluate_surface_integral_component(patches[i], integrand, npts); + } + + return total_integral; +} + +template ::PointType>> +LambdaRetType evaluate_surface_integral(const axom::Array>& patches, + Lambda&& integrand, + int npts_Q, + int npts_P = 0) +{ + static_assert(detail::internal::is_integrable_v, + "evaluate_integral methods require addition and scalar multiplication for lambda " + "function return type"); + + if(npts_P <= 0) + { + npts_P = npts_Q; + } + + LambdaRetType total_integral = LambdaRetType {}; + for(int i = 0; i < patches.size(); ++i) + { + total_integral += + detail::evaluate_surface_integral_component(patches[i], integrand, npts_Q, npts_P); + } + + return total_integral; +} +//@} + +///@{ +/// \name Evaluates scalar-field volume integrals for functions f : R^3 -> R^m + +template ::PointType>> +LambdaRetType evaluate_volume_integral(const primal::BezierPatch& patch, + Lambda&& integrand, + int npts_uv, + int npts_z = 0) +{ + static_assert(detail::internal::is_integrable_v, + "evaluate_integral methods require addition and scalar multiplication for lambda " + "function return type"); + + if(npts_z <= 0) + { + npts_z = npts_uv; + } + + return detail::evaluate_volume_integral_component(patch, + std::forward(integrand), + patch.boundingBox().getMin()[2], + npts_uv, + npts_z); +} + +template ::PointType>> +LambdaRetType evaluate_volume_integral(const primal::NURBSPatch& patch, + Lambda&& integrand, + int npts_Q, + int npts_P = 0, + int npts_Z = 0) +{ + static_assert(detail::internal::is_integrable_v, + "evaluate_integral methods require addition and scalar multiplication for lambda " + "function return type"); + + if(npts_P <= 0) + { + npts_P = npts_Q; + } + if(npts_Z <= 0) + { + npts_Z = npts_Q; + } + + return detail::evaluate_volume_integral_component(patch, + std::forward(integrand), + patch.boundingBox().getMin()[2], + npts_Q, + npts_P, + npts_Z); +} + +template ::PointType>> +LambdaRetType evaluate_volume_integral(const axom::Array>& patches, + Lambda&& integrand, + int npts_uv, + int npts_z = 0) +{ + static_assert(detail::internal::is_integrable_v, + "evaluate_integral methods require addition and scalar multiplication for lambda " + "function return type"); + + if(npts_z <= 0) + { + npts_z = npts_uv; + } + + if(patches.empty()) + { + return LambdaRetType {}; + } + + T lower_bound_z = patches[0].boundingBox().getMin()[2]; + for(int i = 1; i < patches.size(); ++i) + { + lower_bound_z = axom::utilities::min(lower_bound_z, patches[i].boundingBox().getMin()[2]); + } + + LambdaRetType total_integral = LambdaRetType {}; + for(int i = 0; i < patches.size(); ++i) + { + total_integral += + detail::evaluate_volume_integral_component(patches[i], integrand, lower_bound_z, npts_uv, npts_z); + } + + return total_integral; +} + +template ::PointType>> +LambdaRetType evaluate_volume_integral(const axom::Array>& patches, + Lambda&& integrand, + int npts_Q, + int npts_P = 0, + int npts_Z = 0) +{ + static_assert(detail::internal::is_integrable_v, + "evaluate_integral methods require addition and scalar multiplication for lambda " + "function return type"); + + if(npts_P <= 0) + { + npts_P = npts_Q; + } + if(npts_Z <= 0) + { + npts_Z = npts_Q; + } + + if(patches.empty()) + { + return LambdaRetType {}; + } + + T lower_bound_z = patches[0].boundingBox().getMin()[2]; + for(int i = 1; i < patches.size(); ++i) + { + lower_bound_z = axom::utilities::min(lower_bound_z, patches[i].boundingBox().getMin()[2]); + } + + LambdaRetType total_integral = LambdaRetType {}; + for(int i = 0; i < patches.size(); ++i) + { + total_integral += detail::evaluate_volume_integral_component(patches[i], + integrand, + lower_bound_z, + npts_Q, + npts_P, + npts_Z); + } + + return total_integral; +} +//@} + } // namespace primal } // end namespace axom diff --git a/src/axom/primal/tests/primal_integral.cpp b/src/axom/primal/tests/primal_integral.cpp index 8724c9c4a1..f7c3b560a7 100644 --- a/src/axom/primal/tests/primal_integral.cpp +++ b/src/axom/primal/tests/primal_integral.cpp @@ -564,6 +564,68 @@ TEST(primal_integral, evaluate_nurbs_surface_normal) } } +TEST(primal_integral, evaluate_patch_surface_and_volume_integrals) +{ + using Point2D = primal::Point; + using Point3D = primal::Point; + using BPatch = primal::BezierPatch; + + auto make_bilinear_patch = + [](const Point3D& p00, const Point3D& p10, const Point3D& p01, const Point3D& p11) { + Point3D control_points[] = {p00, p01, p10, p11}; + return BPatch(control_points, 1, 1); + }; + + axom::Array cube_faces(6); + cube_faces[0] = make_bilinear_patch({0., 0., 0.}, {0., 1., 0.}, {1., 0., 0.}, {1., 1., 0.}); + cube_faces[1] = make_bilinear_patch({0., 0., 1.}, {1., 0., 1.}, {0., 1., 1.}, {1., 1., 1.}); + cube_faces[2] = make_bilinear_patch({0., 0., 0.}, {0., 0., 1.}, {0., 1., 0.}, {0., 1., 1.}); + cube_faces[3] = make_bilinear_patch({1., 0., 0.}, {1., 1., 0.}, {1., 0., 1.}, {1., 1., 1.}); + cube_faces[4] = make_bilinear_patch({0., 0., 0.}, {1., 0., 0.}, {0., 0., 1.}, {1., 0., 1.}); + cube_faces[5] = make_bilinear_patch({0., 1., 0.}, {0., 1., 1.}, {1., 1., 0.}, {1., 1., 1.}); + + auto const_integrand = [](Point3D /*x*/) -> double { return 1.0; }; + auto z_integrand = [](Point3D x) -> double { return x[2]; }; + + constexpr int npts = 6; + constexpr double abs_tol = 1e-10; + + EXPECT_NEAR(evaluate_surface_integral(cube_faces, const_integrand, npts), 6.0, abs_tol); + EXPECT_NEAR(evaluate_volume_integral(cube_faces, const_integrand, npts), 1.0, abs_tol); + EXPECT_NEAR(evaluate_volume_integral(cube_faces, z_integrand, npts), 0.5, abs_tol); +} + +TEST(primal_integral, evaluate_trimmed_nurbs_patch_surface_integral) +{ + using Point2D = primal::Point; + using Point3D = primal::Point; + using NPatch = primal::NURBSPatch; + using TrimmingCurve = primal::NURBSCurve; + + Point3D control_points[] = {Point3D {0.0, 0.0, 0.0}, + Point3D {0.0, 1.0, 0.0}, + Point3D {1.0, 0.0, 0.0}, + Point3D {1.0, 1.0, 0.0}}; + + NPatch patch(control_points, 2, 2, 1, 1); + + axom::Array trim_pts {Point2D {0.25, 0.25}, + Point2D {0.75, 0.25}, + Point2D {0.75, 0.75}, + Point2D {0.25, 0.75}, + Point2D {0.25, 0.25}}; + patch.addTrimmingCurve(TrimmingCurve(trim_pts, 1)); + + auto const_integrand = [](Point3D /*x*/) -> double { return 1.0; }; + auto linear_integrand = [](Point3D x) -> double { return x[0] + x[1]; }; + + constexpr int npts = 8; + constexpr double abs_tol = 1e-10; + + EXPECT_NEAR(evaluate_surface_integral(patch, const_integrand, npts), 0.25, abs_tol); + EXPECT_NEAR(evaluate_surface_integral(patch, linear_integrand, npts), 0.25, abs_tol); +} + TEST(primal_integral, evaluate_integral_nurbs_gwn_cache) { using Point2D = primal::Point; diff --git a/src/axom/quest/tests/CMakeLists.txt b/src/axom/quest/tests/CMakeLists.txt index 95ad186df4..d30e7f40ab 100644 --- a/src/axom/quest/tests/CMakeLists.txt +++ b/src/axom/quest/tests/CMakeLists.txt @@ -30,6 +30,7 @@ blt_list_append(TO quest_tests ELEMENTS quest_meshtester.cpp) if(OPENCASCADE_FOUND AND AXOM_DATA_DIR) + list(APPEND quest_tests quest_step_quadrature.cpp) list(APPEND quest_tests quest_step_reader.cpp) endif() diff --git a/src/axom/quest/tests/quest_step_quadrature.cpp b/src/axom/quest/tests/quest_step_quadrature.cpp new file mode 100644 index 0000000000..a7ef9a53e7 --- /dev/null +++ b/src/axom/quest/tests/quest_step_quadrature.cpp @@ -0,0 +1,40 @@ +// 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/io/STEPReader.hpp" +#include "axom/primal/operators/evaluate_integral.hpp" + +#include "gtest/gtest.h" + +#include +#include + +namespace primal = axom::primal; +namespace quest = axom::quest; + +TEST(quest_step_quadrature, tet_surface_and_volume) +{ + using Point3D = primal::Point; + + const std::string file = std::string(AXOM_DATA_DIR) + "/quest/step/tet.step"; + + quest::STEPReader reader; + reader.setFileName(file); + EXPECT_EQ(reader.read(false), 0); + + const auto& patches = reader.getPatchArray(); + EXPECT_EQ(patches.size(), 4); + + auto const_integrand = [](Point3D /*x*/) -> double { return 1.0; }; + + constexpr int npts = 6; + constexpr double abs_tol = 1e-10; + + EXPECT_NEAR(primal::evaluate_surface_integral(patches, const_integrand, npts), + 8.0 * std::sqrt(3.0), + abs_tol); + EXPECT_NEAR(primal::evaluate_volume_integral(patches, const_integrand, npts), 8.0 / 3.0, abs_tol); +} From be9fdbd708b71af5648fde93e50efe8f7137a4e5 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Mon, 16 Mar 2026 01:08:26 -0700 Subject: [PATCH 047/986] Improves testing against two parametric spheres --- .../quest/tests/quest_step_quadrature.cpp | 253 +++++++++++++++++- 1 file changed, 248 insertions(+), 5 deletions(-) diff --git a/src/axom/quest/tests/quest_step_quadrature.cpp b/src/axom/quest/tests/quest_step_quadrature.cpp index a7ef9a53e7..c9574d82d1 100644 --- a/src/axom/quest/tests/quest_step_quadrature.cpp +++ b/src/axom/quest/tests/quest_step_quadrature.cpp @@ -6,6 +6,7 @@ #include "axom/quest/io/STEPReader.hpp" #include "axom/primal/operators/evaluate_integral.hpp" +#include "axom/core/numerics/transforms.hpp" #include "gtest/gtest.h" @@ -15,17 +16,114 @@ namespace primal = axom::primal; namespace quest = axom::quest; -TEST(quest_step_quadrature, tet_surface_and_volume) +namespace { - using Point3D = primal::Point; - - const std::string file = std::string(AXOM_DATA_DIR) + "/quest/step/tet.step"; +using Point3D = primal::Point; +using Matrix = axom::numerics::Matrix; +using PatchArray = quest::STEPReader::PatchArray; +PatchArray read_step_patches(const std::string& file) +{ quest::STEPReader reader; reader.setFileName(file); EXPECT_EQ(reader.read(false), 0); + return reader.getPatchArray(); +} + +Matrix embed_linear_transform(const Matrix& linear) +{ + EXPECT_EQ(linear.getNumRows(), 3); + EXPECT_EQ(linear.getNumColumns(), 3); + + Matrix affine = Matrix::identity(4); + for(int i = 0; i < 3; ++i) + { + for(int j = 0; j < 3; ++j) + { + affine(i, j) = linear(i, j); + } + } + + return affine; +} + +void transform_patches(PatchArray& patches, const Matrix& transform) +{ + for(int p = 0; p < patches.size(); ++p) + { + auto& control_points = patches[p].getControlPoints(); + for(axom::IndexType ui = 0; ui < control_points.shape()[0]; ++ui) + { + for(axom::IndexType vi = 0; vi < control_points.shape()[1]; ++vi) + { + control_points(ui, vi) = primal::transform_point(control_points(ui, vi), transform); + } + } + } +} + +PatchArray replicate_biquartic_sphere_faces(const PatchArray& base_patches) +{ + EXPECT_EQ(base_patches.size(), 1); + + PatchArray all_patches; + all_patches.push_back(base_patches[0]); - const auto& patches = reader.getPatchArray(); + const Matrix rot_pos_y = embed_linear_transform(axom::numerics::transforms::xRotation(M_PI / 2.0)); + const Matrix rot_neg_y = embed_linear_transform(axom::numerics::transforms::xRotation(-M_PI / 2.0)); + const Matrix rot_pos_z = embed_linear_transform(axom::numerics::transforms::xRotation(M_PI)); + const Matrix rot_neg_x = embed_linear_transform(axom::numerics::transforms::yRotation(M_PI / 2.0)); + const Matrix rot_pos_x = embed_linear_transform(axom::numerics::transforms::yRotation(-M_PI / 2.0)); + + const Matrix rotations[] = {rot_pos_y, rot_neg_y, rot_pos_z, rot_neg_x, rot_pos_x}; + for(const Matrix& rotation : rotations) + { + PatchArray rotated(1); + rotated[0] = base_patches[0]; + transform_patches(rotated, rotation); + all_patches.push_back(rotated[0]); + } + + return all_patches; +} + +template +void expect_matching_surface_values(const PatchArray& patches_a, + const PatchArray& patches_b, + const Lambda& integrand, + int npts, + double expected, + double abs_tol) +{ + const double observed_a = primal::evaluate_surface_integral(patches_a, integrand, npts); + const double observed_b = primal::evaluate_surface_integral(patches_b, integrand, npts); + + EXPECT_NEAR(observed_a, expected, abs_tol); + EXPECT_NEAR(observed_b, expected, abs_tol); + EXPECT_NEAR(observed_a, observed_b, abs_tol); +} + +template +void expect_matching_volume_values(const PatchArray& patches_a, + const PatchArray& patches_b, + const Lambda& integrand, + int npts, + double expected, + double abs_tol) +{ + const double observed_a = primal::evaluate_volume_integral(patches_a, integrand, npts); + const double observed_b = primal::evaluate_volume_integral(patches_b, integrand, npts); + + EXPECT_NEAR(observed_a, expected, abs_tol); + EXPECT_NEAR(observed_b, expected, abs_tol); + EXPECT_NEAR(observed_a, observed_b, abs_tol); +} + +} // namespace + +TEST(quest_step_quadrature, tet_surface_and_volume) +{ + auto patches = read_step_patches(std::string(AXOM_DATA_DIR) + "/quest/step/tet.step"); EXPECT_EQ(patches.size(), 4); auto const_integrand = [](Point3D /*x*/) -> double { return 1.0; }; @@ -38,3 +136,148 @@ TEST(quest_step_quadrature, tet_surface_and_volume) abs_tol); EXPECT_NEAR(primal::evaluate_volume_integral(patches, const_integrand, npts), 8.0 / 3.0, abs_tol); } + +TEST(quest_step_quadrature, sphere_models_match_unit_sphere_moments) +{ + const std::string step_dir = std::string(AXOM_DATA_DIR) + "/quest/step/"; + + auto revolved = read_step_patches(step_dir + "revolved_sphere.step"); + auto biquartic = read_step_patches(step_dir + "biquartic_sphere_surface.step"); + biquartic = replicate_biquartic_sphere_faces(biquartic); + + EXPECT_EQ(revolved.size(), 1); + EXPECT_EQ(biquartic.size(), 6); + + // revolved_sphere.step is a radius-5 sphere, while the biquartic sphere patch is unit-radius. + transform_patches(revolved, axom::numerics::transforms::scale(1.0 / 5.0, 4)); + + constexpr int npts = 16; + constexpr double abs_tol = 5e-5; + + auto const_integrand = [](Point3D /*x*/) -> double { return 1.0; }; + auto x_integrand = [](Point3D x) -> double { return x[0]; }; + auto y_integrand = [](Point3D x) -> double { return x[1]; }; + auto z_integrand = [](Point3D x) -> double { return x[2]; }; + auto xx_integrand = [](Point3D x) -> double { return x[0] * x[0]; }; + auto yy_integrand = [](Point3D x) -> double { return x[1] * x[1]; }; + auto zz_integrand = [](Point3D x) -> double { return x[2] * x[2]; }; + + const double sphere_area = 4.0 * M_PI; + const double sphere_volume = 4.0 * M_PI / 3.0; + const double surface_second_moment = 4.0 * M_PI / 3.0; + const double volume_second_moment = 4.0 * M_PI / 15.0; + + expect_matching_surface_values(revolved, biquartic, const_integrand, npts, sphere_area, abs_tol); + expect_matching_surface_values(revolved, biquartic, x_integrand, npts, 0.0, abs_tol); + expect_matching_surface_values(revolved, biquartic, y_integrand, npts, 0.0, abs_tol); + expect_matching_surface_values(revolved, biquartic, z_integrand, npts, 0.0, abs_tol); + expect_matching_surface_values(revolved, biquartic, xx_integrand, npts, surface_second_moment, abs_tol); + expect_matching_surface_values(revolved, biquartic, yy_integrand, npts, surface_second_moment, abs_tol); + expect_matching_surface_values(revolved, biquartic, zz_integrand, npts, surface_second_moment, abs_tol); + + expect_matching_volume_values(revolved, biquartic, const_integrand, npts, sphere_volume, abs_tol); + expect_matching_volume_values(revolved, biquartic, x_integrand, npts, 0.0, abs_tol); + expect_matching_volume_values(revolved, biquartic, y_integrand, npts, 0.0, abs_tol); + expect_matching_volume_values(revolved, biquartic, z_integrand, npts, 0.0, abs_tol); + expect_matching_volume_values(revolved, biquartic, xx_integrand, npts, volume_second_moment, abs_tol); + expect_matching_volume_values(revolved, biquartic, yy_integrand, npts, volume_second_moment, abs_tol); + expect_matching_volume_values(revolved, biquartic, zz_integrand, npts, volume_second_moment, abs_tol); +} + +TEST(quest_step_quadrature, transformed_sphere_models_match_expected_moments) +{ + const std::string step_dir = std::string(AXOM_DATA_DIR) + "/quest/step/"; + + auto revolved = read_step_patches(step_dir + "revolved_sphere.step"); + auto biquartic = read_step_patches(step_dir + "biquartic_sphere_surface.step"); + biquartic = replicate_biquartic_sphere_faces(biquartic); + + transform_patches(revolved, axom::numerics::transforms::scale(1.0 / 5.0, 4)); + + constexpr double sphere_scale = 1.7; + const Point3D center {1.25, -0.75, 2.0}; + + const Matrix scale = axom::numerics::transforms::scale(sphere_scale, 4); + const Matrix rotation = + embed_linear_transform(axom::numerics::transforms::axisRotation(M_PI / 4.0, 1.0, 2.0, 3.0)); + const Matrix translation = axom::numerics::transforms::translate(center[0], center[1], center[2]); + + transform_patches(revolved, scale); + transform_patches(revolved, rotation); + transform_patches(revolved, translation); + + transform_patches(biquartic, scale); + transform_patches(biquartic, rotation); + transform_patches(biquartic, translation); + + constexpr int npts = 16; + constexpr double abs_tol = 1e-4; + + auto const_integrand = [](Point3D /*x*/) -> double { return 1.0; }; + auto x_integrand = [](Point3D x) -> double { return x[0]; }; + auto y_integrand = [](Point3D x) -> double { return x[1]; }; + auto z_integrand = [](Point3D x) -> double { return x[2]; }; + auto centered_xx_integrand = [center](Point3D x) -> double { + const double dx = x[0] - center[0]; + return dx * dx; + }; + auto centered_yy_integrand = [center](Point3D x) -> double { + const double dy = x[1] - center[1]; + return dy * dy; + }; + auto centered_zz_integrand = [center](Point3D x) -> double { + const double dz = x[2] - center[2]; + return dz * dz; + }; + + const double sphere_area = 4.0 * M_PI * sphere_scale * sphere_scale; + const double sphere_volume = (4.0 * M_PI / 3.0) * sphere_scale * sphere_scale * sphere_scale; + const double surface_second_moment = (4.0 * M_PI / 3.0) * std::pow(sphere_scale, 4); + const double volume_second_moment = (4.0 * M_PI / 15.0) * std::pow(sphere_scale, 5); + + expect_matching_surface_values(revolved, biquartic, const_integrand, npts, sphere_area, abs_tol); + expect_matching_surface_values(revolved, biquartic, x_integrand, npts, sphere_area * center[0], abs_tol); + expect_matching_surface_values(revolved, biquartic, y_integrand, npts, sphere_area * center[1], abs_tol); + expect_matching_surface_values(revolved, biquartic, z_integrand, npts, sphere_area * center[2], abs_tol); + expect_matching_surface_values(revolved, + biquartic, + centered_xx_integrand, + npts, + surface_second_moment, + abs_tol); + expect_matching_surface_values(revolved, + biquartic, + centered_yy_integrand, + npts, + surface_second_moment, + abs_tol); + expect_matching_surface_values(revolved, + biquartic, + centered_zz_integrand, + npts, + surface_second_moment, + abs_tol); + + expect_matching_volume_values(revolved, biquartic, const_integrand, npts, sphere_volume, abs_tol); + expect_matching_volume_values(revolved, biquartic, x_integrand, npts, sphere_volume * center[0], abs_tol); + expect_matching_volume_values(revolved, biquartic, y_integrand, npts, sphere_volume * center[1], abs_tol); + expect_matching_volume_values(revolved, biquartic, z_integrand, npts, sphere_volume * center[2], abs_tol); + expect_matching_volume_values(revolved, + biquartic, + centered_xx_integrand, + npts, + volume_second_moment, + abs_tol); + expect_matching_volume_values(revolved, + biquartic, + centered_yy_integrand, + npts, + volume_second_moment, + abs_tol); + expect_matching_volume_values(revolved, + biquartic, + centered_zz_integrand, + npts, + volume_second_moment, + abs_tol); +} From c38a07d409fce99ba1a6be0efa6be67c0e6c9236 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Mon, 16 Mar 2026 01:38:37 -0700 Subject: [PATCH 048/986] Adds doxygen comments to new integral evaluation functions --- .../primal/operators/evaluate_integral.hpp | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/src/axom/primal/operators/evaluate_integral.hpp b/src/axom/primal/operators/evaluate_integral.hpp index ff03bf5a37..40f52d88fe 100644 --- a/src/axom/primal/operators/evaluate_integral.hpp +++ b/src/axom/primal/operators/evaluate_integral.hpp @@ -377,6 +377,17 @@ LambdaRetType evaluate_area_integral(const axom::Array& carray, ///@{ /// \name Evaluates scalar-field surface integrals for functions f : R^3 -> R^m +/*! + * \brief Evaluate a scalar surface integral on a single Bezier patch. + * + * Uses tensor-product Gauss-Legendre quadrature in the patch parameter space. + * + * \param [in] patch the Bezier patch + * \param [in] integrand callable representing the integrand + * \param [in] npts the number of quadrature points in each parametric direction + * + * \pre The patch parameterization must be valid on its full parameter domain. + */ template ::PointType>> @@ -391,6 +402,24 @@ LambdaRetType evaluate_surface_integral(const primal::BezierPatch& patch, return detail::evaluate_surface_integral_component(patch, std::forward(integrand), npts); } +/*! + * \brief Evaluate a scalar surface integral on a single NURBS patch. + * + * Untrimmed patches are integrated by Bezier extraction followed by tensor-product + * Gauss-Legendre quadrature. Trimmed patches are integrated by reducing the + * parameter-space area integral to line integrals over the trimming curves. + * + * \param [in] patch the NURBS patch + * \param [in] integrand callable representing the integrand + * \param [in] npts_Q the number of quadrature points on each trimming curve or + * in each parametric direction for untrimmed Bezier pieces + * \param [in] npts_P the number of quadrature points used for numerical + * antidifferentiation in parameter space + * + * \pre The patch parameterization must be valid on its full parameter domain. + * \pre If the patch is trimmed, its trimming curves must bound the intended + * interior region in parameter space. + */ template ::PointType>> @@ -414,6 +443,17 @@ LambdaRetType evaluate_surface_integral(const primal::NURBSPatch& patch, npts_P); } +/*! + * \brief Evaluate a scalar surface integral on a collection of Bezier patches. + * + * The result is the sum of the surface integrals over each patch in the array. + * + * \param [in] patches the patch collection + * \param [in] integrand callable representing the integrand + * \param [in] npts the number of quadrature points in each parametric direction + * + * \pre Each patch parameterization must be valid on its full parameter domain. + */ template ::PointType>> @@ -434,6 +474,22 @@ LambdaRetType evaluate_surface_integral(const axom::Array>& pa return total_integral; } +/*! + * \brief Evaluate a scalar surface integral on a collection of NURBS patches. + * + * The result is the sum of the surface integrals over each patch in the array. + * + * \param [in] patches the patch collection + * \param [in] integrand callable representing the integrand + * \param [in] npts_Q the number of quadrature points on each trimming curve or + * in each parametric direction for untrimmed Bezier pieces + * \param [in] npts_P the number of quadrature points used for numerical + * antidifferentiation in parameter space + * + * \pre Each patch parameterization must be valid on its full parameter domain. + * \pre Any trimmed patch in the array must have trimming curves that bound its + * intended interior region in parameter space. + */ template ::PointType>> @@ -465,6 +521,22 @@ LambdaRetType evaluate_surface_integral(const axom::Array>& pat ///@{ /// \name Evaluates scalar-field volume integrals for functions f : R^3 -> R^m +/*! + * \brief Evaluate a scalar volume-integral contribution from a single Bezier patch. + * + * This applies the Stokes-based reduction used for the full volume algorithm to + * one patch using a z-directed numerical antiderivative. + * + * \param [in] patch the Bezier patch + * \param [in] integrand callable representing the integrand + * \param [in] npts_uv the number of quadrature points in each patch parameter direction + * \param [in] npts_z the number of quadrature points used for numerical + * antidifferentiation in z + * + * \pre The patch parameterization must be valid on its full parameter domain. + * \pre The returned value is geometrically meaningful as a volume only when this + * patch is interpreted as part of a closed, consistently oriented boundary. + */ template ::PointType>> @@ -489,6 +561,28 @@ LambdaRetType evaluate_volume_integral(const primal::BezierPatch& patch, npts_z); } +/*! + * \brief Evaluate a scalar volume-integral contribution from a single NURBS patch. + * + * Trimmed patches use the same Green/Stokes reduction as the surface-integral + * algorithm, combined with a z-directed numerical antiderivative for the volume + * reduction. + * + * \param [in] patch the NURBS patch + * \param [in] integrand callable representing the integrand + * \param [in] npts_Q the number of quadrature points on each trimming curve or + * in each parametric direction for untrimmed Bezier pieces + * \param [in] npts_P the number of quadrature points used for numerical + * antidifferentiation in parameter space + * \param [in] npts_Z the number of quadrature points used for numerical + * antidifferentiation in z + * + * \pre The patch parameterization must be valid on its full parameter domain. + * \pre If the patch is trimmed, its trimming curves must bound the intended + * interior region in parameter space. + * \pre The returned value is geometrically meaningful as a volume only when this + * patch is interpreted as part of a closed, consistently oriented boundary. + */ template ::PointType>> @@ -519,6 +613,22 @@ LambdaRetType evaluate_volume_integral(const primal::NURBSPatch& patch, npts_Z); } +/*! + * \brief Evaluate a scalar volume integral over a collection of Bezier patches. + * + * The result is obtained by summing the Stokes-based contribution from each + * patch in the collection. + * + * \param [in] patches the patch collection + * \param [in] integrand callable representing the integrand + * \param [in] npts_uv the number of quadrature points in each patch parameter direction + * \param [in] npts_z the number of quadrature points used for numerical + * antidifferentiation in z + * + * \pre Each patch parameterization must be valid on its full parameter domain. + * \pre The patch collection must represent a closed, consistently oriented + * boundary of the target volume. + */ template ::PointType>> @@ -557,6 +667,27 @@ LambdaRetType evaluate_volume_integral(const axom::Array>& pat return total_integral; } +/*! + * \brief Evaluate a scalar volume integral over a collection of NURBS patches. + * + * The result is obtained by summing the Stokes-based contribution from each + * patch in the collection. + * + * \param [in] patches the patch collection + * \param [in] integrand callable representing the integrand + * \param [in] npts_Q the number of quadrature points on each trimming curve or + * in each parametric direction for untrimmed Bezier pieces + * \param [in] npts_P the number of quadrature points used for numerical + * antidifferentiation in parameter space + * \param [in] npts_Z the number of quadrature points used for numerical + * antidifferentiation in z + * + * \pre Each patch parameterization must be valid on its full parameter domain. + * \pre Any trimmed patch in the array must have trimming curves that bound its + * intended interior region in parameter space. + * \pre The patch collection must represent a closed, consistently oriented + * boundary of the target volume. + */ template ::PointType>> From d8e7b7941870dec915f7b207b80d45698d43b24e Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Fri, 20 Mar 2026 11:31:44 -0700 Subject: [PATCH 049/986] Adds a quest example that evaluates moments on a STEP file --- src/axom/quest/examples/CMakeLists.txt | 19 + .../quest/examples/quest_step_moments.cpp | 445 ++++++++++++++++++ 2 files changed, 464 insertions(+) create mode 100644 src/axom/quest/examples/quest_step_moments.cpp diff --git a/src/axom/quest/examples/CMakeLists.txt b/src/axom/quest/examples/CMakeLists.txt index fd2215ee43..8d570c8131 100644 --- a/src/axom/quest/examples/CMakeLists.txt +++ b/src/axom/quest/examples/CMakeLists.txt @@ -728,6 +728,14 @@ endif() if(OPENCASCADE_FOUND) + axom_add_executable( + NAME quest_step_moments_ex + SOURCES quest_step_moments.cpp + OUTPUT_DIR ${EXAMPLE_OUTPUT_DIRECTORY} + DEPENDS_ON axom cli11 fmt opencascade + FOLDER axom/quest/examples + ) + axom_add_executable( NAME quest_step_file_ex SOURCES quest_step_file.cpp @@ -742,6 +750,15 @@ if(OPENCASCADE_FOUND) set(_num_ranks 2) endif() + axom_add_test( + NAME quest_step_moments_revolved_sphere + COMMAND quest_step_moments_ex -f ${quest_data_dir}/step/revolved_sphere.step + --integral both + --order 1 + --npts 16) + set_tests_properties(quest_step_moments_revolved_sphere PROPERTIES + PASS_REGULAR_EXPRESSION "SUMMARY surface_m0=3\\.14159265358979[0-9]*e\\+02 volume_m0=5\\.23598775598298[0-9]*e\\+02") + axom_add_test( NAME quest_step_file_sliced_cylinder COMMAND quest_step_file_ex -f ${quest_data_dir}/step/sliced_cylinder.step @@ -771,3 +788,5 @@ if(OPENCASCADE_FOUND) endif() endif() + + diff --git a/src/axom/quest/examples/quest_step_moments.cpp b/src/axom/quest/examples/quest_step_moments.cpp new file mode 100644 index 0000000000..8b0664c3ef --- /dev/null +++ b/src/axom/quest/examples/quest_step_moments.cpp @@ -0,0 +1,445 @@ +// 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) + +/*! + * \file quest_step_moments.cpp + * \brief Example that reads a STEP file and computes geometric moments of the + * corresponding trimmed NURBS surface and/or enclosed volume. + * + * The example loads a STEP BRep with Quest's STEP reader, then evaluates + * monomial moments \f$\int x^i y^j z^k \, dS\f$ and/or + * \f$\int x^i y^j z^k \, dV\f$ for all nonnegative exponent triples with + * total degree \f$i + j + k \le n\f$, where \a n is user supplied. + * It also derives centroids and inertia tensors from the first- and second- + * order moments. + * + * \note Volume moments are only geometrically meaningful when the STEP model + * represents a closed, consistently oriented boundary. + */ + +#include "axom/config.hpp" +#include "axom/core.hpp" +#include "axom/core/utilities/CommandLineUtilities.hpp" +#include "axom/primal.hpp" +#include "axom/primal/operators/evaluate_integral.hpp" +#include "axom/quest.hpp" +#include "axom/slic.hpp" + +#include "axom/CLI11.hpp" +#include "axom/fmt.hpp" + +#include +#include +#include +#include +#include + +namespace primal = axom::primal; +namespace slic = axom::slic; + +namespace +{ + +using Point3D = primal::Point; +using PatchArray = axom::quest::STEPReader::PatchArray; +using MomentKey = std::tuple; + +enum class IntegralMode +{ + SURFACE, + VOLUME, + BOTH +}; + +const std::map s_validIntegralModes {{"surface", IntegralMode::SURFACE}, + {"volume", IntegralMode::VOLUME}, + {"both", IntegralMode::BOTH}}; + +const char* integral_mode_name(IntegralMode mode) +{ + switch(mode) + { + case IntegralMode::SURFACE: + return "surface"; + case IntegralMode::VOLUME: + return "volume"; + case IntegralMode::BOTH: + return "both"; + } + + return "unknown"; +} + +bool should_compute_surface(IntegralMode mode) +{ + return mode == IntegralMode::SURFACE || mode == IntegralMode::BOTH; +} + +bool should_compute_volume(IntegralMode mode) +{ + return mode == IntegralMode::VOLUME || mode == IntegralMode::BOTH; +} + +struct MomentIndex +{ + int degree {}; + int px {}; + int py {}; + int pz {}; +}; + +struct MomentEntry +{ + MomentIndex index {}; + double value {}; +}; + +struct MomentSet +{ + std::vector requested_entries; + std::map values; +}; + +struct InertiaTensor +{ + double xx {}; + double yy {}; + double zz {}; + double xy {}; + double xz {}; + double yz {}; +}; + +struct MassProperties +{ + bool valid {false}; + double measure {}; + Point3D centroid {}; + InertiaTensor inertia_origin {}; + InertiaTensor inertia_centroid {}; +}; + +std::vector enumerate_moment_indices(int max_degree) +{ + std::vector moments; + for(int degree = 0; degree <= max_degree; ++degree) + { + for(int px = 0; px <= degree; ++px) + { + for(int py = 0; py <= degree - px; ++py) + { + const int pz = degree - px - py; + moments.push_back(MomentIndex {degree, px, py, pz}); + } + } + } + + return moments; +} + +double ipow(double base, int exponent) +{ + double result = 1.0; + for(int i = 0; i < exponent; ++i) + { + result *= base; + } + return result; +} + +double evaluate_monomial(const Point3D& x, const MomentIndex& idx) +{ + return ipow(x[0], idx.px) * ipow(x[1], idx.py) * ipow(x[2], idx.pz); +} + +template +MomentSet compute_moments(int requested_max_degree, int computed_max_degree, Integrator&& integrator) +{ + MomentSet moments; + const auto indices = enumerate_moment_indices(computed_max_degree); + moments.requested_entries.reserve(indices.size()); + + for(const auto& idx : indices) + { + const double value = integrator(idx); + moments.values[MomentKey {idx.px, idx.py, idx.pz}] = value; + + if(idx.degree <= requested_max_degree) + { + moments.requested_entries.push_back(MomentEntry {idx, value}); + } + } + + return moments; +} + +double get_moment(const MomentSet& moments, int px, int py, int pz) +{ + const auto it = moments.values.find(MomentKey {px, py, pz}); + SLIC_ASSERT(it != moments.values.end()); + return it->second; +} + +InertiaTensor shift_to_centroid(const InertiaTensor& inertia_origin, + double measure, + const Point3D& centroid) +{ + InertiaTensor shifted = inertia_origin; + + shifted.xx -= measure * (centroid[1] * centroid[1] + centroid[2] * centroid[2]); + shifted.yy -= measure * (centroid[0] * centroid[0] + centroid[2] * centroid[2]); + shifted.zz -= measure * (centroid[0] * centroid[0] + centroid[1] * centroid[1]); + shifted.xy += measure * centroid[0] * centroid[1]; + shifted.xz += measure * centroid[0] * centroid[2]; + shifted.yz += measure * centroid[1] * centroid[2]; + + return shifted; +} + +MassProperties compute_mass_properties(const MomentSet& moments) +{ + constexpr double eps = 1e-14; + + MassProperties props; + props.measure = get_moment(moments, 0, 0, 0); + if(std::abs(props.measure) <= eps) + { + return props; + } + + props.valid = true; + props.centroid[0] = get_moment(moments, 1, 0, 0) / props.measure; + props.centroid[1] = get_moment(moments, 0, 1, 0) / props.measure; + props.centroid[2] = get_moment(moments, 0, 0, 1) / props.measure; + + const double xx = get_moment(moments, 2, 0, 0); + const double yy = get_moment(moments, 0, 2, 0); + const double zz = get_moment(moments, 0, 0, 2); + const double xy = get_moment(moments, 1, 1, 0); + const double xz = get_moment(moments, 1, 0, 1); + const double yz = get_moment(moments, 0, 1, 1); + + props.inertia_origin.xx = yy + zz; + props.inertia_origin.yy = xx + zz; + props.inertia_origin.zz = xx + yy; + props.inertia_origin.xy = -xy; + props.inertia_origin.xz = -xz; + props.inertia_origin.yz = -yz; + + props.inertia_centroid = shift_to_centroid(props.inertia_origin, props.measure, props.centroid); + + return props; +} + +void log_moment_entries(const char* label, const MomentSet& moments) +{ + for(const auto& entry : moments.requested_entries) + { + SLIC_INFO(axom::fmt::format("{}_MOMENT degree={} exponents=({},{},{}) value={:.16e}", + label, + entry.index.degree, + entry.index.px, + entry.index.py, + entry.index.pz, + entry.value)); + } +} + +void log_mass_properties(const char* label, const MassProperties& props) +{ + if(!props.valid) + { + SLIC_INFO(axom::fmt::format("{}_PROPERTIES unavailable=measure_is_zero", label)); + return; + } + + SLIC_INFO(axom::fmt::format("{}_CENTROID x={:.16e} y={:.16e} z={:.16e}", + label, + props.centroid[0], + props.centroid[1], + props.centroid[2])); + SLIC_INFO(axom::fmt::format( + "{}_INERTIA_ORIGIN xx={:.16e} yy={:.16e} zz={:.16e} xy={:.16e} xz={:.16e} yz={:.16e}", + label, + props.inertia_origin.xx, + props.inertia_origin.yy, + props.inertia_origin.zz, + props.inertia_origin.xy, + props.inertia_origin.xz, + props.inertia_origin.yz)); + SLIC_INFO(axom::fmt::format( + "{}_INERTIA_CENTROID xx={:.16e} yy={:.16e} zz={:.16e} xy={:.16e} xz={:.16e} yz={:.16e}", + label, + props.inertia_centroid.xx, + props.inertia_centroid.yy, + props.inertia_centroid.zz, + props.inertia_centroid.xy, + props.inertia_centroid.xz, + props.inertia_centroid.yz)); +} + +} // namespace + +int main(int argc, char** argv) +{ + std::string input_file; + int max_degree {1}; + int quadrature_order {0}; + bool verbose {false}; + bool validate_model {false}; + std::string annotationMode {"none"}; + IntegralMode integral_mode {IntegralMode::BOTH}; + + axom::CLI::App app { + "Load a STEP model and compute geometric moments, centroids, and inertia tensors."}; + app.add_option("-f,--file", input_file) + ->description("Input STEP file") + ->required() + ->check(axom::CLI::ExistingFile); + app.add_option("-n,--order", max_degree) + ->description("Maximum total degree n for explicit monomial output x^i y^j z^k with i+j+k<=n") + ->capture_default_str() + ->check(axom::CLI::NonNegativeNumber); + app.add_option("--npts", quadrature_order) + ->description("Quadrature order for each numerical integration stage; defaults to max(8, n+4)") + ->capture_default_str() + ->check(axom::CLI::PositiveNumber); + app.add_option("--integral", integral_mode) + ->description("Which measures to compute: 'surface', 'volume', or 'both'") + ->capture_default_str() + ->transform(axom::CLI::CheckedTransformer(s_validIntegralModes)); + app.add_flag("-v,--verbose", verbose, "Enable verbose output")->capture_default_str(); + app.add_flag("--validate", validate_model, "Run STEP model validation checks")->capture_default_str(); +#ifdef AXOM_USE_CALIPER + app.add_option("--caliper", annotationMode) + ->description( + "caliper annotation mode. Valid options include 'none' and 'report'. " + "See Axom's Caliper support for additional modes.") + ->capture_default_str() + ->check(axom::utilities::ValidCaliperMode); +#endif + app.get_formatter()->column_width(44); + + try + { + app.parse(argc, argv); + } + catch(const axom::CLI::ParseError& e) + { + return app.exit(e); + } + + axom::slic::SimpleLogger logger(axom::slic::message::Info); +#ifdef AXOM_USE_CALIPER + axom::utilities::raii::AnnotationsWrapper annotation_raii_wrapper(annotationMode); +#endif + AXOM_ANNOTATE_SCOPE("quest step moments example"); + + if(quadrature_order <= 0) + { + quadrature_order = axom::utilities::max(8, max_degree + 4); + } + + const int computed_max_degree = axom::utilities::max(max_degree, 2); + + SLIC_INFO(axom::fmt::format("Reading STEP file '{}'", input_file)); + + axom::quest::STEPReader reader; + reader.setFileName(input_file); + reader.setVerbosity(verbose); + + { + AXOM_ANNOTATE_SCOPE("read step"); + const int read_status = reader.read(validate_model); + if(read_status != 0) + { + SLIC_ERROR("Failed to read STEP file."); + return 1; + } + } + + const PatchArray& patches = reader.getPatchArray(); + if(patches.empty()) + { + SLIC_ERROR("STEP file did not contain any patches."); + return 1; + } + + if(verbose) + { + SLIC_INFO(axom::fmt::format("STEP file units: '{}'", reader.getFileUnits())); + SLIC_INFO(reader.getBRepStats()); + } + + if(should_compute_volume(integral_mode)) + { + SLIC_WARNING( + "Volume properties assume the STEP model is a closed, consistently oriented boundary."); + } + + SLIC_INFO(axom::fmt::format("MODEL file='{}' patches={} units='{}'", + input_file, + patches.size(), + reader.getFileUnits())); + SLIC_INFO(axom::fmt::format("CONFIG requested_order={} computed_order={} npts={} integral={}", + max_degree, + computed_max_degree, + quadrature_order, + integral_mode_name(integral_mode))); + + MomentSet surface_moments; + MomentSet volume_moments; + MassProperties surface_props; + MassProperties volume_props; + + if(should_compute_surface(integral_mode)) + { + AXOM_ANNOTATE_SCOPE("compute surface properties"); + surface_moments = compute_moments(max_degree, computed_max_degree, [&](const MomentIndex& idx) { + auto integrand = [idx](const Point3D& x) -> double { return evaluate_monomial(x, idx); }; + return primal::evaluate_surface_integral(patches, integrand, quadrature_order); + }); + surface_props = compute_mass_properties(surface_moments); + } + + if(should_compute_volume(integral_mode)) + { + AXOM_ANNOTATE_SCOPE("compute volume properties"); + volume_moments = compute_moments(max_degree, computed_max_degree, [&](const MomentIndex& idx) { + auto integrand = [idx](const Point3D& x) -> double { return evaluate_monomial(x, idx); }; + return primal::evaluate_volume_integral(patches, integrand, quadrature_order); + }); + volume_props = compute_mass_properties(volume_moments); + } + + { + AXOM_ANNOTATE_SCOPE("log results"); + std::string summary = "SUMMARY"; + if(should_compute_surface(integral_mode)) + { + summary += axom::fmt::format(" surface_m0={:.16e}", surface_props.measure); + } + if(should_compute_volume(integral_mode)) + { + summary += axom::fmt::format(" volume_m0={:.16e}", volume_props.measure); + } + SLIC_INFO(summary); + + if(should_compute_surface(integral_mode)) + { + log_mass_properties("SURFACE", surface_props); + log_moment_entries("SURFACE", surface_moments); + } + + if(should_compute_volume(integral_mode)) + { + log_mass_properties("VOLUME", volume_props); + log_moment_entries("VOLUME", volume_moments); + } + } + + return 0; +} From fc0be760392df8113e14939ebfa3eeb99d60d905 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Sun, 22 Mar 2026 18:39:10 -0700 Subject: [PATCH 050/986] Adds centroid, moments of inertia and fit to ellipsoid And improves output formatting. --- .../quest/examples/quest_step_moments.cpp | 837 ++++++++++++++++-- 1 file changed, 768 insertions(+), 69 deletions(-) diff --git a/src/axom/quest/examples/quest_step_moments.cpp b/src/axom/quest/examples/quest_step_moments.cpp index 8b0664c3ef..d7406ebd61 100644 --- a/src/axom/quest/examples/quest_step_moments.cpp +++ b/src/axom/quest/examples/quest_step_moments.cpp @@ -9,12 +9,12 @@ * \brief Example that reads a STEP file and computes geometric moments of the * corresponding trimmed NURBS surface and/or enclosed volume. * - * The example loads a STEP BRep with Quest's STEP reader, then evaluates + * The example loads a BRep with Quest's STEP reader, then evaluates * monomial moments \f$\int x^i y^j z^k \, dS\f$ and/or * \f$\int x^i y^j z^k \, dV\f$ for all nonnegative exponent triples with * total degree \f$i + j + k \le n\f$, where \a n is user supplied. - * It also derives centroids and inertia tensors from the first- and second- - * order moments. + * It also derives centroids, inertia tensors, and volume-based principal-axis + * fit proxies from the first- and second-order moments. * * \note Volume moments are only geometrically meaningful when the STEP model * represents a closed, consistently oriented boundary. @@ -22,21 +22,22 @@ #include "axom/config.hpp" #include "axom/core.hpp" -#include "axom/core/utilities/CommandLineUtilities.hpp" +#include "axom/slic.hpp" #include "axom/primal.hpp" -#include "axom/primal/operators/evaluate_integral.hpp" +#include "axom/mint.hpp" #include "axom/quest.hpp" -#include "axom/slic.hpp" #include "axom/CLI11.hpp" #include "axom/fmt.hpp" +#include #include #include #include #include #include +namespace mint = axom::mint; namespace primal = axom::primal; namespace slic = axom::slic; @@ -44,6 +45,9 @@ namespace { using Point3D = primal::Point; +using Vector3D = primal::Vector; +using OBB3D = primal::OrientedBoundingBox; +using TriMesh = mint::UnstructuredMesh; using PatchArray = axom::quest::STEPReader::PatchArray; using MomentKey = std::tuple; @@ -58,6 +62,8 @@ const std::map s_validIntegralModes {{"surface", Inte {"volume", IntegralMode::VOLUME}, {"both", IntegralMode::BOTH}}; +constexpr double eps = 1e-14; + const char* integral_mode_name(IntegralMode mode) { switch(mode) @@ -122,6 +128,32 @@ struct MassProperties InertiaTensor inertia_centroid {}; }; +struct PrincipalFrame +{ + bool valid {false}; + double measure {}; + Point3D centroid {}; + double principal_inertia[3] {}; + double principal_second_moments[3] {}; + Vector3D axes[3] {}; +}; + +struct EllipsoidFit +{ + bool valid {false}; + Point3D centroid {}; + Vector3D axes[3] {}; + double radii[3] {}; + double volume {}; +}; + +struct ObbFit +{ + bool valid {false}; + OBB3D box {}; + double volume {}; +}; + std::vector enumerate_moment_indices(int max_degree) { std::vector moments; @@ -201,8 +233,6 @@ InertiaTensor shift_to_centroid(const InertiaTensor& inertia_origin, MassProperties compute_mass_properties(const MomentSet& moments) { - constexpr double eps = 1e-14; - MassProperties props; props.measure = get_moment(moments, 0, 0, 0); if(std::abs(props.measure) <= eps) @@ -234,51 +264,672 @@ MassProperties compute_mass_properties(const MomentSet& moments) return props; } -void log_moment_entries(const char* label, const MomentSet& moments) +PrincipalFrame compute_principal_frame(const MassProperties& props) { + constexpr int ndims = 3; + + PrincipalFrame frame; + if(!props.valid || std::abs(props.measure) <= eps) + { + return frame; + } + + axom::numerics::Matrix inertia(ndims, ndims); + inertia(0, 0) = props.inertia_centroid.xx; + inertia(0, 1) = props.inertia_centroid.xy; + inertia(0, 2) = props.inertia_centroid.xz; + inertia(1, 0) = props.inertia_centroid.xy; + inertia(1, 1) = props.inertia_centroid.yy; + inertia(1, 2) = props.inertia_centroid.yz; + inertia(2, 0) = props.inertia_centroid.xz; + inertia(2, 1) = props.inertia_centroid.yz; + inertia(2, 2) = props.inertia_centroid.zz; + + axom::numerics::Matrix eigenvectors(ndims, ndims); + double eigenvalues[ndims] {}; + const int rc = axom::numerics::jacobi_eigensolve(inertia, eigenvectors, eigenvalues); + if(rc != axom::numerics::JACOBI_EIGENSOLVE_SUCCESS) + { + return frame; + } + + frame.valid = true; + frame.measure = props.measure; + frame.centroid = props.centroid; + + double inertia_trace = 0.0; + for(int i = 0; i < ndims; ++i) + { + frame.principal_inertia[i] = eigenvalues[i]; + inertia_trace += eigenvalues[i]; + + frame.axes[i][0] = eigenvectors(0, i); + frame.axes[i][1] = eigenvectors(1, i); + frame.axes[i][2] = eigenvectors(2, i); + } + + for(int i = 0; i < ndims; ++i) + { + frame.principal_second_moments[i] = 0.5 * (inertia_trace - 2.0 * frame.principal_inertia[i]); + } + + return frame; +} + +bool compute_shape_dimensions(const PrincipalFrame& frame, double factor, double (&dims)[3]) +{ + constexpr int ndims = 3; + constexpr double min_scale = 1.0; + + if(!frame.valid || std::abs(frame.measure) <= eps) + { + return false; + } + + const double scale = std::max({min_scale, + std::abs(frame.measure), + std::abs(frame.principal_second_moments[0]), + std::abs(frame.principal_second_moments[1]), + std::abs(frame.principal_second_moments[2])}); + const double tol = 1e-12 * scale; + + for(int i = 0; i < ndims; ++i) + { + const double dim_sq = factor * frame.principal_second_moments[i] / frame.measure; + if(dim_sq < -tol) + { + return false; + } + + dims[i] = std::sqrt(std::max(dim_sq, 0.0)); + } + + return true; +} + +double compute_ellipsoid_volume(const double (&radii)[3]) +{ + return (4.0 / 3.0) * M_PI * radii[0] * radii[1] * radii[2]; +} + +double compute_obb_volume(const Vector3D& extents) +{ + return 8.0 * extents[0] * extents[1] * extents[2]; +} + +double compute_volume_ratio(double fit_volume, double reference_volume) +{ + if(std::abs(reference_volume) <= eps) + { + return 0.0; + } + + return fit_volume / reference_volume; +} + +double compute_effective_density(double mass, double geometric_volume) +{ + if(std::abs(geometric_volume) <= eps) + { + return 0.0; + } + + return mass / geometric_volume; +} + +EllipsoidFit compute_inertia_matched_ellipsoid(const PrincipalFrame& frame) +{ + EllipsoidFit fit; + double radii[3] {}; + if(!compute_shape_dimensions(frame, 5.0, radii)) + { + return fit; + } + + fit.valid = true; + fit.centroid = frame.centroid; + for(int i = 0; i < 3; ++i) + { + fit.axes[i] = frame.axes[i]; + fit.radii[i] = radii[i]; + } + fit.volume = compute_ellipsoid_volume(fit.radii); + + return fit; +} + +EllipsoidFit scale_ellipsoid_to_volume(const EllipsoidFit& fit, double target_volume) +{ + EllipsoidFit scaled; + if(!fit.valid || std::abs(fit.volume) <= eps || target_volume <= 0.0) + { + return scaled; + } + + const double scale = std::cbrt(target_volume / fit.volume); + scaled.valid = true; + scaled.centroid = fit.centroid; + for(int i = 0; i < 3; ++i) + { + scaled.axes[i] = fit.axes[i]; + scaled.radii[i] = scale * fit.radii[i]; + } + scaled.volume = compute_ellipsoid_volume(scaled.radii); + + return scaled; +} + +ObbFit compute_inertia_matched_obb(const PrincipalFrame& frame) +{ + ObbFit fit; + double extents_data[3] {}; + if(!compute_shape_dimensions(frame, 3.0, extents_data)) + { + return fit; + } + + Vector3D extents; + for(int i = 0; i < 3; ++i) + { + extents[i] = extents_data[i]; + } + + fit.box = OBB3D(frame.centroid, frame.axes, extents); + fit.valid = fit.box.isValid(); + fit.volume = compute_obb_volume(extents); + + return fit; +} + +ObbFit scale_obb_to_volume(const ObbFit& fit, double target_volume) +{ + ObbFit scaled; + if(!fit.valid || std::abs(fit.volume) <= eps || target_volume <= 0.0) + { + return scaled; + } + + const double scale = std::cbrt(target_volume / fit.volume); + const auto& centroid = fit.box.getCentroid(); + const auto* axes = fit.box.getAxes(); + const auto& extents = fit.box.getExtents(); + + Vector3D scaled_axes[3]; + Vector3D scaled_extents; + for(int i = 0; i < 3; ++i) + { + scaled_axes[i] = axes[i]; + scaled_extents[i] = scale * extents[i]; + } + + scaled.box = OBB3D(centroid, scaled_axes, scaled_extents); + scaled.valid = scaled.box.isValid(); + scaled.volume = compute_obb_volume(scaled_extents); + + return scaled; +} + +std::string make_ellipsoid_vtk_filename(const std::string& prefix, const std::string& variant) +{ + return axom::fmt::format("{}_{}.vtk", axom::utilities::string::removeSuffix(prefix, ".vtk"), variant); +} + +Point3D transform_unit_sphere_point(const EllipsoidFit& fit, double x, double y, double z) +{ + Point3D point = fit.centroid; + const double local_coords[3] {x, y, z}; + + for(int axis = 0; axis < 3; ++axis) + { + for(int dim = 0; dim < 3; ++dim) + { + point[dim] += fit.radii[axis] * local_coords[axis] * fit.axes[axis][dim]; + } + } + + return point; +} + +bool write_ellipsoid_vtk(const EllipsoidFit& fit, + const std::string& file_path, + int theta_resolution = 48, + int phi_resolution = 24) +{ + if(!fit.valid || file_path.empty()) + { + return false; + } + + SLIC_ASSERT(theta_resolution >= 3); + SLIC_ASSERT(phi_resolution >= 4); + + const int num_rings = phi_resolution - 2; + const int total_nodes = 2 + theta_resolution * num_rings; + const int total_cells = 2 * theta_resolution * (phi_resolution - 2); + + TriMesh mesh(3, mint::TRIANGLE); + mesh.reserve(total_nodes, total_cells); + + auto append_node = [&mesh, &fit](double x, double y, double z) { + const Point3D point = transform_unit_sphere_point(fit, x, y, z); + mesh.appendNode(point[0], point[1], point[2]); + }; + + append_node(0.0, 0.0, 1.0); + append_node(0.0, 0.0, -1.0); + + for(int i = 0; i < theta_resolution; ++i) + { + const double theta = 2.0 * M_PI * static_cast(i) / theta_resolution; + for(int j = 1; j <= num_rings; ++j) + { + const double phi = M_PI * static_cast(j) / (phi_resolution - 1); + const double sin_phi = std::sin(phi); + append_node(std::cos(theta) * sin_phi, std::sin(theta) * sin_phi, std::cos(phi)); + } + } + + auto ring_node = [num_rings](int theta_idx, int ring_idx) { + return 2 + theta_idx * num_rings + ring_idx; + }; + + axom::IndexType cell[3]; + + for(int i = 0; i < theta_resolution; ++i) + { + const int next_i = (i + 1) % theta_resolution; + cell[0] = 0; + cell[1] = ring_node(next_i, 0); + cell[2] = ring_node(i, 0); + mesh.appendCell(cell); + } + + for(int ring = 0; ring < num_rings - 1; ++ring) + { + for(int i = 0; i < theta_resolution; ++i) + { + const int next_i = (i + 1) % theta_resolution; + cell[0] = ring_node(i, ring); + cell[1] = ring_node(i, ring + 1); + cell[2] = ring_node(next_i, ring); + mesh.appendCell(cell); + + cell[0] = ring_node(next_i, ring); + cell[1] = ring_node(i, ring + 1); + cell[2] = ring_node(next_i, ring + 1); + mesh.appendCell(cell); + } + } + + const int last_ring = num_rings - 1; + for(int i = 0; i < theta_resolution; ++i) + { + const int next_i = (i + 1) % theta_resolution; + cell[0] = 1; + cell[1] = ring_node(i, last_ring); + cell[2] = ring_node(next_i, last_ring); + mesh.appendCell(cell); + } + + return mint::write_vtk(&mesh, file_path) == 0; +} + +std::string format_real(double value) { return axom::fmt::format("{:.16e}", value); } + +std::string format_point_inline(const Point3D& point) +{ + return axom::fmt::format("[{}, {}, {}]", + format_real(point[0]), + format_real(point[1]), + format_real(point[2])); +} + +std::string format_vector_inline(const Vector3D& vector) +{ + return axom::fmt::format("[{}, {}, {}]", + format_real(vector[0]), + format_real(vector[1]), + format_real(vector[2])); +} + +std::string format_triplet_inline(double a, double b, double c) +{ + return axom::fmt::format("[{}, {}, {}]", format_real(a), format_real(b), format_real(c)); +} + +void append_line(std::string& output, int indent, const std::string& line) +{ + output += std::string(indent * 2, ' '); + output += line; + output += '\n'; +} + +void append_inertia_tensor_yaml(std::string& output, + int indent, + const char* key, + const InertiaTensor& tensor) +{ + append_line(output, indent, axom::fmt::format("{}:", key)); + append_line(output, indent + 1, axom::fmt::format("xx: {}", format_real(tensor.xx))); + append_line(output, indent + 1, axom::fmt::format("yy: {}", format_real(tensor.yy))); + append_line(output, indent + 1, axom::fmt::format("zz: {}", format_real(tensor.zz))); + append_line(output, indent + 1, axom::fmt::format("xy: {}", format_real(tensor.xy))); + append_line(output, indent + 1, axom::fmt::format("xz: {}", format_real(tensor.xz))); + append_line(output, indent + 1, axom::fmt::format("yz: {}", format_real(tensor.yz))); +} + +void append_axes_yaml(std::string& output, int indent, const Vector3D (&axes)[3]) +{ + append_line(output, indent, "axes:"); + for(int i = 0; i < 3; ++i) + { + append_line(output, indent + 1, axom::fmt::format("- {}", format_vector_inline(axes[i]))); + } +} + +void append_axes_yaml(std::string& output, int indent, const Vector3D* axes) +{ + append_line(output, indent, "axes:"); + for(int i = 0; i < 3; ++i) + { + append_line(output, indent + 1, axom::fmt::format("- {}", format_vector_inline(axes[i]))); + } +} + +void append_moment_entries_yaml(std::string& output, int indent, const MomentSet& moments) +{ + append_line(output, indent, "raw_moments:"); + append_line(output, + indent + 1, + axom::fmt::format("measure: {}", format_real(get_moment(moments, 0, 0, 0)))); + + if(moments.requested_entries.empty()) + { + append_line(output, indent + 1, "entries: []"); + return; + } + + append_line(output, indent + 1, "entries:"); for(const auto& entry : moments.requested_entries) { - SLIC_INFO(axom::fmt::format("{}_MOMENT degree={} exponents=({},{},{}) value={:.16e}", - label, - entry.index.degree, - entry.index.px, - entry.index.py, - entry.index.pz, - entry.value)); + append_line(output, indent + 2, axom::fmt::format("- degree: {}", entry.index.degree)); + append_line( + output, + indent + 3, + axom::fmt::format("exponents: [{}, {}, {}]", entry.index.px, entry.index.py, entry.index.pz)); + append_line(output, indent + 3, axom::fmt::format("value: {}", format_real(entry.value))); } } -void log_mass_properties(const char* label, const MassProperties& props) +void append_mass_properties_yaml(std::string& output, int indent, const MassProperties& props) { + append_line(output, indent, "derived:"); if(!props.valid) { - SLIC_INFO(axom::fmt::format("{}_PROPERTIES unavailable=measure_is_zero", label)); + append_line(output, indent + 1, "available: false"); + append_line(output, indent + 1, "reason: measure_is_zero"); + return; + } + + append_line(output, indent + 1, "available: true"); + append_line(output, + indent + 1, + axom::fmt::format("centroid: {}", format_point_inline(props.centroid))); + append_inertia_tensor_yaml(output, indent + 1, "inertia_origin", props.inertia_origin); + append_inertia_tensor_yaml(output, indent + 1, "inertia_centroid", props.inertia_centroid); +} + +void append_principal_frame_yaml(std::string& output, int indent, const PrincipalFrame& frame) +{ + append_line(output, indent, "principal_frame:"); + if(!frame.valid) + { + append_line(output, indent + 1, "available: false"); + append_line(output, indent + 1, "reason: eigensolve_failed_or_measure_is_zero"); + return; + } + + append_line(output, indent + 1, "available: true"); + append_line(output, + indent + 1, + axom::fmt::format("principal_inertia: {}", + format_triplet_inline(frame.principal_inertia[0], + frame.principal_inertia[1], + frame.principal_inertia[2]))); + append_line(output, + indent + 1, + axom::fmt::format("principal_second_moments: {}", + format_triplet_inline(frame.principal_second_moments[0], + frame.principal_second_moments[1], + frame.principal_second_moments[2]))); + append_axes_yaml(output, indent + 1, &frame.axes[0]); +} + +void append_ellipsoid_fit_yaml(std::string& output, + int indent, + const char* key, + const EllipsoidFit& fit, + double reference_volume, + const char* assumption, + const std::string& vtk_file) +{ + append_line(output, indent, axom::fmt::format("{}:", key)); + if(!fit.valid) + { + append_line(output, indent + 1, "available: false"); + append_line(output, indent + 1, "reason: invalid_second_moments"); + return; + } + + append_line(output, indent + 1, "available: true"); + if(assumption != nullptr) + { + append_line(output, indent + 1, axom::fmt::format("assumption: '{}'", assumption)); + } + if(!vtk_file.empty()) + { + append_line(output, indent + 1, axom::fmt::format("vtk_file: '{}'", vtk_file)); + } + append_line(output, indent + 1, axom::fmt::format("center: {}", format_point_inline(fit.centroid))); + append_line(output, + indent + 1, + axom::fmt::format("semiaxes: {}", + format_triplet_inline(fit.radii[0], fit.radii[1], fit.radii[2]))); + append_axes_yaml(output, indent + 1, &fit.axes[0]); + append_line(output, indent + 1, axom::fmt::format("geometric_volume: {}", format_real(fit.volume))); + append_line(output, + indent + 1, + axom::fmt::format("volume_ratio: {}", + format_real(compute_volume_ratio(fit.volume, reference_volume)))); + append_line( + output, + indent + 1, + axom::fmt::format("effective_density: {}", + format_real(compute_effective_density(reference_volume, fit.volume)))); +} + +void append_obb_fit_yaml(std::string& output, + int indent, + const char* key, + const ObbFit& fit, + double reference_volume, + const char* assumption) +{ + append_line(output, indent, axom::fmt::format("{}:", key)); + if(!fit.valid) + { + append_line(output, indent + 1, "available: false"); + append_line(output, indent + 1, "reason: invalid_second_moments"); return; } - SLIC_INFO(axom::fmt::format("{}_CENTROID x={:.16e} y={:.16e} z={:.16e}", - label, - props.centroid[0], - props.centroid[1], - props.centroid[2])); - SLIC_INFO(axom::fmt::format( - "{}_INERTIA_ORIGIN xx={:.16e} yy={:.16e} zz={:.16e} xy={:.16e} xz={:.16e} yz={:.16e}", - label, - props.inertia_origin.xx, - props.inertia_origin.yy, - props.inertia_origin.zz, - props.inertia_origin.xy, - props.inertia_origin.xz, - props.inertia_origin.yz)); - SLIC_INFO(axom::fmt::format( - "{}_INERTIA_CENTROID xx={:.16e} yy={:.16e} zz={:.16e} xy={:.16e} xz={:.16e} yz={:.16e}", - label, - props.inertia_centroid.xx, - props.inertia_centroid.yy, - props.inertia_centroid.zz, - props.inertia_centroid.xy, - props.inertia_centroid.xz, - props.inertia_centroid.yz)); + const auto& centroid = fit.box.getCentroid(); + const auto& extents = fit.box.getExtents(); + const auto* axes = fit.box.getAxes(); + + append_line(output, indent + 1, "available: true"); + if(assumption != nullptr) + { + append_line(output, indent + 1, axom::fmt::format("assumption: '{}'", assumption)); + } + append_line(output, indent + 1, axom::fmt::format("center: {}", format_point_inline(centroid))); + append_line( + output, + indent + 1, + axom::fmt::format("extents: {}", format_triplet_inline(extents[0], extents[1], extents[2]))); + append_axes_yaml(output, indent + 1, axes); + append_line(output, indent + 1, axom::fmt::format("geometric_volume: {}", format_real(fit.volume))); + append_line(output, + indent + 1, + axom::fmt::format("volume_ratio: {}", + format_real(compute_volume_ratio(fit.volume, reference_volume)))); + append_line( + output, + indent + 1, + axom::fmt::format("effective_density: {}", + format_real(compute_effective_density(reference_volume, fit.volume)))); +} + +void append_surface_yaml(std::string& output, + int indent, + const MomentSet& moments, + const MassProperties& props) +{ + append_line(output, indent, "surface:"); + append_moment_entries_yaml(output, indent + 1, moments); + append_mass_properties_yaml(output, indent + 1, props); +} + +void append_volume_yaml(std::string& output, + int indent, + const MomentSet& moments, + const MassProperties& props, + const PrincipalFrame& frame, + const EllipsoidFit& inertia_matched_ellipsoid, + const EllipsoidFit& same_volume_ellipsoid, + const ObbFit& inertia_matched_obb, + const ObbFit& same_volume_obb, + const std::string& inertia_matched_ellipsoid_vtk_file, + const std::string& same_volume_ellipsoid_vtk_file) +{ + append_line(output, indent, "volume:"); + append_line(output, indent + 1, "assumptions:"); + append_line( + output, + indent + 2, + "- 'volume properties assume the STEP model is a closed, consistently oriented boundary'"); + append_line(output, + indent + 2, + "- 'inertia_matched fits reproduce the 0th, 1st, and 2nd moments but may not match " + "the geometric volume at unit density'"); + append_line(output, + indent + 2, + "- 'same_volume_scaled fits uniformly scale the inertia_matched shape to match the " + "geometric volume while preserving principal directions and aspect ratios'"); + append_line(output, + indent + 2, + "- 'oriented_boxes reported here are moment-based proxies and are not guaranteed to " + "bound the model'"); + + append_moment_entries_yaml(output, indent + 1, moments); + append_mass_properties_yaml(output, indent + 1, props); + append_principal_frame_yaml(output, indent + 1, frame); + + append_line(output, indent + 1, "fit_proxies:"); + append_line(output, indent + 2, "ellipsoids:"); + append_ellipsoid_fit_yaml(output, + indent + 3, + "inertia_matched", + inertia_matched_ellipsoid, + props.measure, + nullptr, + inertia_matched_ellipsoid_vtk_file); + append_ellipsoid_fit_yaml(output, + indent + 3, + "same_volume_scaled", + same_volume_ellipsoid, + props.measure, + "uniformly scaled from the inertia_matched ellipsoid", + same_volume_ellipsoid_vtk_file); + + append_line(output, indent + 2, "oriented_boxes:"); + append_obb_fit_yaml(output, indent + 3, "inertia_matched", inertia_matched_obb, props.measure, nullptr); + append_obb_fit_yaml(output, + indent + 3, + "same_volume_scaled", + same_volume_obb, + props.measure, + "uniformly scaled from the inertia_matched oriented box"); +} + +std::string build_results_yaml(const std::string& input_file, + int patch_count, + const std::string& units, + int requested_order, + int computed_order, + int quadrature_order, + IntegralMode integral_mode, + bool has_surface, + const MomentSet& surface_moments, + const MassProperties& surface_props, + bool has_volume, + const MomentSet& volume_moments, + const MassProperties& volume_props, + const PrincipalFrame& volume_frame, + const EllipsoidFit& inertia_matched_ellipsoid, + const EllipsoidFit& same_volume_ellipsoid, + const ObbFit& inertia_matched_obb, + const ObbFit& same_volume_obb, + const std::string& inertia_matched_ellipsoid_vtk_file, + const std::string& same_volume_ellipsoid_vtk_file) +{ + std::string output; + append_line(output, 0, "results:"); + + append_line(output, 1, "model:"); + append_line(output, 2, axom::fmt::format("file: '{}'", input_file)); + append_line(output, 2, axom::fmt::format("patches: {}", patch_count)); + append_line(output, 2, axom::fmt::format("units: '{}'", units)); + + append_line(output, 1, "config:"); + append_line(output, 2, axom::fmt::format("requested_order: {}", requested_order)); + append_line(output, 2, axom::fmt::format("computed_order: {}", computed_order)); + append_line(output, 2, axom::fmt::format("quadrature_order: {}", quadrature_order)); + append_line(output, 2, axom::fmt::format("integral: '{}'", integral_mode_name(integral_mode))); + + append_line(output, 1, "summary:"); + if(has_surface) + { + append_line(output, + 2, + axom::fmt::format("surface_measure: {}", format_real(surface_props.measure))); + } + if(has_volume) + { + append_line(output, 2, axom::fmt::format("volume_measure: {}", format_real(volume_props.measure))); + } + + if(has_surface) + { + append_surface_yaml(output, 1, surface_moments, surface_props); + } + + if(has_volume) + { + append_volume_yaml(output, + 1, + volume_moments, + volume_props, + volume_frame, + inertia_matched_ellipsoid, + same_volume_ellipsoid, + inertia_matched_obb, + same_volume_obb, + inertia_matched_ellipsoid_vtk_file, + same_volume_ellipsoid_vtk_file); + } + + return output; } } // namespace @@ -290,11 +941,13 @@ int main(int argc, char** argv) int quadrature_order {0}; bool verbose {false}; bool validate_model {false}; + std::string ellipsoid_vtk_prefix; std::string annotationMode {"none"}; IntegralMode integral_mode {IntegralMode::BOTH}; axom::CLI::App app { - "Load a STEP model and compute geometric moments, centroids, and inertia tensors."}; + "Load a STEP model and compute geometric moments, centroids, inertia tensors, and volume-based " + "fit proxies."}; app.add_option("-f,--file", input_file) ->description("Input STEP file") ->required() @@ -313,6 +966,10 @@ int main(int argc, char** argv) ->transform(axom::CLI::CheckedTransformer(s_validIntegralModes)); app.add_flag("-v,--verbose", verbose, "Enable verbose output")->capture_default_str(); app.add_flag("--validate", validate_model, "Run STEP model validation checks")->capture_default_str(); + app.add_option("--ellipsoid-vtk-prefix", ellipsoid_vtk_prefix) + ->description( + "Write the two volume ellipsoid fits to '_inertia_matched_ellipsoid.vtk' and " + "'_same_volume_scaled_ellipsoid.vtk'"); #ifdef AXOM_USE_CALIPER app.add_option("--caliper", annotationMode) ->description( @@ -374,26 +1031,17 @@ int main(int argc, char** argv) SLIC_INFO(reader.getBRepStats()); } - if(should_compute_volume(integral_mode)) - { - SLIC_WARNING( - "Volume properties assume the STEP model is a closed, consistently oriented boundary."); - } - - SLIC_INFO(axom::fmt::format("MODEL file='{}' patches={} units='{}'", - input_file, - patches.size(), - reader.getFileUnits())); - SLIC_INFO(axom::fmt::format("CONFIG requested_order={} computed_order={} npts={} integral={}", - max_degree, - computed_max_degree, - quadrature_order, - integral_mode_name(integral_mode))); - MomentSet surface_moments; MomentSet volume_moments; MassProperties surface_props; MassProperties volume_props; + PrincipalFrame volume_frame; + EllipsoidFit inertia_matched_ellipsoid; + EllipsoidFit same_volume_ellipsoid; + ObbFit inertia_matched_obb; + ObbFit same_volume_obb; + std::string inertia_matched_ellipsoid_vtk_file; + std::string same_volume_ellipsoid_vtk_file; if(should_compute_surface(integral_mode)) { @@ -413,6 +1061,48 @@ int main(int argc, char** argv) return primal::evaluate_volume_integral(patches, integrand, quadrature_order); }); volume_props = compute_mass_properties(volume_moments); + + if(volume_props.valid) + { + AXOM_ANNOTATE_SCOPE("compute volume fit proxies"); + volume_frame = compute_principal_frame(volume_props); + inertia_matched_ellipsoid = compute_inertia_matched_ellipsoid(volume_frame); + same_volume_ellipsoid = + scale_ellipsoid_to_volume(inertia_matched_ellipsoid, volume_props.measure); + inertia_matched_obb = compute_inertia_matched_obb(volume_frame); + same_volume_obb = scale_obb_to_volume(inertia_matched_obb, volume_props.measure); + } + } + + if(!ellipsoid_vtk_prefix.empty()) + { + AXOM_ANNOTATE_SCOPE("write ellipsoid vtk"); + if(!should_compute_volume(integral_mode)) + { + SLIC_WARNING("Ellipsoid VTK export requested, but volume integrals were not computed."); + } + else if(!inertia_matched_ellipsoid.valid || !same_volume_ellipsoid.valid) + { + SLIC_WARNING( + "Ellipsoid VTK export requested, but the volume ellipsoid fits are unavailable."); + } + else + { + inertia_matched_ellipsoid_vtk_file = + make_ellipsoid_vtk_filename(ellipsoid_vtk_prefix, "inertia_matched_ellipsoid"); + same_volume_ellipsoid_vtk_file = + make_ellipsoid_vtk_filename(ellipsoid_vtk_prefix, "same_volume_scaled_ellipsoid"); + + const bool wrote_inertia = + write_ellipsoid_vtk(inertia_matched_ellipsoid, inertia_matched_ellipsoid_vtk_file); + const bool wrote_same_volume = + write_ellipsoid_vtk(same_volume_ellipsoid, same_volume_ellipsoid_vtk_file); + + if(!wrote_inertia || !wrote_same_volume) + { + SLIC_WARNING("Failed to write one or more ellipsoid VTK files."); + } + } } { @@ -428,17 +1118,26 @@ int main(int argc, char** argv) } SLIC_INFO(summary); - if(should_compute_surface(integral_mode)) - { - log_mass_properties("SURFACE", surface_props); - log_moment_entries("SURFACE", surface_moments); - } - - if(should_compute_volume(integral_mode)) - { - log_mass_properties("VOLUME", volume_props); - log_moment_entries("VOLUME", volume_moments); - } + SLIC_INFO(build_results_yaml(input_file, + static_cast(patches.size()), + reader.getFileUnits(), + max_degree, + computed_max_degree, + quadrature_order, + integral_mode, + should_compute_surface(integral_mode), + surface_moments, + surface_props, + should_compute_volume(integral_mode), + volume_moments, + volume_props, + volume_frame, + inertia_matched_ellipsoid, + same_volume_ellipsoid, + inertia_matched_obb, + same_volume_obb, + inertia_matched_ellipsoid_vtk_file, + same_volume_ellipsoid_vtk_file)); } return 0; From 15dee3e5778dced0ed7e581e6bfc0ed560276802 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Sun, 22 Mar 2026 21:02:51 -0700 Subject: [PATCH 051/986] Refactors output to use conduit, when available --- .../quest/examples/quest_step_moments.cpp | 766 +++++++++++------- 1 file changed, 487 insertions(+), 279 deletions(-) diff --git a/src/axom/quest/examples/quest_step_moments.cpp b/src/axom/quest/examples/quest_step_moments.cpp index d7406ebd61..f333592d44 100644 --- a/src/axom/quest/examples/quest_step_moments.cpp +++ b/src/axom/quest/examples/quest_step_moments.cpp @@ -30,11 +30,17 @@ #include "axom/CLI11.hpp" #include "axom/fmt.hpp" +#ifdef AXOM_USE_CONDUIT + #include "conduit.hpp" +#endif + #include #include #include #include #include +#include +#include #include namespace mint = axom::mint; @@ -576,190 +582,403 @@ bool write_ellipsoid_vtk(const EllipsoidFit& fit, std::string format_real(double value) { return axom::fmt::format("{:.16e}", value); } -std::string format_point_inline(const Point3D& point) +std::string quote_yaml_string(const std::string& value) +{ + std::string escaped; + escaped.reserve(value.size()); + + for(char ch : value) + { + escaped += ch; + if(ch == '\'') + { + escaped += '\''; + } + } + + return axom::fmt::format("'{}'", escaped); +} + +std::string join_path(const std::string& prefix, const std::string& key) { - return axom::fmt::format("[{}, {}, {}]", - format_real(point[0]), - format_real(point[1]), - format_real(point[2])); + return prefix.empty() ? key : axom::fmt::format("{}/{}", prefix, key); } -std::string format_vector_inline(const Vector3D& vector) +class ResultsStore +{ +public: + void add(const std::string& prefix, const std::string& key, const std::string& value) + { + set_string(join_path(prefix, key), value); + } + + void add(const std::string& prefix, const std::string& key, const char* value) + { + set_string(join_path(prefix, key), value); + } + + void add(const std::string& prefix, const std::string& key, double value) + { + set_real(join_path(prefix, key), value); + } + + void add(const std::string& prefix, const std::string& key, int value) + { + set_integer(join_path(prefix, key), value); + } + + void add(const std::string& prefix, const std::string& key, bool value) + { + set_boolean(join_path(prefix, key), value); + } + + std::string to_yaml() const + { +#ifdef AXOM_USE_CONDUIT + return m_root.to_yaml(); +#else + std::string output; + append_yaml(output, m_root, 0); + return output; +#endif + } + +private: +#ifdef AXOM_USE_CONDUIT + conduit::Node m_root; + + void set_string(const std::string& path, const std::string& value) { m_root[path] = value; } + + void set_string(const std::string& path, const char* value) { m_root[path].set_string(value); } + + void set_real(const std::string& path, double value) { m_root[path] = value; } + + void set_integer(const std::string& path, int value) { m_root[path] = value; } + + void set_boolean(const std::string& path, bool value) + { + m_root[path].set_string(value ? "true" : "false"); + } +#else + using ScalarValue = std::variant; + + struct TreeNode + { + bool has_scalar {false}; + ScalarValue scalar_value {std::string {}}; + std::unordered_map children; + std::vector child_order; + + TreeNode& fetch_child(const std::string& key) + { + auto it = children.find(key); + if(it == children.end()) + { + child_order.push_back(key); + it = children.emplace(key, TreeNode {}).first; + } + + return it->second; + } + }; + + TreeNode m_root; + + TreeNode& fetch_path(const std::string& path) + { + TreeNode* node = &m_root; + std::size_t pos = 0; + + while(pos < path.size()) + { + const std::size_t next = path.find('/', pos); + const std::string key = + path.substr(pos, next == std::string::npos ? std::string::npos : next - pos); + if(!key.empty()) + { + node = &node->fetch_child(key); + } + + if(next == std::string::npos) + { + break; + } + pos = next + 1; + } + + return *node; + } + + void set_string(const std::string& path, const std::string& value) + { + TreeNode& node = fetch_path(path); + node.has_scalar = true; + node.scalar_value = value; + } + + void set_string(const std::string& path, const char* value) + { + set_string(path, std::string(value)); + } + + void set_real(const std::string& path, double value) + { + TreeNode& node = fetch_path(path); + node.has_scalar = true; + node.scalar_value = value; + } + + void set_integer(const std::string& path, int value) + { + TreeNode& node = fetch_path(path); + node.has_scalar = true; + node.scalar_value = value; + } + + void set_boolean(const std::string& path, bool value) + { + TreeNode& node = fetch_path(path); + node.has_scalar = true; + node.scalar_value = value; + } + + static std::string format_scalar(const ScalarValue& value) + { + if(const auto* string_value = std::get_if(&value)) + { + return quote_yaml_string(*string_value); + } + if(const auto* integer_value = std::get_if(&value)) + { + return axom::fmt::format("{}", *integer_value); + } + if(const auto* real_value = std::get_if(&value)) + { + return format_real(*real_value); + } + + return std::get(value) ? "true" : "false"; + } + + static void append_yaml(std::string& output, const TreeNode& node, int indent) + { + for(const auto& key : node.child_order) + { + const auto it = node.children.find(key); + SLIC_ASSERT(it != node.children.end()); + const TreeNode& child = it->second; + + output += std::string(indent * 2, ' '); + output += key; + output += ':'; + + if(child.children.empty()) + { + if(child.has_scalar) + { + output += ' '; + output += format_scalar(child.scalar_value); + } + output += '\n'; + continue; + } + + output += '\n'; + if(child.has_scalar) + { + output += std::string((indent + 1) * 2, ' '); + output += "value: "; + output += format_scalar(child.scalar_value); + output += '\n'; + } + append_yaml(output, child, indent + 1); + } + } +#endif +}; + +void add_xyz_triplet(ResultsStore& results, + const std::string& prefix, + const std::string& key, + double x, + double y, + double z) { - return axom::fmt::format("[{}, {}, {}]", - format_real(vector[0]), - format_real(vector[1]), - format_real(vector[2])); + const std::string triplet_prefix = join_path(prefix, key); + results.add(triplet_prefix, "x", x); + results.add(triplet_prefix, "y", y); + results.add(triplet_prefix, "z", z); } -std::string format_triplet_inline(double a, double b, double c) +void add_point(ResultsStore& results, + const std::string& prefix, + const std::string& key, + const Point3D& point) { - return axom::fmt::format("[{}, {}, {}]", format_real(a), format_real(b), format_real(c)); + add_xyz_triplet(results, prefix, key, point[0], point[1], point[2]); } -void append_line(std::string& output, int indent, const std::string& line) +void add_vector(ResultsStore& results, + const std::string& prefix, + const std::string& key, + const Vector3D& vector) { - output += std::string(indent * 2, ' '); - output += line; - output += '\n'; + add_xyz_triplet(results, prefix, key, vector[0], vector[1], vector[2]); } -void append_inertia_tensor_yaml(std::string& output, - int indent, - const char* key, - const InertiaTensor& tensor) +void add_axis_scalars(ResultsStore& results, + const std::string& prefix, + const std::string& key, + double a, + double b, + double c) { - append_line(output, indent, axom::fmt::format("{}:", key)); - append_line(output, indent + 1, axom::fmt::format("xx: {}", format_real(tensor.xx))); - append_line(output, indent + 1, axom::fmt::format("yy: {}", format_real(tensor.yy))); - append_line(output, indent + 1, axom::fmt::format("zz: {}", format_real(tensor.zz))); - append_line(output, indent + 1, axom::fmt::format("xy: {}", format_real(tensor.xy))); - append_line(output, indent + 1, axom::fmt::format("xz: {}", format_real(tensor.xz))); - append_line(output, indent + 1, axom::fmt::format("yz: {}", format_real(tensor.yz))); + const std::string values_prefix = join_path(prefix, key); + results.add(values_prefix, "axis_0", a); + results.add(values_prefix, "axis_1", b); + results.add(values_prefix, "axis_2", c); } -void append_axes_yaml(std::string& output, int indent, const Vector3D (&axes)[3]) +void add_axes(ResultsStore& results, + const std::string& prefix, + const std::string& key, + const Vector3D* axes) { - append_line(output, indent, "axes:"); + const std::string axes_prefix = join_path(prefix, key); for(int i = 0; i < 3; ++i) { - append_line(output, indent + 1, axom::fmt::format("- {}", format_vector_inline(axes[i]))); + add_vector(results, axes_prefix, axom::fmt::format("axis_{}", i), axes[i]); } } -void append_axes_yaml(std::string& output, int indent, const Vector3D* axes) +void add_inertia_tensor(ResultsStore& results, + const std::string& prefix, + const std::string& key, + const InertiaTensor& tensor) { - append_line(output, indent, "axes:"); - for(int i = 0; i < 3; ++i) - { - append_line(output, indent + 1, axom::fmt::format("- {}", format_vector_inline(axes[i]))); - } + const std::string tensor_prefix = join_path(prefix, key); + results.add(tensor_prefix, "xx", tensor.xx); + results.add(tensor_prefix, "yy", tensor.yy); + results.add(tensor_prefix, "zz", tensor.zz); + results.add(tensor_prefix, "xy", tensor.xy); + results.add(tensor_prefix, "xz", tensor.xz); + results.add(tensor_prefix, "yz", tensor.yz); } -void append_moment_entries_yaml(std::string& output, int indent, const MomentSet& moments) +void populate_moment_entries(ResultsStore& results, const std::string& prefix, const MomentSet& moments) { - append_line(output, indent, "raw_moments:"); - append_line(output, - indent + 1, - axom::fmt::format("measure: {}", format_real(get_moment(moments, 0, 0, 0)))); + const std::string raw_prefix = join_path(prefix, "raw_moments"); + results.add(raw_prefix, "measure", get_moment(moments, 0, 0, 0)); - if(moments.requested_entries.empty()) - { - append_line(output, indent + 1, "entries: []"); - return; - } + const std::string entries_prefix = join_path(raw_prefix, "entries"); + results.add(entries_prefix, "count", static_cast(moments.requested_entries.size())); - append_line(output, indent + 1, "entries:"); for(const auto& entry : moments.requested_entries) { - append_line(output, indent + 2, axom::fmt::format("- degree: {}", entry.index.degree)); - append_line( - output, - indent + 3, - axom::fmt::format("exponents: [{}, {}, {}]", entry.index.px, entry.index.py, entry.index.pz)); - append_line(output, indent + 3, axom::fmt::format("value: {}", format_real(entry.value))); + const std::string moment_prefix = + join_path(entries_prefix, + axom::fmt::format("m_{}_{}_{}", entry.index.px, entry.index.py, entry.index.pz)); + results.add(moment_prefix, "degree", entry.index.degree); + + const std::string exponents_prefix = join_path(moment_prefix, "exponents"); + results.add(exponents_prefix, "x", entry.index.px); + results.add(exponents_prefix, "y", entry.index.py); + results.add(exponents_prefix, "z", entry.index.pz); + results.add(moment_prefix, "value", entry.value); } } -void append_mass_properties_yaml(std::string& output, int indent, const MassProperties& props) +void populate_mass_properties(ResultsStore& results, + const std::string& prefix, + const MassProperties& props) { - append_line(output, indent, "derived:"); + const std::string derived_prefix = join_path(prefix, "derived"); + results.add(derived_prefix, "available", props.valid); if(!props.valid) { - append_line(output, indent + 1, "available: false"); - append_line(output, indent + 1, "reason: measure_is_zero"); + results.add(derived_prefix, "reason", "measure_is_zero"); return; } - append_line(output, indent + 1, "available: true"); - append_line(output, - indent + 1, - axom::fmt::format("centroid: {}", format_point_inline(props.centroid))); - append_inertia_tensor_yaml(output, indent + 1, "inertia_origin", props.inertia_origin); - append_inertia_tensor_yaml(output, indent + 1, "inertia_centroid", props.inertia_centroid); + add_point(results, derived_prefix, "centroid", props.centroid); + add_inertia_tensor(results, derived_prefix, "inertia_origin", props.inertia_origin); + add_inertia_tensor(results, derived_prefix, "inertia_centroid", props.inertia_centroid); } -void append_principal_frame_yaml(std::string& output, int indent, const PrincipalFrame& frame) +void populate_principal_frame(ResultsStore& results, + const std::string& prefix, + const PrincipalFrame& frame) { - append_line(output, indent, "principal_frame:"); + const std::string frame_prefix = join_path(prefix, "principal_frame"); + results.add(frame_prefix, "available", frame.valid); if(!frame.valid) { - append_line(output, indent + 1, "available: false"); - append_line(output, indent + 1, "reason: eigensolve_failed_or_measure_is_zero"); + results.add(frame_prefix, "reason", "eigensolve_failed_or_measure_is_zero"); return; } - append_line(output, indent + 1, "available: true"); - append_line(output, - indent + 1, - axom::fmt::format("principal_inertia: {}", - format_triplet_inline(frame.principal_inertia[0], - frame.principal_inertia[1], - frame.principal_inertia[2]))); - append_line(output, - indent + 1, - axom::fmt::format("principal_second_moments: {}", - format_triplet_inline(frame.principal_second_moments[0], - frame.principal_second_moments[1], - frame.principal_second_moments[2]))); - append_axes_yaml(output, indent + 1, &frame.axes[0]); + add_axis_scalars(results, + frame_prefix, + "principal_inertia", + frame.principal_inertia[0], + frame.principal_inertia[1], + frame.principal_inertia[2]); + add_axis_scalars(results, + frame_prefix, + "principal_second_moments", + frame.principal_second_moments[0], + frame.principal_second_moments[1], + frame.principal_second_moments[2]); + add_axes(results, frame_prefix, "axes", &frame.axes[0]); } -void append_ellipsoid_fit_yaml(std::string& output, - int indent, - const char* key, - const EllipsoidFit& fit, - double reference_volume, - const char* assumption, - const std::string& vtk_file) +void populate_ellipsoid_fit(ResultsStore& results, + const std::string& prefix, + const std::string& key, + const EllipsoidFit& fit, + double reference_volume, + const char* assumption, + const std::string& vtk_file) { - append_line(output, indent, axom::fmt::format("{}:", key)); + const std::string fit_prefix = join_path(prefix, key); + results.add(fit_prefix, "available", fit.valid); if(!fit.valid) { - append_line(output, indent + 1, "available: false"); - append_line(output, indent + 1, "reason: invalid_second_moments"); + results.add(fit_prefix, "reason", "invalid_second_moments"); return; } - append_line(output, indent + 1, "available: true"); if(assumption != nullptr) { - append_line(output, indent + 1, axom::fmt::format("assumption: '{}'", assumption)); + results.add(fit_prefix, "assumption", assumption); } if(!vtk_file.empty()) { - append_line(output, indent + 1, axom::fmt::format("vtk_file: '{}'", vtk_file)); - } - append_line(output, indent + 1, axom::fmt::format("center: {}", format_point_inline(fit.centroid))); - append_line(output, - indent + 1, - axom::fmt::format("semiaxes: {}", - format_triplet_inline(fit.radii[0], fit.radii[1], fit.radii[2]))); - append_axes_yaml(output, indent + 1, &fit.axes[0]); - append_line(output, indent + 1, axom::fmt::format("geometric_volume: {}", format_real(fit.volume))); - append_line(output, - indent + 1, - axom::fmt::format("volume_ratio: {}", - format_real(compute_volume_ratio(fit.volume, reference_volume)))); - append_line( - output, - indent + 1, - axom::fmt::format("effective_density: {}", - format_real(compute_effective_density(reference_volume, fit.volume)))); + results.add(fit_prefix, "vtk_file", vtk_file); + } + + add_point(results, fit_prefix, "center", fit.centroid); + add_axis_scalars(results, fit_prefix, "semiaxes", fit.radii[0], fit.radii[1], fit.radii[2]); + add_axes(results, fit_prefix, "axes", &fit.axes[0]); + results.add(fit_prefix, "geometric_volume", fit.volume); + results.add(fit_prefix, "volume_ratio", compute_volume_ratio(fit.volume, reference_volume)); + results.add(fit_prefix, + "effective_density", + compute_effective_density(reference_volume, fit.volume)); } -void append_obb_fit_yaml(std::string& output, - int indent, - const char* key, - const ObbFit& fit, - double reference_volume, - const char* assumption) +void populate_obb_fit(ResultsStore& results, + const std::string& prefix, + const std::string& key, + const ObbFit& fit, + double reference_volume, + const char* assumption) { - append_line(output, indent, axom::fmt::format("{}:", key)); + const std::string fit_prefix = join_path(prefix, key); + results.add(fit_prefix, "available", fit.valid); if(!fit.valid) { - append_line(output, indent + 1, "available: false"); - append_line(output, indent + 1, "reason: invalid_second_moments"); + results.add(fit_prefix, "reason", "invalid_second_moments"); return; } @@ -767,171 +986,157 @@ void append_obb_fit_yaml(std::string& output, const auto& extents = fit.box.getExtents(); const auto* axes = fit.box.getAxes(); - append_line(output, indent + 1, "available: true"); if(assumption != nullptr) { - append_line(output, indent + 1, axom::fmt::format("assumption: '{}'", assumption)); - } - append_line(output, indent + 1, axom::fmt::format("center: {}", format_point_inline(centroid))); - append_line( - output, - indent + 1, - axom::fmt::format("extents: {}", format_triplet_inline(extents[0], extents[1], extents[2]))); - append_axes_yaml(output, indent + 1, axes); - append_line(output, indent + 1, axom::fmt::format("geometric_volume: {}", format_real(fit.volume))); - append_line(output, - indent + 1, - axom::fmt::format("volume_ratio: {}", - format_real(compute_volume_ratio(fit.volume, reference_volume)))); - append_line( - output, - indent + 1, - axom::fmt::format("effective_density: {}", - format_real(compute_effective_density(reference_volume, fit.volume)))); + results.add(fit_prefix, "assumption", assumption); + } + + add_point(results, fit_prefix, "center", centroid); + add_xyz_triplet(results, fit_prefix, "extents", extents[0], extents[1], extents[2]); + add_axes(results, fit_prefix, "axes", axes); + results.add(fit_prefix, "geometric_volume", fit.volume); + results.add(fit_prefix, "volume_ratio", compute_volume_ratio(fit.volume, reference_volume)); + results.add(fit_prefix, + "effective_density", + compute_effective_density(reference_volume, fit.volume)); } -void append_surface_yaml(std::string& output, - int indent, - const MomentSet& moments, - const MassProperties& props) +void populate_surface_results(ResultsStore& results, + const std::string& prefix, + const MomentSet& moments, + const MassProperties& props) { - append_line(output, indent, "surface:"); - append_moment_entries_yaml(output, indent + 1, moments); - append_mass_properties_yaml(output, indent + 1, props); + const std::string surface_prefix = join_path(prefix, "surface"); + populate_moment_entries(results, surface_prefix, moments); + populate_mass_properties(results, surface_prefix, props); } -void append_volume_yaml(std::string& output, - int indent, - const MomentSet& moments, - const MassProperties& props, - const PrincipalFrame& frame, - const EllipsoidFit& inertia_matched_ellipsoid, - const EllipsoidFit& same_volume_ellipsoid, - const ObbFit& inertia_matched_obb, - const ObbFit& same_volume_obb, - const std::string& inertia_matched_ellipsoid_vtk_file, - const std::string& same_volume_ellipsoid_vtk_file) +void populate_volume_results(ResultsStore& results, + const std::string& prefix, + const MomentSet& moments, + const MassProperties& props, + const PrincipalFrame& frame, + const EllipsoidFit& inertia_matched_ellipsoid, + const EllipsoidFit& same_volume_ellipsoid, + const ObbFit& inertia_matched_obb, + const ObbFit& same_volume_obb, + const std::string& inertia_matched_ellipsoid_vtk_file, + const std::string& same_volume_ellipsoid_vtk_file) { - append_line(output, indent, "volume:"); - append_line(output, indent + 1, "assumptions:"); - append_line( - output, - indent + 2, - "- 'volume properties assume the STEP model is a closed, consistently oriented boundary'"); - append_line(output, - indent + 2, - "- 'inertia_matched fits reproduce the 0th, 1st, and 2nd moments but may not match " - "the geometric volume at unit density'"); - append_line(output, - indent + 2, - "- 'same_volume_scaled fits uniformly scale the inertia_matched shape to match the " - "geometric volume while preserving principal directions and aspect ratios'"); - append_line(output, - indent + 2, - "- 'oriented_boxes reported here are moment-based proxies and are not guaranteed to " - "bound the model'"); - - append_moment_entries_yaml(output, indent + 1, moments); - append_mass_properties_yaml(output, indent + 1, props); - append_principal_frame_yaml(output, indent + 1, frame); - - append_line(output, indent + 1, "fit_proxies:"); - append_line(output, indent + 2, "ellipsoids:"); - append_ellipsoid_fit_yaml(output, - indent + 3, - "inertia_matched", - inertia_matched_ellipsoid, - props.measure, - nullptr, - inertia_matched_ellipsoid_vtk_file); - append_ellipsoid_fit_yaml(output, - indent + 3, - "same_volume_scaled", - same_volume_ellipsoid, - props.measure, - "uniformly scaled from the inertia_matched ellipsoid", - same_volume_ellipsoid_vtk_file); - - append_line(output, indent + 2, "oriented_boxes:"); - append_obb_fit_yaml(output, indent + 3, "inertia_matched", inertia_matched_obb, props.measure, nullptr); - append_obb_fit_yaml(output, - indent + 3, - "same_volume_scaled", - same_volume_obb, - props.measure, - "uniformly scaled from the inertia_matched oriented box"); + const std::string volume_prefix = join_path(prefix, "volume"); + const std::string assumptions_prefix = join_path(volume_prefix, "assumptions"); + results.add( + assumptions_prefix, + "assumption_0", + "volume properties assume the STEP model is a closed, consistently oriented boundary"); + results.add(assumptions_prefix, + "assumption_1", + "inertia_matched fits reproduce the 0th, 1st, and 2nd moments but may not match the " + "geometric volume at unit density"); + results.add(assumptions_prefix, + "assumption_2", + "same_volume_scaled fits uniformly scale the inertia_matched shape to match the " + "geometric volume while preserving principal directions and aspect ratios"); + results.add(assumptions_prefix, + "assumption_3", + "oriented_boxes reported here are moment-based proxies and are not guaranteed to " + "bound the model"); + + populate_moment_entries(results, volume_prefix, moments); + populate_mass_properties(results, volume_prefix, props); + populate_principal_frame(results, volume_prefix, frame); + + const std::string fit_prefix = join_path(volume_prefix, "fit_proxies"); + const std::string ellipsoids_prefix = join_path(fit_prefix, "ellipsoids"); + populate_ellipsoid_fit(results, + ellipsoids_prefix, + "inertia_matched", + inertia_matched_ellipsoid, + props.measure, + nullptr, + inertia_matched_ellipsoid_vtk_file); + populate_ellipsoid_fit(results, + ellipsoids_prefix, + "same_volume_scaled", + same_volume_ellipsoid, + props.measure, + "uniformly scaled from the inertia_matched ellipsoid", + same_volume_ellipsoid_vtk_file); + + const std::string obb_prefix = join_path(fit_prefix, "oriented_boxes"); + populate_obb_fit(results, obb_prefix, "inertia_matched", inertia_matched_obb, props.measure, nullptr); + populate_obb_fit(results, + obb_prefix, + "same_volume_scaled", + same_volume_obb, + props.measure, + "uniformly scaled from the inertia_matched oriented box"); } -std::string build_results_yaml(const std::string& input_file, - int patch_count, - const std::string& units, - int requested_order, - int computed_order, - int quadrature_order, - IntegralMode integral_mode, - bool has_surface, - const MomentSet& surface_moments, - const MassProperties& surface_props, - bool has_volume, - const MomentSet& volume_moments, - const MassProperties& volume_props, - const PrincipalFrame& volume_frame, - const EllipsoidFit& inertia_matched_ellipsoid, - const EllipsoidFit& same_volume_ellipsoid, - const ObbFit& inertia_matched_obb, - const ObbFit& same_volume_obb, - const std::string& inertia_matched_ellipsoid_vtk_file, - const std::string& same_volume_ellipsoid_vtk_file) +void populate_results_store(ResultsStore& results, + const std::string& input_file, + int patch_count, + const std::string& units, + int requested_order, + int computed_order, + int quadrature_order, + IntegralMode integral_mode, + bool has_surface, + const MomentSet& surface_moments, + const MassProperties& surface_props, + bool has_volume, + const MomentSet& volume_moments, + const MassProperties& volume_props, + const PrincipalFrame& volume_frame, + const EllipsoidFit& inertia_matched_ellipsoid, + const EllipsoidFit& same_volume_ellipsoid, + const ObbFit& inertia_matched_obb, + const ObbFit& same_volume_obb, + const std::string& inertia_matched_ellipsoid_vtk_file, + const std::string& same_volume_ellipsoid_vtk_file) { - std::string output; - append_line(output, 0, "results:"); - - append_line(output, 1, "model:"); - append_line(output, 2, axom::fmt::format("file: '{}'", input_file)); - append_line(output, 2, axom::fmt::format("patches: {}", patch_count)); - append_line(output, 2, axom::fmt::format("units: '{}'", units)); - - append_line(output, 1, "config:"); - append_line(output, 2, axom::fmt::format("requested_order: {}", requested_order)); - append_line(output, 2, axom::fmt::format("computed_order: {}", computed_order)); - append_line(output, 2, axom::fmt::format("quadrature_order: {}", quadrature_order)); - append_line(output, 2, axom::fmt::format("integral: '{}'", integral_mode_name(integral_mode))); - - append_line(output, 1, "summary:"); + const std::string root_prefix = "results"; + const std::string model_prefix = join_path(root_prefix, "model"); + results.add(model_prefix, "file", input_file); + results.add(model_prefix, "patches", patch_count); + results.add(model_prefix, "units", units); + + const std::string config_prefix = join_path(root_prefix, "config"); + results.add(config_prefix, "requested_order", requested_order); + results.add(config_prefix, "computed_order", computed_order); + results.add(config_prefix, "quadrature_order", quadrature_order); + results.add(config_prefix, "integral", integral_mode_name(integral_mode)); + + const std::string summary_prefix = join_path(root_prefix, "summary"); if(has_surface) { - append_line(output, - 2, - axom::fmt::format("surface_measure: {}", format_real(surface_props.measure))); + results.add(summary_prefix, "surface_measure", surface_props.measure); } if(has_volume) { - append_line(output, 2, axom::fmt::format("volume_measure: {}", format_real(volume_props.measure))); + results.add(summary_prefix, "volume_measure", volume_props.measure); } if(has_surface) { - append_surface_yaml(output, 1, surface_moments, surface_props); + populate_surface_results(results, root_prefix, surface_moments, surface_props); } if(has_volume) { - append_volume_yaml(output, - 1, - volume_moments, - volume_props, - volume_frame, - inertia_matched_ellipsoid, - same_volume_ellipsoid, - inertia_matched_obb, - same_volume_obb, - inertia_matched_ellipsoid_vtk_file, - same_volume_ellipsoid_vtk_file); + populate_volume_results(results, + root_prefix, + volume_moments, + volume_props, + volume_frame, + inertia_matched_ellipsoid, + same_volume_ellipsoid, + inertia_matched_obb, + same_volume_obb, + inertia_matched_ellipsoid_vtk_file, + same_volume_ellipsoid_vtk_file); } - - return output; } - } // namespace int main(int argc, char** argv) @@ -1118,26 +1323,29 @@ int main(int argc, char** argv) } SLIC_INFO(summary); - SLIC_INFO(build_results_yaml(input_file, - static_cast(patches.size()), - reader.getFileUnits(), - max_degree, - computed_max_degree, - quadrature_order, - integral_mode, - should_compute_surface(integral_mode), - surface_moments, - surface_props, - should_compute_volume(integral_mode), - volume_moments, - volume_props, - volume_frame, - inertia_matched_ellipsoid, - same_volume_ellipsoid, - inertia_matched_obb, - same_volume_obb, - inertia_matched_ellipsoid_vtk_file, - same_volume_ellipsoid_vtk_file)); + ResultsStore results; + populate_results_store(results, + input_file, + static_cast(patches.size()), + reader.getFileUnits(), + max_degree, + computed_max_degree, + quadrature_order, + integral_mode, + should_compute_surface(integral_mode), + surface_moments, + surface_props, + should_compute_volume(integral_mode), + volume_moments, + volume_props, + volume_frame, + inertia_matched_ellipsoid, + same_volume_ellipsoid, + inertia_matched_obb, + same_volume_obb, + inertia_matched_ellipsoid_vtk_file, + same_volume_ellipsoid_vtk_file); + SLIC_INFO(results.to_yaml()); } return 0; From 6fb094cda35e24856d0491c978bf5ad8c9bcc3e2 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Sun, 22 Mar 2026 21:18:13 -0700 Subject: [PATCH 052/986] Cleans up write_ellipsoid function --- .../quest/examples/quest_step_moments.cpp | 144 +++++++++++------- 1 file changed, 87 insertions(+), 57 deletions(-) diff --git a/src/axom/quest/examples/quest_step_moments.cpp b/src/axom/quest/examples/quest_step_moments.cpp index f333592d44..f0a341565d 100644 --- a/src/axom/quest/examples/quest_step_moments.cpp +++ b/src/axom/quest/examples/quest_step_moments.cpp @@ -480,22 +480,6 @@ std::string make_ellipsoid_vtk_filename(const std::string& prefix, const std::st return axom::fmt::format("{}_{}.vtk", axom::utilities::string::removeSuffix(prefix, ".vtk"), variant); } -Point3D transform_unit_sphere_point(const EllipsoidFit& fit, double x, double y, double z) -{ - Point3D point = fit.centroid; - const double local_coords[3] {x, y, z}; - - for(int axis = 0; axis < 3; ++axis) - { - for(int dim = 0; dim < 3; ++dim) - { - point[dim] += fit.radii[axis] * local_coords[axis] * fit.axes[axis][dim]; - } - } - - return point; -} - bool write_ellipsoid_vtk(const EllipsoidFit& fit, const std::string& file_path, int theta_resolution = 48, @@ -516,67 +500,113 @@ bool write_ellipsoid_vtk(const EllipsoidFit& fit, TriMesh mesh(3, mint::TRIANGLE); mesh.reserve(total_nodes, total_cells); - auto append_node = [&mesh, &fit](double x, double y, double z) { - const Point3D point = transform_unit_sphere_point(fit, x, y, z); + axom::IndexType next_node_id = 0; + + auto append_node = [&mesh, &fit, &next_node_id](double x, double y, double z) { + // Map a point on the unit sphere into the ellipsoid's principal-axis frame. + Point3D point = fit.centroid; + const double local_coords[3] {x, y, z}; + for(int axis = 0; axis < 3; ++axis) + { + for(int dim = 0; dim < 3; ++dim) + { + point[dim] += fit.radii[axis] * local_coords[axis] * fit.axes[axis][dim]; + } + } + mesh.appendNode(point[0], point[1], point[2]); + return next_node_id++; }; - append_node(0.0, 0.0, 1.0); - append_node(0.0, 0.0, -1.0); + auto append_ring = [&append_node, theta_resolution](std::vector& ring, double phi) { + ring.clear(); - for(int i = 0; i < theta_resolution; ++i) - { - const double theta = 2.0 * M_PI * static_cast(i) / theta_resolution; - for(int j = 1; j <= num_rings; ++j) + const double sin_phi = std::sin(phi); + const double cos_phi = std::cos(phi); + if(std::abs(sin_phi) <= eps) { - const double phi = M_PI * static_cast(j) / (phi_resolution - 1); - const double sin_phi = std::sin(phi); - append_node(std::cos(theta) * sin_phi, std::sin(theta) * sin_phi, std::cos(phi)); + ring.push_back(append_node(0.0, 0.0, cos_phi)); + return; } - } - auto ring_node = [num_rings](int theta_idx, int ring_idx) { - return 2 + theta_idx * num_rings + ring_idx; + ring.reserve(theta_resolution); + for(int i = 0; i < theta_resolution; ++i) + { + const double theta = 2.0 * M_PI * static_cast(i) / theta_resolution; + ring.push_back(append_node(std::cos(theta) * sin_phi, std::sin(theta) * sin_phi, cos_phi)); + } }; - axom::IndexType cell[3]; + auto append_ring_triangles = [&mesh](const std::vector& lower_ring, + const std::vector& upper_ring) { + SLIC_ASSERT(!lower_ring.empty()); + SLIC_ASSERT(!upper_ring.empty()); - for(int i = 0; i < theta_resolution; ++i) - { - const int next_i = (i + 1) % theta_resolution; - cell[0] = 0; - cell[1] = ring_node(next_i, 0); - cell[2] = ring_node(i, 0); - mesh.appendCell(cell); - } + axom::IndexType cell[3]; + if(lower_ring.size() == 1) + { + const axom::IndexType pole = lower_ring.front(); + const int ring_size = static_cast(upper_ring.size()); + for(int i = 0; i < ring_size; ++i) + { + const int next_i = (i + 1) % ring_size; + cell[0] = pole; + cell[1] = upper_ring[next_i]; + cell[2] = upper_ring[i]; + mesh.appendCell(cell); + } + return; + } - for(int ring = 0; ring < num_rings - 1; ++ring) - { - for(int i = 0; i < theta_resolution; ++i) + if(upper_ring.size() == 1) { - const int next_i = (i + 1) % theta_resolution; - cell[0] = ring_node(i, ring); - cell[1] = ring_node(i, ring + 1); - cell[2] = ring_node(next_i, ring); + const axom::IndexType pole = upper_ring.front(); + const int ring_size = static_cast(lower_ring.size()); + for(int i = 0; i < ring_size; ++i) + { + const int next_i = (i + 1) % ring_size; + cell[0] = pole; + cell[1] = lower_ring[i]; + cell[2] = lower_ring[next_i]; + mesh.appendCell(cell); + } + return; + } + + SLIC_ASSERT(lower_ring.size() == upper_ring.size()); + const int ring_size = static_cast(lower_ring.size()); + for(int i = 0; i < ring_size; ++i) + { + const int next_i = (i + 1) % ring_size; + cell[0] = lower_ring[i]; + cell[1] = upper_ring[i]; + cell[2] = lower_ring[next_i]; mesh.appendCell(cell); - cell[0] = ring_node(next_i, ring); - cell[1] = ring_node(i, ring + 1); - cell[2] = ring_node(next_i, ring + 1); + cell[0] = lower_ring[next_i]; + cell[1] = upper_ring[i]; + cell[2] = upper_ring[next_i]; mesh.appendCell(cell); } - } + }; - const int last_ring = num_rings - 1; - for(int i = 0; i < theta_resolution; ++i) + std::vector lower_ring; + std::vector upper_ring; + lower_ring.reserve(theta_resolution); + upper_ring.reserve(theta_resolution); + + append_ring(lower_ring, 0.0); + for(int j = 1; j <= num_rings; ++j) { - const int next_i = (i + 1) % theta_resolution; - cell[0] = 1; - cell[1] = ring_node(i, last_ring); - cell[2] = ring_node(next_i, last_ring); - mesh.appendCell(cell); + const double phi = M_PI * static_cast(j) / (phi_resolution - 1); + append_ring(upper_ring, phi); + append_ring_triangles(lower_ring, upper_ring); + lower_ring.swap(upper_ring); } + append_ring(upper_ring, M_PI); + append_ring_triangles(lower_ring, upper_ring); + return mint::write_vtk(&mesh, file_path) == 0; } From 3fec4dea531a494238521030310ae95640abded5 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Sun, 22 Mar 2026 22:37:58 -0700 Subject: [PATCH 053/986] Cleans up output in step_moments example --- .../quest/examples/quest_step_moments.cpp | 561 ++++++++---------- 1 file changed, 245 insertions(+), 316 deletions(-) diff --git a/src/axom/quest/examples/quest_step_moments.cpp b/src/axom/quest/examples/quest_step_moments.cpp index f0a341565d..5dadb31f7a 100644 --- a/src/axom/quest/examples/quest_step_moments.cpp +++ b/src/axom/quest/examples/quest_step_moments.cpp @@ -610,25 +610,6 @@ bool write_ellipsoid_vtk(const EllipsoidFit& fit, return mint::write_vtk(&mesh, file_path) == 0; } -std::string format_real(double value) { return axom::fmt::format("{:.16e}", value); } - -std::string quote_yaml_string(const std::string& value) -{ - std::string escaped; - escaped.reserve(value.size()); - - for(char ch : value) - { - escaped += ch; - if(ch == '\'') - { - escaped += '\''; - } - } - - return axom::fmt::format("'{}'", escaped); -} - std::string join_path(const std::string& prefix, const std::string& key) { return prefix.empty() ? key : axom::fmt::format("{}/{}", prefix, key); @@ -717,23 +698,13 @@ class ResultsStore TreeNode& fetch_path(const std::string& path) { TreeNode* node = &m_root; - std::size_t pos = 0; - while(pos < path.size()) + for(const auto& key : axom::utilities::string::split(path, '/')) { - const std::size_t next = path.find('/', pos); - const std::string key = - path.substr(pos, next == std::string::npos ? std::string::npos : next - pos); if(!key.empty()) { node = &node->fetch_child(key); } - - if(next == std::string::npos) - { - break; - } - pos = next + 1; } return *node; @@ -776,7 +747,18 @@ class ResultsStore { if(const auto* string_value = std::get_if(&value)) { - return quote_yaml_string(*string_value); + std::string escaped; + escaped.reserve(string_value->size()); + for(char ch : *string_value) + { + escaped += ch; + if(ch == '\'') + { + escaped += '\''; + } + } + + return axom::fmt::format("'{}'", escaped); } if(const auto* integer_value = std::get_if(&value)) { @@ -784,7 +766,7 @@ class ResultsStore } if(const auto* real_value = std::get_if(&value)) { - return format_real(*real_value); + return axom::fmt::format("{:.16e}", *real_value); } return std::get(value) ? "true" : "false"; @@ -792,34 +774,42 @@ class ResultsStore static void append_yaml(std::string& output, const TreeNode& node, int indent) { + const auto append_line = [&output](int line_indent, + const std::string& key, + const std::string* value = nullptr) { + axom::fmt::format_to(std::back_inserter(output), "{}{}:", std::string(line_indent * 2, ' '), key); + if(value != nullptr) + { + axom::fmt::format_to(std::back_inserter(output), " {}", *value); + } + output += '\n'; + }; + for(const auto& key : node.child_order) { const auto it = node.children.find(key); SLIC_ASSERT(it != node.children.end()); const TreeNode& child = it->second; - output += std::string(indent * 2, ' '); - output += key; - output += ':'; - if(child.children.empty()) { if(child.has_scalar) { - output += ' '; - output += format_scalar(child.scalar_value); + const std::string value = format_scalar(child.scalar_value); + append_line(indent, key, &value); + } + else + { + append_line(indent, key); } - output += '\n'; continue; } - output += '\n'; + append_line(indent, key); if(child.has_scalar) { - output += std::string((indent + 1) * 2, ' '); - output += "value: "; - output += format_scalar(child.scalar_value); - output += '\n'; + const std::string value = format_scalar(child.scalar_value); + append_line(indent + 1, "value", &value); } append_yaml(output, child, indent + 1); } @@ -827,344 +817,283 @@ class ResultsStore #endif }; -void add_xyz_triplet(ResultsStore& results, - const std::string& prefix, - const std::string& key, - double x, - double y, - double z) +class ResultsNode { - const std::string triplet_prefix = join_path(prefix, key); - results.add(triplet_prefix, "x", x); - results.add(triplet_prefix, "y", y); - results.add(triplet_prefix, "z", z); +public: + ResultsNode(ResultsStore& results, std::string prefix) + : m_results(results) + , m_prefix(std::move(prefix)) + { } + + ResultsNode child(const std::string& key) const + { + return ResultsNode {m_results, join_path(m_prefix, key)}; + } + + void add(const std::string& key, const std::string& value) const + { + m_results.add(m_prefix, key, value); + } + + void add(const std::string& key, const char* value) const { m_results.add(m_prefix, key, value); } + + void add(const std::string& key, double value) const { m_results.add(m_prefix, key, value); } + + void add(const std::string& key, int value) const { m_results.add(m_prefix, key, value); } + + void add(const std::string& key, bool value) const { m_results.add(m_prefix, key, value); } + +private: + ResultsStore& m_results; + std::string m_prefix; +}; + +void write_xyz_triplet(const ResultsNode& node, double x, double y, double z) +{ + node.add("x", x); + node.add("y", y); + node.add("z", z); } -void add_point(ResultsStore& results, - const std::string& prefix, - const std::string& key, - const Point3D& point) +void write(const ResultsNode& node, const Point3D& point) { - add_xyz_triplet(results, prefix, key, point[0], point[1], point[2]); + write_xyz_triplet(node, point[0], point[1], point[2]); } -void add_vector(ResultsStore& results, - const std::string& prefix, - const std::string& key, - const Vector3D& vector) +void write(const ResultsNode& node, const Vector3D& vector) { - add_xyz_triplet(results, prefix, key, vector[0], vector[1], vector[2]); + write_xyz_triplet(node, vector[0], vector[1], vector[2]); } -void add_axis_scalars(ResultsStore& results, - const std::string& prefix, - const std::string& key, - double a, - double b, - double c) +void write_axis_scalars(const ResultsNode& node, double a, double b, double c) { - const std::string values_prefix = join_path(prefix, key); - results.add(values_prefix, "axis_0", a); - results.add(values_prefix, "axis_1", b); - results.add(values_prefix, "axis_2", c); + node.add("axis_0", a); + node.add("axis_1", b); + node.add("axis_2", c); } -void add_axes(ResultsStore& results, - const std::string& prefix, - const std::string& key, - const Vector3D* axes) +void write_axes(const ResultsNode& node, const Vector3D* axes) { - const std::string axes_prefix = join_path(prefix, key); for(int i = 0; i < 3; ++i) { - add_vector(results, axes_prefix, axom::fmt::format("axis_{}", i), axes[i]); + write(node.child(axom::fmt::format("axis_{}", i)), axes[i]); } } -void add_inertia_tensor(ResultsStore& results, - const std::string& prefix, - const std::string& key, - const InertiaTensor& tensor) +void write(const ResultsNode& node, const InertiaTensor& tensor) { - const std::string tensor_prefix = join_path(prefix, key); - results.add(tensor_prefix, "xx", tensor.xx); - results.add(tensor_prefix, "yy", tensor.yy); - results.add(tensor_prefix, "zz", tensor.zz); - results.add(tensor_prefix, "xy", tensor.xy); - results.add(tensor_prefix, "xz", tensor.xz); - results.add(tensor_prefix, "yz", tensor.yz); + node.add("xx", tensor.xx); + node.add("yy", tensor.yy); + node.add("zz", tensor.zz); + node.add("xy", tensor.xy); + node.add("xz", tensor.xz); + node.add("yz", tensor.yz); } -void populate_moment_entries(ResultsStore& results, const std::string& prefix, const MomentSet& moments) +void write(const ResultsNode& node, const MomentSet& moments) { - const std::string raw_prefix = join_path(prefix, "raw_moments"); - results.add(raw_prefix, "measure", get_moment(moments, 0, 0, 0)); - - const std::string entries_prefix = join_path(raw_prefix, "entries"); - results.add(entries_prefix, "count", static_cast(moments.requested_entries.size())); + node.add("measure", get_moment(moments, 0, 0, 0)); + const ResultsNode entries = node.child("entries"); + entries.add("count", static_cast(moments.requested_entries.size())); for(const auto& entry : moments.requested_entries) { - const std::string moment_prefix = - join_path(entries_prefix, - axom::fmt::format("m_{}_{}_{}", entry.index.px, entry.index.py, entry.index.pz)); - results.add(moment_prefix, "degree", entry.index.degree); - - const std::string exponents_prefix = join_path(moment_prefix, "exponents"); - results.add(exponents_prefix, "x", entry.index.px); - results.add(exponents_prefix, "y", entry.index.py); - results.add(exponents_prefix, "z", entry.index.pz); - results.add(moment_prefix, "value", entry.value); + entries.add(axom::fmt::format("m_{}_{}_{}", entry.index.px, entry.index.py, entry.index.pz), + entry.value); } } -void populate_mass_properties(ResultsStore& results, - const std::string& prefix, - const MassProperties& props) +void write(const ResultsNode& node, const MassProperties& props) { - const std::string derived_prefix = join_path(prefix, "derived"); - results.add(derived_prefix, "available", props.valid); + node.add("available", props.valid); if(!props.valid) { - results.add(derived_prefix, "reason", "measure_is_zero"); + node.add("reason", "measure_is_zero"); return; } - add_point(results, derived_prefix, "centroid", props.centroid); - add_inertia_tensor(results, derived_prefix, "inertia_origin", props.inertia_origin); - add_inertia_tensor(results, derived_prefix, "inertia_centroid", props.inertia_centroid); + write(node.child("centroid"), props.centroid); + write(node.child("inertia_origin"), props.inertia_origin); + write(node.child("inertia_centroid"), props.inertia_centroid); } -void populate_principal_frame(ResultsStore& results, - const std::string& prefix, - const PrincipalFrame& frame) +void write(const ResultsNode& node, const PrincipalFrame& frame) { - const std::string frame_prefix = join_path(prefix, "principal_frame"); - results.add(frame_prefix, "available", frame.valid); + node.add("available", frame.valid); if(!frame.valid) { - results.add(frame_prefix, "reason", "eigensolve_failed_or_measure_is_zero"); + node.add("reason", "eigensolve_failed_or_measure_is_zero"); return; } - add_axis_scalars(results, - frame_prefix, - "principal_inertia", - frame.principal_inertia[0], - frame.principal_inertia[1], - frame.principal_inertia[2]); - add_axis_scalars(results, - frame_prefix, - "principal_second_moments", - frame.principal_second_moments[0], - frame.principal_second_moments[1], - frame.principal_second_moments[2]); - add_axes(results, frame_prefix, "axes", &frame.axes[0]); + write_axis_scalars(node.child("principal_inertia"), + frame.principal_inertia[0], + frame.principal_inertia[1], + frame.principal_inertia[2]); + write_axis_scalars(node.child("principal_second_moments"), + frame.principal_second_moments[0], + frame.principal_second_moments[1], + frame.principal_second_moments[2]); + write_axes(node.child("axes"), &frame.axes[0]); +} + +void write_assumptions(const ResultsNode& node, std::initializer_list assumptions) +{ + int idx = 0; + for(const char* assumption : assumptions) + { + node.add(axom::fmt::format("assumption_{}", idx++), assumption); + } } -void populate_ellipsoid_fit(ResultsStore& results, - const std::string& prefix, - const std::string& key, - const EllipsoidFit& fit, - double reference_volume, - const char* assumption, - const std::string& vtk_file) +void write_ellipsoid_fit(const ResultsNode& node, + const EllipsoidFit& fit, + double reference_volume, + const char* assumption, + const std::string& vtk_file) { - const std::string fit_prefix = join_path(prefix, key); - results.add(fit_prefix, "available", fit.valid); + node.add("available", fit.valid); if(!fit.valid) { - results.add(fit_prefix, "reason", "invalid_second_moments"); + node.add("reason", "invalid_second_moments"); return; } if(assumption != nullptr) { - results.add(fit_prefix, "assumption", assumption); + node.add("assumption", assumption); } if(!vtk_file.empty()) { - results.add(fit_prefix, "vtk_file", vtk_file); + node.add("vtk_file", vtk_file); } - add_point(results, fit_prefix, "center", fit.centroid); - add_axis_scalars(results, fit_prefix, "semiaxes", fit.radii[0], fit.radii[1], fit.radii[2]); - add_axes(results, fit_prefix, "axes", &fit.axes[0]); - results.add(fit_prefix, "geometric_volume", fit.volume); - results.add(fit_prefix, "volume_ratio", compute_volume_ratio(fit.volume, reference_volume)); - results.add(fit_prefix, - "effective_density", - compute_effective_density(reference_volume, fit.volume)); + write(node.child("center"), fit.centroid); + write_axis_scalars(node.child("semiaxes"), fit.radii[0], fit.radii[1], fit.radii[2]); + write_axes(node.child("axes"), &fit.axes[0]); + node.add("geometric_volume", fit.volume); + node.add("volume_ratio", compute_volume_ratio(fit.volume, reference_volume)); + node.add("effective_density", compute_effective_density(reference_volume, fit.volume)); } -void populate_obb_fit(ResultsStore& results, - const std::string& prefix, - const std::string& key, - const ObbFit& fit, - double reference_volume, - const char* assumption) +void write_obb_fit(const ResultsNode& node, + const ObbFit& fit, + double reference_volume, + const char* assumption) { - const std::string fit_prefix = join_path(prefix, key); - results.add(fit_prefix, "available", fit.valid); + node.add("available", fit.valid); if(!fit.valid) { - results.add(fit_prefix, "reason", "invalid_second_moments"); + node.add("reason", "invalid_second_moments"); return; } - const auto& centroid = fit.box.getCentroid(); - const auto& extents = fit.box.getExtents(); - const auto* axes = fit.box.getAxes(); - if(assumption != nullptr) { - results.add(fit_prefix, "assumption", assumption); + node.add("assumption", assumption); } - add_point(results, fit_prefix, "center", centroid); - add_xyz_triplet(results, fit_prefix, "extents", extents[0], extents[1], extents[2]); - add_axes(results, fit_prefix, "axes", axes); - results.add(fit_prefix, "geometric_volume", fit.volume); - results.add(fit_prefix, "volume_ratio", compute_volume_ratio(fit.volume, reference_volume)); - results.add(fit_prefix, - "effective_density", - compute_effective_density(reference_volume, fit.volume)); + const auto& centroid = fit.box.getCentroid(); + const auto& extents = fit.box.getExtents(); + const auto* axes = fit.box.getAxes(); + write(node.child("center"), centroid); + write_xyz_triplet(node.child("extents"), extents[0], extents[1], extents[2]); + write_axes(node.child("axes"), axes); + node.add("geometric_volume", fit.volume); + node.add("volume_ratio", compute_volume_ratio(fit.volume, reference_volume)); + node.add("effective_density", compute_effective_density(reference_volume, fit.volume)); } -void populate_surface_results(ResultsStore& results, - const std::string& prefix, - const MomentSet& moments, - const MassProperties& props) +void write_results(ResultsStore& results, + const std::string& input_file, + int patch_count, + const std::string& units, + int requested_order, + int computed_order, + int quadrature_order, + IntegralMode integral_mode, + bool has_surface, + const MomentSet& surface_moments, + const MassProperties& surface_props, + bool has_volume, + const MomentSet& volume_moments, + const MassProperties& volume_props, + const PrincipalFrame& volume_frame, + const EllipsoidFit& inertia_matched_ellipsoid, + const EllipsoidFit& same_volume_ellipsoid, + const ObbFit& inertia_matched_obb, + const ObbFit& same_volume_obb, + const std::string& inertia_matched_ellipsoid_vtk_file, + const std::string& same_volume_ellipsoid_vtk_file) { - const std::string surface_prefix = join_path(prefix, "surface"); - populate_moment_entries(results, surface_prefix, moments); - populate_mass_properties(results, surface_prefix, props); -} + const ResultsNode root {results, "results"}; -void populate_volume_results(ResultsStore& results, - const std::string& prefix, - const MomentSet& moments, - const MassProperties& props, - const PrincipalFrame& frame, - const EllipsoidFit& inertia_matched_ellipsoid, - const EllipsoidFit& same_volume_ellipsoid, - const ObbFit& inertia_matched_obb, - const ObbFit& same_volume_obb, - const std::string& inertia_matched_ellipsoid_vtk_file, - const std::string& same_volume_ellipsoid_vtk_file) -{ - const std::string volume_prefix = join_path(prefix, "volume"); - const std::string assumptions_prefix = join_path(volume_prefix, "assumptions"); - results.add( - assumptions_prefix, - "assumption_0", - "volume properties assume the STEP model is a closed, consistently oriented boundary"); - results.add(assumptions_prefix, - "assumption_1", - "inertia_matched fits reproduce the 0th, 1st, and 2nd moments but may not match the " - "geometric volume at unit density"); - results.add(assumptions_prefix, - "assumption_2", - "same_volume_scaled fits uniformly scale the inertia_matched shape to match the " - "geometric volume while preserving principal directions and aspect ratios"); - results.add(assumptions_prefix, - "assumption_3", - "oriented_boxes reported here are moment-based proxies and are not guaranteed to " - "bound the model"); - - populate_moment_entries(results, volume_prefix, moments); - populate_mass_properties(results, volume_prefix, props); - populate_principal_frame(results, volume_prefix, frame); - - const std::string fit_prefix = join_path(volume_prefix, "fit_proxies"); - const std::string ellipsoids_prefix = join_path(fit_prefix, "ellipsoids"); - populate_ellipsoid_fit(results, - ellipsoids_prefix, - "inertia_matched", - inertia_matched_ellipsoid, - props.measure, - nullptr, - inertia_matched_ellipsoid_vtk_file); - populate_ellipsoid_fit(results, - ellipsoids_prefix, - "same_volume_scaled", - same_volume_ellipsoid, - props.measure, - "uniformly scaled from the inertia_matched ellipsoid", - same_volume_ellipsoid_vtk_file); - - const std::string obb_prefix = join_path(fit_prefix, "oriented_boxes"); - populate_obb_fit(results, obb_prefix, "inertia_matched", inertia_matched_obb, props.measure, nullptr); - populate_obb_fit(results, - obb_prefix, - "same_volume_scaled", - same_volume_obb, - props.measure, - "uniformly scaled from the inertia_matched oriented box"); -} + const ResultsNode model = root.child("model"); + model.add("file", input_file); + model.add("patches", patch_count); + model.add("units", units); -void populate_results_store(ResultsStore& results, - const std::string& input_file, - int patch_count, - const std::string& units, - int requested_order, - int computed_order, - int quadrature_order, - IntegralMode integral_mode, - bool has_surface, - const MomentSet& surface_moments, - const MassProperties& surface_props, - bool has_volume, - const MomentSet& volume_moments, - const MassProperties& volume_props, - const PrincipalFrame& volume_frame, - const EllipsoidFit& inertia_matched_ellipsoid, - const EllipsoidFit& same_volume_ellipsoid, - const ObbFit& inertia_matched_obb, - const ObbFit& same_volume_obb, - const std::string& inertia_matched_ellipsoid_vtk_file, - const std::string& same_volume_ellipsoid_vtk_file) -{ - const std::string root_prefix = "results"; - const std::string model_prefix = join_path(root_prefix, "model"); - results.add(model_prefix, "file", input_file); - results.add(model_prefix, "patches", patch_count); - results.add(model_prefix, "units", units); - - const std::string config_prefix = join_path(root_prefix, "config"); - results.add(config_prefix, "requested_order", requested_order); - results.add(config_prefix, "computed_order", computed_order); - results.add(config_prefix, "quadrature_order", quadrature_order); - results.add(config_prefix, "integral", integral_mode_name(integral_mode)); - - const std::string summary_prefix = join_path(root_prefix, "summary"); + const ResultsNode config = root.child("config"); + config.add("requested_order", requested_order); + config.add("computed_order", computed_order); + config.add("quadrature_order", quadrature_order); + config.add("integral", integral_mode_name(integral_mode)); + + const ResultsNode summary = root.child("summary"); if(has_surface) { - results.add(summary_prefix, "surface_measure", surface_props.measure); + summary.add("surface_measure", surface_props.measure); } if(has_volume) { - results.add(summary_prefix, "volume_measure", volume_props.measure); + summary.add("volume_measure", volume_props.measure); } if(has_surface) { - populate_surface_results(results, root_prefix, surface_moments, surface_props); + const ResultsNode surface = root.child("surface"); + write(surface.child("raw_moments"), surface_moments); + write(surface.child("derived"), surface_props); } if(has_volume) { - populate_volume_results(results, - root_prefix, - volume_moments, - volume_props, - volume_frame, - inertia_matched_ellipsoid, - same_volume_ellipsoid, - inertia_matched_obb, - same_volume_obb, - inertia_matched_ellipsoid_vtk_file, - same_volume_ellipsoid_vtk_file); + const ResultsNode volume = root.child("volume"); + write_assumptions( + volume.child("assumptions"), + {"volume properties assume the STEP model is a closed, consistently oriented boundary", + "inertia_matched fits reproduce the 0th, 1st, and 2nd moments but may not match the " + "geometric volume at unit density", + "same_volume_scaled fits uniformly scale the inertia_matched shape to match the geometric " + "volume while preserving principal directions and aspect ratios", + "oriented_boxes reported here are moment-based proxies and are not guaranteed to bound the " + "model"}); + write(volume.child("raw_moments"), volume_moments); + write(volume.child("derived"), volume_props); + write(volume.child("principal_frame"), volume_frame); + + const ResultsNode fit_proxies = volume.child("fit_proxies"); + const ResultsNode ellipsoids = fit_proxies.child("ellipsoids"); + write_ellipsoid_fit(ellipsoids.child("inertia_matched"), + inertia_matched_ellipsoid, + volume_props.measure, + nullptr, + inertia_matched_ellipsoid_vtk_file); + write_ellipsoid_fit(ellipsoids.child("same_volume_scaled"), + same_volume_ellipsoid, + volume_props.measure, + "uniformly scaled from the inertia_matched ellipsoid", + same_volume_ellipsoid_vtk_file); + + const ResultsNode oriented_boxes = fit_proxies.child("oriented_boxes"); + write_obb_fit(oriented_boxes.child("inertia_matched"), + inertia_matched_obb, + volume_props.measure, + nullptr); + write_obb_fit(oriented_boxes.child("same_volume_scaled"), + same_volume_obb, + volume_props.measure, + "uniformly scaled from the inertia_matched oriented box"); } } } // namespace @@ -1354,27 +1283,27 @@ int main(int argc, char** argv) SLIC_INFO(summary); ResultsStore results; - populate_results_store(results, - input_file, - static_cast(patches.size()), - reader.getFileUnits(), - max_degree, - computed_max_degree, - quadrature_order, - integral_mode, - should_compute_surface(integral_mode), - surface_moments, - surface_props, - should_compute_volume(integral_mode), - volume_moments, - volume_props, - volume_frame, - inertia_matched_ellipsoid, - same_volume_ellipsoid, - inertia_matched_obb, - same_volume_obb, - inertia_matched_ellipsoid_vtk_file, - same_volume_ellipsoid_vtk_file); + write_results(results, + input_file, + static_cast(patches.size()), + reader.getFileUnits(), + max_degree, + computed_max_degree, + quadrature_order, + integral_mode, + should_compute_surface(integral_mode), + surface_moments, + surface_props, + should_compute_volume(integral_mode), + volume_moments, + volume_props, + volume_frame, + inertia_matched_ellipsoid, + same_volume_ellipsoid, + inertia_matched_obb, + same_volume_obb, + inertia_matched_ellipsoid_vtk_file, + same_volume_ellipsoid_vtk_file); SLIC_INFO(results.to_yaml()); } From 91f3c6623954d39a867bc38730a2fd7e6297c9cb Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Mon, 23 Mar 2026 00:22:45 -0700 Subject: [PATCH 054/986] Improves formatting of output in step_moments example --- .../quest/examples/quest_step_moments.cpp | 96 ++++++++++++++++--- 1 file changed, 84 insertions(+), 12 deletions(-) diff --git a/src/axom/quest/examples/quest_step_moments.cpp b/src/axom/quest/examples/quest_step_moments.cpp index 5dadb31f7a..3f3868a305 100644 --- a/src/axom/quest/examples/quest_step_moments.cpp +++ b/src/axom/quest/examples/quest_step_moments.cpp @@ -35,6 +35,7 @@ #endif #include +#include #include #include #include @@ -70,6 +71,11 @@ const std::map s_validIntegralModes {{"surface", Inte constexpr double eps = 1e-14; +double clamp_near_zero(double value, double tolerance) +{ + return std::abs(value) <= tolerance ? 0.0 : value; +} + const char* integral_mode_name(IntegralMode mode) { switch(mode) @@ -618,6 +624,10 @@ std::string join_path(const std::string& prefix, const std::string& key) class ResultsStore { public: + explicit ResultsStore(double zero_tolerance = 0.0) : m_zero_tolerance(zero_tolerance) { } + + void set(const std::string& path, const std::array& value) { set_vec3(path, value); } + void add(const std::string& prefix, const std::string& key, const std::string& value) { set_string(join_path(prefix, key), value); @@ -633,6 +643,16 @@ class ResultsStore set_real(join_path(prefix, key), value); } + void add(const std::string& prefix, const std::string& key, const std::array& value) + { + set_vec3(join_path(prefix, key), value); + } + + void add_unclamped_real(const std::string& prefix, const std::string& key, double value) + { + set_real_unclamped(join_path(prefix, key), value); + } + void add(const std::string& prefix, const std::string& key, int value) { set_integer(join_path(prefix, key), value); @@ -655,6 +675,10 @@ class ResultsStore } private: + double sanitize_real(double value) const { return clamp_near_zero(value, m_zero_tolerance); } + + const double m_zero_tolerance; + #ifdef AXOM_USE_CONDUIT conduit::Node m_root; @@ -662,7 +686,17 @@ class ResultsStore void set_string(const std::string& path, const char* value) { m_root[path].set_string(value); } - void set_real(const std::string& path, double value) { m_root[path] = value; } + void set_real(const std::string& path, double value) { m_root[path] = sanitize_real(value); } + + void set_vec3(const std::string& path, const std::array& value) + { + const std::array sanitized {sanitize_real(value[0]), + sanitize_real(value[1]), + sanitize_real(value[2])}; + m_root[path].set(sanitized.data(), 3); + } + + void set_real_unclamped(const std::string& path, double value) { m_root[path] = value; } void set_integer(const std::string& path, int value) { m_root[path] = value; } @@ -671,7 +705,7 @@ class ResultsStore m_root[path].set_string(value ? "true" : "false"); } #else - using ScalarValue = std::variant; + using ScalarValue = std::variant>; struct TreeNode { @@ -723,6 +757,22 @@ class ResultsStore } void set_real(const std::string& path, double value) + { + TreeNode& node = fetch_path(path); + node.has_scalar = true; + node.scalar_value = sanitize_real(value); + } + + void set_vec3(const std::string& path, const std::array& value) + { + TreeNode& node = fetch_path(path); + node.has_scalar = true; + node.scalar_value = std::array {sanitize_real(value[0]), + sanitize_real(value[1]), + sanitize_real(value[2])}; + } + + void set_real_unclamped(const std::string& path, double value) { TreeNode& node = fetch_path(path); node.has_scalar = true; @@ -768,6 +818,13 @@ class ResultsStore { return axom::fmt::format("{:.16e}", *real_value); } + if(const auto* vec3_value = std::get_if>(&value)) + { + return axom::fmt::format("[{:.16e}, {:.16e}, {:.16e}]", + (*vec3_value)[0], + (*vec3_value)[1], + (*vec3_value)[2]); + } return std::get(value) ? "true" : "false"; } @@ -839,6 +896,13 @@ class ResultsNode void add(const std::string& key, double value) const { m_results.add(m_prefix, key, value); } + void add(const std::string& key, const std::array& value) const + { + m_results.add(m_prefix, key, value); + } + + void set(const std::array& value) const { m_results.set(m_prefix, value); } + void add(const std::string& key, int value) const { m_results.add(m_prefix, key, value); } void add(const std::string& key, bool value) const { m_results.add(m_prefix, key, value); } @@ -850,9 +914,7 @@ class ResultsNode void write_xyz_triplet(const ResultsNode& node, double x, double y, double z) { - node.add("x", x); - node.add("y", y); - node.add("z", z); + node.set(std::array {x, y, z}); } void write(const ResultsNode& node, const Point3D& point) @@ -905,9 +967,9 @@ void write(const ResultsNode& node, const MomentSet& moments) void write(const ResultsNode& node, const MassProperties& props) { - node.add("available", props.valid); if(!props.valid) { + node.add("available", props.valid); node.add("reason", "measure_is_zero"); return; } @@ -919,9 +981,9 @@ void write(const ResultsNode& node, const MassProperties& props) void write(const ResultsNode& node, const PrincipalFrame& frame) { - node.add("available", frame.valid); if(!frame.valid) { + node.add("available", frame.valid); node.add("reason", "eigensolve_failed_or_measure_is_zero"); return; } @@ -952,9 +1014,9 @@ void write_ellipsoid_fit(const ResultsNode& node, const char* assumption, const std::string& vtk_file) { - node.add("available", fit.valid); if(!fit.valid) { + node.add("available", fit.valid); node.add("reason", "invalid_second_moments"); return; } @@ -981,9 +1043,9 @@ void write_obb_fit(const ResultsNode& node, double reference_volume, const char* assumption) { - node.add("available", fit.valid); if(!fit.valid) { + node.add("available", fit.valid); node.add("reason", "invalid_second_moments"); return; } @@ -1011,6 +1073,7 @@ void write_results(ResultsStore& results, int requested_order, int computed_order, int quadrature_order, + double zero_tolerance, IntegralMode integral_mode, bool has_surface, const MomentSet& surface_moments, @@ -1037,6 +1100,7 @@ void write_results(ResultsStore& results, config.add("requested_order", requested_order); config.add("computed_order", computed_order); config.add("quadrature_order", quadrature_order); + results.add_unclamped_real("results/config", "zero_tolerance", zero_tolerance); config.add("integral", integral_mode_name(integral_mode)); const ResultsNode summary = root.child("summary"); @@ -1105,6 +1169,7 @@ int main(int argc, char** argv) int quadrature_order {0}; bool verbose {false}; bool validate_model {false}; + double zero_tolerance {0.0}; std::string ellipsoid_vtk_prefix; std::string annotationMode {"none"}; IntegralMode integral_mode {IntegralMode::BOTH}; @@ -1128,6 +1193,10 @@ int main(int argc, char** argv) ->description("Which measures to compute: 'surface', 'volume', or 'both'") ->capture_default_str() ->transform(axom::CLI::CheckedTransformer(s_validIntegralModes)); + app.add_option("--zero-tolerance", zero_tolerance) + ->description("Clamp reported floating-point values with abs(value) <= tol to exactly zero") + ->capture_default_str() + ->check(axom::CLI::NonNegativeNumber); app.add_flag("-v,--verbose", verbose, "Enable verbose output")->capture_default_str(); app.add_flag("--validate", validate_model, "Run STEP model validation checks")->capture_default_str(); app.add_option("--ellipsoid-vtk-prefix", ellipsoid_vtk_prefix) @@ -1274,15 +1343,17 @@ int main(int argc, char** argv) std::string summary = "SUMMARY"; if(should_compute_surface(integral_mode)) { - summary += axom::fmt::format(" surface_m0={:.16e}", surface_props.measure); + summary += axom::fmt::format(" surface_m0={:.16e}", + clamp_near_zero(surface_props.measure, zero_tolerance)); } if(should_compute_volume(integral_mode)) { - summary += axom::fmt::format(" volume_m0={:.16e}", volume_props.measure); + summary += axom::fmt::format(" volume_m0={:.16e}", + clamp_near_zero(volume_props.measure, zero_tolerance)); } SLIC_INFO(summary); - ResultsStore results; + ResultsStore results(zero_tolerance); write_results(results, input_file, static_cast(patches.size()), @@ -1290,6 +1361,7 @@ int main(int argc, char** argv) max_degree, computed_max_degree, quadrature_order, + zero_tolerance, integral_mode, should_compute_surface(integral_mode), surface_moments, From 53622312cdc44accdbc1f9c795cb03f546c1eeda Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Mon, 23 Mar 2026 00:49:51 -0700 Subject: [PATCH 055/986] Bugfix: User must provide the min z-component for integration --- .../primal/operators/evaluate_integral.hpp | 20 +++++++++++++------ src/axom/primal/tests/primal_integral.cpp | 14 +++++++++++++ 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/axom/primal/operators/evaluate_integral.hpp b/src/axom/primal/operators/evaluate_integral.hpp index 40f52d88fe..d96246d1c3 100644 --- a/src/axom/primal/operators/evaluate_integral.hpp +++ b/src/axom/primal/operators/evaluate_integral.hpp @@ -529,19 +529,23 @@ LambdaRetType evaluate_surface_integral(const axom::Array>& pat * * \param [in] patch the Bezier patch * \param [in] integrand callable representing the integrand + * \param [in] lower_bound_z the shared lower integration bound used for the + * z-directed antiderivative across the full boundary * \param [in] npts_uv the number of quadrature points in each patch parameter direction * \param [in] npts_z the number of quadrature points used for numerical * antidifferentiation in z * * \pre The patch parameterization must be valid on its full parameter domain. - * \pre The returned value is geometrically meaningful as a volume only when this - * patch is interpreted as part of a closed, consistently oriented boundary. + * \pre The returned value is geometrically meaningful as a volume contribution only + * when this patch is interpreted as part of a closed, consistently oriented + * boundary that uses the same lower integration bound. */ template ::PointType>> LambdaRetType evaluate_volume_integral(const primal::BezierPatch& patch, Lambda&& integrand, + T lower_bound_z, int npts_uv, int npts_z = 0) { @@ -556,7 +560,7 @@ LambdaRetType evaluate_volume_integral(const primal::BezierPatch& patch, return detail::evaluate_volume_integral_component(patch, std::forward(integrand), - patch.boundingBox().getMin()[2], + lower_bound_z, npts_uv, npts_z); } @@ -570,6 +574,8 @@ LambdaRetType evaluate_volume_integral(const primal::BezierPatch& patch, * * \param [in] patch the NURBS patch * \param [in] integrand callable representing the integrand + * \param [in] lower_bound_z the shared lower integration bound used for the + * z-directed antiderivative across the full boundary * \param [in] npts_Q the number of quadrature points on each trimming curve or * in each parametric direction for untrimmed Bezier pieces * \param [in] npts_P the number of quadrature points used for numerical @@ -580,14 +586,16 @@ LambdaRetType evaluate_volume_integral(const primal::BezierPatch& patch, * \pre The patch parameterization must be valid on its full parameter domain. * \pre If the patch is trimmed, its trimming curves must bound the intended * interior region in parameter space. - * \pre The returned value is geometrically meaningful as a volume only when this - * patch is interpreted as part of a closed, consistently oriented boundary. + * \pre The returned value is geometrically meaningful as a volume contribution only + * when this patch is interpreted as part of a closed, consistently oriented + * boundary that uses the same lower integration bound. */ template ::PointType>> LambdaRetType evaluate_volume_integral(const primal::NURBSPatch& patch, Lambda&& integrand, + T lower_bound_z, int npts_Q, int npts_P = 0, int npts_Z = 0) @@ -607,7 +615,7 @@ LambdaRetType evaluate_volume_integral(const primal::NURBSPatch& patch, return detail::evaluate_volume_integral_component(patch, std::forward(integrand), - patch.boundingBox().getMin()[2], + lower_bound_z, npts_Q, npts_P, npts_Z); diff --git a/src/axom/primal/tests/primal_integral.cpp b/src/axom/primal/tests/primal_integral.cpp index f7c3b560a7..34ad3824b9 100644 --- a/src/axom/primal/tests/primal_integral.cpp +++ b/src/axom/primal/tests/primal_integral.cpp @@ -589,10 +589,24 @@ TEST(primal_integral, evaluate_patch_surface_and_volume_integrals) constexpr int npts = 6; constexpr double abs_tol = 1e-10; + constexpr double lower_bound_z = 0.0; + + double patchwise_volume = 0.0; + double patchwise_z_moment = 0.0; + for(int i = 0; i < cube_faces.size(); ++i) + { + patchwise_volume += evaluate_volume_integral(cube_faces[i], const_integrand, lower_bound_z, npts); + patchwise_z_moment += evaluate_volume_integral(cube_faces[i], z_integrand, lower_bound_z, npts); + } EXPECT_NEAR(evaluate_surface_integral(cube_faces, const_integrand, npts), 6.0, abs_tol); EXPECT_NEAR(evaluate_volume_integral(cube_faces, const_integrand, npts), 1.0, abs_tol); EXPECT_NEAR(evaluate_volume_integral(cube_faces, z_integrand, npts), 0.5, abs_tol); + EXPECT_NEAR(patchwise_volume, 1.0, abs_tol); + EXPECT_NEAR(patchwise_z_moment, 0.5, abs_tol); + EXPECT_NEAR(evaluate_volume_integral(cube_faces[1], const_integrand, lower_bound_z, npts), + 1.0, + abs_tol); } TEST(primal_integral, evaluate_trimmed_nurbs_patch_surface_integral) From 3b15039e6ee5da4255ea97b58e42d915c9ec6c25 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Mon, 23 Mar 2026 01:19:55 -0700 Subject: [PATCH 056/986] Handles negative orientations in step_moments example --- .../quest/examples/quest_step_moments.cpp | 49 ++++++++++++++----- 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/src/axom/quest/examples/quest_step_moments.cpp b/src/axom/quest/examples/quest_step_moments.cpp index 3f3868a305..03209d138b 100644 --- a/src/axom/quest/examples/quest_step_moments.cpp +++ b/src/axom/quest/examples/quest_step_moments.cpp @@ -134,7 +134,9 @@ struct InertiaTensor struct MassProperties { bool valid {false}; + bool orientation_reversed {false}; double measure {}; + double signed_measure {}; Point3D centroid {}; InertiaTensor inertia_origin {}; InertiaTensor inertia_centroid {}; @@ -243,26 +245,35 @@ InertiaTensor shift_to_centroid(const InertiaTensor& inertia_origin, return shifted; } -MassProperties compute_mass_properties(const MomentSet& moments) +MassProperties compute_mass_properties(const MomentSet& moments, bool normalize_orientation = false) { MassProperties props; - props.measure = get_moment(moments, 0, 0, 0); - if(std::abs(props.measure) <= eps) + props.signed_measure = get_moment(moments, 0, 0, 0); + if(std::abs(props.signed_measure) <= eps) { return props; } + const double orientation_sign = normalize_orientation && props.signed_measure < 0.0 ? -1.0 : 1.0; + props.valid = true; - props.centroid[0] = get_moment(moments, 1, 0, 0) / props.measure; - props.centroid[1] = get_moment(moments, 0, 1, 0) / props.measure; - props.centroid[2] = get_moment(moments, 0, 0, 1) / props.measure; + props.orientation_reversed = orientation_sign < 0.0; + props.measure = orientation_sign * props.signed_measure; + + const double mx = orientation_sign * get_moment(moments, 1, 0, 0); + const double my = orientation_sign * get_moment(moments, 0, 1, 0); + const double mz = orientation_sign * get_moment(moments, 0, 0, 1); - const double xx = get_moment(moments, 2, 0, 0); - const double yy = get_moment(moments, 0, 2, 0); - const double zz = get_moment(moments, 0, 0, 2); - const double xy = get_moment(moments, 1, 1, 0); - const double xz = get_moment(moments, 1, 0, 1); - const double yz = get_moment(moments, 0, 1, 1); + props.centroid[0] = mx / props.measure; + props.centroid[1] = my / props.measure; + props.centroid[2] = mz / props.measure; + + const double xx = orientation_sign * get_moment(moments, 2, 0, 0); + const double yy = orientation_sign * get_moment(moments, 0, 2, 0); + const double zz = orientation_sign * get_moment(moments, 0, 0, 2); + const double xy = orientation_sign * get_moment(moments, 1, 1, 0); + const double xz = orientation_sign * get_moment(moments, 1, 0, 1); + const double yz = orientation_sign * get_moment(moments, 0, 1, 1); props.inertia_origin.xx = yy + zz; props.inertia_origin.yy = xx + zz; @@ -974,6 +985,12 @@ void write(const ResultsNode& node, const MassProperties& props) return; } + if(props.orientation_reversed) + { + node.add("signed_measure", props.signed_measure); + node.add("orientation_reversed", props.orientation_reversed); + } + write(node.child("centroid"), props.centroid); write(node.child("inertia_origin"), props.inertia_origin); write(node.child("inertia_centroid"), props.inertia_centroid); @@ -1132,6 +1149,12 @@ void write_results(ResultsStore& results, "volume while preserving principal directions and aspect ratios", "oriented_boxes reported here are moment-based proxies and are not guaranteed to bound the " "model"}); + if(volume_props.orientation_reversed) + { + volume.add("orientation_note", + "raw volume moments indicated inward orientation; derived properties and fit " + "proxies were reoriented to use a positive enclosed volume"); + } write(volume.child("raw_moments"), volume_moments); write(volume.child("derived"), volume_props); write(volume.child("principal_frame"), volume_frame); @@ -1293,7 +1316,7 @@ int main(int argc, char** argv) auto integrand = [idx](const Point3D& x) -> double { return evaluate_monomial(x, idx); }; return primal::evaluate_volume_integral(patches, integrand, quadrature_order); }); - volume_props = compute_mass_properties(volume_moments); + volume_props = compute_mass_properties(volume_moments, true); if(volume_props.valid) { From cd494a5efe65f1378a4bde3d57f0afa1e2fd7bec Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Mon, 23 Mar 2026 01:29:06 -0700 Subject: [PATCH 057/986] Adds some comments --- src/axom/quest/examples/quest_step_moments.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/axom/quest/examples/quest_step_moments.cpp b/src/axom/quest/examples/quest_step_moments.cpp index 03209d138b..bcdf8cf9b4 100644 --- a/src/axom/quest/examples/quest_step_moments.cpp +++ b/src/axom/quest/examples/quest_step_moments.cpp @@ -254,6 +254,10 @@ MassProperties compute_mass_properties(const MomentSet& moments, bool normalize_ return props; } + // Closed STEP shells can be oriented inward, which flips the sign of every + // volume moment without changing the underlying geometry. When requested, + // normalize that sign here so downstream centroid/inertia fits use a + // positive enclosed-volume convention. const double orientation_sign = normalize_orientation && props.signed_measure < 0.0 ? -1.0 : 1.0; props.valid = true; @@ -331,6 +335,9 @@ PrincipalFrame compute_principal_frame(const MassProperties& props) frame.axes[i][2] = eigenvectors(2, i); } + // In the principal basis, the centroidal inertia tensor diagonal entries are + // pairwise sums of the centroidal second moments. Solve that 3x3 system to + // recover the second moments used by the ellipsoid and OBB proxies. for(int i = 0; i < ndims; ++i) { frame.principal_second_moments[i] = 0.5 * (inertia_trace - 2.0 * frame.principal_inertia[i]); @@ -358,6 +365,8 @@ bool compute_shape_dimensions(const PrincipalFrame& frame, double factor, double for(int i = 0; i < ndims; ++i) { + // The factor selects the proxy family: 5 for a uniform ellipsoid semiaxis + // and 3 for a box half-extent that matches the same centroidal moments. const double dim_sq = factor * frame.principal_second_moments[i] / frame.measure; if(dim_sq < -tol) { @@ -1316,6 +1325,9 @@ int main(int argc, char** argv) auto integrand = [idx](const Point3D& x) -> double { return evaluate_monomial(x, idx); }; return primal::evaluate_volume_integral(patches, integrand, quadrature_order); }); + // Normalize inward-oriented closed shells before deriving principal axes and + // fit proxies so the reported volume-based quantities use the same sign + // convention as outward-oriented solids. volume_props = compute_mass_properties(volume_moments, true); if(volume_props.valid) From 1dbbaf26036a9fbefb29efbd34465caf02241b8d Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Mon, 23 Mar 2026 01:42:50 -0700 Subject: [PATCH 058/986] Updates RELEASE-NOTES --- RELEASE-NOTES.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 4b0daf21cc..f0797dca2e 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -19,6 +19,9 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ ## [Unreleased] - Release date yyyy-mm-dd ### Added +- Primal: Functions to evaluate surface and volume integrals over collections of `BezierPatch` and `NURBSPatch` objects +- Quest: An example to evaluate surface and volume integrals over a STEP model + and to fit a ellipsoid or oriented bounding box to a model based on its low order moments. ### Removed @@ -54,8 +57,11 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ - Core: the `ArrayView` class was modified so it defers initializing an internal allocator id via Umpire, if present. This prevents excessive calls to Umpire, which are not needed in all use cases. -- Quest: A compilation problem with `-DAXOM_NO_INT64_T=1` was fixed. -- Quest: `STEPReader` now catches additional edge cases related to orientation of OpenCascade primitives. + +### Removed + +### Deprecated + ## [Version 0.13.0] - Release date 2026-02-05 @@ -1423,8 +1429,7 @@ fractions for the associated materials must be supplied before shaping. - Use this section in case of vulnerabilities -[Unreleased]: https://github.com/LLNL/axom/compare/v0.14.0...develop -[Version 0.14.0]: https://github.com/LLNL/axom/compare/v0.13.0...v0.14.0 +[Unreleased]: https://github.com/LLNL/axom/compare/v0.13.0...develop [Version 0.13.0]: https://github.com/LLNL/axom/compare/v0.12.0...v0.13.0 [Version 0.12.0]: https://github.com/LLNL/axom/compare/v0.11.0...v0.12.0 [Version 0.11.0]: https://github.com/LLNL/axom/compare/v0.10.1...v0.11.0 From 6b15cb177c9b4a659f7d3f43249d4048f63bd8e2 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Mon, 23 Mar 2026 13:37:10 -0700 Subject: [PATCH 059/986] Bugfix for compiler error with llvm --- src/axom/primal/tests/primal_integral.cpp | 1 - src/axom/quest/examples/quest_step_moments.cpp | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/axom/primal/tests/primal_integral.cpp b/src/axom/primal/tests/primal_integral.cpp index 34ad3824b9..1f86155e8b 100644 --- a/src/axom/primal/tests/primal_integral.cpp +++ b/src/axom/primal/tests/primal_integral.cpp @@ -566,7 +566,6 @@ TEST(primal_integral, evaluate_nurbs_surface_normal) TEST(primal_integral, evaluate_patch_surface_and_volume_integrals) { - using Point2D = primal::Point; using Point3D = primal::Point; using BPatch = primal::BezierPatch; diff --git a/src/axom/quest/examples/quest_step_moments.cpp b/src/axom/quest/examples/quest_step_moments.cpp index bcdf8cf9b4..7d3ad7d0fd 100644 --- a/src/axom/quest/examples/quest_step_moments.cpp +++ b/src/axom/quest/examples/quest_step_moments.cpp @@ -149,14 +149,14 @@ struct PrincipalFrame Point3D centroid {}; double principal_inertia[3] {}; double principal_second_moments[3] {}; - Vector3D axes[3] {}; + Vector3D axes[3]; }; struct EllipsoidFit { bool valid {false}; Point3D centroid {}; - Vector3D axes[3] {}; + Vector3D axes[3]; double radii[3] {}; double volume {}; }; From f5721347ace213d14682517b07bd15e9c547114e Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Mon, 23 Mar 2026 16:03:08 -0700 Subject: [PATCH 060/986] When computing patch-based integrals for trimming curves, ensure lower bound is inside patch Assumption is that control points can leave the patch, but the trimming curves themselves lie within the patch. --- .../detail/evaluate_integral_impl.hpp | 12 +++-- src/axom/primal/tests/primal_integral.cpp | 44 +++++++++++++++++++ 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/src/axom/primal/operators/detail/evaluate_integral_impl.hpp b/src/axom/primal/operators/detail/evaluate_integral_impl.hpp index debd511c6e..b275c4eb80 100644 --- a/src/axom/primal/operators/detail/evaluate_integral_impl.hpp +++ b/src/axom/primal/operators/detail/evaluate_integral_impl.hpp @@ -482,7 +482,10 @@ inline LambdaRetType evaluate_surface_integral_component(const primal::NURBSPatc continue; } - const auto lower_bound_y = detail::curve_array_lower_bound_y(curves); + // clamp the lower bound to lie within the parametric space of the patch + const auto lower_bound_y = axom::utilities::clampVal(detail::curve_array_lower_bound_y(curves), + split_patch.getMinKnot_v(), + split_patch.getMaxKnot_v()); for(int i = 0; i < curves.size(); ++i) { total_integral += detail::evaluate_area_integral_component( @@ -569,7 +572,10 @@ inline LambdaRetType evaluate_volume_integral_component(const primal::NURBSPatch continue; } - const auto lower_bound_y = detail::curve_array_lower_bound_y(curves); + // clamp the lower bound to lie within the parametric space of the patch + const auto lower_bound_y = axom::utilities::clampVal(detail::curve_array_lower_bound_y(curves), + split_patch.getMinKnot_v(), + split_patch.getMaxKnot_v()); for(int i = 0; i < curves.size(); ++i) { total_integral += detail::evaluate_area_integral_component( @@ -603,4 +609,4 @@ inline LambdaRetType evaluate_volume_integral_component(const primal::NURBSPatch } // end namespace primal } // end namespace axom -#endif \ No newline at end of file +#endif diff --git a/src/axom/primal/tests/primal_integral.cpp b/src/axom/primal/tests/primal_integral.cpp index 1f86155e8b..a50369c0cc 100644 --- a/src/axom/primal/tests/primal_integral.cpp +++ b/src/axom/primal/tests/primal_integral.cpp @@ -639,6 +639,50 @@ TEST(primal_integral, evaluate_trimmed_nurbs_patch_surface_integral) EXPECT_NEAR(evaluate_surface_integral(patch, linear_integrand, npts), 0.25, abs_tol); } +TEST(primal_integral, evaluate_trimmed_nurbs_patch_surface_integral_clamps_bounds) +{ + using Point2D = primal::Point; + using Point3D = primal::Point; + using NPatch = primal::NURBSPatch; + using TrimmingCurve = primal::NURBSCurve; + + Point3D control_points[] = {Point3D {0.0, 0.0, 0.0}, + Point3D {0.0, 1.0, 0.0}, + Point3D {1.0, 0.0, 0.0}, + Point3D {1.0, 1.0, 0.0}}; + + NPatch patch(control_points, 2, 2, 1, 1); + + // Closed trimming loop with a control point below v-min (vmin == 0 for this patch), + // but whose geometric curve remains inside the patch. + axom::Array bottom_pts {Point2D {0.25, 0.25}, Point2D {0.50, -0.05}, Point2D {0.75, 0.25}}; + patch.addTrimmingCurve(TrimmingCurve(bottom_pts, 2)); + + Point2D right_pts[] = {Point2D {0.75, 0.25}, Point2D {0.75, 0.75}}; + patch.addTrimmingCurve(TrimmingCurve(right_pts, 2, 1)); + + Point2D top_pts[] = {Point2D {0.75, 0.75}, Point2D {0.25, 0.75}}; + patch.addTrimmingCurve(TrimmingCurve(top_pts, 2, 1)); + + Point2D left_pts[] = {Point2D {0.25, 0.75}, Point2D {0.25, 0.25}}; + patch.addTrimmingCurve(TrimmingCurve(left_pts, 2, 1)); + + bool saw_clamped_node = false; + const double vmin = patch.getMinKnot_v(); + auto integrand = [&saw_clamped_node, vmin](Point3D x) -> double { + if(x[1] == vmin) + { + saw_clamped_node = true; + } + return x[1]; + }; + + constexpr int npts = 8; + (void)evaluate_surface_integral(patch, integrand, npts); + + EXPECT_FALSE(saw_clamped_node); +} + TEST(primal_integral, evaluate_integral_nurbs_gwn_cache) { using Point2D = primal::Point; From 2a92925d583cddfd7ee3c089d659bc6e6daed39a Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Mon, 23 Mar 2026 18:14:27 -0700 Subject: [PATCH 061/986] Bugfix: Pass CurvedPolygon by reference instead of by-value --- src/axom/primal/operators/evaluate_integral.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/axom/primal/operators/evaluate_integral.hpp b/src/axom/primal/operators/evaluate_integral.hpp index d96246d1c3..1ad3350c54 100644 --- a/src/axom/primal/operators/evaluate_integral.hpp +++ b/src/axom/primal/operators/evaluate_integral.hpp @@ -63,7 +63,7 @@ namespace primal template > -LambdaRetType evaluate_line_integral(const primal::CurvedPolygon cpoly, +LambdaRetType evaluate_line_integral(const primal::CurvedPolygon& cpoly, Lambda&& integrand, int npts) { @@ -167,7 +167,7 @@ LambdaRetType evaluate_line_integral(const axom::Array& carray, Lambd * \return the value of the integral */ template -FuncRetType evaluate_vector_line_integral(const CurvedPolygon cpoly, +FuncRetType evaluate_vector_line_integral(const CurvedPolygon& cpoly, Lambda&& vector_integrand, int npts) { From de20aca6a8e5d9379241467db7663a9f0909658a Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Mon, 23 Mar 2026 18:20:53 -0700 Subject: [PATCH 062/986] Bugfix: Off-by-one indexing when finding the min y-coordinate --- .../operators/detail/evaluate_integral_impl.hpp | 6 +++--- src/axom/primal/tests/primal_integral.cpp | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/axom/primal/operators/detail/evaluate_integral_impl.hpp b/src/axom/primal/operators/detail/evaluate_integral_impl.hpp index b275c4eb80..d5187caaca 100644 --- a/src/axom/primal/operators/detail/evaluate_integral_impl.hpp +++ b/src/axom/primal/operators/detail/evaluate_integral_impl.hpp @@ -416,14 +416,14 @@ inline typename CurveType::NumericType curve_array_lower_bound_y(const axom::Arr { using T = typename CurveType::NumericType; - SLIC_ASSERT(!carray.empty()); + SLIC_ASSERT(!carray.empty() && carray[0].getNumControlPoints() > 0); T lower_bound_y = carray[0][0][1]; for(int i = 0; i < carray.size(); ++i) { - for(int j = 1; j < carray[i].getNumControlPoints(); ++j) + for(int j = 0; j < carray[i].getNumControlPoints(); ++j) { - lower_bound_y = std::min(lower_bound_y, carray[i][j][1]); + lower_bound_y = axom::utilities::min(lower_bound_y, carray[i][j][1]); } } diff --git a/src/axom/primal/tests/primal_integral.cpp b/src/axom/primal/tests/primal_integral.cpp index a50369c0cc..673fda35fd 100644 --- a/src/axom/primal/tests/primal_integral.cpp +++ b/src/axom/primal/tests/primal_integral.cpp @@ -5,6 +5,7 @@ // SPDX-License-Identifier: (BSD-3-Clause) #include "axom/primal.hpp" +#include "axom/primal/operators/detail/evaluate_integral_impl.hpp" #include "axom/slic.hpp" #include "axom/fmt.hpp" #include @@ -683,6 +684,21 @@ TEST(primal_integral, evaluate_trimmed_nurbs_patch_surface_integral_clamps_bound EXPECT_FALSE(saw_clamped_node); } +TEST(primal_integral, trimming_curve_array_lower_bound_includes_all_first_control_points) +{ + using Point2D = primal::Point; + using TrimmingCurve = primal::NURBSCurve; + + axom::Array curve0_pts {Point2D {0.0, 0.5}, Point2D {1.0, 0.6}}; + axom::Array curve1_pts {Point2D {0.0, -1.0}, Point2D {1.0, 1.0}}; + + axom::Array curves; + curves.push_back(TrimmingCurve(curve0_pts, 1)); + curves.push_back(TrimmingCurve(curve1_pts, 1)); + + EXPECT_DOUBLE_EQ(primal::detail::curve_array_lower_bound_y(curves), -1.0); +} + TEST(primal_integral, evaluate_integral_nurbs_gwn_cache) { using Point2D = primal::Point; From 0d123849a5dd96dc4c6cc69ca5f4e444a925ee8d Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Mon, 23 Mar 2026 18:35:45 -0700 Subject: [PATCH 063/986] Loosens tolerance on comparison b/w Axom and MFEM Gaussian quadrature weights There were ULP-level differences when compiling with `-march=native`. --- src/axom/primal/tests/primal_integral.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/axom/primal/tests/primal_integral.cpp b/src/axom/primal/tests/primal_integral.cpp index 673fda35fd..82e3cdbbd4 100644 --- a/src/axom/primal/tests/primal_integral.cpp +++ b/src/axom/primal/tests/primal_integral.cpp @@ -760,8 +760,12 @@ TEST(primal_integral, evaluate_integral_nurbs_gwn_cache) #ifdef AXOM_USE_MFEM TEST(primal_integral, check_axom_mfem_quadrature_values) { - const int N = 200; + // MFEM and Axom both generate Gauss-Legendre rules, but in builds that enable + // `-march=native` we can see ULP-level differences in the computed nodes/weights + // even though the rules are equivalent for integration purposes. + const double fp_tol = 8 * axom::numeric_limits::epsilon(); + constexpr int N = 200; for(int npts = 1; npts <= N; ++npts) { // Generate the Axom quadrature rule @@ -774,12 +778,8 @@ TEST(primal_integral, check_axom_mfem_quadrature_values) // Check that the nodes and weights are the same between the two rules for(int j = 0; j < npts; ++j) { - EXPECT_NEAR(axom_rule.node(j), mfem_rule.IntPoint(j).x, axom::numeric_limits::epsilon()); - - // Relax tolerance slightly for intel-oneapi - EXPECT_NEAR(axom_rule.weight(j), - mfem_rule.IntPoint(j).weight, - 10 * axom::numeric_limits::epsilon()); + EXPECT_NEAR(axom_rule.node(j), mfem_rule.IntPoint(j).x, fp_tol); + EXPECT_NEAR(axom_rule.weight(j), mfem_rule.IntPoint(j).weight, fp_tol); } } } From 8bcb835e83307c6b5e09988fdbf29c03cff9ee41 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Mon, 23 Mar 2026 18:49:47 -0700 Subject: [PATCH 064/986] Removes extraneous doxygen group annotation --- src/axom/primal/operators/detail/evaluate_integral_impl.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/axom/primal/operators/detail/evaluate_integral_impl.hpp b/src/axom/primal/operators/detail/evaluate_integral_impl.hpp index d5187caaca..0fd5d0df51 100644 --- a/src/axom/primal/operators/detail/evaluate_integral_impl.hpp +++ b/src/axom/primal/operators/detail/evaluate_integral_impl.hpp @@ -278,8 +278,6 @@ inline T evaluate_vector_line_integral_component(const NURBSCurveGWNCache& nc } //@} -//@} - ///@{ /// \name Evaluates scalar-field 2D area integrals for functions f : R^2 -> R^m From aeef0a14313a4320b23872ceba8c6b0c860346ae Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 7 Apr 2026 17:49:32 -0700 Subject: [PATCH 065/986] Refactors 2D and 3D integrals into separate files This avoids circular dependency issue between NURBSPatch and the 2D integral functions. --- RELEASE-NOTES.md | 12 +- src/axom/primal/CMakeLists.txt | 4 + src/axom/primal/geometry/CurvedPolygon.hpp | 2 +- src/axom/primal/geometry/NURBSPatch.hpp | 3 +- .../detail/evaluate_integral_curve_impl.hpp | 296 +++++++ .../detail/evaluate_integral_impl.hpp | 600 +------------- .../detail/evaluate_integral_surface_impl.hpp | 216 +++++ .../primal/operators/evaluate_integral.hpp | 742 +----------------- .../operators/evaluate_integral_curve.hpp | 367 +++++++++ .../operators/evaluate_integral_surface.hpp | 409 ++++++++++ src/axom/quest/examples/CMakeLists.txt | 1 - 11 files changed, 1315 insertions(+), 1337 deletions(-) create mode 100644 src/axom/primal/operators/detail/evaluate_integral_curve_impl.hpp create mode 100644 src/axom/primal/operators/detail/evaluate_integral_surface_impl.hpp create mode 100644 src/axom/primal/operators/evaluate_integral_curve.hpp create mode 100644 src/axom/primal/operators/evaluate_integral_surface.hpp diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index f0797dca2e..0c94b673d5 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -19,7 +19,7 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ ## [Unreleased] - Release date yyyy-mm-dd ### Added -- Primal: Functions to evaluate surface and volume integrals over collections of `BezierPatch` and `NURBSPatch` objects +- Primal: Functions to evaluate surface and volume integrals over collections of `BezierPatch` and `NURBSPatch` objects. - Quest: An example to evaluate surface and volume integrals over a STEP model and to fit a ellipsoid or oriented bounding box to a model based on its low order moments. @@ -57,11 +57,8 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ - Core: the `ArrayView` class was modified so it defers initializing an internal allocator id via Umpire, if present. This prevents excessive calls to Umpire, which are not needed in all use cases. - -### Removed - -### Deprecated - +- Quest: Fixed a compilation problem with `-DAXOM_NO_INT64_T=1` +- Quest: `STEPReader` now catches additional edge cases related to orientation of OpenCascade primitives. ## [Version 0.13.0] - Release date 2026-02-05 @@ -1429,7 +1426,8 @@ fractions for the associated materials must be supplied before shaping. - Use this section in case of vulnerabilities -[Unreleased]: https://github.com/LLNL/axom/compare/v0.13.0...develop +[Unreleased]: https://github.com/LLNL/axom/compare/v0.14.0...develop +[Version 0.14.0]: https://github.com/LLNL/axom/compare/v0.13.0...v0.14.0 [Version 0.13.0]: https://github.com/LLNL/axom/compare/v0.12.0...v0.13.0 [Version 0.12.0]: https://github.com/LLNL/axom/compare/v0.11.0...v0.12.0 [Version 0.11.0]: https://github.com/LLNL/axom/compare/v0.10.1...v0.11.0 diff --git a/src/axom/primal/CMakeLists.txt b/src/axom/primal/CMakeLists.txt index ca3e591070..6fb85e2205 100644 --- a/src/axom/primal/CMakeLists.txt +++ b/src/axom/primal/CMakeLists.txt @@ -54,6 +54,8 @@ set( primal_headers operators/clip.hpp operators/closest_point.hpp operators/evaluate_integral.hpp + operators/evaluate_integral_curve.hpp + operators/evaluate_integral_surface.hpp operators/intersect.hpp operators/intersection_volume.hpp operators/orientation.hpp @@ -71,6 +73,8 @@ set( primal_headers operators/detail/clip_impl.hpp operators/detail/compute_moments_impl.hpp operators/detail/evaluate_integral_impl.hpp + operators/detail/evaluate_integral_curve_impl.hpp + operators/detail/evaluate_integral_surface_impl.hpp operators/detail/fuzzy_comparators.hpp operators/detail/intersect_bezier_impl.hpp operators/detail/intersect_bounding_box_impl.hpp diff --git a/src/axom/primal/geometry/CurvedPolygon.hpp b/src/axom/primal/geometry/CurvedPolygon.hpp index 52a61218b8..f543d3106c 100644 --- a/src/axom/primal/geometry/CurvedPolygon.hpp +++ b/src/axom/primal/geometry/CurvedPolygon.hpp @@ -23,7 +23,7 @@ #include "axom/primal/geometry/BoundingBox.hpp" // For NURBSCurveGWNCache objects -#include "axom/primal/operators/detail/winding_number_3d_memoization.hpp" +#include "axom/primal/operators/detail/winding_number_2d_memoization.hpp" #include #include diff --git a/src/axom/primal/geometry/NURBSPatch.hpp b/src/axom/primal/geometry/NURBSPatch.hpp index eed10b824d..bf4e8b233d 100644 --- a/src/axom/primal/geometry/NURBSPatch.hpp +++ b/src/axom/primal/geometry/NURBSPatch.hpp @@ -26,6 +26,7 @@ #include "axom/primal/geometry/OrientedBoundingBox.hpp" #include "axom/primal/operators/squared_distance.hpp" +#include "axom/primal/operators/evaluate_integral_curve.hpp" #include "axom/primal/operators/detail/winding_number_2d_impl.hpp" #include "axom/primal/operators/detail/intersect_bezier_impl.hpp" @@ -4192,4 +4193,4 @@ template struct axom::fmt::formatter> : ostream_formatter { }; -#endif // AXOM_PRIMAL_NURBSPATCH_HPP_ \ No newline at end of file +#endif // AXOM_PRIMAL_NURBSPATCH_HPP_ diff --git a/src/axom/primal/operators/detail/evaluate_integral_curve_impl.hpp b/src/axom/primal/operators/detail/evaluate_integral_curve_impl.hpp new file mode 100644 index 0000000000..69b7ddf0e9 --- /dev/null +++ b/src/axom/primal/operators/detail/evaluate_integral_curve_impl.hpp @@ -0,0 +1,296 @@ +// 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) + +/*! + * \file evaluate_integral_curve_impl.hpp + * + * \brief Implementation helpers for curve/area integral evaluation. + * + * \note This header intentionally avoids including surface/volume (patch) + * dependencies to prevent circular include chains (e.g. with NURBSPatch). + */ + +#ifndef PRIMAL_EVAL_INTEGRAL_CURVE_IMPL_HPP_ +#define PRIMAL_EVAL_INTEGRAL_CURVE_IMPL_HPP_ + +// Axom includes +#include "axom/core.hpp" +#include "axom/config.hpp" +#include "axom/slic.hpp" + +#include "axom/core/utilities/Utilities.hpp" +#include "axom/primal/geometry/Point.hpp" +#include "axom/primal/geometry/Vector.hpp" +#include "axom/primal/geometry/BezierCurve.hpp" +#include "axom/primal/geometry/NURBSCurve.hpp" +#include "axom/primal/operators/detail/winding_number_2d_memoization.hpp" + +#include "axom/core/numerics/quadrature.hpp" + +// C++ includes +#include +#include +#include + +namespace axom +{ +namespace primal +{ +namespace detail +{ +namespace internal +{ +template +struct has_addition : std::false_type +{ }; + +template +struct has_addition() + std::declval())>> : std::true_type +{ }; + +template +struct has_scalar_multiplication : std::false_type +{ }; + +template +struct has_scalar_multiplication() * std::declval())>> + : std::true_type +{ }; + +template +using is_integrable = std::conjunction, has_scalar_multiplication>; + +template +constexpr bool is_integrable_v = is_integrable::value; +} // namespace internal + +///@{ +/// \name Scalar-field line integrals + +template ::PointType>> +inline LambdaRetType evaluate_line_integral_component(const BezierCurve& c, + Lambda&& integrand, + const int npts) +{ + const axom::numerics::QuadratureRule& quad = axom::numerics::get_gauss_legendre(npts); + + LambdaRetType full_quadrature = LambdaRetType {}; + for(int q = 0; q < npts; q++) + { + auto x_q = c.evaluate(quad.node(q)); + auto dx_q = c.dt(quad.node(q)); + + full_quadrature += quad.weight(q) * integrand(x_q) * dx_q.norm(); + } + + return full_quadrature; +} + +template ::PointType>> +inline LambdaRetType evaluate_line_integral_component(const NURBSCurve& n, + Lambda&& integrand, + const int npts) +{ + LambdaRetType total_integral = LambdaRetType {}; + for(const auto& bez : n.extractBezier()) + { + total_integral += + detail::evaluate_line_integral_component(bez, std::forward(integrand), npts); + } + return total_integral; +} + +template ::PointType>> +inline LambdaRetType evaluate_line_integral_component(const NURBSCurveGWNCache& nc, + Lambda&& integrand, + const int npts) +{ + LambdaRetType total_integral = LambdaRetType {}; + for(int i = 0; i < nc.getNumKnotSpans(); ++i) + { + const auto& this_bezier_data = nc.getSubdivisionData(i, 0, 0); + total_integral += detail::evaluate_line_integral_component(this_bezier_data.getCurve(), + std::forward(integrand), + npts); + } + + return total_integral; +} +//@} + +///@{ +/// \name Vector-field line integrals + +template +inline T evaluate_vector_line_integral_component(const primal::BezierCurve& c, + Lambda&& vector_integrand, + const int npts) +{ + const axom::numerics::QuadratureRule& quad = axom::numerics::get_gauss_legendre(npts); + + T full_quadrature = T {}; + for(int q = 0; q < npts; q++) + { + auto x_q = c.evaluate(quad.node(q)); + auto dx_q = c.dt(quad.node(q)); + auto func_val = vector_integrand(x_q); + + full_quadrature += quad.weight(q) * Vector::dot_product(func_val, dx_q); + } + + return full_quadrature; +} + +template +inline T evaluate_vector_line_integral_component(const primal::NURBSCurve& n, + Lambda&& vector_integrand, + const int npts) +{ + T total_integral = T {}; + for(const auto& bez : n.extractBezier()) + { + total_integral += + detail::evaluate_vector_line_integral_component(bez, + std::forward(vector_integrand), + npts); + } + return total_integral; +} + +template +inline T evaluate_vector_line_integral_component(const NURBSCurveGWNCache& nc, + Lambda&& vector_integrand, + const int npts) +{ + T total_integral = T {}; + for(int i = 0; i < nc.getNumKnotSpans(); ++i) + { + const auto& this_bezier_data = nc.getSubdivisionData(i, 0, 0); + total_integral += + detail::evaluate_vector_line_integral_component(this_bezier_data.getCurve(), + std::forward(vector_integrand), + npts); + } + + return total_integral; +} +//@} + +///@{ +/// \name Scalar-field 2D area integrals + +template ::PointType>> +inline LambdaRetType evaluate_area_integral_component(const primal::BezierCurve& c, + Lambda&& integrand, + double int_lb, + const int npts_Q, + const int npts_P) +{ + const axom::numerics::QuadratureRule& quad_Q = axom::numerics::get_gauss_legendre(npts_Q); + const axom::numerics::QuadratureRule& quad_P = axom::numerics::get_gauss_legendre(npts_P); + + LambdaRetType full_quadrature = LambdaRetType {}; + for(int q = 0; q < npts_Q; q++) + { + auto x_q = c.evaluate(quad_Q.node(q)); + + for(int xi = 0; xi < npts_P; xi++) + { + auto x_qxi = Point({x_q[0], (x_q[1] - int_lb) * quad_P.node(xi) + int_lb}); + + auto antiderivative = quad_P.weight(xi) * (x_q[1] - int_lb) * integrand(x_qxi); + + full_quadrature += quad_Q.weight(q) * c.dt(quad_Q.node(q))[0] * -antiderivative; + } + } + + return full_quadrature; +} + +template ::PointType>> +inline LambdaRetType evaluate_area_integral_component(const primal::NURBSCurve& n, + Lambda&& integrand, + double int_lb, + const int npts_Q, + const int npts_P) +{ + LambdaRetType total_integral = LambdaRetType {}; + for(const auto& bez : n.extractBezier()) + { + total_integral += detail::evaluate_area_integral_component(bez, + std::forward(integrand), + int_lb, + npts_Q, + npts_P); + } + return total_integral; +} + +template ::PointType>> +inline RetType evaluate_area_integral_component(const NURBSCurveGWNCache& nc, + Lambda&& integrand, + double int_lb, + const int npts_Q, + const int npts_P) +{ + RetType total_integral = RetType {}; + for(int i = 0; i < nc.getNumKnotSpans(); ++i) + { + const auto& this_bezier_data = nc.getSubdivisionData(i, 0, 0); + total_integral += detail::evaluate_area_integral_component(this_bezier_data.getCurve(), + std::forward(integrand), + int_lb, + npts_Q, + npts_P); + } + + return total_integral; +} +//@} + +///@{ +/// \name Helper routines for patch-based integrals in 3D + +template +inline typename CurveType::NumericType curve_array_lower_bound_y(const axom::Array& carray) +{ + using T = typename CurveType::NumericType; + + SLIC_ASSERT(!carray.empty() && carray[0].getNumControlPoints() > 0); + + T lower_bound_y = carray[0][0][1]; + for(int i = 0; i < carray.size(); ++i) + { + for(int j = 0; j < carray[i].getNumControlPoints(); ++j) + { + lower_bound_y = axom::utilities::min(lower_bound_y, carray[i][j][1]); + } + } + + return lower_bound_y; +} + +//@} + +} // end namespace detail +} // end namespace primal +} // end namespace axom + +#endif diff --git a/src/axom/primal/operators/detail/evaluate_integral_impl.hpp b/src/axom/primal/operators/detail/evaluate_integral_impl.hpp index 0fd5d0df51..99e69eebbb 100644 --- a/src/axom/primal/operators/detail/evaluate_integral_impl.hpp +++ b/src/axom/primal/operators/detail/evaluate_integral_impl.hpp @@ -5,606 +5,18 @@ // SPDX-License-Identifier: (BSD-3-Clause) /*! - * \file evaluate_integral.hpp + * \file evaluate_integral_impl.hpp * - * \brief Consists of methods that evaluate scalar-field integrals on curves and - * regions defined by 2D curves, and vector-field integrals on curves + * \brief Header to include implementation files for evaluating integrals * - * All integrals are evaluated numerically with Gauss-Legendre quadrature - * - * Scalar-field line integrals and scalar-field area integrals are of form - * int_D f(x) dr, with f : R^n -> R^m, D is a curve or a 2D region bound by curves - * - * Vector-field line integrals are of form int_C f(x) \cdot d\vec{r}, - * with f : R^n -> R^n, C is a curve - * - * 2D area integrals computed with "Spectral Mesh-Free Quadrature for Planar - * Regions Bounded by Rational Parametric Curves" by David Gunderman et al. + * Splitting the implementation avoids circular include dependencies when patches + * internally evaluate curve-based integrals (e.g. via trimming curves). */ #ifndef PRIMAL_EVAL_INTEGRAL_IMPL_HPP_ #define PRIMAL_EVAL_INTEGRAL_IMPL_HPP_ -// Axom includes -#include "axom/config.hpp" // for compile-time configuration options -#include "axom/primal/geometry/Point.hpp" -#include "axom/primal/geometry/Vector.hpp" -#include "axom/primal/geometry/BezierCurve.hpp" -#include "axom/primal/geometry/BezierPatch.hpp" -#include "axom/primal/geometry/NURBSCurve.hpp" -#include "axom/primal/geometry/NURBSPatch.hpp" -#include "axom/primal/operators/detail/winding_number_2d_memoization.hpp" - -#include "axom/core/numerics/quadrature.hpp" - -// C++ includes -#include -#include -#include -#include - -namespace axom -{ -namespace primal -{ -namespace detail -{ -namespace internal -{ -///@{ -/// \name Type traits to support integrals of functions with general return types, -/// provided it supports addition and scalar multiplication -template -struct has_addition : std::false_type -{ }; - -template -struct has_addition() + std::declval())>> : std::true_type -{ }; - -template -struct has_scalar_multiplication : std::false_type -{ }; - -template -struct has_scalar_multiplication() * std::declval())>> - : std::true_type -{ }; - -template -using is_integrable = std::conjunction, has_scalar_multiplication>; - -template -constexpr bool is_integrable_v = is_integrable::value; -///@} -} // namespace internal - -///@{ -/// \name Evaluates scalar-field line integrals for functions f : R^n -> R^m - -/*! - * \brief Evaluate a line integral on a single Bezier curve. - * - * Evaluate the line integral with Gauss-Legendre quadrature - * - * \tparam Lambda A callable type taking an CurveType's PointType and returning an integrable type - * \tparam LambdaRetType A type which supports addition and scalar multiplication - * \param [in] c the Bezier curve object - * \param [in] integrand the lambda function representing the integrand. - * \param [in] npts The number of quadrature points in the rule - * \return the value of the integral - */ -template ::PointType>> -inline LambdaRetType evaluate_line_integral_component(const BezierCurve& c, - Lambda&& integrand, - const int npts) -{ - const axom::numerics::QuadratureRule& quad = axom::numerics::get_gauss_legendre(npts); - - // Store/compute quadrature result - LambdaRetType full_quadrature = LambdaRetType {}; - for(int q = 0; q < npts; q++) - { - // Get intermediate quadrature point - // at which to evaluate tangent vector - auto x_q = c.evaluate(quad.node(q)); - auto dx_q = c.dt(quad.node(q)); - - full_quadrature += quad.weight(q) * integrand(x_q) * dx_q.norm(); - } - - return full_quadrature; -} - -/*! - * \brief Evaluate a line integral on a single NURBS curve. - * - * Decompose the NURBS curve into Bezier segments, then sum the integral on each - * - * \tparam Lambda A callable type taking an CurveType's PointType and returning an integrable type - * \tparam LambdaRetType The return type of Lambda, which must support addition and scalar multiplication - * \param [in] n The NURBS curve object - * \param [in] integrand the lambda function representing the integrand. - * \param [in] npts The number of quadrature points in the rule - * \return the value of the integral - */ -template ::PointType>> -inline LambdaRetType evaluate_line_integral_component(const NURBSCurve& n, - Lambda&& integrand, - const int npts) -{ - LambdaRetType total_integral = LambdaRetType {}; - for(const auto& bez : n.extractBezier()) - { - total_integral += - detail::evaluate_line_integral_component(bez, std::forward(integrand), npts); - } - return total_integral; -} - -/*! - * \brief Evaluate an integral on a single NURBS curve with cached data for GWN evaluation. - * - * The cache object has already decomposed the NURBS curve into Bezier segments, - * which are used to evaluate the integral over each - * - * \tparam Lambda A callable type taking an CurveType's PointType and returning an integrable type - * \tparam LambdaRetType A type which supports addition and scalar multiplication - * \param [in] n The NURBS curve object - * \param [in] integrand the lambda function representing the integrand. - * \param [in] npts The number of quadrature points in the rule - * \return the value of the integral - */ -template ::PointType>> -inline LambdaRetType evaluate_line_integral_component(const NURBSCurveGWNCache& nc, - Lambda&& integrand, - const int npts) -{ - LambdaRetType total_integral = LambdaRetType {}; - for(int i = 0; i < nc.getNumKnotSpans(); ++i) - { - // Assuming the cache is properly initialized, this operation will never add to the cache - const auto& this_bezier_data = nc.getSubdivisionData(i, 0, 0); - total_integral += detail::evaluate_line_integral_component(this_bezier_data.getCurve(), - std::forward(integrand), - npts); - } - - return total_integral; -} -//@} - -///@{ -/// \name Evaluates vector-field line integrals for functions f : R^n -> R^n - -/*! - * \brief Evaluate a vector field line integral on a single Bezier curve. - * - * Evaluate the vector field line integral with Gauss-Legendre quadrature - * - * \tparam Lambda A callable type taking an CurveType's PointType and returning its numeric type - * \param [in] c the Bezier curve object - * \param [in] vector_integrand the lambda function representing the integrand. - * \param [in] npts The number of quadrature points in the rule - * \return the value of the integral - */ -template -inline T evaluate_vector_line_integral_component(const primal::BezierCurve& c, - Lambda&& vector_integrand, - const int npts) -{ - const axom::numerics::QuadratureRule& quad = axom::numerics::get_gauss_legendre(npts); - - // Store/compute quadrature result - T full_quadrature = T {}; - for(int q = 0; q < npts; q++) - { - // Get intermediate quadrature point - // on which to evaluate dot product - auto x_q = c.evaluate(quad.node(q)); - auto dx_q = c.dt(quad.node(q)); - auto func_val = vector_integrand(x_q); - - full_quadrature += quad.weight(q) * Vector::dot_product(func_val, dx_q); - } - - return full_quadrature; -} - -/*! - * \brief Evaluate a vector field line integral on a single NURBS curve. - * - * Decompose the NURBS curve into Bezier segments, then sum the integral on each - * - * \tparam Lambda A callable type taking an CurveType's PointType and returning its numeric type - * \param [in] n The NURBS curve object - * \param [in] vector_integrand the lambda function representing the integrand. - * \param [in] npts The number of quadrature points in the rule - * \return the value of the integral - */ -template -inline T evaluate_vector_line_integral_component(const primal::NURBSCurve& n, - Lambda&& vector_integrand, - const int npts) -{ - T total_integral = T {}; - for(const auto& bez : n.extractBezier()) - { - total_integral += - detail::evaluate_vector_line_integral_component(bez, - std::forward(vector_integrand), - npts); - } - return total_integral; -} - -/*! - * \brief Evaluate the vector integral on a single NURBS curve with cached data for GWN evaluation. - * - * The cache object has already decomposed the NURBS curve into Bezier segments, - * which are used to evaluate the integral over each - * - * \tparam Lambda A callable type taking an CurveType's PointType and returning its numeric type - * \param [in] n The NURBS curve object - * \param [in] vector_integrand the lambda function representing the integrand. - * \param [in] npts The number of quadrature points in the rule - * \return the value of the integral - */ -template -inline T evaluate_vector_line_integral_component(const NURBSCurveGWNCache& nc, - Lambda&& vector_integrand, - const int npts) -{ - T total_integral = T {}; - for(int i = 0; i < nc.getNumKnotSpans(); ++i) - { - // Assuming the cache is properly initialized, this operation will never add to the cache - const auto& this_bezier_data = nc.getSubdivisionData(i, 0, 0); - total_integral += - detail::evaluate_vector_line_integral_component(this_bezier_data.getCurve(), - std::forward(vector_integrand), - npts); - } - - return total_integral; -} -//@} - -///@{ -/// \name Evaluates scalar-field 2D area integrals for functions f : R^2 -> R^m - -/*! - * \brief Evaluate the area integral across one component of the curved polygon. - * - * Intended to be called for each BezierCurve object in a curved polygon. - * Uses a Spectral Mesh-Free Quadrature derived from Green's theorem, evaluating - * the area integral as a line integral of the antiderivative over the curve. - * For algorithm details, see "Spectral Mesh-Free Quadrature for Planar - * Regions Bounded by Rational Parametric Curves" by David Gunderman et al. - * - * \tparam Lambda A callable type taking an CurveType's PointType and returning an integrable type - * \tparam LambdaRetType A type which supports addition and scalar multiplication - * \param [in] c The component Bezier curve - * \param [in] integrand The lambda function representing the scalar integrand. - * \param [in] The lower bound of integration for the antiderivatives - * \param [in] npts_Q The number of quadrature points for the line integral - * \param [in] npts_P The number of quadrature points for the antiderivative - * \return the value of the integral, which is mathematically meaningless. - */ -template ::PointType>> -LambdaRetType evaluate_area_integral_component(const primal::BezierCurve& c, - Lambda&& integrand, - double int_lb, - const int npts_Q, - const int npts_P) -{ - const axom::numerics::QuadratureRule& quad_Q = axom::numerics::get_gauss_legendre(npts_Q); - const axom::numerics::QuadratureRule& quad_P = axom::numerics::get_gauss_legendre(npts_P); - - // Store/compute quadrature result - LambdaRetType full_quadrature = LambdaRetType {}; - for(int q = 0; q < npts_Q; q++) - { - // Get intermediate quadrature point - // on which to evaluate antiderivative - auto x_q = c.evaluate(quad_Q.node(q)); - - // Evaluate the antiderivative at x_q, add it to full quadrature - for(int xi = 0; xi < npts_P; xi++) - { - // Define interior quadrature points - auto x_qxi = Point({x_q[0], (x_q[1] - int_lb) * quad_P.node(xi) + int_lb}); - - auto antiderivative = quad_P.weight(xi) * (x_q[1] - int_lb) * integrand(x_qxi); - - full_quadrature += quad_Q.weight(q) * c.dt(quad_Q.node(q))[0] * -antiderivative; - } - } - - return full_quadrature; -} - -/*! - * \brief Evaluate the area integral across one component NURBS of the region. - * - * Intended to be called for each NURBSCurve object bounding a closed 2D region. - * - * \tparam Lambda A callable type taking an CurveType's PointType and returning an integrable type - * \tparam LambdaRetType A type which supports addition and scalar multiplication - * \param [in] n The component NURBSCurve object - * \param [in] integrand The lambda function representing the scalar integrand. - * \param [in] int_lb The lower bound of integration for the antiderivatives - * \param [in] npts_Q The number of quadrature points for the line integral - * \param [in] npts_P The number of quadrature points for the antiderivative - * \return the value of the integral, which is mathematically meaningless. - */ -template ::PointType>> -LambdaRetType evaluate_area_integral_component(const primal::NURBSCurve& n, - Lambda&& integrand, - double int_lb, - const int npts_Q, - const int npts_P) -{ - LambdaRetType total_integral = LambdaRetType {}; - for(const auto& bez : n.extractBezier()) - { - total_integral += detail::evaluate_area_integral_component(bez, - std::forward(integrand), - int_lb, - npts_Q, - npts_P); - } - return total_integral; -} - -/*! - * \brief Evaluate the area integral across one component NURBSCurveGWNCache of the region. - * - * Intended to be called for each NURBSCurveGWNCache object bounding a closed 2D region. - * - * \tparam Lambda A callable type taking an CurveType's PointType and returning an integrable type - * \tparam RetType The return type of Lambda, which must support addition and scalar multiplication - * \param [in] nc The component NURBSCurveGWNCache object - * \param [in] integrand The lambda function representing the scalar integrand. - * \param [in] int_lb The lower bound of integration for the antiderivatives - * \param [in] npts_Q The number of quadrature points for the line integral - * \param [in] npts_P The number of quadrature points for the antiderivative - * \return the value of the integral, which is mathematically meaningless. - */ -template ::PointType>> -inline RetType evaluate_area_integral_component(const NURBSCurveGWNCache& nc, - Lambda&& integrand, - double int_lb, - const int npts_Q, - const int npts_P) -{ - RetType total_integral = RetType {}; - for(int i = 0; i < nc.getNumKnotSpans(); ++i) - { - // Assuming the cache is properly initialized, this operation will never add to the cache - const auto& this_bezier_data = nc.getSubdivisionData(i, 0, 0); - total_integral += detail::evaluate_area_integral_component(this_bezier_data.getCurve(), - std::forward(integrand), - int_lb, - npts_Q, - npts_P); - } - - return total_integral; -} -///@{ -/// \name Helper routines for patch-based integrals in 3D - -template -inline typename CurveType::NumericType curve_array_lower_bound_y(const axom::Array& carray) -{ - using T = typename CurveType::NumericType; - - SLIC_ASSERT(!carray.empty() && carray[0].getNumControlPoints() > 0); - - T lower_bound_y = carray[0][0][1]; - for(int i = 0; i < carray.size(); ++i) - { - for(int j = 0; j < carray[i].getNumControlPoints(); ++j) - { - lower_bound_y = axom::utilities::min(lower_bound_y, carray[i][j][1]); - } - } - - return lower_bound_y; -} - -template ::PointType>> -inline LambdaRetType evaluate_surface_integral_component(const primal::BezierPatch& b, - Lambda&& integrand, - const int npts) -{ - const axom::numerics::QuadratureRule& quad = axom::numerics::get_gauss_legendre(npts); - - LambdaRetType full_quadrature = LambdaRetType {}; - for(int qu = 0; qu < npts; ++qu) - { - for(int qv = 0; qv < npts; ++qv) - { - const auto x_q = b.evaluate(quad.node(qu), quad.node(qv)); - const auto n_q = b.normal(quad.node(qu), quad.node(qv)); - - full_quadrature += quad.weight(qu) * quad.weight(qv) * integrand(x_q) * n_q.norm(); - } - } - - return full_quadrature; -} - -template ::PointType>> -inline LambdaRetType evaluate_surface_integral_component(const primal::NURBSPatch& n, - Lambda&& integrand, - const int npts_Q, - const int npts_P) -{ - if(!n.isTrimmed()) - { - LambdaRetType total_integral = LambdaRetType {}; - for(const auto& bez : n.extractBezier()) - { - total_integral += detail::evaluate_surface_integral_component(bez, integrand, npts_Q); - } - - return total_integral; - } - - LambdaRetType total_integral = LambdaRetType {}; - for(const auto& split_patch : n.extractTrimmedBezier()) - { - const auto& curves = split_patch.getTrimmingCurves(); - if(curves.empty()) - { - continue; - } - - // clamp the lower bound to lie within the parametric space of the patch - const auto lower_bound_y = axom::utilities::clampVal(detail::curve_array_lower_bound_y(curves), - split_patch.getMinKnot_v(), - split_patch.getMaxKnot_v()); - for(int i = 0; i < curves.size(); ++i) - { - total_integral += detail::evaluate_area_integral_component( - curves[i], - [&split_patch, &integrand](Point uv) -> LambdaRetType { - const auto x_q = split_patch.evaluate(uv[0], uv[1]); - const auto n_q = split_patch.normal(uv[0], uv[1]); - return integrand(x_q) * n_q.norm(); - }, - lower_bound_y, - npts_Q, - npts_P); - } - } - - return total_integral; -} - -template ::PointType>> -inline LambdaRetType evaluate_volume_integral_component(const primal::BezierPatch& b, - Lambda&& integrand, - double int_lb, - const int npts_uv, - const int npts_z) -{ - const axom::numerics::QuadratureRule& quad_uv = axom::numerics::get_gauss_legendre(npts_uv); - const axom::numerics::QuadratureRule& quad_z = axom::numerics::get_gauss_legendre(npts_z); - - LambdaRetType full_quadrature = LambdaRetType {}; - for(int qu = 0; qu < npts_uv; ++qu) - { - for(int qv = 0; qv < npts_uv; ++qv) - { - const auto x_q = b.evaluate(quad_uv.node(qu), quad_uv.node(qv)); - const auto n_q = b.normal(quad_uv.node(qu), quad_uv.node(qv)); - - LambdaRetType antiderivative = LambdaRetType {}; - const T z_scale = x_q[2] - int_lb; - for(int qz = 0; qz < npts_z; ++qz) - { - const auto x_qz = Point({x_q[0], x_q[1], z_scale * quad_z.node(qz) + int_lb}); - antiderivative += quad_z.weight(qz) * z_scale * integrand(x_qz); - } - - full_quadrature += quad_uv.weight(qu) * quad_uv.weight(qv) * antiderivative * n_q[2]; - } - } - - return full_quadrature; -} - -template ::PointType>> -inline LambdaRetType evaluate_volume_integral_component(const primal::NURBSPatch& n, - Lambda&& integrand, - double int_lb, - const int npts_Q, - const int npts_P, - const int npts_Z) -{ - if(!n.isTrimmed()) - { - LambdaRetType total_integral = LambdaRetType {}; - for(const auto& bez : n.extractBezier()) - { - total_integral += - detail::evaluate_volume_integral_component(bez, integrand, int_lb, npts_Q, npts_Z); - } - - return total_integral; - } - - const axom::numerics::QuadratureRule& quad_z = axom::numerics::get_gauss_legendre(npts_Z); - - LambdaRetType total_integral = LambdaRetType {}; - for(const auto& split_patch : n.extractTrimmedBezier()) - { - const auto& curves = split_patch.getTrimmingCurves(); - if(curves.empty()) - { - continue; - } - - // clamp the lower bound to lie within the parametric space of the patch - const auto lower_bound_y = axom::utilities::clampVal(detail::curve_array_lower_bound_y(curves), - split_patch.getMinKnot_v(), - split_patch.getMaxKnot_v()); - for(int i = 0; i < curves.size(); ++i) - { - total_integral += detail::evaluate_area_integral_component( - curves[i], - [&split_patch, &integrand, &int_lb, &quad_z, &npts_Z](Point uv) -> LambdaRetType { - const auto x_q = split_patch.evaluate(uv[0], uv[1]); - const auto n_q = split_patch.normal(uv[0], uv[1]); - - LambdaRetType antiderivative = LambdaRetType {}; - const T z_scale = x_q[2] - int_lb; - for(int qz = 0; qz < npts_Z; ++qz) - { - const auto x_qz = Point({x_q[0], x_q[1], z_scale * quad_z.node(qz) + int_lb}); - antiderivative += quad_z.weight(qz) * z_scale * integrand(x_qz); - } - - return antiderivative * n_q[2]; - }, - lower_bound_y, - npts_Q, - npts_P); - } - } - - return total_integral; -} - -//@} - -} // end namespace detail -} // end namespace primal -} // end namespace axom +#include "axom/primal/operators/detail/evaluate_integral_curve_impl.hpp" +#include "axom/primal/operators/detail/evaluate_integral_surface_impl.hpp" #endif diff --git a/src/axom/primal/operators/detail/evaluate_integral_surface_impl.hpp b/src/axom/primal/operators/detail/evaluate_integral_surface_impl.hpp new file mode 100644 index 0000000000..78f9268de9 --- /dev/null +++ b/src/axom/primal/operators/detail/evaluate_integral_surface_impl.hpp @@ -0,0 +1,216 @@ +// 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) + +/*! + * \file evaluate_integral_surface_impl.hpp + * + * \brief Implementation helpers for surface/volume integral evaluation. + */ + +#ifndef PRIMAL_EVAL_INTEGRAL_SURFACE_IMPL_HPP_ +#define PRIMAL_EVAL_INTEGRAL_SURFACE_IMPL_HPP_ + +// Axom includes +#include "axom/core.hpp" +#include "axom/config.hpp" + +#include "axom/core/utilities/Utilities.hpp" +#include "axom/primal/geometry/BezierPatch.hpp" +#include "axom/primal/geometry/NURBSPatch.hpp" +#include "axom/primal/operators/detail/evaluate_integral_curve_impl.hpp" + +// C++ includes +#include +#include + +namespace axom +{ +namespace primal +{ +namespace detail +{ +///@{ +/// \name Scalar-field surface/volume integral components + +template ::PointType>> +inline LambdaRetType evaluate_surface_integral_component(const primal::BezierPatch& b, + Lambda&& integrand, + const int npts) +{ + const axom::numerics::QuadratureRule& quad = axom::numerics::get_gauss_legendre(npts); + + LambdaRetType full_quadrature = LambdaRetType {}; + for(int qu = 0; qu < npts; ++qu) + { + for(int qv = 0; qv < npts; ++qv) + { + const auto x_q = b.evaluate(quad.node(qu), quad.node(qv)); + const auto n_q = b.normal(quad.node(qu), quad.node(qv)); + + full_quadrature += quad.weight(qu) * quad.weight(qv) * integrand(x_q) * n_q.norm(); + } + } + + return full_quadrature; +} + +template ::PointType>> +inline LambdaRetType evaluate_surface_integral_component(const primal::NURBSPatch& n, + Lambda&& integrand, + const int npts_Q, + const int npts_P) +{ + if(!n.isTrimmed()) + { + LambdaRetType total_integral = LambdaRetType {}; + for(const auto& bez : n.extractBezier()) + { + total_integral += detail::evaluate_surface_integral_component(bez, integrand, npts_Q); + } + + return total_integral; + } + + LambdaRetType total_integral = LambdaRetType {}; + for(const auto& split_patch : n.extractTrimmedBezier()) + { + const auto& curves = split_patch.getTrimmingCurves(); + if(curves.empty()) + { + continue; + } + + // clamp the lower bound to lie within the parametric space of the patch + const auto lower_bound_y = axom::utilities::clampVal(detail::curve_array_lower_bound_y(curves), + split_patch.getMinKnot_v(), + split_patch.getMaxKnot_v()); + for(int i = 0; i < curves.size(); ++i) + { + total_integral += detail::evaluate_area_integral_component( + curves[i], + [&split_patch, &integrand](Point uv) -> LambdaRetType { + const auto x_q = split_patch.evaluate(uv[0], uv[1]); + const auto n_q = split_patch.normal(uv[0], uv[1]); + return integrand(x_q) * n_q.norm(); + }, + lower_bound_y, + npts_Q, + npts_P); + } + } + + return total_integral; +} + +template ::PointType>> +inline LambdaRetType evaluate_volume_integral_component(const primal::BezierPatch& b, + Lambda&& integrand, + double int_lb, + const int npts_uv, + const int npts_z) +{ + const axom::numerics::QuadratureRule& quad_uv = axom::numerics::get_gauss_legendre(npts_uv); + const axom::numerics::QuadratureRule& quad_z = axom::numerics::get_gauss_legendre(npts_z); + + LambdaRetType full_quadrature = LambdaRetType {}; + for(int qu = 0; qu < npts_uv; ++qu) + { + for(int qv = 0; qv < npts_uv; ++qv) + { + const auto x_q = b.evaluate(quad_uv.node(qu), quad_uv.node(qv)); + const auto n_q = b.normal(quad_uv.node(qu), quad_uv.node(qv)); + + LambdaRetType antiderivative = LambdaRetType {}; + const T z_scale = x_q[2] - int_lb; + for(int qz = 0; qz < npts_z; ++qz) + { + const auto x_qz = Point({x_q[0], x_q[1], z_scale * quad_z.node(qz) + int_lb}); + antiderivative += quad_z.weight(qz) * z_scale * integrand(x_qz); + } + + full_quadrature += quad_uv.weight(qu) * quad_uv.weight(qv) * antiderivative * n_q[2]; + } + } + + return full_quadrature; +} + +template ::PointType>> +inline LambdaRetType evaluate_volume_integral_component(const primal::NURBSPatch& n, + Lambda&& integrand, + double int_lb, + const int npts_Q, + const int npts_P, + const int npts_Z) +{ + if(!n.isTrimmed()) + { + LambdaRetType total_integral = LambdaRetType {}; + for(const auto& bez : n.extractBezier()) + { + total_integral += + detail::evaluate_volume_integral_component(bez, integrand, int_lb, npts_Q, npts_Z); + } + + return total_integral; + } + + const axom::numerics::QuadratureRule& quad_z = axom::numerics::get_gauss_legendre(npts_Z); + + LambdaRetType total_integral = LambdaRetType {}; + for(const auto& split_patch : n.extractTrimmedBezier()) + { + const auto& curves = split_patch.getTrimmingCurves(); + if(curves.empty()) + { + continue; + } + + const auto lower_bound_y = axom::utilities::clampVal(detail::curve_array_lower_bound_y(curves), + split_patch.getMinKnot_v(), + split_patch.getMaxKnot_v()); + for(int i = 0; i < curves.size(); ++i) + { + total_integral += detail::evaluate_area_integral_component( + curves[i], + [&split_patch, &integrand, &int_lb, &quad_z, &npts_Z](Point uv) -> LambdaRetType { + const auto x_q = split_patch.evaluate(uv[0], uv[1]); + const auto n_q = split_patch.normal(uv[0], uv[1]); + + LambdaRetType antiderivative = LambdaRetType {}; + const T z_scale = x_q[2] - int_lb; + for(int qz = 0; qz < npts_Z; ++qz) + { + const auto x_qz = Point({x_q[0], x_q[1], z_scale * quad_z.node(qz) + int_lb}); + antiderivative += quad_z.weight(qz) * z_scale * integrand(x_qz); + } + + return antiderivative * n_q[2]; + }, + lower_bound_y, + npts_Q, + npts_P); + } + } + + return total_integral; +} + +//@} + +} // end namespace detail +} // end namespace primal +} // end namespace axom + +#endif diff --git a/src/axom/primal/operators/evaluate_integral.hpp b/src/axom/primal/operators/evaluate_integral.hpp index 1ad3350c54..1bc47377a4 100644 --- a/src/axom/primal/operators/evaluate_integral.hpp +++ b/src/axom/primal/operators/evaluate_integral.hpp @@ -7,744 +7,20 @@ /*! * \file evaluate_integral.hpp * - * \brief Consists of methods that evaluate scalar-field integrals on curves and - * regions defined by 2D curves, and vector-field integrals on curves + * \brief Header for including integral evaluation functions * - * All integrals are evaluated numerically with Gauss-Legendre quadrature - * - * Scalar-field line integrals and scalar-field area integrals are of form - * int_D f(x) dr, with f : R^n -> R^m, D is a curve or a 2D region bound by curves - * - * Vector-field line integrals are of form int_C f(x) \cdot d\vec{r}, - * with f : R^n -> R^n, C is a curve - * - * 2D area integrals computed with "Spectral Mesh-Free Quadrature for Planar - * Regions Bounded by Rational Parametric Curves" by David Gunderman et al. + * The API is split into: + * - evaluate_integral_curve.hpp (line/area) + * - evaluate_integral_surface.hpp (surface/volume) + * + * This split avoids circular include dependencies when patches internally + * evaluate curve-based integrals (e.g. via trimming curves). */ #ifndef PRIMAL_EVAL_INTEGRAL_HPP_ #define PRIMAL_EVAL_INTEGRAL_HPP_ -// Axom includes -#include "axom/config.hpp" - -#include "axom/core/utilities/Utilities.hpp" -#include "axom/primal/geometry/CurvedPolygon.hpp" -#include "axom/primal/operators/detail/evaluate_integral_impl.hpp" - -// C++ includes -#include - -namespace axom -{ -namespace primal -{ -///@{ -/// \name Evaluates scalar-field line integrals for functions f : R^n -> R^m - -/*! - * \brief Evaluate a line integral along the boundary of a CurvedPolygon object - * for a function with an arbitrary return type. - * - * The line integral is evaluated on each curve in the CurvedPolygon, and added - * together to represent the total integral. The curved polygon need not be connected. - * - * Evaluate the line integral with Gauss-Legendre quadrature - * - * \tparam CurveType The BezierCurve, NURBSCurve, or NURBSCurveGWNCache which represents the curve - * \tparam Lambda A callable type taking a CurveType's PointType and returning an integrable type - * \tparam LambdaRetType A type which supports addition and scalar multiplication - * \param [in] cpoly the CurvedPolygon object - * \param [in] integrand the lambda function representing the integrand. - * \param [in] npts the number of quadrature points to evaluate the line integral - * on each edge of the CurvedPolygon - * \return the value of the integral - */ -template > -LambdaRetType evaluate_line_integral(const primal::CurvedPolygon& cpoly, - Lambda&& integrand, - int npts) -{ - static_assert( - detail::internal::is_integrable_v, - "evaluate_integral methods require addition and scalar multiplication for lambda function " - "return type"); - - LambdaRetType total_integral = LambdaRetType {}; - for(int i = 0; i < cpoly.numEdges(); i++) - { - // Compute the line integral along each component. - total_integral += - detail::evaluate_line_integral_component(cpoly[i], std::forward(integrand), npts); - } - - return total_integral; -} - -/*! - * \brief Evaluate a line integral along the boundary of a generic curve - * for a function with an arbitrary return type. - * - * \tparam CurveType The BezierCurve, NURBSCurve, or NURBSCurveGWNCache which represents the curve - * \tparam Lambda A callable type taking a CurveType's PointType and returning an integrable type - * \tparam LambdaRetType A type which supports addition and scalar multiplication - * \param [in] c the generic curve object - * \param [in] integrand the lambda function representing the integrand. - * \param [in] npts the number of quadrature nodes - * \return the value of the integral - */ -template > -LambdaRetType evaluate_line_integral(const CurveType& c, Lambda&& integrand, int npts) -{ - static_assert( - detail::internal::is_integrable_v, - "evaluate_integral methods require addition and scalar multiplication for lambda function " - "return type"); - - return detail::evaluate_line_integral_component(c, std::forward(integrand), npts); -} - -/*! - * \brief Evaluate a line integral on an array of NURBS curves on a scalar field - * for a function with an arbitrary return type. - * - * \tparam CurveType The BezierCurve, NURBSCurve, or NURBSCurveGWNCache which represents the curve - * \tparam Lambda A callable type taking a CurveType's PointType and returning an integrable type - * \tparam LambdaRetType A type which supports addition and scalar multiplication - * \param [in] carray The array of generic curve objects - * \param [in] integrand the lambda function representing the integrand. - * \param [in] npts the number of quadrature nodes per curve per knot span - * - * \note Each NURBS curve is decomposed into Bezier segments, and the Gaussian quadrature - * is computed using npts on each segment - * - * \return the value of the integral - */ -template > -LambdaRetType evaluate_line_integral(const axom::Array& carray, Lambda&& integrand, int npts) -{ - static_assert( - detail::internal::is_integrable_v, - "evaluate_integral methods require addition and scalar multiplication for lambda function " - "return type"); - - LambdaRetType total_integral = LambdaRetType {}; - for(int i = 0; i < carray.size(); i++) - { - total_integral += - detail::evaluate_line_integral_component(carray[i], std::forward(integrand), npts); - } - - return total_integral; -} -//@} - -///@{ -/// \name Evaluates vector-field line integrals for functions f : R^n -> R^n - -/*! - * \brief Evaluate a vector-field line integral along the boundary of a CurvedPolygon object - * - * The line integral is evaluated on each curve in the CurvedPolygon, and added - * together to represent the total integral. The Polygon need not be connected. - * - * Evaluate the vector field line integral with Gauss-Legendre quadrature - * - * \tparam CurveType The BezierCurve, NURBSCurve, or NURBSCurveGWNCache which represents the curve - * \tparam Lambda A callable type taking a CurveType's PointType and returning its numeric type - * \tparam FuncRetType The CurveType's numeric type - * \param [in] cpoly the CurvedPolygon object - * \param [in] vector_integrand the lambda function representing the integrand. - * \param [in] npts the number of quadrature points to evaluate the line integral - * on each edge of the CurvedPolygon - * \pre Lambda must return the CurveTypes's vector type - * \return the value of the integral - */ -template -FuncRetType evaluate_vector_line_integral(const CurvedPolygon& cpoly, - Lambda&& vector_integrand, - int npts) -{ - FuncRetType total_integral = FuncRetType {}; - for(int i = 0; i < cpoly.numEdges(); i++) - { - // Compute the line integral along each component. - total_integral += - detail::evaluate_vector_line_integral_component(cpoly[i], - std::forward(vector_integrand), - npts); - } - - return total_integral; -} - -/*! - * \brief Evaluate a vector-field line integral on a single generic curve - * - * \tparam CurveType The BezierCurve, NURBSCurve, or NURBSCurveGWNCache which represents the curve - * \tparam Lambda A callable type taking a CurveType's PointType and returning its numeric type - * \tparam FuncRetType The CurveType's numeric type - * \param [in] c the generic curve object - * \param [in] vector_integrand the lambda function representing the integrand. - * \param [in] npts the number of quadrature nodes - * - * \pre Lambda must return the CurveTypes's vector type - * \return the value of the integral - */ -template -FuncRetType evaluate_vector_line_integral(const CurveType& c, Lambda&& vector_integrand, int npts) -{ - return detail::evaluate_vector_line_integral_component(c, - std::forward(vector_integrand), - npts); -} - -/*! - * \brief Evaluate a line integral on an array of generic curves on a vector field - * - * \tparam CurveType The BezierCurve, NURBSCurve, or NURBSCurveGWNCache which represents the curve - * \tparam Lambda A callable type taking a CurveType's PointType and returning its numeric type - * \tparam FuncRetType The CurveType's numeric type - * \param [in] carray The array of generic curve objects - * \param [in] vector_integrand the lambda function representing the integrand. - * \param [in] npts the number of quadrature nodes per curve per knot span - * - * \note Each NURBS curve is decomposed into Bezier segments, and the Gaussian quadrature - * is computed using npts on each segment - * - * \return the value of the integral - */ -template -FuncRetType evaluate_vector_line_integral(const axom::Array& carray, - Lambda&& vector_integrand, - int npts) -{ - FuncRetType total_integral = FuncRetType {}; - for(int i = 0; i < carray.size(); i++) - { - total_integral += - detail::evaluate_vector_line_integral_component(carray[i], - std::forward(vector_integrand), - npts); - } - - return total_integral; -} -//@} - -///@{ -/// \name Evaluates scalar-field 2D area integrals for functions f : R^2 -> R^m - -/*! - * \brief Evaluate an integral on the interior of a CurvedPolygon object. - * - * Evaluates the integral using a Spectral Mesh-Free Quadrature derived from - * Green's theorem, evaluating the area integral as a line integral of the - * antiderivative over each component curve. - * - * For algorithm details, see "Spectral Mesh-Free Quadrature for Planar - * Regions Bounded by Rational Parametric Curves" by David Gunderman et al. - * - * \tparam Lambda A callable type taking a CurveType's PointType and returning an integrable type - * \tparam CurveType The BezierCurve, NURBSCurve, or NURBSCurveGWNCache which represents the geometry - * \tparam LambdaRetType A type which supports addition and scalar multiplication - * \param [in] cpoly the CurvedPolygon object - * \param [in] integrand the lambda function representing the integrand. - * \param [in] npts_Q the number of quadrature points to evaluate the line integral - * \param [in] npts_P the number of quadrature points to evaluate the antiderivative - * \return the value of the integral - */ -template > -LambdaRetType evaluate_area_integral(const primal::CurvedPolygon& cpoly, - Lambda&& integrand, - int npts_Q, - int npts_P = 0) -{ - using T = typename CurveType::NumericType; - - static_assert( - detail::internal::is_integrable_v, - "evaluate_integral methods require addition and scalar multiplication for lambda function " - "return type"); - - if(npts_P <= 0) - { - npts_P = npts_Q; - } - - // Use minimum y-coord of control nodes as lower bound for integration - T lower_bound_y = cpoly[0][0][1]; - for(int i = 0; i < cpoly.numEdges(); i++) - { - for(int j = 1; j < cpoly[i].getOrder() + 1; j++) - { - lower_bound_y = axom::utilities::min(lower_bound_y, cpoly[i][j][1]); - } - } - - // Evaluate the antiderivative line integral along each component - LambdaRetType total_integral = LambdaRetType {}; - for(int i = 0; i < cpoly.numEdges(); i++) - { - total_integral += detail::evaluate_area_integral_component(cpoly[i], - std::forward(integrand), - lower_bound_y, - npts_Q, - npts_P); - } - - return total_integral; -} - -/*! - * \brief Evaluate an integral on the interior of a region bound by 2D curves - * - * See above definition for details. - * - * \tparam Lambda A callable type taking a CurveType's PointType and returning an integrable type - * \tparam CurveType The BezierCurve, NURBSCurve, or NURBSCurveGWNCache which represents the geometry - * \tparam LambdaRetType A type which supports addition and scalar multiplication - * \param [in] carray the array of generic curve objects that bound the region - * \param [in] integrand the lambda function representing the integrand. - * \param [in] npts_Q the number of quadrature points to evaluate the line integral - * \param [in] npts_P the number of quadrature points to evaluate the antiderivative - * - * \note The numerical result is only meaningful if the curves enclose a region - * - * \return the value of the integral - */ -template > -LambdaRetType evaluate_area_integral(const axom::Array& carray, - Lambda&& integrand, - int npts_Q, - int npts_P = 0) -{ - using T = typename CurveType::NumericType; - - static_assert( - detail::internal::is_integrable_v, - "evaluate_integral methods require addition and scalar multiplication for lambda function " - "return type"); - - if(npts_P <= 0) - { - npts_P = npts_Q; - } - - if(carray.empty()) - { - return LambdaRetType {}; - } - - // Use minimum y-coord of control nodes as lower bound for integration - T lower_bound_y = carray[0][0][1]; - for(int i = 0; i < carray.size(); i++) - { - for(int j = 1; j < carray[i].getNumControlPoints(); j++) - { - lower_bound_y = axom::utilities::min(lower_bound_y, carray[i][j][1]); - } - } - - // Evaluate the antiderivative line integral along each component - LambdaRetType total_integral = LambdaRetType {}; - for(int i = 0; i < carray.size(); i++) - { - for(const auto& bez : carray[i].extractBezier()) - { - total_integral += detail::evaluate_area_integral_component(bez, - std::forward(integrand), - lower_bound_y, - npts_Q, - npts_P); - } - } - - return total_integral; -} -//@} - -///@{ -/// \name Evaluates scalar-field surface integrals for functions f : R^3 -> R^m - -/*! - * \brief Evaluate a scalar surface integral on a single Bezier patch. - * - * Uses tensor-product Gauss-Legendre quadrature in the patch parameter space. - * - * \param [in] patch the Bezier patch - * \param [in] integrand callable representing the integrand - * \param [in] npts the number of quadrature points in each parametric direction - * - * \pre The patch parameterization must be valid on its full parameter domain. - */ -template ::PointType>> -LambdaRetType evaluate_surface_integral(const primal::BezierPatch& patch, - Lambda&& integrand, - int npts) -{ - static_assert(detail::internal::is_integrable_v, - "evaluate_integral methods require addition and scalar multiplication for lambda " - "function return type"); - - return detail::evaluate_surface_integral_component(patch, std::forward(integrand), npts); -} - -/*! - * \brief Evaluate a scalar surface integral on a single NURBS patch. - * - * Untrimmed patches are integrated by Bezier extraction followed by tensor-product - * Gauss-Legendre quadrature. Trimmed patches are integrated by reducing the - * parameter-space area integral to line integrals over the trimming curves. - * - * \param [in] patch the NURBS patch - * \param [in] integrand callable representing the integrand - * \param [in] npts_Q the number of quadrature points on each trimming curve or - * in each parametric direction for untrimmed Bezier pieces - * \param [in] npts_P the number of quadrature points used for numerical - * antidifferentiation in parameter space - * - * \pre The patch parameterization must be valid on its full parameter domain. - * \pre If the patch is trimmed, its trimming curves must bound the intended - * interior region in parameter space. - */ -template ::PointType>> -LambdaRetType evaluate_surface_integral(const primal::NURBSPatch& patch, - Lambda&& integrand, - int npts_Q, - int npts_P = 0) -{ - static_assert(detail::internal::is_integrable_v, - "evaluate_integral methods require addition and scalar multiplication for lambda " - "function return type"); - - if(npts_P <= 0) - { - npts_P = npts_Q; - } - - return detail::evaluate_surface_integral_component(patch, - std::forward(integrand), - npts_Q, - npts_P); -} - -/*! - * \brief Evaluate a scalar surface integral on a collection of Bezier patches. - * - * The result is the sum of the surface integrals over each patch in the array. - * - * \param [in] patches the patch collection - * \param [in] integrand callable representing the integrand - * \param [in] npts the number of quadrature points in each parametric direction - * - * \pre Each patch parameterization must be valid on its full parameter domain. - */ -template ::PointType>> -LambdaRetType evaluate_surface_integral(const axom::Array>& patches, - Lambda&& integrand, - int npts) -{ - static_assert(detail::internal::is_integrable_v, - "evaluate_integral methods require addition and scalar multiplication for lambda " - "function return type"); - - LambdaRetType total_integral = LambdaRetType {}; - for(int i = 0; i < patches.size(); ++i) - { - total_integral += detail::evaluate_surface_integral_component(patches[i], integrand, npts); - } - - return total_integral; -} - -/*! - * \brief Evaluate a scalar surface integral on a collection of NURBS patches. - * - * The result is the sum of the surface integrals over each patch in the array. - * - * \param [in] patches the patch collection - * \param [in] integrand callable representing the integrand - * \param [in] npts_Q the number of quadrature points on each trimming curve or - * in each parametric direction for untrimmed Bezier pieces - * \param [in] npts_P the number of quadrature points used for numerical - * antidifferentiation in parameter space - * - * \pre Each patch parameterization must be valid on its full parameter domain. - * \pre Any trimmed patch in the array must have trimming curves that bound its - * intended interior region in parameter space. - */ -template ::PointType>> -LambdaRetType evaluate_surface_integral(const axom::Array>& patches, - Lambda&& integrand, - int npts_Q, - int npts_P = 0) -{ - static_assert(detail::internal::is_integrable_v, - "evaluate_integral methods require addition and scalar multiplication for lambda " - "function return type"); - - if(npts_P <= 0) - { - npts_P = npts_Q; - } - - LambdaRetType total_integral = LambdaRetType {}; - for(int i = 0; i < patches.size(); ++i) - { - total_integral += - detail::evaluate_surface_integral_component(patches[i], integrand, npts_Q, npts_P); - } - - return total_integral; -} -//@} - -///@{ -/// \name Evaluates scalar-field volume integrals for functions f : R^3 -> R^m - -/*! - * \brief Evaluate a scalar volume-integral contribution from a single Bezier patch. - * - * This applies the Stokes-based reduction used for the full volume algorithm to - * one patch using a z-directed numerical antiderivative. - * - * \param [in] patch the Bezier patch - * \param [in] integrand callable representing the integrand - * \param [in] lower_bound_z the shared lower integration bound used for the - * z-directed antiderivative across the full boundary - * \param [in] npts_uv the number of quadrature points in each patch parameter direction - * \param [in] npts_z the number of quadrature points used for numerical - * antidifferentiation in z - * - * \pre The patch parameterization must be valid on its full parameter domain. - * \pre The returned value is geometrically meaningful as a volume contribution only - * when this patch is interpreted as part of a closed, consistently oriented - * boundary that uses the same lower integration bound. - */ -template ::PointType>> -LambdaRetType evaluate_volume_integral(const primal::BezierPatch& patch, - Lambda&& integrand, - T lower_bound_z, - int npts_uv, - int npts_z = 0) -{ - static_assert(detail::internal::is_integrable_v, - "evaluate_integral methods require addition and scalar multiplication for lambda " - "function return type"); - - if(npts_z <= 0) - { - npts_z = npts_uv; - } - - return detail::evaluate_volume_integral_component(patch, - std::forward(integrand), - lower_bound_z, - npts_uv, - npts_z); -} - -/*! - * \brief Evaluate a scalar volume-integral contribution from a single NURBS patch. - * - * Trimmed patches use the same Green/Stokes reduction as the surface-integral - * algorithm, combined with a z-directed numerical antiderivative for the volume - * reduction. - * - * \param [in] patch the NURBS patch - * \param [in] integrand callable representing the integrand - * \param [in] lower_bound_z the shared lower integration bound used for the - * z-directed antiderivative across the full boundary - * \param [in] npts_Q the number of quadrature points on each trimming curve or - * in each parametric direction for untrimmed Bezier pieces - * \param [in] npts_P the number of quadrature points used for numerical - * antidifferentiation in parameter space - * \param [in] npts_Z the number of quadrature points used for numerical - * antidifferentiation in z - * - * \pre The patch parameterization must be valid on its full parameter domain. - * \pre If the patch is trimmed, its trimming curves must bound the intended - * interior region in parameter space. - * \pre The returned value is geometrically meaningful as a volume contribution only - * when this patch is interpreted as part of a closed, consistently oriented - * boundary that uses the same lower integration bound. - */ -template ::PointType>> -LambdaRetType evaluate_volume_integral(const primal::NURBSPatch& patch, - Lambda&& integrand, - T lower_bound_z, - int npts_Q, - int npts_P = 0, - int npts_Z = 0) -{ - static_assert(detail::internal::is_integrable_v, - "evaluate_integral methods require addition and scalar multiplication for lambda " - "function return type"); - - if(npts_P <= 0) - { - npts_P = npts_Q; - } - if(npts_Z <= 0) - { - npts_Z = npts_Q; - } - - return detail::evaluate_volume_integral_component(patch, - std::forward(integrand), - lower_bound_z, - npts_Q, - npts_P, - npts_Z); -} - -/*! - * \brief Evaluate a scalar volume integral over a collection of Bezier patches. - * - * The result is obtained by summing the Stokes-based contribution from each - * patch in the collection. - * - * \param [in] patches the patch collection - * \param [in] integrand callable representing the integrand - * \param [in] npts_uv the number of quadrature points in each patch parameter direction - * \param [in] npts_z the number of quadrature points used for numerical - * antidifferentiation in z - * - * \pre Each patch parameterization must be valid on its full parameter domain. - * \pre The patch collection must represent a closed, consistently oriented - * boundary of the target volume. - */ -template ::PointType>> -LambdaRetType evaluate_volume_integral(const axom::Array>& patches, - Lambda&& integrand, - int npts_uv, - int npts_z = 0) -{ - static_assert(detail::internal::is_integrable_v, - "evaluate_integral methods require addition and scalar multiplication for lambda " - "function return type"); - - if(npts_z <= 0) - { - npts_z = npts_uv; - } - - if(patches.empty()) - { - return LambdaRetType {}; - } - - T lower_bound_z = patches[0].boundingBox().getMin()[2]; - for(int i = 1; i < patches.size(); ++i) - { - lower_bound_z = axom::utilities::min(lower_bound_z, patches[i].boundingBox().getMin()[2]); - } - - LambdaRetType total_integral = LambdaRetType {}; - for(int i = 0; i < patches.size(); ++i) - { - total_integral += - detail::evaluate_volume_integral_component(patches[i], integrand, lower_bound_z, npts_uv, npts_z); - } - - return total_integral; -} - -/*! - * \brief Evaluate a scalar volume integral over a collection of NURBS patches. - * - * The result is obtained by summing the Stokes-based contribution from each - * patch in the collection. - * - * \param [in] patches the patch collection - * \param [in] integrand callable representing the integrand - * \param [in] npts_Q the number of quadrature points on each trimming curve or - * in each parametric direction for untrimmed Bezier pieces - * \param [in] npts_P the number of quadrature points used for numerical - * antidifferentiation in parameter space - * \param [in] npts_Z the number of quadrature points used for numerical - * antidifferentiation in z - * - * \pre Each patch parameterization must be valid on its full parameter domain. - * \pre Any trimmed patch in the array must have trimming curves that bound its - * intended interior region in parameter space. - * \pre The patch collection must represent a closed, consistently oriented - * boundary of the target volume. - */ -template ::PointType>> -LambdaRetType evaluate_volume_integral(const axom::Array>& patches, - Lambda&& integrand, - int npts_Q, - int npts_P = 0, - int npts_Z = 0) -{ - static_assert(detail::internal::is_integrable_v, - "evaluate_integral methods require addition and scalar multiplication for lambda " - "function return type"); - - if(npts_P <= 0) - { - npts_P = npts_Q; - } - if(npts_Z <= 0) - { - npts_Z = npts_Q; - } - - if(patches.empty()) - { - return LambdaRetType {}; - } - - T lower_bound_z = patches[0].boundingBox().getMin()[2]; - for(int i = 1; i < patches.size(); ++i) - { - lower_bound_z = axom::utilities::min(lower_bound_z, patches[i].boundingBox().getMin()[2]); - } - - LambdaRetType total_integral = LambdaRetType {}; - for(int i = 0; i < patches.size(); ++i) - { - total_integral += detail::evaluate_volume_integral_component(patches[i], - integrand, - lower_bound_z, - npts_Q, - npts_P, - npts_Z); - } - - return total_integral; -} -//@} - -} // namespace primal -} // end namespace axom +#include "axom/primal/operators/evaluate_integral_curve.hpp" +#include "axom/primal/operators/evaluate_integral_surface.hpp" #endif diff --git a/src/axom/primal/operators/evaluate_integral_curve.hpp b/src/axom/primal/operators/evaluate_integral_curve.hpp new file mode 100644 index 0000000000..0bbd2be4d0 --- /dev/null +++ b/src/axom/primal/operators/evaluate_integral_curve.hpp @@ -0,0 +1,367 @@ +// 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) + +/*! + * \file evaluate_integral_curve.hpp + * + * \brief Consists of methods that evaluate scalar-field integrals on curves and + * regions defined by 2D curves, and vector-field integrals on curves + * + * All integrals are evaluated numerically with Gauss-Legendre quadrature + * + * Scalar-field line integrals and scalar-field area integrals are of the form + * int_D f(x) dr, with f : R^n -> R^m, D is a curve or a 2D region bound by curves + * + * Vector-field line integrals are of form int_C f(x) \cdot d\vec{r}, + * with f : R^n -> R^n, C is a curve + * + * 2D area integrals computed with "Spectral Mesh-Free Quadrature for Planar + * Regions Bounded by Rational Parametric Curves" by D. Gunderman et al. + * https://doi.org/10.1016/j.cad.2020.102944 + */ + +#ifndef PRIMAL_EVAL_INTEGRAL_CURVE_HPP_ +#define PRIMAL_EVAL_INTEGRAL_CURVE_HPP_ + +// Axom includes +#include "axom/core.hpp" +#include "axom/config.hpp" + +#include "axom/core/utilities/Utilities.hpp" +#include "axom/primal/geometry/CurvedPolygon.hpp" +#include "axom/primal/operators/detail/evaluate_integral_curve_impl.hpp" + +// C++ includes +#include + +namespace axom +{ +namespace primal +{ +///@{ +/// \name Evaluates scalar-field line integrals for functions f : R^n -> R^m + +/*! + * \brief Evaluate a line integral along the boundary of a CurvedPolygon object + * for a function with an arbitrary return type. + * + * The line integral is evaluated on each curve in the CurvedPolygon, and added + * together to represent the total integral. The curved polygon need not be connected. + * + * Evaluate the line integral with Gauss-Legendre quadrature + * + * \tparam CurveType The BezierCurve, NURBSCurve, or NURBSCurveGWNCache which represents the curve + * \tparam Lambda A callable type taking a CurveType's PointType and returning an integrable type + * \tparam LambdaRetType A type which supports addition and scalar multiplication + * \param [in] cpoly the CurvedPolygon object + * \param [in] integrand the lambda function representing the integrand. + * \param [in] npts the number of quadrature points to evaluate the line integral + * on each edge of the CurvedPolygon + * \return the value of the integral + */ +template > +LambdaRetType evaluate_line_integral(const primal::CurvedPolygon& cpoly, + Lambda&& integrand, + int npts) +{ + static_assert( + detail::internal::is_integrable_v, + "evaluate_integral methods require addition and scalar multiplication for lambda function " + "return type"); + + LambdaRetType total_integral = LambdaRetType {}; + for(int i = 0; i < cpoly.numEdges(); i++) + { + total_integral += + detail::evaluate_line_integral_component(cpoly[i], std::forward(integrand), npts); + } + + return total_integral; +} + +/*! + * \brief Evaluate a line integral along the boundary of a generic curve + * for a function with an arbitrary return type. + * + * \tparam CurveType The BezierCurve, NURBSCurve, or NURBSCurveGWNCache which represents the curve + * \tparam Lambda A callable type taking a CurveType's PointType and returning an integrable type + * \tparam LambdaRetType A type which supports addition and scalar multiplication + * \param [in] c the generic curve object + * \param [in] integrand the lambda function representing the integrand. + * \param [in] npts the number of quadrature nodes + * \return the value of the integral + */ +template > +LambdaRetType evaluate_line_integral(const CurveType& c, Lambda&& integrand, int npts) +{ + static_assert( + detail::internal::is_integrable_v, + "evaluate_integral methods require addition and scalar multiplication for lambda function " + "return type"); + + return detail::evaluate_line_integral_component(c, std::forward(integrand), npts); +} + +/*! + * \brief Evaluate a line integral on an array of NURBS curves on a scalar field + * for a function with an arbitrary return type. + * + * \tparam CurveType The BezierCurve, NURBSCurve, or NURBSCurveGWNCache which represents the curve + * \tparam Lambda A callable type taking a CurveType's PointType and returning an integrable type + * \tparam LambdaRetType A type which supports addition and scalar multiplication + * \param [in] carray The array of generic curve objects + * \param [in] integrand the lambda function representing the integrand. + * \param [in] npts the number of quadrature nodes per curve per knot span + * + * \note Each NURBS curve is decomposed into Bezier segments, and the Gaussian quadrature + * is computed using npts on each segment + * + * \return the value of the integral + */ +template > +LambdaRetType evaluate_line_integral(const axom::Array& carray, Lambda&& integrand, int npts) +{ + static_assert( + detail::internal::is_integrable_v, + "evaluate_integral methods require addition and scalar multiplication for lambda function " + "return type"); + + LambdaRetType total_integral = LambdaRetType {}; + for(int i = 0; i < carray.size(); i++) + { + total_integral += + detail::evaluate_line_integral_component(carray[i], std::forward(integrand), npts); + } + + return total_integral; +} +//@} + +///@{ +/// \name Evaluates vector-field line integrals for functions f : R^n -> R^n + +/*! + * \brief Evaluate a vector-field line integral along the boundary of a CurvedPolygon object + * + * The line integral is evaluated on each curve in the CurvedPolygon, and added + * together to represent the total integral. The Polygon need not be connected. + * + * Evaluate the vector field line integral with Gauss-Legendre quadrature + * + * \tparam CurveType The BezierCurve, NURBSCurve, or NURBSCurveGWNCache which represents the curve + * \tparam Lambda A callable type taking a CurveType's PointType and returning its numeric type + * \tparam FuncRetType The CurveType's numeric type + * \param [in] cpoly the CurvedPolygon object + * \param [in] vector_integrand the lambda function representing the integrand. + * \param [in] npts the number of quadrature points to evaluate the line integral + * on each edge of the CurvedPolygon + * \pre Lambda must return the CurveTypes's vector type + * \return the value of the integral + */ +template +FuncRetType evaluate_vector_line_integral(const CurvedPolygon& cpoly, + Lambda&& vector_integrand, + int npts) +{ + FuncRetType total_integral = FuncRetType {}; + for(int i = 0; i < cpoly.numEdges(); i++) + { + total_integral += + detail::evaluate_vector_line_integral_component(cpoly[i], + std::forward(vector_integrand), + npts); + } + + return total_integral; +} + +/*! + * \brief Evaluate a vector-field line integral on a single generic curve + * + * \tparam CurveType The BezierCurve, NURBSCurve, or NURBSCurveGWNCache which represents the curve + * \tparam Lambda A callable type taking a CurveType's PointType and returning its numeric type + * \tparam FuncRetType The CurveType's numeric type + * \param [in] c the generic curve object + * \param [in] vector_integrand the lambda function representing the integrand. + * \param [in] npts the number of quadrature nodes + * + * \pre Lambda must return the CurveTypes's vector type + * \return the value of the integral + */ +template +FuncRetType evaluate_vector_line_integral(const CurveType& c, Lambda&& vector_integrand, int npts) +{ + return detail::evaluate_vector_line_integral_component(c, + std::forward(vector_integrand), + npts); +} + +/*! + * \brief Evaluate a line integral on an array of generic curves on a vector field + * + * \tparam CurveType The BezierCurve, NURBSCurve, or NURBSCurveGWNCache which represents the curve + * \tparam Lambda A callable type taking a CurveType's PointType and returning its numeric type + * \tparam FuncRetType The CurveType's numeric type + * \param [in] carray The array of generic curve objects + * \param [in] vector_integrand the lambda function representing the integrand. + * \param [in] npts the number of quadrature nodes per curve per knot span + * + * \note Each NURBS curve is decomposed into Bezier segments, and the Gaussian quadrature + * is computed using npts on each segment + * + * \return the value of the integral + */ +template +FuncRetType evaluate_vector_line_integral(const axom::Array& carray, + Lambda&& vector_integrand, + int npts) +{ + FuncRetType total_integral = FuncRetType {}; + for(int i = 0; i < carray.size(); i++) + { + total_integral += + detail::evaluate_vector_line_integral_component(carray[i], + std::forward(vector_integrand), + npts); + } + + return total_integral; +} +//@} + +///@{ +/// \name Evaluates scalar-field 2D area integrals for functions f : R^2 -> R^m + +/*! + * \brief Evaluate an integral on the interior of a CurvedPolygon object. + * + * Evaluates the integral using a Spectral Mesh-Free Quadrature derived from + * Green's theorem, evaluating the area integral as a line integral of the + * antiderivative over each component curve. + * + * For algorithm details, see "Spectral Mesh-Free Quadrature for Planar + * Regions Bounded by Rational Parametric Curves" by David Gunderman et al. + * + * \tparam Lambda A callable type taking a CurveType's PointType and returning an integrable type + * \tparam CurveType The BezierCurve, NURBSCurve, or NURBSCurveGWNCache which represents the geometry + * \tparam LambdaRetType A type which supports addition and scalar multiplication + * \param [in] cpoly the CurvedPolygon object + * \param [in] integrand the lambda function representing the integrand. + * \param [in] npts_Q the number of quadrature points to evaluate the line integral + * \param [in] npts_P the number of quadrature points to evaluate the antiderivative + * \return the value of the integral + */ +template > +LambdaRetType evaluate_area_integral(const primal::CurvedPolygon& cpoly, + Lambda&& integrand, + int npts_Q, + int npts_P = 0) +{ + static_assert( + detail::internal::is_integrable_v, + "evaluate_integral methods require addition and scalar multiplication for lambda function " + "return type"); + + if(npts_P <= 0) + { + npts_P = npts_Q; + } + + LambdaRetType total_integral = LambdaRetType {}; + if(cpoly.numEdges() == 0) + { + return total_integral; + } + + auto lower_bound_y = cpoly[0][0][1]; + for(int i = 0; i < cpoly.numEdges(); ++i) + { + for(int j = 0; j < cpoly[i].getNumControlPoints(); ++j) + { + lower_bound_y = axom::utilities::min(lower_bound_y, cpoly[i][j][1]); + } + } + + for(int i = 0; i < cpoly.numEdges(); ++i) + { + total_integral += detail::evaluate_area_integral_component(cpoly[i], + std::forward(integrand), + lower_bound_y, + npts_Q, + npts_P); + } + + return total_integral; +} + +/*! + * \brief Evaluate an integral on the interior of a region bound by 2D curves + * + * See above definition for details. + * + * \tparam Lambda A callable type taking a CurveType's PointType and returning an integrable type + * \tparam CurveType The BezierCurve, NURBSCurve, or NURBSCurveGWNCache which represents the geometry + * \tparam LambdaRetType A type which supports addition and scalar multiplication + * \param [in] carray the array of generic curve objects that bound the region + * \param [in] integrand the lambda function representing the integrand. + * \param [in] npts_Q the number of quadrature points to evaluate the line integral + * \param [in] npts_P the number of quadrature points to evaluate the antiderivative + * + * \note The numerical result is only meaningful if the curves enclose a region + * + * \return the value of the integral + */ +template > +LambdaRetType evaluate_area_integral(const axom::Array& carray, + Lambda&& integrand, + int npts_Q, + int npts_P = 0) +{ + static_assert( + detail::internal::is_integrable_v, + "evaluate_integral methods require addition and scalar multiplication for lambda function " + "return type"); + + if(npts_P <= 0) + { + npts_P = npts_Q; + } + + LambdaRetType total_integral = LambdaRetType {}; + if(carray.empty()) + { + return total_integral; + } + + const auto lower_bound_y = detail::curve_array_lower_bound_y(carray); + + for(int i = 0; i < carray.size(); ++i) + { + total_integral += detail::evaluate_area_integral_component(carray[i], + std::forward(integrand), + lower_bound_y, + npts_Q, + npts_P); + } + + return total_integral; +} +//@} + +} // namespace primal +} // end namespace axom + +#endif diff --git a/src/axom/primal/operators/evaluate_integral_surface.hpp b/src/axom/primal/operators/evaluate_integral_surface.hpp new file mode 100644 index 0000000000..8ade0b431e --- /dev/null +++ b/src/axom/primal/operators/evaluate_integral_surface.hpp @@ -0,0 +1,409 @@ +// 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) + +/*! + * \file evaluate_integral_surface.hpp + * + * \brief Consists of methods that evaluate surface and volume integrals on + * surface patches and regions defined by collections of patches. + * + * All integrals are evaluated numerically with Gauss-Legendre quadrature + * + * 3D integrals computed with "High-accuracy mesh-free quadrature + * for trimmed parametric surfaces and volumes" by D. Gunderman et al. + * https://doi.org/10.1016/j.cad.2021.103093 + */ + +#ifndef PRIMAL_EVAL_INTEGRAL_SURFACE_HPP_ +#define PRIMAL_EVAL_INTEGRAL_SURFACE_HPP_ + +// Axom includes +#include "axom/core.hpp" +#include "axom/config.hpp" + +#include "axom/core/utilities/Utilities.hpp" +#include "axom/primal/geometry/BezierPatch.hpp" +#include "axom/primal/geometry/NURBSPatch.hpp" +#include "axom/primal/operators/detail/evaluate_integral_surface_impl.hpp" + +namespace axom +{ +namespace primal +{ +///@{ +/// \name Evaluates scalar-field surface integrals for functions f : R^3 -> R^m + +/*! + * \brief Evaluate a scalar surface integral on a single Bezier patch. + * + * Uses tensor-product Gauss-Legendre quadrature in the patch parameter space. + * + * \param [in] patch the Bezier patch + * \param [in] integrand callable representing the integrand + * \param [in] npts the number of quadrature points in each parametric direction + * + * \pre The patch parameterization must be valid on its full parameter domain. + */ +template ::PointType>> +LambdaRetType evaluate_surface_integral(const primal::BezierPatch& patch, + Lambda&& integrand, + int npts) +{ + static_assert(detail::internal::is_integrable_v, + "evaluate_integral methods require addition and scalar multiplication for lambda " + "function return type"); + + return detail::evaluate_surface_integral_component(patch, std::forward(integrand), npts); +} + +/*! + * \brief Evaluate a scalar surface integral on a single NURBS patch. + * + * Untrimmed patches are integrated by Bezier extraction followed by tensor-product + * Gauss-Legendre quadrature. Trimmed patches are integrated by reducing the + * parameter-space area integral to line integrals over the trimming curves. + * + * \param [in] patch the NURBS patch + * \param [in] integrand callable representing the integrand + * \param [in] npts_Q the number of quadrature points on each trimming curve or + * in each parametric direction for untrimmed Bezier pieces + * \param [in] npts_P the number of quadrature points used for numerical + * antidifferentiation in parameter space + * + * \pre The patch parameterization must be valid on its full parameter domain. + * \pre If the patch is trimmed, its trimming curves must bound the intended + * interior region in parameter space. + */ +template ::PointType>> +LambdaRetType evaluate_surface_integral(const primal::NURBSPatch& patch, + Lambda&& integrand, + int npts_Q, + int npts_P = 0) +{ + static_assert(detail::internal::is_integrable_v, + "evaluate_integral methods require addition and scalar multiplication for lambda " + "function return type"); + + if(npts_P <= 0) + { + npts_P = npts_Q; + } + + return detail::evaluate_surface_integral_component(patch, + std::forward(integrand), + npts_Q, + npts_P); +} + +/*! + * \brief Evaluate a scalar surface integral on a collection of Bezier patches. + * + * The result is the sum of the surface integrals over each patch in the array. + * + * \param [in] patches the patch collection + * \param [in] integrand callable representing the integrand + * \param [in] npts the number of quadrature points in each parametric direction + * + * \pre Each patch parameterization must be valid on its full parameter domain. + */ +template ::PointType>> +LambdaRetType evaluate_surface_integral(const axom::Array>& patches, + Lambda&& integrand, + int npts) +{ + static_assert(detail::internal::is_integrable_v, + "evaluate_integral methods require addition and scalar multiplication for lambda " + "function return type"); + + LambdaRetType total_integral = LambdaRetType {}; + for(int i = 0; i < patches.size(); ++i) + { + total_integral += detail::evaluate_surface_integral_component(patches[i], integrand, npts); + } + + return total_integral; +} + +/*! + * \brief Evaluate a scalar surface integral on a collection of NURBS patches. + * + * The result is the sum of the surface integrals over each patch in the array. + * + * \param [in] patches the patch collection + * \param [in] integrand callable representing the integrand + * \param [in] npts_Q the number of quadrature points on each trimming curve or + * in each parametric direction for untrimmed Bezier pieces + * \param [in] npts_P the number of quadrature points used for numerical + * antidifferentiation in parameter space + * + * \pre Each patch parameterization must be valid on its full parameter domain. + * \pre Any trimmed patch in the array must have trimming curves that bound its + * intended interior region in parameter space. + */ +template ::PointType>> +LambdaRetType evaluate_surface_integral(const axom::Array>& patches, + Lambda&& integrand, + int npts_Q, + int npts_P = 0) +{ + static_assert(detail::internal::is_integrable_v, + "evaluate_integral methods require addition and scalar multiplication for lambda " + "function return type"); + + if(npts_P <= 0) + { + npts_P = npts_Q; + } + + LambdaRetType total_integral = LambdaRetType {}; + for(int i = 0; i < patches.size(); ++i) + { + total_integral += + detail::evaluate_surface_integral_component(patches[i], integrand, npts_Q, npts_P); + } + + return total_integral; +} +//@} + +///@{ +/// \name Evaluates scalar-field volume integrals for functions f : R^3 -> R^m + +/*! + * \brief Evaluate a scalar volume-integral contribution from a single Bezier patch. + * + * This applies the Stokes-based reduction used for the full volume algorithm to + * one patch using a z-directed numerical antiderivative. + * + * \param [in] patch the Bezier patch + * \param [in] integrand callable representing the integrand + * \param [in] lower_bound_z the shared lower integration bound used for the + * z-directed antiderivative across the full boundary + * \param [in] npts_uv the number of quadrature points in each patch parameter direction + * \param [in] npts_z the number of quadrature points used for numerical + * antidifferentiation in z + * + * \pre The patch parameterization must be valid on its full parameter domain. + * \pre The returned value is geometrically meaningful as a volume contribution only + * when this patch is interpreted as part of a closed, consistently oriented + * boundary that uses the same lower integration bound. + */ +template ::PointType>> +LambdaRetType evaluate_volume_integral(const primal::BezierPatch& patch, + Lambda&& integrand, + T lower_bound_z, + int npts_uv, + int npts_z = 0) +{ + static_assert(detail::internal::is_integrable_v, + "evaluate_integral methods require addition and scalar multiplication for lambda " + "function return type"); + + if(npts_z <= 0) + { + npts_z = npts_uv; + } + + return detail::evaluate_volume_integral_component(patch, + std::forward(integrand), + lower_bound_z, + npts_uv, + npts_z); +} + +/*! + * \brief Evaluate a scalar volume-integral contribution from a single NURBS patch. + * + * Trimmed patches use the same Green/Stokes reduction as the surface-integral + * algorithm, combined with a z-directed numerical antiderivative for the volume + * reduction. + * + * \param [in] patch the NURBS patch + * \param [in] integrand callable representing the integrand + * \param [in] lower_bound_z the shared lower integration bound used for the + * z-directed antiderivative across the full boundary + * \param [in] npts_Q the number of quadrature points on each trimming curve or + * in each parametric direction for untrimmed Bezier pieces + * \param [in] npts_P the number of quadrature points used for numerical + * antidifferentiation in parameter space + * \param [in] npts_Z the number of quadrature points used for numerical + * antidifferentiation in z + * + * \pre The patch parameterization must be valid on its full parameter domain. + * \pre If the patch is trimmed, its trimming curves must bound the intended + * interior region in parameter space. + * \pre The returned value is geometrically meaningful as a volume contribution only + * when this patch is interpreted as part of a closed, consistently oriented + * boundary that uses the same lower integration bound. + */ +template ::PointType>> +LambdaRetType evaluate_volume_integral(const primal::NURBSPatch& patch, + Lambda&& integrand, + T lower_bound_z, + int npts_Q, + int npts_P = 0, + int npts_Z = 0) +{ + static_assert(detail::internal::is_integrable_v, + "evaluate_integral methods require addition and scalar multiplication for lambda " + "function return type"); + + if(npts_P <= 0) + { + npts_P = npts_Q; + } + if(npts_Z <= 0) + { + npts_Z = npts_Q; + } + + return detail::evaluate_volume_integral_component(patch, + std::forward(integrand), + lower_bound_z, + npts_Q, + npts_P, + npts_Z); +} + +/*! + * \brief Evaluate a scalar volume integral over a collection of Bezier patches. + * + * The result is obtained by summing the Stokes-based contribution from each + * patch in the collection. + * + * \param [in] patches the patch collection + * \param [in] integrand callable representing the integrand + * \param [in] npts_uv the number of quadrature points in each patch parameter direction + * \param [in] npts_z the number of quadrature points used for numerical + * antidifferentiation in z + * + * \pre Each patch parameterization must be valid on its full parameter domain. + * \pre The patch collection must represent a closed, consistently oriented + * boundary of the target volume. + */ +template ::PointType>> +LambdaRetType evaluate_volume_integral(const axom::Array>& patches, + Lambda&& integrand, + int npts_uv, + int npts_z = 0) +{ + static_assert(detail::internal::is_integrable_v, + "evaluate_integral methods require addition and scalar multiplication for lambda " + "function return type"); + + if(npts_z <= 0) + { + npts_z = npts_uv; + } + + if(patches.empty()) + { + return LambdaRetType {}; + } + + T lower_bound_z = patches[0].boundingBox().getMin()[2]; + for(int i = 1; i < patches.size(); ++i) + { + lower_bound_z = axom::utilities::min(lower_bound_z, patches[i].boundingBox().getMin()[2]); + } + + LambdaRetType total_integral = LambdaRetType {}; + for(int i = 0; i < patches.size(); ++i) + { + total_integral += + detail::evaluate_volume_integral_component(patches[i], integrand, lower_bound_z, npts_uv, npts_z); + } + + return total_integral; +} + +/*! + * \brief Evaluate a scalar volume integral over a collection of NURBS patches. + * + * The result is obtained by summing the Stokes-based contribution from each + * patch in the collection. + * + * \param [in] patches the patch collection + * \param [in] integrand callable representing the integrand + * \param [in] npts_Q the number of quadrature points on each trimming curve or + * in each parametric direction for untrimmed Bezier pieces + * \param [in] npts_P the number of quadrature points used for numerical + * antidifferentiation in parameter space + * \param [in] npts_Z the number of quadrature points used for numerical + * antidifferentiation in z + * + * \pre Each patch parameterization must be valid on its full parameter domain. + * \pre Any trimmed patch in the array must have trimming curves that bound its + * intended interior region in parameter space. + * \pre The patch collection must represent a closed, consistently oriented + * boundary of the target volume. + */ +template ::PointType>> +LambdaRetType evaluate_volume_integral(const axom::Array>& patches, + Lambda&& integrand, + int npts_Q, + int npts_P = 0, + int npts_Z = 0) +{ + static_assert(detail::internal::is_integrable_v, + "evaluate_integral methods require addition and scalar multiplication for lambda " + "function return type"); + + if(npts_P <= 0) + { + npts_P = npts_Q; + } + if(npts_Z <= 0) + { + npts_Z = npts_Q; + } + + if(patches.empty()) + { + return LambdaRetType {}; + } + + T lower_bound_z = patches[0].boundingBox().getMin()[2]; + for(int i = 1; i < patches.size(); ++i) + { + lower_bound_z = axom::utilities::min(lower_bound_z, patches[i].boundingBox().getMin()[2]); + } + + LambdaRetType total_integral = LambdaRetType {}; + for(int i = 0; i < patches.size(); ++i) + { + total_integral += detail::evaluate_volume_integral_component(patches[i], + integrand, + lower_bound_z, + npts_Q, + npts_P, + npts_Z); + } + + return total_integral; +} +//@} + +} // namespace primal +} // end namespace axom + +#endif diff --git a/src/axom/quest/examples/CMakeLists.txt b/src/axom/quest/examples/CMakeLists.txt index 8d570c8131..b9574c2cee 100644 --- a/src/axom/quest/examples/CMakeLists.txt +++ b/src/axom/quest/examples/CMakeLists.txt @@ -789,4 +789,3 @@ if(OPENCASCADE_FOUND) endif() endif() - From 026b77c5386de5218e7ed50443a82b4a8424ae33 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 7 Apr 2026 18:19:14 -0700 Subject: [PATCH 066/986] Use the quartic sphere w/ six patches instead of a single patch --- .../quest/tests/quest_step_quadrature.cpp | 32 ++----------------- 1 file changed, 3 insertions(+), 29 deletions(-) diff --git a/src/axom/quest/tests/quest_step_quadrature.cpp b/src/axom/quest/tests/quest_step_quadrature.cpp index c9574d82d1..8a32ad1ed9 100644 --- a/src/axom/quest/tests/quest_step_quadrature.cpp +++ b/src/axom/quest/tests/quest_step_quadrature.cpp @@ -62,31 +62,6 @@ void transform_patches(PatchArray& patches, const Matrix& transform) } } -PatchArray replicate_biquartic_sphere_faces(const PatchArray& base_patches) -{ - EXPECT_EQ(base_patches.size(), 1); - - PatchArray all_patches; - all_patches.push_back(base_patches[0]); - - const Matrix rot_pos_y = embed_linear_transform(axom::numerics::transforms::xRotation(M_PI / 2.0)); - const Matrix rot_neg_y = embed_linear_transform(axom::numerics::transforms::xRotation(-M_PI / 2.0)); - const Matrix rot_pos_z = embed_linear_transform(axom::numerics::transforms::xRotation(M_PI)); - const Matrix rot_neg_x = embed_linear_transform(axom::numerics::transforms::yRotation(M_PI / 2.0)); - const Matrix rot_pos_x = embed_linear_transform(axom::numerics::transforms::yRotation(-M_PI / 2.0)); - - const Matrix rotations[] = {rot_pos_y, rot_neg_y, rot_pos_z, rot_neg_x, rot_pos_x}; - for(const Matrix& rotation : rotations) - { - PatchArray rotated(1); - rotated[0] = base_patches[0]; - transform_patches(rotated, rotation); - all_patches.push_back(rotated[0]); - } - - return all_patches; -} - template void expect_matching_surface_values(const PatchArray& patches_a, const PatchArray& patches_b, @@ -142,10 +117,10 @@ TEST(quest_step_quadrature, sphere_models_match_unit_sphere_moments) const std::string step_dir = std::string(AXOM_DATA_DIR) + "/quest/step/"; auto revolved = read_step_patches(step_dir + "revolved_sphere.step"); - auto biquartic = read_step_patches(step_dir + "biquartic_sphere_surface.step"); - biquartic = replicate_biquartic_sphere_faces(biquartic); + auto biquartic = read_step_patches(step_dir + "biquartic_sphere.step"); EXPECT_EQ(revolved.size(), 1); + EXPECT_FALSE(biquartic.empty()); EXPECT_EQ(biquartic.size(), 6); // revolved_sphere.step is a radius-5 sphere, while the biquartic sphere patch is unit-radius. @@ -189,8 +164,7 @@ TEST(quest_step_quadrature, transformed_sphere_models_match_expected_moments) const std::string step_dir = std::string(AXOM_DATA_DIR) + "/quest/step/"; auto revolved = read_step_patches(step_dir + "revolved_sphere.step"); - auto biquartic = read_step_patches(step_dir + "biquartic_sphere_surface.step"); - biquartic = replicate_biquartic_sphere_faces(biquartic); + auto biquartic = read_step_patches(step_dir + "biquartic_sphere.step"); transform_patches(revolved, axom::numerics::transforms::scale(1.0 / 5.0, 4)); From 5fc274ef2eedb3433a9d8f24aa714680c81be415 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 12 Mar 2026 12:19:56 -0700 Subject: [PATCH 067/986] Adds option to step reader example to write out patch trimming curves as mfem file Uses new nurbs patch format, which was added to mfem after the 4.9 release. --- src/axom/quest/examples/quest_step_file.cpp | 178 ++++++++++++++++++++ 1 file changed, 178 insertions(+) diff --git a/src/axom/quest/examples/quest_step_file.cpp b/src/axom/quest/examples/quest_step_file.cpp index 2cd720d6c6..47c8265a93 100644 --- a/src/axom/quest/examples/quest_step_file.cpp +++ b/src/axom/quest/examples/quest_step_file.cpp @@ -16,6 +16,7 @@ #include "axom/quest.hpp" #include +#include #ifdef AXOM_USE_MPI #include @@ -343,6 +344,150 @@ class PatchParametricSpaceProcessor int m_numFillZeros {0}; }; +/** + * Class that writes a patch-wise MFEM NURBS mesh containing a patch's trimming curves + * + * Each trimming curve is output as a separate 1D NURBS "patch" embedded in 2D space (u,v). + * + * Note: Support for reading these meshes was added to mfem after mfem@4.9.0 + * and is not in the current release of VisIt (visit@3.4.2) + */ +class PatchMFEMTrimmingCurveWriter +{ +public: + PatchMFEMTrimmingCurveWriter() { } + + void setOutputDirectory(const std::string& dir) { m_outputDirectory = dir; } + void setVerbosity(bool verbosityFlag) { m_verbose = verbosityFlag; } + void setNumFillZeros(int num) + { + if(num >= 0) + { + m_numFillZeros = num; + } + } + + void writeMFEMForPatch(int patchIndex, const NURBSPatch& patch) const + { + const auto& curves = patch.getTrimmingCurves(); + const int numCurves = curves.size(); + if(numCurves == 0) + { + return; + } + + using axom::utilities::filesystem::joinPath; + + const std::string outDir = joinPath(m_outputDirectory, "mfem_trim_curves"); + if(!axom::utilities::filesystem::pathExists(outDir)) + { + axom::utilities::filesystem::makeDirsForPath(outDir); + } + + const std::string meshFilename = + joinPath(outDir, + axom::fmt::format("trim_curves_patch_{:0{}}.mesh", patchIndex, m_numFillZeros)); + + std::ofstream meshFile(meshFilename); + if(!meshFile.is_open()) + { + SLIC_WARNING(axom::fmt::format("Unable to open '{}' for writing.", meshFilename)); + return; + } + + axom::fmt::memory_buffer content; + + axom::fmt::format_to(std::back_inserter(content), "MFEM NURBS mesh v1.0\n\n"); + axom::fmt::format_to(std::back_inserter(content), + "# Trim curves for STEP NURBSPatch {}\n", + patchIndex); + axom::fmt::format_to(std::back_inserter(content), + "# Parametric bbox: [{:.17g}, {:.17g}] x [{:.17g}, {:.17g}]\n", + patch.getMinKnot_u(), + patch.getMaxKnot_u(), + patch.getMinKnot_v(), + patch.getMaxKnot_v()); + axom::fmt::format_to(std::back_inserter(content), "# Number of trimming curves: {}\n\n", numCurves); + + axom::fmt::format_to(std::back_inserter(content), "dimension\n1\n\n"); + + // One element per trimming curve; each element uses its own vertex pair. + axom::fmt::format_to(std::back_inserter(content), "elements\n{}\n", numCurves); + for(int i = 0; i < numCurves; ++i) + { + const int v0 = 2 * i; + const int v1 = 2 * i + 1; + axom::fmt::format_to(std::back_inserter(content), "1 1 {} {}\n", v0, v1); + } + + axom::fmt::format_to(std::back_inserter(content), "\nboundary\n0\n\n"); + + // Edge list provides the unique knotvector index and (optionally) encodes orientation. + axom::fmt::format_to(std::back_inserter(content), "edges\n{}\n", numCurves); + for(int i = 0; i < numCurves; ++i) + { + const int v0 = 2 * i; + const int v1 = 2 * i + 1; + axom::fmt::format_to(std::back_inserter(content), "{} {} {}\n", i, v0, v1); + } + + axom::fmt::format_to(std::back_inserter(content), "\nvertices\n{}\n\n", 2 * numCurves); + + axom::fmt::format_to(std::back_inserter(content), "patches\n\n"); + for(int i = 0; i < numCurves; ++i) + { + const auto& curve = curves[i]; + SLIC_ASSERT(curve.isValidNURBS()); + + const int degree = curve.getDegree(); + const int ncp = curve.getNumControlPoints(); + const auto& knots = curve.getKnots().getArray(); + SLIC_ASSERT(knots.size() == ncp + degree + 1); + + axom::fmt::format_to(std::back_inserter(content), + "# Curve {}: {} (degree {}, {} control points)\n", + i, + curve.isRational() ? "rational" : "polynomial", + degree, + ncp); + + axom::fmt::format_to(std::back_inserter(content), "knotvectors\n1\n"); + axom::fmt::format_to(std::back_inserter(content), "{} {}", degree, ncp); + for(const auto& kv : knots) + { + axom::fmt::format_to(std::back_inserter(content), " {:.17g}", kv); + } + axom::fmt::format_to(std::back_inserter(content), "\n\n"); + + axom::fmt::format_to(std::back_inserter(content), "dimension\n2\n\n"); + axom::fmt::format_to(std::back_inserter(content), "controlpoints_cartesian\n"); + + const auto& cps = curve.getControlPoints(); + const auto& wts = curve.getWeights(); + for(int j = 0; j < ncp; ++j) + { + const double w = curve.isRational() ? wts[j] : 1.0; + axom::fmt::format_to(std::back_inserter(content), + "{:.17g} {:.17g} {:.17g}\n", + cps[j][0], + cps[j][1], + w); + } + axom::fmt::format_to(std::back_inserter(content), "\n"); + } + + meshFile << axom::fmt::to_string(content); + meshFile.close(); + + SLIC_INFO_IF(m_verbose, axom::fmt::format("MFEM trim-curve mesh generated: '{}'", meshFilename)); + } + +private: + std::string m_outputDirectory; + bool m_verbose {false}; + int m_numFillZeros {0}; +}; + #ifdef AXOM_USE_MPI // utility function to help with MPI_Allreduce calls @@ -646,6 +791,11 @@ int main(int argc, char** argv) ->description("Generate SVG files for each NURBS patch?") ->capture_default_str(); + bool output_mfem_trim_curves {false}; + app.add_flag("--output-mfem-trim-curves", output_mfem_trim_curves) + ->description("Generate one MFEM NURBS mesh per trimmed patch containing its trimming curves") + ->capture_default_str(); + app.get_formatter()->column_width(50); try @@ -844,5 +994,33 @@ int main(int argc, char** argv) } } + //--------------------------------------------------------------------------- + // Optionally output an MFEM patch-wise NURBS mesh for each patch's trimming curves, only on root rank + //--------------------------------------------------------------------------- + if(output_mfem_trim_curves && is_root) + { + const std::string outDir = joinPath(output_dir, "mfem_trim_curves"); + if(!axom::utilities::filesystem::pathExists(outDir)) + { + axom::utilities::filesystem::makeDirsForPath(outDir); + } + + SLIC_INFO( + axom::fmt::format("Generating MFEM meshes for patch trimming curves in '{}' directory", outDir)); + + const int numPatches = patches.size(); + const int numFillZeros = static_cast(std::log10(numPatches)) + 1; + + PatchMFEMTrimmingCurveWriter writer; + writer.setVerbosity(verbosity); + writer.setOutputDirectory(output_dir); + writer.setNumFillZeros(numFillZeros); + + for(int index = 0; index < numPatches; ++index) + { + writer.writeMFEMForPatch(index, patches[index]); + } + } + return rc; } From 9a74eefc9c494ed35fbfa524fdbd70226b0fe69f Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 12 Mar 2026 13:03:44 -0700 Subject: [PATCH 068/986] Update MFEMReader to read in NURBS patches (when supported) --- src/axom/quest/io/MFEMReader.cpp | 106 ++++++++++++++++++++++++++----- 1 file changed, 91 insertions(+), 15 deletions(-) diff --git a/src/axom/quest/io/MFEMReader.cpp b/src/axom/quest/io/MFEMReader.cpp index 2f02e590b9..69bbcb9fe4 100644 --- a/src/axom/quest/io/MFEMReader.cpp +++ b/src/axom/quest/io/MFEMReader.cpp @@ -19,6 +19,15 @@ #include #include +// MFEM does not support reading patch-based 1D NURBS meshes until after the v4.9 +// release. Prefer patch-based extraction only when the MFEM version is new +// enough, otherwise fall back to element-based extraction. +#ifndef MFEM_VERSION + #define MFEM_VERSION 0 +#endif + +#define AXOM_MFEM_MIN_VERSION_PATCH_BASED_1D_NURBS 40901 + namespace axom { namespace quest @@ -68,6 +77,27 @@ int read_mfem(const std::string &fileName, return MFEMReader::READ_FAILED; } + // lambda to extract the control points for element idx from the mfem mesh as an array of primal::Point + using ControlPoint = primal::Point; + auto get_controlpoints = [nodes, fes](int idx) -> axom::Array { + mfem::Array vdofs; + mfem::Vector v; + fes->GetElementVDofs(idx, vdofs); + nodes->GetSubVector(vdofs, v); + const auto ord = fes->GetOrdering(); + + const int ncp = v.Size() / 2; + axom::Array cp(0, ncp); + for(int i = 0; i < ncp; ++i) + { + ord == mfem::Ordering::byVDIM ? cp.push_back({v[i], v[i + ncp]}) + : cp.push_back({v[2 * i], v[2 * i + 1]}); + } + + return cp; + }; + +#if MFEM_VERSION >= AXOM_MFEM_MIN_VERSION_PATCH_BASED_1D_NURBS // lambda to extract the knot vector associated with curve idx. Converts from mfem::KnotVector to primal::KnotVector auto get_knots = [&mesh](int idx) -> primal::KnotVector { mfem::Array kvs; @@ -78,16 +108,21 @@ int read_mfem(const std::string &fileName, return primal::KnotVector(knots_view, kv.GetOrder()); }; - // lambda to extract the control points for curve idx from the mfem mesh as an array of primal::Point - using ControlPoint = primal::Point; - auto get_controlpoints = [nodes, fes](int idx) -> axom::Array { - mfem::Array vdofs; + // lambda to extract the weights for curve idx from the mfem mesh + // Patch-based NURBS meshes can have multiple elements (knot spans) per patch. + // For robust extraction, build control points/weights from patch DOFs (not element DOFs). + auto get_patch_controlpoints = [nodes, fes, &mesh](int patchId) -> axom::Array { + mfem::Array dofs; + mesh->NURBSext->GetPatchDofs(patchId, dofs); + + mfem::Array vdofs(dofs); + fes->DofsToVDofs(vdofs); + mfem::Vector v; - fes->GetElementVDofs(idx, vdofs); nodes->GetSubVector(vdofs, v); const auto ord = fes->GetOrdering(); - const int ncp = v.Size() / 2; + const int ncp = dofs.Size(); axom::Array cp(0, ncp); for(int i = 0; i < ncp; ++i) { @@ -98,20 +133,39 @@ int read_mfem(const std::string &fileName, return cp; }; - // lambda to extract the weights for curve idx from the mfem mesh - auto get_weights = [&mesh, fes](int idx) -> axom::Array { + auto get_patch_weights = [&mesh](int patchId) -> axom::Array { mfem::Array dofs; - fes->GetElementDofs(idx, dofs); + mesh->NURBSext->GetPatchDofs(patchId, dofs); - const int NW = dofs.Size(); - axom::Array w(NW, NW); + const int nw = dofs.Size(); + axom::Array w(nw, nw); - // wrap our array's buffer w/ an mfem::Vector for GetSubVector - mfem::Vector mfem_vec_weights(w.data(), NW); + mfem::Vector mfem_vec_weights(w.data(), nw); mesh->NURBSext->GetWeights().GetSubVector(dofs, mfem_vec_weights); return w; }; +#endif + +#if MFEM_VERSION < AXOM_MFEM_MIN_VERSION_PATCH_BASED_1D_NURBS + auto get_element_weights = [fes, &mesh](int elemId) -> axom::Array { + mfem::Array dofs; + fes->GetElementDofs(elemId, dofs); + + const int nw = dofs.Size(); + axom::Array w(nw, nw); + + mfem::Vector mfem_vec_weights(w.data(), nw); + mesh->NURBSext->GetWeights().GetSubVector(dofs, mfem_vec_weights); + + return w; + }; + + auto get_element_degree = [fes, &mesh](int elemId) -> int { + const mfem::Array &orders = mesh->NURBSext->GetOrders(); + return (elemId < orders.Size()) ? orders[elemId] : fes->GetOrder(elemId); + }; +#endif // lambda to check if the weights correspond to a rational curve. If they are all equal it is not rational auto is_rational = [](const axom::Array &weights) -> bool { @@ -138,18 +192,40 @@ int read_mfem(const std::string &fileName, const bool isNURBS = dynamic_cast(fec) != nullptr; if(isNURBS) { +#if MFEM_VERSION >= AXOM_MFEM_MIN_VERSION_PATCH_BASED_1D_NURBS + // When MFEM can read patch-based 1D NURBS meshes, prefer reading curves from + // patches (which can contain multiple knot spans). This patch-based + // extraction is also compatible with older MFEM NURBS mesh v1.0 files. const int num_patches = fes->GetNURBSext()->GetNP(); for(int patchId = 0; patchId < num_patches; ++patchId) { const int attribute = mesh->GetPatchAttribute(patchId); const auto kv = get_knots(patchId); - const auto cp = get_controlpoints(patchId); - const auto w = get_weights(patchId); + const auto cp = get_patch_controlpoints(patchId); + const auto w = get_patch_weights(patchId); is_rational(w) ? curvemap[attribute].push_back({cp, w, kv}) : curvemap[attribute].push_back({cp, kv}); } +#else + { + // MFEM versions prior to AXOM_MFEM_MIN_VERSION_PATCH_BASED_1D_NURBS do not + // support reading patch-based 1D NURBS meshes. In that case, treat each + // MFEM element as a single (rational) Bezier span. + for(int zoneId = 0; zoneId < mesh->GetNE(); ++zoneId) + { + const int attribute = mesh->GetAttribute(zoneId); + const int degree = get_element_degree(zoneId); + + const auto cp = get_controlpoints(zoneId); + const auto w = get_element_weights(zoneId); + + is_rational(w) ? curvemap[attribute].push_back({cp, w, degree}) + : curvemap[attribute].push_back({cp, degree}); + } + } +#endif } else { From 33a8f13d52745dc74231625ab62d4115dd3f0547 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 12 Mar 2026 15:10:09 -0700 Subject: [PATCH 069/986] Extract the wire index of each trimming curve per patch and use as mfem attribute --- src/axom/quest/examples/quest_step_file.cpp | 32 +++++++++--- src/axom/quest/io/PSTEPReader.cpp | 16 ++++++ src/axom/quest/io/PSTEPReader.hpp | 11 +++-- src/axom/quest/io/STEPReader.cpp | 54 ++++++++++++++++----- src/axom/quest/io/STEPReader.hpp | 19 ++++++++ 5 files changed, 107 insertions(+), 25 deletions(-) diff --git a/src/axom/quest/examples/quest_step_file.cpp b/src/axom/quest/examples/quest_step_file.cpp index 47c8265a93..427dc4f5f5 100644 --- a/src/axom/quest/examples/quest_step_file.cpp +++ b/src/axom/quest/examples/quest_step_file.cpp @@ -367,7 +367,9 @@ class PatchMFEMTrimmingCurveWriter } } - void writeMFEMForPatch(int patchIndex, const NURBSPatch& patch) const + void writeMFEMForPatch(int patchId, + const NURBSPatch& patch, + axom::ArrayView trimmingCurveWireIds) const { const auto& curves = patch.getTrimmingCurves(); const int numCurves = curves.size(); @@ -376,6 +378,14 @@ class PatchMFEMTrimmingCurveWriter return; } + SLIC_WARNING_IF(trimmingCurveWireIds.size() != numCurves, + axom::fmt::format( + "Trimming curve wire id list size mismatch for patch {}: ids={}, curves={}. " + "Falling back to a single wire id.", + patchId, + trimmingCurveWireIds.size(), + numCurves)); + using axom::utilities::filesystem::joinPath; const std::string outDir = joinPath(m_outputDirectory, "mfem_trim_curves"); @@ -385,8 +395,7 @@ class PatchMFEMTrimmingCurveWriter } const std::string meshFilename = - joinPath(outDir, - axom::fmt::format("trim_curves_patch_{:0{}}.mesh", patchIndex, m_numFillZeros)); + joinPath(outDir, axom::fmt::format("trim_curves_patch_{:0{}}.mesh", patchId, m_numFillZeros)); std::ofstream meshFile(meshFilename); if(!meshFile.is_open()) @@ -400,7 +409,7 @@ class PatchMFEMTrimmingCurveWriter axom::fmt::format_to(std::back_inserter(content), "MFEM NURBS mesh v1.0\n\n"); axom::fmt::format_to(std::back_inserter(content), "# Trim curves for STEP NURBSPatch {}\n", - patchIndex); + patchId); axom::fmt::format_to(std::back_inserter(content), "# Parametric bbox: [{:.17g}, {:.17g}] x [{:.17g}, {:.17g}]\n", patch.getMinKnot_u(), @@ -415,9 +424,12 @@ class PatchMFEMTrimmingCurveWriter axom::fmt::format_to(std::back_inserter(content), "elements\n{}\n", numCurves); for(int i = 0; i < numCurves; ++i) { + // MFEM attributes are 1-based; we store the STEP 0-based wire index + 1. + const int wireId = (trimmingCurveWireIds.size() == numCurves) ? trimmingCurveWireIds[i] : 0; + const int mfem_attribute = wireId + 1; const int v0 = 2 * i; const int v1 = 2 * i + 1; - axom::fmt::format_to(std::back_inserter(content), "1 1 {} {}\n", v0, v1); + axom::fmt::format_to(std::back_inserter(content), "{} 1 {} {}\n", mfem_attribute, v0, v1); } axom::fmt::format_to(std::back_inserter(content), "\nboundary\n0\n\n"); @@ -438,6 +450,8 @@ class PatchMFEMTrimmingCurveWriter { const auto& curve = curves[i]; SLIC_ASSERT(curve.isValidNURBS()); + const int wireId = (trimmingCurveWireIds.size() == numCurves) ? trimmingCurveWireIds[i] : 0; + const int mfem_attribute = wireId + 1; const int degree = curve.getDegree(); const int ncp = curve.getNumControlPoints(); @@ -445,7 +459,10 @@ class PatchMFEMTrimmingCurveWriter SLIC_ASSERT(knots.size() == ncp + degree + 1); axom::fmt::format_to(std::back_inserter(content), - "# Curve {}: {} (degree {}, {} control points)\n", + "# Curve wireId={} (mfem_attribute={}, output index {}): {} (degree {}, " + "{} control points)\n", + wireId, + mfem_attribute, i, curve.isRational() ? "rational" : "polynomial", degree, @@ -1018,7 +1035,8 @@ int main(int argc, char** argv) for(int index = 0; index < numPatches; ++index) { - writer.writeMFEMForPatch(index, patches[index]); + const int patch_id = stepReader.getPatchIds()[index]; + writer.writeMFEMForPatch(patch_id, patches[index], stepReader.getTrimmingCurveWireIds(index)); } } diff --git a/src/axom/quest/io/PSTEPReader.cpp b/src/axom/quest/io/PSTEPReader.cpp index 00513f7566..8a5051eebc 100644 --- a/src/axom/quest/io/PSTEPReader.cpp +++ b/src/axom/quest/io/PSTEPReader.cpp @@ -78,6 +78,13 @@ int PSTEPReader::read(bool validate_model) } } } + + // Broadcast stable ids that match the input STEP enumeration. + bcast_array(m_patchIds); + for(auto& wire_ids : m_trimmingCurveWireIds) + { + bcast_array(wire_ids); + } } break; //handle other ranks @@ -145,6 +152,15 @@ int PSTEPReader::read(bool validate_model) } } } + + // Receive stable ids that match the input STEP enumeration. + bcast_array(m_patchIds); + m_trimmingCurveWireIds.clear(); + m_trimmingCurveWireIds.resize(numPatches); + for(int i = 0; i < numPatches; ++i) + { + bcast_array(m_trimmingCurveWireIds[i]); + } } break; } diff --git a/src/axom/quest/io/PSTEPReader.hpp b/src/axom/quest/io/PSTEPReader.hpp index 7a4b62e907..cefb82d377 100644 --- a/src/axom/quest/io/PSTEPReader.hpp +++ b/src/axom/quest/io/PSTEPReader.hpp @@ -113,9 +113,10 @@ class PSTEPReader : public STEPReader constexpr int ARR_DIM = axom::detail::ArrayTraits::dimension; static_assert(ARR_DIM == 1 || ARR_DIM == 2); - // Check that the value_type of the array is either double or Point + // Check that the value_type of the array is either double, int, or Point using value_type = typename ArrayType::value_type; - static_assert(std::is_same_v || primal::detail::is_point_v); + static_assert(std::is_same_v || std::is_same_v || + primal::detail::is_point_v); const bool is_root = (m_my_rank == 0); @@ -149,9 +150,9 @@ class PSTEPReader : public STEPReader } // then, send/receive the data - if constexpr(std::is_same_v) + if constexpr(std::is_same_v || std::is_same_v) { - // handles Array and Array + // handles Array, Array, and Array bcast_data(arr.view()); } else if constexpr(primal::detail::is_point_v) @@ -190,4 +191,4 @@ class PSTEPReader : public STEPReader } // namespace quest } // namespace axom -#endif // QUEST_PSTEPREADER_HPP_ \ No newline at end of file +#endif // QUEST_PSTEPREADER_HPP_ diff --git a/src/axom/quest/io/STEPReader.cpp b/src/axom/quest/io/STEPReader.cpp index be1cb649a6..5c0ba2e0dc 100644 --- a/src/axom/quest/io/STEPReader.cpp +++ b/src/axom/quest/io/STEPReader.cpp @@ -73,6 +73,7 @@ struct PatchData axom::primal::BoundingBox parametricBBox; axom::primal::BoundingBox physicalBBox; axom::Array trimmingCurves_originallyPeriodic; + axom::Array trimmingCurves_wireIds; }; using PatchDataMap = std::map; @@ -890,16 +891,14 @@ class StepFileProcessor for(; edgeExp.More(); edgeExp.Next(), ++edgeIndex) { const TopoDS_Edge& edge = TopoDS::Edge(edgeExp.Current()); - const int curveIndex = patch.getNumTrimmingCurves(); if(m_verbose) { BRepAdaptor_Curve curveAdaptor(edge); - SLIC_INFO(axom::fmt::format("[Patch {} Wire {} Edge {} Curve {}] Curve type: '{}'", + SLIC_INFO(axom::fmt::format("[Patch {} Wire {} Edge {}] Curve type: '{}'", patchIndex, wireIndex, edgeIndex, - curveIndex, curveTypeMap[curveAdaptor.GetType()])); } @@ -917,6 +916,7 @@ class StepFileProcessor auto curve = curveProcessor.nurbsCurve(); patchData.trimmingCurves_originallyPeriodic.push_back( curveProcessor.curveWasOriginallyPeriodic()); + patchData.trimmingCurves_wireIds.push_back(wireIndex); SLIC_ASSERT(curve.isValidNURBS()); SLIC_ASSERT(curve.getDegree() == bsplineCurve->Degree()); @@ -934,11 +934,10 @@ class StepFileProcessor patch.addTrimmingCurve(curve); SLIC_INFO_IF(m_verbose, - axom::fmt::format("[Patch {} Wire {} Edge {} Curve {}] Added curve: {}", + axom::fmt::format("[Patch {} Wire {} Edge {}] Added curve: {}", patchIndex, wireIndex, edgeIndex, - curveIndex, curve)); // Check to ensure that curve did not change geometrically after making non-periodic @@ -947,14 +946,12 @@ class StepFileProcessor opencascade::handle origCurve = Geom2dConvert::CurveToBSplineCurve(parametricCurve); const bool withinThreshold = curveProcessor.compareToCurve(origCurve, 25); - SLIC_WARNING_IF( - !withinThreshold, - axom::fmt::format("[Patch {} Wire {} Edge {} Curve {}] Trimming curve was not " - "within threshold after clamping.", - patchIndex, - wireIndex, - edgeIndex, - curveIndex)); + SLIC_WARNING_IF(!withinThreshold, + axom::fmt::format("[Patch {} Wire {} Edge {}] Trimming curve was not " + "within threshold after clamping.", + patchIndex, + wireIndex, + edgeIndex)); } // TODO: Check that curve control points are within UV patch after adjusting periodicity @@ -1296,6 +1293,16 @@ class PatchTriangulator std::string STEPReader::getFileUnits() const { return m_stepProcessor->getFileUnits(); } +axom::ArrayView STEPReader::getTrimmingCurveWireIds(int patchArrayIndex) const +{ + if(patchArrayIndex < 0 || patchArrayIndex >= static_cast(m_trimmingCurveWireIds.size())) + { + return {}; + } + + return m_trimmingCurveWireIds[patchArrayIndex].view(); +} + std::string STEPReader::getBRepStats() const { // early return if the step file has not been loaded @@ -1585,6 +1592,27 @@ int STEPReader::read(bool validate_model) m_stepProcessor->extractPatches(m_patches); m_stepProcessor->extractTrimmingCurves(m_patches); + // Record stable ids that match the input STEP enumeration, even if consumers + // later filter/skip patches or trimming curves. + m_patchIds.clear(); + m_patchIds.resize(m_patches.size()); + m_trimmingCurveWireIds.clear(); + m_trimmingCurveWireIds.resize(m_patches.size()); + + const auto& patchDataMap = m_stepProcessor->getPatchDataMap(); + for(int patchArrayIndex = 0; patchArrayIndex < m_patches.size(); ++patchArrayIndex) + { + // Current implementation preserves patch array index == input face index. + // Keep this explicit mapping in case future logic filters patches. + m_patchIds[patchArrayIndex] = patchArrayIndex; + + auto it = patchDataMap.find(patchArrayIndex); + if(it != patchDataMap.end()) + { + m_trimmingCurveWireIds[patchArrayIndex] = it->second.trimmingCurves_wireIds; + } + } + return 0; } diff --git a/src/axom/quest/io/STEPReader.hpp b/src/axom/quest/io/STEPReader.hpp index 4570a696b2..089d67cbec 100644 --- a/src/axom/quest/io/STEPReader.hpp +++ b/src/axom/quest/io/STEPReader.hpp @@ -43,6 +43,7 @@ class STEPReader using PatchArray = axom::Array; using NURBSCurve = axom::primal::NURBSCurve; + using IndexArray = axom::Array; STEPReader() = default; virtual ~STEPReader(); @@ -69,6 +70,22 @@ class STEPReader /// Get the number of patches in the read file int numPatches() { return m_patches.size(); } + /*! + * \brief Returns a 0-based patch id per entry in \a getPatchArray() + * + * This id corresponds to the face index in the input STEP file enumeration. + * If consumers skip patches, these ids will be non-contiguous. + */ + axom::ArrayView getPatchIds() const { return m_patchIds.view(); } + + /*! + * \brief Returns the 0-based wire index for each extracted trimming curve + * + * The i-th entry corresponds to `getPatchArray()[patchArrayIndex].getTrimmingCurves()[i]` + * and stores the 0-based wire index from the input face enumeration. + */ + axom::ArrayView getTrimmingCurveWireIds(int patchArrayIndex) const; + /// Returns some information about the loaded BRep std::string getBRepStats() const; @@ -105,6 +122,8 @@ class STEPReader bool m_verbosity {false}; internal::StepFileProcessor* m_stepProcessor {nullptr}; PatchArray m_patches; + IndexArray m_patchIds; + axom::Array m_trimmingCurveWireIds; }; } // namespace quest From 8679f0faf4bd40b129a068ed54a44fe2a7619907 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Sun, 15 Mar 2026 11:30:31 -0700 Subject: [PATCH 070/986] Adds primal::NURBSCurve::isLinear This is a NURBS analogue to a function in the BezierCurve class. --- src/axom/primal/geometry/NURBSCurve.hpp | 48 +++++++++++++++++++ src/axom/primal/tests/primal_nurbs_curve.cpp | 50 ++++++++++++++++++++ 2 files changed, 98 insertions(+) diff --git a/src/axom/primal/geometry/NURBSCurve.hpp b/src/axom/primal/geometry/NURBSCurve.hpp index 42f95b1eac..dcd5e7eb0b 100644 --- a/src/axom/primal/geometry/NURBSCurve.hpp +++ b/src/axom/primal/geometry/NURBSCurve.hpp @@ -680,6 +680,54 @@ class NURBSCurve return OrientedBoundingBoxType(m_controlPoints.data(), static_cast(m_controlPoints.size())); } + /*! + * \brief Predicate to check if the NURBS curve is approximately linear + * + * This function checks if the interior control points of the NURBSCurve + * are approximately on the line segment defined by its two endpoints. + * + * \param [in] tol Threshold for squared distance + * \param [in] useStrictLinear If true, checks that the control points are + * evenly spaced along the line and not too far from the line + * \return True if curve is near-linear + */ + bool isLinear(double tol = 1e-8, bool useStrictLinear = false) const + { + const int npts = getNumControlPoints(); + if(npts <= 2) + { + return true; + } + + const int end_idx = npts - 1; + + if(useStrictLinear) + { + for(int p = 1; p < end_idx; ++p) + { + const double t = p / static_cast(end_idx); + PointType the_pt = PointType::lerp(m_controlPoints[0], m_controlPoints[end_idx], t); + if(squared_distance(m_controlPoints[p], the_pt) > tol) + { + return false; + } + } + } + else + { + SegmentType seg(m_controlPoints[0], m_controlPoints[end_idx]); + for(int p = 1; p < end_idx; ++p) + { + if(squared_distance(m_controlPoints[p], seg) > tol) + { + return false; + } + } + } + + return true; + } + ///@} ///@{ diff --git a/src/axom/primal/tests/primal_nurbs_curve.cpp b/src/axom/primal/tests/primal_nurbs_curve.cpp index b7a12dea0e..52f1f7ade1 100644 --- a/src/axom/primal/tests/primal_nurbs_curve.cpp +++ b/src/axom/primal/tests/primal_nurbs_curve.cpp @@ -18,6 +18,56 @@ namespace primal = axom::primal; +//------------------------------------------------------------------------------ +TEST(primal_nurbscurve, is_linear_predicate) +{ + constexpr int DIM = 2; + using CoordType = double; + using PointType = primal::Point; + using NURBSCurveType = primal::NURBSCurve; + + constexpr double tol = 1e-12; + + // Degree-1 segment + { + NURBSCurveType c(2, 1); + c[0] = PointType {0.0, 0.0}; + c[1] = PointType {1.0, 0.0}; + EXPECT_TRUE(c.isLinear(tol)); + } + + // Degree-2, collinear control polygon + { + NURBSCurveType c(3, 2); + c[0] = PointType {0.0, 0.0}; + c[1] = PointType {0.5, 0.0}; + c[2] = PointType {1.0, 0.0}; + EXPECT_TRUE(c.isLinear(tol)); + } + + // Degree-2, non-collinear interior point + { + NURBSCurveType c(3, 2); + c[0] = PointType {0.0, 0.0}; + c[1] = PointType {0.5, 1e-3}; + c[2] = PointType {1.0, 0.0}; + EXPECT_FALSE(c.isLinear(tol)); + } + + // Rational, collinear should still be linear + { + NURBSCurveType c(3, 2); + c[0] = PointType {0.0, 0.0}; + c[1] = PointType {0.5, 0.0}; + c[2] = PointType {1.0, 0.0}; + c.makeRational(); + c.setWeight(0, 1.0); + c.setWeight(1, 2.0); + c.setWeight(2, 0.5); + EXPECT_TRUE(c.isLinear(tol)); + } +} + //------------------------------------------------------------------------------ TEST(primal_nurbscurve, default_constructor) { From b0d74b5823fdd6db0e811475ab6ee543bd4d798f Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Sun, 15 Mar 2026 12:05:05 -0700 Subject: [PATCH 071/986] Adds NURBSPatch::isTriviallyTrimmed predicate This returns true if the patch has not trimming curves, or has four trimming curves that form a loop around the patch boundary in parametric space. --- src/axom/primal/geometry/NURBSPatch.hpp | 234 +++++++++++++++++++ src/axom/primal/tests/primal_nurbs_patch.cpp | 212 +++++++++++++++++ 2 files changed, 446 insertions(+) diff --git a/src/axom/primal/geometry/NURBSPatch.hpp b/src/axom/primal/geometry/NURBSPatch.hpp index bf4e8b233d..cf4c3fd76d 100644 --- a/src/axom/primal/geometry/NURBSPatch.hpp +++ b/src/axom/primal/geometry/NURBSPatch.hpp @@ -2792,6 +2792,240 @@ class NURBSPatch /// \brief Get number of trimming curves int getNumTrimmingCurves() const { return m_trimmingCurves.size(); } + /*! + * \brief Predicate to check if the patch is "trivially trimmed" in parameter space. + * + * A patch is considered trivially trimmed if either: + * - it has no trimming curves, or + * - it has exactly four trimming curves and they form an axis-aligned rectangle + * on the patch's parametric boundary: + * - Two curves are horizontal (v=min_v and v=max_v) and have opposite directions + * - Two curves are vertical (u=min_u and u=max_u) and have opposite directions + * - Each curve is approximately linear in (u,v) space + * - Curve endpoints match the patch's (min_u,max_u,min_v,max_v) corner coordinates + * + * \param [in] tol Threshold for squared distance used by NURBSCurve::isLinear and + * for the axis-alignment check on the curve endpoints. + */ + bool isTriviallyTrimmed(double tol = 1e-8) const + { + const int ncurves = getNumTrimmingCurves(); + if(ncurves == 0) + { + return true; + } + + if(ncurves != 4) + { + return false; + } + + const double min_u = static_cast(getMinKnot_u()); + const double max_u = static_cast(getMaxKnot_u()); + const double min_v = static_cast(getMinKnot_v()); + const double max_v = static_cast(getMaxKnot_v()); + + enum Flag : unsigned int + { + // Edge placement on the patch boundary + HasUminVertical = 1u << 0, + HasUmaxVertical = 1u << 1, + HasVminHorizontal = 1u << 2, + HasVmaxHorizontal = 1u << 3, + + // Edge direction coverage (opposing directions required) + HasHorizPosU = 1u << 4, + HasHorizNegU = 1u << 5, + HasVertPosV = 1u << 6, + HasVertNegV = 1u << 7, + + // Corner coverage and parity (each corner must appear exactly twice across endpoints) + SeenCornerMinUMinV = 1u << 8, + SeenCornerMaxUMinV = 1u << 9, + SeenCornerMaxUMaxV = 1u << 10, + SeenCornerMinUMaxV = 1u << 11, + + ParityCornerMinUMinV = 1u << 12, + ParityCornerMaxUMinV = 1u << 13, + ParityCornerMaxUMaxV = 1u << 14, + ParityCornerMinUMaxV = 1u << 15, + + // Use these flags to define larger checks + HasAllEdges = HasUminVertical | HasUmaxVertical | HasVminHorizontal | HasVmaxHorizontal, + HasOppositeOrientations = HasHorizPosU | HasHorizNegU | HasVertPosV | HasVertNegV, + SeenAllCorners = + SeenCornerMinUMinV | SeenCornerMaxUMinV | SeenCornerMaxUMaxV | SeenCornerMinUMaxV, + ParityFlags = + ParityCornerMinUMinV | ParityCornerMaxUMinV | ParityCornerMaxUMaxV | ParityCornerMinUMaxV, + TriviallyTrimmedFlags = HasAllEdges | HasOppositeOrientations | SeenAllCorners + }; + + auto sq = [](double x) -> double { return x * x; }; + auto near = [&](double a, double b) -> bool { return sq(a - b) <= tol; }; + + enum BoundMask : unsigned int + { + UMin = 1u << 0, + UMax = 1u << 1, + VMin = 1u << 2, + VMax = 1u << 3, + + UMask = UMin | UMax, + VMask = VMin | VMax + }; + + auto bound_mask = [&](double u, double v) -> unsigned int { + unsigned int m = 0u; + if(near(u, min_u)) + { + m |= UMin; + } + if(near(u, max_u)) + { + m |= UMax; + } + if(near(v, min_v)) + { + m |= VMin; + } + if(near(v, max_v)) + { + m |= VMax; + } + return m; + }; + + auto toggle_corner = [](unsigned int& flags, unsigned int seen_bit, unsigned int parity_bit) { + flags |= seen_bit; + flags ^= parity_bit; + }; + + auto toggle_corner_for_mask = [&](unsigned int& flags, unsigned int mask) -> bool { + switch(mask) + { + case(UMin | VMin): + toggle_corner(flags, SeenCornerMinUMinV, ParityCornerMinUMinV); + return true; + case(UMax | VMin): + toggle_corner(flags, SeenCornerMaxUMinV, ParityCornerMaxUMinV); + return true; + case(UMax | VMax): + toggle_corner(flags, SeenCornerMaxUMaxV, ParityCornerMaxUMaxV); + return true; + case(UMin | VMax): + toggle_corner(flags, SeenCornerMinUMaxV, ParityCornerMinUMaxV); + return true; + default: + return false; + } + }; + + unsigned int flags = 0u; + + for(int i = 0; i < ncurves; ++i) + { + const auto& c = m_trimmingCurves[i]; + if(!c.isValidNURBS()) + { + return false; + } + + const auto& p0 = c.getInitPoint(); + const auto& p1 = c.getEndPoint(); + const double u0 = static_cast(p0[0]); + const double v0 = static_cast(p0[1]); + const double u1 = static_cast(p1[0]); + const double v1 = static_cast(p1[1]); + + const unsigned int m0 = bound_mask(u0, v0); + const unsigned int m1 = bound_mask(u1, v1); + + // Endpoints must match a corner of the patch boundary (with tolerance) + if(!toggle_corner_for_mask(flags, m0) || !toggle_corner_for_mask(flags, m1)) + { + return false; + } + + const unsigned int u0m = (m0 & UMask); + const unsigned int v0m = (m0 & VMask); + const unsigned int u1m = (m1 & UMask); + const unsigned int v1m = (m1 & VMask); + + const double du = u1 - u0; + const double dv = v1 - v0; + const double du2 = du * du; + const double dv2 = dv * dv; + + const bool is_horizontal = (dv2 <= tol) && (du2 > tol); + const bool is_vertical = (du2 <= tol) && (dv2 > tol); + if(!(is_horizontal || is_vertical)) + { + return false; + } + + if(is_horizontal) + { + // Must lie on v=min_v or v=max_v and span u=min_u..max_u + if(v0m != v1m) + { + return false; + } + if(!(v0m == VMin || v0m == VMax)) + { + return false; + } + + const unsigned int edge_bit = (v0m == VMin) ? HasVminHorizontal : HasVmaxHorizontal; + if((flags & edge_bit) != 0u) + { + return false; + } + flags |= edge_bit; + + if(u0m == u1m) + { + return false; + } + + flags |= (du > 0.0) ? HasHorizPosU : HasHorizNegU; + } + else // is_vertical + { + // Must lie on u=min_u or u=max_u and span v=min_v..max_v + if(u0m != u1m) + { + return false; + } + if(!(u0m == UMin || u0m == UMax)) + { + return false; + } + + const unsigned int edge_bit = (u0m == UMin) ? HasUminVertical : HasUmaxVertical; + if((flags & edge_bit) != 0u) + { + return false; + } + flags |= edge_bit; + + if(v0m == v1m) + { + return false; + } + + flags |= (dv > 0.0) ? HasVertPosV : HasVertNegV; + } + + // More expensive geometric check last. + if(!c.isLinear(tol)) + { + return false; + } + } + + return flags == TriviallyTrimmedFlags; + } + /// \brief use boolean flag for trimmed-ness bool isTrimmed() const { return m_isTrimmed; } diff --git a/src/axom/primal/tests/primal_nurbs_patch.cpp b/src/axom/primal/tests/primal_nurbs_patch.cpp index c4bd00b3e8..8c452faa9c 100644 --- a/src/axom/primal/tests/primal_nurbs_patch.cpp +++ b/src/axom/primal/tests/primal_nurbs_patch.cpp @@ -1196,6 +1196,218 @@ TEST(primal_nurbspatch, nurbs_parameter_space_scaling) } } +//------------------------------------------------------------------------------ +TEST(primal_nurbspatch, is_trivially_trimmed_predicate) +{ + constexpr int DIM = 3; + using CoordType = double; + using PointType = primal::Point; + using NURBSPatchType = primal::NURBSPatch; + using TrimmingCurveType = primal::NURBSCurve; + + constexpr double tol = 1e-12; + + // Simple bilinear patch geometry. + PointType controlPoints[2 * 2] = {PointType {0.0, 0.0, 0.0}, + PointType {0.0, 1.0, 0.0}, + PointType {1.0, 0.0, 0.0}, + PointType {1.0, 1.0, 0.0}}; + NURBSPatchType patch(controlPoints, 2, 2, 1, 1); + + // No trimming curves -> trivially trimmed + EXPECT_TRUE(patch.isTriviallyTrimmed(tol)); + + // Wrong number of curves -> not trivially trimmed + { + NURBSPatchType p = patch; + TrimmingCurveType c(2, 1); + c[0] = primal::Point {0.0, 0.0}; + c[1] = primal::Point {1.0, 0.0}; + p.addTrimmingCurve(c); + EXPECT_FALSE(p.isTriviallyTrimmed(tol)); + } + + // Add four axis-aligned linear trimming curves + { + NURBSPatchType p = patch; + + TrimmingCurveType c0(2, 1); + c0[0] = primal::Point {0.0, 0.0}; + c0[1] = primal::Point {1.0, 0.0}; + + TrimmingCurveType c1(2, 1); + c1[0] = primal::Point {1.0, 0.0}; + c1[1] = primal::Point {1.0, 1.0}; + + TrimmingCurveType c2(2, 1); + c2[0] = primal::Point {1.0, 1.0}; + c2[1] = primal::Point {0.0, 1.0}; + + TrimmingCurveType c3(2, 1); + c3[0] = primal::Point {0.0, 1.0}; + c3[1] = primal::Point {0.0, 0.0}; + + p.addTrimmingCurve(c0); + p.addTrimmingCurve(c1); + p.addTrimmingCurve(c2); + p.addTrimmingCurve(c3); + + EXPECT_TRUE(p.isTriviallyTrimmed(tol)); + } + + // Trivially-trimmed helper generates a trivially-trimmed patch + { + NURBSPatchType p = patch; + p.makeTriviallyTrimmed(); + EXPECT_TRUE(p.isTriviallyTrimmed(tol)); + } + + // Fuzzy boundary matching should succeed within tolerance + { + constexpr double loose_tol = 1e-10; // sqrt(loose_tol) ~ 1e-5 + constexpr double eps = 1e-6; + + NURBSPatchType p = patch; + + TrimmingCurveType c0(2, 1); + c0[0] = primal::Point {0.0 + eps, 0.0 + eps}; + c0[1] = primal::Point {1.0 - eps, 0.0 + eps}; + + TrimmingCurveType c1(2, 1); + c1[0] = primal::Point {1.0 - eps, 0.0 + eps}; + c1[1] = primal::Point {1.0 - eps, 1.0 - eps}; + + TrimmingCurveType c2(2, 1); + c2[0] = primal::Point {1.0 - eps, 1.0 - eps}; + c2[1] = primal::Point {0.0 + eps, 1.0 - eps}; + + TrimmingCurveType c3(2, 1); + c3[0] = primal::Point {0.0 + eps, 1.0 - eps}; + c3[1] = primal::Point {0.0 + eps, 0.0 + eps}; + + p.addTrimmingCurve(c0); + p.addTrimmingCurve(c1); + p.addTrimmingCurve(c2); + p.addTrimmingCurve(c3); + + EXPECT_TRUE(p.isTriviallyTrimmed(loose_tol)); + } + + // Four boundary curves, but horizontal directions match -> not trivially trimmed + { + NURBSPatchType p = patch; + + TrimmingCurveType c0(2, 1); + c0[0] = primal::Point {0.0, 0.0}; + c0[1] = primal::Point {1.0, 0.0}; + + TrimmingCurveType c1(2, 1); + c1[0] = primal::Point {1.0, 0.0}; + c1[1] = primal::Point {1.0, 1.0}; + + // Top edge also left-to-right (same direction as bottom) + TrimmingCurveType c2(2, 1); + c2[0] = primal::Point {0.0, 1.0}; + c2[1] = primal::Point {1.0, 1.0}; + + TrimmingCurveType c3(2, 1); + c3[0] = primal::Point {0.0, 1.0}; + c3[1] = primal::Point {0.0, 0.0}; + + p.addTrimmingCurve(c0); + p.addTrimmingCurve(c1); + p.addTrimmingCurve(c2); + p.addTrimmingCurve(c3); + + EXPECT_FALSE(p.isTriviallyTrimmed(tol)); + } + + // Four axis-aligned linear curves, but not aligned to patch boundaries -> not trivially trimmed + { + NURBSPatchType p = patch; + + TrimmingCurveType c0(2, 1); + c0[0] = primal::Point {0.1, 0.0}; + c0[1] = primal::Point {0.9, 0.0}; + + TrimmingCurveType c1(2, 1); + c1[0] = primal::Point {0.9, 0.0}; + c1[1] = primal::Point {0.9, 1.0}; + + TrimmingCurveType c2(2, 1); + c2[0] = primal::Point {0.9, 1.0}; + c2[1] = primal::Point {0.1, 1.0}; + + TrimmingCurveType c3(2, 1); + c3[0] = primal::Point {0.1, 1.0}; + c3[1] = primal::Point {0.1, 0.0}; + + p.addTrimmingCurve(c0); + p.addTrimmingCurve(c1); + p.addTrimmingCurve(c2); + p.addTrimmingCurve(c3); + + EXPECT_FALSE(p.isTriviallyTrimmed(tol)); + } + + // Four curves, but one is diagonal -> not trivially trimmed + { + NURBSPatchType p = patch; + + TrimmingCurveType c0(2, 1); + c0[0] = primal::Point {0.0, 0.0}; + c0[1] = primal::Point {1.0, 1.0}; + + TrimmingCurveType c1(2, 1); + c1[0] = primal::Point {1.0, 1.0}; + c1[1] = primal::Point {0.0, 1.0}; + + TrimmingCurveType c2(2, 1); + c2[0] = primal::Point {0.0, 1.0}; + c2[1] = primal::Point {0.0, 0.0}; + + TrimmingCurveType c3(2, 1); + c3[0] = primal::Point {0.0, 0.0}; + c3[1] = primal::Point {1.0, 0.0}; + + p.addTrimmingCurve(c0); + p.addTrimmingCurve(c1); + p.addTrimmingCurve(c2); + p.addTrimmingCurve(c3); + + EXPECT_FALSE(p.isTriviallyTrimmed(tol)); + } + + // Four curves, but one is non-linear -> not trivially trimmed + { + NURBSPatchType p = patch; + + TrimmingCurveType c0(3, 2); + c0[0] = primal::Point {0.0, 0.0}; + c0[1] = primal::Point {0.5, 1e-3}; + c0[2] = primal::Point {1.0, 0.0}; + + TrimmingCurveType c1(2, 1); + c1[0] = primal::Point {1.0, 0.0}; + c1[1] = primal::Point {1.0, 1.0}; + + TrimmingCurveType c2(2, 1); + c2[0] = primal::Point {1.0, 1.0}; + c2[1] = primal::Point {0.0, 1.0}; + + TrimmingCurveType c3(2, 1); + c3[0] = primal::Point {0.0, 1.0}; + c3[1] = primal::Point {0.0, 0.0}; + + p.addTrimmingCurve(c0); + p.addTrimmingCurve(c1); + p.addTrimmingCurve(c2); + p.addTrimmingCurve(c3); + + EXPECT_FALSE(p.isTriviallyTrimmed(tol)); + } +} + //------------------------------------------------------------------------------ TEST(primal_nurbspatch, bezier_extraction) { From aaf11edae993d1de90f50132ebd019a2d468e8f1 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Sun, 15 Mar 2026 13:24:18 -0700 Subject: [PATCH 072/986] Adds an option to output a stats file per patch in quest_step_file example This lists statistics like the curve orders and min/max parametric space bounds per patch. --- src/axom/quest/examples/quest_step_file.cpp | 200 +++++++++++++++++++- 1 file changed, 199 insertions(+), 1 deletion(-) diff --git a/src/axom/quest/examples/quest_step_file.cpp b/src/axom/quest/examples/quest_step_file.cpp index 427dc4f5f5..327ff06a8a 100644 --- a/src/axom/quest/examples/quest_step_file.cpp +++ b/src/axom/quest/examples/quest_step_file.cpp @@ -17,6 +17,8 @@ #include #include +#include +#include #ifdef AXOM_USE_MPI #include @@ -505,6 +507,144 @@ class PatchMFEMTrimmingCurveWriter int m_numFillZeros {0}; }; +/** + * Class that writes a JSON stats file per patch summarizing trimming curves. + * + * The output includes: + * - number of wires (unique STEP wire indices) + * - number of trimming curves + * - min/max curve orders (order = degree + 1) + * - patch (u,v) knot-domain bounds + */ +class PatchTrimmingCurveStatsWriter +{ +public: + PatchTrimmingCurveStatsWriter() { } + + void setOutputDirectory(const std::string& dir) { m_outputDirectory = dir; } + void setVerbosity(bool verbosityFlag) { m_verbose = verbosityFlag; } + void setNumFillZeros(int num) + { + if(num >= 0) + { + m_numFillZeros = num; + } + } + + void writeStatsForPatch(int patchId, + const NURBSPatch& patch, + axom::ArrayView trimmingCurveWireIds) const + { + const auto& curves = patch.getTrimmingCurves(); + const int numCurves = curves.size(); + + int minOrder = 0; + int maxOrder = 0; + std::map order_histogram; + for(int i = 0; i < numCurves; ++i) + { + const int order = curves[i].getDegree() + 1; + ++order_histogram[order]; + if(i == 0) + { + minOrder = order; + maxOrder = order; + } + else + { + minOrder = std::min(minOrder, order); + maxOrder = std::max(maxOrder, order); + } + } + + int numWires = 0; + if(numCurves == 0) + { + numWires = 0; + } + else if(trimmingCurveWireIds.size() == numCurves) + { + std::set uniqueWireIds; + for(int i = 0; i < numCurves; ++i) + { + uniqueWireIds.insert(trimmingCurveWireIds[i]); + } + numWires = static_cast(uniqueWireIds.size()); + } + else + { + // If we can't trust the per-curve wire ids, conservatively report a single wire. + numWires = 1; + } + + using axom::utilities::filesystem::joinPath; + + const std::string outDir = joinPath(m_outputDirectory, "trim_curve_stats"); + if(!axom::utilities::filesystem::pathExists(outDir)) + { + axom::utilities::filesystem::makeDirsForPath(outDir); + } + + const std::string statsFilename = + joinPath(outDir, + axom::fmt::format("trim_curves_patch_{:0{}}.stats.json", patchId, m_numFillZeros)); + + std::ofstream statsFile(statsFilename); + if(!statsFile.is_open()) + { + SLIC_WARNING(axom::fmt::format("Unable to open '{}' for writing.", statsFilename)); + return; + } + + axom::fmt::memory_buffer content; + axom::fmt::format_to(std::back_inserter(content), "{{\n"); + axom::fmt::format_to(std::back_inserter(content), " \"patch_id\": {},\n", patchId); + axom::fmt::format_to(std::back_inserter(content), " \"num_wires\": {},\n", numWires); + axom::fmt::format_to(std::back_inserter(content), " \"num_trimming_curves\": {},\n", numCurves); + axom::fmt::format_to(std::back_inserter(content), " \"min_curve_order\": {},\n", minOrder); + axom::fmt::format_to(std::back_inserter(content), " \"max_curve_order\": {},\n", maxOrder); + axom::fmt::format_to(std::back_inserter(content), " \"curves_by_order\": {{"); + { + bool first = true; + for(const auto& kv : order_histogram) + { + axom::fmt::format_to(std::back_inserter(content), + "{}\n \"{}\": {}", + first ? "" : ",", + kv.first, + kv.second); + first = false; + } + if(!order_histogram.empty()) + { + axom::fmt::format_to(std::back_inserter(content), "\n "); + } + } + axom::fmt::format_to(std::back_inserter(content), "}},\n"); + axom::fmt::format_to(std::back_inserter(content), + " \"uv_bbox\": {{\n" + " \"min\": [{:.17g}, {:.17g}],\n" + " \"max\": [{:.17g}, {:.17g}]\n" + " }}\n", + patch.getMinKnot_u(), + patch.getMinKnot_v(), + patch.getMaxKnot_u(), + patch.getMaxKnot_v()); + axom::fmt::format_to(std::back_inserter(content), "}}\n"); + + statsFile << axom::fmt::to_string(content); + statsFile.close(); + + SLIC_INFO_IF(m_verbose, + axom::fmt::format("Trim-curve stats JSON generated: '{}'", statsFilename)); + } + +private: + std::string m_outputDirectory; + bool m_verbose {false}; + int m_numFillZeros {0}; +}; + #ifdef AXOM_USE_MPI // utility function to help with MPI_Allreduce calls @@ -813,6 +953,19 @@ int main(int argc, char** argv) ->description("Generate one MFEM NURBS mesh per trimmed patch containing its trimming curves") ->capture_default_str(); + bool skip_trivial_trimmed_patches {false}; + app.add_flag("--skip-trivial-trimmed-patches", skip_trivial_trimmed_patches) + ->description( + "Skip patch-wise outputs for trivially-trimmed patches (4 axis-aligned linear boundary " + "curves). " + "Applies to SVG, MFEM trim-curve meshes and trim-curve stats JSON when enabled.") + ->capture_default_str(); + + bool output_trim_curve_stats_json {false}; + app.add_flag("--output-trim-curve-stats-json", output_trim_curve_stats_json) + ->description("Generate one JSON stats file per patch summarizing its trimming curves") + ->capture_default_str(); + app.get_formatter()->column_width(50); try @@ -1007,6 +1160,10 @@ int main(int argc, char** argv) for(int index = 0; index < numPatches; ++index) { + if(skip_trivial_trimmed_patches && patches[index].isTriviallyTrimmed()) + { + continue; + } patchProcessor.generateSVGForPatch(index, patches[index]); } } @@ -1036,7 +1193,48 @@ int main(int argc, char** argv) for(int index = 0; index < numPatches; ++index) { const int patch_id = stepReader.getPatchIds()[index]; - writer.writeMFEMForPatch(patch_id, patches[index], stepReader.getTrimmingCurveWireIds(index)); + const auto wire_ids = stepReader.getTrimmingCurveWireIds(index); + if(skip_trivial_trimmed_patches && patches[index].isTriviallyTrimmed()) + { + continue; + } + + writer.writeMFEMForPatch(patch_id, patches[index], wire_ids); + } + } + + //--------------------------------------------------------------------------- + // Optionally output trim-curve stats JSON per patch, only on root rank + //--------------------------------------------------------------------------- + if(output_trim_curve_stats_json && is_root) + { + const std::string outDir = joinPath(output_dir, "trim_curve_stats"); + if(!axom::utilities::filesystem::pathExists(outDir)) + { + axom::utilities::filesystem::makeDirsForPath(outDir); + } + + SLIC_INFO( + axom::fmt::format("Generating JSON trim-curve stats for patches in '{}' directory", outDir)); + + const int numPatches = patches.size(); + const int numFillZeros = static_cast(std::log10(numPatches)) + 1; + + PatchTrimmingCurveStatsWriter stats_writer; + stats_writer.setVerbosity(verbosity); + stats_writer.setOutputDirectory(output_dir); + stats_writer.setNumFillZeros(numFillZeros); + + for(int index = 0; index < numPatches; ++index) + { + const int patch_id = stepReader.getPatchIds()[index]; + const auto wire_ids = stepReader.getTrimmingCurveWireIds(index); + if(skip_trivial_trimmed_patches && patches[index].isTriviallyTrimmed()) + { + continue; + } + + stats_writer.writeStatsForPatch(patch_id, patches[index], wire_ids); } } From 40c09667b92b29213ab22cdabd1aef1477ae7ae9 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Sun, 15 Mar 2026 13:38:16 -0700 Subject: [PATCH 073/986] Adds additional patch stats (isTriviallyTrimmed & num knot spans in u- and v-) --- src/axom/quest/examples/quest_step_file.cpp | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/axom/quest/examples/quest_step_file.cpp b/src/axom/quest/examples/quest_step_file.cpp index 327ff06a8a..20ffe736a4 100644 --- a/src/axom/quest/examples/quest_step_file.cpp +++ b/src/axom/quest/examples/quest_step_file.cpp @@ -537,6 +537,10 @@ class PatchTrimmingCurveStatsWriter { const auto& curves = patch.getTrimmingCurves(); const int numCurves = curves.size(); + const bool is_trivially_trimmed = patch.isTriviallyTrimmed(); + + const int num_knot_spans_u = static_cast(patch.getKnots_u().getNumKnotSpans()); + const int num_knot_spans_v = static_cast(patch.getKnots_v().getNumKnotSpans()); int minOrder = 0; int maxOrder = 0; @@ -599,6 +603,15 @@ class PatchTrimmingCurveStatsWriter axom::fmt::memory_buffer content; axom::fmt::format_to(std::back_inserter(content), "{{\n"); axom::fmt::format_to(std::back_inserter(content), " \"patch_id\": {},\n", patchId); + axom::fmt::format_to(std::back_inserter(content), + " \"is_trivially_trimmed\": {},\n", + is_trivially_trimmed ? "true" : "false"); + axom::fmt::format_to(std::back_inserter(content), + " \"num_knot_spans_u\": {},\n", + num_knot_spans_u); + axom::fmt::format_to(std::back_inserter(content), + " \"num_knot_spans_v\": {},\n", + num_knot_spans_v); axom::fmt::format_to(std::back_inserter(content), " \"num_wires\": {},\n", numWires); axom::fmt::format_to(std::back_inserter(content), " \"num_trimming_curves\": {},\n", numCurves); axom::fmt::format_to(std::back_inserter(content), " \"min_curve_order\": {},\n", minOrder); @@ -958,7 +971,7 @@ int main(int argc, char** argv) ->description( "Skip patch-wise outputs for trivially-trimmed patches (4 axis-aligned linear boundary " "curves). " - "Applies to SVG, MFEM trim-curve meshes and trim-curve stats JSON when enabled.") + "Applies to SVG and MFEM trim-curve meshes when enabled.") ->capture_default_str(); bool output_trim_curve_stats_json {false}; @@ -1229,11 +1242,6 @@ int main(int argc, char** argv) { const int patch_id = stepReader.getPatchIds()[index]; const auto wire_ids = stepReader.getTrimmingCurveWireIds(index); - if(skip_trivial_trimmed_patches && patches[index].isTriviallyTrimmed()) - { - continue; - } - stats_writer.writeStatsForPatch(patch_id, patches[index], wire_ids); } } From a7b56ff4f9f5e2bc7da21e6d3f4157eeca25e5cc Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Sun, 15 Mar 2026 13:48:21 -0700 Subject: [PATCH 074/986] Adds stats to indicate original periodicity of each patch When reading in the STEP file, we remove the periodicity, but users might want to know if the patch was originally periodic. --- src/axom/quest/examples/quest_step_file.cpp | 16 ++++++++++++++-- src/axom/quest/io/PSTEPReader.cpp | 12 ++++++++++-- src/axom/quest/io/STEPReader.cpp | 20 ++++++++++++++++++++ src/axom/quest/io/STEPReader.hpp | 8 ++++++++ 4 files changed, 52 insertions(+), 4 deletions(-) diff --git a/src/axom/quest/examples/quest_step_file.cpp b/src/axom/quest/examples/quest_step_file.cpp index 20ffe736a4..247e3036b0 100644 --- a/src/axom/quest/examples/quest_step_file.cpp +++ b/src/axom/quest/examples/quest_step_file.cpp @@ -533,7 +533,9 @@ class PatchTrimmingCurveStatsWriter void writeStatsForPatch(int patchId, const NURBSPatch& patch, - axom::ArrayView trimmingCurveWireIds) const + axom::ArrayView trimmingCurveWireIds, + bool was_originally_periodic_u, + bool was_originally_periodic_v) const { const auto& curves = patch.getTrimmingCurves(); const int numCurves = curves.size(); @@ -606,6 +608,12 @@ class PatchTrimmingCurveStatsWriter axom::fmt::format_to(std::back_inserter(content), " \"is_trivially_trimmed\": {},\n", is_trivially_trimmed ? "true" : "false"); + axom::fmt::format_to(std::back_inserter(content), + " \"was_originally_periodic_u\": {},\n", + was_originally_periodic_u ? "true" : "false"); + axom::fmt::format_to(std::back_inserter(content), + " \"was_originally_periodic_v\": {},\n", + was_originally_periodic_v ? "true" : "false"); axom::fmt::format_to(std::back_inserter(content), " \"num_knot_spans_u\": {},\n", num_knot_spans_u); @@ -1242,7 +1250,11 @@ int main(int argc, char** argv) { const int patch_id = stepReader.getPatchIds()[index]; const auto wire_ids = stepReader.getTrimmingCurveWireIds(index); - stats_writer.writeStatsForPatch(patch_id, patches[index], wire_ids); + stats_writer.writeStatsForPatch(patch_id, + patches[index], + wire_ids, + stepReader.patchWasOriginallyPeriodic_u(index), + stepReader.patchWasOriginallyPeriodic_v(index)); } } diff --git a/src/axom/quest/io/PSTEPReader.cpp b/src/axom/quest/io/PSTEPReader.cpp index 8a5051eebc..4c1907a535 100644 --- a/src/axom/quest/io/PSTEPReader.cpp +++ b/src/axom/quest/io/PSTEPReader.cpp @@ -79,12 +79,16 @@ int PSTEPReader::read(bool validate_model) } } - // Broadcast stable ids that match the input STEP enumeration. + // Broadcast stable ids that match the input STEP enumeration bcast_array(m_patchIds); for(auto& wire_ids : m_trimmingCurveWireIds) { bcast_array(wire_ids); } + + // Broadcast periodicity flags for each patch + bcast_array(m_patchOriginallyPeriodic_u); + bcast_array(m_patchOriginallyPeriodic_v); } break; //handle other ranks @@ -153,7 +157,7 @@ int PSTEPReader::read(bool validate_model) } } - // Receive stable ids that match the input STEP enumeration. + // Receive stable ids that match the input STEP enumeration bcast_array(m_patchIds); m_trimmingCurveWireIds.clear(); m_trimmingCurveWireIds.resize(numPatches); @@ -161,6 +165,10 @@ int PSTEPReader::read(bool validate_model) { bcast_array(m_trimmingCurveWireIds[i]); } + + // Receive periodicity flags for each patch + bcast_array(m_patchOriginallyPeriodic_u); + bcast_array(m_patchOriginallyPeriodic_v); } break; } diff --git a/src/axom/quest/io/STEPReader.cpp b/src/axom/quest/io/STEPReader.cpp index 5c0ba2e0dc..9e1aeaec7a 100644 --- a/src/axom/quest/io/STEPReader.cpp +++ b/src/axom/quest/io/STEPReader.cpp @@ -1576,6 +1576,18 @@ STEPReader::~STEPReader() } } +bool STEPReader::patchWasOriginallyPeriodic_u(int patchArrayIndex) const +{ + SLIC_ASSERT(patchArrayIndex >= 0 && patchArrayIndex < m_patchOriginallyPeriodic_u.size()); + return m_patchOriginallyPeriodic_u[patchArrayIndex] != 0; +} + +bool STEPReader::patchWasOriginallyPeriodic_v(int patchArrayIndex) const +{ + SLIC_ASSERT(patchArrayIndex >= 0 && patchArrayIndex < m_patchOriginallyPeriodic_v.size()); + return m_patchOriginallyPeriodic_v[patchArrayIndex] != 0; +} + int STEPReader::read(bool validate_model) { m_stepProcessor = new internal::StepFileProcessor(m_fileName, m_verbosity); @@ -1598,6 +1610,12 @@ int STEPReader::read(bool validate_model) m_patchIds.resize(m_patches.size()); m_trimmingCurveWireIds.clear(); m_trimmingCurveWireIds.resize(m_patches.size()); + m_patchOriginallyPeriodic_u.clear(); + m_patchOriginallyPeriodic_u.resize(m_patches.size()); + m_patchOriginallyPeriodic_u.fill(0); + m_patchOriginallyPeriodic_v.clear(); + m_patchOriginallyPeriodic_v.resize(m_patches.size()); + m_patchOriginallyPeriodic_v.fill(0); const auto& patchDataMap = m_stepProcessor->getPatchDataMap(); for(int patchArrayIndex = 0; patchArrayIndex < m_patches.size(); ++patchArrayIndex) @@ -1610,6 +1628,8 @@ int STEPReader::read(bool validate_model) if(it != patchDataMap.end()) { m_trimmingCurveWireIds[patchArrayIndex] = it->second.trimmingCurves_wireIds; + m_patchOriginallyPeriodic_u[patchArrayIndex] = it->second.wasOriginallyPeriodic_u ? 1 : 0; + m_patchOriginallyPeriodic_v[patchArrayIndex] = it->second.wasOriginallyPeriodic_v ? 1 : 0; } } diff --git a/src/axom/quest/io/STEPReader.hpp b/src/axom/quest/io/STEPReader.hpp index 089d67cbec..f3d91f8400 100644 --- a/src/axom/quest/io/STEPReader.hpp +++ b/src/axom/quest/io/STEPReader.hpp @@ -86,6 +86,12 @@ class STEPReader */ axom::ArrayView getTrimmingCurveWireIds(int patchArrayIndex) const; + /// \brief Returns whether the input STEP surface was originally periodic in u for this patch + bool patchWasOriginallyPeriodic_u(int patchArrayIndex) const; + + /// \brief Returns whether the input STEP surface was originally periodic in v for this patch + bool patchWasOriginallyPeriodic_v(int patchArrayIndex) const; + /// Returns some information about the loaded BRep std::string getBRepStats() const; @@ -124,6 +130,8 @@ class STEPReader PatchArray m_patches; IndexArray m_patchIds; axom::Array m_trimmingCurveWireIds; + IndexArray m_patchOriginallyPeriodic_u; + IndexArray m_patchOriginallyPeriodic_v; }; } // namespace quest From ee02ad09fc7cf27acac5e26b0cbaede5a95b4808 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Sun, 15 Mar 2026 14:07:53 -0700 Subject: [PATCH 075/986] Improves output command line options for quest_step_reader example --- src/axom/quest/examples/quest_step_file.cpp | 71 +++++++++++---------- 1 file changed, 39 insertions(+), 32 deletions(-) diff --git a/src/axom/quest/examples/quest_step_file.cpp b/src/axom/quest/examples/quest_step_file.cpp index 247e3036b0..653d485dbf 100644 --- a/src/axom/quest/examples/quest_step_file.cpp +++ b/src/axom/quest/examples/quest_step_file.cpp @@ -925,8 +925,11 @@ int main(int argc, char** argv) axom::fmt::format("Validate the model while reading it in? (default: {})", validate_model)) ->capture_default_str(); + // Output options ----------------------------------------------------------- + auto* output_opts = app.add_option_group("Output", "Parameters associated with output"); + std::string output_dir = "step_output"; - app.add_option("-o,--output-dir", output_dir) + output_opts->add_option("-o,--out,--output-dir", output_dir) ->description("Output directory for generated meshes") ->capture_default_str() ->check([](const std::string& dir) -> std::string { @@ -937,56 +940,57 @@ int main(int argc, char** argv) return std::string(); }); - TriangleMeshOutputType output_trimmed {TriangleMeshOutputType::VTK}; - app.add_option("--output-trimmed", output_trimmed) + bool output_svg {false}; + output_opts->add_flag("--svg,--output-svg", output_svg) + ->description("Generate SVG files for each NURBS patch") + ->capture_default_str(); + + bool output_mfem_trim_curves {false}; + output_opts->add_flag("--mfem-trim-curves,--output-mfem-trim-curves", output_mfem_trim_curves) + ->description("Generate one MFEM NURBS mesh per trimmed patch containing its trimming curves") + ->capture_default_str(); + + bool output_trim_curve_stats_json {false}; + output_opts->add_flag("--stats-json,--output-trim-curve-stats-json", output_trim_curve_stats_json) + ->description("Generate one JSON stats file per patch summarizing its trimming curves") + ->capture_default_str(); + + bool skip_trivial_trimmed_patches {false}; + output_opts + ->add_flag("--skip-trivial,--skip-trivial-trimmed-patches", skip_trivial_trimmed_patches) + ->description("Skip patch-wise SVG/MFEM outputs for trivially-trimmed patches") + ->capture_default_str(); + + // Triangulation options ---------------------------------------------------- + auto* tri_opts = app.add_option_group("Triangulation", "Parameters associated with triangulation"); + + TriangleMeshOutputType output_trimmed {TriangleMeshOutputType::NONE}; + tri_opts->add_option("--tri.trimmed,--output-trimmed", output_trimmed) ->description("Output format for trimmed model triangulation: 'none', 'vtk', 'stl'") ->capture_default_str() ->transform(axom::CLI::CheckedTransformer(validTriangleMeshOutputs)); TriangleMeshOutputType output_untrimmed {TriangleMeshOutputType::NONE}; - app.add_option("--output-untrimmed", output_untrimmed) + tri_opts->add_option("--tri.untrimmed,--output-untrimmed", output_untrimmed) ->description("Output format for untrimmed model triangulation: 'none', 'vtk', 'stl'") ->capture_default_str() ->transform(axom::CLI::CheckedTransformer(validTriangleMeshOutputs)); double deflection {.1}; - app.add_option("--deflection", deflection) + tri_opts->add_option("--deflection", deflection) ->description("Max distance between actual geometry and triangulated geometry") ->capture_default_str(); bool relative_deflection {false}; - app.add_flag("--relative", relative_deflection) + tri_opts->add_flag("--relative", relative_deflection) ->description("Use relative deflection instead of absolute?") ->capture_default_str(); double angular_deflection {0.5}; - app.add_option("--angular-deflection", angular_deflection) + tri_opts->add_option("--angular-deflection", angular_deflection) ->description("Angular deflection between adjacent normals when triangulating surfaces") ->capture_default_str(); - bool output_svg {false}; - app.add_flag("--output-svg", output_svg) - ->description("Generate SVG files for each NURBS patch?") - ->capture_default_str(); - - bool output_mfem_trim_curves {false}; - app.add_flag("--output-mfem-trim-curves", output_mfem_trim_curves) - ->description("Generate one MFEM NURBS mesh per trimmed patch containing its trimming curves") - ->capture_default_str(); - - bool skip_trivial_trimmed_patches {false}; - app.add_flag("--skip-trivial-trimmed-patches", skip_trivial_trimmed_patches) - ->description( - "Skip patch-wise outputs for trivially-trimmed patches (4 axis-aligned linear boundary " - "curves). " - "Applies to SVG and MFEM trim-curve meshes when enabled.") - ->capture_default_str(); - - bool output_trim_curve_stats_json {false}; - app.add_flag("--output-trim-curve-stats-json", output_trim_curve_stats_json) - ->description("Generate one JSON stats file per patch summarizing its trimming curves") - ->capture_default_str(); - app.get_formatter()->column_width(50); try @@ -1009,8 +1013,11 @@ int main(int argc, char** argv) #endif } - // Ensure output directory exists - if(is_root && !axom::utilities::filesystem::pathExists(output_dir)) + // Ensure output directory exists iff we will write anything. + const bool will_write_output = (output_trimmed != TriangleMeshOutputType::NONE) || + (output_untrimmed != TriangleMeshOutputType::NONE) || output_svg || output_mfem_trim_curves || + output_trim_curve_stats_json; + if(is_root && will_write_output && !axom::utilities::filesystem::pathExists(output_dir)) { axom::utilities::filesystem::makeDirsForPath(output_dir); } From 68cd9ec1cbb3e4be2142c4850c0ea0c772bcf572 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Sun, 15 Mar 2026 14:27:38 -0700 Subject: [PATCH 076/986] Updates svg2contours to optionally output mfem meshes using patches format This lets us avoid degree elevation of all curves to degree 3. --- src/tools/svg2contours/svg2contours.py | 191 +++++++++++++++++++++++-- 1 file changed, 177 insertions(+), 14 deletions(-) diff --git a/src/tools/svg2contours/svg2contours.py b/src/tools/svg2contours/svg2contours.py index 31ab1a8c0d..0dd598f4a7 100755 --- a/src/tools/svg2contours/svg2contours.py +++ b/src/tools/svg2contours/svg2contours.py @@ -12,6 +12,13 @@ description: Reads in an SVG document and outputs an MFEM NURBS mesh. Depends on the svgpathtools module + + notes: + - By default, all SVG curve segments are output as cubic NURBS (degree 3) for + compatibility with older MFEM/VisIt workflows. + - This script optionally supports MFEM's newer "patches" NURBS mesh format + for 1D NURBS segments embedded in 2D. Support for reading patch-based 1D + NURBS meshes was added to MFEM after MFEM 4.9.0. """ import sys @@ -127,6 +134,18 @@ def to_complex(v): return bpoints2bezier([to_complex(tf.dot(to_point(p))) for p in cubic.bpoints()]) +def transform_segment(seg, tf): + """Apply transformation `tf` to a Line/QuadraticBezier/CubicBezier segment.""" + + def to_point(p): + return np.array([[p.real], [p.imag], [1.0]]) + + def to_complex(v): + return v.item(0) + 1j * v.item(1) + + return bpoints2bezier([to_complex(tf.dot(to_point(p))) for p in seg.bpoints()]) + + def lerp(a, b, t): """linear interpolation from a to b with parameter t, typically between 0 and 1""" return (1 - t) * a + t * b @@ -240,6 +259,39 @@ def segment_as_cubic(seg, reverse_paths: bool): return (cubic, weights) +def segment_as_native_nurbs(seg, reverse_paths: bool): + """Convert an svgpathtools segment to a (Bezier) NURBS segment (degree 1/2/3) when possible. + + For elliptical arcs, this returns a rational cubic Bezier segment. + Returns (segment, weights, degree). + """ + + if isinstance(seg, Line): + out_seg = seg + weights = [1, 1] + degree = 1 + elif isinstance(seg, QuadraticBezier): + out_seg = seg + weights = [1, 1, 1] + degree = 2 + elif isinstance(seg, CubicBezier): + out_seg = seg + weights = [1, 1, 1, 1] + degree = 3 + elif isinstance(seg, Arc): + cubic, weights = arc_to_cubic(seg) + out_seg = cubic + degree = 3 + else: + raise Exception(f"'{type(seg)}' type not supported yet") + + if reverse_paths: + out_seg = out_seg.reversed() + weights.reverse() + + return (out_seg, weights, degree) + + def dist_to_ellipse(center, radius, angle, pt): cx, cy = center.real, center.imag rx, ry = radius.real, radius.imag @@ -330,6 +382,83 @@ def write_file(self, filename): f.write("\n".join(mfem_file)) +class MFEMPatchesData: + + def __init__(self): + self.elem_cnt = 0 + self.vert_cnt = 0 + self.elems = [] + self.edges = [] + self.patches = [] + + @staticmethod + def _bezier_knotvector(degree: int): + # Bezier knot vector on [0,1] + return [0] * (degree + 1) + [1] * (degree + 1) + + def add_bezier(self, seg, degree: int, weights, attrib: int): + v0 = self.vert_cnt + v1 = self.vert_cnt + 1 + self.elems.append(" ".join(map(str, [attrib, 1, v0, v1]))) + self.edges.append(f"{self.elem_cnt} {v0} {v1}") + self.vert_cnt += 2 + self.elem_cnt += 1 + + cps = seg.bpoints() + if len(cps) != degree + 1: + raise Exception( + f"Expected {degree + 1} control points for degree {degree}, got {len(cps)}") + if len(weights) != len(cps): + raise Exception(f"Expected {len(cps)} weights, got {len(weights)}") + + knots = self._bezier_knotvector(degree) + + patch_lines = [] + patch_lines.append("") + patch_lines.append( + f"# Patch {self.elem_cnt - 1}: degree {degree} ({len(cps)} control points)") + patch_lines.append("knotvectors") + patch_lines.append("1") + patch_lines.append("{} {} {}".format( + degree, + len(cps), + " ".join(str(k) for k in knots), + )) + patch_lines.append("") + patch_lines.append("dimension") + patch_lines.append("2") + patch_lines.append("") + patch_lines.append("controlpoints") + for (cp, w) in zip(cps, weights): + patch_lines.append(f"{cp.real} {cp.imag} {w}") + patch_lines.append("") + + self.patches.append("\n".join(patch_lines)) + + def write_file(self, filename): + mfem_file = [] + + mfem_file.extend([ + "MFEM NURBS mesh v1.0", + "", + "#", + "# Patch-based 1D NURBS segments embedded in 2D.", + "# NOTE: MFEM support for reading patch-based 1D NURBS meshes was added after MFEM 4.9.0.", + "#", + "", + ]) + + mfem_file.extend(["dimension", "1", ""]) + mfem_file.extend(["elements", f"{self.elem_cnt}", "\n".join(self.elems), ""]) + mfem_file.extend(["boundary", "0", ""]) + mfem_file.extend(["edges", f"{self.elem_cnt}", "\n".join(self.edges), ""]) + mfem_file.extend(["vertices", f"{self.vert_cnt}", ""]) + mfem_file.extend(["patches", "\n".join(self.patches)]) + + with open(filename, mode="w") as f: + f.write("\n".join(mfem_file)) + + def compute_svg_path_stats(paths): stats = { "paths_total": len(paths), @@ -378,6 +507,18 @@ def parse_args(): help="Output file in mfem NURBS mesh format (*.mesh)", ) + parser.add_argument( + "--mfem-patches", + dest="mfem_patches", + default=False, + action="store_true", + help= + ("Write the newer MFEM NURBS 'patches' mesh format for 1D segments embedded in 2D " + "(requires MFEM > 4.9.0 to read). When enabled, Lines/Quadratic/Cubic segments are " + "written using degree 1/2/3 respectively; elliptical arcs are written as rational cubics." + ), + ) + parser.add_argument( "--stats", dest="statsfile", @@ -468,6 +609,8 @@ def main(): print("SVG paths: \n", paths) mfem_data = MFEMData() + mfem_patches = opts.get("mfem_patches", False) + mfem_patches_data = MFEMPatchesData() if mfem_patches else None for p_idx, p in enumerate(paths): # print(f"""reading {p_idx=} {p=} \n w/ {p.d()=}""") @@ -491,23 +634,43 @@ def main(): # in `arc_to_cubic` algorithm arc1, arc2 = seg.split(0.5) - cubic, weights = segment_as_cubic(arc1, reverse_paths) - xformed_cubic = transform_cubic(cubic, coordinate_transform) - mfem_data.add_cubic_bezier(xformed_cubic, weights, attrib) - - cubic, weights = segment_as_cubic(arc2, reverse_paths) - xformed_cubic = transform_cubic(cubic, coordinate_transform) - mfem_data.add_cubic_bezier(xformed_cubic, weights, attrib) + if mfem_patches: + seg1, weights1, degree1 = segment_as_native_nurbs(arc1, reverse_paths) + xformed_seg1 = transform_segment(seg1, coordinate_transform) + mfem_patches_data.add_bezier(xformed_seg1, degree1, weights1, attrib) + + seg2, weights2, degree2 = segment_as_native_nurbs(arc2, reverse_paths) + xformed_seg2 = transform_segment(seg2, coordinate_transform) + mfem_patches_data.add_bezier(xformed_seg2, degree2, weights2, attrib) + else: + cubic, weights = segment_as_cubic(arc1, reverse_paths) + xformed_cubic = transform_cubic(cubic, coordinate_transform) + mfem_data.add_cubic_bezier(xformed_cubic, weights, attrib) + + cubic, weights = segment_as_cubic(arc2, reverse_paths) + xformed_cubic = transform_cubic(cubic, coordinate_transform) + mfem_data.add_cubic_bezier(xformed_cubic, weights, attrib) else: - cubic, weights = segment_as_cubic(seg, reverse_paths) - xformed_cubic = transform_cubic(cubic, coordinate_transform) - mfem_data.add_cubic_bezier(xformed_cubic, weights, attrib) + if mfem_patches: + seg0, weights0, degree0 = segment_as_native_nurbs(seg, reverse_paths) + xformed_seg0 = transform_segment(seg0, coordinate_transform) + mfem_patches_data.add_bezier(xformed_seg0, degree0, weights0, attrib) + else: + cubic, weights = segment_as_cubic(seg, reverse_paths) + xformed_cubic = transform_cubic(cubic, coordinate_transform) + mfem_data.add_cubic_bezier(xformed_cubic, weights, attrib) output_file = opts["outputfile"] - mfem_data.write_file(output_file) - print( - f"Wrote '{output_file}' with {mfem_data.vert_cnt} vertices and NURBS {mfem_data.elem_cnt} elements" - ) + if mfem_patches: + mfem_patches_data.write_file(output_file) + print( + f"Wrote '{output_file}' with {mfem_patches_data.vert_cnt} vertices and NURBS {mfem_patches_data.elem_cnt} elements (patches format)" + ) + else: + mfem_data.write_file(output_file) + print( + f"Wrote '{output_file}' with {mfem_data.vert_cnt} vertices and NURBS {mfem_data.elem_cnt} elements" + ) if __name__ == "__main__": From 8b9a8945ace0c33a5d6682641af1c0fe5df7564f Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Sun, 15 Mar 2026 15:29:42 -0700 Subject: [PATCH 077/986] Output elliptic arcs as rational quadratics instead of rational cubics --- src/tools/svg2contours/README.md | 17 ++ src/tools/svg2contours/svg2contours.py | 299 ++++++++++++++++++++----- 2 files changed, 263 insertions(+), 53 deletions(-) diff --git a/src/tools/svg2contours/README.md b/src/tools/svg2contours/README.md index a24fa7a622..2647189a82 100644 --- a/src/tools/svg2contours/README.md +++ b/src/tools/svg2contours/README.md @@ -39,6 +39,23 @@ Wrote 'drawing.mesh' with 54 vertices and NURBS 27 elements ``` > :information_source: This assumes your Axom clone has the `data` submodule located at `/data` +### Optional: write MFEM patches-format NURBS meshes + +MFEM added support for reading patch-based 1D NURBS segments embedded in 2D after MFEM 4.9.0. +To write this newer format, pass `--mfem-patches`: + +```shell +> cd / +> uv run --project ../src/tools/svg2contours ../src/tools/svg2contours/svg2contours.py \ + -i ../data/contours/svg/shapes.svg --mfem-patches -o drawing_patches.mesh +``` + +In `--mfem-patches` mode, Lines/Quadratic/Cubic segments are written with degree 1/2/3 respectively, +and elliptical arcs are written as rational quadratics (degree 2). When an SVG arc is split into +multiple quadratic Bezier spans internally, the `--mfem-patches` output merges them into a single +multi-span quadratic NURBS patch (multi-knotvector) to reduce element/patch count. +Control points in the MFEM patches format are stored in homogeneous form `(x*w, y*w, w)`. + ### Run the quest winding number example Now that we have an MFEM NURBS mesh, we can run our winding number application diff --git a/src/tools/svg2contours/svg2contours.py b/src/tools/svg2contours/svg2contours.py index 0dd598f4a7..c07035cf82 100755 --- a/src/tools/svg2contours/svg2contours.py +++ b/src/tools/svg2contours/svg2contours.py @@ -24,6 +24,7 @@ import sys import os import json +from typing import Optional from svgpathtools import ( Document, Path, @@ -240,6 +241,119 @@ def area(p1, p2, p3): return (CubicBezier(q_0, c_1, c_2, q_1), [3, 1, 1, 3]) +def arc_to_quadratic_beziers(arc: Arc, *, max_sweep_angle_rad: float = np.pi / 2): + """Convert an svgpathtools Arc to rational QuadraticBezier segments. + + Notes: + - Rational quadratics represent conic sections exactly. + - We subdivide the arc into pieces (default: <= 90 degrees) to keep the + interior weight bounded away from zero. + - This function avoids relying on svgpathtools' internal angle units for + `arc.theta` / `arc.delta` (which may be degrees depending on version). + Instead, it reconstructs the start angle and sweep angle from the arc's + start/end points in the ellipse's local parameter space, then selects + the correct branch using `arc.point(0.5)` (and `arc.large_arc` as a hint). + Returns: + List[(QuadraticBezier, weights)] where weights has length 3 and the degree is 2. + """ + + if max_sweep_angle_rad <= 0: + raise ValueError("max_sweep_angle_rad must be positive") + + center = getattr(arc, "center", None) + radius = getattr(arc, "radius", None) + + if center is None or radius is None: + raise Exception("Arc is missing required attributes (center/radius)") + + rx = float(np.abs(radius.real)) + ry = float(np.abs(radius.imag)) + + if rx == 0.0 or ry == 0.0: + # Degenerate arc; let caller fall back (svgpathtools generally models these as Lines). + return [] + + # We build rational quadratic pieces by using the conic (tangent intersection) construction. + # This avoids any ambiguity about svgpathtools' internal angle units and tends to be robust. + + def cross(a: complex, b: complex) -> float: + return float(a.real * b.imag - a.imag * b.real) + + def dot(a: complex, b: complex) -> float: + return float(a.real * b.real + a.imag * b.imag) + + def try_quad_for_arc_piece(arc_piece: Arc): + p0 = arc_piece.start + p2 = arc_piece.end + m = arc_piece.point(0.5) + + d0 = arc_piece.derivative(0.0) + d2 = arc_piece.derivative(1.0) + + # Compute intersection of tangents at endpoints. + denom = cross(d0, d2) + if np.abs(denom) < 1e-14: + return None + + # p0 + s*d0 = p2 + t*d2 + s = cross((p2 - p0), d2) / denom + p1 = p0 + s * d0 + + # Solve for the middle weight w s.t. the rational quadratic matches the midpoint: + # M = (P0 + 2 w P1 + P2) / (2(1+w)) => 2 w (M - P1) = P0 + P2 - 2 M + u = m - p1 + uu = dot(u, u) + if uu <= 0.0: + return None + + rhs = p0 + p2 - 2.0 * m + w = dot(rhs, u) / (2.0 * uu) + + if not np.isfinite(w) or w <= 0.0: + return None + + # Consistency check (least-squares residual; should be near zero for true conics). + res = (p0 + p2 + 2.0 * w * p1) - (2.0 * (1.0 + w) * m) + scale2 = max(rx, ry) ** 2 + if dot(res, res) > 1e-8 * max(1.0, scale2): + return None + + return (QuadraticBezier(p0, p1, p2), [1.0, float(w), 1.0]) + + # Split until each piece's w is at least cos(max_sweep/2) (matches the circle-arc weight bound). + w_min = float(np.cos(0.5 * max_sweep_angle_rad)) + max_pieces = 1024 + + pieces = [] + work = [arc] + while work: + if len(pieces) + len(work) > max_pieces: + return [] + + a = work.pop(0) + quad = try_quad_for_arc_piece(a) + if quad is None: + a1, a2 = a.split(0.5) + work.insert(0, a2) + work.insert(0, a1) + continue + + _, wts = quad + if wts[1] < w_min - 1e-12: + a1, a2 = a.split(0.5) + work.insert(0, a2) + work.insert(0, a1) + continue + + pieces.append(quad) + + # Match legacy `arc_to_cubic` orientation handling: reverse when sweep flag is not set. + if not getattr(arc, "sweep", True): + pieces = [(q.reversed(), list(reversed(w))) for (q, w) in reversed(pieces)] + + return pieces + + def segment_as_cubic(seg, reverse_paths: bool): if isinstance(seg, Line): cubic, weights = line_to_cubic(seg) @@ -259,37 +373,38 @@ def segment_as_cubic(seg, reverse_paths: bool): return (cubic, weights) -def segment_as_native_nurbs(seg, reverse_paths: bool): - """Convert an svgpathtools segment to a (Bezier) NURBS segment (degree 1/2/3) when possible. +def segment_as_native_nurbs_segments(seg, reverse_paths: bool): + """Convert an svgpathtools segment to Bezier (NURBS) segments with native degree where possible. - For elliptical arcs, this returns a rational cubic Bezier segment. - Returns (segment, weights, degree). + Returns a list of (segment, weights, degree). + - Line: 1 segment, degree 1 + - QuadraticBezier: 1 segment, degree 2 + - CubicBezier: 1 segment, degree 3 + - Arc: 1+ rational QuadraticBezier segments (degree 2), subdivided for robustness """ + out = [] if isinstance(seg, Line): - out_seg = seg - weights = [1, 1] - degree = 1 + out = [(seg, [1, 1], 1)] elif isinstance(seg, QuadraticBezier): - out_seg = seg - weights = [1, 1, 1] - degree = 2 + out = [(seg, [1, 1, 1], 2)] elif isinstance(seg, CubicBezier): - out_seg = seg - weights = [1, 1, 1, 1] - degree = 3 + out = [(seg, [1, 1, 1, 1], 3)] elif isinstance(seg, Arc): - cubic, weights = arc_to_cubic(seg) - out_seg = cubic - degree = 3 + quads = arc_to_quadratic_beziers(seg) + if not quads: + # Fall back to a rational cubic if the arc conversion is ill-conditioned. + cubic, weights = arc_to_cubic(seg) + out = [(cubic, weights, 3)] + else: + out = [(q, w, 2) for (q, w) in quads] else: raise Exception(f"'{type(seg)}' type not supported yet") if reverse_paths: - out_seg = out_seg.reversed() - weights.reverse() + out = [(s.reversed(), list(reversed(w)), d) for (s, w, d) in reversed(out)] - return (out_seg, weights, degree) + return out def dist_to_ellipse(center, radius, angle, pt): @@ -396,27 +511,80 @@ def _bezier_knotvector(degree: int): # Bezier knot vector on [0,1] return [0] * (degree + 1) + [1] * (degree + 1) - def add_bezier(self, seg, degree: int, weights, attrib: int): + @staticmethod + def _quadratic_multispan_knotvector(num_spans: int, *, degree: int = 2): + if degree != 2: + raise ValueError("Only quadratic multispan knotvectors are supported here") + if num_spans < 1: + raise ValueError("num_spans must be >= 1") + + knots = [0.0] * (degree + 1) + if num_spans > 1: + # Use multiplicity=degree at internal knots so each span is a Bezier segment. + for i in range(1, num_spans): + t = float(i) / float(num_spans) + knots.extend([t] * degree) + knots.extend([1.0] * (degree + 1)) + return knots + + @staticmethod + def quadratic_beziers_to_multispan(quads_with_weights): + """Merge quadratic rational Bezier spans into a single quadratic multi-span NURBS patch. + + This uses internal knot multiplicity=degree (2), so each span remains a Bezier segment. + Returns (cps, weights, knots). + """ + + num_spans = len(quads_with_weights) + if num_spans < 1: + raise ValueError("Expected at least one span") + + cps = [] + weights = [] + for span_idx, (quad, wts) in enumerate(quads_with_weights): + bpts = quad.bpoints() + if len(bpts) != 3 or len(wts) != 3: + raise Exception( + "Expected quadratic Bezier spans with 3 control points and 3 weights") + if span_idx == 0: + cps.extend(bpts) + weights.extend(wts) + else: + cps.extend(bpts[1:]) + weights.extend(wts[1:]) + + knots = MFEMPatchesData._quadratic_multispan_knotvector(num_spans) + return cps, weights, knots + + def add_nurbs_patch(self, + *, + cps, + degree: int, + weights, + knots, + attrib: int, + patch_comment: Optional[str] = None): + if len(cps) != len(weights): + raise Exception(f"Expected {len(cps)} weights, got {len(weights)}") + expected_knots = 1 + degree + len(cps) + if len(knots) != expected_knots: + raise Exception(f"Expected {expected_knots} knots, got {len(knots)}") + v0 = self.vert_cnt v1 = self.vert_cnt + 1 + self.vert_cnt += 2 + self.elems.append(" ".join(map(str, [attrib, 1, v0, v1]))) self.edges.append(f"{self.elem_cnt} {v0} {v1}") - self.vert_cnt += 2 self.elem_cnt += 1 - cps = seg.bpoints() - if len(cps) != degree + 1: - raise Exception( - f"Expected {degree + 1} control points for degree {degree}, got {len(cps)}") - if len(weights) != len(cps): - raise Exception(f"Expected {len(cps)} weights, got {len(weights)}") - - knots = self._bezier_knotvector(degree) - patch_lines = [] patch_lines.append("") - patch_lines.append( - f"# Patch {self.elem_cnt - 1}: degree {degree} ({len(cps)} control points)") + if patch_comment: + patch_lines.append(f"# Patch {self.elem_cnt - 1}: {patch_comment}") + else: + patch_lines.append( + f"# Patch {self.elem_cnt - 1}: degree {degree} ({len(cps)} control points)") patch_lines.append("knotvectors") patch_lines.append("1") patch_lines.append("{} {} {}".format( @@ -430,11 +598,26 @@ def add_bezier(self, seg, degree: int, weights, attrib: int): patch_lines.append("") patch_lines.append("controlpoints") for (cp, w) in zip(cps, weights): - patch_lines.append(f"{cp.real} {cp.imag} {w}") + # MFEM expects NURBS control points in homogeneous form: (x*w, y*w, w). + ww = float(w) + patch_lines.append(f"{cp.real * ww} {cp.imag * ww} {ww}") patch_lines.append("") self.patches.append("\n".join(patch_lines)) + def add_bezier(self, seg, degree: int, weights, attrib: int): + cps = seg.bpoints() + if len(cps) != degree + 1: + raise Exception( + f"Expected {degree + 1} control points for degree {degree}, got {len(cps)}") + self.add_nurbs_patch( + cps=cps, + degree=degree, + weights=weights, + knots=self._bezier_knotvector(degree), + attrib=attrib, + ) + def write_file(self, filename): mfem_file = [] @@ -515,7 +698,7 @@ def parse_args(): help= ("Write the newer MFEM NURBS 'patches' mesh format for 1D segments embedded in 2D " "(requires MFEM > 4.9.0 to read). When enabled, Lines/Quadratic/Cubic segments are " - "written using degree 1/2/3 respectively; elliptical arcs are written as rational cubics." + "written using degree 1/2/3 respectively; elliptical arcs are written as rational quadratics." ), ) @@ -628,33 +811,43 @@ def main(): for seg_idx, seg in enumerate(p): # print(f"""processing {seg_idx=} {seg=}""") - if isinstance(seg, Arc) and seg.large_arc and is_d_path: + if (not mfem_patches) and isinstance(seg, Arc) and seg.large_arc and is_d_path: # split large elliptical arcs for easier processing # this simplifies the derivation of the internal control points # in `arc_to_cubic` algorithm arc1, arc2 = seg.split(0.5) - if mfem_patches: - seg1, weights1, degree1 = segment_as_native_nurbs(arc1, reverse_paths) - xformed_seg1 = transform_segment(seg1, coordinate_transform) - mfem_patches_data.add_bezier(xformed_seg1, degree1, weights1, attrib) - - seg2, weights2, degree2 = segment_as_native_nurbs(arc2, reverse_paths) - xformed_seg2 = transform_segment(seg2, coordinate_transform) - mfem_patches_data.add_bezier(xformed_seg2, degree2, weights2, attrib) - else: - cubic, weights = segment_as_cubic(arc1, reverse_paths) - xformed_cubic = transform_cubic(cubic, coordinate_transform) - mfem_data.add_cubic_bezier(xformed_cubic, weights, attrib) + cubic, weights = segment_as_cubic(arc1, reverse_paths) + xformed_cubic = transform_cubic(cubic, coordinate_transform) + mfem_data.add_cubic_bezier(xformed_cubic, weights, attrib) - cubic, weights = segment_as_cubic(arc2, reverse_paths) - xformed_cubic = transform_cubic(cubic, coordinate_transform) - mfem_data.add_cubic_bezier(xformed_cubic, weights, attrib) + cubic, weights = segment_as_cubic(arc2, reverse_paths) + xformed_cubic = transform_cubic(cubic, coordinate_transform) + mfem_data.add_cubic_bezier(xformed_cubic, weights, attrib) else: if mfem_patches: - seg0, weights0, degree0 = segment_as_native_nurbs(seg, reverse_paths) - xformed_seg0 = transform_segment(seg0, coordinate_transform) - mfem_patches_data.add_bezier(xformed_seg0, degree0, weights0, attrib) + out_segments = segment_as_native_nurbs_segments(seg, reverse_paths) + if isinstance(seg, Arc) and len(out_segments) > 1 and all( + d == 2 for (_, _, d) in out_segments): + quads_with_weights = [] + for out_seg, weights0, _ in out_segments: + xformed_seg0 = transform_segment(out_seg, coordinate_transform) + quads_with_weights.append((xformed_seg0, weights0)) + + cps, weights, knots = MFEMPatchesData.quadratic_beziers_to_multispan( + quads_with_weights) + mfem_patches_data.add_nurbs_patch( + cps=cps, + degree=2, + weights=weights, + knots=knots, + attrib=attrib, + patch_comment=f"quadratic (order 2, {len(out_segments)} spans)", + ) + else: + for out_seg, weights0, degree0 in out_segments: + xformed_seg0 = transform_segment(out_seg, coordinate_transform) + mfem_patches_data.add_bezier(xformed_seg0, degree0, weights0, attrib) else: cubic, weights = segment_as_cubic(seg, reverse_paths) xformed_cubic = transform_cubic(cubic, coordinate_transform) From fbafd30fbd70169e9e087228c46fb9df2875a3f8 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Mon, 16 Mar 2026 13:11:42 -0700 Subject: [PATCH 078/986] Adds option to 2D winding number example to output degree `elevated` mfem mesh This is in support of downstream applications (such as VisIt) that do not yet support the variable-order mfem NURBS meshes (or patch-based mfem NURBS meshes) which became available after the mfem@4.9 release. --- .../examples/quest_winding_number_2d.cpp | 234 +++++++++++++++++- 1 file changed, 233 insertions(+), 1 deletion(-) diff --git a/src/axom/quest/examples/quest_winding_number_2d.cpp b/src/axom/quest/examples/quest_winding_number_2d.cpp index f6e32b7767..886e7016c0 100644 --- a/src/axom/quest/examples/quest_winding_number_2d.cpp +++ b/src/axom/quest/examples/quest_winding_number_2d.cpp @@ -5,7 +5,7 @@ // SPDX-License-Identifier: (BSD-3-Clause) /*! - * \file quest_winding_number.cpp + * \file quest_winding_number2d.cpp * \brief Example that computes the winding number of a grid of points * against a collection of 2D parametric rational curves. * Supports MFEM meshes in the cubic positive Bernstein basis or the (rational) @@ -25,6 +25,13 @@ #include "mfem.hpp" +#include +#include +#include +#include +#include +#include +#include #include namespace primal = axom::primal; @@ -35,6 +42,216 @@ using BoundingBox2D = primal::BoundingBox; using NURBSCurve2D = primal::NURBSCurve; +namespace +{ +/** + * This helper class takes in an mfem mesh (potentially with variable order curves from mfem>4.9) + * and writes out a version that is compatible with mfem@4.9 + * In particular, this allows us to visualize it with current versions of VisIt which do not yet support this feature. + * + * We can remove this class once downstream appications (such as VisIt) are updated to a version of mfem + * that support the NURBS patches format. + */ +class MFEM49ElevatedNURBSMeshWriter +{ +public: + explicit MFEM49ElevatedNURBSMeshWriter(double tol = 1e-12) : m_tol(tol) { } + + bool writeElevatedMesh(const std::string& input_file, const std::string& output_file) const + { + mfem::Mesh mesh(input_file, /*generate_edges=*/1, /*refine=*/1); + + if(mesh.NURBSext == nullptr) + { + SLIC_WARNING( + axom::fmt::format("Input mesh '{}' has no NURBS extension; skipping degree elevation", + input_file)); + return false; + } + + // Note: NURBS curve meshes in mfem@4.9 must all have the same degree, and their knotvectors must have the same length + const mfem::Array& orders = mesh.NURBSext->GetOrders(); + int max_order = 0; + for(int i = 0; i < orders.Size(); ++i) + { + max_order = std::max(max_order, orders[i]); + } + + if(max_order <= 0) + { + SLIC_WARNING( + axom::fmt::format("Input mesh '{}' has invalid NURBS orders; skipping degree elevation", + input_file)); + return false; + } + + mesh.DegreeElevate(max_order, max_order); + + makeKnotVectorsUniform(mesh); + + // Write the modified mesh to output_file + if(!writeMeshPrintFormat(mesh, output_file)) + { + SLIC_WARNING(axom::fmt::format("Failed to write elevated mesh '{}'", output_file)); + return false; + } + + SLIC_INFO(axom::fmt::format("Wrote elevated MFEM 4.9-compatible NURBS mesh '{}' (max order {})", + output_file, + max_order)); + return true; + } + +private: + struct KnotBin + { + std::int64_t key {0}; + double value {0.0}; + int max_multiplicity {0}; + }; + + // MFEM 4.9's 1D NURBS reader assumes all knotvectors have the same GetNE/GetNCP + // and uses KnotVec(0) when computing offsets. To generate MFEM 4.9/VisIt + // compatible files, insert knots so every knotvector matches the maximum + // knot multiplicity pattern present in the mesh. + void makeKnotVectorsUniform(mfem::Mesh& mesh) const + { + if(mesh.NURBSext == nullptr) + { + return; + } + if(mesh.Dimension() != 1 || mesh.NURBSext->Dimension() != 1) + { + return; + } + + const int nkv = mesh.NURBSext->GetNKV(); + if(nkv <= 1) + { + return; + } + + const double inv_tol = (m_tol > 0.0) ? (1.0 / m_tol) : 1.0e12; + auto key_for = [inv_tol](double t) -> std::int64_t { + return static_cast(std::llround(t * inv_tol)); + }; + + std::vector> counts(nkv); + std::unordered_map bins; + + for(int i = 0; i < nkv; ++i) + { + const mfem::KnotVector* kv = mesh.NURBSext->GetKnotVector(i); + if(kv == nullptr) + { + continue; + } + + auto& local = counts[i]; + for(int j = 0; j < kv->Size(); ++j) + { + const double value = (*kv)[j]; + const std::int64_t key = key_for(value); + + const int multiplicity = ++local[key]; + auto& bin = bins[key]; + bin.key = key; + bin.value = value; + bin.max_multiplicity = std::max(bin.max_multiplicity, multiplicity); + } + } + + std::vector sorted_bins; + sorted_bins.reserve(bins.size()); + for(const auto& it : bins) + { + sorted_bins.push_back(it.second); + } + std::sort(sorted_bins.begin(), sorted_bins.end(), [](const KnotBin& a, const KnotBin& b) { + if(a.value != b.value) + { + return a.value < b.value; + } + return a.key < b.key; + }); + + int total_to_insert = 0; + mfem::Array insertions(nkv); + for(int i = 0; i < nkv; ++i) + { + int need_total = 0; + for(const auto& bin : sorted_bins) + { + const auto found = counts[i].find(bin.key); + const int have = (found == counts[i].end()) ? 0 : found->second; + need_total += std::max(0, bin.max_multiplicity - have); + } + + insertions[i] = new mfem::Vector(need_total); + int pos = 0; + for(const auto& bin : sorted_bins) + { + const auto found = counts[i].find(bin.key); + const int have = (found == counts[i].end()) ? 0 : found->second; + const int need = std::max(0, bin.max_multiplicity - have); + for(int k = 0; k < need; ++k) + { + (*insertions[i])[pos++] = bin.value; + } + } + total_to_insert += need_total; + } + + if(total_to_insert > 0) + { + mesh.KnotInsert(insertions); + } + + for(int i = 0; i < nkv; ++i) + { + delete insertions[i]; + } + } + + bool writeMeshPrintFormat(const mfem::Mesh& mesh, const std::string& output_file) const + { + std::ostringstream oss; + oss.precision(16); + mesh.Print(oss); + + std::istringstream iss(oss.str()); + std::ofstream ofs(output_file); + if(!ofs.is_open()) + { + return false; + } + ofs.precision(16); + + // Note: mfem@4.9's mesh reader does not support the "NURBS2" finite element collection, + // if it occurs, we replace it with the (essentially equivalent) "NURBS" + std::string line; + while(std::getline(iss, line)) + { + constexpr const char* prefix = "FiniteElementCollection: NURBS"; + if(line.rfind(prefix, 0) == 0) + { + ofs << prefix << '\n'; + } + else + { + ofs << line << '\n'; + } + } + + return true; + } + +private: + double m_tol {1e-12}; +}; + +} // namespace + //------------------------------------------------------------------------------ // CLI input //------------------------------------------------------------------------------ @@ -50,6 +267,7 @@ class Input bool memoized {true}; bool vis {true}; bool stats {false}; + std::string elevatedMeshFile; const std::array valid_algorithms {"direct", "fast-approximation"}; std::string algorithm {valid_algorithms[1]}; // fast-approximation @@ -90,6 +308,12 @@ class Input app.add_flag("--stats,!--no-stats", stats, "Compute summary stats for query fields?") ->capture_default_str(); + app.add_option("--output-elevated-mesh", elevatedMeshFile) + ->description( + "Optional. Output MFEM mesh after elevating all NURBS curve orders to the maximum order of " + "input") + ->capture_default_str(); + // Options for query tolerances app.add_option("--edge-tol", tol.edge_tol) ->description("Relative edge tolerance for queries") @@ -219,6 +443,14 @@ int main(int argc, char** argv) axom::utilities::raii::AnnotationsWrapper annotation_raii_wrapper(input.annotationMode); AXOM_ANNOTATE_SCOPE("winding number example"); + if(!input.elevatedMeshFile.empty()) + { + AXOM_ANNOTATE_SCOPE("write_elevated_mesh"); + + MFEM49ElevatedNURBSMeshWriter writer; + writer.writeElevatedMesh(input.inputFile, input.elevatedMeshFile); + } + // Read curves from the MFEM mesh axom::Array curves; { From 96f90483407a4d7ee909217e9c99bde03a4c90cd Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Mon, 16 Mar 2026 17:26:19 -0700 Subject: [PATCH 079/986] Misc comments and cleanup --- src/axom/primal/geometry/NURBSCurve.hpp | 9 ++++----- src/tools/svg2contours/svg2contours.py | 7 +++---- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/axom/primal/geometry/NURBSCurve.hpp b/src/axom/primal/geometry/NURBSCurve.hpp index dcd5e7eb0b..0200324232 100644 --- a/src/axom/primal/geometry/NURBSCurve.hpp +++ b/src/axom/primal/geometry/NURBSCurve.hpp @@ -700,14 +700,13 @@ class NURBSCurve } const int end_idx = npts - 1; - if(useStrictLinear) { for(int p = 1; p < end_idx; ++p) { - const double t = p / static_cast(end_idx); - PointType the_pt = PointType::lerp(m_controlPoints[0], m_controlPoints[end_idx], t); - if(squared_distance(m_controlPoints[p], the_pt) > tol) + if(const double t = p / static_cast(end_idx); + squared_distance(m_controlPoints[p], + PointType::lerp(m_controlPoints[0], m_controlPoints[end_idx], t)) > tol) { return false; } @@ -715,7 +714,7 @@ class NURBSCurve } else { - SegmentType seg(m_controlPoints[0], m_controlPoints[end_idx]); + const SegmentType seg(m_controlPoints[0], m_controlPoints[end_idx]); for(int p = 1; p < end_idx; ++p) { if(squared_distance(m_controlPoints[p], seg) > tol) diff --git a/src/tools/svg2contours/svg2contours.py b/src/tools/svg2contours/svg2contours.py index c07035cf82..5b2f782d7a 100755 --- a/src/tools/svg2contours/svg2contours.py +++ b/src/tools/svg2contours/svg2contours.py @@ -248,9 +248,7 @@ def arc_to_quadratic_beziers(arc: Arc, *, max_sweep_angle_rad: float = np.pi / 2 - Rational quadratics represent conic sections exactly. - We subdivide the arc into pieces (default: <= 90 degrees) to keep the interior weight bounded away from zero. - - This function avoids relying on svgpathtools' internal angle units for - `arc.theta` / `arc.delta` (which may be degrees depending on version). - Instead, it reconstructs the start angle and sweep angle from the arc's + - This function reconstructs the start angle and sweep angle from the arc's start/end points in the ellipse's local parameter space, then selects the correct branch using `arc.point(0.5)` (and `arc.large_arc` as a hint). Returns: @@ -347,7 +345,7 @@ def try_quad_for_arc_piece(arc_piece: Arc): pieces.append(quad) - # Match legacy `arc_to_cubic` orientation handling: reverse when sweep flag is not set. + # Match `arc_to_cubic` orientation handling: reverse when sweep flag is not set. if not getattr(arc, "sweep", True): pieces = [(q.reversed(), list(reversed(w))) for (q, w) in reversed(pieces)] @@ -532,6 +530,7 @@ def quadratic_beziers_to_multispan(quads_with_weights): """Merge quadratic rational Bezier spans into a single quadratic multi-span NURBS patch. This uses internal knot multiplicity=degree (2), so each span remains a Bezier segment. + Note: It would be better to fix the knots/weights to keep it C1 Returns (cps, weights, knots). """ From 66faa6f221243c9a5886ca3911e3cadf0e75e157 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Mon, 16 Mar 2026 18:32:32 -0700 Subject: [PATCH 080/986] Fixes file filters for code checks * Only use checked in files (when using git) * Don't try to lint files from a .venv virtual environment --- src/cmake/AxomMacros.cmake | 55 +++++++++++++++++++++++++++++++------- 1 file changed, 46 insertions(+), 9 deletions(-) diff --git a/src/cmake/AxomMacros.cmake b/src/cmake/AxomMacros.cmake index 192277fed0..0dcd45095f 100644 --- a/src/cmake/AxomMacros.cmake +++ b/src/cmake/AxomMacros.cmake @@ -34,20 +34,57 @@ macro(axom_add_code_checks) set(_base_dirs "axom" "examples" "thirdparty/tests" "tools") set(_ext_expressions "*.cpp" "*.hpp" "*.inl" "*.cxx" "*.hxx" "*.cc" "*.c" "*.h" "*.hh" - "*.F" "*.f" "*.f90" "*.F90" "*.py") + "*.F" "*.f" "*.f90" "*.F90" + "*.py" + "*.cmake" "CMakeLists.txt") - set(_glob_expressions) - foreach(_exp ${_ext_expressions}) - foreach(_base_dir ${_base_dirs}) - list(APPEND _glob_expressions "${PROJECT_SOURCE_DIR}/${_base_dir}/${_exp}") + set(_sources) + set(_use_git_sources FALSE) + + if(COMMAND blt_is_git_repo AND COMMAND blt_git) + blt_is_git_repo(OUTPUT_STATE _axom_is_git_repo + SOURCE_DIR ${PROJECT_SOURCE_DIR}) + if(_axom_is_git_repo) + set(_use_git_sources TRUE) + endif() + endif() + + if(_use_git_sources) + blt_git(SOURCE_DIR ${PROJECT_SOURCE_DIR} + GIT_COMMAND ls-files -- ${_base_dirs} + OUTPUT_VARIABLE _git_ls_files + RETURN_CODE _git_ls_files_result) + + if(_git_ls_files_result EQUAL 0 AND NOT _git_ls_files STREQUAL "") + string(REPLACE "\n" ";" _sources "${_git_ls_files}") + + # Keep only files handled by BLT language filters. + list(FILTER _sources INCLUDE REGEX "(\\.(cpp|hpp|inl|cxx|hxx|cc|c|h|hh|f|f90|py|cmake)|\\.F90|\\.F|CMakeLists\\.txt)$") + + # Make source paths absolute. + set(_sources_prefixed) + foreach(_tracked_file ${_sources}) + list(APPEND _sources_prefixed "${PROJECT_SOURCE_DIR}/${_tracked_file}") + endforeach() + set(_sources ${_sources_prefixed}) + else() + set(_use_git_sources FALSE) + endif() + else() + set(_glob_expressions) + foreach(_exp ${_ext_expressions}) + foreach(_base_dir ${_base_dirs}) + list(APPEND _glob_expressions "${PROJECT_SOURCE_DIR}/${_base_dir}/${_exp}") + endforeach() endforeach() - endforeach() - # Glob for list of files to run code checks on - set(_sources) - file(GLOB_RECURSE _sources ${_glob_expressions}) + # Glob for list of files to run code checks on + file(GLOB_RECURSE _sources ${_glob_expressions}) + endif() # Filter out exclusions + # Never run checks on local python environments + list(FILTER _sources EXCLUDE REGEX ".*[\\\\/]\\.venv[\\\\/].*") set(_exclude_expressions "${PROJECT_SOURCE_DIR}/axom/sidre/examples/lulesh2/*" "${PROJECT_SOURCE_DIR}/axom/slam/examples/lulesh2.0.3/*" From 05b6c4104dafc38923a3cbe929ba63baa3857bcf Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 17 Mar 2026 10:39:05 -0700 Subject: [PATCH 081/986] Guard against calling log(0) --- src/axom/quest/examples/quest_step_file.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/axom/quest/examples/quest_step_file.cpp b/src/axom/quest/examples/quest_step_file.cpp index 653d485dbf..8a84f3f05d 100644 --- a/src/axom/quest/examples/quest_step_file.cpp +++ b/src/axom/quest/examples/quest_step_file.cpp @@ -1178,7 +1178,7 @@ int main(int argc, char** argv) output_dir)); const int numPatches = patches.size(); - const int numFillZeros = static_cast(std::log10(numPatches)) + 1; + const int numFillZeros = (numPatches > 0) ? static_cast(std::log10(numPatches)) + 1 : 1; PatchParametricSpaceProcessor patchProcessor; patchProcessor.setUnits(stepReader.getFileUnits()); @@ -1211,7 +1211,7 @@ int main(int argc, char** argv) axom::fmt::format("Generating MFEM meshes for patch trimming curves in '{}' directory", outDir)); const int numPatches = patches.size(); - const int numFillZeros = static_cast(std::log10(numPatches)) + 1; + const int numFillZeros = (numPatches > 0) ? static_cast(std::log10(numPatches)) + 1 : 1; PatchMFEMTrimmingCurveWriter writer; writer.setVerbosity(verbosity); @@ -1246,7 +1246,7 @@ int main(int argc, char** argv) axom::fmt::format("Generating JSON trim-curve stats for patches in '{}' directory", outDir)); const int numPatches = patches.size(); - const int numFillZeros = static_cast(std::log10(numPatches)) + 1; + const int numFillZeros = (numPatches > 0) ? static_cast(std::log10(numPatches)) + 1 : 1; PatchTrimmingCurveStatsWriter stats_writer; stats_writer.setVerbosity(verbosity); From 7273f4c571f2d7ecf7dfa1f412745a28a9f6e214 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 17 Mar 2026 12:17:55 -0700 Subject: [PATCH 082/986] Updates RELEASE-NOTES --- RELEASE-NOTES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 0c94b673d5..420af7d17e 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -22,12 +22,16 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ - Primal: Functions to evaluate surface and volume integrals over collections of `BezierPatch` and `NURBSPatch` objects. - Quest: An example to evaluate surface and volume integrals over a STEP model and to fit a ellipsoid or oriented bounding box to a model based on its low order moments. +- Primal: Adds `NURBSCurve::isLinear()` to check if a curve is (nearly) flat (corresponding to `BezierCurve::isLinear()`) +- Primal: Adds `NURBSPatch::isTriviallyTrimmed()` to check if the trimming curves for a patch lie on the patch boundaries +- Quest: Adds support for reading mfem files with variable order NURBS curves (requires mfem>4.9). ### Removed ### Deprecated ### Changed +- Updates CMake code check targets to only use checked in files (via `git ls-files`, when available) ### Fixed From de66f0254c5758a5bc7f12f91ab5b90655cfa52c Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 17 Mar 2026 16:01:59 -0700 Subject: [PATCH 083/986] MSVC appears to not like `near` as a variable name I think it is due to a `#define near` within the msvc headers. --- src/axom/primal/geometry/NURBSPatch.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/axom/primal/geometry/NURBSPatch.hpp b/src/axom/primal/geometry/NURBSPatch.hpp index cf4c3fd76d..ea8fc67235 100644 --- a/src/axom/primal/geometry/NURBSPatch.hpp +++ b/src/axom/primal/geometry/NURBSPatch.hpp @@ -2861,7 +2861,7 @@ class NURBSPatch }; auto sq = [](double x) -> double { return x * x; }; - auto near = [&](double a, double b) -> bool { return sq(a - b) <= tol; }; + auto is_near = [&](double a, double b) -> bool { return sq(a - b) <= tol; }; enum BoundMask : unsigned int { @@ -2876,19 +2876,19 @@ class NURBSPatch auto bound_mask = [&](double u, double v) -> unsigned int { unsigned int m = 0u; - if(near(u, min_u)) + if(is_near(u, min_u)) { m |= UMin; } - if(near(u, max_u)) + if(is_near(u, max_u)) { m |= UMax; } - if(near(v, min_v)) + if(is_near(v, min_v)) { m |= VMin; } - if(near(v, max_v)) + if(is_near(v, max_v)) { m |= VMax; } From 4480cb72c56f582250784910085bb527214daa92 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Fri, 10 Apr 2026 12:38:39 -0700 Subject: [PATCH 084/986] Updates `isTriviallyTrimmed` logic A patch without trimming curves is not considered to be trivially trimmed. Rather, it must have exactly four trimming curves aligned with the patch boundaries to be considered trivially trimmed. --- src/axom/primal/geometry/NURBSPatch.hpp | 21 ++++++++++---------- src/axom/primal/tests/primal_nurbs_patch.cpp | 12 +++++++++-- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/axom/primal/geometry/NURBSPatch.hpp b/src/axom/primal/geometry/NURBSPatch.hpp index ea8fc67235..d46a167907 100644 --- a/src/axom/primal/geometry/NURBSPatch.hpp +++ b/src/axom/primal/geometry/NURBSPatch.hpp @@ -2795,26 +2795,25 @@ class NURBSPatch /*! * \brief Predicate to check if the patch is "trivially trimmed" in parameter space. * - * A patch is considered trivially trimmed if either: - * - it has no trimming curves, or - * - it has exactly four trimming curves and they form an axis-aligned rectangle - * on the patch's parametric boundary: - * - Two curves are horizontal (v=min_v and v=max_v) and have opposite directions - * - Two curves are vertical (u=min_u and u=max_u) and have opposite directions - * - Each curve is approximately linear in (u,v) space - * - Curve endpoints match the patch's (min_u,max_u,min_v,max_v) corner coordinates + * A patch is considered trivially trimmed if it is marked as trimmed and has + * exactly four trimming curves that form an axis-aligned rectangle on the + * patch's parametric boundary: + * - Two curves are horizontal (v=min_v and v=max_v) and have opposite directions + * - Two curves are vertical (u=min_u and u=max_u) and have opposite directions + * - Each curve is approximately linear in (u,v) space + * - Curve endpoints match the patch's (min_u,max_u,min_v,max_v) corner coordinates * * \param [in] tol Threshold for squared distance used by NURBSCurve::isLinear and * for the axis-alignment check on the curve endpoints. */ bool isTriviallyTrimmed(double tol = 1e-8) const { - const int ncurves = getNumTrimmingCurves(); - if(ncurves == 0) + if(!isTrimmed()) { - return true; + return false; } + const int ncurves = getNumTrimmingCurves(); if(ncurves != 4) { return false; diff --git a/src/axom/primal/tests/primal_nurbs_patch.cpp b/src/axom/primal/tests/primal_nurbs_patch.cpp index 8c452faa9c..41c7983cfc 100644 --- a/src/axom/primal/tests/primal_nurbs_patch.cpp +++ b/src/axom/primal/tests/primal_nurbs_patch.cpp @@ -1214,8 +1214,8 @@ TEST(primal_nurbspatch, is_trivially_trimmed_predicate) PointType {1.0, 1.0, 0.0}}; NURBSPatchType patch(controlPoints, 2, 2, 1, 1); - // No trimming curves -> trivially trimmed - EXPECT_TRUE(patch.isTriviallyTrimmed(tol)); + // Untrimmed patch -> not trivially trimmed + EXPECT_FALSE(patch.isTriviallyTrimmed(tol)); // Wrong number of curves -> not trivially trimmed { @@ -1262,6 +1262,14 @@ TEST(primal_nurbspatch, is_trivially_trimmed_predicate) EXPECT_TRUE(p.isTriviallyTrimmed(tol)); } + // Marked-trimmed patch with no curves -> not trivially trimmed + { + NURBSPatchType p = patch; + p.makeTriviallyTrimmed(); + p.clearTrimmingCurves(); + EXPECT_FALSE(p.isTriviallyTrimmed(tol)); + } + // Fuzzy boundary matching should succeed within tolerance { constexpr double loose_tol = 1e-10; // sqrt(loose_tol) ~ 1e-5 From 8c824181cf5e4291b4c520206c8d3bc9110809ca Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Fri, 10 Apr 2026 13:09:57 -0700 Subject: [PATCH 085/986] Adds NURBSPatch::isInvisible() for patches that are marked as trimmed but don't have trimming curves Used this to detect and filter "boring" patches in the quest_step_file example. --- src/axom/primal/geometry/NURBSPatch.hpp | 8 +++ src/axom/primal/tests/primal_nurbs_patch.cpp | 67 ++++++++++++++++++++ src/axom/quest/examples/quest_step_file.cpp | 44 +++++++++++-- 3 files changed, 113 insertions(+), 6 deletions(-) diff --git a/src/axom/primal/geometry/NURBSPatch.hpp b/src/axom/primal/geometry/NURBSPatch.hpp index d46a167907..973b5ccf89 100644 --- a/src/axom/primal/geometry/NURBSPatch.hpp +++ b/src/axom/primal/geometry/NURBSPatch.hpp @@ -3028,6 +3028,14 @@ class NURBSPatch /// \brief use boolean flag for trimmed-ness bool isTrimmed() const { return m_isTrimmed; } + /*! + * \brief Predicate to check if the patch is entirely invisible due to trimming state. + * + * A patch is considered invisible when it is marked as trimmed, but has no + * trimming curves. This represents a trimmed patch whose visible region is empty. + */ + bool isInvisible() const { return isTrimmed() && getNumTrimmingCurves() == 0; } + /// \brief Mark as trimmed void markAsTrimmed() { m_isTrimmed = true; } diff --git a/src/axom/primal/tests/primal_nurbs_patch.cpp b/src/axom/primal/tests/primal_nurbs_patch.cpp index 41c7983cfc..7f9fb27230 100644 --- a/src/axom/primal/tests/primal_nurbs_patch.cpp +++ b/src/axom/primal/tests/primal_nurbs_patch.cpp @@ -1416,6 +1416,73 @@ TEST(primal_nurbspatch, is_trivially_trimmed_predicate) } } +//------------------------------------------------------------------------------ +TEST(primal_nurbspatch, is_invisible_predicate) +{ + constexpr int DIM = 3; + using CoordType = double; + using PointType = primal::Point; + using NURBSPatchType = primal::NURBSPatch; + using TrimmingCurveType = primal::NURBSCurve; + + // Simple bilinear patch geometry. + PointType controlPoints[2 * 2] = {PointType {0.0, 0.0, 0.0}, + PointType {0.0, 1.0, 0.0}, + PointType {1.0, 0.0, 0.0}, + PointType {1.0, 1.0, 0.0}}; + NURBSPatchType patch(controlPoints, 2, 2, 1, 1); + + // Untrimmed with no curves is not considered invisible. + EXPECT_FALSE(patch.isTrimmed()); + EXPECT_EQ(patch.getNumTrimmingCurves(), 0); + EXPECT_FALSE(patch.isInvisible()); + + // Trivially trimmed patch has curves and is not invisible. + { + NURBSPatchType p = patch; + p.makeTriviallyTrimmed(); + EXPECT_TRUE(p.isTrimmed()); + EXPECT_EQ(p.getNumTrimmingCurves(), 4); + EXPECT_FALSE(p.isInvisible()); + } + + // Marked trimmed but no curves is invisible. + { + NURBSPatchType p = patch; + p.makeTriviallyTrimmed(); + p.clearTrimmingCurves(); + EXPECT_TRUE(p.isTrimmed()); + EXPECT_EQ(p.getNumTrimmingCurves(), 0); + EXPECT_TRUE(p.isInvisible()); + } + + // Setting trimming curves to an empty set marks trimmed and is invisible. + { + NURBSPatchType p = patch; + typename NURBSPatchType::TrimmingCurveVec empty_curves; + p.setTrimmingCurves(empty_curves); + EXPECT_TRUE(p.isTrimmed()); + EXPECT_EQ(p.getNumTrimmingCurves(), 0); + EXPECT_TRUE(p.isInvisible()); + } + + // Adding any trimming curve makes it not invisible. + { + NURBSPatchType p = patch; + typename NURBSPatchType::TrimmingCurveVec empty_curves; + p.setTrimmingCurves(empty_curves); + + TrimmingCurveType c(2, 1); + c[0] = primal::Point {0.0, 0.0}; + c[1] = primal::Point {1.0, 0.0}; + p.addTrimmingCurve(c); + + EXPECT_TRUE(p.isTrimmed()); + EXPECT_EQ(p.getNumTrimmingCurves(), 1); + EXPECT_FALSE(p.isInvisible()); + } +} + //------------------------------------------------------------------------------ TEST(primal_nurbspatch, bezier_extraction) { diff --git a/src/axom/quest/examples/quest_step_file.cpp b/src/axom/quest/examples/quest_step_file.cpp index 8a84f3f05d..6d5987b907 100644 --- a/src/axom/quest/examples/quest_step_file.cpp +++ b/src/axom/quest/examples/quest_step_file.cpp @@ -955,10 +955,11 @@ int main(int argc, char** argv) ->description("Generate one JSON stats file per patch summarizing its trimming curves") ->capture_default_str(); - bool skip_trivial_trimmed_patches {false}; - output_opts - ->add_flag("--skip-trivial,--skip-trivial-trimmed-patches", skip_trivial_trimmed_patches) - ->description("Skip patch-wise SVG/MFEM outputs for trivially-trimmed patches") + bool skip_boring_patches {false}; + output_opts->add_flag("--skip-boring", skip_boring_patches) + ->description( + "Skip patch-wise SVG/MFEM/stats outputs for boring patches (trivially trimmed, invisible, or " + "untrimmed)") ->capture_default_str(); // Triangulation options ---------------------------------------------------- @@ -1055,6 +1056,33 @@ int main(int argc, char** argv) PatchArray& patches = stepReader.getPatchArray(); + auto should_skip_patchwise_outputs = [&](const NURBSPatch& p) -> bool { + if(!skip_boring_patches) + { + return false; + } + + // Skip untrimmed patches (no trimming curves). + if(!p.isTrimmed() && p.getNumTrimmingCurves() == 0) + { + return true; + } + + // Skip trimmed patches with no trimming curves (empty visible region). + if(p.isInvisible()) + { + return true; + } + + // Skip trimmed patches whose trimming curves coincide with the patch boundary. + if(p.isTriviallyTrimmed()) + { + return true; + } + + return false; + }; + #ifdef AXOM_USE_MPI if(validate_model && !validate_patches(patches)) { @@ -1188,7 +1216,7 @@ int main(int argc, char** argv) for(int index = 0; index < numPatches; ++index) { - if(skip_trivial_trimmed_patches && patches[index].isTriviallyTrimmed()) + if(should_skip_patchwise_outputs(patches[index])) { continue; } @@ -1222,7 +1250,7 @@ int main(int argc, char** argv) { const int patch_id = stepReader.getPatchIds()[index]; const auto wire_ids = stepReader.getTrimmingCurveWireIds(index); - if(skip_trivial_trimmed_patches && patches[index].isTriviallyTrimmed()) + if(should_skip_patchwise_outputs(patches[index])) { continue; } @@ -1257,6 +1285,10 @@ int main(int argc, char** argv) { const int patch_id = stepReader.getPatchIds()[index]; const auto wire_ids = stepReader.getTrimmingCurveWireIds(index); + if(should_skip_patchwise_outputs(patches[index])) + { + continue; + } stats_writer.writeStatsForPatch(patch_id, patches[index], wire_ids, From c6c3c437ef37a8d656f02f2918ee727288dcaab9 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Fri, 10 Apr 2026 13:42:29 -0700 Subject: [PATCH 086/986] Bugfixes and improvements based on code review * Broadcasts `isTrimmed` flag for parallel step reader * Handles MFEM NURBS curves without knot vectors * Improves comments --- src/axom/primal/geometry/NURBSCurve.hpp | 5 +- src/axom/quest/examples/quest_step_file.cpp | 2 +- .../examples/quest_winding_number_2d.cpp | 11 +++-- src/axom/quest/io/MFEMReader.cpp | 46 ++++++++++++++----- src/axom/quest/io/PSTEPReader.cpp | 17 +++++++ src/axom/quest/io/PSTEPReader.hpp | 2 +- 6 files changed, 64 insertions(+), 19 deletions(-) diff --git a/src/axom/primal/geometry/NURBSCurve.hpp b/src/axom/primal/geometry/NURBSCurve.hpp index 0200324232..d2c80d961e 100644 --- a/src/axom/primal/geometry/NURBSCurve.hpp +++ b/src/axom/primal/geometry/NURBSCurve.hpp @@ -688,7 +688,10 @@ class NURBSCurve * * \param [in] tol Threshold for squared distance * \param [in] useStrictLinear If true, checks that the control points are - * evenly spaced along the line and not too far from the line + * evenly spaced along the endpoint segment (in addition to being near the + * segment). This is a stronger condition and may return false for curves + * that are geometrically linear but have a non-uniform control-point + * distribution along the segment. * \return True if curve is near-linear */ bool isLinear(double tol = 1e-8, bool useStrictLinear = false) const diff --git a/src/axom/quest/examples/quest_step_file.cpp b/src/axom/quest/examples/quest_step_file.cpp index 6d5987b907..307c46c7ab 100644 --- a/src/axom/quest/examples/quest_step_file.cpp +++ b/src/axom/quest/examples/quest_step_file.cpp @@ -959,7 +959,7 @@ int main(int argc, char** argv) output_opts->add_flag("--skip-boring", skip_boring_patches) ->description( "Skip patch-wise SVG/MFEM/stats outputs for boring patches (trivially trimmed, invisible, or " - "untrimmed)") + "untrimmed). Does not affect triangulations.") ->capture_default_str(); // Triangulation options ---------------------------------------------------- diff --git a/src/axom/quest/examples/quest_winding_number_2d.cpp b/src/axom/quest/examples/quest_winding_number_2d.cpp index 886e7016c0..89d227f328 100644 --- a/src/axom/quest/examples/quest_winding_number_2d.cpp +++ b/src/axom/quest/examples/quest_winding_number_2d.cpp @@ -46,11 +46,11 @@ namespace { /** * This helper class takes in an mfem mesh (potentially with variable order curves from mfem>4.9) - * and writes out a version that is compatible with mfem@4.9 + * and writes out a version that is compatible with mfem@4.9 * In particular, this allows us to visualize it with current versions of VisIt which do not yet support this feature. - * - * We can remove this class once downstream appications (such as VisIt) are updated to a version of mfem - * that support the NURBS patches format. + * + * We can remove this class once downstream applications (such as VisIt) are updated to a version of mfem + * that supports the NURBS patches format. */ class MFEM49ElevatedNURBSMeshWriter { @@ -69,7 +69,8 @@ class MFEM49ElevatedNURBSMeshWriter return false; } - // Note: NURBS curve meshes in mfem@4.9 must all have the same degree, and their knotvectors must have the same length + // Note: NURBS curve meshes in mfem@4.9 must all have the same degree, and their knotvectors must have the same length. + // MFEM calls this quantity "order"; in Axom terminology this is the polynomial degree. const mfem::Array& orders = mesh.NURBSext->GetOrders(); int max_order = 0; for(int i = 0; i < orders.Size(); ++i) diff --git a/src/axom/quest/io/MFEMReader.cpp b/src/axom/quest/io/MFEMReader.cpp index 69bbcb9fe4..ab674c4ce6 100644 --- a/src/axom/quest/io/MFEMReader.cpp +++ b/src/axom/quest/io/MFEMReader.cpp @@ -98,22 +98,18 @@ int read_mfem(const std::string &fileName, }; #if MFEM_VERSION >= AXOM_MFEM_MIN_VERSION_PATCH_BASED_1D_NURBS - // lambda to extract the knot vector associated with curve idx. Converts from mfem::KnotVector to primal::KnotVector - auto get_knots = [&mesh](int idx) -> primal::KnotVector { - mfem::Array kvs; - mesh->NURBSext->GetPatchKnotVectors(idx, kvs); - const mfem::KnotVector &kv = *kvs[0]; - - axom::ArrayView knots_view(&kv[0], kv.Size()); - return primal::KnotVector(knots_view, kv.GetOrder()); - }; - // lambda to extract the weights for curve idx from the mfem mesh // Patch-based NURBS meshes can have multiple elements (knot spans) per patch. // For robust extraction, build control points/weights from patch DOFs (not element DOFs). auto get_patch_controlpoints = [nodes, fes, &mesh](int patchId) -> axom::Array { mfem::Array dofs; mesh->NURBSext->GetPatchDofs(patchId, dofs); + if(dofs.Size() <= 0) + { + SLIC_WARNING( + axom::fmt::format("MFEM patch {} has no DOFs; cannot extract NURBS curve.", patchId)); + return {}; + } mfem::Array vdofs(dofs); fes->DofsToVDofs(vdofs); @@ -136,6 +132,10 @@ int read_mfem(const std::string &fileName, auto get_patch_weights = [&mesh](int patchId) -> axom::Array { mfem::Array dofs; mesh->NURBSext->GetPatchDofs(patchId, dofs); + if(dofs.Size() <= 0) + { + return {}; + } const int nw = dofs.Size(); axom::Array w(nw, nw); @@ -163,6 +163,7 @@ int read_mfem(const std::string &fileName, auto get_element_degree = [fes, &mesh](int elemId) -> int { const mfem::Array &orders = mesh->NURBSext->GetOrders(); + // MFEM calls this "order"; in Axom terminology this is the polynomial degree. return (elemId < orders.Size()) ? orders[elemId] : fes->GetOrder(elemId); }; #endif @@ -201,9 +202,32 @@ int read_mfem(const std::string &fileName, { const int attribute = mesh->GetPatchAttribute(patchId); - const auto kv = get_knots(patchId); + mfem::Array kvs; + mesh->NURBSext->GetPatchKnotVectors(patchId, kvs); + if(kvs.Size() < 1 || kvs[0] == nullptr) + { + SLIC_WARNING( + axom::fmt::format("MFEM patch {} has no valid knot vector; cannot extract NURBS curve.", + patchId)); + return MFEMReader::READ_FAILED; + } + const mfem::KnotVector &kv0 = *kvs[0]; + if(kv0.Size() <= 0) + { + SLIC_WARNING( + axom::fmt::format("MFEM patch {} has an empty knot vector; cannot extract NURBS curve.", + patchId)); + return MFEMReader::READ_FAILED; + } + axom::ArrayView knots_view(&kv0[0], kv0.Size()); + const primal::KnotVector kv(knots_view, kv0.GetOrder()); + const auto cp = get_patch_controlpoints(patchId); const auto w = get_patch_weights(patchId); + if(cp.empty()) + { + return MFEMReader::READ_FAILED; + } is_rational(w) ? curvemap[attribute].push_back({cp, w, kv}) : curvemap[attribute].push_back({cp, kv}); diff --git a/src/axom/quest/io/PSTEPReader.cpp b/src/axom/quest/io/PSTEPReader.cpp index 4c1907a535..b46047d27b 100644 --- a/src/axom/quest/io/PSTEPReader.cpp +++ b/src/axom/quest/io/PSTEPReader.cpp @@ -42,6 +42,9 @@ int PSTEPReader::read(bool validate_model) bcast_int(m_patches.size()); for(auto& patch : m_patches) { + // broadcast trimmed flag (independent from number of trimming curves) + const bool is_trimmed = bcast_bool(patch.isTrimmed()); + // broadcast u- and v- knot vector bcast_array(patch.getKnots_u().getArray()); @@ -77,6 +80,12 @@ int PSTEPReader::read(bool validate_model) bcast_array(cur.getWeights()); } } + + // Preserve the trimmed state even when there are no trimming curves. + if(is_trimmed) + { + patch.markAsTrimmed(); + } } // Broadcast stable ids that match the input STEP enumeration @@ -103,6 +112,8 @@ int PSTEPReader::read(bool validate_model) m_patches.reserve(numPatches); for(int i = 0; i < numPatches; ++i) { + const bool is_trimmed = bcast_bool(); + { // receive the u-knotvector axom::Array uKnotsArr; @@ -155,6 +166,12 @@ int PSTEPReader::read(bool validate_model) m_patches[i].addTrimmingCurve(NURBSCurve {curControlPoints, curKnotsArr}); } } + + // Preserve the trimmed state even when there are no trimming curves. + if(is_trimmed) + { + m_patches[i].markAsTrimmed(); + } } // Receive stable ids that match the input STEP enumeration diff --git a/src/axom/quest/io/PSTEPReader.hpp b/src/axom/quest/io/PSTEPReader.hpp index cefb82d377..76082acf4b 100644 --- a/src/axom/quest/io/PSTEPReader.hpp +++ b/src/axom/quest/io/PSTEPReader.hpp @@ -152,7 +152,7 @@ class PSTEPReader : public STEPReader // then, send/receive the data if constexpr(std::is_same_v || std::is_same_v) { - // handles Array, Array, and Array + // handles Array, Array, Array, and Array bcast_data(arr.view()); } else if constexpr(primal::detail::is_point_v) From 2b0f7fa6e16674de1a0fea63a235998c7ea50804 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Fri, 10 Apr 2026 14:14:27 -0700 Subject: [PATCH 087/986] Improves testing of NURBSPatch::isLinear --- src/axom/primal/tests/primal_nurbs_curve.cpp | 27 ++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/axom/primal/tests/primal_nurbs_curve.cpp b/src/axom/primal/tests/primal_nurbs_curve.cpp index 52f1f7ade1..2fd06c3242 100644 --- a/src/axom/primal/tests/primal_nurbs_curve.cpp +++ b/src/axom/primal/tests/primal_nurbs_curve.cpp @@ -66,6 +66,33 @@ TEST(primal_nurbscurve, is_linear_predicate) c.setWeight(2, 0.5); EXPECT_TRUE(c.isLinear(tol)); } + + // Strict mode requires a uniform control-point distribution along the endpoint segment + { + // evenly spaced -> strict linear + NURBSCurveType c(3, 2); + c[0] = PointType {0.0, 0.0}; + c[1] = PointType {0.5, 0.0}; + c[2] = PointType {1.0, 0.0}; + EXPECT_TRUE(c.isLinear(tol, /*useStrictLinear=*/true)); + } + { + // collinear but not evenly spaced -> not strict linear + NURBSCurveType c(3, 2); + c[0] = PointType {0.0, 0.0}; + c[1] = PointType {0.2, 0.0}; + c[2] = PointType {1.0, 0.0}; + EXPECT_TRUE(c.isLinear(tol, /*useStrictLinear=*/false)); + EXPECT_FALSE(c.isLinear(tol, /*useStrictLinear=*/true)); + } + { + // slight deviation within tolerance should still be linear (non-strict) + NURBSCurveType c(3, 2); + c[0] = PointType {0.0, 0.0}; + c[1] = PointType {0.5, 0.5e-6}; // squared distance 2.5e-13 < 1e-12 + c[2] = PointType {1.0, 0.0}; + EXPECT_TRUE(c.isLinear(tol, /*useStrictLinear=*/false)); + } } //------------------------------------------------------------------------------ From 2e52b4dcdb7e6a07a27bd6ec492246d07e80f5ac Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Fri, 10 Apr 2026 14:28:00 -0700 Subject: [PATCH 088/986] Adds an MFEMReader test for patch-based NURBS files Only active when mfem version is beyond mfem@4.9 --- src/axom/quest/tests/quest_mfem_reader.cpp | 121 +++++++++++++++++++++ 1 file changed, 121 insertions(+) diff --git a/src/axom/quest/tests/quest_mfem_reader.cpp b/src/axom/quest/tests/quest_mfem_reader.cpp index a98e410d23..e61738baa0 100644 --- a/src/axom/quest/tests/quest_mfem_reader.cpp +++ b/src/axom/quest/tests/quest_mfem_reader.cpp @@ -14,6 +14,7 @@ #include "axom/core.hpp" #include "axom/slic.hpp" #include "axom/primal.hpp" +#include "axom/fmt.hpp" #include "mfem.hpp" @@ -23,10 +24,12 @@ #include #include #include +#include // namespace aliases namespace primal = axom::primal; namespace quest = axom::quest; +namespace fs = axom::utilities::filesystem; //------------------------------------------------------------------------------ std::string pjoin(const std::string &str) { return str; } @@ -394,6 +397,124 @@ TEST(quest_mfem_reader, read_curved_polygon_noncontiguous_attributes) //------------------------------------------------------------------------------ +#if defined(MFEM_VERSION) && (MFEM_VERSION >= 40901) +TEST(quest_mfem_reader, read_patches_format_1d_nurbs) +{ + // Minimal patch-based 1D NURBS mesh embedded in 2D. MFEM support for reading + // patch-based 1D NURBS meshes was added after the 4.9.0 release. + + fs::TempFile tmp_mesh("mfem_patches_1d_nurbs_test", ".mesh"); + + // Two "patches" (each corresponds to a 1D NURBS curve in (x,y)). + // Patch 0: degree 1, non-rational + // Patch 1: degree 2, rational (weights differ) + const std::string mesh_string = R"MFEM( +MFEM NURBS mesh v1.0 + +dimension +1 + +elements +2 +7 1 0 1 +9 1 2 3 + +boundary +0 + +edges +2 +0 0 1 +1 2 3 + +vertices +4 + +patches + +# Patch 0: degree 1 +knotvectors +1 +1 2 0 0 1 1 + +dimension +2 + +controlpoints +0 0 1 +1 0 1 + +# Patch 1: degree 2 (rational) +knotvectors +1 +2 3 0 0 0 1 1 1 + +dimension +2 + +controlpoints +# points (0,0), (0.5,0), (1,0) with weights (1,2,1) in homogeneous form (x*w, y*w, w) +0 0 1 +1 0 2 +1 0 1 +)MFEM"; + + tmp_mesh.write(mesh_string); + + quest::MFEMReader reader; + reader.setFileName(tmp_mesh.getPath()); + + axom::Array> curves; + axom::Array attributes; + EXPECT_EQ(reader.read(curves, attributes), quest::MFEMReader::READ_SUCCESS); + + ASSERT_EQ(curves.size(), 2); + ASSERT_EQ(attributes.size(), 2); + + // Attributes correspond to element attributes; ordering follows std::map key order. + std::unordered_set attribs; + for(int a : attributes) + { + attribs.insert(a); + } + EXPECT_EQ(attribs.size(), 2u); + EXPECT_TRUE(attribs.count(7) == 1u); + EXPECT_TRUE(attribs.count(9) == 1u); + + // Validate basic properties of the extracted curves. + for(int i = 0; i < curves.size(); ++i) + { + const auto &c = curves[i]; + const int a = attributes[i]; + ASSERT_TRUE(c.isValidNURBS()); + + if(a == 7) + { + EXPECT_EQ(c.getDegree(), 1); + EXPECT_EQ(c.getNumControlPoints(), 2); + EXPECT_FALSE(c.isRational()); + } + else if(a == 9) + { + EXPECT_EQ(c.getDegree(), 2); + EXPECT_EQ(c.getNumControlPoints(), 3); + EXPECT_TRUE(c.isRational()); + ASSERT_EQ(c.getWeights().size(), 3); + EXPECT_NE(c.getWeights()[0], c.getWeights()[1]); + } + else + { + FAIL() << "Unexpected MFEM attribute " << a; + } + } +} +#else +TEST(quest_mfem_reader, read_patches_format_1d_nurbs) +{ + GTEST_SKIP() << "MFEM patches-format NURBS reading requires MFEM_VERSION >= 40901"; +} +#endif + int main(int argc, char *argv[]) { ::testing::InitGoogleTest(&argc, argv); From 3d898326459608c3a909277d6832cba778ec8f5e Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Wed, 18 Mar 2026 12:39:23 -0700 Subject: [PATCH 089/986] Add OMP for 2D linearized --- src/axom/quest/GWNMethods.hpp | 18 +- .../examples/quest_winding_number_2d.cpp | 505 ++++++++++-------- 2 files changed, 278 insertions(+), 245 deletions(-) diff --git a/src/axom/quest/GWNMethods.hpp b/src/axom/quest/GWNMethods.hpp index 0f6a7b052b..bc2af68dc5 100644 --- a/src/axom/quest/GWNMethods.hpp +++ b/src/axom/quest/GWNMethods.hpp @@ -256,7 +256,7 @@ class DirectGWN2D NURBSCacheArray m_nurbs_caches; }; -template +template class PolylineGWN2D { public: @@ -291,7 +291,7 @@ class PolylineGWN2D m_segments.resize(poly_mesh->getNumberOfCells()); auto segments_view = m_segments.view(); - axom::mint::for_all_cells( + axom::mint::for_all_cells( poly_mesh, AXOM_LAMBDA(axom::IndexType cellIdx, const axom::numerics::Matrix& coords, @@ -316,7 +316,7 @@ class PolylineGWN2D auto aabbs_view = aabbs.view(); const auto segments_view = m_segments.view(); - axom::for_all( + axom::for_all( nlines, AXOM_LAMBDA(axom::IndexType i) { aabbs_view[i] = BoxType {segments_view[i].source(), segments_view[i].target()}; @@ -340,7 +340,7 @@ class PolylineGWN2D }; const auto traverser = m_bvh.getTraverser(); - m_internal_moments = traverser.reduce_tree(compute_moments); + m_internal_moments = traverser.reduce_tree(compute_moments); } stage_timer.stop(); SLIC_INFO( @@ -396,7 +396,7 @@ class PolylineGWN2D const auto traverser = m_bvh.getTraverser(); const auto internal_moments_view = m_internal_moments.view(); - axom::for_all(num_query_points, [=, &winding, &inout](axom::IndexType index) { + axom::for_all(num_query_points, [=, &winding, &inout](axom::IndexType index) { const double wn = axom::quest::fast_approximate_winding_number(query_point(index), traverser, segments_view, @@ -410,7 +410,7 @@ class PolylineGWN2D // Use direct formula else { - axom::for_all(num_query_points, [=, &winding, &inout](axom::IndexType index) { + axom::for_all(num_query_points, [=, &winding, &inout](axom::IndexType index) { const axom::primal::Point q = query_point(static_cast(index)); double wn {}; for(const auto& seg : segments_view) @@ -444,7 +444,7 @@ class PolylineGWN2D // Only needed for fast approximation method axom::Array m_internal_moments; - axom::spin::BVH<2, axom::SEQ_EXEC> m_bvh; + axom::spin::BVH<2, ExecSpace> m_bvh; }; ///@} @@ -837,8 +837,8 @@ enum class GWNInputType template struct gwn_input_traits; -template -struct gwn_input_traits> +template +struct gwn_input_traits> : std::integral_constant { }; diff --git a/src/axom/quest/examples/quest_winding_number_2d.cpp b/src/axom/quest/examples/quest_winding_number_2d.cpp index 89d227f328..cf807b7812 100644 --- a/src/axom/quest/examples/quest_winding_number_2d.cpp +++ b/src/axom/quest/examples/quest_winding_number_2d.cpp @@ -5,7 +5,7 @@ // SPDX-License-Identifier: (BSD-3-Clause) /*! - * \file quest_winding_number2d.cpp + * \file quest_winding_number_2d.cpp * \brief Example that computes the winding number of a grid of points * against a collection of 2D parametric rational curves. * Supports MFEM meshes in the cubic positive Bernstein basis or the (rational) @@ -42,214 +42,216 @@ using BoundingBox2D = primal::BoundingBox; using NURBSCurve2D = primal::NURBSCurve; +using RuntimePolicy = axom::runtime_policy::Policy; + namespace { -/** - * This helper class takes in an mfem mesh (potentially with variable order curves from mfem>4.9) - * and writes out a version that is compatible with mfem@4.9 - * In particular, this allows us to visualize it with current versions of VisIt which do not yet support this feature. - * - * We can remove this class once downstream applications (such as VisIt) are updated to a version of mfem - * that supports the NURBS patches format. - */ -class MFEM49ElevatedNURBSMeshWriter -{ -public: - explicit MFEM49ElevatedNURBSMeshWriter(double tol = 1e-12) : m_tol(tol) { } - - bool writeElevatedMesh(const std::string& input_file, const std::string& output_file) const + /** + * This helper class takes in an mfem mesh (potentially with variable order curves from mfem>4.9) + * and writes out a version that is compatible with mfem@4.9 + * In particular, this allows us to visualize it with current versions of VisIt which do not yet support this feature. + * + * We can remove this class once downstream applications (such as VisIt) are updated to a version of mfem + * that supports the NURBS patches format. + */ + class MFEM49ElevatedNURBSMeshWriter { - mfem::Mesh mesh(input_file, /*generate_edges=*/1, /*refine=*/1); - - if(mesh.NURBSext == nullptr) - { - SLIC_WARNING( - axom::fmt::format("Input mesh '{}' has no NURBS extension; skipping degree elevation", - input_file)); - return false; - } + public: + explicit MFEM49ElevatedNURBSMeshWriter(double tol = 1e-12) : m_tol(tol) {} - // Note: NURBS curve meshes in mfem@4.9 must all have the same degree, and their knotvectors must have the same length. - // MFEM calls this quantity "order"; in Axom terminology this is the polynomial degree. - const mfem::Array& orders = mesh.NURBSext->GetOrders(); - int max_order = 0; - for(int i = 0; i < orders.Size(); ++i) + bool writeElevatedMesh(const std::string& input_file, const std::string& output_file) const { - max_order = std::max(max_order, orders[i]); - } + mfem::Mesh mesh(input_file, /*generate_edges=*/1, /*refine=*/1); - if(max_order <= 0) - { - SLIC_WARNING( - axom::fmt::format("Input mesh '{}' has invalid NURBS orders; skipping degree elevation", - input_file)); - return false; - } + if (mesh.NURBSext == nullptr) + { + SLIC_WARNING( + axom::fmt::format("Input mesh '{}' has no NURBS extension; skipping degree elevation", + input_file)); + return false; + } - mesh.DegreeElevate(max_order, max_order); + // Note: NURBS curve meshes in mfem@4.9 must all have the same degree, and their knotvectors must have the same length. + // MFEM calls this quantity "order"; in Axom terminology this is the polynomial degree. + const mfem::Array& orders = mesh.NURBSext->GetOrders(); + int max_order = 0; + for (int i = 0; i < orders.Size(); ++i) + { + max_order = std::max(max_order, orders[i]); + } - makeKnotVectorsUniform(mesh); + if (max_order <= 0) + { + SLIC_WARNING( + axom::fmt::format("Input mesh '{}' has invalid NURBS orders; skipping degree elevation", + input_file)); + return false; + } - // Write the modified mesh to output_file - if(!writeMeshPrintFormat(mesh, output_file)) - { - SLIC_WARNING(axom::fmt::format("Failed to write elevated mesh '{}'", output_file)); - return false; - } + mesh.DegreeElevate(max_order, max_order); - SLIC_INFO(axom::fmt::format("Wrote elevated MFEM 4.9-compatible NURBS mesh '{}' (max order {})", - output_file, - max_order)); - return true; - } + makeKnotVectorsUniform(mesh); -private: - struct KnotBin - { - std::int64_t key {0}; - double value {0.0}; - int max_multiplicity {0}; - }; + // Write the modified mesh to output_file + if (!writeMeshPrintFormat(mesh, output_file)) + { + SLIC_WARNING(axom::fmt::format("Failed to write elevated mesh '{}'", output_file)); + return false; + } - // MFEM 4.9's 1D NURBS reader assumes all knotvectors have the same GetNE/GetNCP - // and uses KnotVec(0) when computing offsets. To generate MFEM 4.9/VisIt - // compatible files, insert knots so every knotvector matches the maximum - // knot multiplicity pattern present in the mesh. - void makeKnotVectorsUniform(mfem::Mesh& mesh) const - { - if(mesh.NURBSext == nullptr) - { - return; - } - if(mesh.Dimension() != 1 || mesh.NURBSext->Dimension() != 1) - { - return; + SLIC_INFO(axom::fmt::format("Wrote elevated MFEM 4.9-compatible NURBS mesh '{}' (max order {})", + output_file, + max_order)); + return true; } - const int nkv = mesh.NURBSext->GetNKV(); - if(nkv <= 1) + private: + struct KnotBin { - return; - } - - const double inv_tol = (m_tol > 0.0) ? (1.0 / m_tol) : 1.0e12; - auto key_for = [inv_tol](double t) -> std::int64_t { - return static_cast(std::llround(t * inv_tol)); + std::int64_t key{ 0 }; + double value{ 0.0 }; + int max_multiplicity{ 0 }; }; - std::vector> counts(nkv); - std::unordered_map bins; - - for(int i = 0; i < nkv; ++i) + // MFEM 4.9's 1D NURBS reader assumes all knotvectors have the same GetNE/GetNCP + // and uses KnotVec(0) when computing offsets. To generate MFEM 4.9/VisIt + // compatible files, insert knots so every knotvector matches the maximum + // knot multiplicity pattern present in the mesh. + void makeKnotVectorsUniform(mfem::Mesh& mesh) const { - const mfem::KnotVector* kv = mesh.NURBSext->GetKnotVector(i); - if(kv == nullptr) + if (mesh.NURBSext == nullptr) { - continue; + return; + } + if (mesh.Dimension() != 1 || mesh.NURBSext->Dimension() != 1) + { + return; } - auto& local = counts[i]; - for(int j = 0; j < kv->Size(); ++j) + const int nkv = mesh.NURBSext->GetNKV(); + if (nkv <= 1) { - const double value = (*kv)[j]; - const std::int64_t key = key_for(value); - - const int multiplicity = ++local[key]; - auto& bin = bins[key]; - bin.key = key; - bin.value = value; - bin.max_multiplicity = std::max(bin.max_multiplicity, multiplicity); + return; } - } - std::vector sorted_bins; - sorted_bins.reserve(bins.size()); - for(const auto& it : bins) - { - sorted_bins.push_back(it.second); - } - std::sort(sorted_bins.begin(), sorted_bins.end(), [](const KnotBin& a, const KnotBin& b) { - if(a.value != b.value) + const double inv_tol = (m_tol > 0.0) ? (1.0 / m_tol) : 1.0e12; + auto key_for = [inv_tol](double t) -> std::int64_t { + return static_cast(std::llround(t * inv_tol)); + }; + + std::vector> counts(nkv); + std::unordered_map bins; + + for (int i = 0; i < nkv; ++i) { - return a.value < b.value; + const mfem::KnotVector* kv = mesh.NURBSext->GetKnotVector(i); + if (kv == nullptr) + { + continue; + } + + auto& local = counts[i]; + for (int j = 0; j < kv->Size(); ++j) + { + const double value = (*kv)[j]; + const std::int64_t key = key_for(value); + + const int multiplicity = ++local[key]; + auto& bin = bins[key]; + bin.key = key; + bin.value = value; + bin.max_multiplicity = std::max(bin.max_multiplicity, multiplicity); + } } - return a.key < b.key; - }); - int total_to_insert = 0; - mfem::Array insertions(nkv); - for(int i = 0; i < nkv; ++i) - { - int need_total = 0; - for(const auto& bin : sorted_bins) + std::vector sorted_bins; + sorted_bins.reserve(bins.size()); + for (const auto& it : bins) { - const auto found = counts[i].find(bin.key); - const int have = (found == counts[i].end()) ? 0 : found->second; - need_total += std::max(0, bin.max_multiplicity - have); + sorted_bins.push_back(it.second); } + std::sort(sorted_bins.begin(), sorted_bins.end(), [](const KnotBin& a, const KnotBin& b) { + if (a.value != b.value) + { + return a.value < b.value; + } + return a.key < b.key; + }); - insertions[i] = new mfem::Vector(need_total); - int pos = 0; - for(const auto& bin : sorted_bins) + int total_to_insert = 0; + mfem::Array insertions(nkv); + for (int i = 0; i < nkv; ++i) { - const auto found = counts[i].find(bin.key); - const int have = (found == counts[i].end()) ? 0 : found->second; - const int need = std::max(0, bin.max_multiplicity - have); - for(int k = 0; k < need; ++k) + int need_total = 0; + for (const auto& bin : sorted_bins) + { + const auto found = counts[i].find(bin.key); + const int have = (found == counts[i].end()) ? 0 : found->second; + need_total += std::max(0, bin.max_multiplicity - have); + } + + insertions[i] = new mfem::Vector(need_total); + int pos = 0; + for (const auto& bin : sorted_bins) { - (*insertions[i])[pos++] = bin.value; + const auto found = counts[i].find(bin.key); + const int have = (found == counts[i].end()) ? 0 : found->second; + const int need = std::max(0, bin.max_multiplicity - have); + for (int k = 0; k < need; ++k) + { + (*insertions[i])[pos++] = bin.value; + } } + total_to_insert += need_total; } - total_to_insert += need_total; - } - if(total_to_insert > 0) - { - mesh.KnotInsert(insertions); - } + if (total_to_insert > 0) + { + mesh.KnotInsert(insertions); + } - for(int i = 0; i < nkv; ++i) - { - delete insertions[i]; + for (int i = 0; i < nkv; ++i) + { + delete insertions[i]; + } } - } - - bool writeMeshPrintFormat(const mfem::Mesh& mesh, const std::string& output_file) const - { - std::ostringstream oss; - oss.precision(16); - mesh.Print(oss); - std::istringstream iss(oss.str()); - std::ofstream ofs(output_file); - if(!ofs.is_open()) + bool writeMeshPrintFormat(const mfem::Mesh& mesh, const std::string& output_file) const { - return false; - } - ofs.precision(16); + std::ostringstream oss; + oss.precision(16); + mesh.Print(oss); - // Note: mfem@4.9's mesh reader does not support the "NURBS2" finite element collection, - // if it occurs, we replace it with the (essentially equivalent) "NURBS" - std::string line; - while(std::getline(iss, line)) - { - constexpr const char* prefix = "FiniteElementCollection: NURBS"; - if(line.rfind(prefix, 0) == 0) + std::istringstream iss(oss.str()); + std::ofstream ofs(output_file); + if (!ofs.is_open()) { - ofs << prefix << '\n'; + return false; } - else + ofs.precision(16); + + // Note: mfem@4.9's mesh reader does not support the "NURBS2" finite element collection, + // if it occurs, we replace it with the (essentially equivalent) "NURBS" + std::string line; + while (std::getline(iss, line)) { - ofs << line << '\n'; + constexpr const char* prefix = "FiniteElementCollection: NURBS"; + if (line.rfind(prefix, 0) == 0) + { + ofs << prefix << '\n'; + } + else + { + ofs << line << '\n'; + } } - } - return true; - } + return true; + } -private: - double m_tol {1e-12}; -}; + private: + double m_tol{ 1e-12 }; + }; } // namespace @@ -261,30 +263,32 @@ class Input { public: std::string inputFile; - std::string outputPrefix = {"winding2d"}; + std::string outputPrefix = { "winding2d" }; - bool verbose {false}; - std::string annotationMode {"none"}; - bool memoized {true}; - bool vis {true}; - bool stats {false}; + bool verbose{ false }; + std::string annotationMode{ "none" }; + bool memoized{ true }; + bool vis{ true }; + bool stats{ false }; std::string elevatedMeshFile; - const std::array valid_algorithms {"direct", "fast-approximation"}; - std::string algorithm {valid_algorithms[1]}; // fast-approximation + axom::runtime_policy::Policy policy = RuntimePolicy::seq; + + const std::array valid_algorithms{ "direct", "fast-approximation" }; + std::string algorithm{ valid_algorithms[1] }; // fast-approximation - bool linearize {false}; - int approximation_order {2}; + bool linearize{ false }; + int approximation_order{ 2 }; bool useUniformLinearization; - int segmentsPerKnotSpan {10}; - double percentError {1.0}; + int segmentsPerKnotSpan{ 10 }; + double percentError{ 1.0 }; // Query mesh parameters std::vector boxMins; std::vector boxMaxs; std::vector boxResolution; - int queryOrder {1}; + int queryOrder{ 1 }; primal::WindingTolerances tol; @@ -333,26 +337,36 @@ class Input ->capture_default_str() ->check(axom::utilities::ValidCaliperMode); #endif + std::stringstream pol_sstr; + pol_sstr << "Set MIR runtime policy method."; + pol_sstr << "\nSet to 'seq' or 0 to use the RAJA sequential policy."; +#ifdef AXOM_RUNTIME_POLICY_USE_OPENMP + pol_sstr << "\nSet to 'omp' or 1 to use the RAJA OpenMP policy."; +#endif + + app.add_option("-p, --policy", policy, pol_sstr.str()) + ->capture_default_str() + ->transform(axom::CLI::CheckedTransformer(axom::runtime_policy::s_nameToPolicy)); // Options for triangulation of the input STEP file auto* linearize_curves_subcommand = app.add_subcommand("linearize_curves") - ->description("Options for linearizing NURBS curves. Default is ") - ->fallthrough(); + ->description("Options for linearizing NURBS curves. Default is ") + ->fallthrough(); auto* nsegments = linearize_curves_subcommand->add_option("--num-segments", segmentsPerKnotSpan) - ->description( - "Number of segments for each knot span of each input curve for a uniform linearization.") - ->check(axom::CLI::PositiveNumber) - ->capture_default_str(); + ->description( + "Number of segments for each knot span of each input curve for a uniform linearization.") + ->check(axom::CLI::PositiveNumber) + ->capture_default_str(); auto* perror = linearize_curves_subcommand->add_option("--percent-error", percentError) - ->description( - "The percent of error that is acceptable to stop refinement during non-uniform " - "linearization.") - ->check(axom::CLI::Range(0.0f, 100.0f)) - ->capture_default_str(); + ->description( + "The percent of error that is acceptable to stop refinement during non-uniform " + "linearization.") + ->check(axom::CLI::Range(0.0f, 100.0f)) + ->capture_default_str(); linearize_curves_subcommand->add_option("--algorithm", algorithm) ->description( "Use direct evaluation instead of fast, heirarchical approximation? (significantly " @@ -361,19 +375,19 @@ class Input ->check(axom::CLI::IsMember(valid_algorithms)); linearize_curves_subcommand ->add_option("--approximation-order", - approximation_order, - "The order of the Taylor expansion (lower is faster, less precise)") + approximation_order, + "The order of the Taylor expansion (lower is faster, less precise)") ->expected(0, 2) ->capture_default_str(); auto* query_mesh_subcommand = app.add_subcommand("query_mesh")->description("Options for setting up a query mesh")->fallthrough(); auto* minbb = query_mesh_subcommand->add_option("--min", boxMins) - ->description("Min bounds for box mesh (x,y)") - ->expected(2); + ->description("Min bounds for box mesh (x,y)") + ->expected(2); auto* maxbb = query_mesh_subcommand->add_option("--max", boxMaxs) - ->description("Max bounds for box mesh (x,y)") - ->expected(2); + ->description("Max bounds for box mesh (x,y)") + ->expected(2); query_mesh_subcommand->add_option("--res", boxResolution) ->description("Resolution of the box mesh (i,j)") ->expected(2) @@ -397,29 +411,47 @@ class Input }; using GWNQueryType = std::variant, - axom::quest::PolylineGWN2D<1>, - axom::quest::PolylineGWN2D<2>>; - -GWNQueryType make_gwn_query(bool linearize_curves, int approximation_order) + axom::quest::PolylineGWN2D, + axom::quest::PolylineGWN2D, + axom::quest::PolylineGWN2D, + axom::quest::PolylineGWN2D, + axom::quest::PolylineGWN2D, + axom::quest::PolylineGWN2D>; + +template +GWNQueryType pick_gwn_method(bool linearize_curves, int approximation_order) { - if(linearize_curves) + if (linearize_curves) { - if(approximation_order == 0) + if (approximation_order == 0) { - return axom::quest::PolylineGWN2D<0> {}; + return axom::quest::PolylineGWN2D {}; } - else if(approximation_order == 1) + else if (approximation_order == 1) { - return axom::quest::PolylineGWN2D<1> {}; + return axom::quest::PolylineGWN2D {}; } - else + else // approximation_order == 2 { - return axom::quest::PolylineGWN2D<2> {}; + return axom::quest::PolylineGWN2D {}; } } - return axom::quest::DirectGWN2D {}; + return axom::quest::DirectGWN2D{}; +} + +GWNQueryType make_gwn_query(axom::runtime_policy::Policy policy, + bool linearize_curves, + int approximation_order) +{ + if (policy == RuntimePolicy::omp) + { + SLIC_INFO(axom::fmt::format("Using policy omp with {} threads", omp_get_max_threads())); + return pick_gwn_method(linearize_curves, approximation_order); + } + + SLIC_INFO("Using policy seq"); + return pick_gwn_method(linearize_curves, approximation_order); } int main(int argc, char** argv) @@ -428,15 +460,15 @@ int main(int argc, char** argv) // Parse command line arguments into input Input input; - axom::CLI::App app { + axom::CLI::App app{ "Load mesh containing collection of curves" - " and optionally generate a query mesh of winding numbers."}; + " and optionally generate a query mesh of winding numbers." }; try { input.parse(argc, argv, app); } - catch(const axom::CLI::ParseError& e) + catch (const axom::CLI::ParseError& e) { return app.exit(e); } @@ -444,7 +476,7 @@ int main(int argc, char** argv) axom::utilities::raii::AnnotationsWrapper annotation_raii_wrapper(input.annotationMode); AXOM_ANNOTATE_SCOPE("winding number example"); - if(!input.elevatedMeshFile.empty()) + if (!input.elevatedMeshFile.empty()) { AXOM_ANNOTATE_SCOPE("write_elevated_mesh"); @@ -461,7 +493,7 @@ int main(int argc, char** argv) mfem_reader.setFileName(input.inputFile); const int ret = mfem_reader.read(curves); - if(ret != axom::quest::MFEMReader::READ_SUCCESS) + if (ret != axom::quest::MFEMReader::READ_SUCCESS) { SLIC_ERROR("Failed to read MFEM file."); return 1; @@ -470,13 +502,13 @@ int main(int argc, char** argv) // Linearize the input curves if asked for axom::mint::UnstructuredMesh poly_mesh(2, axom::mint::SEGMENT); - if(input.linearize) + if (input.linearize) { AXOM_ANNOTATE_SCOPE("linearization"); axom::utilities::Timer timer(true); axom::quest::LinearizeCurves lc; - if(input.useUniformLinearization) + if (input.useUniformLinearization) { lc.getLinearMeshUniform(curves.view(), &poly_mesh, input.segmentsPerKnotSpan); } @@ -497,14 +529,14 @@ int main(int argc, char** argv) } // Early return if user didn't set up a query mesh - if(input.boxResolution.empty()) + if (input.boxResolution.empty()) { return 0; } // Extract the curves and compute their bounding boxes along the way BoundingBox2D shape_bbox; - for(const auto& cur : curves) + for (const auto& cur : curves) { shape_bbox.addBox(cur.boundingBox()); } @@ -515,25 +547,26 @@ int main(int argc, char** argv) mfem::DataCollection dc("winding_query"); { // Create the desired winding number query instance - auto wn_query = make_gwn_query(app.got_subcommand("linearize_curves"), input.approximation_order); + auto wn_query = + make_gwn_query(input.policy, app.got_subcommand("linearize_curves"), input.approximation_order); // Generate the query grid and fields quest::generate_gwn_query_mesh(dc, - shape_bbox, - input.boxMins, - input.boxMaxs, - input.boxResolution, - input.queryOrder); + shape_bbox, + input.boxMins, + input.boxMaxs, + input.boxResolution, + input.queryOrder); // Run the preprocess std::visit( [&](auto& wn) { using T = std::decay_t; - if constexpr(quest::gwn_input_type_v == quest::GWNInputType::Curve) + if constexpr (quest::gwn_input_type_v == quest::GWNInputType::Curve) { wn.preprocess(curves, input.memoized); } - else if constexpr(quest::gwn_input_type_v == quest::GWNInputType::Polyline) + else if constexpr (quest::gwn_input_type_v == quest::GWNInputType::Polyline) { wn.preprocess(&poly_mesh, input.algorithm == "direct"); } @@ -545,7 +578,7 @@ int main(int argc, char** argv) } // Postprocess query results: norms, ranges, and integral statistics - if(input.stats) + if (input.stats) { AXOM_ANNOTATE_SCOPE("postprocess"); @@ -558,14 +591,14 @@ int main(int argc, char** argv) std::int64_t pos_inout_dofs = 0; std::int64_t neg_inout_dofs = 0; - for(int i = 0; i < inout.Size(); ++i) + for (int i = 0; i < inout.Size(); ++i) { const double v = inout[i]; - if(v > 0.0) + if (v > 0.0) { ++pos_inout_dofs; } - else if(v < 0.0) + else if (v < 0.0) { ++neg_inout_dofs; } @@ -573,11 +606,11 @@ int main(int argc, char** argv) SLIC_INFO( axom::fmt::format("WN_STATS: dof_l2={:.6e} dof_linf={:.6e} l2={:.6e} min={:.6e} max={:.6e}", - winding_stats.dof_l2, - winding_stats.dof_linf, - winding_stats.l2, - winding_stats.min, - winding_stats.max)); + winding_stats.dof_l2, + winding_stats.dof_linf, + winding_stats.l2, + winding_stats.min, + winding_stats.max)); SLIC_INFO(axom::fmt::format( "INOUT_STATS: dof_l2={:.6e} dof_linf={:.6e} l2={:.6e} min={:.6e} max={:.6e} volume={:.6e} " @@ -590,13 +623,13 @@ int main(int argc, char** argv) inout_integrals.integral, inout_integrals.domain_volume, (inout_integrals.domain_volume > 0.0 ? inout_integrals.integral / inout_integrals.domain_volume - : 0.0), + : 0.0), pos_inout_dofs, neg_inout_dofs)); } // Save the query mesh and fields to disk using a format that can be viewed in VisIt - if(input.vis) + if (input.vis) { AXOM_ANNOTATE_SCOPE("dump_mesh"); @@ -606,8 +639,8 @@ int main(int argc, char** argv) windingDC.Save(); SLIC_INFO(axom::fmt::format("Outputting generated mesh '{}' to '{}'", - windingDC.GetCollectionName(), - axom::utilities::filesystem::getCWD())); + windingDC.GetCollectionName(), + axom::utilities::filesystem::getCWD())); } return 0; From 68d46b5332cb68145532b43817a67543fa3cd469 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Mon, 23 Mar 2026 11:03:00 -0700 Subject: [PATCH 090/986] Update with some 3D methods --- src/axom/quest/GWNMethods.hpp | 20 +++---- .../examples/quest_winding_number_3d.cpp | 58 ++++++++++++++----- 2 files changed, 55 insertions(+), 23 deletions(-) diff --git a/src/axom/quest/GWNMethods.hpp b/src/axom/quest/GWNMethods.hpp index bc2af68dc5..dfdee0bc2f 100644 --- a/src/axom/quest/GWNMethods.hpp +++ b/src/axom/quest/GWNMethods.hpp @@ -594,7 +594,7 @@ class DirectGWN3D NURBSCacheArray m_nurbs_caches; }; -template +template class TriangleGWN3D { public: @@ -628,7 +628,7 @@ class TriangleGWN3D // Iterate over mesh nodes and get a bounding box for the shape BoxType shape_bbox; BoxType* shape_bbox_ptr = &shape_bbox; - axom::mint::for_all_nodes( + axom::mint::for_all_nodes( tri_mesh, AXOM_LAMBDA(axom::IndexType, double x, double y, double z) { shape_bbox_ptr->addPoint(Point3D {x, y, z}); @@ -645,7 +645,7 @@ class TriangleGWN3D m_triangles.resize(ntris); auto triangles_view = m_triangles.view(); - axom::mint::for_all_cells( + axom::mint::for_all_cells( tri_mesh, AXOM_LAMBDA(axom::IndexType cellIdx, const axom::numerics::Matrix& coords, @@ -677,7 +677,7 @@ class TriangleGWN3D auto aabbs_view = aabbs.view(); const auto triangles_view = m_triangles.view(); - axom::for_all( + axom::for_all( ntris, AXOM_LAMBDA(axom::IndexType i) { aabbs_view[i] = @@ -702,7 +702,7 @@ class TriangleGWN3D }; const auto traverser = m_bvh.getTraverser(); - m_internal_moments = traverser.reduce_tree(compute_moments); + m_internal_moments = traverser.reduce_tree(compute_moments); } stage_timer.stop(); SLIC_INFO( @@ -766,7 +766,7 @@ class TriangleGWN3D const auto traverser = m_bvh.getTraverser(); const auto internal_moments_view = m_internal_moments.view(); - axom::for_all(num_query_points, [=, &winding, &inout](axom::IndexType index) { + axom::for_all(num_query_points, [=, &winding, &inout](axom::IndexType index) { const double wn = axom::quest::fast_approximate_winding_number(scaled_query_point(index), traverser, triangles_view, @@ -780,7 +780,7 @@ class TriangleGWN3D // Use direct formula else { - axom::for_all(num_query_points, [=, &winding, &inout](axom::IndexType index) { + axom::for_all(num_query_points, [=, &winding, &inout](axom::IndexType index) { const auto q = scaled_query_point(static_cast(index)); double wn {}; for(const auto& tri : triangles_view) @@ -814,7 +814,7 @@ class TriangleGWN3D // Only needed for fast approximation method axom::Array m_internal_moments; - axom::spin::BVH<3, axom::SEQ_EXEC> m_bvh; + axom::spin::BVH<3, ExecSpace> m_bvh; // Parameters for normalization axom::primal::Point m_shape_center; @@ -847,8 +847,8 @@ struct gwn_input_traits : std::integral_constant { }; -template -struct gwn_input_traits> +template +struct gwn_input_traits> : std::integral_constant { }; diff --git a/src/axom/quest/examples/quest_winding_number_3d.cpp b/src/axom/quest/examples/quest_winding_number_3d.cpp index 5dda1c53eb..592c022a08 100644 --- a/src/axom/quest/examples/quest_winding_number_3d.cpp +++ b/src/axom/quest/examples/quest_winding_number_3d.cpp @@ -40,6 +40,8 @@ using BoundingBox3D = primal::BoundingBox; using NURBSPatch3D = quest::STEPReader::NURBSPatch; using Triangle3D = primal::Triangle; +using RuntimePolicy = axom::runtime_policy::Policy; + //------------------------------------------------------------------------------ // CLI input //------------------------------------------------------------------------------ @@ -57,6 +59,8 @@ class Input bool validate {false}; bool stats {false}; + axom::runtime_policy::Policy policy = RuntimePolicy::seq; + const std::array valid_algorithms {"direct", "fast-approximation"}; std::string algorithm {valid_algorithms[1]}; // fast-approximation @@ -160,6 +164,16 @@ class Input ->capture_default_str() ->check(axom::utilities::ValidCaliperMode); #endif + std::stringstream pol_sstr; + pol_sstr << "Set MIR runtime policy method."; + pol_sstr << "\nSet to 'seq' or 0 to use the RAJA sequential policy."; +#ifdef AXOM_RUNTIME_POLICY_USE_OPENMP + pol_sstr << "\nSet to 'omp' or 1 to use the RAJA OpenMP policy."; +#endif + + app.add_option("-p, --policy", policy, pol_sstr.str()) + ->capture_default_str() + ->transform(axom::CLI::CheckedTransformer(axom::runtime_policy::s_nameToPolicy)); auto* query_mesh_subcommand = app.add_subcommand("query_mesh")->description("Options for setting up a query mesh")->fallthrough(); @@ -232,31 +246,49 @@ class Input }; using GWNQueryType = std::variant, - axom::quest::TriangleGWN3D<1>, - axom::quest::TriangleGWN3D<2>>; - -GWNQueryType make_gwn_query(Input input) + axom::quest::TriangleGWN3D, + axom::quest::TriangleGWN3D, + axom::quest::TriangleGWN3D, + axom::quest::TriangleGWN3D, + axom::quest::TriangleGWN3D, + axom::quest::TriangleGWN3D>; + +template +GWNQueryType pick_gwn_method(bool triangulate, int approximation_order) { - if(input.triangulate) + if(triangulate) { - if(input.approximation_order == 0) + if(approximation_order == 0) { - return axom::quest::TriangleGWN3D<0> {}; + return axom::quest::TriangleGWN3D {}; } - else if(input.approximation_order == 1) + else if(approximation_order == 1) { - return axom::quest::TriangleGWN3D<1> {}; + return axom::quest::TriangleGWN3D {}; } - else + else // approximation_order == 2 { - return axom::quest::TriangleGWN3D<2> {}; + return axom::quest::TriangleGWN3D {}; } } return axom::quest::DirectGWN3D {}; } +GWNQueryType make_gwn_query(axom::runtime_policy::Policy policy, + bool triangulate, + int approximation_order) +{ + if(policy == RuntimePolicy::omp) + { + SLIC_INFO(axom::fmt::format("Using policy omp with {} threads", omp_get_max_threads())); + return pick_gwn_method(triangulate, approximation_order); + } + + SLIC_INFO("Using policy seq"); + return pick_gwn_method(triangulate, approximation_order); +} + int main(int argc, char** argv) { axom::slic::SimpleLogger raii_logger; @@ -398,7 +430,7 @@ int main(int argc, char** argv) mfem::DataCollection dc("winding_query"); { // Create the desired winding number query instance - auto wn_query = make_gwn_query(input); + auto wn_query = make_gwn_query(input.policy, input.triangulate, input.approximation_order); // Generate the query grid and fields quest::generate_gwn_query_mesh(dc, From b4ee0f0d0add4bbffe7b7395ef12f2c3e26bb5ae Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Tue, 24 Mar 2026 19:04:55 -0700 Subject: [PATCH 091/986] Added OMP support for 2D curves --- .../detail/winding_number_2d_memoization.hpp | 111 ++++++++++++++++++ src/axom/primal/operators/winding_number.hpp | 17 +++ src/axom/quest/GWNMethods.hpp | 39 +++--- .../examples/quest_winding_number_2d.cpp | 17 +-- 4 files changed, 155 insertions(+), 29 deletions(-) diff --git a/src/axom/primal/operators/detail/winding_number_2d_memoization.hpp b/src/axom/primal/operators/detail/winding_number_2d_memoization.hpp index 87084b073a..de527c0c8c 100644 --- a/src/axom/primal/operators/detail/winding_number_2d_memoization.hpp +++ b/src/axom/primal/operators/detail/winding_number_2d_memoization.hpp @@ -256,6 +256,117 @@ std::ostream& operator<<(std::ostream& os, const NURBSCurveGWNCache& nCurveCa } } // namespace detail + +/*! + * \brief Manage an array of NURBSCurveGWNCache + */ +class NURBSCurveCacheManager +{ + using NURBSCache = axom::primal::detail::NURBSCurveGWNCache; + using NURBSCacheArray = axom::Array; + using NURBSCacheArrayView = axom::ArrayView; + + using CurveArrayView = axom::ArrayView>; + +public: + NURBSCurveCacheManager() = default; + + NURBSCurveCacheManager(const CurveArrayView& curves, double bbExpansionAmount = 0.0) + { + for(const auto& curve : curves) + { + m_nurbs_caches.push_back(NURBSCache(curve, bbExpansionAmount)); + } + } + + /// A view of the manager object. + struct View + { + NURBSCacheArrayView m_view; + + /// Return the NURBSCacheArrayView. + NURBSCacheArrayView caches() const { return m_view; } + }; + + /// Return a view of this manager to pass into a device function. + View view() const { return View {m_nurbs_caches.view()}; } + + /// Return if the underlying array is empty + bool empty() const { return m_nurbs_caches.empty(); } + +private: + NURBSCacheArray m_nurbs_caches; +}; + +template +struct nurbs_cache_2d_traits +{ + using type = NURBSCurveCacheManager; +}; + +#if defined(AXOM_USE_OPENMP) +/*! + * \brief Manage per-thread arrays of NURBSCurveGWNCache + */ +class NURBSCurveCacheManagerOMP +{ + using NURBSCache = axom::primal::detail::NURBSCurveGWNCache; + using NURBSCachePerThreadArray = axom::Array>; + using NURBSCachePerThreadArrayView = axom::ArrayView>; + using NURBSCacheArrayView = axom::ArrayView; + + using CurveArrayView = axom::ArrayView>; + +public: + NURBSCurveCacheManagerOMP() = default; + + NURBSCurveCacheManagerOMP(const CurveArrayView& curves, double bbExpansionAmount = 0.0) + { + const int nt = omp_get_max_threads(); + m_nurbs_caches.resize(nt); + auto nurbs_caches_view = m_nurbs_caches.view(); + + // Make the first one + nurbs_caches_view[0].resize(curves.size()); + axom::for_all( + curves.size(), + AXOM_LAMBDA(axom::IndexType i) { + nurbs_caches_view[0][i] = NURBSCache(curves[i], bbExpansionAmount); + }); + + // Copy the constructed cache to the other threads' copies (less work than construction) + axom::for_all( + 1, + nt, + AXOM_LAMBDA(axom::IndexType t) { nurbs_caches_view[t] = nurbs_caches_view[0]; }); + } + + /// A view of the manager object. + struct View + { + NURBSCachePerThreadArrayView m_views; + + /// Return the NURBSCacheArrayView for the current OMP thread. + NURBSCacheArrayView caches() const { return m_views[omp_get_thread_num()].view(); } + }; + + /// Return a view of this manager to pass into a device function. + View view() const { return View {m_nurbs_caches.view()}; } + + /// Return if the underlying array is empty + bool empty() const { return m_nurbs_caches.empty(); } + +private: + NURBSCachePerThreadArray m_nurbs_caches; +}; + +template <> +struct nurbs_cache_2d_traits +{ + using type = NURBSCurveCacheManagerOMP; +}; +#endif + } // namespace primal } // namespace axom diff --git a/src/axom/primal/operators/winding_number.hpp b/src/axom/primal/operators/winding_number.hpp index 3d0d97b6a0..f9d719aad0 100644 --- a/src/axom/primal/operators/winding_number.hpp +++ b/src/axom/primal/operators/winding_number.hpp @@ -347,6 +347,23 @@ double winding_number(const Point& query, return gwn; } +/// \brief Overload for views +template +double winding_number(const Point& query, + const axom::ArrayView>& nurbs_curve_arr, + double edge_tol = 1e-8, + double EPS = 1e-8) +{ + double gwn = 0; + bool dummy_isOnCurve = false; + for(int i = 0; i < nurbs_curve_arr.size(); ++i) + { + gwn += winding_number(query, nurbs_curve_arr[i], dummy_isOnCurve, edge_tol, EPS); + } + + return gwn; +} + //! \brief Overload without optional return parameter template double winding_number(const Point& query, diff --git a/src/axom/quest/GWNMethods.hpp b/src/axom/quest/GWNMethods.hpp index dfdee0bc2f..d50b0171d5 100644 --- a/src/axom/quest/GWNMethods.hpp +++ b/src/axom/quest/GWNMethods.hpp @@ -128,11 +128,12 @@ void generate_gwn_query_mesh(mfem::DataCollection& dc, ///@{ /// \name Query methods for 2D GWN applications +template class DirectGWN2D { public: using CurveArrayType = axom::Array>; - using NURBSCacheArray = axom::Array>; + using NURBSCacheManager = typename axom::primal::nurbs_cache_2d_traits::type; DirectGWN2D() = default; @@ -152,11 +153,7 @@ class DirectGWN2D AXOM_ANNOTATE_SCOPE("preprocessing"); if(use_memoization) { - m_nurbs_caches.reserve(input_curves.size()); - for(const auto& curv : input_curves) - { - m_nurbs_caches.emplace_back(curv); - } + m_nurbs_cache_mgr = NURBSCacheManager(input_curves); } } timer.stop(); @@ -206,14 +203,14 @@ class DirectGWN2D const auto input_curves_view = m_input_curves_view; // Use non-memoized form - if(m_nurbs_caches.empty()) + if(m_nurbs_cache_mgr.empty()) { - axom::for_all(num_query_points, [=, &winding, &inout](axom::IndexType nidx) { + axom::for_all(num_query_points, [=, &winding, &inout](axom::IndexType nidx) { const auto q = query_point(static_cast(nidx)); double wn {}; - for(const auto& cache : input_curves_view) + for(const auto& curve : m_input_curves_view) { - wn += axom::primal::winding_number(q, cache, tol_copy.edge_tol, tol_copy.EPS); + wn += axom::primal::winding_number(q, curve, tol_copy.edge_tol, tol_copy.EPS); } winding[static_cast(nidx)] = wn; inout[static_cast(nidx)] = std::lround(wn); @@ -221,14 +218,14 @@ class DirectGWN2D } else // Use memoized form { - const auto nurbs_caches_view = m_nurbs_caches.view(); - axom::for_all(num_query_points, [=, &winding, &inout](axom::IndexType nidx) { + const auto cache_mgr_view = m_nurbs_cache_mgr.view(); + axom::for_all(num_query_points, [=, &winding, &inout](axom::IndexType nidx) { const auto q = query_point(static_cast(nidx)); - double wn {}; - for(const auto& cache : nurbs_caches_view) - { - wn += axom::primal::winding_number(q, cache, tol_copy.edge_tol, tol_copy.EPS); - } + const auto caches_view = cache_mgr_view.caches(); + + const double wn = + axom::primal::winding_number(q, caches_view, tol_copy.edge_tol, tol_copy.EPS); + winding[static_cast(nidx)] = wn; inout[static_cast(nidx)] = std::lround(wn); }); @@ -243,7 +240,7 @@ class DirectGWN2D "Querying {:L} samples in winding number field with{} memoization took {:.3Lf} seconds" " (@ {:.0Lf} queries per second; {:.6Lf} ms per query)", num_query_points, - m_nurbs_caches.empty() ? "out" : "", + m_nurbs_cache_mgr.empty() ? "out" : "", query_time_s, num_query_points / query_time_s, ms_per_query)); @@ -253,7 +250,7 @@ class DirectGWN2D private: axom::ArrayView> m_input_curves_view; - NURBSCacheArray m_nurbs_caches; + NURBSCacheManager m_nurbs_cache_mgr; }; template @@ -842,8 +839,8 @@ struct gwn_input_traits> : std::integral_constant { }; -template <> -struct gwn_input_traits +template +struct gwn_input_traits> : std::integral_constant { }; diff --git a/src/axom/quest/examples/quest_winding_number_2d.cpp b/src/axom/quest/examples/quest_winding_number_2d.cpp index cf807b7812..6bc458b906 100644 --- a/src/axom/quest/examples/quest_winding_number_2d.cpp +++ b/src/axom/quest/examples/quest_winding_number_2d.cpp @@ -410,13 +410,14 @@ class Input } }; -using GWNQueryType = std::variant, - axom::quest::PolylineGWN2D, - axom::quest::PolylineGWN2D, - axom::quest::PolylineGWN2D, - axom::quest::PolylineGWN2D, - axom::quest::PolylineGWN2D>; +using GWNQueryType = std::variant, + axom::quest::DirectGWN2D, + axom::quest::PolylineGWN2D, + axom::quest::PolylineGWN2D, + axom::quest::PolylineGWN2D, + axom::quest::PolylineGWN2D, + axom::quest::PolylineGWN2D, + axom::quest::PolylineGWN2D>; template GWNQueryType pick_gwn_method(bool linearize_curves, int approximation_order) @@ -437,7 +438,7 @@ GWNQueryType pick_gwn_method(bool linearize_curves, int approximation_order) } } - return axom::quest::DirectGWN2D{}; + return axom::quest::DirectGWN2D {}; } GWNQueryType make_gwn_query(axom::runtime_policy::Policy policy, From 50dce97b5fcdf3568b39e1568b3eb70192f61041 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Fri, 27 Mar 2026 14:24:55 -0700 Subject: [PATCH 092/986] Add 3D OMP support --- src/axom/core/numerics/quadrature.cpp | 7 +- .../detail/winding_number_3d_memoization.hpp | 133 +++++++++++++++++- src/axom/primal/operators/winding_number.hpp | 64 ++++----- src/axom/quest/GWNMethods.hpp | 46 +++--- .../examples/quest_winding_number_2d.cpp | 2 +- .../examples/quest_winding_number_3d.cpp | 7 +- 6 files changed, 192 insertions(+), 67 deletions(-) diff --git a/src/axom/core/numerics/quadrature.cpp b/src/axom/core/numerics/quadrature.cpp index 7cc0367514..709992ed95 100644 --- a/src/axom/core/numerics/quadrature.cpp +++ b/src/axom/core/numerics/quadrature.cpp @@ -15,6 +15,7 @@ #include #include +#include namespace axom { @@ -158,7 +159,7 @@ void compute_gauss_legendre_data(int npts, * \note If this method has already been called for a given order, it will reuse the same quadrature points * without needing to recompute them * - * \warning The use of a static variable to store cached nodes makes this method not threadsafe. + * \note This implementation uses a process-wide cache protected by a mutex for thread safety. * * \return The `QuadratureRule` object which contains axom::ArrayView's of stored nodes and weights */ @@ -166,8 +167,10 @@ QuadratureRule get_gauss_legendre(int npts, int allocatorID) { assert("Quadrature rules must have >= 1 point" && (npts >= 1)); - // Store cached rules keyed by (npts, allocatorID). This cache is not thread-safe. + // Store cached rules keyed by (npts, allocatorID). static axom::FlatMap rule_library(64); + static std::mutex rule_library_mutex; + const std::lock_guard lock(rule_library_mutex); const std::uint64_t key = make_gauss_legendre_key(npts, allocatorID); diff --git a/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp b/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp index c4486b81c4..235a865df9 100644 --- a/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp +++ b/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp @@ -170,6 +170,19 @@ class NURBSPatchGWNCache m_averageNormal = m_alteredPatch.calculateTrimmedPatchNormal(); } + // Cast direction is set to average normal, unless it is near zero + if(m_averageNormal.norm() < 1e-10) + { + // ...unless the average direction is zero + double theta = axom::utilities::random_real(0.0, 2 * M_PI); + double u = axom::utilities::random_real(-1.0, 1.0); + m_castDirection = Vector {sin(theta) * sqrt(1 - u * u), cos(theta) * sqrt(1 - u * u), u}; + } + else + { + m_castDirection = m_averageNormal; + } + m_pboxDiag = m_alteredPatch.getParameterSpaceDiagonal(); // Make a bounding box by doing (trimmed) bezier extraction, @@ -238,6 +251,7 @@ class NURBSPatchGWNCache ///@{ //! \name Accessors for precomputed data const Vector& getAverageNormal() const { return m_averageNormal; } + const Vector& getCastDirection() const { return m_castDirection; } const BoundingBox& boundingBox() const { return m_bBox; } const OrientedBoundingBox& orientedBoundingBox() const { return m_oBox; } //@} @@ -272,7 +286,7 @@ class NURBSPatchGWNCache // Per patch data BoundingBox m_bBox; OrientedBoundingBox m_oBox; - Vector m_averageNormal; + Vector m_averageNormal, m_castDirection; double m_pboxDiag; // Per trimming curve data, keyed by (whichRefinementLevel, whichRefinementIndex) @@ -280,6 +294,123 @@ class NURBSPatchGWNCache }; } // namespace detail + +/*! + * \brief Manage an array of NURBSPatchGWNCache + */ +class NURBSPatchCacheManager +{ + using NURBSCache = axom::primal::detail::NURBSPatchGWNCache; + using NURBSCacheArray = axom::Array; + using NURBSCacheArrayView = axom::ArrayView; + + using PatchArrayView = axom::ArrayView>; + +public: + NURBSPatchCacheManager() = default; + + NURBSPatchCacheManager(const PatchArrayView& patchs) + { + for(const auto& patch : patchs) + { + m_nurbs_caches.push_back(NURBSCache(patch)); + } + } + + /// A view of the manager object. + struct View + { + NURBSCacheArrayView m_view; + + /// Return the NURBSCacheArrayView. + NURBSCacheArrayView caches() const { return m_view; } + }; + + /// Return a view of this manager to pass into a device function. + View view() const { return View {m_nurbs_caches.view()}; } + + /// Return if the underlying array is empty + bool empty() const { return m_nurbs_caches.empty(); } + +private: + NURBSCacheArray m_nurbs_caches; +}; + +template +struct nurbs_cache_3d_traits +{ + using type = NURBSPatchCacheManager; +}; + +#if defined(AXOM_USE_OPENMP) +/*! + * \brief Manage per-thread arrays of NURBSPatchGWNCache + */ +class NURBSPatchCacheManagerOMP +{ + using NURBSCache = axom::primal::detail::NURBSPatchGWNCache; + using NURBSCachePerThreadArray = axom::Array>; + using NURBSCachePerThreadArrayView = axom::ArrayView>; + using NURBSCacheArrayView = axom::ArrayView; + + using PatchArrayView = axom::ArrayView>; + +public: + NURBSPatchCacheManagerOMP() = default; + + NURBSPatchCacheManagerOMP(const PatchArrayView& patches) + { + const int nt = omp_get_max_threads(); + m_nurbs_caches.resize(nt); + auto nurbs_caches_view = m_nurbs_caches.view(); + + // Make the first one + nurbs_caches_view[0].resize(patches.size()); + axom::for_all( + patches.size(), + AXOM_LAMBDA(axom::IndexType i) { nurbs_caches_view[0][i] = NURBSCache(patches[i]); }); + SLIC_INFO("Finished the first construction"); + // Copy the constructed cache to the other threads' copies (less work than construction) + axom::for_all( + 1, + nt, + AXOM_LAMBDA(axom::IndexType t) { nurbs_caches_view[t].resize(nurbs_caches_view[0].size()); }); + axom::for_all( + patches.size(), + AXOM_LAMBDA(axom::IndexType i) { + for(int t = 0; t < nt; t++) + { + nurbs_caches_view[t][i] = nurbs_caches_view[0][i]; + } + }); + } + + /// A view of the manager object. + struct View + { + NURBSCachePerThreadArrayView m_views; + + /// Return the NURBSCacheArrayView for the current OMP thread. + NURBSCacheArrayView caches() const { return m_views[omp_get_thread_num()].view(); } + }; + + /// Return a view of this manager to pass into a device function. + View view() const { return View {m_nurbs_caches.view()}; } + + /// Return if the underlying array is empty + bool empty() const { return m_nurbs_caches.empty(); } + +private: + NURBSCachePerThreadArray m_nurbs_caches; +}; + +template <> +struct nurbs_cache_3d_traits +{ + using type = NURBSPatchCacheManagerOMP; +}; +#endif + } // namespace primal } // namespace axom diff --git a/src/axom/primal/operators/winding_number.hpp b/src/axom/primal/operators/winding_number.hpp index f9d719aad0..23e7549a19 100644 --- a/src/axom/primal/operators/winding_number.hpp +++ b/src/axom/primal/operators/winding_number.hpp @@ -759,23 +759,9 @@ double winding_number(const Point& query, const double disk_size = 0.01, const double EPS = 1e-8) { - // Select the cast direction as an average normal of the untrimmed surface - auto cast_direction = nurbs.getAverageNormal(); - if(cast_direction.norm() < 1e-10) - { - // ...unless the average direction is zero - double theta = axom::utilities::random_real(0.0, 2 * M_PI); - double u = axom::utilities::random_real(-1.0, 1.0); - cast_direction = Vector {sin(theta) * sqrt(1 - u * u), cos(theta) * sqrt(1 - u * u), u}; - } - else - { - cast_direction = cast_direction.unitVector(); - } - return detail::nurbs_winding_number(query, nurbs, - cast_direction, + nurbs.getCastDirection(), edge_tol, ls_tol, quad_tol, @@ -783,6 +769,32 @@ double winding_number(const Point& query, EPS); } +/// \brief Overload for a single query and an ArrayView +template +double winding_number(const Point& query, + const axom::ArrayView>& nurbs_arr, + const double edge_tol = 1e-8, + const double ls_tol = 1e-8, + const double quad_tol = 1e-8, + const double disk_size = 0.01, + const double EPS = 1e-8) +{ + double ret_val = 0.0; + for(int i = 0; i < nurbs_arr.size(); ++i) + { + ret_val += detail::nurbs_winding_number(query, + nurbs_arr[i], + nurbs_arr[i].getCastDirection(), + edge_tol, + ls_tol, + quad_tol, + disk_size, + EPS); + } + + return ret_val; +} + /*! * \brief Computes the GWN for a 3D point wrt a generic 3D surface object * @@ -847,26 +859,6 @@ axom::Array winding_number(const axom::Array>& query_arr, const double disk_size = 0.01, const double EPS = 1e-8) { - // Pull precomputed cast directions for each patch - axom::Array> cast_direction_arr(0, nurbs_arr.size()); - for(int i = 0; i < nurbs_arr.size(); ++i) - { - // Select the cast direction as an average normal of the untrimmed surface - cast_direction_arr.emplace_back(nurbs_arr[i].getAverageNormal()); - if(cast_direction_arr[i].norm() < 1e-10) - { - // ...unless the average direction is zero - double theta = axom::utilities::random_real(0.0, 2 * M_PI); - double u = axom::utilities::random_real(-1.0, 1.0); - cast_direction_arr[i] = - Vector {sin(theta) * sqrt(1 - u * u), cos(theta) * sqrt(1 - u * u), u}; - } - else - { - cast_direction_arr[i] = cast_direction_arr[i].unitVector(); - } - } - axom::Array ret_val(query_arr.size()); for(int n = 0; n < query_arr.size(); ++n) { @@ -876,7 +868,7 @@ axom::Array winding_number(const axom::Array>& query_arr, { ret_val[n] += detail::nurbs_winding_number(query_arr[n], nurbs_arr[i], - cast_direction_arr[i], + nurbs_arr[i].castDirection(), edge_tol, ls_tol, quad_tol, diff --git a/src/axom/quest/GWNMethods.hpp b/src/axom/quest/GWNMethods.hpp index d50b0171d5..d754fa852b 100644 --- a/src/axom/quest/GWNMethods.hpp +++ b/src/axom/quest/GWNMethods.hpp @@ -447,11 +447,14 @@ class PolylineGWN2D ///@{ /// \name Query methods for 3D GWN applications + +template class DirectGWN3D { public: using PatchArrayType = axom::Array>; using NURBSCacheArray = axom::Array>; + using NURBSCacheManager = typename axom::primal::nurbs_cache_3d_traits::type; DirectGWN3D() = default; @@ -471,11 +474,7 @@ class DirectGWN3D AXOM_ANNOTATE_SCOPE("preprocessing"); if(use_memoization) { - m_nurbs_caches.reserve(input_patches.size()); - for(const auto& patch : input_patches) - { - m_nurbs_caches.emplace_back(patch); - } + m_nurbs_cache_mgr = NURBSCacheManager(input_patches); } } timer.stop(); @@ -529,9 +528,9 @@ class DirectGWN3D const auto input_patches_view = m_input_patches_view; // Use non-memoized form - if(m_nurbs_caches.empty()) + if(m_nurbs_cache_mgr.empty()) { - axom::for_all(num_query_points, [=, &winding, &inout](axom::IndexType nidx) { + axom::for_all(num_query_points, [=, &winding, &inout](axom::IndexType nidx) { const auto q = query_point(static_cast(nidx)); double wn {}; for(const auto& patch : input_patches_view) @@ -550,20 +549,19 @@ class DirectGWN3D } else // Use memoized form { - const auto nurbs_patches_view = m_nurbs_caches.view(); - axom::for_all(num_query_points, [=, &winding, &inout](axom::IndexType nidx) { + const auto cache_mgr_view = m_nurbs_cache_mgr.view(); + axom::for_all(num_query_points, [=, &winding, &inout](axom::IndexType nidx) { const auto q = query_point(static_cast(nidx)); - double wn {}; - for(const auto& cache : nurbs_patches_view) - { - wn += axom::primal::winding_number(q, - cache, - tol_copy.edge_tol, - tol_copy.ls_tol, - tol_copy.quad_tol, - tol_copy.disk_size, - tol_copy.EPS); - } + const auto caches_view = cache_mgr_view.caches(); + + const double wn = axom::primal::winding_number(q, + caches_view, + tol_copy.edge_tol, + tol_copy.ls_tol, + tol_copy.quad_tol, + tol_copy.disk_size, + tol_copy.EPS); + winding[static_cast(nidx)] = wn; inout[static_cast(nidx)] = std::lround(wn); }); @@ -578,7 +576,7 @@ class DirectGWN3D "Querying {:L} samples in winding number field with{} memoization took {:.3Lf} seconds" " (@ {:.0Lf} queries per second; {:.6Lf} ms per query)", num_query_points, - m_nurbs_caches.empty() ? "out" : "", + m_nurbs_cache_mgr.empty() ? "out" : "", query_time_s, num_query_points / query_time_s, ms_per_query)); @@ -588,7 +586,7 @@ class DirectGWN3D private: axom::ArrayView> m_input_patches_view; - NURBSCacheArray m_nurbs_caches; + NURBSCacheManager m_nurbs_cache_mgr; }; template @@ -849,8 +847,8 @@ struct gwn_input_traits> : std::integral_constant { }; -template <> -struct gwn_input_traits +template +struct gwn_input_traits> : std::integral_constant { }; diff --git a/src/axom/quest/examples/quest_winding_number_2d.cpp b/src/axom/quest/examples/quest_winding_number_2d.cpp index 6bc458b906..de31d8da62 100644 --- a/src/axom/quest/examples/quest_winding_number_2d.cpp +++ b/src/axom/quest/examples/quest_winding_number_2d.cpp @@ -475,7 +475,7 @@ int main(int argc, char** argv) } axom::utilities::raii::AnnotationsWrapper annotation_raii_wrapper(input.annotationMode); - AXOM_ANNOTATE_SCOPE("winding number example"); + AXOM_ANNOTATE_SCOPE("2D winding number example"); if (!input.elevatedMeshFile.empty()) { diff --git a/src/axom/quest/examples/quest_winding_number_3d.cpp b/src/axom/quest/examples/quest_winding_number_3d.cpp index 592c022a08..58b08e9b32 100644 --- a/src/axom/quest/examples/quest_winding_number_3d.cpp +++ b/src/axom/quest/examples/quest_winding_number_3d.cpp @@ -245,7 +245,8 @@ class Input } }; -using GWNQueryType = std::variant, + axom::quest::DirectGWN3D, axom::quest::TriangleGWN3D, axom::quest::TriangleGWN3D, axom::quest::TriangleGWN3D, @@ -272,7 +273,7 @@ GWNQueryType pick_gwn_method(bool triangulate, int approximation_order) } } - return axom::quest::DirectGWN3D {}; + return axom::quest::DirectGWN3D {}; } GWNQueryType make_gwn_query(axom::runtime_policy::Policy policy, @@ -379,7 +380,7 @@ int main(int argc, char** argv) SLIC_INFO(axom::fmt::format( axom::utilities::locale(), "Loaded {} trimmed NURBS patches (with {} trimming curves) in {:.3Lf} seconds", - patches.size(), + step_reader.numPatches(), num_trimming_curves, read_timer.elapsed())); From 9459efc4eca0cf4480f3c52263828ab10c1b3895 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Fri, 27 Mar 2026 15:06:20 -0700 Subject: [PATCH 093/986] Augment test with broken OMP examples --- src/axom/quest/tests/quest_gwn_methods.cpp | 45 ++++++++++++++++++---- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/src/axom/quest/tests/quest_gwn_methods.cpp b/src/axom/quest/tests/quest_gwn_methods.cpp index 886594caaf..32a51c7fe0 100644 --- a/src/axom/quest/tests/quest_gwn_methods.cpp +++ b/src/axom/quest/tests/quest_gwn_methods.cpp @@ -154,7 +154,8 @@ TEST(quest_gwn_methods, gwn_moment_data_triangle) } //------------------------------------------------------------------------------ -TEST(quest_gwn_methods, mfem_mesh_linearization) +template +void check_mfem_mesh_linearization() { using NURBSCurve2D = axom::primal::NURBSCurve; const std::string fileName = pjoin(AXOM_DATA_DIR, "contours", "svg", "mfem_logo_simp.mesh"); @@ -212,19 +213,19 @@ TEST(quest_gwn_methods, mfem_mesh_linearization) // Direct SLIC_INFO("Testing Direct Evaluation"); - axom::quest::DirectGWN2D gwn_direct {}; + axom::quest::DirectGWN2D gwn_direct {}; gwn_direct.preprocess(curves); gwn_direct.query(dc[0], tol); // Linearized SLIC_INFO("Testing Direct Evaluation of Triangulation"); - axom::quest::PolylineGWN2D<0> gwn_polyline {}; + axom::quest::PolylineGWN2D gwn_polyline {}; gwn_polyline.preprocess(&poly_mesh, useDirectPolyline); gwn_polyline.query(dc[1], tol); // Linearized, fast approximation SLIC_INFO("Testing Fast-Approximate Evaluation of Triangulation"); - axom::quest::PolylineGWN2D<0> gwn_polyline_fast {}; + axom::quest::PolylineGWN2D gwn_polyline_fast {}; gwn_polyline_fast.preprocess(&poly_mesh, !useDirectPolyline); gwn_polyline_fast.query(dc[2], tol); @@ -245,7 +246,8 @@ TEST(quest_gwn_methods, mfem_mesh_linearization) #ifdef AXOM_USE_OPENCASCADE //------------------------------------------------------------------------------ -TEST(quest_gwn_methods, step_file_triangulation) +template +void check_step_file_triangulation() { const std::string fileName = pjoin(AXOM_DATA_DIR, "quest", "step", "nut.step"); @@ -298,19 +300,19 @@ TEST(quest_gwn_methods, step_file_triangulation) // Direct SLIC_INFO("Testing Direct Evaluation"); - axom::quest::DirectGWN3D gwn_direct {}; + axom::quest::DirectGWN3D gwn_direct {}; gwn_direct.preprocess(patches); gwn_direct.query(dc[0], tol); // Triangulated SLIC_INFO("Testing Direct Evaluation of Polyline"); - axom::quest::TriangleGWN3D<0> gwn_tri {}; + axom::quest::TriangleGWN3D gwn_tri {}; gwn_tri.preprocess(&tri_mesh, useDirectTriangle); gwn_tri.query(dc[1], tol); // Triangulated, fast approximation SLIC_INFO("Testing Fast-Approximate Evaluation of Polyline"); - axom::quest::TriangleGWN3D<0> gwn_tri_fast {}; + axom::quest::TriangleGWN3D gwn_tri_fast {}; gwn_tri_fast.preprocess(&tri_mesh, !useDirectTriangle); gwn_tri_fast.query(dc[2], tol); @@ -330,6 +332,33 @@ TEST(quest_gwn_methods, step_file_triangulation) } #endif +//------------------------------------------------------------------------------ +TEST(quest_gwn_methods, mfem_mesh_linearization) +{ + check_mfem_mesh_linearization(); +} + +//#ifdef AXOM_USE_OMP +//TEST(quest_gwn_methods, mfem_mesh_linearization_omp) +//{ +// check_mfem_mesh_linearization(); +//} +//#endif + +#ifdef AXOM_USE_OPENCASCADE +TEST(quest_gwn_methods, step_file_triangulation) +{ + check_step_file_triangulation(); +} +#endif + +//#if defined(AXOM_USE_OMP) && defined(AXOM_USE_OPENCASCADE) +//TEST(quest_gwn_methods, step_file_triangulation_omp) +//{ +// check_step_file_triangulation(); +//} +//#endif + //------------------------------------------------------------------------------ int main(int argc, char *argv[]) { From 4207ea1f7f9110314a37f14e3d2a246448044356 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Fri, 27 Mar 2026 16:36:23 -0700 Subject: [PATCH 094/986] Fix macros --- .../detail/winding_number_2d_memoization.hpp | 2 +- .../detail/winding_number_3d_memoization.hpp | 4 ++-- src/axom/primal/operators/winding_number.hpp | 2 +- .../examples/quest_winding_number_2d.cpp | 7 ++++-- .../examples/quest_winding_number_3d.cpp | 7 ++++-- src/axom/quest/tests/quest_gwn_methods.cpp | 24 +++++++++---------- 6 files changed, 26 insertions(+), 20 deletions(-) diff --git a/src/axom/primal/operators/detail/winding_number_2d_memoization.hpp b/src/axom/primal/operators/detail/winding_number_2d_memoization.hpp index de527c0c8c..37ba2257bf 100644 --- a/src/axom/primal/operators/detail/winding_number_2d_memoization.hpp +++ b/src/axom/primal/operators/detail/winding_number_2d_memoization.hpp @@ -304,7 +304,7 @@ struct nurbs_cache_2d_traits using type = NURBSCurveCacheManager; }; -#if defined(AXOM_USE_OPENMP) +#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_OPENMP) /*! * \brief Manage per-thread arrays of NURBSCurveGWNCache */ diff --git a/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp b/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp index 235a865df9..cb568c18ca 100644 --- a/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp +++ b/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp @@ -180,7 +180,7 @@ class NURBSPatchGWNCache } else { - m_castDirection = m_averageNormal; + m_castDirection = m_averageNormal.unitVector(); } m_pboxDiag = m_alteredPatch.getParameterSpaceDiagonal(); @@ -342,7 +342,7 @@ struct nurbs_cache_3d_traits using type = NURBSPatchCacheManager; }; -#if defined(AXOM_USE_OPENMP) +#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_OPENMP) /*! * \brief Manage per-thread arrays of NURBSPatchGWNCache */ diff --git a/src/axom/primal/operators/winding_number.hpp b/src/axom/primal/operators/winding_number.hpp index 23e7549a19..69db83afb9 100644 --- a/src/axom/primal/operators/winding_number.hpp +++ b/src/axom/primal/operators/winding_number.hpp @@ -868,7 +868,7 @@ axom::Array winding_number(const axom::Array>& query_arr, { ret_val[n] += detail::nurbs_winding_number(query_arr[n], nurbs_arr[i], - nurbs_arr[i].castDirection(), + nurbs_arr[i].getCastDirection(), edge_tol, ls_tol, quad_tol, diff --git a/src/axom/quest/examples/quest_winding_number_2d.cpp b/src/axom/quest/examples/quest_winding_number_2d.cpp index de31d8da62..fed7d69b38 100644 --- a/src/axom/quest/examples/quest_winding_number_2d.cpp +++ b/src/axom/quest/examples/quest_winding_number_2d.cpp @@ -411,13 +411,16 @@ class Input }; using GWNQueryType = std::variant, - axom::quest::DirectGWN2D, axom::quest::PolylineGWN2D, axom::quest::PolylineGWN2D, axom::quest::PolylineGWN2D, +#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_OPENMP) + axom::quest::DirectGWN2D, axom::quest::PolylineGWN2D, axom::quest::PolylineGWN2D, - axom::quest::PolylineGWN2D>; + axom::quest::PolylineGWN2D +#endif + >; template GWNQueryType pick_gwn_method(bool linearize_curves, int approximation_order) diff --git a/src/axom/quest/examples/quest_winding_number_3d.cpp b/src/axom/quest/examples/quest_winding_number_3d.cpp index 58b08e9b32..16f9759efe 100644 --- a/src/axom/quest/examples/quest_winding_number_3d.cpp +++ b/src/axom/quest/examples/quest_winding_number_3d.cpp @@ -246,13 +246,16 @@ class Input }; using GWNQueryType = std::variant, - axom::quest::DirectGWN3D, axom::quest::TriangleGWN3D, axom::quest::TriangleGWN3D, axom::quest::TriangleGWN3D, +#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_OPENMP) + axom::quest::DirectGWN3D, axom::quest::TriangleGWN3D, axom::quest::TriangleGWN3D, - axom::quest::TriangleGWN3D>; + axom::quest::TriangleGWN3D +#endif + >; template GWNQueryType pick_gwn_method(bool triangulate, int approximation_order) diff --git a/src/axom/quest/tests/quest_gwn_methods.cpp b/src/axom/quest/tests/quest_gwn_methods.cpp index 32a51c7fe0..3238eb825c 100644 --- a/src/axom/quest/tests/quest_gwn_methods.cpp +++ b/src/axom/quest/tests/quest_gwn_methods.cpp @@ -338,12 +338,12 @@ TEST(quest_gwn_methods, mfem_mesh_linearization) check_mfem_mesh_linearization(); } -//#ifdef AXOM_USE_OMP -//TEST(quest_gwn_methods, mfem_mesh_linearization_omp) -//{ -// check_mfem_mesh_linearization(); -//} -//#endif +#if defined AXOM_USE_OPENMP && defined(AXOM_USE_RAJA) +TEST(quest_gwn_methods, mfem_mesh_linearization_omp) +{ + check_mfem_mesh_linearization(); +} +#endif #ifdef AXOM_USE_OPENCASCADE TEST(quest_gwn_methods, step_file_triangulation) @@ -352,12 +352,12 @@ TEST(quest_gwn_methods, step_file_triangulation) } #endif -//#if defined(AXOM_USE_OMP) && defined(AXOM_USE_OPENCASCADE) -//TEST(quest_gwn_methods, step_file_triangulation_omp) -//{ -// check_step_file_triangulation(); -//} -//#endif +#if defined(AXOM_USE_OPENCASCADE) && defined(AXOM_USE_OPENMP) && defined(AXOM_USE_RAJA) +TEST(quest_gwn_methods, step_file_triangulation_omp) +{ + check_step_file_triangulation(); +} +#endif //------------------------------------------------------------------------------ int main(int argc, char *argv[]) From 60081ea141579e1887bee015fcd3e13f32cc9b7f Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Fri, 27 Mar 2026 16:39:38 -0700 Subject: [PATCH 095/986] One more policy flag --- src/axom/quest/examples/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/axom/quest/examples/CMakeLists.txt b/src/axom/quest/examples/CMakeLists.txt index b9574c2cee..b6431e547e 100644 --- a/src/axom/quest/examples/CMakeLists.txt +++ b/src/axom/quest/examples/CMakeLists.txt @@ -719,6 +719,7 @@ if(MFEM_FOUND AND OPENCASCADE_FOUND) --output-prefix wn3d_${_model}_res${_res_i} --no-vis --stats + --policy seq query_mesh --res ${_res_i} ${_res_j} ${_res_k} --order 1) set_tests_properties(${_testname} PROPERTIES PASS_REGULAR_EXPRESSION "${_pass_regex}") From 2e3d1d20f6e7528cb4a9ebafc18464507f71e2ba Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Tue, 31 Mar 2026 12:58:56 -0700 Subject: [PATCH 096/986] Fix broken/circular includes --- src/axom/primal/geometry/NURBSPatch.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/axom/primal/geometry/NURBSPatch.hpp b/src/axom/primal/geometry/NURBSPatch.hpp index 973b5ccf89..742cbd6804 100644 --- a/src/axom/primal/geometry/NURBSPatch.hpp +++ b/src/axom/primal/geometry/NURBSPatch.hpp @@ -29,6 +29,7 @@ #include "axom/primal/operators/evaluate_integral_curve.hpp" #include "axom/primal/operators/detail/winding_number_2d_impl.hpp" #include "axom/primal/operators/detail/intersect_bezier_impl.hpp" +#include "axom/primal/operators/evaluate_integral.hpp" #include #include From 2f3780ab6f39d0734bf1503cd39f2fb2f8c65233 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Tue, 31 Mar 2026 15:33:51 -0700 Subject: [PATCH 097/986] Fix templates and warning --- src/axom/quest/GWNMethods.hpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/axom/quest/GWNMethods.hpp b/src/axom/quest/GWNMethods.hpp index d754fa852b..0c16c0c5ec 100644 --- a/src/axom/quest/GWNMethods.hpp +++ b/src/axom/quest/GWNMethods.hpp @@ -200,7 +200,6 @@ class DirectGWN2D { AXOM_ANNOTATE_SCOPE("query"); const primal::WindingTolerances tol_copy = tol; - const auto input_curves_view = m_input_curves_view; // Use non-memoized form if(m_nurbs_cache_mgr.empty()) @@ -337,7 +336,7 @@ class PolylineGWN2D }; const auto traverser = m_bvh.getTraverser(); - m_internal_moments = traverser.reduce_tree(compute_moments); + m_internal_moments = traverser.template reduce_tree(compute_moments); } stage_timer.stop(); SLIC_INFO( @@ -697,7 +696,7 @@ class TriangleGWN3D }; const auto traverser = m_bvh.getTraverser(); - m_internal_moments = traverser.reduce_tree(compute_moments); + m_internal_moments = traverser.template reduce_tree(compute_moments); } stage_timer.stop(); SLIC_INFO( From f43974498d02d92985dbd75348111c0e086e6184 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Wed, 1 Apr 2026 16:10:00 -0700 Subject: [PATCH 098/986] Another fixed guard --- src/axom/quest/examples/quest_winding_number_2d.cpp | 4 +++- src/axom/quest/examples/quest_winding_number_3d.cpp | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/axom/quest/examples/quest_winding_number_2d.cpp b/src/axom/quest/examples/quest_winding_number_2d.cpp index fed7d69b38..02fedb04f8 100644 --- a/src/axom/quest/examples/quest_winding_number_2d.cpp +++ b/src/axom/quest/examples/quest_winding_number_2d.cpp @@ -448,11 +448,13 @@ GWNQueryType make_gwn_query(axom::runtime_policy::Policy policy, bool linearize_curves, int approximation_order) { - if (policy == RuntimePolicy::omp) +#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_OPENMP) + if(policy == RuntimePolicy::omp) { SLIC_INFO(axom::fmt::format("Using policy omp with {} threads", omp_get_max_threads())); return pick_gwn_method(linearize_curves, approximation_order); } +#endif SLIC_INFO("Using policy seq"); return pick_gwn_method(linearize_curves, approximation_order); diff --git a/src/axom/quest/examples/quest_winding_number_3d.cpp b/src/axom/quest/examples/quest_winding_number_3d.cpp index 16f9759efe..2579e9cc95 100644 --- a/src/axom/quest/examples/quest_winding_number_3d.cpp +++ b/src/axom/quest/examples/quest_winding_number_3d.cpp @@ -283,11 +283,13 @@ GWNQueryType make_gwn_query(axom::runtime_policy::Policy policy, bool triangulate, int approximation_order) { +#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_OPENMP) if(policy == RuntimePolicy::omp) { SLIC_INFO(axom::fmt::format("Using policy omp with {} threads", omp_get_max_threads())); return pick_gwn_method(triangulate, approximation_order); } +#endif SLIC_INFO("Using policy seq"); return pick_gwn_method(triangulate, approximation_order); From d739e6152900c96f829a10d2e25a2297c8b8af6e Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Wed, 1 Apr 2026 15:12:44 -0700 Subject: [PATCH 099/986] Improved guards --- src/axom/quest/examples/quest_winding_number_2d.cpp | 3 ++- src/axom/quest/examples/quest_winding_number_3d.cpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/axom/quest/examples/quest_winding_number_2d.cpp b/src/axom/quest/examples/quest_winding_number_2d.cpp index 02fedb04f8..ef8b218b47 100644 --- a/src/axom/quest/examples/quest_winding_number_2d.cpp +++ b/src/axom/quest/examples/quest_winding_number_2d.cpp @@ -413,8 +413,9 @@ class Input using GWNQueryType = std::variant, axom::quest::PolylineGWN2D, axom::quest::PolylineGWN2D, - axom::quest::PolylineGWN2D, + axom::quest::PolylineGWN2D #if defined(AXOM_USE_RAJA) && defined(AXOM_USE_OPENMP) + , axom::quest::DirectGWN2D, axom::quest::PolylineGWN2D, axom::quest::PolylineGWN2D, diff --git a/src/axom/quest/examples/quest_winding_number_3d.cpp b/src/axom/quest/examples/quest_winding_number_3d.cpp index 2579e9cc95..f9dee47b0d 100644 --- a/src/axom/quest/examples/quest_winding_number_3d.cpp +++ b/src/axom/quest/examples/quest_winding_number_3d.cpp @@ -248,8 +248,9 @@ class Input using GWNQueryType = std::variant, axom::quest::TriangleGWN3D, axom::quest::TriangleGWN3D, - axom::quest::TriangleGWN3D, + axom::quest::TriangleGWN3D #if defined(AXOM_USE_RAJA) && defined(AXOM_USE_OPENMP) + , axom::quest::DirectGWN3D, axom::quest::TriangleGWN3D, axom::quest::TriangleGWN3D, From 8421094aebff83e4d5ea931d49a230830fa642d3 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Sat, 11 Apr 2026 15:03:51 -0700 Subject: [PATCH 100/986] Removes now circular include --- src/axom/primal/geometry/NURBSPatch.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/axom/primal/geometry/NURBSPatch.hpp b/src/axom/primal/geometry/NURBSPatch.hpp index 742cbd6804..973b5ccf89 100644 --- a/src/axom/primal/geometry/NURBSPatch.hpp +++ b/src/axom/primal/geometry/NURBSPatch.hpp @@ -29,7 +29,6 @@ #include "axom/primal/operators/evaluate_integral_curve.hpp" #include "axom/primal/operators/detail/winding_number_2d_impl.hpp" #include "axom/primal/operators/detail/intersect_bezier_impl.hpp" -#include "axom/primal/operators/evaluate_integral.hpp" #include #include From 01741356c8a575380325c10946bd25eb6a3ea92b Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Mon, 13 Apr 2026 13:08:35 -0700 Subject: [PATCH 101/986] Fix comments, release notes --- RELEASE-NOTES.md | 1 + src/axom/quest/examples/quest_winding_number_2d.cpp | 2 +- src/axom/quest/examples/quest_winding_number_3d.cpp | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 420af7d17e..b060e18ef1 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -25,6 +25,7 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ - Primal: Adds `NURBSCurve::isLinear()` to check if a curve is (nearly) flat (corresponding to `BezierCurve::isLinear()`) - Primal: Adds `NURBSPatch::isTriviallyTrimmed()` to check if the trimming curves for a patch lie on the patch boundaries - Quest: Adds support for reading mfem files with variable order NURBS curves (requires mfem>4.9). +- Quest: Adds OMP support for fast GWN methods for STL/Triangulated STEP input and linearized NURBS Curve input. ### Removed diff --git a/src/axom/quest/examples/quest_winding_number_2d.cpp b/src/axom/quest/examples/quest_winding_number_2d.cpp index ef8b218b47..8f039ce927 100644 --- a/src/axom/quest/examples/quest_winding_number_2d.cpp +++ b/src/axom/quest/examples/quest_winding_number_2d.cpp @@ -338,7 +338,7 @@ class Input ->check(axom::utilities::ValidCaliperMode); #endif std::stringstream pol_sstr; - pol_sstr << "Set MIR runtime policy method."; + pol_sstr << "Set runtime policy method."; pol_sstr << "\nSet to 'seq' or 0 to use the RAJA sequential policy."; #ifdef AXOM_RUNTIME_POLICY_USE_OPENMP pol_sstr << "\nSet to 'omp' or 1 to use the RAJA OpenMP policy."; diff --git a/src/axom/quest/examples/quest_winding_number_3d.cpp b/src/axom/quest/examples/quest_winding_number_3d.cpp index f9dee47b0d..e043e0c2a5 100644 --- a/src/axom/quest/examples/quest_winding_number_3d.cpp +++ b/src/axom/quest/examples/quest_winding_number_3d.cpp @@ -165,7 +165,7 @@ class Input ->check(axom::utilities::ValidCaliperMode); #endif std::stringstream pol_sstr; - pol_sstr << "Set MIR runtime policy method."; + pol_sstr << "Set runtime policy method."; pol_sstr << "\nSet to 'seq' or 0 to use the RAJA sequential policy."; #ifdef AXOM_RUNTIME_POLICY_USE_OPENMP pol_sstr << "\nSet to 'omp' or 1 to use the RAJA OpenMP policy."; From a2dbdaf94975f8b7133d436c168d4f29512587fd Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Mon, 13 Apr 2026 13:31:52 -0700 Subject: [PATCH 102/986] format --- .../examples/quest_winding_number_2d.cpp | 438 +++++++++--------- 1 file changed, 219 insertions(+), 219 deletions(-) diff --git a/src/axom/quest/examples/quest_winding_number_2d.cpp b/src/axom/quest/examples/quest_winding_number_2d.cpp index 8f039ce927..5e29371646 100644 --- a/src/axom/quest/examples/quest_winding_number_2d.cpp +++ b/src/axom/quest/examples/quest_winding_number_2d.cpp @@ -46,7 +46,7 @@ using RuntimePolicy = axom::runtime_policy::Policy; namespace { - /** +/** * This helper class takes in an mfem mesh (potentially with variable order curves from mfem>4.9) * and writes out a version that is compatible with mfem@4.9 * In particular, this allows us to visualize it with current versions of VisIt which do not yet support this feature. @@ -54,204 +54,204 @@ namespace * We can remove this class once downstream applications (such as VisIt) are updated to a version of mfem * that supports the NURBS patches format. */ - class MFEM49ElevatedNURBSMeshWriter +class MFEM49ElevatedNURBSMeshWriter +{ +public: + explicit MFEM49ElevatedNURBSMeshWriter(double tol = 1e-12) : m_tol(tol) { } + + bool writeElevatedMesh(const std::string& input_file, const std::string& output_file) const { - public: - explicit MFEM49ElevatedNURBSMeshWriter(double tol = 1e-12) : m_tol(tol) {} + mfem::Mesh mesh(input_file, /*generate_edges=*/1, /*refine=*/1); - bool writeElevatedMesh(const std::string& input_file, const std::string& output_file) const + if(mesh.NURBSext == nullptr) { - mfem::Mesh mesh(input_file, /*generate_edges=*/1, /*refine=*/1); + SLIC_WARNING( + axom::fmt::format("Input mesh '{}' has no NURBS extension; skipping degree elevation", + input_file)); + return false; + } - if (mesh.NURBSext == nullptr) - { - SLIC_WARNING( - axom::fmt::format("Input mesh '{}' has no NURBS extension; skipping degree elevation", - input_file)); - return false; - } + // Note: NURBS curve meshes in mfem@4.9 must all have the same degree, and their knotvectors must have the same length. + // MFEM calls this quantity "order"; in Axom terminology this is the polynomial degree. + const mfem::Array& orders = mesh.NURBSext->GetOrders(); + int max_order = 0; + for(int i = 0; i < orders.Size(); ++i) + { + max_order = std::max(max_order, orders[i]); + } - // Note: NURBS curve meshes in mfem@4.9 must all have the same degree, and their knotvectors must have the same length. - // MFEM calls this quantity "order"; in Axom terminology this is the polynomial degree. - const mfem::Array& orders = mesh.NURBSext->GetOrders(); - int max_order = 0; - for (int i = 0; i < orders.Size(); ++i) - { - max_order = std::max(max_order, orders[i]); - } + if(max_order <= 0) + { + SLIC_WARNING( + axom::fmt::format("Input mesh '{}' has invalid NURBS orders; skipping degree elevation", + input_file)); + return false; + } - if (max_order <= 0) - { - SLIC_WARNING( - axom::fmt::format("Input mesh '{}' has invalid NURBS orders; skipping degree elevation", - input_file)); - return false; - } + mesh.DegreeElevate(max_order, max_order); - mesh.DegreeElevate(max_order, max_order); + makeKnotVectorsUniform(mesh); - makeKnotVectorsUniform(mesh); + // Write the modified mesh to output_file + if(!writeMeshPrintFormat(mesh, output_file)) + { + SLIC_WARNING(axom::fmt::format("Failed to write elevated mesh '{}'", output_file)); + return false; + } - // Write the modified mesh to output_file - if (!writeMeshPrintFormat(mesh, output_file)) - { - SLIC_WARNING(axom::fmt::format("Failed to write elevated mesh '{}'", output_file)); - return false; - } + SLIC_INFO(axom::fmt::format("Wrote elevated MFEM 4.9-compatible NURBS mesh '{}' (max order {})", + output_file, + max_order)); + return true; + } - SLIC_INFO(axom::fmt::format("Wrote elevated MFEM 4.9-compatible NURBS mesh '{}' (max order {})", - output_file, - max_order)); - return true; +private: + struct KnotBin + { + std::int64_t key {0}; + double value {0.0}; + int max_multiplicity {0}; + }; + + // MFEM 4.9's 1D NURBS reader assumes all knotvectors have the same GetNE/GetNCP + // and uses KnotVec(0) when computing offsets. To generate MFEM 4.9/VisIt + // compatible files, insert knots so every knotvector matches the maximum + // knot multiplicity pattern present in the mesh. + void makeKnotVectorsUniform(mfem::Mesh& mesh) const + { + if(mesh.NURBSext == nullptr) + { + return; + } + if(mesh.Dimension() != 1 || mesh.NURBSext->Dimension() != 1) + { + return; } - private: - struct KnotBin + const int nkv = mesh.NURBSext->GetNKV(); + if(nkv <= 1) { - std::int64_t key{ 0 }; - double value{ 0.0 }; - int max_multiplicity{ 0 }; + return; + } + + const double inv_tol = (m_tol > 0.0) ? (1.0 / m_tol) : 1.0e12; + auto key_for = [inv_tol](double t) -> std::int64_t { + return static_cast(std::llround(t * inv_tol)); }; - // MFEM 4.9's 1D NURBS reader assumes all knotvectors have the same GetNE/GetNCP - // and uses KnotVec(0) when computing offsets. To generate MFEM 4.9/VisIt - // compatible files, insert knots so every knotvector matches the maximum - // knot multiplicity pattern present in the mesh. - void makeKnotVectorsUniform(mfem::Mesh& mesh) const + std::vector> counts(nkv); + std::unordered_map bins; + + for(int i = 0; i < nkv; ++i) { - if (mesh.NURBSext == nullptr) + const mfem::KnotVector* kv = mesh.NURBSext->GetKnotVector(i); + if(kv == nullptr) { - return; - } - if (mesh.Dimension() != 1 || mesh.NURBSext->Dimension() != 1) - { - return; + continue; } - const int nkv = mesh.NURBSext->GetNKV(); - if (nkv <= 1) + auto& local = counts[i]; + for(int j = 0; j < kv->Size(); ++j) { - return; + const double value = (*kv)[j]; + const std::int64_t key = key_for(value); + + const int multiplicity = ++local[key]; + auto& bin = bins[key]; + bin.key = key; + bin.value = value; + bin.max_multiplicity = std::max(bin.max_multiplicity, multiplicity); } + } - const double inv_tol = (m_tol > 0.0) ? (1.0 / m_tol) : 1.0e12; - auto key_for = [inv_tol](double t) -> std::int64_t { - return static_cast(std::llround(t * inv_tol)); - }; - - std::vector> counts(nkv); - std::unordered_map bins; - - for (int i = 0; i < nkv; ++i) + std::vector sorted_bins; + sorted_bins.reserve(bins.size()); + for(const auto& it : bins) + { + sorted_bins.push_back(it.second); + } + std::sort(sorted_bins.begin(), sorted_bins.end(), [](const KnotBin& a, const KnotBin& b) { + if(a.value != b.value) { - const mfem::KnotVector* kv = mesh.NURBSext->GetKnotVector(i); - if (kv == nullptr) - { - continue; - } - - auto& local = counts[i]; - for (int j = 0; j < kv->Size(); ++j) - { - const double value = (*kv)[j]; - const std::int64_t key = key_for(value); - - const int multiplicity = ++local[key]; - auto& bin = bins[key]; - bin.key = key; - bin.value = value; - bin.max_multiplicity = std::max(bin.max_multiplicity, multiplicity); - } + return a.value < b.value; } + return a.key < b.key; + }); - std::vector sorted_bins; - sorted_bins.reserve(bins.size()); - for (const auto& it : bins) + int total_to_insert = 0; + mfem::Array insertions(nkv); + for(int i = 0; i < nkv; ++i) + { + int need_total = 0; + for(const auto& bin : sorted_bins) { - sorted_bins.push_back(it.second); + const auto found = counts[i].find(bin.key); + const int have = (found == counts[i].end()) ? 0 : found->second; + need_total += std::max(0, bin.max_multiplicity - have); } - std::sort(sorted_bins.begin(), sorted_bins.end(), [](const KnotBin& a, const KnotBin& b) { - if (a.value != b.value) - { - return a.value < b.value; - } - return a.key < b.key; - }); - int total_to_insert = 0; - mfem::Array insertions(nkv); - for (int i = 0; i < nkv; ++i) + insertions[i] = new mfem::Vector(need_total); + int pos = 0; + for(const auto& bin : sorted_bins) { - int need_total = 0; - for (const auto& bin : sorted_bins) + const auto found = counts[i].find(bin.key); + const int have = (found == counts[i].end()) ? 0 : found->second; + const int need = std::max(0, bin.max_multiplicity - have); + for(int k = 0; k < need; ++k) { - const auto found = counts[i].find(bin.key); - const int have = (found == counts[i].end()) ? 0 : found->second; - need_total += std::max(0, bin.max_multiplicity - have); + (*insertions[i])[pos++] = bin.value; } - - insertions[i] = new mfem::Vector(need_total); - int pos = 0; - for (const auto& bin : sorted_bins) - { - const auto found = counts[i].find(bin.key); - const int have = (found == counts[i].end()) ? 0 : found->second; - const int need = std::max(0, bin.max_multiplicity - have); - for (int k = 0; k < need; ++k) - { - (*insertions[i])[pos++] = bin.value; - } - } - total_to_insert += need_total; } + total_to_insert += need_total; + } - if (total_to_insert > 0) - { - mesh.KnotInsert(insertions); - } + if(total_to_insert > 0) + { + mesh.KnotInsert(insertions); + } - for (int i = 0; i < nkv; ++i) - { - delete insertions[i]; - } + for(int i = 0; i < nkv; ++i) + { + delete insertions[i]; } + } - bool writeMeshPrintFormat(const mfem::Mesh& mesh, const std::string& output_file) const + bool writeMeshPrintFormat(const mfem::Mesh& mesh, const std::string& output_file) const + { + std::ostringstream oss; + oss.precision(16); + mesh.Print(oss); + + std::istringstream iss(oss.str()); + std::ofstream ofs(output_file); + if(!ofs.is_open()) { - std::ostringstream oss; - oss.precision(16); - mesh.Print(oss); + return false; + } + ofs.precision(16); - std::istringstream iss(oss.str()); - std::ofstream ofs(output_file); - if (!ofs.is_open()) + // Note: mfem@4.9's mesh reader does not support the "NURBS2" finite element collection, + // if it occurs, we replace it with the (essentially equivalent) "NURBS" + std::string line; + while(std::getline(iss, line)) + { + constexpr const char* prefix = "FiniteElementCollection: NURBS"; + if(line.rfind(prefix, 0) == 0) { - return false; + ofs << prefix << '\n'; } - ofs.precision(16); - - // Note: mfem@4.9's mesh reader does not support the "NURBS2" finite element collection, - // if it occurs, we replace it with the (essentially equivalent) "NURBS" - std::string line; - while (std::getline(iss, line)) + else { - constexpr const char* prefix = "FiniteElementCollection: NURBS"; - if (line.rfind(prefix, 0) == 0) - { - ofs << prefix << '\n'; - } - else - { - ofs << line << '\n'; - } + ofs << line << '\n'; } - - return true; } - private: - double m_tol{ 1e-12 }; - }; + return true; + } + +private: + double m_tol {1e-12}; +}; } // namespace @@ -263,32 +263,32 @@ class Input { public: std::string inputFile; - std::string outputPrefix = { "winding2d" }; + std::string outputPrefix = {"winding2d"}; - bool verbose{ false }; - std::string annotationMode{ "none" }; - bool memoized{ true }; - bool vis{ true }; - bool stats{ false }; + bool verbose {false}; + std::string annotationMode {"none"}; + bool memoized {true}; + bool vis {true}; + bool stats {false}; std::string elevatedMeshFile; axom::runtime_policy::Policy policy = RuntimePolicy::seq; - const std::array valid_algorithms{ "direct", "fast-approximation" }; - std::string algorithm{ valid_algorithms[1] }; // fast-approximation + const std::array valid_algorithms {"direct", "fast-approximation"}; + std::string algorithm {valid_algorithms[1]}; // fast-approximation - bool linearize{ false }; - int approximation_order{ 2 }; + bool linearize {false}; + int approximation_order {2}; bool useUniformLinearization; - int segmentsPerKnotSpan{ 10 }; - double percentError{ 1.0 }; + int segmentsPerKnotSpan {10}; + double percentError {1.0}; // Query mesh parameters std::vector boxMins; std::vector boxMaxs; std::vector boxResolution; - int queryOrder{ 1 }; + int queryOrder {1}; primal::WindingTolerances tol; @@ -351,22 +351,22 @@ class Input // Options for triangulation of the input STEP file auto* linearize_curves_subcommand = app.add_subcommand("linearize_curves") - ->description("Options for linearizing NURBS curves. Default is ") - ->fallthrough(); + ->description("Options for linearizing NURBS curves. Default is ") + ->fallthrough(); auto* nsegments = linearize_curves_subcommand->add_option("--num-segments", segmentsPerKnotSpan) - ->description( - "Number of segments for each knot span of each input curve for a uniform linearization.") - ->check(axom::CLI::PositiveNumber) - ->capture_default_str(); + ->description( + "Number of segments for each knot span of each input curve for a uniform linearization.") + ->check(axom::CLI::PositiveNumber) + ->capture_default_str(); auto* perror = linearize_curves_subcommand->add_option("--percent-error", percentError) - ->description( - "The percent of error that is acceptable to stop refinement during non-uniform " - "linearization.") - ->check(axom::CLI::Range(0.0f, 100.0f)) - ->capture_default_str(); + ->description( + "The percent of error that is acceptable to stop refinement during non-uniform " + "linearization.") + ->check(axom::CLI::Range(0.0f, 100.0f)) + ->capture_default_str(); linearize_curves_subcommand->add_option("--algorithm", algorithm) ->description( "Use direct evaluation instead of fast, heirarchical approximation? (significantly " @@ -375,19 +375,19 @@ class Input ->check(axom::CLI::IsMember(valid_algorithms)); linearize_curves_subcommand ->add_option("--approximation-order", - approximation_order, - "The order of the Taylor expansion (lower is faster, less precise)") + approximation_order, + "The order of the Taylor expansion (lower is faster, less precise)") ->expected(0, 2) ->capture_default_str(); auto* query_mesh_subcommand = app.add_subcommand("query_mesh")->description("Options for setting up a query mesh")->fallthrough(); auto* minbb = query_mesh_subcommand->add_option("--min", boxMins) - ->description("Min bounds for box mesh (x,y)") - ->expected(2); + ->description("Min bounds for box mesh (x,y)") + ->expected(2); auto* maxbb = query_mesh_subcommand->add_option("--max", boxMaxs) - ->description("Max bounds for box mesh (x,y)") - ->expected(2); + ->description("Max bounds for box mesh (x,y)") + ->expected(2); query_mesh_subcommand->add_option("--res", boxResolution) ->description("Resolution of the box mesh (i,j)") ->expected(2) @@ -426,13 +426,13 @@ using GWNQueryType = std::variant, template GWNQueryType pick_gwn_method(bool linearize_curves, int approximation_order) { - if (linearize_curves) + if(linearize_curves) { - if (approximation_order == 0) + if(approximation_order == 0) { return axom::quest::PolylineGWN2D {}; } - else if (approximation_order == 1) + else if(approximation_order == 1) { return axom::quest::PolylineGWN2D {}; } @@ -446,8 +446,8 @@ GWNQueryType pick_gwn_method(bool linearize_curves, int approximation_order) } GWNQueryType make_gwn_query(axom::runtime_policy::Policy policy, - bool linearize_curves, - int approximation_order) + bool linearize_curves, + int approximation_order) { #if defined(AXOM_USE_RAJA) && defined(AXOM_USE_OPENMP) if(policy == RuntimePolicy::omp) @@ -467,15 +467,15 @@ int main(int argc, char** argv) // Parse command line arguments into input Input input; - axom::CLI::App app{ + axom::CLI::App app { "Load mesh containing collection of curves" - " and optionally generate a query mesh of winding numbers." }; + " and optionally generate a query mesh of winding numbers."}; try { input.parse(argc, argv, app); } - catch (const axom::CLI::ParseError& e) + catch(const axom::CLI::ParseError& e) { return app.exit(e); } @@ -483,7 +483,7 @@ int main(int argc, char** argv) axom::utilities::raii::AnnotationsWrapper annotation_raii_wrapper(input.annotationMode); AXOM_ANNOTATE_SCOPE("2D winding number example"); - if (!input.elevatedMeshFile.empty()) + if(!input.elevatedMeshFile.empty()) { AXOM_ANNOTATE_SCOPE("write_elevated_mesh"); @@ -500,7 +500,7 @@ int main(int argc, char** argv) mfem_reader.setFileName(input.inputFile); const int ret = mfem_reader.read(curves); - if (ret != axom::quest::MFEMReader::READ_SUCCESS) + if(ret != axom::quest::MFEMReader::READ_SUCCESS) { SLIC_ERROR("Failed to read MFEM file."); return 1; @@ -509,13 +509,13 @@ int main(int argc, char** argv) // Linearize the input curves if asked for axom::mint::UnstructuredMesh poly_mesh(2, axom::mint::SEGMENT); - if (input.linearize) + if(input.linearize) { AXOM_ANNOTATE_SCOPE("linearization"); axom::utilities::Timer timer(true); axom::quest::LinearizeCurves lc; - if (input.useUniformLinearization) + if(input.useUniformLinearization) { lc.getLinearMeshUniform(curves.view(), &poly_mesh, input.segmentsPerKnotSpan); } @@ -536,14 +536,14 @@ int main(int argc, char** argv) } // Early return if user didn't set up a query mesh - if (input.boxResolution.empty()) + if(input.boxResolution.empty()) { return 0; } // Extract the curves and compute their bounding boxes along the way BoundingBox2D shape_bbox; - for (const auto& cur : curves) + for(const auto& cur : curves) { shape_bbox.addBox(cur.boundingBox()); } @@ -559,21 +559,21 @@ int main(int argc, char** argv) // Generate the query grid and fields quest::generate_gwn_query_mesh(dc, - shape_bbox, - input.boxMins, - input.boxMaxs, - input.boxResolution, - input.queryOrder); + shape_bbox, + input.boxMins, + input.boxMaxs, + input.boxResolution, + input.queryOrder); // Run the preprocess std::visit( [&](auto& wn) { using T = std::decay_t; - if constexpr (quest::gwn_input_type_v == quest::GWNInputType::Curve) + if constexpr(quest::gwn_input_type_v == quest::GWNInputType::Curve) { wn.preprocess(curves, input.memoized); } - else if constexpr (quest::gwn_input_type_v == quest::GWNInputType::Polyline) + else if constexpr(quest::gwn_input_type_v == quest::GWNInputType::Polyline) { wn.preprocess(&poly_mesh, input.algorithm == "direct"); } @@ -585,7 +585,7 @@ int main(int argc, char** argv) } // Postprocess query results: norms, ranges, and integral statistics - if (input.stats) + if(input.stats) { AXOM_ANNOTATE_SCOPE("postprocess"); @@ -598,14 +598,14 @@ int main(int argc, char** argv) std::int64_t pos_inout_dofs = 0; std::int64_t neg_inout_dofs = 0; - for (int i = 0; i < inout.Size(); ++i) + for(int i = 0; i < inout.Size(); ++i) { const double v = inout[i]; - if (v > 0.0) + if(v > 0.0) { ++pos_inout_dofs; } - else if (v < 0.0) + else if(v < 0.0) { ++neg_inout_dofs; } @@ -613,11 +613,11 @@ int main(int argc, char** argv) SLIC_INFO( axom::fmt::format("WN_STATS: dof_l2={:.6e} dof_linf={:.6e} l2={:.6e} min={:.6e} max={:.6e}", - winding_stats.dof_l2, - winding_stats.dof_linf, - winding_stats.l2, - winding_stats.min, - winding_stats.max)); + winding_stats.dof_l2, + winding_stats.dof_linf, + winding_stats.l2, + winding_stats.min, + winding_stats.max)); SLIC_INFO(axom::fmt::format( "INOUT_STATS: dof_l2={:.6e} dof_linf={:.6e} l2={:.6e} min={:.6e} max={:.6e} volume={:.6e} " @@ -630,13 +630,13 @@ int main(int argc, char** argv) inout_integrals.integral, inout_integrals.domain_volume, (inout_integrals.domain_volume > 0.0 ? inout_integrals.integral / inout_integrals.domain_volume - : 0.0), + : 0.0), pos_inout_dofs, neg_inout_dofs)); } // Save the query mesh and fields to disk using a format that can be viewed in VisIt - if (input.vis) + if(input.vis) { AXOM_ANNOTATE_SCOPE("dump_mesh"); @@ -646,8 +646,8 @@ int main(int argc, char** argv) windingDC.Save(); SLIC_INFO(axom::fmt::format("Outputting generated mesh '{}' to '{}'", - windingDC.GetCollectionName(), - axom::utilities::filesystem::getCWD())); + windingDC.GetCollectionName(), + axom::utilities::filesystem::getCWD())); } return 0; From bb7b3150c56d8c5ec9940f3d185ca31c60ffa25b Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 21 Apr 2026 15:59:43 -0700 Subject: [PATCH 103/986] Update nvcc compilation flags --- host-configs/matrix-toss_4_x86_64_ib-gcc@13.3.1_cuda.cmake | 2 +- host-configs/matrix-toss_4_x86_64_ib-llvm@19.1.3_cuda.cmake | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/host-configs/matrix-toss_4_x86_64_ib-gcc@13.3.1_cuda.cmake b/host-configs/matrix-toss_4_x86_64_ib-gcc@13.3.1_cuda.cmake index 001a7314bd..162852c99d 100644 --- a/host-configs/matrix-toss_4_x86_64_ib-gcc@13.3.1_cuda.cmake +++ b/host-configs/matrix-toss_4_x86_64_ib-gcc@13.3.1_cuda.cmake @@ -81,7 +81,7 @@ set(ENABLE_CUDA ON CACHE BOOL "") set(CMAKE_CUDA_SEPARABLE_COMPILATION ON CACHE BOOL "") -set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -restrict --expt-extended-lambda " CACHE STRING "" FORCE) +set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -restrict --expt-extended-lambda --expt-relaxed-constexpr " CACHE STRING "" FORCE) # nvcc does not like gtest's 'pthreads' flag diff --git a/host-configs/matrix-toss_4_x86_64_ib-llvm@19.1.3_cuda.cmake b/host-configs/matrix-toss_4_x86_64_ib-llvm@19.1.3_cuda.cmake index c439eeb3cd..fcb7db0261 100644 --- a/host-configs/matrix-toss_4_x86_64_ib-llvm@19.1.3_cuda.cmake +++ b/host-configs/matrix-toss_4_x86_64_ib-llvm@19.1.3_cuda.cmake @@ -83,7 +83,7 @@ set(ENABLE_CUDA ON CACHE BOOL "") set(CMAKE_CUDA_SEPARABLE_COMPILATION ON CACHE BOOL "") -set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -restrict --expt-extended-lambda " CACHE STRING "" FORCE) +set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -restrict --expt-extended-lambda --expt-relaxed-constexpr " CACHE STRING "" FORCE) # nvcc does not like gtest's 'pthreads' flag From 259b0e5b799eba350a9a263270ebf866a5754115 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 21 Apr 2026 16:01:19 -0700 Subject: [PATCH 104/986] Fix some unused variables. --- src/axom/sidre/spio/IOManager.cpp | 139 +++++++++++------------------- 1 file changed, 49 insertions(+), 90 deletions(-) diff --git a/src/axom/sidre/spio/IOManager.cpp b/src/axom/sidre/spio/IOManager.cpp index b1bb6790e7..60282735b1 100644 --- a/src/axom/sidre/spio/IOManager.cpp +++ b/src/axom/sidre/spio/IOManager.cpp @@ -64,6 +64,31 @@ std::string broadcastString(const std::string& str, MPI_Comm comm, int rank) return res; } +#ifdef AXOM_USE_HDF5 +inline void checkHDF5Status(herr_t status) +{ +#ifdef AXOM_DEBUG + SLIC_ASSERT(status >= 0); +#else + AXOM_UNUSED_VAR(status); +#endif +} + +inline void closeHDF5Group(hid_t group_id) +{ + checkHDF5Status(H5Gclose(group_id)); +} + +inline void flushHDF5File(hid_t file_id) +{ + checkHDF5Status(H5Fflush(file_id, H5F_SCOPE_LOCAL)); +} + +inline void closeHDF5File(hid_t file_id) +{ + checkHDF5Status(H5Fclose(file_id)); +} +#endif } // end anonymous namespace namespace axom @@ -229,15 +254,9 @@ void IOManager::write(sidre::Group* datagroup, SLIC_ASSERT(h5_group_id >= 0); datagroup->save(h5_group_id); - herr_t status; - AXOM_UNUSED_VAR(status); - - status = H5Gclose(h5_group_id); - SLIC_ASSERT(status >= 0); - status = H5Fflush(h5_file_id, H5F_SCOPE_LOCAL); - SLIC_ASSERT(status >= 0); - status = H5Fclose(h5_file_id); - SLIC_ASSERT(status >= 0); + closeHDF5Group(h5_group_id); + flushHDF5File(h5_file_id); + closeHDF5File(h5_file_id); #else SLIC_WARNING("'sidre_hdf5' protocol only available " << "when axom is configured with hdf5"); #endif /* AXOM_USE_HDF5 */ @@ -380,9 +399,6 @@ void IOManager::loadExternalData(sidre::Group* datagroup, const std::string& roo { if(m_my_rank < num_groups) { - herr_t errv; - AXOM_UNUSED_VAR(errv); - std::string hdf5_name = getFileNameForRank(file_pattern, root_file, set_id); hdf5_name = getSCRPath(hdf5_name); @@ -401,20 +417,14 @@ void IOManager::loadExternalData(sidre::Group* datagroup, const std::string& roo datagroup->loadExternalData(h5_group_id); - errv = H5Gclose(h5_group_id); - SLIC_ASSERT(errv >= 0); - - errv = H5Fclose(h5_file_id); - SLIC_ASSERT(errv >= 0); + closeHDF5Group(h5_group_id); + closeHDF5File(h5_file_id); } } else { for(int input_rank = m_my_rank; input_rank < num_groups; input_rank += m_comm_size) { - herr_t errv; - AXOM_UNUSED_VAR(errv); - std::string hdf5_name = getFileNameForRank(file_pattern, root_file, input_rank); hdf5_name = getSCRPath(hdf5_name); @@ -436,11 +446,8 @@ void IOManager::loadExternalData(sidre::Group* datagroup, const std::string& roo one_rank_input->loadExternalData(h5_group_id); - errv = H5Gclose(h5_group_id); - SLIC_ASSERT(errv >= 0); - - errv = H5Fclose(h5_file_id); - SLIC_ASSERT(errv >= 0); + closeHDF5Group(h5_group_id); + closeHDF5File(h5_file_id); } } @@ -520,9 +527,6 @@ void IOManager::loadExternalData(sidre::Group* parent_group, { if(m_my_rank < num_groups) { - herr_t errv; - AXOM_UNUSED_VAR(errv); - std::string hdf5_name = getFileNameForRank(file_pattern, root_file, set_id); hdf5_name = getSCRPath(hdf5_name); @@ -541,20 +545,14 @@ void IOManager::loadExternalData(sidre::Group* parent_group, load_group->loadExternalData(h5_group_id, subpath); - errv = H5Gclose(h5_group_id); - SLIC_ASSERT(errv >= 0); - - errv = H5Fclose(h5_file_id); - SLIC_ASSERT(errv >= 0); + closeHDF5Group(h5_group_id); + closeHDF5File(h5_file_id); } } else { for(int input_rank = m_my_rank; input_rank < num_groups; input_rank += m_comm_size) { - herr_t errv; - AXOM_UNUSED_VAR(errv); - std::string hdf5_name = getFileNameForRank(file_pattern, root_file, input_rank); hdf5_name = getSCRPath(hdf5_name); @@ -579,11 +577,8 @@ void IOManager::loadExternalData(sidre::Group* parent_group, one_rank_input->loadExternalData(h5_group_id, subpath); - errv = H5Gclose(h5_group_id); - SLIC_ASSERT(errv >= 0); - - errv = H5Fclose(h5_file_id); - SLIC_ASSERT(errv >= 0); + closeHDF5Group(h5_group_id); + closeHDF5File(h5_file_id); } } } @@ -719,9 +714,7 @@ std::string IOManager::getProtocol(const std::string& root_orig) if(file_id > 0) { relay_protocol = "hdf5"; - herr_t errv = H5Fclose(file_id); - AXOM_UNUSED_VAR(errv); - SLIC_ASSERT(errv >= 0); + closeHDF5File(file_id); } // Restore error output @@ -852,9 +845,6 @@ void IOManager::readSidreHDF5(sidre::Group* datagroup, { if(m_my_rank < num_groups) { - herr_t errv; - AXOM_UNUSED_VAR(errv); - std::string hdf5_name = getFileNameForRank(file_pattern, root_file, set_id); hdf5_name = getSCRPath(hdf5_name); @@ -872,11 +862,8 @@ void IOManager::readSidreHDF5(sidre::Group* datagroup, datagroup->load(h5_group_id, "sidre_hdf5", preserve_contents); - errv = H5Gclose(h5_group_id); - SLIC_ASSERT(errv >= 0); - - errv = H5Fclose(h5_file_id); - SLIC_ASSERT(errv >= 0); + closeHDF5Group(h5_group_id); + closeHDF5File(h5_file_id); } } else @@ -885,9 +872,6 @@ void IOManager::readSidreHDF5(sidre::Group* datagroup, for(int input_rank = m_my_rank; input_rank < num_groups; input_rank += m_comm_size) { - herr_t errv; - AXOM_UNUSED_VAR(errv); - std::string hdf5_name = getFileNameForRank(file_pattern, root_file, input_rank); hdf5_name = getSCRPath(hdf5_name); @@ -908,11 +892,8 @@ void IOManager::readSidreHDF5(sidre::Group* datagroup, one_rank_input->load(h5_group_id, "sidre_hdf5", preserve_contents); - errv = H5Gclose(h5_group_id); - SLIC_ASSERT(errv >= 0); - - errv = H5Fclose(h5_file_id); - SLIC_ASSERT(errv >= 0); + closeHDF5Group(h5_group_id); + closeHDF5File(h5_file_id); } } (void)m_baton->pass(); @@ -1060,17 +1041,9 @@ void IOManager::writeGroupToRootFile(sidre::Group* group, const std::string& fil conduit::relay::io::hdf5_write(data_holder, group_id); - herr_t errv; - AXOM_UNUSED_VAR(errv); - - errv = H5Gclose(group_id); - SLIC_ASSERT(errv >= 0); - - errv = H5Fflush(root_file_id, H5F_SCOPE_LOCAL); - SLIC_ASSERT(errv >= 0); - - errv = H5Fclose(root_file_id); - SLIC_ASSERT(errv >= 0); + closeHDF5Group(group_id); + flushHDF5File(root_file_id); + closeHDF5File(root_file_id); #else AXOM_UNUSED_VAR(group); AXOM_UNUSED_VAR(file_name); @@ -1114,17 +1087,9 @@ void IOManager::writeGroupToRootFileAtPath(sidre::Group* group, conduit::relay::io::hdf5_write(data_holder, group_id); - herr_t errv; - AXOM_UNUSED_VAR(errv); - - errv = H5Gclose(group_id); - SLIC_ASSERT(errv >= 0); - - errv = H5Fflush(root_file_id, H5F_SCOPE_LOCAL); - SLIC_ASSERT(errv >= 0); - - errv = H5Fclose(root_file_id); - SLIC_ASSERT(errv >= 0); + closeHDF5Group(group_id); + flushHDF5File(root_file_id); + closeHDF5File(root_file_id); #else AXOM_UNUSED_VAR(group); AXOM_UNUSED_VAR(file_name); @@ -1164,14 +1129,8 @@ void IOManager::writeViewToRootFileAtPath(sidre::View* view, conduit::relay::io::hdf5_write(data_holder, path_id); - herr_t errv; - AXOM_UNUSED_VAR(errv); - - errv = H5Fflush(root_file_id, H5F_SCOPE_LOCAL); - SLIC_ASSERT(errv >= 0); - - errv = H5Fclose(root_file_id); - SLIC_ASSERT(errv >= 0); + flushHDF5File(root_file_id); + closeHDF5File(root_file_id); #else AXOM_UNUSED_VAR(view); AXOM_UNUSED_VAR(file_name); From 7fc310fc89018937d78de66473ab69eb6ca48928 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 21 Apr 2026 16:02:11 -0700 Subject: [PATCH 105/986] Move away from lambdas to prevent warnings. --- .../quest/tests/quest_sampling_shaper.cpp | 126 +++++++++++++----- 1 file changed, 94 insertions(+), 32 deletions(-) diff --git a/src/axom/quest/tests/quest_sampling_shaper.cpp b/src/axom/quest/tests/quest_sampling_shaper.cpp index bdf41883cf..c6c6a9ded4 100644 --- a/src/axom/quest/tests/quest_sampling_shaper.cpp +++ b/src/axom/quest/tests/quest_sampling_shaper.cpp @@ -45,6 +45,9 @@ namespace fs = axom::utilities::filesystem; namespace { +using Point2D = primal::Point; +using Point3D = primal::Point; + const std::string unit_circle_contour = "piece = circle(origin=(0cm, 0cm), radius=1cm, start=0deg, end=360deg)"; @@ -64,6 +67,81 @@ const std::string proe_tet_fmt_str = R"( // Set the following to true for verbose output and for saving vis files constexpr bool very_verbose_output = false; +struct IdentityProjector22 +{ + AXOM_HOST_DEVICE Point2D operator()(const Point2D& pt) const { return Point2D {pt[0], pt[1]}; } +}; + +struct IdentityProjector33 +{ + AXOM_HOST_DEVICE Point3D operator()(const Point3D& pt) const + { + return Point3D {pt[0], pt[1], pt[2]}; + } +}; + +struct Projector32 +{ + AXOM_HOST_DEVICE Point2D operator()(const Point3D& pt) const { return Point2D {pt[0], pt[1]}; } +}; + +struct Projector23 +{ + AXOM_HOST_DEVICE Point3D operator()(const Point2D& pt) const { return Point3D {pt[0], pt[1], 0.}; } +}; + +struct ScaleProjector22 +{ + double scale_a; + double scale_b; + + AXOM_HOST_DEVICE Point2D operator()(const Point2D& pt) const + { + return Point2D {pt[0] / scale_a, pt[1] / scale_b}; + } +}; + +struct ZeroProjector33 +{ + AXOM_HOST_DEVICE Point3D operator()(const Point3D&) const { return Point3D {0., 0., 0.}; } +}; + +struct HalfScaleProjector33 +{ + AXOM_HOST_DEVICE Point3D operator()(const Point3D& pt) const + { + return Point3D {pt[0] / 2., pt[1] / 2., pt[2] / 2.}; + } +}; + +struct ZeroProjector32 +{ + AXOM_HOST_DEVICE Point2D operator()(const Point3D&) const { return Point2D {0., 0.}; } +}; + +struct AxisymmetricProjector32 +{ + AXOM_HOST_DEVICE Point2D operator()(Point3D pt) const + { + const double& x = pt[0]; + const double& y = pt[1]; + const double& z = pt[2]; + return Point2D {z, sqrt(x * x + y * y)}; + } +}; + +struct PlaneProjector23 +{ + double z; + + AXOM_HOST_DEVICE Point3D operator()(Point2D pt) const + { + const double& x = pt[0]; + const double& y = pt[1]; + return Point3D {x, y, z}; + } +}; + // Utility function to slice a tetrahedron along a plane primal::Polygon slice(const primal::Tetrahedron& tet, const primal::Plane& plane) @@ -624,10 +702,10 @@ dimensions: 2 // check that we can set several projectors in 2D and 3D // uses simplest projectors, e.g. identity in 2D and 3D - this->m_shaper->setPointProjector33([](const Point3D& pt) { return Point3D {pt[0], pt[1], pt[2]}; }); - this->m_shaper->setPointProjector22([](const Point2D& pt) { return Point2D {pt[0], pt[1]}; }); - this->m_shaper->setPointProjector32([](const Point3D& pt) { return Point2D {pt[0], pt[1]}; }); - this->m_shaper->setPointProjector23([](const Point2D& pt) { return Point3D {pt[0], pt[1], 0}; }); + this->m_shaper->setPointProjector33(IdentityProjector33 {}); + this->m_shaper->setPointProjector22(IdentityProjector22 {}); + this->m_shaper->setPointProjector32(Projector32 {}); + this->m_shaper->setPointProjector23(Projector23 {}); this->runShaping(); @@ -676,10 +754,9 @@ dimensions: 2 // creating an ellipse by scaling input x and y by scale_a and scale_b constexpr double scale_a = 3. / 2.; constexpr double scale_b = 3. / 4.; - this->m_shaper->setPointProjector22( - [](const Point2D& pt) { return Point2D {pt[0] / scale_a, pt[1] / scale_b}; }); + this->m_shaper->setPointProjector22(ScaleProjector22 {scale_a, scale_b}); // check that we can register another projector that's not used - this->m_shaper->setPointProjector33([](const Point3D&) { return Point3D {0., 0.}; }); + this->m_shaper->setPointProjector33(ZeroProjector33 {}); this->runShaping(); @@ -1215,7 +1292,7 @@ dimensions: 2 this->initializeShaping(shape_file.getPath(), initialGridFunctions); // set projector from 2D mesh points to 3D query points within STL - this->m_shaper->setPointProjector23([](Point2D pt) { return Point3D {pt[0], pt[1], 0.}; }); + this->m_shaper->setPointProjector23(Projector23 {}); this->m_shaper->setQuadratureOrder(8); @@ -1678,10 +1755,10 @@ dimensions: 3 // check that we can set several projectors in 2D and 3D // uses simplest projectors, e.g. identity in 2D and 3D - this->m_shaper->setPointProjector33([](const Point3D& pt) { return Point3D {pt[0], pt[1], pt[2]}; }); - this->m_shaper->setPointProjector22([](const Point2D& pt) { return Point2D {pt[0], pt[1]}; }); - this->m_shaper->setPointProjector32([](const Point3D& pt) { return Point2D {pt[0], pt[1]}; }); - this->m_shaper->setPointProjector23([](const Point2D& pt) { return Point3D {pt[0], pt[1], 0}; }); + this->m_shaper->setPointProjector33(IdentityProjector33 {}); + this->m_shaper->setPointProjector22(IdentityProjector22 {}); + this->m_shaper->setPointProjector32(Projector32 {}); + this->m_shaper->setPointProjector23(Projector23 {}); this->runShaping(); @@ -1731,11 +1808,10 @@ dimensions: 3 this->initializeShaping(shape_file.getPath()); // scale input points by a factor of 1/2 in each dimension - this->m_shaper->setPointProjector33( - [](const Point3D& pt) { return Point3D {pt[0] / 2, pt[1] / 2, pt[2] / 2}; }); + this->m_shaper->setPointProjector33(HalfScaleProjector33 {}); // for good measure, add a 3D->2D projector that will not be used - this->m_shaper->setPointProjector32([](const Point3D&) { return Point2D {0, 0}; }); + this->m_shaper->setPointProjector32(ZeroProjector32 {}); this->runShaping(); @@ -1795,12 +1871,7 @@ dimensions: 2 this->initializeShaping(shape_file.getPath()); // set projector from 3D points to axisymmetric plane - this->m_shaper->setPointProjector32([](Point3D pt) { - const double& x = pt[0]; - const double& y = pt[1]; - const double& z = pt[2]; - return Point2D {z, sqrt(x * x + y * y)}; - }); + this->m_shaper->setPointProjector32(AxisymmetricProjector32 {}); // we need a higher quadrature order to resolve this shape at the (low) testing resolution this->m_shaper->setQuadratureOrder(8); @@ -1874,12 +1945,7 @@ dimensions: 2 this->initializeShaping(shape_file.getPath()); // set projector from 3D points to axisymmetric plane - this->m_shaper->setPointProjector32([](Point3D pt) { - const double& x = pt[0]; - const double& y = pt[1]; - const double& z = pt[2]; - return Point2D {z, sqrt(x * x + y * y)}; - }); + this->m_shaper->setPointProjector32(AxisymmetricProjector32 {}); // we need a higher quadrature order to resolve this shape at the (low) testing resolution this->m_shaper->setQuadratureOrder(8); @@ -1968,11 +2034,7 @@ dimensions: 3 SLIC_INFO(axom::fmt::format("Area of intersection polygon: {}", intersectionArea)); // set projector from 2D points to 3-space, z-coord is lambda captured - this->m_shaper->setPointProjector23([z](Point2D pt) -> Point3D { - const double& x = pt[0]; - const double& y = pt[1]; - return Point3D {x, y, z}; - }); + this->m_shaper->setPointProjector23(PlaneProjector23 {z}); // we need a higher quadrature order to resolve this shape at the (low) testing resolution this->m_shaper->setQuadratureOrder(8); From 308e00ee8834db64cd65a664638c40943fb3de79 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 21 Apr 2026 16:02:36 -0700 Subject: [PATCH 106/986] Move away from lambdas to prevent warnings. --- src/axom/quest/examples/shaping_driver.cpp | 35 +++++++++++++++------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/axom/quest/examples/shaping_driver.cpp b/src/axom/quest/examples/shaping_driver.cpp index 6219e264e1..8210d30d06 100644 --- a/src/axom/quest/examples/shaping_driver.cpp +++ b/src/axom/quest/examples/shaping_driver.cpp @@ -46,6 +46,28 @@ namespace sidre = axom::sidre; using VolFracSampling = quest::shaping::VolFracSampling; using SamplingMethod = quest::SamplingShaper::SamplingMethod; +namespace +{ +using Point2D = primal::Point; +using Point3D = primal::Point; + +struct AxisymmetricProjector32 +{ + AXOM_HOST_DEVICE Point2D operator()(Point3D pt) const + { + const double& x = pt[0]; + const double& y = pt[1]; + const double& z = pt[2]; + return Point2D {z, sqrt(x * x + y * y)}; + } +}; + +struct Projector23 +{ + AXOM_HOST_DEVICE Point3D operator()(Point2D pt) const { return Point3D {pt[0], pt[1], 0.}; } +}; +} // namespace + //------------------------------------------------------------------------------ /// Struct to help choose our shaping method: sampling or intersection for now @@ -574,20 +596,11 @@ int main(int argc, char** argv) // register point projectors if(shapingDC.GetMesh()->Dimension() == 3) { - samplingShaper->setPointProjector32([](primal::Point pt) { - const double& x = pt[0]; - const double& y = pt[1]; - const double& z = pt[2]; - return primal::Point {z, sqrt(x * x + y * y)}; - }); + samplingShaper->setPointProjector32(AxisymmetricProjector32 {}); } else if(shapingDC.GetMesh()->Dimension() == 2) { - samplingShaper->setPointProjector23([](primal::Point pt) { - const double& x = pt[0]; - const double& y = pt[1]; - return primal::Point {x, y, 0.}; - }); + samplingShaper->setPointProjector23(Projector23 {}); } } From f941a4f25ea8db5eed9617ed5872fe16b7eff0e0 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 21 Apr 2026 16:03:57 -0700 Subject: [PATCH 107/986] Move away from axom::mint::for_all_faces --- src/axom/quest/io/STLWriter.cpp | 59 +++++++++++++++------------------ 1 file changed, 26 insertions(+), 33 deletions(-) diff --git a/src/axom/quest/io/STLWriter.cpp b/src/axom/quest/io/STLWriter.cpp index 42cb9ce061..d63dce34ba 100644 --- a/src/axom/quest/io/STLWriter.cpp +++ b/src/axom/quest/io/STLWriter.cpp @@ -108,13 +108,10 @@ IndexType STLWriter::getNumberOfTriangles() const } else if(m_mesh->getDimension() == 3) { - axom::ReduceSum ntri_reduce(0); - axom::mint::for_all_faces( - m_mesh, - AXOM_LAMBDA(IndexType AXOM_UNUSED_PARAM(faceID), - const IndexType *AXOM_UNUSED_PARAM(nodes), - IndexType N) { ntri_reduce += (N - 2); }); - ntri = ntri_reduce.get(); + for(IndexType faceId = 0; faceId < m_mesh->getNumberOfFaces(); faceId++) + { + ntri += (m_mesh->getNumberOfFaceNodes(faceId) - 2); + } } return ntri; } @@ -195,34 +192,30 @@ int STLWriter::write(const mint::Mesh *mesh) } else { - // For value capture. - std::ofstream *out_ptr = &out; - const bool binary = m_binary; - - axom::mint::for_all_faces( - m_mesh, - AXOM_LAMBDA(IndexType AXOM_UNUSED_PARAM(faceID), const IndexType *nodes, IndexType nnodes) { - // NOTE: Here in the lambda, we use "mesh" instead of "m_mesh" so we do - // not capture STLWriter's "this" pointer. - - // Iterate over the face like a triangle fan. - double coords[3][3] = {{0., 0., 0.}, {0., 0., 0.}, {0., 0., 0.}}; - mesh->getNode(nodes[0], coords[0]); - const IndexType ntri = nnodes - 2; - for(IndexType ti = 0; ti < ntri; ti++) - { - mesh->getNode(nodes[ti + 1], coords[1]); - mesh->getNode(nodes[ti + 2], coords[2]); + axom::Array nodes; + for(IndexType faceId = 0; faceId < mesh->getNumberOfFaces(); faceId++) + { + nodes.resize(mesh->getNumberOfFaceNodes(faceId)); + const auto nnodes = mesh->getFaceNodeIDs(faceId, nodes.data()); - // Compute facet normal. - const VectorType A(coords[0], 3); - const VectorType B(coords[1], 3); - const VectorType C(coords[2], 3); - const VectorType N = VectorType::cross_product(B - A, C - A).unitVector(); + // Iterate over the face like a triangle fan. + double coords[3][3] = {{0., 0., 0.}, {0., 0., 0.}, {0., 0., 0.}}; + mesh->getNode(nodes[0], coords[0]); + const IndexType ntri = nnodes - 2; + for(IndexType ti = 0; ti < ntri; ti++) + { + mesh->getNode(nodes[ti + 1], coords[1]); + mesh->getNode(nodes[ti + 2], coords[2]); - internal::writeTriangle(*out_ptr, binary, coords, N); - } - }); + // Compute facet normal. + const VectorType A(coords[0], 3); + const VectorType B(coords[1], 3); + const VectorType C(coords[2], 3); + const VectorType N = VectorType::cross_product(B - A, C - A).unitVector(); + + internal::writeTriangle(out, m_binary, coords, N); + } + } } if(!m_binary) From 577a04550a0bae895f8b249d016d69c8644b5bf3 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 21 Apr 2026 16:04:16 -0700 Subject: [PATCH 108/986] Added a using statement. --- src/axom/quest/detail/clipping/SORClipper.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/axom/quest/detail/clipping/SORClipper.hpp b/src/axom/quest/detail/clipping/SORClipper.hpp index e7805e57c6..a63202ecd9 100644 --- a/src/axom/quest/detail/clipping/SORClipper.hpp +++ b/src/axom/quest/detail/clipping/SORClipper.hpp @@ -33,6 +33,8 @@ namespace experimental class SORClipper : public MeshClipperStrategy { public: + using MeshClipperStrategy::specializedClipCells; + /*! * @brief Constructor. * From a9ee704e5c0551451f846071dcf0ce8ed90e1cea Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 21 Apr 2026 16:04:45 -0700 Subject: [PATCH 109/986] Added a AXOM_HOST_DEVICE to a constructor. --- src/axom/core/IteratorBase.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/axom/core/IteratorBase.hpp b/src/axom/core/IteratorBase.hpp index 7fab525fb9..56aa107b54 100644 --- a/src/axom/core/IteratorBase.hpp +++ b/src/axom/core/IteratorBase.hpp @@ -54,6 +54,7 @@ class IteratorBase static_assert(std::is_integral::value, "PosType must be integral"); protected: + AXOM_HOST_DEVICE IteratorBase() : m_pos(PosType()) { } AXOM_HOST_DEVICE From 28c338fe34f964fe487a42f330a0dcb790cd50a0 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 22 Apr 2026 10:36:06 -0700 Subject: [PATCH 110/986] Namespace changes in Sina tests that eliminate gtest warnings under nvcc. --- src/axom/sina/tests/sina_AdiakWriter.cpp | 19 ++++--------- src/axom/sina/tests/sina_ConduitUtil.cpp | 22 ++++++--------- src/axom/sina/tests/sina_Curve.cpp | 19 ++++--------- src/axom/sina/tests/sina_CurveSet.cpp | 17 ++++++------ src/axom/sina/tests/sina_DataHolder.cpp | 22 ++++++--------- src/axom/sina/tests/sina_Datum.cpp | 18 ++++-------- src/axom/sina/tests/sina_Document.cpp | 34 +++++++++++++++-------- src/axom/sina/tests/sina_File.cpp | 32 +++++++-------------- src/axom/sina/tests/sina_ID.cpp | 18 ++++-------- src/axom/sina/tests/sina_Record.cpp | 30 +++++++++++--------- src/axom/sina/tests/sina_Relationship.cpp | 18 ++++-------- src/axom/sina/tests/sina_Run.cpp | 19 ++++--------- 12 files changed, 108 insertions(+), 160 deletions(-) diff --git a/src/axom/sina/tests/sina_AdiakWriter.cpp b/src/axom/sina/tests/sina_AdiakWriter.cpp index 542aa47eeb..7dc991304b 100644 --- a/src/axom/sina/tests/sina_AdiakWriter.cpp +++ b/src/axom/sina/tests/sina_AdiakWriter.cpp @@ -25,14 +25,12 @@ extern "C" { #include "axom/sina/core/ID.hpp" #include "axom/sina/core/Run.hpp" -namespace axom -{ -namespace sina -{ -namespace testing -{ -namespace -{ +namespace sina = axom::sina; + +using sina::ID; +using sina::IDType; +using sina::Record; +using sina::adiakSinaCallback; using ::testing::DoubleEq; using ::testing::ElementsAre; @@ -164,9 +162,4 @@ TEST_F(AdiakWriterTest, files_list) EXPECT_EQ(fileListName, asNode[EXPECTED_FILES_KEY].child(fileListVal2)["tags"][0].as_string()); } -} // namespace -} // namespace testing -} // namespace sina -} // namespace axom - #endif // AXOM_USE_ADIAK diff --git a/src/axom/sina/tests/sina_ConduitUtil.cpp b/src/axom/sina/tests/sina_ConduitUtil.cpp index 81cb28c04f..1ffa04f8b7 100644 --- a/src/axom/sina/tests/sina_ConduitUtil.cpp +++ b/src/axom/sina/tests/sina_ConduitUtil.cpp @@ -12,14 +12,15 @@ #include "axom/sina/core/ConduitUtil.hpp" #include "axom/sina/tests/SinaMatchers.hpp" -namespace axom -{ -namespace sina -{ -namespace testing -{ -namespace -{ +namespace sina = axom::sina; + +using sina::getOptionalString; +using sina::getRequiredDouble; +using sina::getRequiredField; +using sina::getRequiredString; +using sina::toDoubleVector; +using sina::toStringVector; +using axom::sina::testing::parseJsonValue; using ::testing::ContainerEq; using ::testing::DoubleEq; @@ -255,8 +256,3 @@ TEST(ConduitUtil, toStringVector_NotListOfStrings) EXPECT_THAT(ex.what(), HasSubstr("someName")); } } - -} // namespace -} // namespace testing -} // namespace sina -} // namespace axom diff --git a/src/axom/sina/tests/sina_Curve.cpp b/src/axom/sina/tests/sina_Curve.cpp index af8b2b91ab..f272bd365c 100644 --- a/src/axom/sina/tests/sina_Curve.cpp +++ b/src/axom/sina/tests/sina_Curve.cpp @@ -11,14 +11,12 @@ #include "axom/sina/core/ConduitUtil.hpp" #include "axom/sina/tests/SinaMatchers.hpp" -namespace axom -{ -namespace sina -{ -namespace testing -{ -namespace -{ +namespace sina = axom::sina; + +using sina::Curve; +using sina::addStringsToNode; +using axom::sina::testing::MatchesJsonMatcher; +using axom::sina::testing::parseJsonValue; using ::testing::ContainerEq; using ::testing::ElementsAre; @@ -119,8 +117,3 @@ TEST(Curve, toNode_optionalFields) })"; EXPECT_THAT(curve.toNode(), MatchesJsonMatcher(expected)); } - -} // namespace -} // namespace testing -} // namespace sina -} // namespace axom diff --git a/src/axom/sina/tests/sina_CurveSet.cpp b/src/axom/sina/tests/sina_CurveSet.cpp index c27b048bda..b19b8d294c 100644 --- a/src/axom/sina/tests/sina_CurveSet.cpp +++ b/src/axom/sina/tests/sina_CurveSet.cpp @@ -42,11 +42,15 @@ bool operator==(Curve const &lhs, Curve const &rhs) lhs.getTags() == rhs.getTags() && lhs.getValues() == rhs.getValues(); return r; } +} // namespace sina +} // namespace axom -namespace testing -{ -namespace -{ +namespace sina = axom::sina; + +using sina::Curve; +using sina::CurveSet; +using axom::sina::testing::MatchesJsonMatcher; +using axom::sina::testing::parseJsonValue; using ::testing::ContainerEq; using ::testing::ElementsAre; @@ -375,8 +379,3 @@ TEST(CurveSet, customIndependentSortOrder) curveSet.applyCustomIndependentCurveOrder(newOrder); EXPECT_THAT(curveSet.getOrderedIndependentCurveNames(), ContainerEq(newOrder)); } - -} // namespace -} // namespace testing -} // namespace sina -} // namespace axom diff --git a/src/axom/sina/tests/sina_DataHolder.cpp b/src/axom/sina/tests/sina_DataHolder.cpp index c9788338be..3c34f3d94a 100644 --- a/src/axom/sina/tests/sina_DataHolder.cpp +++ b/src/axom/sina/tests/sina_DataHolder.cpp @@ -13,14 +13,15 @@ #include "axom/sina/core/DataHolder.hpp" #include "axom/sina/tests/SinaMatchers.hpp" -namespace axom -{ -namespace sina -{ -namespace testing -{ -namespace -{ +namespace sina = axom::sina; + +using sina::Curve; +using sina::CurveSet; +using sina::DataHolder; +using sina::Datum; +using sina::addStringsToNode; +using axom::sina::testing::MatchesJsonMatcher; +using axom::sina::testing::parseJsonValue; using ::testing::Contains; using ::testing::DoubleEq; @@ -311,8 +312,3 @@ TEST(DataHolder, toNode_userDefined) int_array + userDefined["k3"].dtype().number_of_elements()); EXPECT_THAT(udef_ints, ElementsAre(1, 2, 3)); } - -} // namespace -} // namespace testing -} // namespace sina -} // namespace axom diff --git a/src/axom/sina/tests/sina_Datum.cpp b/src/axom/sina/tests/sina_Datum.cpp index 7c9e5cd0ad..88699bb1cb 100644 --- a/src/axom/sina/tests/sina_Datum.cpp +++ b/src/axom/sina/tests/sina_Datum.cpp @@ -14,14 +14,11 @@ #include "axom/sina/core/Datum.hpp" #include "axom/sina/core/ConduitUtil.hpp" -namespace axom -{ -namespace sina -{ -namespace testing -{ -namespace -{ +namespace sina = axom::sina; + +using sina::Datum; +using sina::ValueType; +using sina::addStringsToNode; using ::testing::DoubleEq; using ::testing::ElementsAre; @@ -189,8 +186,3 @@ TEST(Datum, toJson) EXPECT_EQ(scal_list, scal_child_vals); EXPECT_EQ(val_list, str_child_vals); } - -} // namespace -} // namespace testing -} // namespace sina -} // namespace axom diff --git a/src/axom/sina/tests/sina_Document.cpp b/src/axom/sina/tests/sina_Document.cpp index 202dee74ce..1c0ea6dc01 100644 --- a/src/axom/sina/tests/sina_Document.cpp +++ b/src/axom/sina/tests/sina_Document.cpp @@ -32,14 +32,28 @@ #include "axom/sina/core/Record.hpp" #include "axom/sina/tests/SinaMatchers.hpp" -namespace axom -{ -namespace sina -{ -namespace testing -{ -namespace -{ +namespace sina = axom::sina; + +using sina::Document; +using sina::File; +using sina::ID; +using sina::IDType; +using sina::Protocol; +using sina::Record; +using sina::RecordLoader; +using sina::Relationship; +using sina::appendDocumentToHDF5; +using sina::appendDocumentToJson; +using sina::createRecordLoaderWithAllKnownTypes; +using sina::getRequiredField; +using sina::getRequiredString; +using sina::loadDocument; +using sina::restoreSlashes; +using sina::saveDocument; +using sina::validateAppendDocument; +using axom::sina::testing::TEST_RECORD_VALUE_KEY; +using axom::sina::testing::TestRecord; +using axom::sina::testing::parseJsonValue; using ::testing::ElementsAre; using ::testing::HasSubstr; @@ -876,7 +890,3 @@ TEST(Document, saveDocument_hdf5) } #endif -} // namespace -} // namespace testing -} // namespace sina -} // namespace axom diff --git a/src/axom/sina/tests/sina_File.cpp b/src/axom/sina/tests/sina_File.cpp index 3a9496a876..0907ffb674 100644 --- a/src/axom/sina/tests/sina_File.cpp +++ b/src/axom/sina/tests/sina_File.cpp @@ -9,29 +9,22 @@ #include "axom/sina/core/File.hpp" #include "axom/sina/core/ConduitUtil.hpp" -namespace axom -{ -namespace sina -{ -namespace testing -{ -namespace -{ +namespace sina = axom::sina; char const EXPECTED_MIMETYPE_KEY[] = "mimetype"; char const EXPECTED_TAGS_KEY[] = "tags"; TEST(File, construct_differentType) { - File f1 {"from literal"}; - File f2 {std::string {"from std::string"}}; + sina::File f1 {"from literal"}; + sina::File f2 {std::string {"from std::string"}}; EXPECT_EQ("from literal", f1.getUri()); EXPECT_EQ("from std::string", f2.getUri()); } TEST(File, setMimeType) { - File file {"the URI"}; + sina::File file {"the URI"}; file.setMimeType("mime"); EXPECT_EQ("the URI", file.getUri()); EXPECT_EQ("mime", file.getMimeType()); @@ -40,7 +33,7 @@ TEST(File, setMimeType) TEST(File, setTags) { std::vector tags = {"these", "are", "tags"}; - File file {"the URI"}; + sina::File file {"the URI"}; file.setTags(tags); EXPECT_EQ("the URI", file.getUri()); EXPECT_EQ(tags, file.getTags()); @@ -50,7 +43,7 @@ TEST(File, create_fromNode_basic) { std::string uri = "the URI"; conduit::Node basic_file(conduit::DataType::object()); - File file {uri, basic_file}; + sina::File file {uri, basic_file}; EXPECT_EQ(uri, file.getUri()); EXPECT_EQ("", file.getMimeType()); EXPECT_EQ(0, file.getTags().size()); @@ -62,8 +55,8 @@ TEST(File, create_fromNode_complete) std::vector tags = {"tags", "are", "fun"}; conduit::Node full_file(conduit::DataType::object()); full_file[EXPECTED_MIMETYPE_KEY] = "the mime type"; - addStringsToNode(full_file, EXPECTED_TAGS_KEY, tags); - File file {uri, full_file}; + sina::addStringsToNode(full_file, EXPECTED_TAGS_KEY, tags); + sina::File file {uri, full_file}; EXPECT_EQ(uri, file.getUri()); EXPECT_EQ("the mime type", file.getMimeType()); EXPECT_EQ(tags, file.getTags()); @@ -71,7 +64,7 @@ TEST(File, create_fromNode_complete) TEST(File, toNode_basic) { - File file {"the URI"}; + sina::File file {"the URI"}; auto asNode = file.toNode(); EXPECT_FALSE(asNode.has_child(EXPECTED_MIMETYPE_KEY)); EXPECT_FALSE(asNode.has_child(EXPECTED_TAGS_KEY)); @@ -80,15 +73,10 @@ TEST(File, toNode_basic) TEST(File, toNode_complete) { std::vector tags = {"these", "are", "tags"}; - File file {"the URI"}; + sina::File file {"the URI"}; file.setMimeType("the mime type"); file.setTags(tags); auto asNode = file.toNode(); EXPECT_EQ("the mime type", asNode[EXPECTED_MIMETYPE_KEY].as_string()); EXPECT_EQ(tags, file.getTags()); } - -} // namespace -} // namespace testing -} // namespace sina -} // namespace axom diff --git a/src/axom/sina/tests/sina_ID.cpp b/src/axom/sina/tests/sina_ID.cpp index 78be84e595..6aa6d70fa6 100644 --- a/src/axom/sina/tests/sina_ID.cpp +++ b/src/axom/sina/tests/sina_ID.cpp @@ -13,14 +13,11 @@ #include "axom/sina/core/ID.hpp" -namespace axom -{ -namespace sina -{ -namespace testing -{ -namespace -{ +namespace sina = axom::sina; +namespace internal = axom::sina::internal; + +using sina::ID; +using sina::IDType; using ::testing::HasSubstr; @@ -104,8 +101,3 @@ TEST(IDField, toNode_global) EXPECT_EQ("the id", value["global name"].as_string()); EXPECT_FALSE(value.has_child("local name")); } - -} // namespace -} // namespace testing -} // namespace sina -} // namespace axom diff --git a/src/axom/sina/tests/sina_Record.cpp b/src/axom/sina/tests/sina_Record.cpp index d0ecbe57ee..9f6a309e5b 100644 --- a/src/axom/sina/tests/sina_Record.cpp +++ b/src/axom/sina/tests/sina_Record.cpp @@ -16,14 +16,23 @@ #include "axom/sina/tests/SinaMatchers.hpp" #include "axom/sina/tests/TestRecord.hpp" -namespace axom -{ -namespace sina -{ -namespace testing -{ -namespace -{ +namespace sina = axom::sina; + +using sina::Curve; +using sina::CurveSet; +using sina::Datum; +using sina::File; +using sina::ID; +using sina::IDType; +using sina::Record; +using sina::RecordLoader; +using sina::addStringsToNode; +using sina::createRecordLoaderWithAllKnownTypes; +using sina::setDefaultCurveOrder; +using axom::sina::testing::MatchesJsonMatcher; +using axom::sina::testing::TEST_RECORD_VALUE_KEY; +using axom::sina::testing::TestRecord; +using axom::sina::testing::parseJsonValue; using ::testing::Contains; using ::testing::DoubleEq; @@ -684,8 +693,3 @@ TEST(RecordLoader, createRecordLoaderWithAllKnownTypes) RecordLoader loader = createRecordLoaderWithAllKnownTypes(); EXPECT_TRUE(loader.canLoad("run")); } - -} // namespace -} // namespace testing -} // namespace sina -} // namespace axom diff --git a/src/axom/sina/tests/sina_Relationship.cpp b/src/axom/sina/tests/sina_Relationship.cpp index 6e0e4155bc..cb03273a32 100644 --- a/src/axom/sina/tests/sina_Relationship.cpp +++ b/src/axom/sina/tests/sina_Relationship.cpp @@ -11,14 +11,11 @@ #include "axom/sina/core/Relationship.hpp" -namespace axom -{ -namespace sina -{ -namespace testing -{ -namespace -{ +namespace sina = axom::sina; + +using sina::ID; +using sina::IDType; +using sina::Relationship; char const EXPECTED_GLOBAL_OBJECT_ID_KEY[] = "object"; char const EXPECTED_LOCAL_OBJECT_ID_KEY[] = "local_object"; @@ -169,8 +166,3 @@ TEST(Relationship, toNode_globalIds) EXPECT_FALSE(asNode.has_child(EXPECTED_LOCAL_SUBJECT_ID_KEY)); EXPECT_FALSE(asNode.has_child(EXPECTED_LOCAL_OBJECT_ID_KEY)); } - -} // namespace -} // namespace testing -} // namespace sina -} // namespace axom \ No newline at end of file diff --git a/src/axom/sina/tests/sina_Run.cpp b/src/axom/sina/tests/sina_Run.cpp index f0c06868b3..66f02ada47 100644 --- a/src/axom/sina/tests/sina_Run.cpp +++ b/src/axom/sina/tests/sina_Run.cpp @@ -11,14 +11,12 @@ #include "axom/sina/core/Run.hpp" -namespace axom -{ -namespace sina -{ -namespace testing -{ -namespace -{ +namespace sina = axom::sina; + +using sina::ID; +using sina::IDType; +using sina::RecordLoader; +using sina::addRunLoader; using ::testing::HasSubstr; @@ -103,8 +101,3 @@ TEST(Run, addRunLoader) EXPECT_EQ("1.2.3", run->getVersion()); EXPECT_EQ("jdoe", run->getUser()); } - -} // namespace -} // namespace testing -} // namespace sina -} // namespace axom From 17cda522d3a666b9aa4be121669398eaf69501b3 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 22 Apr 2026 10:36:35 -0700 Subject: [PATCH 111/986] Change to axom::utilities::swap in TetMeshClipper.cpp --- src/axom/quest/detail/clipping/TetMeshClipper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/axom/quest/detail/clipping/TetMeshClipper.cpp b/src/axom/quest/detail/clipping/TetMeshClipper.cpp index 9f241dff4a..3199d83fd1 100644 --- a/src/axom/quest/detail/clipping/TetMeshClipper.cpp +++ b/src/axom/quest/detail/clipping/TetMeshClipper.cpp @@ -736,7 +736,7 @@ void TetMeshClipper::checkTetOrientations(conduit::Node& connNode, { if(fixOrientation) { - std::swap(connArray(iTet, 2), connArray(iTet, 3)); + axom::utilities::swap(connArray(iTet, 2), connArray(iTet, 3)); } else { From c8fcbef374dec0ee3b41eb09ce3466e6b3e879c4 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 22 Apr 2026 10:38:04 -0700 Subject: [PATCH 112/986] Change to normal for loops to reduce warnings under nvcc. --- src/axom/quest/DiscreteShape.cpp | 50 +++++++++++++++----------------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/src/axom/quest/DiscreteShape.cpp b/src/axom/quest/DiscreteShape.cpp index dc49d28ffa..7190904940 100644 --- a/src/axom/quest/DiscreteShape.cpp +++ b/src/axom/quest/DiscreteShape.cpp @@ -576,22 +576,21 @@ void DiscreteShape::createRepresentationOfSphere() auto nodeCoordsView = nodeCoords.view(); auto connectivityView = connectivity.view(); - axom::for_all( - octCount, - AXOM_LAMBDA(axom::IndexType octIdx) { - TetType tetsInOct[TETS_PER_OCT]; - axom::primal::split(octs[octIdx], tetsInOct); - for(int iTet = 0; iTet < TETS_PER_OCT; ++iTet) + for(axom::IndexType octIdx = 0; octIdx < octCount; ++octIdx) + { + TetType tetsInOct[TETS_PER_OCT]; + axom::primal::split(octs[octIdx], tetsInOct); + for(int iTet = 0; iTet < TETS_PER_OCT; ++iTet) + { + axom::IndexType tetIdx = octIdx * TETS_PER_OCT + iTet; + for(int iNode = 0; iNode < NODES_PER_TET; ++iNode) { - axom::IndexType tetIdx = octIdx * TETS_PER_OCT + iTet; - for(int iNode = 0; iNode < NODES_PER_TET; ++iNode) - { - axom::IndexType nodeIdx = tetIdx * NODES_PER_TET + iNode; - nodeCoordsView[nodeIdx] = tetsInOct[iTet][iNode]; - connectivityView[tetIdx][iNode] = nodeIdx; - } + axom::IndexType nodeIdx = tetIdx * NODES_PER_TET + iNode; + nodeCoordsView[nodeIdx] = tetsInOct[iTet][iNode]; + connectivityView[tetIdx][iNode] = nodeIdx; } - }); + } + } TetMesh* tetMesh = nullptr; if(m_sidreGroup != nullptr) @@ -632,18 +631,17 @@ void DiscreteShape::createRepresentationOfSOR() numerics::Matrix rotate = sorAxisRotMatrix(sorGeom.getSorDirection()); const auto& translate = sorGeom.getSorOriginCoords(); auto octsView = octs.view(); - axom::for_all( - octCount, - AXOM_LAMBDA(axom::IndexType iOct) { - auto& oct = octsView[iOct]; - for(int iVert = 0; iVert < OctType::NUM_VERTS; ++iVert) - { - auto& newCoords = oct[iVert]; - auto oldCoords = newCoords; - numerics::matrix_vector_multiply(rotate, oldCoords.data(), newCoords.data()); - newCoords.array() += translate.array(); - } - }); + for(axom::IndexType iOct = 0; iOct < octCount; ++iOct) + { + auto& oct = octsView[iOct]; + for(int iVert = 0; iVert < OctType::NUM_VERTS; ++iVert) + { + auto& newCoords = oct[iVert]; + auto oldCoords = newCoords; + numerics::matrix_vector_multiply(rotate, oldCoords.data(), newCoords.data()); + newCoords.array() += translate.array(); + } + } // Dump discretized octs as a tet mesh // From 6e6fa907d0f8b85e6b2f3aa0bde24df2f007264c Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 22 Apr 2026 10:45:30 -0700 Subject: [PATCH 113/986] Change hashing function for string to reduce warning count under nvcc. --- src/axom/core/tests/core_flatmap.hpp | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/axom/core/tests/core_flatmap.hpp b/src/axom/core/tests/core_flatmap.hpp index f94a5de563..5466b5fb5e 100644 --- a/src/axom/core/tests/core_flatmap.hpp +++ b/src/axom/core/tests/core_flatmap.hpp @@ -64,6 +64,28 @@ inline void flatmap_get_value(T key, U& out) out = key; } +struct FlatMapHostStringHash +{ + using argument_type = std::string; + using result_type = axom::IndexType; + + AXOM_HOST_DEVICE result_type operator()(const std::string& key) const + { +#if defined(AXOM_DEVICE_CODE) + AXOM_UNUSED_VAR(key); + return 0; +#else + uint64_t hash = static_cast(std::hash {}(key)); + hash *= 0xbf58476d1ce4e5b9ULL; + hash ^= hash >> 32; + hash *= 0x94d049bb133111ebULL; + hash ^= hash >> 32; + hash *= 0x94d049bb133111ebULL; + return static_cast(hash); +#endif + } +}; + template class core_flatmap : public ::testing::Test { @@ -102,8 +124,10 @@ class core_flatmap : public ::testing::Test using MyTypes = ::testing::Types, axom::FlatMap, - axom::FlatMap, - axom::FlatMap>; + axom::FlatMap, + axom::FlatMap>; TYPED_TEST_SUITE(core_flatmap, MyTypes); From 5c6297c92e16a6bd352e4ac4391a66ba77ee7f7a Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 22 Apr 2026 15:26:47 -0700 Subject: [PATCH 114/986] Change klee namespace in tests to reduce nvcc warnings. --- .../klee/tests/klee_geometry_operators.cpp | 29 ++++++++++------ .../klee/tests/klee_geometry_operators_io.cpp | 34 +++++++++++++------ src/axom/klee/tests/klee_io.cpp | 32 ++++++++++------- src/axom/klee/tests/klee_shape.cpp | 16 +++++---- src/axom/klee/tests/klee_shape_set.cpp | 14 +++----- 5 files changed, 74 insertions(+), 51 deletions(-) diff --git a/src/axom/klee/tests/klee_geometry_operators.cpp b/src/axom/klee/tests/klee_geometry_operators.cpp index 9d7f089cbb..a2e1c007a1 100644 --- a/src/axom/klee/tests/klee_geometry_operators.cpp +++ b/src/axom/klee/tests/klee_geometry_operators.cpp @@ -19,12 +19,21 @@ #include "gtest/gtest.h" #include "gmock/gmock.h" -namespace axom -{ -namespace klee -{ -namespace -{ +namespace klee = axom::klee; +namespace numerics = axom::numerics; +namespace primal = axom::primal; +namespace test = axom::klee::test; + +using klee::CompositeOperator; +using klee::Dimensions; +using klee::GeometryOperatorVisitor; +using klee::LengthUnit; +using klee::Rotation; +using klee::Scale; +using klee::SliceOperator; +using klee::TransformableGeometryProperties; +using klee::Translation; +using klee::UnitConverter; using test::affine; using test::AlmostEqMatrix; using test::AlmostEqPoint; @@ -42,6 +51,8 @@ using ::testing::Return; using primal::Point3D; using primal::Vector3D; +namespace +{ template ColumnVector operator*(const numerics::Matrix &matrix, const ColumnVector &rhs) { @@ -50,7 +61,7 @@ ColumnVector operator*(const numerics::Matrix &matrix, const ColumnVecto throw std::logic_error("Can't multiply entities of this size"); } ColumnVector result; - matrix_vector_multiply(matrix, rhs.data(), result.data()); + numerics::matrix_vector_multiply(matrix, rhs.data(), result.data()); return result; } @@ -69,6 +80,7 @@ primal::Point affinePoint(const Point3D &point3d) } Dimensions ALL_DIMS[] = {Dimensions::Two, Dimensions::Three}; +} // namespace class MockVisitor : public GeometryOperatorVisitor { @@ -401,6 +413,3 @@ TEST(Slice, accept) EXPECT_CALL(visitor, visit(Matcher(Ref(slice)))); slice.accept(visitor); } -} // namespace -} // namespace klee -} // namespace axom diff --git a/src/axom/klee/tests/klee_geometry_operators_io.cpp b/src/axom/klee/tests/klee_geometry_operators_io.cpp index 1a278dc1ee..ffe5255cc4 100644 --- a/src/axom/klee/tests/klee_geometry_operators_io.cpp +++ b/src/axom/klee/tests/klee_geometry_operators_io.cpp @@ -19,14 +19,30 @@ #include #include -namespace axom -{ -namespace klee -{ -namespace internal -{ namespace { +namespace inlet = axom::inlet; +namespace internal = axom::klee::internal; +namespace klee = axom::klee; +namespace primal = axom::primal; +namespace sidre = axom::sidre; +namespace test = axom::klee::test; + +using axom::Path; +using internal::GeometryOperatorData; +using internal::NamedOperatorMap; +using internal::NamedOperatorMapData; +using klee::CompositeOperator; +using klee::Dimensions; +using klee::GeometryOperator; +using klee::KleeError; +using klee::LengthUnit; +using klee::Rotation; +using klee::Scale; +using klee::SliceOperator; +using klee::TransformableGeometryProperties; +using klee::Translation; +using klee::UnitConverter; using primal::Point3D; using primal::Vector3D; using test::AlmostEqMatrix; @@ -189,6 +205,7 @@ SliceOperator make_slice(Point3D origin, { return SliceOperator {origin, normal, up, startProperties}; } +} // namespace TEST(GeometryOperatorsIO, readMultipleOperatorsIncluded) { @@ -854,11 +871,6 @@ TEST(GeometryOperatorsIO, readNamedOperators_ref) EXPECT_THAT(referencedTranslation.getOffset(), AlmostEqVector(Vector3D {10, 20, 0})); } -} // namespace -} // namespace internal -} // namespace klee -} // namespace axom - int main(int argc, char *argv[]) { ::testing::InitGoogleTest(&argc, argv); diff --git a/src/axom/klee/tests/klee_io.cpp b/src/axom/klee/tests/klee_io.cpp index 10af4d0017..6efe350d39 100644 --- a/src/axom/klee/tests/klee_io.cpp +++ b/src/axom/klee/tests/klee_io.cpp @@ -17,23 +17,34 @@ #include #include -namespace axom -{ -namespace klee -{ -namespace -{ +namespace klee = axom::klee; +namespace inlet = axom::inlet; +namespace test = axom::klee::test; +namespace primal = axom::primal; + +using klee::CompositeOperator; +using klee::Dimensions; +using klee::KleeError; +using klee::LengthUnit; +using klee::Rotation; +using klee::ShapeSet; +using klee::SliceOperator; +using klee::TransformableGeometryProperties; +using klee::Translation; using primal::Vector3D; using test::AlmostEqVector; using ::testing::Contains; using ::testing::HasSubstr; using ::testing::Truly; +namespace +{ ShapeSet readShapeSetFromString(const std::string &input) { std::istringstream istream(input); - return readShapeSet(istream); + return klee::readShapeSet(istream); } +} // namespace TEST(IOTest, readShapeSet_noShapes) { @@ -271,7 +282,7 @@ TEST(IOTest, readShapeSet_file) fout << fileContents; fout.close(); - auto shapeSet = readShapeSet(fileName); + auto shapeSet = klee::readShapeSet(fileName); EXPECT_EQ(1u, shapeSet.getShapes().size()); EXPECT_EQ("testFile.yaml", shapeSet.getPath()); } @@ -652,11 +663,6 @@ TEST(IOTest, readShapeSet_namedGeometryOperators) ASSERT_NE(translation, nullptr); EXPECT_THAT(translation->getOffset(), AlmostEqVector(Vector3D {10, 20, 0})); } - -} // namespace -} // namespace klee -} // namespace axom - int main(int argc, char *argv[]) { ::testing::InitGoogleTest(&argc, argv); diff --git a/src/axom/klee/tests/klee_shape.cpp b/src/axom/klee/tests/klee_shape.cpp index ba57d10aa1..b4bb9159fc 100644 --- a/src/axom/klee/tests/klee_shape.cpp +++ b/src/axom/klee/tests/klee_shape.cpp @@ -10,10 +10,14 @@ #include "gtest/gtest.h" -namespace axom -{ -namespace klee -{ +namespace klee = axom::klee; + +using klee::Dimensions; +using klee::Geometry; +using klee::LengthUnit; +using klee::Shape; +using klee::TransformableGeometryProperties; + namespace { Geometry createTestGeometry() @@ -26,6 +30,7 @@ Geometry createTestGeometry() "test path", nullptr}; } +} // namespace TEST(ShapeTest, replaces_no_lists_given) { @@ -54,6 +59,3 @@ TEST(ShapeTest, both_replacement_lists_given) EXPECT_THROW(Shape("name", "material", {"replaced"}, {"not replaced"}, createTestGeometry()), std::logic_error); } -} // namespace -} // namespace klee -} // namespace axom diff --git a/src/axom/klee/tests/klee_shape_set.cpp b/src/axom/klee/tests/klee_shape_set.cpp index f54098e288..1f7cf43b8b 100644 --- a/src/axom/klee/tests/klee_shape_set.cpp +++ b/src/axom/klee/tests/klee_shape_set.cpp @@ -10,12 +10,10 @@ #include -namespace axom -{ -namespace klee -{ -namespace -{ +namespace klee = axom::klee; + +using klee::Dimensions; +using klee::ShapeSet; TEST(ShapeSetTest, dimensions_getAndSet) { @@ -38,7 +36,3 @@ TEST(ShapeSetTest, dimensions_getAndSet) EXPECT_EQ(Dimensions::Three, shapeSet.getDimensions()); } } - -} // namespace -} // namespace klee -} // namespace axom From 1030012f0eb36ca5d7e118c4e5dea71ec7a9b1d0 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 22 Apr 2026 15:52:47 -0700 Subject: [PATCH 115/986] Change namespace in a primal test --- src/axom/primal/tests/primal_cone.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/axom/primal/tests/primal_cone.cpp b/src/axom/primal/tests/primal_cone.cpp index efeae67d94..06a78734e0 100644 --- a/src/axom/primal/tests/primal_cone.cpp +++ b/src/axom/primal/tests/primal_cone.cpp @@ -29,6 +29,7 @@ double cone_volume(double baseRad, double topRad, double len) { return M_PI / 3 * len * (baseRad * baseRad + baseRad * topRad + topRad * topRad); } +} /* end anonymous namespace */ //------------------------------------------------------------------------------ TEST(primal_cone, default_constructor) @@ -111,8 +112,6 @@ TEST(primal_cone, assignment_operator) EXPECT_EQ(coneA.volume(), coneB.volume()); } -} /* end anonymous namespace */ - //------------------------------------------------------------------------------ int main(int argc, char* argv[]) { From 4b6e9e61eaf1a390b94c6f2c758ad4e113b82eff Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 22 Apr 2026 16:08:13 -0700 Subject: [PATCH 116/986] Reduce nvcc warnings --- .../slam/examples/lulesh2.0.3/lulesh-util.cpp | 16 ++--- .../tests/slam_tinyHydro_unitTests.cpp | 68 +++++++------------ 2 files changed, 32 insertions(+), 52 deletions(-) diff --git a/src/axom/slam/examples/lulesh2.0.3/lulesh-util.cpp b/src/axom/slam/examples/lulesh2.0.3/lulesh-util.cpp index 2df988be49..3a98414bd0 100644 --- a/src/axom/slam/examples/lulesh2.0.3/lulesh-util.cpp +++ b/src/axom/slam/examples/lulesh2.0.3/lulesh-util.cpp @@ -298,15 +298,12 @@ namespace slamLulesh { << " actual energy at origin was " << locDom.e(ElemId) << ". Difference was " << std::fabs(resultCheckMap[gEdge].second - locDom.e(ElemId) ) ); - double diff = std::fabs(resultCheckMap[gEdge].second - locDom.e(ElemId) ); - double maxFabs = std::max( std::fabs(resultCheckMap[gEdge].second), std::fabs(locDom.e(ElemId) ) ); - double relMaxFabs = 1.0e-6 * maxFabs; - double relMaxFabsWithAbsolute = relMaxFabs + 1.0e-8; - - AXOM_UNUSED_VAR( diff); - AXOM_UNUSED_VAR( maxFabs); - AXOM_UNUSED_VAR( relMaxFabs); - AXOM_UNUSED_VAR( relMaxFabsWithAbsolute); +#ifdef AXOM_DEBUG + const double diff = std::fabs(resultCheckMap[gEdge].second - locDom.e(ElemId)); + const double maxFabs = + std::max(std::fabs(resultCheckMap[gEdge].second), std::fabs(locDom.e(ElemId))); + const double relMaxFabs = 1.0e-6 * maxFabs; + const double relMaxFabsWithAbsolute = relMaxFabs + 1.0e-8; SLIC_DEBUG("** comparing " << resultCheckMap[gEdge].second << " with " << locDom.e(ElemId) << "\n\tfabs difference: " << diff @@ -316,6 +313,7 @@ namespace slamLulesh { << "\n\tdiff of last two: " << relMaxFabsWithAbsolute - diff << "\n\tNearly equal: " << ( diff <= relMaxFabsWithAbsolute ? "TRUE" : "FALSE" ) ); +#endif } return; diff --git a/src/axom/slam/examples/tinyHydro/tests/slam_tinyHydro_unitTests.cpp b/src/axom/slam/examples/tinyHydro/tests/slam_tinyHydro_unitTests.cpp index a403326a64..da704e532b 100644 --- a/src/axom/slam/examples/tinyHydro/tests/slam_tinyHydro_unitTests.cpp +++ b/src/axom/slam/examples/tinyHydro/tests/slam_tinyHydro_unitTests.cpp @@ -69,7 +69,6 @@ TEST(slam_tinyHydro,test_02_density_with_prescribed_velocity) s.addPart(&p); Part* pp = s.getPart(0); - AXOM_UNUSED_VAR(pp); SLIC_INFO("**making hydro"); Hydro h(&s); @@ -95,11 +94,10 @@ TEST(slam_tinyHydro,test_02_density_with_prescribed_velocity) rhoTheory = rhoTheory * rhoTheory * rho0; double tol = 1.0e-10; - AXOM_UNUSED_VAR(tol); - SLIC_ASSERT_MSG( + EXPECT_TRUE( std::fabs(pp->rho(0) - rhoTheory) < tol && std::fabs(pp->rho(n - 1) - rhoTheory) < tol - , "FAIL -- densities are not correct\n"); + ) << "FAIL -- densities are not correct"; SLIC_INFO(" *** PASS *** " ); } @@ -180,18 +178,16 @@ TEST(slam_tinyHydro,test_03_gradAndForce) double tol = 1e-12; - AXOM_UNUSED_VAR(tol); VectorXY f12 = h.getForce(12); VectorXY f13 = h.getForce(13); - SLIC_ASSERT_MSG( std::fabs(f12.x - f13.x) < tol - && std::fabs(f12.y) < tol - && std::fabs(f13.y) < tol, - "force calculation FAILS --" - << " f12: " << f12 - << ", f13: " << f13 - << ", diff in x: " << std::fabs(f12.x - f13.x) - << ", tolerance: " << tol - ); + const bool symmetricForce = std::fabs(f12.x - f13.x) < tol + && std::fabs(f12.y) < tol + && std::fabs(f13.y) < tol; + EXPECT_TRUE(symmetricForce) << "force calculation FAILS --" + << " f12=(" << f12.x << ", " << f12.y << ")" + << ", f13=(" << f13.x << ", " << f13.y << ")" + << ", diff in x: " << std::fabs(f12.x - f13.x) + << ", tolerance: " << tol; SLIC_INFO("**Testing acceleration:"); @@ -210,20 +206,13 @@ TEST(slam_tinyHydro,test_03_gradAndForce) VectorXY u0 = s.u(0); VectorXY u11 = s.u(11); VectorXY u12 = s.u(12); - SLIC_ASSERT_MSG( std::fabs(u0.x - u12.x) < tol - && std::fabs(u11.y) < tol - && std::fabs(u12.y) < tol, - "acceleration/velocity calculation FAILS"); + const bool symmetricVelocity = std::fabs(u0.x - u12.x) < tol + && std::fabs(u11.y) < tol + && std::fabs(u12.y) < tol; + EXPECT_TRUE(symmetricVelocity) << "acceleration/velocity calculation FAILS"; SLIC_INFO(" *** PASS ***"); - - // Deal with unused variables - AXOM_UNUSED_VAR(f12); - AXOM_UNUSED_VAR(f13); - AXOM_UNUSED_VAR(u0); - AXOM_UNUSED_VAR(u11); - AXOM_UNUSED_VAR(u12); } @@ -301,14 +290,13 @@ TEST(slam_tinyHydro,test_04_BC) double tol = 1e-21; - AXOM_UNUSED_VAR(tol); - SLIC_ASSERT_MSG( std::fabs(u0.y) < tol + EXPECT_TRUE( std::fabs(u0.y) < tol && std::fabs(u11.x) > tol && std::fabs(u21.x + 1.0) < tol && std::fabs(u120.x + 1.0) < tol && std::fabs(u120.y ) < tol - , "BC test FAILS "); + ) << "BC test FAILS"; SLIC_INFO(" *** PASS *** " ); } @@ -403,13 +391,11 @@ TEST(slam_tinyHydro,test_05_newDT_Noh) SLIC_INFO("\tnewDT = " << dt ); double tol = 1.0e-16; - AXOM_UNUSED_VAR( tol); - double expDT = 0.1 * h.cfl; - AXOM_UNUSED_VAR( expDT); - SLIC_ASSERT_MSG( std::fabs(dt - expDT) < tol, - " newDT calculation FAILS -- expected dt = " << expDT << " but got " << dt << " instead, leaving " << dt - expDT); + EXPECT_TRUE( std::fabs(dt - expDT) < tol) + << "newDT calculation FAILS -- expected dt = " << expDT + << " but got " << dt << " instead, leaving " << dt - expDT; SLIC_INFO(" *** PASS *** " ); } @@ -477,13 +463,11 @@ TEST(slam_tinyHydro,test_05_newDT_Sedov) double cfl = 0.7; double cs = sqrt(10 * E / (4 * zonemass * 9)); double theoryDT = cfl * L / cs; - AXOM_UNUSED_VAR( theoryDT); - double tol = 1.0e-16; - AXOM_UNUSED_VAR( tol); - SLIC_ASSERT_MSG( std::fabs(dt - theoryDT) < tol, - " newDT calculation FAILS -- expected dt = " << theoryDT << " but code got " << dt << ". Diff:" << dt - theoryDT); + EXPECT_TRUE( std::fabs(dt - theoryDT) < tol) + << "newDT calculation FAILS -- expected dt = " << theoryDT + << " but code got " << dt << ". Diff:" << dt - theoryDT; SLIC_INFO(" *** PASS ***"); } @@ -559,14 +543,12 @@ TEST(slam_tinyHydro,test_06_PdV_work) SLIC_INFO("**done stepping. "); double tol = 1.0e-6; - AXOM_UNUSED_VAR(tol); - double theoryRho = 1.0 / (1.0 + h.time * u0); double rhoCode = h.getState()->getPart(0)->rho(0); - AXOM_UNUSED_VAR(rhoCode); - SLIC_ASSERT_MSG( std::fabs(theoryRho - rhoCode) < tol, - "density calculation FAILS -- rhoCode = " << rhoCode << " but should be " << theoryRho ); + EXPECT_TRUE( std::fabs(theoryRho - rhoCode) < tol) + << "density calculation FAILS -- rhoCode = " << rhoCode + << " but should be " << theoryRho; double theoryE = std::pow(theoryRho / rho0, 2.0 / 3.0); // e = e0*(rho/rho0)**(gamma-1) From 1a084c15b130f09cbfe263294d371c52ca338df9 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 22 Apr 2026 18:10:41 -0700 Subject: [PATCH 117/986] Fix some new warnings with RelWithDebInfo on nvcc. --- src/axom/sidre/core/View.hpp | 4 ++-- src/axom/slam/examples/ShockTube.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/axom/sidre/core/View.hpp b/src/axom/sidre/core/View.hpp index 7509501581..1e1893b060 100644 --- a/src/axom/sidre/core/View.hpp +++ b/src/axom/sidre/core/View.hpp @@ -613,7 +613,7 @@ class View #if defined(AXOM_DEBUG) if(m_state == TUPLE) { - DataTypeId arg_id = detail::SidreTT::id; + [[maybe_unused]] DataTypeId arg_id = detail::SidreTT::id; SLIC_CHECK_MSG(arg_id == m_node.dtype().id(), SIDRE_VIEW_LOG_PREPEND << "You are setting a scalar value which has changed " << " the underlying data type. " @@ -657,7 +657,7 @@ class View #if defined(AXOM_DEBUG) if(m_state == TUPLE) { - DataTypeId arg_id = detail::SidreTT::id; + [[maybe_unused]] DataTypeId arg_id = detail::SidreTT::id; SLIC_CHECK_MSG(arg_id == m_node.dtype().id(), SIDRE_VIEW_LOG_PREPEND << "You are setting a scalar value which has changed " << " the underlying data type. " diff --git a/src/axom/slam/examples/ShockTube.cpp b/src/axom/slam/examples/ShockTube.cpp index c707e750b5..b96cbdf3a3 100644 --- a/src/axom/slam/examples/ShockTube.cpp +++ b/src/axom/slam/examples/ShockTube.cpp @@ -73,7 +73,7 @@ const double INIT_P_RATIO = 0.5; const double INIT_D_RATIO = 0.5; #ifdef AXOM_DEBUG -const bool verboseOutput = false; +[[maybe_unused]] const bool verboseOutput = false; #endif /** From 95c33a2267c909dde71c5b5c8ca5b1152d11a4bc Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 23 Apr 2026 10:31:18 -0700 Subject: [PATCH 118/986] Remove deprecated this capture. --- src/axom/quest/detail/clipping/TetMeshClipper.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/axom/quest/detail/clipping/TetMeshClipper.cpp b/src/axom/quest/detail/clipping/TetMeshClipper.cpp index 3199d83fd1..1b48d3b25a 100644 --- a/src/axom/quest/detail/clipping/TetMeshClipper.cpp +++ b/src/axom/quest/detail/clipping/TetMeshClipper.cpp @@ -792,12 +792,12 @@ void TetMeshClipper::transformCoordset() axom::ArrayView xV(coordset.fetch_existing("values/x").as_double_ptr(), count); axom::ArrayView yV(coordset.fetch_existing("values/y").as_double_ptr(), count); axom::ArrayView zV(coordset.fetch_existing("values/z").as_double_ptr(), count); - axom::for_all( - count, - AXOM_LAMBDA(axom::IndexType i) { - transformer.transform(xV[i], yV[i], zV[i]); - m_tetMeshBb.addPoint(Point3DType {xV[i], yV[i], zV[i]}); - }); + const axom::IndexType numPts = static_cast(count); + for(axom::IndexType i = 0; i < numPts; ++i) + { + transformer.transform(xV[i], yV[i], zV[i]); + m_tetMeshBb.addPoint(Point3DType {xV[i], yV[i], zV[i]}); + } m_tetMesh.fetch_existing("topologies") .fetch_existing(m_topoName) .fetch_existing("coordset") From 34a26fee00ff7d62e63712dad99582c13abfa4fc Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 23 Apr 2026 15:34:09 -0700 Subject: [PATCH 119/986] make style --- src/axom/core/tests/core_flatmap.hpp | 4 +--- .../quest/tests/quest_sampling_shaper.cpp | 5 ++++- src/axom/sidre/spio/IOManager.cpp | 21 ++++++------------- src/axom/sina/tests/sina_AdiakWriter.cpp | 2 +- src/axom/sina/tests/sina_ConduitUtil.cpp | 2 +- src/axom/sina/tests/sina_Curve.cpp | 4 ++-- src/axom/sina/tests/sina_CurveSet.cpp | 4 ++-- src/axom/sina/tests/sina_DataHolder.cpp | 6 +++--- src/axom/sina/tests/sina_Datum.cpp | 2 +- src/axom/sina/tests/sina_Document.cpp | 18 ++++++++-------- src/axom/sina/tests/sina_Record.cpp | 12 +++++------ src/axom/sina/tests/sina_Run.cpp | 2 +- 12 files changed, 37 insertions(+), 45 deletions(-) diff --git a/src/axom/core/tests/core_flatmap.hpp b/src/axom/core/tests/core_flatmap.hpp index 5466b5fb5e..61d2471665 100644 --- a/src/axom/core/tests/core_flatmap.hpp +++ b/src/axom/core/tests/core_flatmap.hpp @@ -125,9 +125,7 @@ class core_flatmap : public ::testing::Test using MyTypes = ::testing::Types, axom::FlatMap, axom::FlatMap, - axom::FlatMap>; + axom::FlatMap>; TYPED_TEST_SUITE(core_flatmap, MyTypes); diff --git a/src/axom/quest/tests/quest_sampling_shaper.cpp b/src/axom/quest/tests/quest_sampling_shaper.cpp index c6c6a9ded4..474a90e0d1 100644 --- a/src/axom/quest/tests/quest_sampling_shaper.cpp +++ b/src/axom/quest/tests/quest_sampling_shaper.cpp @@ -87,7 +87,10 @@ struct Projector32 struct Projector23 { - AXOM_HOST_DEVICE Point3D operator()(const Point2D& pt) const { return Point3D {pt[0], pt[1], 0.}; } + AXOM_HOST_DEVICE Point3D operator()(const Point2D& pt) const + { + return Point3D {pt[0], pt[1], 0.}; + } }; struct ScaleProjector22 diff --git a/src/axom/sidre/spio/IOManager.cpp b/src/axom/sidre/spio/IOManager.cpp index 60282735b1..e67819d7d3 100644 --- a/src/axom/sidre/spio/IOManager.cpp +++ b/src/axom/sidre/spio/IOManager.cpp @@ -67,27 +67,18 @@ std::string broadcastString(const std::string& str, MPI_Comm comm, int rank) #ifdef AXOM_USE_HDF5 inline void checkHDF5Status(herr_t status) { -#ifdef AXOM_DEBUG + #ifdef AXOM_DEBUG SLIC_ASSERT(status >= 0); -#else + #else AXOM_UNUSED_VAR(status); -#endif + #endif } -inline void closeHDF5Group(hid_t group_id) -{ - checkHDF5Status(H5Gclose(group_id)); -} +inline void closeHDF5Group(hid_t group_id) { checkHDF5Status(H5Gclose(group_id)); } -inline void flushHDF5File(hid_t file_id) -{ - checkHDF5Status(H5Fflush(file_id, H5F_SCOPE_LOCAL)); -} +inline void flushHDF5File(hid_t file_id) { checkHDF5Status(H5Fflush(file_id, H5F_SCOPE_LOCAL)); } -inline void closeHDF5File(hid_t file_id) -{ - checkHDF5Status(H5Fclose(file_id)); -} +inline void closeHDF5File(hid_t file_id) { checkHDF5Status(H5Fclose(file_id)); } #endif } // end anonymous namespace diff --git a/src/axom/sina/tests/sina_AdiakWriter.cpp b/src/axom/sina/tests/sina_AdiakWriter.cpp index 7dc991304b..c43fd32b7a 100644 --- a/src/axom/sina/tests/sina_AdiakWriter.cpp +++ b/src/axom/sina/tests/sina_AdiakWriter.cpp @@ -27,10 +27,10 @@ extern "C" { namespace sina = axom::sina; +using sina::adiakSinaCallback; using sina::ID; using sina::IDType; using sina::Record; -using sina::adiakSinaCallback; using ::testing::DoubleEq; using ::testing::ElementsAre; diff --git a/src/axom/sina/tests/sina_ConduitUtil.cpp b/src/axom/sina/tests/sina_ConduitUtil.cpp index 1ffa04f8b7..7635ebce74 100644 --- a/src/axom/sina/tests/sina_ConduitUtil.cpp +++ b/src/axom/sina/tests/sina_ConduitUtil.cpp @@ -14,13 +14,13 @@ namespace sina = axom::sina; +using axom::sina::testing::parseJsonValue; using sina::getOptionalString; using sina::getRequiredDouble; using sina::getRequiredField; using sina::getRequiredString; using sina::toDoubleVector; using sina::toStringVector; -using axom::sina::testing::parseJsonValue; using ::testing::ContainerEq; using ::testing::DoubleEq; diff --git a/src/axom/sina/tests/sina_Curve.cpp b/src/axom/sina/tests/sina_Curve.cpp index f272bd365c..ba15ec44f0 100644 --- a/src/axom/sina/tests/sina_Curve.cpp +++ b/src/axom/sina/tests/sina_Curve.cpp @@ -13,10 +13,10 @@ namespace sina = axom::sina; -using sina::Curve; -using sina::addStringsToNode; using axom::sina::testing::MatchesJsonMatcher; using axom::sina::testing::parseJsonValue; +using sina::addStringsToNode; +using sina::Curve; using ::testing::ContainerEq; using ::testing::ElementsAre; diff --git a/src/axom/sina/tests/sina_CurveSet.cpp b/src/axom/sina/tests/sina_CurveSet.cpp index b19b8d294c..74c7efba91 100644 --- a/src/axom/sina/tests/sina_CurveSet.cpp +++ b/src/axom/sina/tests/sina_CurveSet.cpp @@ -47,10 +47,10 @@ bool operator==(Curve const &lhs, Curve const &rhs) namespace sina = axom::sina; -using sina::Curve; -using sina::CurveSet; using axom::sina::testing::MatchesJsonMatcher; using axom::sina::testing::parseJsonValue; +using sina::Curve; +using sina::CurveSet; using ::testing::ContainerEq; using ::testing::ElementsAre; diff --git a/src/axom/sina/tests/sina_DataHolder.cpp b/src/axom/sina/tests/sina_DataHolder.cpp index 3c34f3d94a..9efbbcae77 100644 --- a/src/axom/sina/tests/sina_DataHolder.cpp +++ b/src/axom/sina/tests/sina_DataHolder.cpp @@ -15,13 +15,13 @@ namespace sina = axom::sina; +using axom::sina::testing::MatchesJsonMatcher; +using axom::sina::testing::parseJsonValue; +using sina::addStringsToNode; using sina::Curve; using sina::CurveSet; using sina::DataHolder; using sina::Datum; -using sina::addStringsToNode; -using axom::sina::testing::MatchesJsonMatcher; -using axom::sina::testing::parseJsonValue; using ::testing::Contains; using ::testing::DoubleEq; diff --git a/src/axom/sina/tests/sina_Datum.cpp b/src/axom/sina/tests/sina_Datum.cpp index 88699bb1cb..30c8ff26d8 100644 --- a/src/axom/sina/tests/sina_Datum.cpp +++ b/src/axom/sina/tests/sina_Datum.cpp @@ -16,9 +16,9 @@ namespace sina = axom::sina; +using sina::addStringsToNode; using sina::Datum; using sina::ValueType; -using sina::addStringsToNode; using ::testing::DoubleEq; using ::testing::ElementsAre; diff --git a/src/axom/sina/tests/sina_Document.cpp b/src/axom/sina/tests/sina_Document.cpp index 1c0ea6dc01..af336ae5d5 100644 --- a/src/axom/sina/tests/sina_Document.cpp +++ b/src/axom/sina/tests/sina_Document.cpp @@ -34,26 +34,26 @@ namespace sina = axom::sina; +using axom::sina::testing::parseJsonValue; +using axom::sina::testing::TEST_RECORD_VALUE_KEY; +using axom::sina::testing::TestRecord; +using sina::appendDocumentToHDF5; +using sina::appendDocumentToJson; +using sina::createRecordLoaderWithAllKnownTypes; using sina::Document; using sina::File; +using sina::getRequiredField; +using sina::getRequiredString; using sina::ID; using sina::IDType; +using sina::loadDocument; using sina::Protocol; using sina::Record; using sina::RecordLoader; using sina::Relationship; -using sina::appendDocumentToHDF5; -using sina::appendDocumentToJson; -using sina::createRecordLoaderWithAllKnownTypes; -using sina::getRequiredField; -using sina::getRequiredString; -using sina::loadDocument; using sina::restoreSlashes; using sina::saveDocument; using sina::validateAppendDocument; -using axom::sina::testing::TEST_RECORD_VALUE_KEY; -using axom::sina::testing::TestRecord; -using axom::sina::testing::parseJsonValue; using ::testing::ElementsAre; using ::testing::HasSubstr; diff --git a/src/axom/sina/tests/sina_Record.cpp b/src/axom/sina/tests/sina_Record.cpp index 9f6a309e5b..217f1baa8c 100644 --- a/src/axom/sina/tests/sina_Record.cpp +++ b/src/axom/sina/tests/sina_Record.cpp @@ -18,6 +18,12 @@ namespace sina = axom::sina; +using axom::sina::testing::MatchesJsonMatcher; +using axom::sina::testing::parseJsonValue; +using axom::sina::testing::TEST_RECORD_VALUE_KEY; +using axom::sina::testing::TestRecord; +using sina::addStringsToNode; +using sina::createRecordLoaderWithAllKnownTypes; using sina::Curve; using sina::CurveSet; using sina::Datum; @@ -26,13 +32,7 @@ using sina::ID; using sina::IDType; using sina::Record; using sina::RecordLoader; -using sina::addStringsToNode; -using sina::createRecordLoaderWithAllKnownTypes; using sina::setDefaultCurveOrder; -using axom::sina::testing::MatchesJsonMatcher; -using axom::sina::testing::TEST_RECORD_VALUE_KEY; -using axom::sina::testing::TestRecord; -using axom::sina::testing::parseJsonValue; using ::testing::Contains; using ::testing::DoubleEq; diff --git a/src/axom/sina/tests/sina_Run.cpp b/src/axom/sina/tests/sina_Run.cpp index 66f02ada47..5e38fab285 100644 --- a/src/axom/sina/tests/sina_Run.cpp +++ b/src/axom/sina/tests/sina_Run.cpp @@ -13,10 +13,10 @@ namespace sina = axom::sina; +using sina::addRunLoader; using sina::ID; using sina::IDType; using sina::RecordLoader; -using sina::addRunLoader; using ::testing::HasSubstr; From 5851cad71258cce9cf5bd92c6c7310fce245e704 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Fri, 24 Apr 2026 19:12:24 -0700 Subject: [PATCH 120/986] Added scaling transforms that can take a center --- src/axom/core/numerics/transforms.hpp | 79 ++++++++++++++++++++++----- 1 file changed, 64 insertions(+), 15 deletions(-) diff --git a/src/axom/core/numerics/transforms.hpp b/src/axom/core/numerics/transforms.hpp index e30e2b8b53..95da0a4d7e 100644 --- a/src/axom/core/numerics/transforms.hpp +++ b/src/axom/core/numerics/transforms.hpp @@ -8,6 +8,7 @@ #include "axom/config.hpp" #include "axom/core/numerics/Matrix.hpp" +#include "axom/core/ArrayView.hpp" #include #include @@ -133,6 +134,33 @@ Matrix axisRotation(double angleRad, double x, double y, double z) return M; } +/*! + * \brief Return translation matrix. + * + * \param tx The translation in x. + * \param ty The translation in y. + * + * \return A Matrix containing the translation transform. + */ +template +Matrix translate(T tx, T ty) +{ + Matrix M = Matrix::identity(3); + M(0, 2) = tx; + M(1, 2) = ty; + return M; +} + +template +Matrix translate(T tx, T ty, T tz) +{ + Matrix M = Matrix::identity(4); + M(0, 3) = tx; + M(1, 3) = ty; + M(2, 3) = tz; + return M; +} + /*! * \brief Return scaling matrix. * @@ -194,30 +222,51 @@ Matrix scale(T sx, T sy, int ndims = 3) } /*! - * \brief Return translation matrix. + * \brief Return scaling matrix relative to a center point. * - * \param tx The translation in x. - * \param ty The translation in y. + * \param sx The scaling value in x. + * \param sy The scaling value in y. + * \param center The center point. * - * \return A Matrix containing the translation transform. + * \return A 3x3 Matrix containing the scaling transform. */ template -Matrix translate(T tx, T ty) +Matrix scale(T sx, T sy, const axom::ArrayView ¢er) { - Matrix M = Matrix::identity(3); - M(0, 2) = tx; - M(1, 2) = ty; - return M; + assert(center.size() == 2); + const auto T0 = translate(-center[0], -center[1]); + const auto S = scale(sx, sy, 3); + const auto T1 = translate(center[0], center[1]); + + Matrix TS, result; + matrix_multiply(T0, S, TS); + matrix_multiply(TS, T1, result); + + return result; } +/*! + * \brief Return scaling matrix relative to a center point. + * + * \param sx The scaling value in x. + * \param sy The scaling value in y. + * \param center The center point. + * + * \return A 4x4 Matrix containing the scaling transform. + */ template -Matrix translate(T tx, T ty, T tz) +Matrix scale(T sx, T sy, T sz, const axom::ArrayView ¢er) { - Matrix M = Matrix::identity(4); - M(0, 3) = tx; - M(1, 3) = ty; - M(2, 3) = tz; - return M; + assert(center.size() == 3); + const auto T0 = translate(-center[0], -center[1], -center[2]); + const auto S = scale(sx, sy, sz, 4); + const auto T1 = translate(center[0], center[1], center[2]); + + Matrix TS, result; + matrix_multiply(T0, S, TS); + matrix_multiply(TS, T1, result); + + return result; } } // end namespace transforms From b40db0786f61c5b0baa693e10a2f42984c6e96b4 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Fri, 24 Apr 2026 19:12:48 -0700 Subject: [PATCH 121/986] Hook up optional scaling center in Klee input --- src/axom/klee/GeometryOperators.cpp | 29 ++++++++++++++---------- src/axom/klee/GeometryOperators.hpp | 25 ++++++++++++++++++++ src/axom/klee/io/GeometryOperatorsIO.cpp | 10 ++++++-- 3 files changed, 50 insertions(+), 14 deletions(-) diff --git a/src/axom/klee/GeometryOperators.cpp b/src/axom/klee/GeometryOperators.cpp index 980205c96e..422f35d6eb 100644 --- a/src/axom/klee/GeometryOperators.cpp +++ b/src/axom/klee/GeometryOperators.cpp @@ -5,6 +5,7 @@ // SPDX-License-Identifier: (BSD-3-Clause) #include "axom/core/numerics/matvecops.hpp" +#include "axom/core/numerics/transforms.hpp" #include "axom/klee/GeometryOperators.hpp" #include "axom/klee/Units.hpp" @@ -48,12 +49,7 @@ Translation::Translation(const primal::Vector3D &offset, numerics::Matrix Translation::toMatrix() const { - auto transformation = numerics::Matrix::identity(4); - for(int i = 0; i < 3; ++i) - { - transformation(i, 3) = m_offset[i]; - } - return transformation; + return axom::numerics::transforms::translate(m_offset[0], m_offset[1], m_offset[2]); } void Translation::accept(GeometryOperatorVisitor &visitor) const { visitor.visit(*this); } @@ -119,16 +115,25 @@ Scale::Scale(double xFactor, , m_xFactor {xFactor} , m_yFactor {yFactor} , m_zFactor {zFactor} + , m_center {0., 0., 0.} +{ } + +Scale::Scale(double xFactor, + double yFactor, + double zFactor, + const primal::Point3D ¢er, + const TransformableGeometryProperties &startProperties) + : MatrixOperator {startProperties} + , m_xFactor {xFactor} + , m_yFactor {yFactor} + , m_zFactor {zFactor} + , m_center {center} { } numerics::Matrix Scale::toMatrix() const { - auto transformation = numerics::Matrix::zeros(4, 4); - transformation(0, 0) = m_xFactor; - transformation(1, 1) = m_yFactor; - transformation(2, 2) = m_zFactor; - transformation(3, 3) = 1; - return transformation; + axom::ArrayView centerView(const_cast(m_center.data()), 3); + return axom::numerics::transforms::scale(m_xFactor, m_yFactor, m_zFactor, centerView); } void Scale::accept(GeometryOperatorVisitor &visitor) const { visitor.visit(*this); } diff --git a/src/axom/klee/GeometryOperators.hpp b/src/axom/klee/GeometryOperators.hpp index 613fe023d4..3f26a96611 100644 --- a/src/axom/klee/GeometryOperators.hpp +++ b/src/axom/klee/GeometryOperators.hpp @@ -217,6 +217,22 @@ class Scale : public MatrixOperator double zFactor, const TransformableGeometryProperties &startProperties); + /** + * Create a new Scale operator. + * + * \param xFactor the amount by which to scale in the x direction + * \param yFactor the amount by which to scale in the y direction + * \param zFactor the amount by which to scale in the z direction + * \param center The center relative to which the scaling is performed. + * \param startProperties the initial properties, as in the parent class. + * If the number of dimensions is 2, zFactor should be 1.0, but this is not enforced. + */ + Scale(double xFactor, + double yFactor, + double zFactor, + const primal::Point3D ¢er, + const TransformableGeometryProperties &startProperties); + /** * Get the scale factor in the x direction. * @@ -238,6 +254,14 @@ class Scale : public MatrixOperator */ double getZFactor() const { return m_zFactor; } + /** + * Get the scale factor in the z direction. + * + * \return the z scale factor + */ + primal::Point3D &getCenter() { return m_center; } + const primal::Point3D &getCenter() const { return m_center; } + numerics::Matrix toMatrix() const override; void accept(GeometryOperatorVisitor &visitor) const override; @@ -246,6 +270,7 @@ class Scale : public MatrixOperator double m_xFactor; double m_yFactor; double m_zFactor; + primal::Point3D m_center; }; /// An operator for converting units diff --git a/src/axom/klee/io/GeometryOperatorsIO.cpp b/src/axom/klee/io/GeometryOperatorsIO.cpp index ca518f4955..dca9ab80e7 100644 --- a/src/axom/klee/io/GeometryOperatorsIO.cpp +++ b/src/axom/klee/io/GeometryOperatorsIO.cpp @@ -360,7 +360,7 @@ OpPtr parseSlice(const inlet::Container &opContainer, OpPtr parseScale(const inlet::Container &opContainer, const TransformableGeometryProperties &startProperties) { - verifyObjectFields(opContainer, "scale", FieldSet {}, FieldSet {}); + verifyObjectFields(opContainer, "scale", FieldSet {}, FieldSet {"center"}); auto factors = opContainer["scale"].get>(); if(factors.size() == 1) { @@ -371,7 +371,13 @@ OpPtr parseScale(const inlet::Container &opContainer, { factors.emplace_back(1.0); } - return std::make_shared(factors[0], factors[1], factors[2], startProperties); + Point3D center{0., 0., 0.}; + if(opContainer.contains("center")) + { + center = toPoint(opContainer, "center", startProperties.dimensions, Point3D {0, 0, 0}); + } + + return std::make_shared(factors[0], factors[1], factors[2], center, startProperties); } /** From a02a65c69085dab87264bdadd0d02bb8efadc1a1 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Fri, 24 Apr 2026 19:15:07 -0700 Subject: [PATCH 122/986] Moved AffineMatrixVisitor to its own files. Moved duplicated matrix calculations into Geometry::getTransform --- src/axom/klee/AffineMatrixVisitor.cpp | 48 +++++++++++++ src/axom/klee/AffineMatrixVisitor.hpp | 41 +++++++++++ src/axom/klee/CMakeLists.txt | 2 + src/axom/klee/Geometry.cpp | 42 ++++++++++++ src/axom/klee/Geometry.hpp | 8 +++ src/axom/quest/DiscreteShape.cpp | 94 +------------------------- src/axom/quest/MeshClipperStrategy.cpp | 45 +----------- src/axom/quest/MeshClipperStrategy.hpp | 7 -- 8 files changed, 143 insertions(+), 144 deletions(-) create mode 100644 src/axom/klee/AffineMatrixVisitor.cpp create mode 100644 src/axom/klee/AffineMatrixVisitor.hpp diff --git a/src/axom/klee/AffineMatrixVisitor.cpp b/src/axom/klee/AffineMatrixVisitor.cpp new file mode 100644 index 0000000000..03e1f5a809 --- /dev/null +++ b/src/axom/klee/AffineMatrixVisitor.cpp @@ -0,0 +1,48 @@ +// 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_KLEE_AFFINE_MATRIX_VISITOR_HPP_ +#define AXOM_KLEE_AFFINE_MATRIX_VISITOR_HPP_ + +namespace axom::klee +{ + +AffineMatrixVisitor::AffineMatrixVisitor() : klee::GeometryOperatorVisitor(), m_isValid(false), m_matrix(4, 4) { } + + void AffineMatrixVisitor::visit(const klee::Translation& translation) + { + m_matrix = translation.toMatrix(); + m_isValid = true; + } + void AffineMatrixVisitor::visit(const klee::Rotation& rotation) + { + m_matrix = rotation.toMatrix(); + m_isValid = true; + } + void AffineMatrixVisitor::visit(const klee::Scale& scale) + { + m_matrix = scale.toMatrix(); + m_isValid = true; + } + void AffineMatrixVisitor::visit(const klee::UnitConverter& converter) + { + m_matrix = converter.toMatrix(); + m_isValid = true; + } + + void AffineMatrixVisitor::visit(const klee::CompositeOperator&) + { + SLIC_WARNING_ROOT("CompositeOperator not supported for Shaper query"); + m_isValid = false; + } + void AffineMatrixVisitor::visit(const klee::SliceOperator&) + { + SLIC_WARNING_ROOT("SliceOperator not yet supported for Shaper query"); + m_isValid = false; + } + +} // end namespace axom::klee + +#endif diff --git a/src/axom/klee/AffineMatrixVisitor.hpp b/src/axom/klee/AffineMatrixVisitor.hpp new file mode 100644 index 0000000000..b3a6c0fd1b --- /dev/null +++ b/src/axom/klee/AffineMatrixVisitor.hpp @@ -0,0 +1,41 @@ +// 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_KLEE_AFFINE_MATRIX_VISITOR_HPP_ +#define AXOM_KLEE_AFFINE_MATRIX_VISITOR_HPP_ + +namespace axom::klee +{ + +/*! + * \brief Implementation of a GeometryOperatorVisitor for processing klee shape operators + * + * This class extracts the matrix form of supported operators and marks the operator as unvalid otherwise + * To use, check the \a isValid() function after visiting and then call the \a getMatrix() function. + */ +class AffineMatrixVisitor : public klee::GeometryOperatorVisitor +{ +public: + AffineMatrixVisitor(); + + void visit(const klee::Translation& translation) override; + void visit(const klee::Rotation& rotation) override; + void visit(const klee::Scale& scale) override; + void visit(const klee::UnitConverter& converter) override; + + void visit(const klee::CompositeOperator&) override; + void visit(const klee::SliceOperator&) override; + + const numerics::Matrix& getMatrix() const { return m_matrix; } + bool isValid() const { return m_isValid; } + +private: + bool m_isValid; + numerics::Matrix m_matrix; +}; + +} // end namespace axom::klee + +#endif diff --git a/src/axom/klee/CMakeLists.txt b/src/axom/klee/CMakeLists.txt index 7ffca6116c..875af45917 100644 --- a/src/axom/klee/CMakeLists.txt +++ b/src/axom/klee/CMakeLists.txt @@ -10,6 +10,7 @@ axom_component_requires(NAME Klee # Specify all headers/sources #------------------------------------------------------------------------------ set(klee_headers + AffineMatrixVisitor.hpp Dimensions.hpp Geometry.hpp GeometryOperators.hpp @@ -26,6 +27,7 @@ set(klee_internal_headers ) set(klee_sources + AffineMatrixVisitor.cpp Geometry.cpp GeometryOperators.cpp KleeError.cpp diff --git a/src/axom/klee/Geometry.cpp b/src/axom/klee/Geometry.cpp index 59e336ccf9..103ec60cd5 100644 --- a/src/axom/klee/Geometry.cpp +++ b/src/axom/klee/Geometry.cpp @@ -228,5 +228,47 @@ const std::string& Geometry::getBlueprintTopology() const return m_topology; } +numerics::Matrix Geometry::getTransform() const +{ + const auto identity4x4 = numerics::Matrix::identity(4); + numerics::Matrix transformation(identity4x4); + if(m_operator) + { + auto composite = std::dynamic_pointer_cast(m_operator); + if(composite) + { + // Concatenate the transformations + + // Why don't we multiply the matrices in CompositeOperator::addOperator()? + // Why keep the matrices factored and multiply them here repeatedly? + // Combining them would also avoid this if-else logic. BTNG + for(auto op : composite->getOperators()) + { + // Use visitor pattern to extract the affine matrix from supported operators + AffineMatrixVisitor visitor; + op->accept(visitor); + if(!visitor.isValid()) + { + continue; + } + const auto& matrix = visitor.getMatrix(); + numerics::Matrix res(identity4x4); + numerics::matrix_multiply(matrix, transformation, res); + transformation = res; + } + } + else + { + AffineMatrixVisitor visitor; + geometryOperator->accept(visitor); + if(visitor.isValid()) + { + transformation = visitor.getMatrix(); + } + } + } + return transformation; +} + } // namespace klee } // namespace axom diff --git a/src/axom/klee/Geometry.hpp b/src/axom/klee/Geometry.hpp index ec4974c579..03a1c22898 100644 --- a/src/axom/klee/Geometry.hpp +++ b/src/axom/klee/Geometry.hpp @@ -260,6 +260,14 @@ class Geometry */ std::shared_ptr const &getGeometryOperator() const { return m_operator; } + /** + * Get any operator transforms concatenated into a 4x4 matrix. If there are no + * geometry operators then the identity matrix is returned. + * + * \return A 4x4 matrix that represents the geometry transforms. + */ + numerics::Matrix getTransform() const; + /** * Get the initial transformable properties of this geometry * diff --git a/src/axom/quest/DiscreteShape.cpp b/src/axom/quest/DiscreteShape.cpp index dc49d28ffa..9bc14dcaad 100644 --- a/src/axom/quest/DiscreteShape.cpp +++ b/src/axom/quest/DiscreteShape.cpp @@ -19,60 +19,6 @@ namespace axom { namespace quest { -namespace internal -{ -/*! - * \brief Implementation of a GeometryOperatorVisitor for processing klee shape operators - * - * This class extracts the matrix form of supported operators and marks the operator as unvalid otherwise - * To use, check the \a isValid() function after visiting and then call the \a getMatrix() function. - */ -class AffineMatrixVisitor : public klee::GeometryOperatorVisitor -{ -public: - AffineMatrixVisitor() : m_matrix(4, 4) { } - - void visit(const klee::Translation& translation) override - { - m_matrix = translation.toMatrix(); - m_isValid = true; - } - void visit(const klee::Rotation& rotation) override - { - m_matrix = rotation.toMatrix(); - m_isValid = true; - } - void visit(const klee::Scale& scale) override - { - m_matrix = scale.toMatrix(); - m_isValid = true; - } - void visit(const klee::UnitConverter& converter) override - { - m_matrix = converter.toMatrix(); - m_isValid = true; - } - - void visit(const klee::CompositeOperator&) override - { - SLIC_WARNING_ROOT("CompositeOperator not supported for Shaper query"); - m_isValid = false; - } - void visit(const klee::SliceOperator&) override - { - SLIC_WARNING_ROOT("SliceOperator not yet supported for Shaper query"); - m_isValid = false; - } - - const numerics::Matrix& getMatrix() const { return m_matrix; } - bool isValid() const { return m_isValid; } - -private: - bool m_isValid {false}; - numerics::Matrix m_matrix; -}; - -} // end namespace internal // TODO: These were needed for linking - but why? They are constexpr. constexpr int DiscreteShape::DEFAULT_SAMPLES_PER_KNOT_SPAN; @@ -716,45 +662,7 @@ void DiscreteShape::applyTransforms() numerics::Matrix DiscreteShape::getTransforms() const { - const auto identity4x4 = numerics::Matrix::identity(4); - numerics::Matrix transformation(identity4x4); - auto& geometryOperator = m_shape.getGeometry().getGeometryOperator(); - if(geometryOperator) - { - auto composite = std::dynamic_pointer_cast(geometryOperator); - if(composite) - { - // Concatenate the transformations - - // Why don't we multiply the matrices in CompositeOperator::addOperator()? - // Why keep the matrices factored and multiply them here repeatedly? - // Combining them would also avoid this if-else logic. BTNG - for(auto op : composite->getOperators()) - { - // Use visitor pattern to extract the affine matrix from supported operators - internal::AffineMatrixVisitor visitor; - op->accept(visitor); - if(!visitor.isValid()) - { - continue; - } - const auto& matrix = visitor.getMatrix(); - numerics::Matrix res(identity4x4); - numerics::matrix_multiply(matrix, transformation, res); - transformation = res; - } - } - else - { - internal::AffineMatrixVisitor visitor; - geometryOperator->accept(visitor); - if(visitor.isValid()) - { - transformation = visitor.getMatrix(); - } - } - } - return transformation; + return m_shape.getGeometry().getTransform(); } // Return a 3x3 matrix that rotates coordinates from the x-axis to the given direction. diff --git a/src/axom/quest/MeshClipperStrategy.cpp b/src/axom/quest/MeshClipperStrategy.cpp index 38b50b6b54..92e867b4f8 100644 --- a/src/axom/quest/MeshClipperStrategy.cpp +++ b/src/axom/quest/MeshClipperStrategy.cpp @@ -73,7 +73,7 @@ class AffineMatrixVisitor : public klee::GeometryOperatorVisitor MeshClipperStrategy::MeshClipperStrategy(const klee::Geometry& kGeom) : m_info(kGeom.asHierarchy()) - , m_extTrans(computeTransformationMatrix(kGeom.getGeometryOperator())) + , m_extTrans(kGeom.getTransform()) { } const std::string& MeshClipperStrategy::name() const @@ -94,49 +94,6 @@ const axom::primal::BoundingBox& MeshClipperStrategy::getBoundingBox3 return invalidBb3d; } -numerics::Matrix MeshClipperStrategy::computeTransformationMatrix( - const std::shared_ptr& op) const -{ - const auto identity4x4 = numerics::Matrix::identity(4); - numerics::Matrix transformation(identity4x4); - if(op) - { - auto composite = std::dynamic_pointer_cast(op); - if(composite) - { - // Concatenate the transformations - - // Why don't we multiply the matrices in CompositeOperator::addOperator()? - // Why keep the matrices factored and multiply them here repeatedly? - // Combining them would also avoid this if-else logic. BTNG - for(auto op : composite->getOperators()) - { - // Use visitor pattern to extract the affine matrix from supported operators - internal::AffineMatrixVisitor visitor; - op->accept(visitor); - if(!visitor.isValid()) - { - continue; - } - const auto& matrix = visitor.getMatrix(); - numerics::Matrix res(identity4x4); - numerics::matrix_multiply(matrix, transformation, res); - transformation = res; - } - } - else - { - internal::AffineMatrixVisitor visitor; - op->accept(visitor); - if(visitor.isValid()) - { - transformation = visitor.getMatrix(); - } - } - } - return transformation; -} - } // namespace experimental } // end namespace quest } // end namespace axom diff --git a/src/axom/quest/MeshClipperStrategy.hpp b/src/axom/quest/MeshClipperStrategy.hpp index f8011a81cf..a8b8ebc394 100644 --- a/src/axom/quest/MeshClipperStrategy.hpp +++ b/src/axom/quest/MeshClipperStrategy.hpp @@ -431,13 +431,6 @@ class MeshClipperStrategy * which apply before m_extTrans. */ numerics::Matrix m_extTrans; - -private: - /*! - * @brief Compute the transformation matrix of a GeometryOperator. - */ - numerics::Matrix computeTransformationMatrix( - const std::shared_ptr& op) const; }; } // namespace experimental From ccadc6705659f3f1da1dcfc570f20b2a912c2a13 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Fri, 24 Apr 2026 19:20:36 -0700 Subject: [PATCH 123/986] Fix compilation --- src/axom/klee/AffineMatrixVisitor.cpp | 7 ++----- src/axom/klee/AffineMatrixVisitor.hpp | 3 ++- src/axom/klee/Geometry.cpp | 3 ++- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/axom/klee/AffineMatrixVisitor.cpp b/src/axom/klee/AffineMatrixVisitor.cpp index 03e1f5a809..6011049963 100644 --- a/src/axom/klee/AffineMatrixVisitor.cpp +++ b/src/axom/klee/AffineMatrixVisitor.cpp @@ -3,13 +3,12 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_KLEE_AFFINE_MATRIX_VISITOR_HPP_ -#define AXOM_KLEE_AFFINE_MATRIX_VISITOR_HPP_ +#include "axom/klee/AffineMatrixVisitor.hpp" namespace axom::klee { -AffineMatrixVisitor::AffineMatrixVisitor() : klee::GeometryOperatorVisitor(), m_isValid(false), m_matrix(4, 4) { } +AffineMatrixVisitor::AffineMatrixVisitor() : GeometryOperatorVisitor(), m_isValid(false), m_matrix(4, 4) { } void AffineMatrixVisitor::visit(const klee::Translation& translation) { @@ -44,5 +43,3 @@ AffineMatrixVisitor::AffineMatrixVisitor() : klee::GeometryOperatorVisitor(), m_ } } // end namespace axom::klee - -#endif diff --git a/src/axom/klee/AffineMatrixVisitor.hpp b/src/axom/klee/AffineMatrixVisitor.hpp index b3a6c0fd1b..4c3fc09897 100644 --- a/src/axom/klee/AffineMatrixVisitor.hpp +++ b/src/axom/klee/AffineMatrixVisitor.hpp @@ -5,6 +5,7 @@ // SPDX-License-Identifier: (BSD-3-Clause) #ifndef AXOM_KLEE_AFFINE_MATRIX_VISITOR_HPP_ #define AXOM_KLEE_AFFINE_MATRIX_VISITOR_HPP_ +#include "axom/klee/GeometryOperators.hpp" namespace axom::klee { @@ -15,7 +16,7 @@ namespace axom::klee * This class extracts the matrix form of supported operators and marks the operator as unvalid otherwise * To use, check the \a isValid() function after visiting and then call the \a getMatrix() function. */ -class AffineMatrixVisitor : public klee::GeometryOperatorVisitor +class AffineMatrixVisitor : public GeometryOperatorVisitor { public: AffineMatrixVisitor(); diff --git a/src/axom/klee/Geometry.cpp b/src/axom/klee/Geometry.cpp index 103ec60cd5..115421dd58 100644 --- a/src/axom/klee/Geometry.cpp +++ b/src/axom/klee/Geometry.cpp @@ -6,6 +6,7 @@ #include "axom/klee/Geometry.hpp" #include "axom/klee/GeometryOperators.hpp" +#include "axom/klee/AffineMatrixVisitor.hpp" #include "conduit_blueprint_mesh.hpp" @@ -260,7 +261,7 @@ numerics::Matrix Geometry::getTransform() const else { AffineMatrixVisitor visitor; - geometryOperator->accept(visitor); + m_operator->accept(visitor); if(visitor.isValid()) { transformation = visitor.getMatrix(); From c24cb4675d3545b58bd495fb98579708b2f45997 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 27 Apr 2026 12:28:32 -0700 Subject: [PATCH 124/986] Fix CUDA compilation errors in memoization. --- .../detail/winding_number_2d_memoization.hpp | 38 +++++++++++-------- .../detail/winding_number_3d_memoization.hpp | 38 +++++++++++-------- 2 files changed, 46 insertions(+), 30 deletions(-) diff --git a/src/axom/primal/operators/detail/winding_number_2d_memoization.hpp b/src/axom/primal/operators/detail/winding_number_2d_memoization.hpp index 37ba2257bf..3fda714200 100644 --- a/src/axom/primal/operators/detail/winding_number_2d_memoization.hpp +++ b/src/axom/primal/operators/detail/winding_number_2d_memoization.hpp @@ -321,6 +321,29 @@ class NURBSCurveCacheManagerOMP NURBSCurveCacheManagerOMP() = default; NURBSCurveCacheManagerOMP(const CurveArrayView& curves, double bbExpansionAmount = 0.0) + { + initialize(curves, bbExpansionAmount); + } + + /// A view of the manager object. + struct View + { + NURBSCachePerThreadArrayView m_views; + + /// Return the NURBSCacheArrayView for the current OMP thread. + NURBSCacheArrayView caches() const { return m_views[omp_get_thread_num()].view(); } + }; + + /// Return a view of this manager to pass into a device function. + View view() const { return View {m_nurbs_caches.view()}; } + + /// Return if the underlying array is empty + bool empty() const { return m_nurbs_caches.empty(); } + +#if !defined(AXOM_USE_CUDA) +private: +#endif + void initialize(const CurveArrayView& curves, double bbExpansionAmount = 0.0) { const int nt = omp_get_max_threads(); m_nurbs_caches.resize(nt); @@ -341,21 +364,6 @@ class NURBSCurveCacheManagerOMP AXOM_LAMBDA(axom::IndexType t) { nurbs_caches_view[t] = nurbs_caches_view[0]; }); } - /// A view of the manager object. - struct View - { - NURBSCachePerThreadArrayView m_views; - - /// Return the NURBSCacheArrayView for the current OMP thread. - NURBSCacheArrayView caches() const { return m_views[omp_get_thread_num()].view(); } - }; - - /// Return a view of this manager to pass into a device function. - View view() const { return View {m_nurbs_caches.view()}; } - - /// Return if the underlying array is empty - bool empty() const { return m_nurbs_caches.empty(); } - private: NURBSCachePerThreadArray m_nurbs_caches; }; diff --git a/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp b/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp index cb568c18ca..157b6e4b9d 100644 --- a/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp +++ b/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp @@ -359,6 +359,29 @@ class NURBSPatchCacheManagerOMP NURBSPatchCacheManagerOMP() = default; NURBSPatchCacheManagerOMP(const PatchArrayView& patches) + { + initialize(patches); + } + + /// A view of the manager object. + struct View + { + NURBSCachePerThreadArrayView m_views; + + /// Return the NURBSCacheArrayView for the current OMP thread. + NURBSCacheArrayView caches() const { return m_views[omp_get_thread_num()].view(); } + }; + + /// Return a view of this manager to pass into a device function. + View view() const { return View {m_nurbs_caches.view()}; } + + /// Return if the underlying array is empty + bool empty() const { return m_nurbs_caches.empty(); } + +#if !defined(AXOM_USE_CUDA) +private: +#endif + void initialize(const PatchArrayView& patches) { const int nt = omp_get_max_threads(); m_nurbs_caches.resize(nt); @@ -385,21 +408,6 @@ class NURBSPatchCacheManagerOMP }); } - /// A view of the manager object. - struct View - { - NURBSCachePerThreadArrayView m_views; - - /// Return the NURBSCacheArrayView for the current OMP thread. - NURBSCacheArrayView caches() const { return m_views[omp_get_thread_num()].view(); } - }; - - /// Return a view of this manager to pass into a device function. - View view() const { return View {m_nurbs_caches.view()}; } - - /// Return if the underlying array is empty - bool empty() const { return m_nurbs_caches.empty(); } - private: NURBSCachePerThreadArray m_nurbs_caches; }; From eff7338513e489781c187bb6e60ccdd5f1d95b5b Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 27 Apr 2026 12:48:42 -0700 Subject: [PATCH 125/986] Remove an AXOM_LAMBDA and type on an internal lambda --- src/axom/spin/policy/LinearBVH.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/axom/spin/policy/LinearBVH.hpp b/src/axom/spin/policy/LinearBVH.hpp index 9c270953b6..58fef9ba83 100644 --- a/src/axom/spin/policy/LinearBVH.hpp +++ b/src/axom/spin/policy/LinearBVH.hpp @@ -166,7 +166,7 @@ class LinearBVHTraverser // Return the precomputed values in the reduction. auto returnLeafValue = - AXOM_LAMBDA(std::int32_t currentNode, const std::int32_t* leafNodes)->ValueType + [&](std::int32_t currentNode, const std::int32_t* leafNodes) { const auto idx = leafNodes[currentNode]; return leafFieldView[idx]; From f612a79e352f702e20abd5ef3e44008d64fa0d68 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 27 Apr 2026 12:50:20 -0700 Subject: [PATCH 126/986] Change previous compilation fix from use of initialize() functions to use of AXOM_HOST_LAMBDA inside some OMP GWN cache classes. --- .../detail/winding_number_2d_memoization.hpp | 42 +++++++----------- .../detail/winding_number_3d_memoization.hpp | 44 ++++++++----------- 2 files changed, 35 insertions(+), 51 deletions(-) diff --git a/src/axom/primal/operators/detail/winding_number_2d_memoization.hpp b/src/axom/primal/operators/detail/winding_number_2d_memoization.hpp index 3fda714200..5055c6215b 100644 --- a/src/axom/primal/operators/detail/winding_number_2d_memoization.hpp +++ b/src/axom/primal/operators/detail/winding_number_2d_memoization.hpp @@ -321,29 +321,6 @@ class NURBSCurveCacheManagerOMP NURBSCurveCacheManagerOMP() = default; NURBSCurveCacheManagerOMP(const CurveArrayView& curves, double bbExpansionAmount = 0.0) - { - initialize(curves, bbExpansionAmount); - } - - /// A view of the manager object. - struct View - { - NURBSCachePerThreadArrayView m_views; - - /// Return the NURBSCacheArrayView for the current OMP thread. - NURBSCacheArrayView caches() const { return m_views[omp_get_thread_num()].view(); } - }; - - /// Return a view of this manager to pass into a device function. - View view() const { return View {m_nurbs_caches.view()}; } - - /// Return if the underlying array is empty - bool empty() const { return m_nurbs_caches.empty(); } - -#if !defined(AXOM_USE_CUDA) -private: -#endif - void initialize(const CurveArrayView& curves, double bbExpansionAmount = 0.0) { const int nt = omp_get_max_threads(); m_nurbs_caches.resize(nt); @@ -353,7 +330,7 @@ class NURBSCurveCacheManagerOMP nurbs_caches_view[0].resize(curves.size()); axom::for_all( curves.size(), - AXOM_LAMBDA(axom::IndexType i) { + AXOM_HOST_LAMBDA(axom::IndexType i) { nurbs_caches_view[0][i] = NURBSCache(curves[i], bbExpansionAmount); }); @@ -361,9 +338,24 @@ class NURBSCurveCacheManagerOMP axom::for_all( 1, nt, - AXOM_LAMBDA(axom::IndexType t) { nurbs_caches_view[t] = nurbs_caches_view[0]; }); + AXOM_HOST_LAMBDA(axom::IndexType t) { nurbs_caches_view[t] = nurbs_caches_view[0]; }); } + /// A view of the manager object. + struct View + { + NURBSCachePerThreadArrayView m_views; + + /// Return the NURBSCacheArrayView for the current OMP thread. + NURBSCacheArrayView caches() const { return m_views[omp_get_thread_num()].view(); } + }; + + /// Return a view of this manager to pass into a device function. + View view() const { return View {m_nurbs_caches.view()}; } + + /// Return if the underlying array is empty + bool empty() const { return m_nurbs_caches.empty(); } + private: NURBSCachePerThreadArray m_nurbs_caches; }; diff --git a/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp b/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp index 157b6e4b9d..90a7c22599 100644 --- a/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp +++ b/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp @@ -359,29 +359,6 @@ class NURBSPatchCacheManagerOMP NURBSPatchCacheManagerOMP() = default; NURBSPatchCacheManagerOMP(const PatchArrayView& patches) - { - initialize(patches); - } - - /// A view of the manager object. - struct View - { - NURBSCachePerThreadArrayView m_views; - - /// Return the NURBSCacheArrayView for the current OMP thread. - NURBSCacheArrayView caches() const { return m_views[omp_get_thread_num()].view(); } - }; - - /// Return a view of this manager to pass into a device function. - View view() const { return View {m_nurbs_caches.view()}; } - - /// Return if the underlying array is empty - bool empty() const { return m_nurbs_caches.empty(); } - -#if !defined(AXOM_USE_CUDA) -private: -#endif - void initialize(const PatchArrayView& patches) { const int nt = omp_get_max_threads(); m_nurbs_caches.resize(nt); @@ -391,16 +368,16 @@ class NURBSPatchCacheManagerOMP nurbs_caches_view[0].resize(patches.size()); axom::for_all( patches.size(), - AXOM_LAMBDA(axom::IndexType i) { nurbs_caches_view[0][i] = NURBSCache(patches[i]); }); + AXOM_HOST_LAMBDA(axom::IndexType i) { nurbs_caches_view[0][i] = NURBSCache(patches[i]); }); SLIC_INFO("Finished the first construction"); // Copy the constructed cache to the other threads' copies (less work than construction) axom::for_all( 1, nt, - AXOM_LAMBDA(axom::IndexType t) { nurbs_caches_view[t].resize(nurbs_caches_view[0].size()); }); + AXOM_HOST_LAMBDA(axom::IndexType t) { nurbs_caches_view[t].resize(nurbs_caches_view[0].size()); }); axom::for_all( patches.size(), - AXOM_LAMBDA(axom::IndexType i) { + AXOM_HOST_LAMBDA(axom::IndexType i) { for(int t = 0; t < nt; t++) { nurbs_caches_view[t][i] = nurbs_caches_view[0][i]; @@ -408,6 +385,21 @@ class NURBSPatchCacheManagerOMP }); } + /// A view of the manager object. + struct View + { + NURBSCachePerThreadArrayView m_views; + + /// Return the NURBSCacheArrayView for the current OMP thread. + NURBSCacheArrayView caches() const { return m_views[omp_get_thread_num()].view(); } + }; + + /// Return a view of this manager to pass into a device function. + View view() const { return View {m_nurbs_caches.view()}; } + + /// Return if the underlying array is empty + bool empty() const { return m_nurbs_caches.empty(); } + private: NURBSCachePerThreadArray m_nurbs_caches; }; From 1d59fd23d1c79d4ba4299cc65e6965985977443f Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 27 Apr 2026 13:02:49 -0700 Subject: [PATCH 127/986] make style --- .../primal/operators/detail/winding_number_3d_memoization.hpp | 4 +++- src/axom/spin/policy/LinearBVH.hpp | 4 +--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp b/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp index 90a7c22599..6dc8678fd8 100644 --- a/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp +++ b/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp @@ -374,7 +374,9 @@ class NURBSPatchCacheManagerOMP axom::for_all( 1, nt, - AXOM_HOST_LAMBDA(axom::IndexType t) { nurbs_caches_view[t].resize(nurbs_caches_view[0].size()); }); + AXOM_HOST_LAMBDA(axom::IndexType t) { + nurbs_caches_view[t].resize(nurbs_caches_view[0].size()); + }); axom::for_all( patches.size(), AXOM_HOST_LAMBDA(axom::IndexType i) { diff --git a/src/axom/spin/policy/LinearBVH.hpp b/src/axom/spin/policy/LinearBVH.hpp index 58fef9ba83..fa4a17ef4a 100644 --- a/src/axom/spin/policy/LinearBVH.hpp +++ b/src/axom/spin/policy/LinearBVH.hpp @@ -165,9 +165,7 @@ class LinearBVHTraverser }); // Return the precomputed values in the reduction. - auto returnLeafValue = - [&](std::int32_t currentNode, const std::int32_t* leafNodes) - { + auto returnLeafValue = [&](std::int32_t currentNode, const std::int32_t* leafNodes) { const auto idx = leafNodes[currentNode]; return leafFieldView[idx]; }; From 2cd354fe2b9376040a0fa6f822b10472512927f0 Mon Sep 17 00:00:00 2001 From: Chris White Date: Mon, 27 Apr 2026 14:25:22 -0700 Subject: [PATCH 128/986] Remove allow_failure for CUDA build job Removed allow_failure flag for the gcc build job. --- .gitlab/build_matrix.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitlab/build_matrix.yml b/.gitlab/build_matrix.yml index d21af3dbdf..337869d676 100644 --- a/.gitlab/build_matrix.yml +++ b/.gitlab/build_matrix.yml @@ -42,8 +42,6 @@ matrix-gcc_13_3_1_cuda-src: COMPILER: "gcc@13.3.1_cuda" HOST_CONFIG: "matrix-toss_4_x86_64_ib-${COMPILER}.cmake" extends: .src_build_on_matrix - # TODO: Fix unit tests failures - allow_failure: true #### # Full Build jobs From d8b0e5233ef92eddd5f18d4594051b56b6e69b42 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Wed, 15 Apr 2026 15:32:55 -0700 Subject: [PATCH 129/986] Remove +rocm +openmp conflict --- scripts/spack/packages/axom/package.py | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/spack/packages/axom/package.py b/scripts/spack/packages/axom/package.py index 659590e30b..949e956577 100644 --- a/scripts/spack/packages/axom/package.py +++ b/scripts/spack/packages/axom/package.py @@ -335,7 +335,6 @@ class Axom(CachedCMakePackage, CudaPackage, ROCmPackage): # Sidre requires conduit_blueprint_mpi.hpp conflicts("^conduit@:0.6.0", when="@0.5.0:") - conflicts("+openmp", when="+rocm") conflicts("+cuda", when="+rocm") conflicts("~raja", when="+cuda") From 4606dbda97ef8478a63cafec003163c00116e18c Mon Sep 17 00:00:00 2001 From: Brian Han Date: Fri, 17 Apr 2026 16:02:05 -0700 Subject: [PATCH 130/986] +rocm+openmp changes - beware ompstub --- scripts/spack/packages/axom/package.py | 42 ++++++++++++++++++++++---- scripts/spack/specs.json | 6 ++-- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/scripts/spack/packages/axom/package.py b/scripts/spack/packages/axom/package.py index 949e956577..00ba27b02a 100644 --- a/scripts/spack/packages/axom/package.py +++ b/scripts/spack/packages/axom/package.py @@ -479,7 +479,7 @@ def initconfig_hardware_entries(self): # Only amdclang requires this path; cray compiler fails if this is included if spec.satisfies("%llvm-amdgpu"): hip_link_flags += "-L{0}/lib -Wl,-rpath,{0}/lib ".format(rocm_root) - hip_link_flags += "-lpgmath -lompstub " + hip_link_flags += "-lpgmath " # Fixes for mpi for rocm until wrapper paths are fixed # These flags are already part of the wrapped compilers on TOSS4 systems @@ -493,11 +493,22 @@ def initconfig_hardware_entries(self): self.spec.compiler.version ) - # Remove extra link library for crayftn - if spec.satisfies("+fortran") and self.is_fortran_compiler("crayftn"): - entries.append( - cmake_cache_string("BLT_CMAKE_IMPLICIT_LINK_LIBRARIES_EXCLUDE", "unwind") - ) + if spec.satisfies("+fortran"): + link_remove_list = "" + + # Remove extra link library for crayftn + if self.is_fortran_compiler("crayftn"): + link_remove_list += "unwind " + + # Remove injected OpenMP stub library + if spec.satisfies("+openmp"): + link_remove_list += "ompstub" + + if link_remove_list: + entries.append( + cmake_cache_string("BLT_CMAKE_IMPLICIT_LINK_LIBRARIES_EXCLUDE", + link_remove_list) + ) # Additional libraries for TOSS4 hip_link_flags += "-lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " @@ -557,6 +568,25 @@ def initconfig_hardware_entries(self): cmake_cache_string("BLT_OPENMP_LINK_FLAGS", openmp_gen_exp, description) ) + if ( + spec.satisfies("+openmp") + and spec.satisfies("+rocm") + and self.spec.satisfies("%cce") + ): + openmp_gen_exp = ( + "$<$>:" + "-fopenmp=libomp>;$<$:-fopenmp>" + ) + + description = "Different OpenMP compile & link flags between HIP and CXX compilers" + entries.append( + cmake_cache_string("BLT_OPENMP_COMPILE_FLAGS", openmp_gen_exp, description) + ) + entries.append( + cmake_cache_string("BLT_OPENMP_LINK_FLAGS", openmp_gen_exp, description) + ) + if spec.satisfies("target=ppc64le:"): # Fix for working around CMake adding implicit link directories # returned by the BlueOS compilers to link executables with diff --git a/scripts/spack/specs.json b/scripts/spack/specs.json index 79ba88ff79..09a02a1335 100644 --- a/scripts/spack/specs.json +++ b/scripts/spack/specs.json @@ -38,9 +38,9 @@ "__comment__":"# -Wno-int-conversion flag needed for building HDF5", "__comment__":"# caliper disabled for rocm@6.3.1, fails to compile", "toss_4_x86_64_ib_cray": - [ "+python+devtools~openmp+mfem+c2c+adiak+caliper+rocm amdgpu_target=gfx942,gfx90a %rocm_6_4_3 ^hip@6.4.3 ^hipblas@6.4.3 ^hipsparse@6.4.3 ^hsa-rocr-dev@6.4.3 ^rocprim@6.4.3 ^mfem+raja+umpire ^raja~openmp+rocm ^umpire~openmp+rocm ^hdf5 cflags=-Wno-int-conversion", - "+python+devtools~openmp+mfem+c2c+adiak~caliper+rocm amdgpu_target=gfx942,gfx90a %rocm_6_3_1 ^hip@6.3.1 ^hipblas@6.3.1 ^hipsparse@6.3.1 ^hsa-rocr-dev@6.3.1 ^rocprim@6.3.1 ^mfem+raja+umpire ^raja~openmp+rocm ^umpire~openmp+rocm ^hdf5 cflags=-Wno-int-conversion", - "+python+devtools~openmp+mfem+c2c+adiak+caliper+rocm amdgpu_target=gfx942,gfx90a %cce_20 ^hip@6.4.3 ^hipblas@6.4.3 ^hipsparse@6.4.3 ^hsa-rocr-dev@6.4.3 ^rocprim@6.4.3 ^caliper~shared ^mfem+raja+umpire ^raja~openmp+rocm ^umpire~openmp+rocm ^hdf5 cflags=-Wno-int-conversion" ], + [ "+python+devtools+openmp+mfem+c2c+adiak+caliper+rocm amdgpu_target=gfx942,gfx90a %rocm_6_4_3 ^hip@6.4.3 ^hipblas@6.4.3 ^hipsparse@6.4.3 ^hsa-rocr-dev@6.4.3 ^rocprim@6.4.3 ^mfem+raja+umpire ^raja+openmp+rocm ^umpire+openmp+rocm ^hdf5 cflags=-Wno-int-conversion", + "+python+devtools+openmp+mfem+c2c+adiak~caliper+rocm amdgpu_target=gfx942,gfx90a %rocm_6_3_1 ^hip@6.3.1 ^hipblas@6.3.1 ^hipsparse@6.3.1 ^hsa-rocr-dev@6.3.1 ^rocprim@6.3.1 ^mfem+raja+umpire ^raja+openmp+rocm ^umpire+openmp+rocm ^hdf5 cflags=-Wno-int-conversion", + "+python+devtools+openmp+mfem+c2c+adiak+caliper+rocm amdgpu_target=gfx942,gfx90a %cce_20 ^hip@6.4.3 ^hipblas@6.4.3 ^hipsparse@6.4.3 ^hsa-rocr-dev@6.4.3 ^rocprim@6.4.3 ^caliper~shared ^mfem+raja+umpire ^raja+openmp+rocm ^umpire+openmp+rocm ^hdf5 cflags=-Wno-int-conversion" ], "darwin-x86_64": [ "+python+devtools+mfem %clang@9.0.0" ] From bf33894b2ca873dd5b73a22f9b778e51802a2fc5 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Mon, 20 Apr 2026 13:01:34 -0700 Subject: [PATCH 131/986] Fix multiple flags logic --- scripts/spack/packages/axom/package.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/scripts/spack/packages/axom/package.py b/scripts/spack/packages/axom/package.py index 00ba27b02a..c4ea8718a2 100644 --- a/scripts/spack/packages/axom/package.py +++ b/scripts/spack/packages/axom/package.py @@ -494,20 +494,21 @@ def initconfig_hardware_entries(self): ) if spec.satisfies("+fortran"): - link_remove_list = "" + link_remove_list = [] # Remove extra link library for crayftn if self.is_fortran_compiler("crayftn"): - link_remove_list += "unwind " + link_remove_list += ["unwind"] # Remove injected OpenMP stub library if spec.satisfies("+openmp"): - link_remove_list += "ompstub" + link_remove_list += ["ompstub"] if link_remove_list: entries.append( - cmake_cache_string("BLT_CMAKE_IMPLICIT_LINK_LIBRARIES_EXCLUDE", - link_remove_list) + cmake_cache_string( + "BLT_CMAKE_IMPLICIT_LINK_LIBRARIES_EXCLUDE", ";".join(link_remove_list) + ) ) # Additional libraries for TOSS4 From c0ba62895e719d846f7289a31273dab337a5ace4 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Tue, 21 Apr 2026 08:52:56 -0700 Subject: [PATCH 132/986] Update RZ host-configs --- ...toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake | 38 ++++++++++--------- ...x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake | 34 +++++++++-------- ...x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake | 34 +++++++++-------- ...toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake | 38 ++++++++++--------- ...x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake | 34 +++++++++-------- ...x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake | 34 +++++++++-------- 6 files changed, 114 insertions(+), 98 deletions(-) diff --git a/host-configs/rzadams-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake b/host-configs/rzadams-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake index 2723deaf78..a8fba5af7e 100644 --- a/host-configs/rzadams-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake +++ b/host-configs/rzadams-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake @@ -4,13 +4,13 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.29.2/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/cce-20.0.0/blt-0.7.1-af45hpsxdxploxhqqi4irjmy4vz2omqx;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/cce-20.0.0/c2c-1.8.0-cpdkv2uxkhs3qmzqakt4sht3vuiucdwt;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/cce-20.0.0/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-wytd46zvuquxg2rqf3zec5ldbmjeaat6;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/cce-20.0.0/conduit-0.9.5-pbrrhvrsmnewby6q3u35ceppt5fxme2p;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/cce-20.0.0/lua-5.4.8-y6z3polqakbuhn6nh6muri3rjxqmiioa;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/cce-20.0.0/mfem-4.9.0-2r7wws3ojiqvv6vk7y3ki443xke656sf;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/cce-20.0.0/py-nanobind-2.7.0-ieb2bzuz435ydpyiq2uj36yyqjbrffml;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/cce-20.0.0/adiak-0.4.0-lzgexbo5qyzepgju2acyy6d2qft443tb;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/cce-20.0.0/libunwind-1.8.3-zvteigxlopfo5tkdr2n2pglgosg42bah;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/cce-20.0.0/hdf5-1.8.23-to3lvqwzxrqzad65zbvnbtvyt4k63uxe;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/cce-20.0.0/parmetis-4.0.3-bajtzsmahjg2wvcjzo5gfb2hr5oj2elc;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/cce-20.0.0/py-numpy-2.4.2-xw5ysy75zfqlalmbmtl5phd2qip43hlo;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/cce-20.0.0/hypre-2.27.0-knkmxfbf3xcut2wmamjsmhrqf7u7xmi7;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/cce-20.0.0/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-y7uevzr5oi5eansgqfm4ppgcdkvnni6t;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/cce-20.0.0/umpire-2025.12.0-ke2uxlxkv74lvrhujuzh3ktf3c6e7lfb;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/cce-20.0.0/zlib-1.3.1-f3sdiqmy2whvmdn2fxeol3k7oqwouays;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/cce-20.0.0/metis-5.1.0-v4d34x7btqhotktvu5bgk4iwk6h2lumb;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/cce-20.0.0/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-a755kudv24eiizq4thcs2ca2fj7vy65x;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/cce-20.0.0/fmt-11.0.2-nzzmwf4nofjv46yeugehjo64opvfbbzr;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cce-tce/cce-20.0.0;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.32-rocmcc-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/blt-0.7.1-af45hpsxdxploxhqqi4irjmy4vz2omqx;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/c2c-1.8.0-cpdkv2uxkhs3qmzqakt4sht3vuiucdwt;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-wytd46zvuquxg2rqf3zec5ldbmjeaat6;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/conduit-0.9.5-pbrrhvrsmnewby6q3u35ceppt5fxme2p;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/lua-5.4.8-y6z3polqakbuhn6nh6muri3rjxqmiioa;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/mfem-4.9.0-wpbbiugqvdw7bylkmyyf2quazcoptzah;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/py-nanobind-2.7.0-ieb2bzuz435ydpyiq2uj36yyqjbrffml;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/adiak-0.4.0-lzgexbo5qyzepgju2acyy6d2qft443tb;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/libunwind-1.8.3-zvteigxlopfo5tkdr2n2pglgosg42bah;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/hdf5-1.8.23-to3lvqwzxrqzad65zbvnbtvyt4k63uxe;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/parmetis-4.0.3-bajtzsmahjg2wvcjzo5gfb2hr5oj2elc;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/py-numpy-2.4.2-xw5ysy75zfqlalmbmtl5phd2qip43hlo;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/hypre-2.27.0-knkmxfbf3xcut2wmamjsmhrqf7u7xmi7;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-z3eibnxgk3jeplydlcizrhurpibzqzqv;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/umpire-2025.12.0-eqokbtuch3pwifb225w2i3rrft5ddcgv;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/zlib-1.3.1-f3sdiqmy2whvmdn2fxeol3k7oqwouays;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/metis-5.1.0-v4d34x7btqhotktvu5bgk4iwk6h2lumb;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-eplsxvli6nnrgdl4l3hl7cizytginfhp;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/fmt-11.0.2-nzzmwf4nofjv46yeugehjo64opvfbbzr;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cce-tce/cce-20.0.0;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.32-rocmcc-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/cce-20.0.0/axom-develop-csdbligyshk6zb3emr4vn5sjrrnbtobj/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/cce-20.0.0/axom-develop-csdbligyshk6zb3emr4vn5sjrrnbtobj/lib64;;/opt/cray/pe/cce/20.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/axom-develop-xz7g7s5cdkdr6lixuhs23vyuqqodocrg/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/axom-develop-xz7g7s5cdkdr6lixuhs23vyuqqodocrg/lib64;;/opt/cray/pe/cce/20.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/cce-20.0.0/axom-develop-csdbligyshk6zb3emr4vn5sjrrnbtobj/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/cce-20.0.0/axom-develop-csdbligyshk6zb3emr4vn5sjrrnbtobj/lib64;;/opt/cray/pe/cce/20.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/axom-develop-xz7g7s5cdkdr6lixuhs23vyuqqodocrg/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/axom-develop-xz7g7s5cdkdr6lixuhs23vyuqqodocrg/lib64;;/opt/cray/pe/cce/20.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") @@ -21,11 +21,11 @@ set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/cce/craycc" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/cce/craycc" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/cce/case-insensitive/crayCC" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/cce/case-insensitive/crayCC" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/cce/crayftn" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/cce/crayftn" CACHE PATH "") else() @@ -84,37 +84,41 @@ set(ENABLE_HIP ON CACHE BOOL "") set(ROCM_ROOT_DIR "/opt/rocm-6.4.3" CACHE PATH "") -set(BLT_CMAKE_IMPLICIT_LINK_LIBRARIES_EXCLUDE "unwind" CACHE STRING "") +set(BLT_CMAKE_IMPLICIT_LINK_LIBRARIES_EXCLUDE "unwind;ompstub" CACHE STRING "") -set(CMAKE_EXE_LINKER_FLAGS "-lxpmem -L/opt/cray/pe/mpich/8.1.32/gtl/lib -Wl,-rpath,/opt/cray/pe/mpich/8.1.32/gtl/lib -lmpi_gtl_hsa -L/opt/rocm-6.4.3/lib/llvm/lib -Wl,-rpath,/opt/rocm-6.4.3/lib/llvm/lib -L/opt/rocm-6.4.3/lib -Wl,-rpath,/opt/rocm-6.4.3/lib -lpgmath -lompstub -L/opt/cray/pe/cce/20.0.0/cce/x86_64/lib -Wl,-rpath,/opt/cray/pe/cce/20.0.0/cce/x86_64/lib -lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " CACHE STRING "") +set(CMAKE_EXE_LINKER_FLAGS "-lxpmem -L/opt/cray/pe/mpich/8.1.32/gtl/lib -Wl,-rpath,/opt/cray/pe/mpich/8.1.32/gtl/lib -lmpi_gtl_hsa -L/opt/rocm-6.4.3/lib/llvm/lib -Wl,-rpath,/opt/rocm-6.4.3/lib/llvm/lib -L/opt/rocm-6.4.3/lib -Wl,-rpath,/opt/rocm-6.4.3/lib -lpgmath -L/opt/cray/pe/cce/20.0.0/cce/x86_64/lib -Wl,-rpath,/opt/cray/pe/cce/20.0.0/cce/x86_64/lib -lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " CACHE STRING "") #------------------------------------------------ # Hardware Specifics #------------------------------------------------ -set(ENABLE_OPENMP OFF CACHE BOOL "") +set(ENABLE_OPENMP ON CACHE BOOL "") set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") +set(BLT_OPENMP_COMPILE_FLAGS "$<$>:-fopenmp=libomp>;$<$:-fopenmp>" CACHE STRING "Different OpenMP compile & link flags between HIP and CXX compilers (amdclang++)") + +set(BLT_OPENMP_LINK_FLAGS "$<$>:-fopenmp=libomp>;$<$:-fopenmp>" CACHE STRING "Different OpenMP compile & link flags between HIP and CXX compilers (amdclang++)") + #------------------------------------------------------------------------------ # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/cce-20.0.0" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0" CACHE PATH "") set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-pbrrhvrsmnewby6q3u35ceppt5fxme2p" CACHE PATH "") set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-cpdkv2uxkhs3qmzqakt4sht3vuiucdwt" CACHE PATH "") -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-2r7wws3ojiqvv6vk7y3ki443xke656sf" CACHE PATH "") +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-wpbbiugqvdw7bylkmyyf2quazcoptzah" CACHE PATH "") set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-to3lvqwzxrqzad65zbvnbtvyt4k63uxe" CACHE PATH "") set(LUA_DIR "${TPL_ROOT}/lua-5.4.8-y6z3polqakbuhn6nh6muri3rjxqmiioa" CACHE PATH "") -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-y7uevzr5oi5eansgqfm4ppgcdkvnni6t" CACHE PATH "") +set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-z3eibnxgk3jeplydlcizrhurpibzqzqv" CACHE PATH "") -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-ke2uxlxkv74lvrhujuzh3ktf3c6e7lfb" CACHE PATH "") +set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-eqokbtuch3pwifb225w2i3rrft5ddcgv" CACHE PATH "") # OPENCASCADE not built @@ -122,7 +126,7 @@ set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-lzgexbo5qyzepgju2acyy6d2qft443tb" CACHE P set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-wytd46zvuquxg2rqf3zec5ldbmjeaat6" CACHE PATH "") -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-a755kudv24eiizq4thcs2ca2fj7vy65x" CACHE PATH "") +set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-eplsxvli6nnrgdl4l3hl7cizytginfhp" CACHE PATH "") # scr not built @@ -152,12 +156,12 @@ set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-ieb2bzuz435ydpyiq2uj36yyqjbrffml/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5/lib/python3.13/site-packages" CACHE PATH "") set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-xw5ysy75zfqlalmbmtl5phd2qip43hlo/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/none-none/py-pluggy-1.6.0-n4ojmtqyawxz6gipk64pup3ahwz3xqpu/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/py-pluggy-1.6.0-n4ojmtqyawxz6gipk64pup3ahwz3xqpu/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/none-none/py-iniconfig-2.1.0-lf3yvch65se22jr7b5unfoxtse4ugkrk/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/py-iniconfig-2.1.0-lf3yvch65se22jr7b5unfoxtse4ugkrk/lib/python3.13/site-packages" CACHE PATH "") diff --git a/host-configs/rzadams-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake b/host-configs/rzadams-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake index 6e6bc29eba..acdca3b154 100644 --- a/host-configs/rzadams-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake +++ b/host-configs/rzadams-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake @@ -4,13 +4,13 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.29.2/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.3.1/adiak-0.4.0-c4srnrz5apjetx74dt6xjah63o3a7xcx;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.3.1/blt-0.7.1-kzmxfjvxm4drr2qhspckvwee6mrlc3e5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.3.1/c2c-1.8.0-lm64gc55hpxilmtsw7geirys3q4wacu5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.3.1/conduit-0.9.5-wswspowegvp5byl5inc2cikljzkcqg64;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.3.1/lua-5.4.8-j7ngytniswhoa536kqfzarcraarkicbm;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.3.1/mfem-4.9.0-wetr3wqseqqobjb7be3uzrecpfewq4mr;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.3.1/py-nanobind-2.7.0-q2kwozph3gwgfyjnx43jp2gkbaghwxoj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.3.1/hdf5-1.8.23-rb4cjthx4wz4wnpaizwmm2jycijdthqm;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.3.1/parmetis-4.0.3-ng66tt5fmjyfoiuuxyl4rxqv4ompawmd;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.3.1/py-numpy-2.4.2-zcxbrxijjc3miotq6s3kq446afrgycuu;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.3.1/hypre-2.27.0-6m2jwyzqbksxgmdznvh54ytysxyme7jy;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.3.1/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-okzhosj73rts33dgatxh4daotairh5jn;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.3.1/umpire-2025.12.0-of6o5lgmd6jgjqfwat2sbyngieagkeco;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.3.1/zlib-1.3.1-5w5muvpvct6uruxrddscblsiyizl3uoc;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.3.1/metis-5.1.0-4punmdkye6rovr3u7ph7bgj66qim77du;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.3.1/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-74scmbkik6ye7bunoclw46d6zrcfwgha;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.3.1/fmt-11.0.2-opsfnufdiedpfpob5wgbvhcrhyhzfkaq;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.3.1;/opt/rocm-6.3.1;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.3.1;/opt/rocm-6.3.1;/opt/rocm-6.3.1" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.3.1/adiak-0.4.0-c4srnrz5apjetx74dt6xjah63o3a7xcx;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.3.1/blt-0.7.1-kzmxfjvxm4drr2qhspckvwee6mrlc3e5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.3.1/c2c-1.8.0-lm64gc55hpxilmtsw7geirys3q4wacu5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.3.1/conduit-0.9.5-wswspowegvp5byl5inc2cikljzkcqg64;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.3.1/lua-5.4.8-j7ngytniswhoa536kqfzarcraarkicbm;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.3.1/mfem-4.9.0-xwg5un3mlbeog3j4voeltekra43ix3ga;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.3.1/py-nanobind-2.7.0-q2kwozph3gwgfyjnx43jp2gkbaghwxoj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.3.1/hdf5-1.8.23-rb4cjthx4wz4wnpaizwmm2jycijdthqm;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.3.1/parmetis-4.0.3-ng66tt5fmjyfoiuuxyl4rxqv4ompawmd;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.3.1/py-numpy-2.4.2-zcxbrxijjc3miotq6s3kq446afrgycuu;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.3.1/hypre-2.27.0-6m2jwyzqbksxgmdznvh54ytysxyme7jy;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.3.1/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-wcyqyggju4u2ornjjnamgdgfbqjrzsir;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.3.1/umpire-2025.12.0-5w5v22edixuttxwueaagiobpjzn5mcgr;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.3.1/zlib-1.3.1-5w5muvpvct6uruxrddscblsiyizl3uoc;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.3.1/metis-5.1.0-4punmdkye6rovr3u7ph7bgj66qim77du;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.3.1/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-jnalmgzqx2nn2afq4gwhyjda3u6yub7y;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.3.1/fmt-11.0.2-opsfnufdiedpfpob5wgbvhcrhyhzfkaq;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.3.1;/opt/rocm-6.3.1;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.3.1;/opt/rocm-6.3.1;/opt/rocm-6.3.1" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.3.1/axom-develop-xoswouqk3ppxzuxtgegijd3kdoqlkvfe/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.3.1/axom-develop-xoswouqk3ppxzuxtgegijd3kdoqlkvfe/lib64;;/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.3.1/axom-develop-qrojk4ujrhd5b734s2yc73n5u3kvcgqa/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.3.1/axom-develop-qrojk4ujrhd5b734s2yc73n5u3kvcgqa/lib64;;/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.3.1/axom-develop-xoswouqk3ppxzuxtgegijd3kdoqlkvfe/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.3.1/axom-develop-xoswouqk3ppxzuxtgegijd3kdoqlkvfe/lib64;;/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.3.1/axom-develop-qrojk4ujrhd5b734s2yc73n5u3kvcgqa/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.3.1/axom-develop-qrojk4ujrhd5b734s2yc73n5u3kvcgqa/lib64;;/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") @@ -21,11 +21,11 @@ set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdclang" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdclang" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdclang++" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdclang++" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdflang" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdflang" CACHE PATH "") else() @@ -82,13 +82,15 @@ set(ENABLE_HIP ON CACHE BOOL "") set(ROCM_ROOT_DIR "/opt/rocm-6.3.1" CACHE PATH "") -set(CMAKE_EXE_LINKER_FLAGS "-lxpmem -L/opt/cray/pe/mpich/8.1.29/gtl/lib -Wl,-rpath,/opt/cray/pe/mpich/8.1.29/gtl/lib -lmpi_gtl_hsa -L/opt/rocm-6.3.1/lib/llvm/lib -Wl,-rpath,/opt/rocm-6.3.1/lib/llvm/lib -L/opt/rocm-6.3.1/lib -Wl,-rpath,/opt/rocm-6.3.1/lib -lpgmath -lompstub -Wl,--disable-new-dtags -lflang -lflangrti -lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " CACHE STRING "") +set(BLT_CMAKE_IMPLICIT_LINK_LIBRARIES_EXCLUDE "ompstub" CACHE STRING "") + +set(CMAKE_EXE_LINKER_FLAGS "-lxpmem -L/opt/cray/pe/mpich/8.1.29/gtl/lib -Wl,-rpath,/opt/cray/pe/mpich/8.1.29/gtl/lib -lmpi_gtl_hsa -L/opt/rocm-6.3.1/lib/llvm/lib -Wl,-rpath,/opt/rocm-6.3.1/lib/llvm/lib -L/opt/rocm-6.3.1/lib -Wl,-rpath,/opt/rocm-6.3.1/lib -lpgmath -Wl,--disable-new-dtags -lflang -lflangrti -lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " CACHE STRING "") #------------------------------------------------ # Hardware Specifics #------------------------------------------------ -set(ENABLE_OPENMP OFF CACHE BOOL "") +set(ENABLE_OPENMP ON CACHE BOOL "") set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") @@ -96,21 +98,21 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.3.1" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.3.1" CACHE PATH "") set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-wswspowegvp5byl5inc2cikljzkcqg64" CACHE PATH "") set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-lm64gc55hpxilmtsw7geirys3q4wacu5" CACHE PATH "") -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-wetr3wqseqqobjb7be3uzrecpfewq4mr" CACHE PATH "") +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-xwg5un3mlbeog3j4voeltekra43ix3ga" CACHE PATH "") set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-rb4cjthx4wz4wnpaizwmm2jycijdthqm" CACHE PATH "") set(LUA_DIR "${TPL_ROOT}/lua-5.4.8-j7ngytniswhoa536kqfzarcraarkicbm" CACHE PATH "") -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-okzhosj73rts33dgatxh4daotairh5jn" CACHE PATH "") +set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-wcyqyggju4u2ornjjnamgdgfbqjrzsir" CACHE PATH "") -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-of6o5lgmd6jgjqfwat2sbyngieagkeco" CACHE PATH "") +set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-5w5v22edixuttxwueaagiobpjzn5mcgr" CACHE PATH "") # OPENCASCADE not built @@ -118,7 +120,7 @@ set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-c4srnrz5apjetx74dt6xjah63o3a7xcx" CACHE P # CALIPER not built -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-74scmbkik6ye7bunoclw46d6zrcfwgha" CACHE PATH "") +set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-jnalmgzqx2nn2afq4gwhyjda3u6yub7y" CACHE PATH "") # scr not built @@ -148,12 +150,12 @@ set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-q2kwozph3gwgfyjnx43jp2gkbaghwxoj/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5/lib/python3.13/site-packages" CACHE PATH "") set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-zcxbrxijjc3miotq6s3kq446afrgycuu/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/none-none/py-pluggy-1.6.0-n4ojmtqyawxz6gipk64pup3ahwz3xqpu/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/py-pluggy-1.6.0-n4ojmtqyawxz6gipk64pup3ahwz3xqpu/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/none-none/py-iniconfig-2.1.0-lf3yvch65se22jr7b5unfoxtse4ugkrk/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/py-iniconfig-2.1.0-lf3yvch65se22jr7b5unfoxtse4ugkrk/lib/python3.13/site-packages" CACHE PATH "") diff --git a/host-configs/rzadams-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake b/host-configs/rzadams-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake index acad35209c..4579e5266d 100644 --- a/host-configs/rzadams-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake +++ b/host-configs/rzadams-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake @@ -4,13 +4,13 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.29.2/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.4.3/blt-0.7.1-4kxhk7nbuiv5oh2ymh7sdimnmor36hmo;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.4.3/c2c-1.8.0-h5kio7nseyazg4pzgvj4kxanb3plhbxm;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.4.3/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-rmlwpdacdtpzf4u72ctyroj72soqzf3f;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.4.3/conduit-0.9.5-zqywrasos2vbears5hvdwxhulm3xox6q;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.4.3/lua-5.4.8-gp4kdewsy4uiy6flsx342lgogcm7fym2;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.4.3/mfem-4.9.0-g6vxdlgc7spurcdacgc6e5245iu2hyiw;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.4.3/py-nanobind-2.7.0-2kp35jcpb73xuujvcz6kmg4q2npek4gg;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.4.3/adiak-0.4.0-cropyu42jr4q6k3zar6mkma6gc2vp6tk;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.4.3/libunwind-1.8.3-plszp43gkd75kzjcvge3qxrtb6j2budl;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.4.3/hdf5-1.8.23-mgic2kxojd5zeivhaz473zzmzrg3gdqe;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.4.3/parmetis-4.0.3-hd2qoczrh3vw3ejm6msitqdgecjk7xwe;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.4.3/py-numpy-2.4.2-a3mo27shjtpliwewbm5cci2kf37crrab;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.4.3/hypre-2.27.0-4nrl5egspbxmnkpzfym5udby6uruhn3i;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.4.3/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-wavyomm5hzyjf3xuyr33txmnspvxveei;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.4.3/umpire-2025.12.0-4oytcvf556ksil4hsthjt4b5y4wdpaxw;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.4.3/zlib-1.3.1-slqujeue3l44x3pgqbu7e2pw3lh2mj5i;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.4.3/metis-5.1.0-puzeddu6ufhz5vpxnw7i3i2i6slsvuuj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.4.3/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-3gtldngru7bva2reuel5iabyytw5fmdf;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.4.3/fmt-11.0.2-lpnox72rqcneew5hucwzq4nmms4at525;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/blt-0.7.1-4kxhk7nbuiv5oh2ymh7sdimnmor36hmo;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/c2c-1.8.0-h5kio7nseyazg4pzgvj4kxanb3plhbxm;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-rmlwpdacdtpzf4u72ctyroj72soqzf3f;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/conduit-0.9.5-zqywrasos2vbears5hvdwxhulm3xox6q;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/lua-5.4.8-gp4kdewsy4uiy6flsx342lgogcm7fym2;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/mfem-4.9.0-6cgokeizyrjzsjzwmtf35p5jvhnws7n5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/py-nanobind-2.7.0-2kp35jcpb73xuujvcz6kmg4q2npek4gg;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/adiak-0.4.0-cropyu42jr4q6k3zar6mkma6gc2vp6tk;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/libunwind-1.8.3-plszp43gkd75kzjcvge3qxrtb6j2budl;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/hdf5-1.8.23-mgic2kxojd5zeivhaz473zzmzrg3gdqe;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/parmetis-4.0.3-hd2qoczrh3vw3ejm6msitqdgecjk7xwe;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/py-numpy-2.4.2-a3mo27shjtpliwewbm5cci2kf37crrab;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/hypre-2.27.0-4nrl5egspbxmnkpzfym5udby6uruhn3i;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-v74yskgcb7tsgho6bdush5jdlbigkttf;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/umpire-2025.12.0-jft5rocv5ev4b57f3664b2kagyfjra2e;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/zlib-1.3.1-slqujeue3l44x3pgqbu7e2pw3lh2mj5i;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/metis-5.1.0-puzeddu6ufhz5vpxnw7i3i2i6slsvuuj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-zadbjpethvnlhpqwx6vm6ahphk42a4gq;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/fmt-11.0.2-lpnox72rqcneew5hucwzq4nmms4at525;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.4.3/axom-develop-cdngjuz7ffr3goroyietece35jivxnkv/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.4.3/axom-develop-cdngjuz7ffr3goroyietece35jivxnkv/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/axom-develop-i4n5ay2l5l2hludfn6z5vyvfwk57p6hx/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/axom-develop-i4n5ay2l5l2hludfn6z5vyvfwk57p6hx/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.4.3/axom-develop-cdngjuz7ffr3goroyietece35jivxnkv/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.4.3/axom-develop-cdngjuz7ffr3goroyietece35jivxnkv/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/axom-develop-i4n5ay2l5l2hludfn6z5vyvfwk57p6hx/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/axom-develop-i4n5ay2l5l2hludfn6z5vyvfwk57p6hx/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") @@ -21,11 +21,11 @@ set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdclang" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdclang" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdclang++" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdclang++" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdflang" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdflang" CACHE PATH "") else() @@ -82,13 +82,15 @@ set(ENABLE_HIP ON CACHE BOOL "") set(ROCM_ROOT_DIR "/opt/rocm-6.4.3" CACHE PATH "") -set(CMAKE_EXE_LINKER_FLAGS "-lxpmem -L/opt/cray/pe/mpich/8.1.29/gtl/lib -Wl,-rpath,/opt/cray/pe/mpich/8.1.29/gtl/lib -lmpi_gtl_hsa -L/opt/rocm-6.4.3/lib/llvm/lib -Wl,-rpath,/opt/rocm-6.4.3/lib/llvm/lib -L/opt/rocm-6.4.3/lib -Wl,-rpath,/opt/rocm-6.4.3/lib -lpgmath -lompstub -Wl,--disable-new-dtags -lflang -lflangrti -lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " CACHE STRING "") +set(BLT_CMAKE_IMPLICIT_LINK_LIBRARIES_EXCLUDE "ompstub" CACHE STRING "") + +set(CMAKE_EXE_LINKER_FLAGS "-lxpmem -L/opt/cray/pe/mpich/8.1.29/gtl/lib -Wl,-rpath,/opt/cray/pe/mpich/8.1.29/gtl/lib -lmpi_gtl_hsa -L/opt/rocm-6.4.3/lib/llvm/lib -Wl,-rpath,/opt/rocm-6.4.3/lib/llvm/lib -L/opt/rocm-6.4.3/lib -Wl,-rpath,/opt/rocm-6.4.3/lib -lpgmath -Wl,--disable-new-dtags -lflang -lflangrti -lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " CACHE STRING "") #------------------------------------------------ # Hardware Specifics #------------------------------------------------ -set(ENABLE_OPENMP OFF CACHE BOOL "") +set(ENABLE_OPENMP ON CACHE BOOL "") set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") @@ -96,21 +98,21 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/llvm-amdgpu-6.4.3" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3" CACHE PATH "") set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-zqywrasos2vbears5hvdwxhulm3xox6q" CACHE PATH "") set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-h5kio7nseyazg4pzgvj4kxanb3plhbxm" CACHE PATH "") -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-g6vxdlgc7spurcdacgc6e5245iu2hyiw" CACHE PATH "") +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-6cgokeizyrjzsjzwmtf35p5jvhnws7n5" CACHE PATH "") set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-mgic2kxojd5zeivhaz473zzmzrg3gdqe" CACHE PATH "") set(LUA_DIR "${TPL_ROOT}/lua-5.4.8-gp4kdewsy4uiy6flsx342lgogcm7fym2" CACHE PATH "") -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-wavyomm5hzyjf3xuyr33txmnspvxveei" CACHE PATH "") +set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-v74yskgcb7tsgho6bdush5jdlbigkttf" CACHE PATH "") -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-4oytcvf556ksil4hsthjt4b5y4wdpaxw" CACHE PATH "") +set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-jft5rocv5ev4b57f3664b2kagyfjra2e" CACHE PATH "") # OPENCASCADE not built @@ -118,7 +120,7 @@ set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-cropyu42jr4q6k3zar6mkma6gc2vp6tk" CACHE P set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-rmlwpdacdtpzf4u72ctyroj72soqzf3f" CACHE PATH "") -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-3gtldngru7bva2reuel5iabyytw5fmdf" CACHE PATH "") +set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-zadbjpethvnlhpqwx6vm6ahphk42a4gq" CACHE PATH "") # scr not built @@ -148,12 +150,12 @@ set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-2kp35jcpb73xuujvcz6kmg4q2npek4gg/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5/lib/python3.13/site-packages" CACHE PATH "") set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-a3mo27shjtpliwewbm5cci2kf37crrab/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/none-none/py-pluggy-1.6.0-n4ojmtqyawxz6gipk64pup3ahwz3xqpu/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/py-pluggy-1.6.0-n4ojmtqyawxz6gipk64pup3ahwz3xqpu/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_37/none-none/py-iniconfig-2.1.0-lf3yvch65se22jr7b5unfoxtse4ugkrk/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/py-iniconfig-2.1.0-lf3yvch65se22jr7b5unfoxtse4ugkrk/lib/python3.13/site-packages" CACHE PATH "") diff --git a/host-configs/rzvernal-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake b/host-configs/rzvernal-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake index 0c4c3ff7a9..063f3a86e8 100644 --- a/host-configs/rzvernal-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake +++ b/host-configs/rzvernal-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake @@ -4,13 +4,13 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.29.2/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/cce-20.0.0/blt-0.7.1-tj6h3jr6fjdlxux2yd4wvdy7xwxxuh23;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/cce-20.0.0/c2c-1.8.0-xav3pjepsvpzrcqd5dcom4is4rioejzr;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/cce-20.0.0/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-37ef6g3godgxug45i3u3x26awgovbhxa;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/cce-20.0.0/conduit-0.9.5-oyjekm66om6cx7yndhpv3yumtxgaaroi;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/cce-20.0.0/lua-5.4.8-ty6vxrgtb5lsthp7rrcy7vx3x4yknrms;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/cce-20.0.0/mfem-4.9.0-qb5a5xcb5sx6mjeqqsuhxifqeikegol3;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/cce-20.0.0/py-nanobind-2.7.0-h7v5ggotyqa5ul7kpyc2yfojjkah4ium;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/cce-20.0.0/adiak-0.4.0-w7lj246x4whjefszgyhapkebe3cjd7bi;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/cce-20.0.0/libunwind-1.8.3-htd47rshpgaa6w73jemf7tycnnlkn5ws;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/cce-20.0.0/hdf5-1.8.23-vy4rnk6awhcfgte2wuwisgp3t3y4gzwy;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/cce-20.0.0/parmetis-4.0.3-jqlganykrtm62xb3ve2eetb3gltn5nki;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/cce-20.0.0/py-numpy-2.4.2-vdrslpslcsdjba3zyryp27z4tmpn3ayu;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/cce-20.0.0/hypre-2.27.0-gjrhbi2v66y2yfxgq7vegqpcawo2b35r;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/cce-20.0.0/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-mraqiovyi37bfdy7d3cpssvqibljl3rs;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/cce-20.0.0/umpire-2025.12.0-wvhebyygqkj3vxwm6quu3rxnzjnvwjix;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/cce-20.0.0/zlib-1.3.1-6yddik2qxvkwtfcckdhbdgos7awok5za;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/cce-20.0.0/metis-5.1.0-bryzqxg7sgprevvt3ux5np2uxam4wbvp;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/cce-20.0.0/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-s7ova7cnq6ibqaoxniya4loizwha2g3p;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/cce-20.0.0/fmt-11.0.2-vjndv6co4e6qonqazcmklw3pamvmos74;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cce-tce/cce-20.0.0;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.32-rocmcc-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/blt-0.7.1-tj6h3jr6fjdlxux2yd4wvdy7xwxxuh23;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/c2c-1.8.0-xav3pjepsvpzrcqd5dcom4is4rioejzr;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-37ef6g3godgxug45i3u3x26awgovbhxa;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/conduit-0.9.5-oyjekm66om6cx7yndhpv3yumtxgaaroi;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/lua-5.4.8-ty6vxrgtb5lsthp7rrcy7vx3x4yknrms;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/mfem-4.9.0-iiiowpg2cxn346mv67ycscrcrt5eacdk;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/py-nanobind-2.7.0-h7v5ggotyqa5ul7kpyc2yfojjkah4ium;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/adiak-0.4.0-w7lj246x4whjefszgyhapkebe3cjd7bi;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/libunwind-1.8.3-htd47rshpgaa6w73jemf7tycnnlkn5ws;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/hdf5-1.8.23-vy4rnk6awhcfgte2wuwisgp3t3y4gzwy;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/parmetis-4.0.3-jqlganykrtm62xb3ve2eetb3gltn5nki;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/py-numpy-2.4.2-vdrslpslcsdjba3zyryp27z4tmpn3ayu;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/hypre-2.27.0-gjrhbi2v66y2yfxgq7vegqpcawo2b35r;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-iwmhvbuhywrcyi4ojshrizfpccb53g3u;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/umpire-2025.12.0-qkp2thztbvdojybrli7tpxea5duxagkn;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/zlib-1.3.1-6yddik2qxvkwtfcckdhbdgos7awok5za;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/metis-5.1.0-bryzqxg7sgprevvt3ux5np2uxam4wbvp;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-xdstcq3vkkl6gysd7agincseoncdi5nz;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/fmt-11.0.2-vjndv6co4e6qonqazcmklw3pamvmos74;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cce-tce/cce-20.0.0;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.32-rocmcc-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/cce-20.0.0/axom-develop-qs7aomrdxkidtsbjs23bs7ou7kg37qya/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/cce-20.0.0/axom-develop-qs7aomrdxkidtsbjs23bs7ou7kg37qya/lib64;;/opt/cray/pe/cce/20.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/axom-develop-2xptn5vzdbqfiuuctci6jdklfwydwete/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/axom-develop-2xptn5vzdbqfiuuctci6jdklfwydwete/lib64;;/opt/cray/pe/cce/20.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/cce-20.0.0/axom-develop-qs7aomrdxkidtsbjs23bs7ou7kg37qya/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/cce-20.0.0/axom-develop-qs7aomrdxkidtsbjs23bs7ou7kg37qya/lib64;;/opt/cray/pe/cce/20.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/axom-develop-2xptn5vzdbqfiuuctci6jdklfwydwete/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/axom-develop-2xptn5vzdbqfiuuctci6jdklfwydwete/lib64;;/opt/cray/pe/cce/20.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") @@ -21,11 +21,11 @@ set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/cce/craycc" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/cce/craycc" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/cce/case-insensitive/crayCC" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/cce/case-insensitive/crayCC" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/cce/crayftn" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/cce/crayftn" CACHE PATH "") else() @@ -84,37 +84,41 @@ set(ENABLE_HIP ON CACHE BOOL "") set(ROCM_ROOT_DIR "/opt/rocm-6.4.3" CACHE PATH "") -set(BLT_CMAKE_IMPLICIT_LINK_LIBRARIES_EXCLUDE "unwind" CACHE STRING "") +set(BLT_CMAKE_IMPLICIT_LINK_LIBRARIES_EXCLUDE "unwind;ompstub" CACHE STRING "") -set(CMAKE_EXE_LINKER_FLAGS "-lxpmem -L/opt/cray/pe/mpich/8.1.32/gtl/lib -Wl,-rpath,/opt/cray/pe/mpich/8.1.32/gtl/lib -lmpi_gtl_hsa -L/opt/rocm-6.4.3/lib/llvm/lib -Wl,-rpath,/opt/rocm-6.4.3/lib/llvm/lib -L/opt/rocm-6.4.3/lib -Wl,-rpath,/opt/rocm-6.4.3/lib -lpgmath -lompstub -L/opt/cray/pe/cce/20.0.0/cce/x86_64/lib -Wl,-rpath,/opt/cray/pe/cce/20.0.0/cce/x86_64/lib -lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " CACHE STRING "") +set(CMAKE_EXE_LINKER_FLAGS "-lxpmem -L/opt/cray/pe/mpich/8.1.32/gtl/lib -Wl,-rpath,/opt/cray/pe/mpich/8.1.32/gtl/lib -lmpi_gtl_hsa -L/opt/rocm-6.4.3/lib/llvm/lib -Wl,-rpath,/opt/rocm-6.4.3/lib/llvm/lib -L/opt/rocm-6.4.3/lib -Wl,-rpath,/opt/rocm-6.4.3/lib -lpgmath -L/opt/cray/pe/cce/20.0.0/cce/x86_64/lib -Wl,-rpath,/opt/cray/pe/cce/20.0.0/cce/x86_64/lib -lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " CACHE STRING "") #------------------------------------------------ # Hardware Specifics #------------------------------------------------ -set(ENABLE_OPENMP OFF CACHE BOOL "") +set(ENABLE_OPENMP ON CACHE BOOL "") set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") +set(BLT_OPENMP_COMPILE_FLAGS "$<$>:-fopenmp=libomp>;$<$:-fopenmp>" CACHE STRING "Different OpenMP compile & link flags between HIP and CXX compilers (amdclang++)") + +set(BLT_OPENMP_LINK_FLAGS "$<$>:-fopenmp=libomp>;$<$:-fopenmp>" CACHE STRING "Different OpenMP compile & link flags between HIP and CXX compilers (amdclang++)") + #------------------------------------------------------------------------------ # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/cce-20.0.0" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0" CACHE PATH "") set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-oyjekm66om6cx7yndhpv3yumtxgaaroi" CACHE PATH "") set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-xav3pjepsvpzrcqd5dcom4is4rioejzr" CACHE PATH "") -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-qb5a5xcb5sx6mjeqqsuhxifqeikegol3" CACHE PATH "") +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-iiiowpg2cxn346mv67ycscrcrt5eacdk" CACHE PATH "") set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-vy4rnk6awhcfgte2wuwisgp3t3y4gzwy" CACHE PATH "") set(LUA_DIR "${TPL_ROOT}/lua-5.4.8-ty6vxrgtb5lsthp7rrcy7vx3x4yknrms" CACHE PATH "") -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-mraqiovyi37bfdy7d3cpssvqibljl3rs" CACHE PATH "") +set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-iwmhvbuhywrcyi4ojshrizfpccb53g3u" CACHE PATH "") -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-wvhebyygqkj3vxwm6quu3rxnzjnvwjix" CACHE PATH "") +set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-qkp2thztbvdojybrli7tpxea5duxagkn" CACHE PATH "") # OPENCASCADE not built @@ -122,7 +126,7 @@ set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-w7lj246x4whjefszgyhapkebe3cjd7bi" CACHE P set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-37ef6g3godgxug45i3u3x26awgovbhxa" CACHE PATH "") -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-s7ova7cnq6ibqaoxniya4loizwha2g3p" CACHE PATH "") +set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-xdstcq3vkkl6gysd7agincseoncdi5nz" CACHE PATH "") # scr not built @@ -152,12 +156,12 @@ set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-h7v5ggotyqa5ul7kpyc2yfojjkah4ium/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj/lib/python3.13/site-packages" CACHE PATH "") set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-vdrslpslcsdjba3zyryp27z4tmpn3ayu/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/none-none/py-pluggy-1.6.0-27aoz44dedjxa6j6r6cavpqyqmpknidb/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/py-pluggy-1.6.0-27aoz44dedjxa6j6r6cavpqyqmpknidb/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/none-none/py-iniconfig-2.1.0-7rty7n4wq6kxxrzflm7wbxqzhhc6ph7q/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/py-iniconfig-2.1.0-7rty7n4wq6kxxrzflm7wbxqzhhc6ph7q/lib/python3.13/site-packages" CACHE PATH "") diff --git a/host-configs/rzvernal-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake b/host-configs/rzvernal-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake index 46e596298d..08e87e85df 100644 --- a/host-configs/rzvernal-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake +++ b/host-configs/rzvernal-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake @@ -4,13 +4,13 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.29.2/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.3.1/adiak-0.4.0-vlugt2gwkx5wzlwudznxg3p2plq76pjq;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.3.1/blt-0.7.1-sv74qxlkpkll7mm6dhfjagyfog254w7c;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.3.1/c2c-1.8.0-tfrrofjzn3rdvyktevespobwrpjai5j4;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.3.1/conduit-0.9.5-vnlbr73jbwhqyvh2q76csdtve3ekpncc;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.3.1/lua-5.4.8-e3i2pbc52bfhrnxbaniektzp3x5jvyxr;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.3.1/mfem-4.9.0-lzyx65dulc7kbahov4m3zy5zsopxpfiz;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.3.1/py-nanobind-2.7.0-4sgpkalsntbmuim5oltb7ue4gggt6rv5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.3.1/hdf5-1.8.23-liive5kzeooveetbjhil4ic7hi3ccsg4;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.3.1/parmetis-4.0.3-fiyw3adearn3nmxjifcqtuzwy24fshp5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.3.1/py-numpy-2.4.2-ij66enxl4hmrmwua5bsec2yslylneqbw;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.3.1/hypre-2.27.0-cnukrpfl32kl5q6ud4wozdqbgtqoo7jx;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.3.1/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-aqopcekt2ikzah5tekoesewfzyexhz4q;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.3.1/umpire-2025.12.0-6jmr57w6t5ctmf4jgyrzb3y2j77a2clz;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.3.1/zlib-1.3.1-dsduxyfnks4bhrkpbskff35l5y36ofp7;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.3.1/metis-5.1.0-6mtxrj6jbrvyd3xpq4pvluywbndjx42w;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.3.1/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-eccm2e7wvkrekpfcvmbu6x3rtnip4oh3;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.3.1/fmt-11.0.2-kw3vpg2ajhrgi7kozhcioz3nrgc6fh3o;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.3.1;/opt/rocm-6.3.1;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.3.1;/opt/rocm-6.3.1;/opt/rocm-6.3.1" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.3.1/adiak-0.4.0-vlugt2gwkx5wzlwudznxg3p2plq76pjq;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.3.1/blt-0.7.1-sv74qxlkpkll7mm6dhfjagyfog254w7c;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.3.1/c2c-1.8.0-tfrrofjzn3rdvyktevespobwrpjai5j4;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.3.1/conduit-0.9.5-vnlbr73jbwhqyvh2q76csdtve3ekpncc;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.3.1/lua-5.4.8-e3i2pbc52bfhrnxbaniektzp3x5jvyxr;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.3.1/mfem-4.9.0-jxeezxksp3c7i53h6dhqf2zz6gnaipub;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.3.1/py-nanobind-2.7.0-4sgpkalsntbmuim5oltb7ue4gggt6rv5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.3.1/hdf5-1.8.23-liive5kzeooveetbjhil4ic7hi3ccsg4;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.3.1/parmetis-4.0.3-fiyw3adearn3nmxjifcqtuzwy24fshp5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.3.1/py-numpy-2.4.2-ij66enxl4hmrmwua5bsec2yslylneqbw;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.3.1/hypre-2.27.0-cnukrpfl32kl5q6ud4wozdqbgtqoo7jx;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.3.1/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-5c3lqbqcjmekrwbfeg6ogptz7ssj3n4y;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.3.1/umpire-2025.12.0-i2dwv2l2jra74eazijtvprcx7acsupx4;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.3.1/zlib-1.3.1-dsduxyfnks4bhrkpbskff35l5y36ofp7;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.3.1/metis-5.1.0-6mtxrj6jbrvyd3xpq4pvluywbndjx42w;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.3.1/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-7nvneljwisxwircaqydhfcl3hl65bmr2;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.3.1/fmt-11.0.2-kw3vpg2ajhrgi7kozhcioz3nrgc6fh3o;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.3.1;/opt/rocm-6.3.1;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.3.1;/opt/rocm-6.3.1;/opt/rocm-6.3.1" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.3.1/axom-develop-te7am32fqfkfq6tx6o5arqikygw5j36c/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.3.1/axom-develop-te7am32fqfkfq6tx6o5arqikygw5j36c/lib64;;/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.3.1/axom-develop-jx2n4ai2kjdjrg7o6oc6nrugd5kvsk24/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.3.1/axom-develop-jx2n4ai2kjdjrg7o6oc6nrugd5kvsk24/lib64;;/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.3.1/axom-develop-te7am32fqfkfq6tx6o5arqikygw5j36c/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.3.1/axom-develop-te7am32fqfkfq6tx6o5arqikygw5j36c/lib64;;/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.3.1/axom-develop-jx2n4ai2kjdjrg7o6oc6nrugd5kvsk24/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.3.1/axom-develop-jx2n4ai2kjdjrg7o6oc6nrugd5kvsk24/lib64;;/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") @@ -21,11 +21,11 @@ set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdclang" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdclang" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdclang++" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdclang++" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdflang" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdflang" CACHE PATH "") else() @@ -82,13 +82,15 @@ set(ENABLE_HIP ON CACHE BOOL "") set(ROCM_ROOT_DIR "/opt/rocm-6.3.1" CACHE PATH "") -set(CMAKE_EXE_LINKER_FLAGS "-lxpmem -L/opt/cray/pe/mpich/8.1.29/gtl/lib -Wl,-rpath,/opt/cray/pe/mpich/8.1.29/gtl/lib -lmpi_gtl_hsa -L/opt/rocm-6.3.1/lib/llvm/lib -Wl,-rpath,/opt/rocm-6.3.1/lib/llvm/lib -L/opt/rocm-6.3.1/lib -Wl,-rpath,/opt/rocm-6.3.1/lib -lpgmath -lompstub -Wl,--disable-new-dtags -lflang -lflangrti -lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " CACHE STRING "") +set(BLT_CMAKE_IMPLICIT_LINK_LIBRARIES_EXCLUDE "ompstub" CACHE STRING "") + +set(CMAKE_EXE_LINKER_FLAGS "-lxpmem -L/opt/cray/pe/mpich/8.1.29/gtl/lib -Wl,-rpath,/opt/cray/pe/mpich/8.1.29/gtl/lib -lmpi_gtl_hsa -L/opt/rocm-6.3.1/lib/llvm/lib -Wl,-rpath,/opt/rocm-6.3.1/lib/llvm/lib -L/opt/rocm-6.3.1/lib -Wl,-rpath,/opt/rocm-6.3.1/lib -lpgmath -Wl,--disable-new-dtags -lflang -lflangrti -lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " CACHE STRING "") #------------------------------------------------ # Hardware Specifics #------------------------------------------------ -set(ENABLE_OPENMP OFF CACHE BOOL "") +set(ENABLE_OPENMP ON CACHE BOOL "") set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") @@ -96,21 +98,21 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.3.1" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.3.1" CACHE PATH "") set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-vnlbr73jbwhqyvh2q76csdtve3ekpncc" CACHE PATH "") set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-tfrrofjzn3rdvyktevespobwrpjai5j4" CACHE PATH "") -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-lzyx65dulc7kbahov4m3zy5zsopxpfiz" CACHE PATH "") +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-jxeezxksp3c7i53h6dhqf2zz6gnaipub" CACHE PATH "") set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-liive5kzeooveetbjhil4ic7hi3ccsg4" CACHE PATH "") set(LUA_DIR "${TPL_ROOT}/lua-5.4.8-e3i2pbc52bfhrnxbaniektzp3x5jvyxr" CACHE PATH "") -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-aqopcekt2ikzah5tekoesewfzyexhz4q" CACHE PATH "") +set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-5c3lqbqcjmekrwbfeg6ogptz7ssj3n4y" CACHE PATH "") -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-6jmr57w6t5ctmf4jgyrzb3y2j77a2clz" CACHE PATH "") +set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-i2dwv2l2jra74eazijtvprcx7acsupx4" CACHE PATH "") # OPENCASCADE not built @@ -118,7 +120,7 @@ set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-vlugt2gwkx5wzlwudznxg3p2plq76pjq" CACHE P # CALIPER not built -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-eccm2e7wvkrekpfcvmbu6x3rtnip4oh3" CACHE PATH "") +set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-7nvneljwisxwircaqydhfcl3hl65bmr2" CACHE PATH "") # scr not built @@ -148,12 +150,12 @@ set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-4sgpkalsntbmuim5oltb7ue4gggt6rv5/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj/lib/python3.13/site-packages" CACHE PATH "") set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-ij66enxl4hmrmwua5bsec2yslylneqbw/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/none-none/py-pluggy-1.6.0-27aoz44dedjxa6j6r6cavpqyqmpknidb/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/py-pluggy-1.6.0-27aoz44dedjxa6j6r6cavpqyqmpknidb/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/none-none/py-iniconfig-2.1.0-7rty7n4wq6kxxrzflm7wbxqzhhc6ph7q/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/py-iniconfig-2.1.0-7rty7n4wq6kxxrzflm7wbxqzhhc6ph7q/lib/python3.13/site-packages" CACHE PATH "") diff --git a/host-configs/rzvernal-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake b/host-configs/rzvernal-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake index dbace9cbca..8ce11caa53 100644 --- a/host-configs/rzvernal-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake +++ b/host-configs/rzvernal-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake @@ -4,13 +4,13 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.29.2/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.4.3/blt-0.7.1-2eahqqwjyauupjq3baxftq7kuv3pvdgp;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.4.3/c2c-1.8.0-oxqhr2ybswpnzt74f4iritx263ji3uqy;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.4.3/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-sos5uklbsgaycb776a2ufcyje4y24yls;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.4.3/conduit-0.9.5-cxdq4qnoptjq6lry5asb2bqodvm3zjig;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.4.3/lua-5.4.8-lsey5vgxvpqf5j3xbrlzswrizuwm65vh;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.4.3/mfem-4.9.0-gkcg2u6vl5yur7gvigiut76ckzxo4fry;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.4.3/py-nanobind-2.7.0-3nchqpyclgle5w56it23ozl4ttbnrfjd;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.4.3/adiak-0.4.0-cbxp6kv6rm4f7oeczuhbplm3yb3zxb6z;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.4.3/libunwind-1.8.3-r4b3cptnf27zp34jbrdtlrf6r6dp6n4l;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.4.3/hdf5-1.8.23-wb7u6epuww3tretkcto7jhq2i7pffimg;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.4.3/parmetis-4.0.3-d6ed2qesb4yprvpetx3fso35rx6amf5b;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.4.3/py-numpy-2.4.2-t7cvohiksz7deeldwxrtqfmahcvanapb;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.4.3/hypre-2.27.0-pxqhlqwksiscco65tsz5somb4ctdq3q2;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.4.3/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-5r3ukxt2ewfn2geqge37ea2fxd7kfixh;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.4.3/umpire-2025.12.0-n4zr37loo2okonovcswffexwi5mbxnxh;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.4.3/zlib-1.3.1-ijxa3tdiibemxtng46hrremmbytl2dqq;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.4.3/metis-5.1.0-mubwzuka5byhpbzmwaxjhtffhst3oj3q;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.4.3/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-k6gr5qtzupytbr5kq2e73qesltauiqcl;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.4.3/fmt-11.0.2-unmvs6vefcyfcfnzlie2eag5i2eib35d;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/blt-0.7.1-2eahqqwjyauupjq3baxftq7kuv3pvdgp;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/c2c-1.8.0-oxqhr2ybswpnzt74f4iritx263ji3uqy;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-sos5uklbsgaycb776a2ufcyje4y24yls;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/conduit-0.9.5-cxdq4qnoptjq6lry5asb2bqodvm3zjig;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/lua-5.4.8-lsey5vgxvpqf5j3xbrlzswrizuwm65vh;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/mfem-4.9.0-xwok5jck2jjg76k4575wkzgyvgopipz2;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/py-nanobind-2.7.0-3nchqpyclgle5w56it23ozl4ttbnrfjd;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/adiak-0.4.0-cbxp6kv6rm4f7oeczuhbplm3yb3zxb6z;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/libunwind-1.8.3-r4b3cptnf27zp34jbrdtlrf6r6dp6n4l;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/hdf5-1.8.23-wb7u6epuww3tretkcto7jhq2i7pffimg;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/parmetis-4.0.3-d6ed2qesb4yprvpetx3fso35rx6amf5b;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/py-numpy-2.4.2-t7cvohiksz7deeldwxrtqfmahcvanapb;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/hypre-2.27.0-pxqhlqwksiscco65tsz5somb4ctdq3q2;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-i5stpeet653njjadpjsekbvxlqkng2zn;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/umpire-2025.12.0-hpevb7stqsg6dltp3t2tcnzzps7tgywb;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/zlib-1.3.1-ijxa3tdiibemxtng46hrremmbytl2dqq;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/metis-5.1.0-mubwzuka5byhpbzmwaxjhtffhst3oj3q;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-j7my6ilwzwzvbaxzscbvishmj5mgyxnz;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/fmt-11.0.2-unmvs6vefcyfcfnzlie2eag5i2eib35d;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.4.3/axom-develop-2ah5imlo6grdjcijxzmnj65v5lolnzam/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.4.3/axom-develop-2ah5imlo6grdjcijxzmnj65v5lolnzam/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/axom-develop-j5cg6odzkrlra4lrnhap3qtdbfgnwvlk/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/axom-develop-j5cg6odzkrlra4lrnhap3qtdbfgnwvlk/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.4.3/axom-develop-2ah5imlo6grdjcijxzmnj65v5lolnzam/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.4.3/axom-develop-2ah5imlo6grdjcijxzmnj65v5lolnzam/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/axom-develop-j5cg6odzkrlra4lrnhap3qtdbfgnwvlk/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/axom-develop-j5cg6odzkrlra4lrnhap3qtdbfgnwvlk/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") @@ -21,11 +21,11 @@ set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdclang" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdclang" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdclang++" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdclang++" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdflang" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdflang" CACHE PATH "") else() @@ -82,13 +82,15 @@ set(ENABLE_HIP ON CACHE BOOL "") set(ROCM_ROOT_DIR "/opt/rocm-6.4.3" CACHE PATH "") -set(CMAKE_EXE_LINKER_FLAGS "-lxpmem -L/opt/cray/pe/mpich/8.1.29/gtl/lib -Wl,-rpath,/opt/cray/pe/mpich/8.1.29/gtl/lib -lmpi_gtl_hsa -L/opt/rocm-6.4.3/lib/llvm/lib -Wl,-rpath,/opt/rocm-6.4.3/lib/llvm/lib -L/opt/rocm-6.4.3/lib -Wl,-rpath,/opt/rocm-6.4.3/lib -lpgmath -lompstub -Wl,--disable-new-dtags -lflang -lflangrti -lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " CACHE STRING "") +set(BLT_CMAKE_IMPLICIT_LINK_LIBRARIES_EXCLUDE "ompstub" CACHE STRING "") + +set(CMAKE_EXE_LINKER_FLAGS "-lxpmem -L/opt/cray/pe/mpich/8.1.29/gtl/lib -Wl,-rpath,/opt/cray/pe/mpich/8.1.29/gtl/lib -lmpi_gtl_hsa -L/opt/rocm-6.4.3/lib/llvm/lib -Wl,-rpath,/opt/rocm-6.4.3/lib/llvm/lib -L/opt/rocm-6.4.3/lib -Wl,-rpath,/opt/rocm-6.4.3/lib -lpgmath -Wl,--disable-new-dtags -lflang -lflangrti -lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " CACHE STRING "") #------------------------------------------------ # Hardware Specifics #------------------------------------------------ -set(ENABLE_OPENMP OFF CACHE BOOL "") +set(ENABLE_OPENMP ON CACHE BOOL "") set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") @@ -96,21 +98,21 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/llvm-amdgpu-6.4.3" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3" CACHE PATH "") set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-cxdq4qnoptjq6lry5asb2bqodvm3zjig" CACHE PATH "") set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-oxqhr2ybswpnzt74f4iritx263ji3uqy" CACHE PATH "") -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-gkcg2u6vl5yur7gvigiut76ckzxo4fry" CACHE PATH "") +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-xwok5jck2jjg76k4575wkzgyvgopipz2" CACHE PATH "") set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-wb7u6epuww3tretkcto7jhq2i7pffimg" CACHE PATH "") set(LUA_DIR "${TPL_ROOT}/lua-5.4.8-lsey5vgxvpqf5j3xbrlzswrizuwm65vh" CACHE PATH "") -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-5r3ukxt2ewfn2geqge37ea2fxd7kfixh" CACHE PATH "") +set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-i5stpeet653njjadpjsekbvxlqkng2zn" CACHE PATH "") -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-n4zr37loo2okonovcswffexwi5mbxnxh" CACHE PATH "") +set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-hpevb7stqsg6dltp3t2tcnzzps7tgywb" CACHE PATH "") # OPENCASCADE not built @@ -118,7 +120,7 @@ set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-cbxp6kv6rm4f7oeczuhbplm3yb3zxb6z" CACHE P set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-sos5uklbsgaycb776a2ufcyje4y24yls" CACHE PATH "") -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-k6gr5qtzupytbr5kq2e73qesltauiqcl" CACHE PATH "") +set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-j7my6ilwzwzvbaxzscbvishmj5mgyxnz" CACHE PATH "") # scr not built @@ -148,12 +150,12 @@ set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-3nchqpyclgle5w56it23ozl4ttbnrfjd/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj/lib/python3.13/site-packages" CACHE PATH "") set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-t7cvohiksz7deeldwxrtqfmahcvanapb/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/none-none/py-pluggy-1.6.0-27aoz44dedjxa6j6r6cavpqyqmpknidb/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/py-pluggy-1.6.0-27aoz44dedjxa6j6r6cavpqyqmpknidb/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_11_12_30/none-none/py-iniconfig-2.1.0-7rty7n4wq6kxxrzflm7wbxqzhhc6ph7q/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/py-iniconfig-2.1.0-7rty7n4wq6kxxrzflm7wbxqzhhc6ph7q/lib/python3.13/site-packages" CACHE PATH "") From 47ca5e62326c8d9512f7b60c27b108173da32997 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Tue, 21 Apr 2026 08:53:40 -0700 Subject: [PATCH 133/986] Add OpenMP amdclang++ specific flags to axom examples for %cce --- scripts/spack/packages/axom/package.py | 2 +- src/examples/radiuss_tutorial/host-config.cmake.in | 7 +++++++ src/examples/shaping_tutorial/host-config.cmake.in | 6 ++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/scripts/spack/packages/axom/package.py b/scripts/spack/packages/axom/package.py index c4ea8718a2..843f5e31ca 100644 --- a/scripts/spack/packages/axom/package.py +++ b/scripts/spack/packages/axom/package.py @@ -580,7 +580,7 @@ def initconfig_hardware_entries(self): "Fortran>:-fopenmp>" ) - description = "Different OpenMP compile & link flags between HIP and CXX compilers" + description = "Different OpenMP compile & link flags between HIP and CXX compilers (amdclang++)" entries.append( cmake_cache_string("BLT_OPENMP_COMPILE_FLAGS", openmp_gen_exp, description) ) diff --git a/src/examples/radiuss_tutorial/host-config.cmake.in b/src/examples/radiuss_tutorial/host-config.cmake.in index f984b3749a..c3f78754d9 100644 --- a/src/examples/radiuss_tutorial/host-config.cmake.in +++ b/src/examples/radiuss_tutorial/host-config.cmake.in @@ -58,6 +58,13 @@ if(ENABLE_HIP) # Add optimization flag workaround for Debug builds with cray compiler if(CMAKE_CXX_COMPILER MATCHES "crayCC") set(CMAKE_CXX_FLAGS_DEBUG "-O1 -g -DNDEBUG" CACHE STRING "") + + if(ENABLE_OPENMP) + set(BLT_OPENMP_COMPILE_FLAGS "$<$>:-fopenmp=libomp>;$<$:-fopenmp>" CACHE STRING "Different OpenMP compile & link flags between HIP and CXX compilers (amdclang++)") + + set(BLT_OPENMP_LINK_FLAGS "$<$>:-fopenmp=libomp>;$<$:-fopenmp>" CACHE STRING "Different OpenMP compile & link flags between HIP and CXX compilers (amdclang++)") + endif() + endif() set(CMAKE_HIP_COMPILER "@CMAKE_HIP_COMPILER@" CACHE PATH "") set(ROCM_PATH "@ROCM_PATH@" CACHE PATH "") diff --git a/src/examples/shaping_tutorial/host-config.cmake.in b/src/examples/shaping_tutorial/host-config.cmake.in index a541bbdbe9..ac507e0d75 100644 --- a/src/examples/shaping_tutorial/host-config.cmake.in +++ b/src/examples/shaping_tutorial/host-config.cmake.in @@ -58,6 +58,12 @@ if(ENABLE_HIP) # Add optimization flag workaround for Debug builds with cray compiler if(CMAKE_CXX_COMPILER MATCHES "crayCC") set(CMAKE_CXX_FLAGS_DEBUG "-O1 -g -DNDEBUG" CACHE STRING "") + + if(ENABLE_OPENMP) + set(BLT_OPENMP_COMPILE_FLAGS "$<$>:-fopenmp=libomp>;$<$:-fopenmp>" CACHE STRING "Different OpenMP compile & link flags between HIP and CXX compilers (amdclang++)") + + set(BLT_OPENMP_LINK_FLAGS "$<$>:-fopenmp=libomp>;$<$:-fopenmp>" CACHE STRING "Different OpenMP compile & link flags between HIP and CXX compilers (amdclang++)") + endif() endif() set(CMAKE_HIP_COMPILER "@CMAKE_HIP_COMPILER@" CACHE PATH "") set(ROCM_PATH "@ROCM_PATH@" CACHE PATH "") From 7d33b096782db6ed8a864235871bc76b9cea98db Mon Sep 17 00:00:00 2001 From: Brian Han Date: Tue, 21 Apr 2026 09:02:51 -0700 Subject: [PATCH 134/986] Update CZ host-configs --- ...toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake | 38 ++++++++++--------- ...x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake | 34 +++++++++-------- ...x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake | 34 +++++++++-------- ...toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake | 38 ++++++++++--------- ...x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake | 34 +++++++++-------- ...x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake | 34 +++++++++-------- 6 files changed, 114 insertions(+), 98 deletions(-) diff --git a/host-configs/tioga-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake b/host-configs/tioga-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake index 480fd4ea6f..3733dd79f9 100644 --- a/host-configs/tioga-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake +++ b/host-configs/tioga-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake @@ -4,13 +4,13 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.29.2/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/cce-20.0.0/blt-0.7.1-tj6h3jr6fjdlxux2yd4wvdy7xwxxuh23;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/cce-20.0.0/c2c-1.8.0-xav3pjepsvpzrcqd5dcom4is4rioejzr;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/cce-20.0.0/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-37ef6g3godgxug45i3u3x26awgovbhxa;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/cce-20.0.0/conduit-0.9.5-oyjekm66om6cx7yndhpv3yumtxgaaroi;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/cce-20.0.0/lua-5.4.8-ty6vxrgtb5lsthp7rrcy7vx3x4yknrms;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/cce-20.0.0/mfem-4.9.0-qb5a5xcb5sx6mjeqqsuhxifqeikegol3;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/cce-20.0.0/py-nanobind-2.7.0-h7v5ggotyqa5ul7kpyc2yfojjkah4ium;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/cce-20.0.0/adiak-0.4.0-w7lj246x4whjefszgyhapkebe3cjd7bi;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/cce-20.0.0/libunwind-1.8.3-htd47rshpgaa6w73jemf7tycnnlkn5ws;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/cce-20.0.0/hdf5-1.8.23-vy4rnk6awhcfgte2wuwisgp3t3y4gzwy;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/cce-20.0.0/parmetis-4.0.3-jqlganykrtm62xb3ve2eetb3gltn5nki;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/cce-20.0.0/py-numpy-2.4.2-vdrslpslcsdjba3zyryp27z4tmpn3ayu;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/cce-20.0.0/hypre-2.27.0-gjrhbi2v66y2yfxgq7vegqpcawo2b35r;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/cce-20.0.0/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-mraqiovyi37bfdy7d3cpssvqibljl3rs;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/cce-20.0.0/umpire-2025.12.0-wvhebyygqkj3vxwm6quu3rxnzjnvwjix;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/cce-20.0.0/zlib-1.3.1-6yddik2qxvkwtfcckdhbdgos7awok5za;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/cce-20.0.0/metis-5.1.0-bryzqxg7sgprevvt3ux5np2uxam4wbvp;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/cce-20.0.0/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-s7ova7cnq6ibqaoxniya4loizwha2g3p;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/cce-20.0.0/fmt-11.0.2-vjndv6co4e6qonqazcmklw3pamvmos74;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cce-tce/cce-20.0.0;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.32-rocmcc-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/blt-0.7.1-tj6h3jr6fjdlxux2yd4wvdy7xwxxuh23;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/c2c-1.8.0-xav3pjepsvpzrcqd5dcom4is4rioejzr;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-37ef6g3godgxug45i3u3x26awgovbhxa;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/conduit-0.9.5-oyjekm66om6cx7yndhpv3yumtxgaaroi;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/lua-5.4.8-ty6vxrgtb5lsthp7rrcy7vx3x4yknrms;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/mfem-4.9.0-iiiowpg2cxn346mv67ycscrcrt5eacdk;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/py-nanobind-2.7.0-h7v5ggotyqa5ul7kpyc2yfojjkah4ium;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/adiak-0.4.0-w7lj246x4whjefszgyhapkebe3cjd7bi;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/libunwind-1.8.3-htd47rshpgaa6w73jemf7tycnnlkn5ws;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/hdf5-1.8.23-vy4rnk6awhcfgte2wuwisgp3t3y4gzwy;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/parmetis-4.0.3-jqlganykrtm62xb3ve2eetb3gltn5nki;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/py-numpy-2.4.2-vdrslpslcsdjba3zyryp27z4tmpn3ayu;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/hypre-2.27.0-gjrhbi2v66y2yfxgq7vegqpcawo2b35r;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-iwmhvbuhywrcyi4ojshrizfpccb53g3u;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/umpire-2025.12.0-qkp2thztbvdojybrli7tpxea5duxagkn;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/zlib-1.3.1-6yddik2qxvkwtfcckdhbdgos7awok5za;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/metis-5.1.0-bryzqxg7sgprevvt3ux5np2uxam4wbvp;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-xdstcq3vkkl6gysd7agincseoncdi5nz;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/fmt-11.0.2-vjndv6co4e6qonqazcmklw3pamvmos74;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cce-tce/cce-20.0.0;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.32-rocmcc-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/cce-20.0.0/axom-develop-qs7aomrdxkidtsbjs23bs7ou7kg37qya/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/cce-20.0.0/axom-develop-qs7aomrdxkidtsbjs23bs7ou7kg37qya/lib64;;/opt/cray/pe/cce/20.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/axom-develop-2xptn5vzdbqfiuuctci6jdklfwydwete/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/axom-develop-2xptn5vzdbqfiuuctci6jdklfwydwete/lib64;;/opt/cray/pe/cce/20.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/cce-20.0.0/axom-develop-qs7aomrdxkidtsbjs23bs7ou7kg37qya/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/cce-20.0.0/axom-develop-qs7aomrdxkidtsbjs23bs7ou7kg37qya/lib64;;/opt/cray/pe/cce/20.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/axom-develop-2xptn5vzdbqfiuuctci6jdklfwydwete/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/axom-develop-2xptn5vzdbqfiuuctci6jdklfwydwete/lib64;;/opt/cray/pe/cce/20.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") @@ -21,11 +21,11 @@ set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/cce/craycc" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/cce/craycc" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/cce/case-insensitive/crayCC" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/cce/case-insensitive/crayCC" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/cce/crayftn" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/cce/crayftn" CACHE PATH "") else() @@ -84,37 +84,41 @@ set(ENABLE_HIP ON CACHE BOOL "") set(ROCM_ROOT_DIR "/opt/rocm-6.4.3" CACHE PATH "") -set(BLT_CMAKE_IMPLICIT_LINK_LIBRARIES_EXCLUDE "unwind" CACHE STRING "") +set(BLT_CMAKE_IMPLICIT_LINK_LIBRARIES_EXCLUDE "unwind;ompstub" CACHE STRING "") -set(CMAKE_EXE_LINKER_FLAGS "-lxpmem -L/opt/cray/pe/mpich/8.1.32/gtl/lib -Wl,-rpath,/opt/cray/pe/mpich/8.1.32/gtl/lib -lmpi_gtl_hsa -L/opt/rocm-6.4.3/lib/llvm/lib -Wl,-rpath,/opt/rocm-6.4.3/lib/llvm/lib -L/opt/rocm-6.4.3/lib -Wl,-rpath,/opt/rocm-6.4.3/lib -lpgmath -lompstub -L/opt/cray/pe/cce/20.0.0/cce/x86_64/lib -Wl,-rpath,/opt/cray/pe/cce/20.0.0/cce/x86_64/lib -lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " CACHE STRING "") +set(CMAKE_EXE_LINKER_FLAGS "-lxpmem -L/opt/cray/pe/mpich/8.1.32/gtl/lib -Wl,-rpath,/opt/cray/pe/mpich/8.1.32/gtl/lib -lmpi_gtl_hsa -L/opt/rocm-6.4.3/lib/llvm/lib -Wl,-rpath,/opt/rocm-6.4.3/lib/llvm/lib -L/opt/rocm-6.4.3/lib -Wl,-rpath,/opt/rocm-6.4.3/lib -lpgmath -L/opt/cray/pe/cce/20.0.0/cce/x86_64/lib -Wl,-rpath,/opt/cray/pe/cce/20.0.0/cce/x86_64/lib -lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " CACHE STRING "") #------------------------------------------------ # Hardware Specifics #------------------------------------------------ -set(ENABLE_OPENMP OFF CACHE BOOL "") +set(ENABLE_OPENMP ON CACHE BOOL "") set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") +set(BLT_OPENMP_COMPILE_FLAGS "$<$>:-fopenmp=libomp>;$<$:-fopenmp>" CACHE STRING "Different OpenMP compile & link flags between HIP and CXX compilers (amdclang++)") + +set(BLT_OPENMP_LINK_FLAGS "$<$>:-fopenmp=libomp>;$<$:-fopenmp>" CACHE STRING "Different OpenMP compile & link flags between HIP and CXX compilers (amdclang++)") + #------------------------------------------------------------------------------ # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/cce-20.0.0" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0" CACHE PATH "") set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-oyjekm66om6cx7yndhpv3yumtxgaaroi" CACHE PATH "") set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-xav3pjepsvpzrcqd5dcom4is4rioejzr" CACHE PATH "") -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-qb5a5xcb5sx6mjeqqsuhxifqeikegol3" CACHE PATH "") +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-iiiowpg2cxn346mv67ycscrcrt5eacdk" CACHE PATH "") set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-vy4rnk6awhcfgte2wuwisgp3t3y4gzwy" CACHE PATH "") set(LUA_DIR "${TPL_ROOT}/lua-5.4.8-ty6vxrgtb5lsthp7rrcy7vx3x4yknrms" CACHE PATH "") -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-mraqiovyi37bfdy7d3cpssvqibljl3rs" CACHE PATH "") +set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-iwmhvbuhywrcyi4ojshrizfpccb53g3u" CACHE PATH "") -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-wvhebyygqkj3vxwm6quu3rxnzjnvwjix" CACHE PATH "") +set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-qkp2thztbvdojybrli7tpxea5duxagkn" CACHE PATH "") # OPENCASCADE not built @@ -122,7 +126,7 @@ set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-w7lj246x4whjefszgyhapkebe3cjd7bi" CACHE P set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-37ef6g3godgxug45i3u3x26awgovbhxa" CACHE PATH "") -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-s7ova7cnq6ibqaoxniya4loizwha2g3p" CACHE PATH "") +set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-xdstcq3vkkl6gysd7agincseoncdi5nz" CACHE PATH "") # scr not built @@ -152,12 +156,12 @@ set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-h7v5ggotyqa5ul7kpyc2yfojjkah4ium/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj/lib/python3.13/site-packages" CACHE PATH "") set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-vdrslpslcsdjba3zyryp27z4tmpn3ayu/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/none-none/py-pluggy-1.6.0-27aoz44dedjxa6j6r6cavpqyqmpknidb/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/py-pluggy-1.6.0-27aoz44dedjxa6j6r6cavpqyqmpknidb/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/none-none/py-iniconfig-2.1.0-7rty7n4wq6kxxrzflm7wbxqzhhc6ph7q/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/py-iniconfig-2.1.0-7rty7n4wq6kxxrzflm7wbxqzhhc6ph7q/lib/python3.13/site-packages" CACHE PATH "") diff --git a/host-configs/tioga-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake b/host-configs/tioga-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake index 8bc3ff8312..d9a623034b 100644 --- a/host-configs/tioga-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake +++ b/host-configs/tioga-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake @@ -4,13 +4,13 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.29.2/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.3.1/adiak-0.4.0-vlugt2gwkx5wzlwudznxg3p2plq76pjq;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.3.1/blt-0.7.1-sv74qxlkpkll7mm6dhfjagyfog254w7c;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.3.1/c2c-1.8.0-tfrrofjzn3rdvyktevespobwrpjai5j4;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.3.1/conduit-0.9.5-vnlbr73jbwhqyvh2q76csdtve3ekpncc;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.3.1/lua-5.4.8-e3i2pbc52bfhrnxbaniektzp3x5jvyxr;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.3.1/mfem-4.9.0-lzyx65dulc7kbahov4m3zy5zsopxpfiz;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.3.1/py-nanobind-2.7.0-4sgpkalsntbmuim5oltb7ue4gggt6rv5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.3.1/hdf5-1.8.23-liive5kzeooveetbjhil4ic7hi3ccsg4;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.3.1/parmetis-4.0.3-fiyw3adearn3nmxjifcqtuzwy24fshp5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.3.1/py-numpy-2.4.2-ij66enxl4hmrmwua5bsec2yslylneqbw;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.3.1/hypre-2.27.0-cnukrpfl32kl5q6ud4wozdqbgtqoo7jx;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.3.1/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-aqopcekt2ikzah5tekoesewfzyexhz4q;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.3.1/umpire-2025.12.0-6jmr57w6t5ctmf4jgyrzb3y2j77a2clz;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.3.1/zlib-1.3.1-dsduxyfnks4bhrkpbskff35l5y36ofp7;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.3.1/metis-5.1.0-6mtxrj6jbrvyd3xpq4pvluywbndjx42w;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.3.1/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-eccm2e7wvkrekpfcvmbu6x3rtnip4oh3;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.3.1/fmt-11.0.2-kw3vpg2ajhrgi7kozhcioz3nrgc6fh3o;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.3.1;/opt/rocm-6.3.1;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.3.1;/opt/rocm-6.3.1;/opt/rocm-6.3.1" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.3.1/adiak-0.4.0-vlugt2gwkx5wzlwudznxg3p2plq76pjq;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.3.1/blt-0.7.1-sv74qxlkpkll7mm6dhfjagyfog254w7c;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.3.1/c2c-1.8.0-tfrrofjzn3rdvyktevespobwrpjai5j4;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.3.1/conduit-0.9.5-vnlbr73jbwhqyvh2q76csdtve3ekpncc;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.3.1/lua-5.4.8-e3i2pbc52bfhrnxbaniektzp3x5jvyxr;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.3.1/mfem-4.9.0-jxeezxksp3c7i53h6dhqf2zz6gnaipub;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.3.1/py-nanobind-2.7.0-4sgpkalsntbmuim5oltb7ue4gggt6rv5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.3.1/hdf5-1.8.23-liive5kzeooveetbjhil4ic7hi3ccsg4;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.3.1/parmetis-4.0.3-fiyw3adearn3nmxjifcqtuzwy24fshp5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.3.1/py-numpy-2.4.2-ij66enxl4hmrmwua5bsec2yslylneqbw;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.3.1/hypre-2.27.0-cnukrpfl32kl5q6ud4wozdqbgtqoo7jx;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.3.1/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-5c3lqbqcjmekrwbfeg6ogptz7ssj3n4y;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.3.1/umpire-2025.12.0-i2dwv2l2jra74eazijtvprcx7acsupx4;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.3.1/zlib-1.3.1-dsduxyfnks4bhrkpbskff35l5y36ofp7;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.3.1/metis-5.1.0-6mtxrj6jbrvyd3xpq4pvluywbndjx42w;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.3.1/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-7nvneljwisxwircaqydhfcl3hl65bmr2;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.3.1/fmt-11.0.2-kw3vpg2ajhrgi7kozhcioz3nrgc6fh3o;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.3.1;/opt/rocm-6.3.1;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.3.1;/opt/rocm-6.3.1;/opt/rocm-6.3.1" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.3.1/axom-develop-te7am32fqfkfq6tx6o5arqikygw5j36c/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.3.1/axom-develop-te7am32fqfkfq6tx6o5arqikygw5j36c/lib64;;/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.3.1/axom-develop-jx2n4ai2kjdjrg7o6oc6nrugd5kvsk24/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.3.1/axom-develop-jx2n4ai2kjdjrg7o6oc6nrugd5kvsk24/lib64;;/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.3.1/axom-develop-te7am32fqfkfq6tx6o5arqikygw5j36c/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.3.1/axom-develop-te7am32fqfkfq6tx6o5arqikygw5j36c/lib64;;/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.3.1/axom-develop-jx2n4ai2kjdjrg7o6oc6nrugd5kvsk24/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.3.1/axom-develop-jx2n4ai2kjdjrg7o6oc6nrugd5kvsk24/lib64;;/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") @@ -21,11 +21,11 @@ set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdclang" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdclang" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdclang++" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdclang++" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdflang" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdflang" CACHE PATH "") else() @@ -82,13 +82,15 @@ set(ENABLE_HIP ON CACHE BOOL "") set(ROCM_ROOT_DIR "/opt/rocm-6.3.1" CACHE PATH "") -set(CMAKE_EXE_LINKER_FLAGS "-lxpmem -L/opt/cray/pe/mpich/8.1.29/gtl/lib -Wl,-rpath,/opt/cray/pe/mpich/8.1.29/gtl/lib -lmpi_gtl_hsa -L/opt/rocm-6.3.1/lib/llvm/lib -Wl,-rpath,/opt/rocm-6.3.1/lib/llvm/lib -L/opt/rocm-6.3.1/lib -Wl,-rpath,/opt/rocm-6.3.1/lib -lpgmath -lompstub -Wl,--disable-new-dtags -lflang -lflangrti -lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " CACHE STRING "") +set(BLT_CMAKE_IMPLICIT_LINK_LIBRARIES_EXCLUDE "ompstub" CACHE STRING "") + +set(CMAKE_EXE_LINKER_FLAGS "-lxpmem -L/opt/cray/pe/mpich/8.1.29/gtl/lib -Wl,-rpath,/opt/cray/pe/mpich/8.1.29/gtl/lib -lmpi_gtl_hsa -L/opt/rocm-6.3.1/lib/llvm/lib -Wl,-rpath,/opt/rocm-6.3.1/lib/llvm/lib -L/opt/rocm-6.3.1/lib -Wl,-rpath,/opt/rocm-6.3.1/lib -lpgmath -Wl,--disable-new-dtags -lflang -lflangrti -lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " CACHE STRING "") #------------------------------------------------ # Hardware Specifics #------------------------------------------------ -set(ENABLE_OPENMP OFF CACHE BOOL "") +set(ENABLE_OPENMP ON CACHE BOOL "") set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") @@ -96,21 +98,21 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.3.1" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.3.1" CACHE PATH "") set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-vnlbr73jbwhqyvh2q76csdtve3ekpncc" CACHE PATH "") set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-tfrrofjzn3rdvyktevespobwrpjai5j4" CACHE PATH "") -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-lzyx65dulc7kbahov4m3zy5zsopxpfiz" CACHE PATH "") +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-jxeezxksp3c7i53h6dhqf2zz6gnaipub" CACHE PATH "") set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-liive5kzeooveetbjhil4ic7hi3ccsg4" CACHE PATH "") set(LUA_DIR "${TPL_ROOT}/lua-5.4.8-e3i2pbc52bfhrnxbaniektzp3x5jvyxr" CACHE PATH "") -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-aqopcekt2ikzah5tekoesewfzyexhz4q" CACHE PATH "") +set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-5c3lqbqcjmekrwbfeg6ogptz7ssj3n4y" CACHE PATH "") -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-6jmr57w6t5ctmf4jgyrzb3y2j77a2clz" CACHE PATH "") +set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-i2dwv2l2jra74eazijtvprcx7acsupx4" CACHE PATH "") # OPENCASCADE not built @@ -118,7 +120,7 @@ set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-vlugt2gwkx5wzlwudznxg3p2plq76pjq" CACHE P # CALIPER not built -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-eccm2e7wvkrekpfcvmbu6x3rtnip4oh3" CACHE PATH "") +set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-7nvneljwisxwircaqydhfcl3hl65bmr2" CACHE PATH "") # scr not built @@ -148,12 +150,12 @@ set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-4sgpkalsntbmuim5oltb7ue4gggt6rv5/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj/lib/python3.13/site-packages" CACHE PATH "") set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-ij66enxl4hmrmwua5bsec2yslylneqbw/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/none-none/py-pluggy-1.6.0-27aoz44dedjxa6j6r6cavpqyqmpknidb/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/py-pluggy-1.6.0-27aoz44dedjxa6j6r6cavpqyqmpknidb/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/none-none/py-iniconfig-2.1.0-7rty7n4wq6kxxrzflm7wbxqzhhc6ph7q/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/py-iniconfig-2.1.0-7rty7n4wq6kxxrzflm7wbxqzhhc6ph7q/lib/python3.13/site-packages" CACHE PATH "") diff --git a/host-configs/tioga-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake b/host-configs/tioga-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake index 90732cfc93..bfd1af74c7 100644 --- a/host-configs/tioga-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake +++ b/host-configs/tioga-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake @@ -4,13 +4,13 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.29.2/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.4.3/blt-0.7.1-2eahqqwjyauupjq3baxftq7kuv3pvdgp;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.4.3/c2c-1.8.0-oxqhr2ybswpnzt74f4iritx263ji3uqy;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.4.3/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-sos5uklbsgaycb776a2ufcyje4y24yls;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.4.3/conduit-0.9.5-cxdq4qnoptjq6lry5asb2bqodvm3zjig;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.4.3/lua-5.4.8-lsey5vgxvpqf5j3xbrlzswrizuwm65vh;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.4.3/mfem-4.9.0-gkcg2u6vl5yur7gvigiut76ckzxo4fry;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.4.3/py-nanobind-2.7.0-3nchqpyclgle5w56it23ozl4ttbnrfjd;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.4.3/adiak-0.4.0-cbxp6kv6rm4f7oeczuhbplm3yb3zxb6z;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.4.3/libunwind-1.8.3-r4b3cptnf27zp34jbrdtlrf6r6dp6n4l;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.4.3/hdf5-1.8.23-wb7u6epuww3tretkcto7jhq2i7pffimg;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.4.3/parmetis-4.0.3-d6ed2qesb4yprvpetx3fso35rx6amf5b;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.4.3/py-numpy-2.4.2-t7cvohiksz7deeldwxrtqfmahcvanapb;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.4.3/hypre-2.27.0-pxqhlqwksiscco65tsz5somb4ctdq3q2;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.4.3/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-5r3ukxt2ewfn2geqge37ea2fxd7kfixh;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.4.3/umpire-2025.12.0-n4zr37loo2okonovcswffexwi5mbxnxh;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.4.3/zlib-1.3.1-ijxa3tdiibemxtng46hrremmbytl2dqq;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.4.3/metis-5.1.0-mubwzuka5byhpbzmwaxjhtffhst3oj3q;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.4.3/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-k6gr5qtzupytbr5kq2e73qesltauiqcl;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.4.3/fmt-11.0.2-unmvs6vefcyfcfnzlie2eag5i2eib35d;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/blt-0.7.1-2eahqqwjyauupjq3baxftq7kuv3pvdgp;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/c2c-1.8.0-oxqhr2ybswpnzt74f4iritx263ji3uqy;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-sos5uklbsgaycb776a2ufcyje4y24yls;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/conduit-0.9.5-cxdq4qnoptjq6lry5asb2bqodvm3zjig;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/lua-5.4.8-lsey5vgxvpqf5j3xbrlzswrizuwm65vh;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/mfem-4.9.0-xwok5jck2jjg76k4575wkzgyvgopipz2;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/py-nanobind-2.7.0-3nchqpyclgle5w56it23ozl4ttbnrfjd;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/adiak-0.4.0-cbxp6kv6rm4f7oeczuhbplm3yb3zxb6z;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/libunwind-1.8.3-r4b3cptnf27zp34jbrdtlrf6r6dp6n4l;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/hdf5-1.8.23-wb7u6epuww3tretkcto7jhq2i7pffimg;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/parmetis-4.0.3-d6ed2qesb4yprvpetx3fso35rx6amf5b;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/py-numpy-2.4.2-t7cvohiksz7deeldwxrtqfmahcvanapb;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/hypre-2.27.0-pxqhlqwksiscco65tsz5somb4ctdq3q2;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-i5stpeet653njjadpjsekbvxlqkng2zn;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/umpire-2025.12.0-hpevb7stqsg6dltp3t2tcnzzps7tgywb;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/zlib-1.3.1-ijxa3tdiibemxtng46hrremmbytl2dqq;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/metis-5.1.0-mubwzuka5byhpbzmwaxjhtffhst3oj3q;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-j7my6ilwzwzvbaxzscbvishmj5mgyxnz;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/fmt-11.0.2-unmvs6vefcyfcfnzlie2eag5i2eib35d;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.4.3/axom-develop-2ah5imlo6grdjcijxzmnj65v5lolnzam/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.4.3/axom-develop-2ah5imlo6grdjcijxzmnj65v5lolnzam/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/axom-develop-j5cg6odzkrlra4lrnhap3qtdbfgnwvlk/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/axom-develop-j5cg6odzkrlra4lrnhap3qtdbfgnwvlk/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.4.3/axom-develop-2ah5imlo6grdjcijxzmnj65v5lolnzam/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.4.3/axom-develop-2ah5imlo6grdjcijxzmnj65v5lolnzam/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/axom-develop-j5cg6odzkrlra4lrnhap3qtdbfgnwvlk/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/axom-develop-j5cg6odzkrlra4lrnhap3qtdbfgnwvlk/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") @@ -21,11 +21,11 @@ set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdclang" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdclang" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdclang++" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdclang++" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdflang" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdflang" CACHE PATH "") else() @@ -82,13 +82,15 @@ set(ENABLE_HIP ON CACHE BOOL "") set(ROCM_ROOT_DIR "/opt/rocm-6.4.3" CACHE PATH "") -set(CMAKE_EXE_LINKER_FLAGS "-lxpmem -L/opt/cray/pe/mpich/8.1.29/gtl/lib -Wl,-rpath,/opt/cray/pe/mpich/8.1.29/gtl/lib -lmpi_gtl_hsa -L/opt/rocm-6.4.3/lib/llvm/lib -Wl,-rpath,/opt/rocm-6.4.3/lib/llvm/lib -L/opt/rocm-6.4.3/lib -Wl,-rpath,/opt/rocm-6.4.3/lib -lpgmath -lompstub -Wl,--disable-new-dtags -lflang -lflangrti -lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " CACHE STRING "") +set(BLT_CMAKE_IMPLICIT_LINK_LIBRARIES_EXCLUDE "ompstub" CACHE STRING "") + +set(CMAKE_EXE_LINKER_FLAGS "-lxpmem -L/opt/cray/pe/mpich/8.1.29/gtl/lib -Wl,-rpath,/opt/cray/pe/mpich/8.1.29/gtl/lib -lmpi_gtl_hsa -L/opt/rocm-6.4.3/lib/llvm/lib -Wl,-rpath,/opt/rocm-6.4.3/lib/llvm/lib -L/opt/rocm-6.4.3/lib -Wl,-rpath,/opt/rocm-6.4.3/lib -lpgmath -Wl,--disable-new-dtags -lflang -lflangrti -lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " CACHE STRING "") #------------------------------------------------ # Hardware Specifics #------------------------------------------------ -set(ENABLE_OPENMP OFF CACHE BOOL "") +set(ENABLE_OPENMP ON CACHE BOOL "") set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") @@ -96,21 +98,21 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/llvm-amdgpu-6.4.3" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3" CACHE PATH "") set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-cxdq4qnoptjq6lry5asb2bqodvm3zjig" CACHE PATH "") set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-oxqhr2ybswpnzt74f4iritx263ji3uqy" CACHE PATH "") -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-gkcg2u6vl5yur7gvigiut76ckzxo4fry" CACHE PATH "") +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-xwok5jck2jjg76k4575wkzgyvgopipz2" CACHE PATH "") set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-wb7u6epuww3tretkcto7jhq2i7pffimg" CACHE PATH "") set(LUA_DIR "${TPL_ROOT}/lua-5.4.8-lsey5vgxvpqf5j3xbrlzswrizuwm65vh" CACHE PATH "") -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-5r3ukxt2ewfn2geqge37ea2fxd7kfixh" CACHE PATH "") +set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-i5stpeet653njjadpjsekbvxlqkng2zn" CACHE PATH "") -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-n4zr37loo2okonovcswffexwi5mbxnxh" CACHE PATH "") +set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-hpevb7stqsg6dltp3t2tcnzzps7tgywb" CACHE PATH "") # OPENCASCADE not built @@ -118,7 +120,7 @@ set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-cbxp6kv6rm4f7oeczuhbplm3yb3zxb6z" CACHE P set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-sos5uklbsgaycb776a2ufcyje4y24yls" CACHE PATH "") -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-k6gr5qtzupytbr5kq2e73qesltauiqcl" CACHE PATH "") +set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-j7my6ilwzwzvbaxzscbvishmj5mgyxnz" CACHE PATH "") # scr not built @@ -148,12 +150,12 @@ set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-3nchqpyclgle5w56it23ozl4ttbnrfjd/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj/lib/python3.13/site-packages" CACHE PATH "") set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-t7cvohiksz7deeldwxrtqfmahcvanapb/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/none-none/py-pluggy-1.6.0-27aoz44dedjxa6j6r6cavpqyqmpknidb/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/py-pluggy-1.6.0-27aoz44dedjxa6j6r6cavpqyqmpknidb/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_10_15_33_36/none-none/py-iniconfig-2.1.0-7rty7n4wq6kxxrzflm7wbxqzhhc6ph7q/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/py-iniconfig-2.1.0-7rty7n4wq6kxxrzflm7wbxqzhhc6ph7q/lib/python3.13/site-packages" CACHE PATH "") diff --git a/host-configs/tuolumne-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake b/host-configs/tuolumne-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake index c9b7bdbb1f..16d26ee5a8 100644 --- a/host-configs/tuolumne-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake +++ b/host-configs/tuolumne-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake @@ -4,13 +4,13 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.29.2/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/cce-20.0.0/blt-0.7.1-af45hpsxdxploxhqqi4irjmy4vz2omqx;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/cce-20.0.0/c2c-1.8.0-cpdkv2uxkhs3qmzqakt4sht3vuiucdwt;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/cce-20.0.0/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-wytd46zvuquxg2rqf3zec5ldbmjeaat6;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/cce-20.0.0/conduit-0.9.5-pbrrhvrsmnewby6q3u35ceppt5fxme2p;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/cce-20.0.0/lua-5.4.8-y6z3polqakbuhn6nh6muri3rjxqmiioa;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/cce-20.0.0/mfem-4.9.0-2r7wws3ojiqvv6vk7y3ki443xke656sf;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/cce-20.0.0/py-nanobind-2.7.0-ieb2bzuz435ydpyiq2uj36yyqjbrffml;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/cce-20.0.0/adiak-0.4.0-lzgexbo5qyzepgju2acyy6d2qft443tb;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/cce-20.0.0/libunwind-1.8.3-zvteigxlopfo5tkdr2n2pglgosg42bah;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/cce-20.0.0/hdf5-1.8.23-to3lvqwzxrqzad65zbvnbtvyt4k63uxe;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/cce-20.0.0/parmetis-4.0.3-bajtzsmahjg2wvcjzo5gfb2hr5oj2elc;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/cce-20.0.0/py-numpy-2.4.2-xw5ysy75zfqlalmbmtl5phd2qip43hlo;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/cce-20.0.0/hypre-2.27.0-knkmxfbf3xcut2wmamjsmhrqf7u7xmi7;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/cce-20.0.0/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-y7uevzr5oi5eansgqfm4ppgcdkvnni6t;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/cce-20.0.0/umpire-2025.12.0-ke2uxlxkv74lvrhujuzh3ktf3c6e7lfb;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/cce-20.0.0/zlib-1.3.1-f3sdiqmy2whvmdn2fxeol3k7oqwouays;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/cce-20.0.0/metis-5.1.0-v4d34x7btqhotktvu5bgk4iwk6h2lumb;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/cce-20.0.0/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-a755kudv24eiizq4thcs2ca2fj7vy65x;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/cce-20.0.0/fmt-11.0.2-nzzmwf4nofjv46yeugehjo64opvfbbzr;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cce-tce/cce-20.0.0;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.32-rocmcc-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/blt-0.7.1-af45hpsxdxploxhqqi4irjmy4vz2omqx;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/c2c-1.8.0-cpdkv2uxkhs3qmzqakt4sht3vuiucdwt;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-wytd46zvuquxg2rqf3zec5ldbmjeaat6;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/conduit-0.9.5-pbrrhvrsmnewby6q3u35ceppt5fxme2p;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/lua-5.4.8-y6z3polqakbuhn6nh6muri3rjxqmiioa;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/mfem-4.9.0-wpbbiugqvdw7bylkmyyf2quazcoptzah;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/py-nanobind-2.7.0-ieb2bzuz435ydpyiq2uj36yyqjbrffml;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/adiak-0.4.0-lzgexbo5qyzepgju2acyy6d2qft443tb;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/libunwind-1.8.3-zvteigxlopfo5tkdr2n2pglgosg42bah;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/hdf5-1.8.23-to3lvqwzxrqzad65zbvnbtvyt4k63uxe;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/parmetis-4.0.3-bajtzsmahjg2wvcjzo5gfb2hr5oj2elc;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/py-numpy-2.4.2-xw5ysy75zfqlalmbmtl5phd2qip43hlo;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/hypre-2.27.0-knkmxfbf3xcut2wmamjsmhrqf7u7xmi7;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-z3eibnxgk3jeplydlcizrhurpibzqzqv;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/umpire-2025.12.0-eqokbtuch3pwifb225w2i3rrft5ddcgv;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/zlib-1.3.1-f3sdiqmy2whvmdn2fxeol3k7oqwouays;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/metis-5.1.0-v4d34x7btqhotktvu5bgk4iwk6h2lumb;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-eplsxvli6nnrgdl4l3hl7cizytginfhp;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/fmt-11.0.2-nzzmwf4nofjv46yeugehjo64opvfbbzr;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cce-tce/cce-20.0.0;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.32-rocmcc-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/cce-20.0.0/axom-develop-csdbligyshk6zb3emr4vn5sjrrnbtobj/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/cce-20.0.0/axom-develop-csdbligyshk6zb3emr4vn5sjrrnbtobj/lib64;;/opt/cray/pe/cce/20.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/axom-develop-xz7g7s5cdkdr6lixuhs23vyuqqodocrg/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/axom-develop-xz7g7s5cdkdr6lixuhs23vyuqqodocrg/lib64;;/opt/cray/pe/cce/20.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/cce-20.0.0/axom-develop-csdbligyshk6zb3emr4vn5sjrrnbtobj/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/cce-20.0.0/axom-develop-csdbligyshk6zb3emr4vn5sjrrnbtobj/lib64;;/opt/cray/pe/cce/20.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/axom-develop-xz7g7s5cdkdr6lixuhs23vyuqqodocrg/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/axom-develop-xz7g7s5cdkdr6lixuhs23vyuqqodocrg/lib64;;/opt/cray/pe/cce/20.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") @@ -21,11 +21,11 @@ set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/cce/craycc" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/cce/craycc" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/cce/case-insensitive/crayCC" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/cce/case-insensitive/crayCC" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/cce/crayftn" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/cce/crayftn" CACHE PATH "") else() @@ -84,37 +84,41 @@ set(ENABLE_HIP ON CACHE BOOL "") set(ROCM_ROOT_DIR "/opt/rocm-6.4.3" CACHE PATH "") -set(BLT_CMAKE_IMPLICIT_LINK_LIBRARIES_EXCLUDE "unwind" CACHE STRING "") +set(BLT_CMAKE_IMPLICIT_LINK_LIBRARIES_EXCLUDE "unwind;ompstub" CACHE STRING "") -set(CMAKE_EXE_LINKER_FLAGS "-lxpmem -L/opt/cray/pe/mpich/8.1.32/gtl/lib -Wl,-rpath,/opt/cray/pe/mpich/8.1.32/gtl/lib -lmpi_gtl_hsa -L/opt/rocm-6.4.3/lib/llvm/lib -Wl,-rpath,/opt/rocm-6.4.3/lib/llvm/lib -L/opt/rocm-6.4.3/lib -Wl,-rpath,/opt/rocm-6.4.3/lib -lpgmath -lompstub -L/opt/cray/pe/cce/20.0.0/cce/x86_64/lib -Wl,-rpath,/opt/cray/pe/cce/20.0.0/cce/x86_64/lib -lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " CACHE STRING "") +set(CMAKE_EXE_LINKER_FLAGS "-lxpmem -L/opt/cray/pe/mpich/8.1.32/gtl/lib -Wl,-rpath,/opt/cray/pe/mpich/8.1.32/gtl/lib -lmpi_gtl_hsa -L/opt/rocm-6.4.3/lib/llvm/lib -Wl,-rpath,/opt/rocm-6.4.3/lib/llvm/lib -L/opt/rocm-6.4.3/lib -Wl,-rpath,/opt/rocm-6.4.3/lib -lpgmath -L/opt/cray/pe/cce/20.0.0/cce/x86_64/lib -Wl,-rpath,/opt/cray/pe/cce/20.0.0/cce/x86_64/lib -lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " CACHE STRING "") #------------------------------------------------ # Hardware Specifics #------------------------------------------------ -set(ENABLE_OPENMP OFF CACHE BOOL "") +set(ENABLE_OPENMP ON CACHE BOOL "") set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") +set(BLT_OPENMP_COMPILE_FLAGS "$<$>:-fopenmp=libomp>;$<$:-fopenmp>" CACHE STRING "Different OpenMP compile & link flags between HIP and CXX compilers (amdclang++)") + +set(BLT_OPENMP_LINK_FLAGS "$<$>:-fopenmp=libomp>;$<$:-fopenmp>" CACHE STRING "Different OpenMP compile & link flags between HIP and CXX compilers (amdclang++)") + #------------------------------------------------------------------------------ # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/cce-20.0.0" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0" CACHE PATH "") set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-pbrrhvrsmnewby6q3u35ceppt5fxme2p" CACHE PATH "") set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-cpdkv2uxkhs3qmzqakt4sht3vuiucdwt" CACHE PATH "") -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-2r7wws3ojiqvv6vk7y3ki443xke656sf" CACHE PATH "") +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-wpbbiugqvdw7bylkmyyf2quazcoptzah" CACHE PATH "") set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-to3lvqwzxrqzad65zbvnbtvyt4k63uxe" CACHE PATH "") set(LUA_DIR "${TPL_ROOT}/lua-5.4.8-y6z3polqakbuhn6nh6muri3rjxqmiioa" CACHE PATH "") -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-y7uevzr5oi5eansgqfm4ppgcdkvnni6t" CACHE PATH "") +set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-z3eibnxgk3jeplydlcizrhurpibzqzqv" CACHE PATH "") -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-ke2uxlxkv74lvrhujuzh3ktf3c6e7lfb" CACHE PATH "") +set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-eqokbtuch3pwifb225w2i3rrft5ddcgv" CACHE PATH "") # OPENCASCADE not built @@ -122,7 +126,7 @@ set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-lzgexbo5qyzepgju2acyy6d2qft443tb" CACHE P set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-wytd46zvuquxg2rqf3zec5ldbmjeaat6" CACHE PATH "") -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-a755kudv24eiizq4thcs2ca2fj7vy65x" CACHE PATH "") +set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-eplsxvli6nnrgdl4l3hl7cizytginfhp" CACHE PATH "") # scr not built @@ -152,12 +156,12 @@ set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-ieb2bzuz435ydpyiq2uj36yyqjbrffml/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5/lib/python3.13/site-packages" CACHE PATH "") set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-xw5ysy75zfqlalmbmtl5phd2qip43hlo/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/none-none/py-pluggy-1.6.0-n4ojmtqyawxz6gipk64pup3ahwz3xqpu/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/py-pluggy-1.6.0-n4ojmtqyawxz6gipk64pup3ahwz3xqpu/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/none-none/py-iniconfig-2.1.0-lf3yvch65se22jr7b5unfoxtse4ugkrk/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/py-iniconfig-2.1.0-lf3yvch65se22jr7b5unfoxtse4ugkrk/lib/python3.13/site-packages" CACHE PATH "") diff --git a/host-configs/tuolumne-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake b/host-configs/tuolumne-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake index 605a7a02c1..bd822962a6 100644 --- a/host-configs/tuolumne-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake +++ b/host-configs/tuolumne-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake @@ -4,13 +4,13 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.29.2/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.3.1/adiak-0.4.0-c4srnrz5apjetx74dt6xjah63o3a7xcx;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.3.1/blt-0.7.1-kzmxfjvxm4drr2qhspckvwee6mrlc3e5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.3.1/c2c-1.8.0-lm64gc55hpxilmtsw7geirys3q4wacu5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.3.1/conduit-0.9.5-wswspowegvp5byl5inc2cikljzkcqg64;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.3.1/lua-5.4.8-j7ngytniswhoa536kqfzarcraarkicbm;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.3.1/mfem-4.9.0-wetr3wqseqqobjb7be3uzrecpfewq4mr;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.3.1/py-nanobind-2.7.0-q2kwozph3gwgfyjnx43jp2gkbaghwxoj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.3.1/hdf5-1.8.23-rb4cjthx4wz4wnpaizwmm2jycijdthqm;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.3.1/parmetis-4.0.3-ng66tt5fmjyfoiuuxyl4rxqv4ompawmd;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.3.1/py-numpy-2.4.2-zcxbrxijjc3miotq6s3kq446afrgycuu;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.3.1/hypre-2.27.0-6m2jwyzqbksxgmdznvh54ytysxyme7jy;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.3.1/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-okzhosj73rts33dgatxh4daotairh5jn;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.3.1/umpire-2025.12.0-of6o5lgmd6jgjqfwat2sbyngieagkeco;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.3.1/zlib-1.3.1-5w5muvpvct6uruxrddscblsiyizl3uoc;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.3.1/metis-5.1.0-4punmdkye6rovr3u7ph7bgj66qim77du;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.3.1/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-74scmbkik6ye7bunoclw46d6zrcfwgha;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.3.1/fmt-11.0.2-opsfnufdiedpfpob5wgbvhcrhyhzfkaq;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.3.1;/opt/rocm-6.3.1;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.3.1;/opt/rocm-6.3.1;/opt/rocm-6.3.1" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.3.1/adiak-0.4.0-c4srnrz5apjetx74dt6xjah63o3a7xcx;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.3.1/blt-0.7.1-kzmxfjvxm4drr2qhspckvwee6mrlc3e5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.3.1/c2c-1.8.0-lm64gc55hpxilmtsw7geirys3q4wacu5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.3.1/conduit-0.9.5-wswspowegvp5byl5inc2cikljzkcqg64;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.3.1/lua-5.4.8-j7ngytniswhoa536kqfzarcraarkicbm;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.3.1/mfem-4.9.0-xwg5un3mlbeog3j4voeltekra43ix3ga;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.3.1/py-nanobind-2.7.0-q2kwozph3gwgfyjnx43jp2gkbaghwxoj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.3.1/hdf5-1.8.23-rb4cjthx4wz4wnpaizwmm2jycijdthqm;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.3.1/parmetis-4.0.3-ng66tt5fmjyfoiuuxyl4rxqv4ompawmd;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.3.1/py-numpy-2.4.2-zcxbrxijjc3miotq6s3kq446afrgycuu;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.3.1/hypre-2.27.0-6m2jwyzqbksxgmdznvh54ytysxyme7jy;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.3.1/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-wcyqyggju4u2ornjjnamgdgfbqjrzsir;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.3.1/umpire-2025.12.0-5w5v22edixuttxwueaagiobpjzn5mcgr;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.3.1/zlib-1.3.1-5w5muvpvct6uruxrddscblsiyizl3uoc;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.3.1/metis-5.1.0-4punmdkye6rovr3u7ph7bgj66qim77du;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.3.1/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-jnalmgzqx2nn2afq4gwhyjda3u6yub7y;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.3.1/fmt-11.0.2-opsfnufdiedpfpob5wgbvhcrhyhzfkaq;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.3.1;/opt/rocm-6.3.1;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.3.1;/opt/rocm-6.3.1;/opt/rocm-6.3.1" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.3.1/axom-develop-xoswouqk3ppxzuxtgegijd3kdoqlkvfe/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.3.1/axom-develop-xoswouqk3ppxzuxtgegijd3kdoqlkvfe/lib64;;/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.3.1/axom-develop-qrojk4ujrhd5b734s2yc73n5u3kvcgqa/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.3.1/axom-develop-qrojk4ujrhd5b734s2yc73n5u3kvcgqa/lib64;;/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.3.1/axom-develop-xoswouqk3ppxzuxtgegijd3kdoqlkvfe/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.3.1/axom-develop-xoswouqk3ppxzuxtgegijd3kdoqlkvfe/lib64;;/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.3.1/axom-develop-qrojk4ujrhd5b734s2yc73n5u3kvcgqa/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.3.1/axom-develop-qrojk4ujrhd5b734s2yc73n5u3kvcgqa/lib64;;/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") @@ -21,11 +21,11 @@ set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdclang" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdclang" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdclang++" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdclang++" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdflang" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdflang" CACHE PATH "") else() @@ -82,13 +82,15 @@ set(ENABLE_HIP ON CACHE BOOL "") set(ROCM_ROOT_DIR "/opt/rocm-6.3.1" CACHE PATH "") -set(CMAKE_EXE_LINKER_FLAGS "-lxpmem -L/opt/cray/pe/mpich/8.1.29/gtl/lib -Wl,-rpath,/opt/cray/pe/mpich/8.1.29/gtl/lib -lmpi_gtl_hsa -L/opt/rocm-6.3.1/lib/llvm/lib -Wl,-rpath,/opt/rocm-6.3.1/lib/llvm/lib -L/opt/rocm-6.3.1/lib -Wl,-rpath,/opt/rocm-6.3.1/lib -lpgmath -lompstub -Wl,--disable-new-dtags -lflang -lflangrti -lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " CACHE STRING "") +set(BLT_CMAKE_IMPLICIT_LINK_LIBRARIES_EXCLUDE "ompstub" CACHE STRING "") + +set(CMAKE_EXE_LINKER_FLAGS "-lxpmem -L/opt/cray/pe/mpich/8.1.29/gtl/lib -Wl,-rpath,/opt/cray/pe/mpich/8.1.29/gtl/lib -lmpi_gtl_hsa -L/opt/rocm-6.3.1/lib/llvm/lib -Wl,-rpath,/opt/rocm-6.3.1/lib/llvm/lib -L/opt/rocm-6.3.1/lib -Wl,-rpath,/opt/rocm-6.3.1/lib -lpgmath -Wl,--disable-new-dtags -lflang -lflangrti -lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " CACHE STRING "") #------------------------------------------------ # Hardware Specifics #------------------------------------------------ -set(ENABLE_OPENMP OFF CACHE BOOL "") +set(ENABLE_OPENMP ON CACHE BOOL "") set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") @@ -96,21 +98,21 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.3.1" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.3.1" CACHE PATH "") set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-wswspowegvp5byl5inc2cikljzkcqg64" CACHE PATH "") set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-lm64gc55hpxilmtsw7geirys3q4wacu5" CACHE PATH "") -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-wetr3wqseqqobjb7be3uzrecpfewq4mr" CACHE PATH "") +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-xwg5un3mlbeog3j4voeltekra43ix3ga" CACHE PATH "") set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-rb4cjthx4wz4wnpaizwmm2jycijdthqm" CACHE PATH "") set(LUA_DIR "${TPL_ROOT}/lua-5.4.8-j7ngytniswhoa536kqfzarcraarkicbm" CACHE PATH "") -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-okzhosj73rts33dgatxh4daotairh5jn" CACHE PATH "") +set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-wcyqyggju4u2ornjjnamgdgfbqjrzsir" CACHE PATH "") -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-of6o5lgmd6jgjqfwat2sbyngieagkeco" CACHE PATH "") +set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-5w5v22edixuttxwueaagiobpjzn5mcgr" CACHE PATH "") # OPENCASCADE not built @@ -118,7 +120,7 @@ set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-c4srnrz5apjetx74dt6xjah63o3a7xcx" CACHE P # CALIPER not built -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-74scmbkik6ye7bunoclw46d6zrcfwgha" CACHE PATH "") +set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-jnalmgzqx2nn2afq4gwhyjda3u6yub7y" CACHE PATH "") # scr not built @@ -148,12 +150,12 @@ set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-q2kwozph3gwgfyjnx43jp2gkbaghwxoj/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5/lib/python3.13/site-packages" CACHE PATH "") set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-zcxbrxijjc3miotq6s3kq446afrgycuu/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/none-none/py-pluggy-1.6.0-n4ojmtqyawxz6gipk64pup3ahwz3xqpu/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/py-pluggy-1.6.0-n4ojmtqyawxz6gipk64pup3ahwz3xqpu/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/none-none/py-iniconfig-2.1.0-lf3yvch65se22jr7b5unfoxtse4ugkrk/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/py-iniconfig-2.1.0-lf3yvch65se22jr7b5unfoxtse4ugkrk/lib/python3.13/site-packages" CACHE PATH "") diff --git a/host-configs/tuolumne-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake b/host-configs/tuolumne-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake index 2b5c73bb42..62b6d60407 100644 --- a/host-configs/tuolumne-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake +++ b/host-configs/tuolumne-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake @@ -4,13 +4,13 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.29.2/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.4.3/blt-0.7.1-4kxhk7nbuiv5oh2ymh7sdimnmor36hmo;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.4.3/c2c-1.8.0-h5kio7nseyazg4pzgvj4kxanb3plhbxm;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.4.3/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-rmlwpdacdtpzf4u72ctyroj72soqzf3f;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.4.3/conduit-0.9.5-zqywrasos2vbears5hvdwxhulm3xox6q;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.4.3/lua-5.4.8-gp4kdewsy4uiy6flsx342lgogcm7fym2;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.4.3/mfem-4.9.0-g6vxdlgc7spurcdacgc6e5245iu2hyiw;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.4.3/py-nanobind-2.7.0-2kp35jcpb73xuujvcz6kmg4q2npek4gg;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.4.3/adiak-0.4.0-cropyu42jr4q6k3zar6mkma6gc2vp6tk;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.4.3/libunwind-1.8.3-plszp43gkd75kzjcvge3qxrtb6j2budl;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.4.3/hdf5-1.8.23-mgic2kxojd5zeivhaz473zzmzrg3gdqe;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.4.3/parmetis-4.0.3-hd2qoczrh3vw3ejm6msitqdgecjk7xwe;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.4.3/py-numpy-2.4.2-a3mo27shjtpliwewbm5cci2kf37crrab;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.4.3/hypre-2.27.0-4nrl5egspbxmnkpzfym5udby6uruhn3i;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.4.3/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-wavyomm5hzyjf3xuyr33txmnspvxveei;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.4.3/umpire-2025.12.0-4oytcvf556ksil4hsthjt4b5y4wdpaxw;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.4.3/zlib-1.3.1-slqujeue3l44x3pgqbu7e2pw3lh2mj5i;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.4.3/metis-5.1.0-puzeddu6ufhz5vpxnw7i3i2i6slsvuuj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.4.3/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-3gtldngru7bva2reuel5iabyytw5fmdf;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.4.3/fmt-11.0.2-lpnox72rqcneew5hucwzq4nmms4at525;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/blt-0.7.1-4kxhk7nbuiv5oh2ymh7sdimnmor36hmo;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/c2c-1.8.0-h5kio7nseyazg4pzgvj4kxanb3plhbxm;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-rmlwpdacdtpzf4u72ctyroj72soqzf3f;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/conduit-0.9.5-zqywrasos2vbears5hvdwxhulm3xox6q;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/lua-5.4.8-gp4kdewsy4uiy6flsx342lgogcm7fym2;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/mfem-4.9.0-6cgokeizyrjzsjzwmtf35p5jvhnws7n5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/py-nanobind-2.7.0-2kp35jcpb73xuujvcz6kmg4q2npek4gg;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/adiak-0.4.0-cropyu42jr4q6k3zar6mkma6gc2vp6tk;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/libunwind-1.8.3-plszp43gkd75kzjcvge3qxrtb6j2budl;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/hdf5-1.8.23-mgic2kxojd5zeivhaz473zzmzrg3gdqe;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/parmetis-4.0.3-hd2qoczrh3vw3ejm6msitqdgecjk7xwe;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/py-numpy-2.4.2-a3mo27shjtpliwewbm5cci2kf37crrab;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/hypre-2.27.0-4nrl5egspbxmnkpzfym5udby6uruhn3i;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-v74yskgcb7tsgho6bdush5jdlbigkttf;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/umpire-2025.12.0-jft5rocv5ev4b57f3664b2kagyfjra2e;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/zlib-1.3.1-slqujeue3l44x3pgqbu7e2pw3lh2mj5i;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/metis-5.1.0-puzeddu6ufhz5vpxnw7i3i2i6slsvuuj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-zadbjpethvnlhpqwx6vm6ahphk42a4gq;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/fmt-11.0.2-lpnox72rqcneew5hucwzq4nmms4at525;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.4.3/axom-develop-cdngjuz7ffr3goroyietece35jivxnkv/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.4.3/axom-develop-cdngjuz7ffr3goroyietece35jivxnkv/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/axom-develop-i4n5ay2l5l2hludfn6z5vyvfwk57p6hx/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/axom-develop-i4n5ay2l5l2hludfn6z5vyvfwk57p6hx/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.4.3/axom-develop-cdngjuz7ffr3goroyietece35jivxnkv/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.4.3/axom-develop-cdngjuz7ffr3goroyietece35jivxnkv/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/axom-develop-i4n5ay2l5l2hludfn6z5vyvfwk57p6hx/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/axom-develop-i4n5ay2l5l2hludfn6z5vyvfwk57p6hx/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") @@ -21,11 +21,11 @@ set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdclang" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdclang" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdclang++" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdclang++" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdflang" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdflang" CACHE PATH "") else() @@ -82,13 +82,15 @@ set(ENABLE_HIP ON CACHE BOOL "") set(ROCM_ROOT_DIR "/opt/rocm-6.4.3" CACHE PATH "") -set(CMAKE_EXE_LINKER_FLAGS "-lxpmem -L/opt/cray/pe/mpich/8.1.29/gtl/lib -Wl,-rpath,/opt/cray/pe/mpich/8.1.29/gtl/lib -lmpi_gtl_hsa -L/opt/rocm-6.4.3/lib/llvm/lib -Wl,-rpath,/opt/rocm-6.4.3/lib/llvm/lib -L/opt/rocm-6.4.3/lib -Wl,-rpath,/opt/rocm-6.4.3/lib -lpgmath -lompstub -Wl,--disable-new-dtags -lflang -lflangrti -lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " CACHE STRING "") +set(BLT_CMAKE_IMPLICIT_LINK_LIBRARIES_EXCLUDE "ompstub" CACHE STRING "") + +set(CMAKE_EXE_LINKER_FLAGS "-lxpmem -L/opt/cray/pe/mpich/8.1.29/gtl/lib -Wl,-rpath,/opt/cray/pe/mpich/8.1.29/gtl/lib -lmpi_gtl_hsa -L/opt/rocm-6.4.3/lib/llvm/lib -Wl,-rpath,/opt/rocm-6.4.3/lib/llvm/lib -L/opt/rocm-6.4.3/lib -Wl,-rpath,/opt/rocm-6.4.3/lib -lpgmath -Wl,--disable-new-dtags -lflang -lflangrti -lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " CACHE STRING "") #------------------------------------------------ # Hardware Specifics #------------------------------------------------ -set(ENABLE_OPENMP OFF CACHE BOOL "") +set(ENABLE_OPENMP ON CACHE BOOL "") set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") @@ -96,21 +98,21 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/llvm-amdgpu-6.4.3" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3" CACHE PATH "") set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-zqywrasos2vbears5hvdwxhulm3xox6q" CACHE PATH "") set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-h5kio7nseyazg4pzgvj4kxanb3plhbxm" CACHE PATH "") -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-g6vxdlgc7spurcdacgc6e5245iu2hyiw" CACHE PATH "") +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-6cgokeizyrjzsjzwmtf35p5jvhnws7n5" CACHE PATH "") set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-mgic2kxojd5zeivhaz473zzmzrg3gdqe" CACHE PATH "") set(LUA_DIR "${TPL_ROOT}/lua-5.4.8-gp4kdewsy4uiy6flsx342lgogcm7fym2" CACHE PATH "") -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-wavyomm5hzyjf3xuyr33txmnspvxveei" CACHE PATH "") +set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-v74yskgcb7tsgho6bdush5jdlbigkttf" CACHE PATH "") -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-4oytcvf556ksil4hsthjt4b5y4wdpaxw" CACHE PATH "") +set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-jft5rocv5ev4b57f3664b2kagyfjra2e" CACHE PATH "") # OPENCASCADE not built @@ -118,7 +120,7 @@ set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-cropyu42jr4q6k3zar6mkma6gc2vp6tk" CACHE P set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-rmlwpdacdtpzf4u72ctyroj72soqzf3f" CACHE PATH "") -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-3gtldngru7bva2reuel5iabyytw5fmdf" CACHE PATH "") +set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-zadbjpethvnlhpqwx6vm6ahphk42a4gq" CACHE PATH "") # scr not built @@ -148,12 +150,12 @@ set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-2kp35jcpb73xuujvcz6kmg4q2npek4gg/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5/lib/python3.13/site-packages" CACHE PATH "") set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-a3mo27shjtpliwewbm5cci2kf37crrab/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/none-none/py-pluggy-1.6.0-n4ojmtqyawxz6gipk64pup3ahwz3xqpu/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/py-pluggy-1.6.0-n4ojmtqyawxz6gipk64pup3ahwz3xqpu/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_03_11_08_14_36/none-none/py-iniconfig-2.1.0-lf3yvch65se22jr7b5unfoxtse4ugkrk/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/py-iniconfig-2.1.0-lf3yvch65se22jr7b5unfoxtse4ugkrk/lib/python3.13/site-packages" CACHE PATH "") From a46891d4f06c86c2098f0684bf2b4864fe370ebb Mon Sep 17 00:00:00 2001 From: Brian Han Date: Tue, 21 Apr 2026 09:43:11 -0700 Subject: [PATCH 135/986] Add 0.14.0 version --- scripts/spack/packages/axom/package.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/spack/packages/axom/package.py b/scripts/spack/packages/axom/package.py index 843f5e31ca..18763ed045 100644 --- a/scripts/spack/packages/axom/package.py +++ b/scripts/spack/packages/axom/package.py @@ -78,6 +78,7 @@ class Axom(CachedCMakePackage, CudaPackage, ROCmPackage): version("main", branch="main") version("develop", branch="develop") + version("0.14.0", tag="v0.14.0", commit="146c8c15386a810791b7ab5c7fcb288cadea6151") version("0.13.0", tag="v0.13.0", commit="d00f6c66ef390ad746ae840f1074d982513611ac") version("0.12.0", tag="v0.12.0", commit="297544010a3dfb98145a1a85f09f9c648c00a18c") version("0.11.0", tag="v0.11.0", commit="685960486aa55d3a74a821ee02f6d9d9a3e67ab1") From 324faf2516ff2c4e8c9c599ae9d3163f443e1187 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Tue, 21 Apr 2026 14:47:38 -0700 Subject: [PATCH 136/986] Try OpenMP off on tuo to avoid timeout --- .gitlab/build_tuolumne.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitlab/build_tuolumne.yml b/.gitlab/build_tuolumne.yml index fbededfef3..5111426daf 100644 --- a/.gitlab/build_tuolumne.yml +++ b/.gitlab/build_tuolumne.yml @@ -36,10 +36,12 @@ #### # PR Build jobs +# Disable OpenMP to avoid timeout tuolumne-llvm-amdgpu_6_4_3_hip-src: variables: COMPILER: "llvm-amdgpu@6.4.3_hip" HOST_CONFIG: "tuolumne-toss_4_x86_64_ib_cray-${COMPILER}.cmake" + EXTRA_CMAKE_OPTIONS: "-DENABLE_OPENMP:BOOL=OFF" extends: .src_build_on_tuolumne #### From b875584db9f93dace1e147b52a13a82cdf4932e7 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 27 Apr 2026 17:55:53 -0700 Subject: [PATCH 137/986] Improved testing for scale transforms and klee scale/center support. --- src/axom/core/tests/CMakeLists.txt | 2 +- src/axom/core/tests/core_serial_main.cpp | 1 + src/axom/core/tests/numerics_transforms.hpp | 98 +++++++++++++++++++ .../klee/tests/klee_geometry_operators.cpp | 38 ++++++- .../klee/tests/klee_geometry_operators_io.cpp | 27 +++++ src/axom/klee/tests/klee_io.cpp | 34 +++++++ 6 files changed, 198 insertions(+), 2 deletions(-) create mode 100644 src/axom/core/tests/numerics_transforms.hpp diff --git a/src/axom/core/tests/CMakeLists.txt b/src/axom/core/tests/CMakeLists.txt index 662f0fee06..eecb1bc949 100644 --- a/src/axom/core/tests/CMakeLists.txt +++ b/src/axom/core/tests/CMakeLists.txt @@ -46,6 +46,7 @@ set(core_serial_tests numerics_lu.hpp numerics_matrix.hpp numerics_matvecops.hpp + numerics_transforms.hpp numerics_polynomial_solvers.hpp utils_endianness.hpp @@ -226,4 +227,3 @@ if (ENABLE_BENCHMARKS) COMMAND ${test_name} --benchmark_min_time=0.0001s) endforeach() endif() - diff --git a/src/axom/core/tests/core_serial_main.cpp b/src/axom/core/tests/core_serial_main.cpp index 6b41b128c6..940f036140 100644 --- a/src/axom/core/tests/core_serial_main.cpp +++ b/src/axom/core/tests/core_serial_main.cpp @@ -40,6 +40,7 @@ #include "numerics_lu.hpp" #include "numerics_matrix.hpp" #include "numerics_matvecops.hpp" +#include "numerics_transforms.hpp" #include "numerics_polynomial_solvers.hpp" #include "numerics_quadrature.hpp" diff --git a/src/axom/core/tests/numerics_transforms.hpp b/src/axom/core/tests/numerics_transforms.hpp new file mode 100644 index 0000000000..e48b17f170 --- /dev/null +++ b/src/axom/core/tests/numerics_transforms.hpp @@ -0,0 +1,98 @@ +// 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/ArrayView.hpp" +#include "axom/core/numerics/matvecops.hpp" +#include "axom/core/numerics/transforms.hpp" + +namespace +{ +void expect_matrix_near(const axom::numerics::Matrix& actual, + const axom::numerics::Matrix& expected, + double tolerance = 1e-12) +{ + ASSERT_EQ(actual.getNumRows(), expected.getNumRows()); + ASSERT_EQ(actual.getNumColumns(), expected.getNumColumns()); + + for(axom::IndexType i = 0; i < actual.getNumRows(); ++i) + { + for(axom::IndexType j = 0; j < actual.getNumColumns(); ++j) + { + EXPECT_NEAR(actual(i, j), expected(i, j), tolerance); + } + } +} + +void expect_vector_near(const double* actual, + const double* expected, + int size, + double tolerance = 1e-12) +{ + for(int i = 0; i < size; ++i) + { + EXPECT_NEAR(actual[i], expected[i], tolerance); + } +} +} // namespace + +TEST(numerics_transforms, scale_2d_about_center) +{ + double centerData[2] = {2.0, 3.0}; + axom::ArrayView center(centerData, 2); + + auto actual = axom::numerics::transforms::scale(4.0, 5.0, center); + + axom::numerics::Matrix expected = axom::numerics::Matrix::identity(3); + expected(0, 0) = 4.0; + expected(1, 1) = 5.0; + expected(0, 2) = -6.0; + expected(1, 2) = -12.0; + expect_matrix_near(actual, expected); + + double point[3] = {3.0, 4.0, 1.0}; + double result[3] = {0.0, 0.0, 0.0}; + double expectedPoint[3] = {6.0, 8.0, 1.0}; + axom::numerics::matrix_vector_multiply(actual, point, result); + expect_vector_near(result, expectedPoint, 3); +} + +TEST(numerics_transforms, scale_3d_about_center) +{ + double centerData[3] = {0.5, 0.5, 0.5}; + axom::ArrayView center(centerData, 3); + + auto actual = axom::numerics::transforms::scale(2.0, 3.0, 4.0, center); + + axom::numerics::Matrix expected = axom::numerics::Matrix::identity(4); + expected(0, 0) = 2.0; + expected(1, 1) = 3.0; + expected(2, 2) = 4.0; + expected(0, 3) = -0.5; + expected(1, 3) = -1.0; + expected(2, 3) = -1.5; + expect_matrix_near(actual, expected); + + double point[4] = {1.5, 1.5, 1.5, 1.0}; + double result[4] = {0.0, 0.0, 0.0, 0.0}; + double expectedPoint[4] = {2.5, 3.5, 4.5, 1.0}; + axom::numerics::matrix_vector_multiply(actual, point, result); + expect_vector_near(result, expectedPoint, 4); +} + +TEST(numerics_transforms, scale_about_zero_center_matches_origin_scale) +{ + double center2dData[2] = {0.0, 0.0}; + axom::ArrayView center2d(center2dData, 2); + expect_matrix_near(axom::numerics::transforms::scale(2.0, 3.0, center2d), + axom::numerics::transforms::scale(2.0, 3.0)); + + double center3dData[3] = {0.0, 0.0, 0.0}; + axom::ArrayView center3d(center3dData, 3); + expect_matrix_near(axom::numerics::transforms::scale(2.0, 3.0, 4.0, center3d), + axom::numerics::transforms::scale(2.0, 3.0, 4.0, 4)); +} diff --git a/src/axom/klee/tests/klee_geometry_operators.cpp b/src/axom/klee/tests/klee_geometry_operators.cpp index 9d7f089cbb..a24b6d0780 100644 --- a/src/axom/klee/tests/klee_geometry_operators.cpp +++ b/src/axom/klee/tests/klee_geometry_operators.cpp @@ -45,7 +45,7 @@ using primal::Vector3D; template ColumnVector operator*(const numerics::Matrix &matrix, const ColumnVector &rhs) { - if(matrix.getNumRows() != matrix.getNumRows() || matrix.getNumRows() != rhs.dimension()) + if(matrix.getNumRows() != matrix.getNumColumns() || matrix.getNumRows() != rhs.dimension()) { throw std::logic_error("Can't multiply entities of this size"); } @@ -223,12 +223,48 @@ TEST(Scale, basics) EXPECT_DOUBLE_EQ(2, scale.getXFactor()); EXPECT_DOUBLE_EQ(3, scale.getYFactor()); EXPECT_DOUBLE_EQ(4, scale.getZFactor()); + EXPECT_DOUBLE_EQ(0., scale.getCenter()[0]); + EXPECT_DOUBLE_EQ(0., scale.getCenter()[1]); + EXPECT_DOUBLE_EQ(0., scale.getCenter()[2]); + + Scale scale2 {2, 4, 6, Point3D{0.5, 0.5, 0.5}, {Dimensions::Three, LengthUnit::cm}}; + EXPECT_DOUBLE_EQ(2, scale2.getXFactor()); + EXPECT_DOUBLE_EQ(4, scale2.getYFactor()); + EXPECT_DOUBLE_EQ(6, scale2.getZFactor()); + EXPECT_DOUBLE_EQ(0.5, scale2.getCenter()[0]); + EXPECT_DOUBLE_EQ(0.5, scale2.getCenter()[1]); + EXPECT_DOUBLE_EQ(0.5, scale2.getCenter()[2]); } TEST(Scale, toMatrix) { Scale scale {2, 3, 4, {Dimensions::Three, LengthUnit::cm}}; EXPECT_THAT(scale.toMatrix(), AlmostEqMatrix(affine({{{2, 0, 0, 0}, {0, 3, 0, 0}, {0, 0, 4, 0}}}))); + + Scale scale2 {2, 2, 2, Point3D{0.5, 0.5, 0.5}, {Dimensions::Three, LengthUnit::cm}}; + EXPECT_THAT(scale2.toMatrix(), + AlmostEqMatrix(affine({{{2, 0, 0, -0.5}, {0, 2, 0, -0.5}, {0, 0, 2, -0.5}}}))); + + EXPECT_THAT(scale2.toMatrix() * affinePoint({0.5, 0.5, 0.5}), + AlmostEqPoint(affinePoint({0.5, 0.5, 0.5}))); + EXPECT_THAT(scale2.toMatrix() * affinePoint({0., 0., 0.}), + AlmostEqPoint(affinePoint({-0.5, -0.5, -0.5}))); + EXPECT_THAT(scale2.toMatrix() * affinePoint({1., 0., 0.}), + AlmostEqPoint(affinePoint({1.5, -0.5, -0.5}))); + EXPECT_THAT(scale2.toMatrix() * affinePoint({1., 1., 0.}), + AlmostEqPoint(affinePoint({1.5, 1.5, -0.5}))); + EXPECT_THAT(scale2.toMatrix() * affinePoint({0., 1., 0.}), + AlmostEqPoint(affinePoint({-0.5, 1.5, -0.5}))); + EXPECT_THAT(scale2.toMatrix() * affinePoint({0., 0., 1.}), + AlmostEqPoint(affinePoint({-0.5, -0.5, 1.5}))); + EXPECT_THAT(scale2.toMatrix() * affinePoint({1., 0., 1.}), + AlmostEqPoint(affinePoint({1.5, -0.5, 1.5}))); + EXPECT_THAT(scale2.toMatrix() * affinePoint({1., 1., 1.}), + AlmostEqPoint(affinePoint({1.5, 1.5, 1.5}))); + EXPECT_THAT(scale2.toMatrix() * affinePoint({0., 1., 1.}), + AlmostEqPoint(affinePoint({-0.5, 1.5, 1.5}))); + EXPECT_THAT(scale2.toMatrix() * affineVec({1., 1., 1.}), + AlmostEqVector(affineVec({2., 2., 2.}))); } TEST(Scale, accept) diff --git a/src/axom/klee/tests/klee_geometry_operators_io.cpp b/src/axom/klee/tests/klee_geometry_operators_io.cpp index 1a278dc1ee..35218e389e 100644 --- a/src/axom/klee/tests/klee_geometry_operators_io.cpp +++ b/src/axom/klee/tests/klee_geometry_operators_io.cpp @@ -364,6 +364,18 @@ TEST(GeometryOperatorsIO, readScale_2d_array) EXPECT_EQ(expectedProperties, scale.getEndProperties()); } +TEST(GeometryOperatorsIO, readScale_2d_array_withCenter) +{ + auto scale = readSingleOperator({Dimensions::Two, LengthUnit::cm}, R"( + scale: [1.2, 3.4] + center: [10, 20] + )"); + EXPECT_DOUBLE_EQ(1.2, scale.getXFactor()); + EXPECT_DOUBLE_EQ(3.4, scale.getYFactor()); + EXPECT_DOUBLE_EQ(1.0, scale.getZFactor()); + EXPECT_THAT(scale.getCenter(), AlmostEqPoint(Point3D {10, 20, 0})); +} + TEST(GeometryOperatorsIO, readScale_3d_array) { auto scale = readSingleOperator({Dimensions::Three, LengthUnit::cm}, @@ -378,6 +390,19 @@ TEST(GeometryOperatorsIO, readScale_3d_array) EXPECT_EQ(expectedProperties, scale.getEndProperties()); } +TEST(GeometryOperatorsIO, readScale_3d_array_withCenter) +{ + auto scale = readSingleOperator({Dimensions::Three, LengthUnit::cm}, + R"( + scale: [1.2, 3.4, 5.6] + center: [4, 5, 6] + )"); + EXPECT_DOUBLE_EQ(1.2, scale.getXFactor()); + EXPECT_DOUBLE_EQ(3.4, scale.getYFactor()); + EXPECT_DOUBLE_EQ(5.6, scale.getZFactor()); + EXPECT_THAT(scale.getCenter(), AlmostEqPoint(Point3D {4, 5, 6})); +} + TEST(GeometryOperatorsIO, readConvertUnits) { auto converter = readSingleOperator({Dimensions::Three, LengthUnit::inches}, R"( @@ -731,6 +756,7 @@ TEST(GeometryOperatorsIO, readNamedOperators_basic) EXPECT_EQ(expectedScaleProperties, scale.getEndProperties()); EXPECT_EQ(1.5, scale.getXFactor()); EXPECT_EQ(1.5, scale.getYFactor()); + EXPECT_THAT(scale.getCenter(), AlmostEqPoint(Point3D {0, 0, 0})); } TEST(GeometryOperatorsIO, readNamedOperators_invalidDimensions) @@ -847,6 +873,7 @@ TEST(GeometryOperatorsIO, readNamedOperators_ref) auto scale = copyOperator(composite->getOperators()[0]); EXPECT_EQ(1.5, scale.getXFactor()); EXPECT_EQ(1.5, scale.getYFactor()); + EXPECT_THAT(scale.getCenter(), AlmostEqPoint(Point3D {0, 0, 0})); auto referencedOperator = copyOperator(composite->getOperators()[1]); ASSERT_EQ(1u, referencedOperator.getOperators().size()); diff --git a/src/axom/klee/tests/klee_io.cpp b/src/axom/klee/tests/klee_io.cpp index 10af4d0017..c14642982d 100644 --- a/src/axom/klee/tests/klee_io.cpp +++ b/src/axom/klee/tests/klee_io.cpp @@ -23,7 +23,9 @@ namespace klee { namespace { +using primal::Point3D; using primal::Vector3D; +using test::AlmostEqPoint; using test::AlmostEqVector; using ::testing::Contains; using ::testing::HasSubstr; @@ -338,6 +340,38 @@ TEST(IOTest, readShapeSet_geometryOperators) EXPECT_EQ(shapeSet.getDimensions(), translation->getEndProperties().dimensions); } +TEST(IOTest, readShapeSet_geometryOperators_scaleWithCenter) +{ + auto shapeSet = readShapeSetFromString(R"( + dimensions: 2 + shapes: + - name: wheel + material: steel + geometry: + format: test_format + path: path/to/file.format + units: m + operators: + - scale: [1.5, 2.5] + center: [10, 20] + )"); + auto &shapes = shapeSet.getShapes(); + ASSERT_EQ(1u, shapes.size()); + auto &geometryOperator = shapes[0].getGeometry().getGeometryOperator(); + ASSERT_TRUE(geometryOperator); + + auto composite = std::dynamic_pointer_cast(geometryOperator); + ASSERT_TRUE(composite); + ASSERT_EQ(1u, composite->getOperators().size()); + + auto scale = dynamic_cast(composite->getOperators()[0].get()); + ASSERT_NE(scale, nullptr); + EXPECT_DOUBLE_EQ(1.5, scale->getXFactor()); + EXPECT_DOUBLE_EQ(2.5, scale->getYFactor()); + EXPECT_DOUBLE_EQ(1.0, scale->getZFactor()); + EXPECT_THAT(scale->getCenter(), AlmostEqPoint(Point3D {10, 20, 0})); +} + TEST(IOTest, readShapeSet_geometryOperatorsWithoutUnits) { try From 2f43c3b0c53b7b06270f9a52062a3cda73175432 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 27 Apr 2026 17:56:10 -0700 Subject: [PATCH 138/986] Fixed scale transform --- src/axom/core/numerics/transforms.hpp | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/axom/core/numerics/transforms.hpp b/src/axom/core/numerics/transforms.hpp index 95da0a4d7e..873724526b 100644 --- a/src/axom/core/numerics/transforms.hpp +++ b/src/axom/core/numerics/transforms.hpp @@ -234,13 +234,19 @@ template Matrix scale(T sx, T sy, const axom::ArrayView ¢er) { assert(center.size() == 2); + const T zero {0}; + if(axom::utilities::isNearlyEqual(center[0], zero) && axom::utilities::isNearlyEqual(center[1], zero)) + { + return scale(sx, sy); + } + const auto T0 = translate(-center[0], -center[1]); const auto S = scale(sx, sy, 3); const auto T1 = translate(center[0], center[1]); - Matrix TS, result; - matrix_multiply(T0, S, TS); - matrix_multiply(TS, T1, result); + Matrix TS(Matrix::identity(3)), result(Matrix::identity(3)); + matrix_multiply(T1, S, TS); + matrix_multiply(TS, T0, result); return result; } @@ -258,13 +264,21 @@ template Matrix scale(T sx, T sy, T sz, const axom::ArrayView ¢er) { assert(center.size() == 3); + const T zero {0}; + if(axom::utilities::isNearlyEqual(center[0], zero) + && axom::utilities::isNearlyEqual(center[1], zero) + && axom::utilities::isNearlyEqual(center[2], zero)) + { + return scale(sx, sy, sz, 4); + } + const auto T0 = translate(-center[0], -center[1], -center[2]); const auto S = scale(sx, sy, sz, 4); const auto T1 = translate(center[0], center[1], center[2]); - Matrix TS, result; - matrix_multiply(T0, S, TS); - matrix_multiply(TS, T1, result); + Matrix TS(Matrix::identity(4)), result(Matrix::identity(4)); + matrix_multiply(T1, S, TS); + matrix_multiply(TS, T0, result); return result; } From d8d87cc87ade6409de90daed81564dade2fb6239 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 27 Apr 2026 17:56:51 -0700 Subject: [PATCH 139/986] make style --- src/axom/core/numerics/transforms.hpp | 9 +-- src/axom/klee/AffineMatrixVisitor.cpp | 68 ++++++++++--------- src/axom/klee/AffineMatrixVisitor.hpp | 2 +- src/axom/klee/io/GeometryOperatorsIO.cpp | 2 +- .../klee/tests/klee_geometry_operators.cpp | 7 +- 5 files changed, 46 insertions(+), 42 deletions(-) diff --git a/src/axom/core/numerics/transforms.hpp b/src/axom/core/numerics/transforms.hpp index 873724526b..efa9d80d1b 100644 --- a/src/axom/core/numerics/transforms.hpp +++ b/src/axom/core/numerics/transforms.hpp @@ -235,7 +235,8 @@ Matrix scale(T sx, T sy, const axom::ArrayView ¢er) { assert(center.size() == 2); const T zero {0}; - if(axom::utilities::isNearlyEqual(center[0], zero) && axom::utilities::isNearlyEqual(center[1], zero)) + if(axom::utilities::isNearlyEqual(center[0], zero) && + axom::utilities::isNearlyEqual(center[1], zero)) { return scale(sx, sy); } @@ -265,9 +266,9 @@ Matrix scale(T sx, T sy, T sz, const axom::ArrayView ¢er) { assert(center.size() == 3); const T zero {0}; - if(axom::utilities::isNearlyEqual(center[0], zero) - && axom::utilities::isNearlyEqual(center[1], zero) - && axom::utilities::isNearlyEqual(center[2], zero)) + if(axom::utilities::isNearlyEqual(center[0], zero) && + axom::utilities::isNearlyEqual(center[1], zero) && + axom::utilities::isNearlyEqual(center[2], zero)) { return scale(sx, sy, sz, 4); } diff --git a/src/axom/klee/AffineMatrixVisitor.cpp b/src/axom/klee/AffineMatrixVisitor.cpp index 6011049963..82c9ad94da 100644 --- a/src/axom/klee/AffineMatrixVisitor.cpp +++ b/src/axom/klee/AffineMatrixVisitor.cpp @@ -8,38 +8,42 @@ namespace axom::klee { -AffineMatrixVisitor::AffineMatrixVisitor() : GeometryOperatorVisitor(), m_isValid(false), m_matrix(4, 4) { } +AffineMatrixVisitor::AffineMatrixVisitor() + : GeometryOperatorVisitor() + , m_isValid(false) + , m_matrix(4, 4) +{ } - void AffineMatrixVisitor::visit(const klee::Translation& translation) - { - m_matrix = translation.toMatrix(); - m_isValid = true; - } - void AffineMatrixVisitor::visit(const klee::Rotation& rotation) - { - m_matrix = rotation.toMatrix(); - m_isValid = true; - } - void AffineMatrixVisitor::visit(const klee::Scale& scale) - { - m_matrix = scale.toMatrix(); - m_isValid = true; - } - void AffineMatrixVisitor::visit(const klee::UnitConverter& converter) - { - m_matrix = converter.toMatrix(); - m_isValid = true; - } +void AffineMatrixVisitor::visit(const klee::Translation& translation) +{ + m_matrix = translation.toMatrix(); + m_isValid = true; +} +void AffineMatrixVisitor::visit(const klee::Rotation& rotation) +{ + m_matrix = rotation.toMatrix(); + m_isValid = true; +} +void AffineMatrixVisitor::visit(const klee::Scale& scale) +{ + m_matrix = scale.toMatrix(); + m_isValid = true; +} +void AffineMatrixVisitor::visit(const klee::UnitConverter& converter) +{ + m_matrix = converter.toMatrix(); + m_isValid = true; +} - void AffineMatrixVisitor::visit(const klee::CompositeOperator&) - { - SLIC_WARNING_ROOT("CompositeOperator not supported for Shaper query"); - m_isValid = false; - } - void AffineMatrixVisitor::visit(const klee::SliceOperator&) - { - SLIC_WARNING_ROOT("SliceOperator not yet supported for Shaper query"); - m_isValid = false; - } +void AffineMatrixVisitor::visit(const klee::CompositeOperator&) +{ + SLIC_WARNING_ROOT("CompositeOperator not supported for Shaper query"); + m_isValid = false; +} +void AffineMatrixVisitor::visit(const klee::SliceOperator&) +{ + SLIC_WARNING_ROOT("SliceOperator not yet supported for Shaper query"); + m_isValid = false; +} -} // end namespace axom::klee +} // end namespace axom::klee diff --git a/src/axom/klee/AffineMatrixVisitor.hpp b/src/axom/klee/AffineMatrixVisitor.hpp index 4c3fc09897..640d37d9ff 100644 --- a/src/axom/klee/AffineMatrixVisitor.hpp +++ b/src/axom/klee/AffineMatrixVisitor.hpp @@ -37,6 +37,6 @@ class AffineMatrixVisitor : public GeometryOperatorVisitor numerics::Matrix m_matrix; }; -} // end namespace axom::klee +} // end namespace axom::klee #endif diff --git a/src/axom/klee/io/GeometryOperatorsIO.cpp b/src/axom/klee/io/GeometryOperatorsIO.cpp index dca9ab80e7..9a1daf4024 100644 --- a/src/axom/klee/io/GeometryOperatorsIO.cpp +++ b/src/axom/klee/io/GeometryOperatorsIO.cpp @@ -371,7 +371,7 @@ OpPtr parseScale(const inlet::Container &opContainer, { factors.emplace_back(1.0); } - Point3D center{0., 0., 0.}; + Point3D center {0., 0., 0.}; if(opContainer.contains("center")) { center = toPoint(opContainer, "center", startProperties.dimensions, Point3D {0, 0, 0}); diff --git a/src/axom/klee/tests/klee_geometry_operators.cpp b/src/axom/klee/tests/klee_geometry_operators.cpp index a24b6d0780..5886794a1d 100644 --- a/src/axom/klee/tests/klee_geometry_operators.cpp +++ b/src/axom/klee/tests/klee_geometry_operators.cpp @@ -227,7 +227,7 @@ TEST(Scale, basics) EXPECT_DOUBLE_EQ(0., scale.getCenter()[1]); EXPECT_DOUBLE_EQ(0., scale.getCenter()[2]); - Scale scale2 {2, 4, 6, Point3D{0.5, 0.5, 0.5}, {Dimensions::Three, LengthUnit::cm}}; + Scale scale2 {2, 4, 6, Point3D {0.5, 0.5, 0.5}, {Dimensions::Three, LengthUnit::cm}}; EXPECT_DOUBLE_EQ(2, scale2.getXFactor()); EXPECT_DOUBLE_EQ(4, scale2.getYFactor()); EXPECT_DOUBLE_EQ(6, scale2.getZFactor()); @@ -241,7 +241,7 @@ TEST(Scale, toMatrix) Scale scale {2, 3, 4, {Dimensions::Three, LengthUnit::cm}}; EXPECT_THAT(scale.toMatrix(), AlmostEqMatrix(affine({{{2, 0, 0, 0}, {0, 3, 0, 0}, {0, 0, 4, 0}}}))); - Scale scale2 {2, 2, 2, Point3D{0.5, 0.5, 0.5}, {Dimensions::Three, LengthUnit::cm}}; + Scale scale2 {2, 2, 2, Point3D {0.5, 0.5, 0.5}, {Dimensions::Three, LengthUnit::cm}}; EXPECT_THAT(scale2.toMatrix(), AlmostEqMatrix(affine({{{2, 0, 0, -0.5}, {0, 2, 0, -0.5}, {0, 0, 2, -0.5}}}))); @@ -263,8 +263,7 @@ TEST(Scale, toMatrix) AlmostEqPoint(affinePoint({1.5, 1.5, 1.5}))); EXPECT_THAT(scale2.toMatrix() * affinePoint({0., 1., 1.}), AlmostEqPoint(affinePoint({-0.5, 1.5, 1.5}))); - EXPECT_THAT(scale2.toMatrix() * affineVec({1., 1., 1.}), - AlmostEqVector(affineVec({2., 2., 2.}))); + EXPECT_THAT(scale2.toMatrix() * affineVec({1., 1., 1.}), AlmostEqVector(affineVec({2., 2., 2.}))); } TEST(Scale, accept) From 37b907eb784e2a4c9654181c7b1126cab0cf3507 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 27 Apr 2026 18:02:59 -0700 Subject: [PATCH 140/986] Update docs --- src/axom/klee/docs/sphinx/specifying_shapes.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/axom/klee/docs/sphinx/specifying_shapes.rst b/src/axom/klee/docs/sphinx/specifying_shapes.rst index 75e43ce2db..c5cfd704d6 100644 --- a/src/axom/klee/docs/sphinx/specifying_shapes.rst +++ b/src/axom/klee/docs/sphinx/specifying_shapes.rst @@ -268,12 +268,19 @@ Operators may also have additional required or optional parameters. :name: :code:`scale` :value: a vector specifying the amount by which to scale in each dimension, or a single value specifying by which to scale in all dimensions + :optional arguments: + :center: a point specifying the center relative to which to scale. + If omitted, scaling is performed relative to the origin. :example: :: # Scale by 2x in the x direction 0.5x in y, and 1.5x in z scale: [2.0, 0.5, 1.5] + # Scale by 2x in every direction relative to the point (1, 2, 3) + scale: 2.0 + center: [1, 2, 3] + * Changing Units :description: Change the units in which subsequent operators are expressed. From cdca4f7f41c7ee029377c4ad4770d1aa19641ee5 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 27 Apr 2026 18:04:35 -0700 Subject: [PATCH 141/986] Update RELEASE-NOTES.md --- RELEASE-NOTES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index b060e18ef1..1b4eaf739c 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -26,6 +26,7 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ - Primal: Adds `NURBSPatch::isTriviallyTrimmed()` to check if the trimming curves for a patch lie on the patch boundaries - Quest: Adds support for reading mfem files with variable order NURBS curves (requires mfem>4.9). - Quest: Adds OMP support for fast GWN methods for STL/Triangulated STEP input and linearized NURBS Curve input. +- Klee: Adds an optional "center" parameter in scale operators that permits scaling relative to a custom center point. ### Removed From 6c5cdf2e24119fb8841c9c4ddbd83df72d1cf489 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Tue, 28 Apr 2026 08:02:13 -0700 Subject: [PATCH 142/986] styling --- src/axom/quest/tests/quest_gwn_methods.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/axom/quest/tests/quest_gwn_methods.cpp b/src/axom/quest/tests/quest_gwn_methods.cpp index 3238eb825c..f366b73e3e 100644 --- a/src/axom/quest/tests/quest_gwn_methods.cpp +++ b/src/axom/quest/tests/quest_gwn_methods.cpp @@ -338,7 +338,7 @@ TEST(quest_gwn_methods, mfem_mesh_linearization) check_mfem_mesh_linearization(); } -#if defined AXOM_USE_OPENMP && defined(AXOM_USE_RAJA) +#if defined(AXOM_USE_OPENMP) && defined(AXOM_USE_RAJA) TEST(quest_gwn_methods, mfem_mesh_linearization_omp) { check_mfem_mesh_linearization(); From eb745c14cc64d36963a0d1fe021863a60fd286e7 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Tue, 28 Apr 2026 08:25:33 -0700 Subject: [PATCH 143/986] Have AXOM_USE_XXX respect ENABLE_XXX=OFF --- src/cmake/AxomConfig.cmake | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/cmake/AxomConfig.cmake b/src/cmake/AxomConfig.cmake index 266c4abd3b..bf023570b4 100644 --- a/src/cmake/AxomConfig.cmake +++ b/src/cmake/AxomConfig.cmake @@ -16,7 +16,9 @@ message(STATUS "Configuring Axom version ${AXOM_VERSION_FULL}") ## check for vars of the form _FOUND or ENABLE_ set(TPL_DEPS ADIAK C2C CALIPER CAMP CLI11 CONDUIT CUDA FMT HIP HDF5 LUA MFEM MPI OPENMP OPENCASCADE RAJA SCR SOL SPARSEHASH UMPIRE ZLIB) foreach(dep ${TPL_DEPS}) - if( ${dep}_FOUND OR ENABLE_${dep} ) + if( (DEFINED ENABLE_${dep} AND ENABLE_${dep}) + OR + (NOT DEFINED ENABLE_${dep} AND ${dep}_FOUND)) set(AXOM_USE_${dep} TRUE ) endif() endforeach() From 992509b88d30400a65e4d3d80392aac9921a7567 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 28 Apr 2026 09:51:59 -0700 Subject: [PATCH 144/986] Fix some new warnings --- src/axom/core/tests/core_static_array.hpp | 2 ++ .../quest/tests/quest_sampling_shaper.cpp | 34 ------------------- 2 files changed, 2 insertions(+), 34 deletions(-) diff --git a/src/axom/core/tests/core_static_array.hpp b/src/axom/core/tests/core_static_array.hpp index 1e4e7c042a..4fda3d7597 100644 --- a/src/axom/core/tests/core_static_array.hpp +++ b/src/axom/core/tests/core_static_array.hpp @@ -25,6 +25,8 @@ struct DevicePair AXOM_HOST_DEVICE explicit DevicePair(int value) : first(value), second(-value) { } + AXOM_HOST_DEVICE DevicePair(const DevicePair &obj) : first(obj.first), second(obj.second) { } + AXOM_HOST_DEVICE DevicePair& operator=(const DevicePair& other) { first = other.first; diff --git a/src/axom/quest/tests/quest_sampling_shaper.cpp b/src/axom/quest/tests/quest_sampling_shaper.cpp index 474a90e0d1..db72d9ea51 100644 --- a/src/axom/quest/tests/quest_sampling_shaper.cpp +++ b/src/axom/quest/tests/quest_sampling_shaper.cpp @@ -432,7 +432,6 @@ class SamplingShaperTest : public ::testing::Test class SamplingShaperTest2D : public SamplingShaperTest { public: - using Point2D = primal::Point; using BBox2D = primal::BoundingBox; public: @@ -559,7 +558,6 @@ class SamplingShaperTest3D : public SamplingShaperTest class SampleTester2D : public SamplingShaperTest { public: - using Point2D = primal::Point; using BBox2D = primal::BoundingBox; public: @@ -675,9 +673,6 @@ dimensions: 2 TEST_F(SamplingShaperTest2D, basic_circle_projector) { - using Point2D = primal::Point; - using Point3D = primal::Point; - const auto& testname = ::testing::UnitTest::GetInstance()->current_test_info()->name(); const std::string shape_template = R"( @@ -725,9 +720,6 @@ dimensions: 2 TEST_F(SamplingShaperTest2D, circle_projector_anisotropic) { - using Point2D = primal::Point; - using Point3D = primal::Point; - const auto& testname = ::testing::UnitTest::GetInstance()->current_test_info()->name(); const std::string shape_template = R"( @@ -833,8 +825,6 @@ units: cm TEST_F(SamplingShaperTest2D, disk_via_replacement_with_background) { - using Point2D = typename SamplingShaperTest2D::Point2D; - const auto& testname = ::testing::UnitTest::GetInstance()->current_test_info()->name(); const std::string shape_template = R"( @@ -943,8 +933,6 @@ units: cm TEST_F(SamplingShaperTest2D, preshaped_materials) { - using Point2D = typename SamplingShaperTest2D::Point2D; - const std::string& testname = ::testing::UnitTest::GetInstance()->current_test_info()->name(); const std::string shape_template = R"( @@ -1024,8 +1012,6 @@ units: cm TEST_F(SamplingShaperTest2D, disk_with_multiple_preshaped_materials) { - using Point2D = typename SamplingShaperTest2D::Point2D; - const std::string& testname = ::testing::UnitTest::GetInstance()->current_test_info()->name(); const std::string shape_template = R"( @@ -1226,9 +1212,6 @@ dimensions: 2 TEST_F(SamplingShaperTest2D, contour_and_stl_2D) { - using Point2D = primal::Point; - using Point3D = primal::Point; - const auto& testname = ::testing::UnitTest::GetInstance()->current_test_info()->name(); constexpr double radius = 1.5; @@ -1323,8 +1306,6 @@ dimensions: 2 TEST_F(SamplingShaperTest2D, contour_and_mfem_2D) { - using Point2D = primal::Point; - const auto& testname = ::testing::UnitTest::GetInstance()->current_test_info()->name(); // Shape file @@ -1726,9 +1707,6 @@ dimensions: 3 TEST_F(SamplingShaperTest3D, tet_boundary_identity_projector) { - using Point2D = primal::Point; - using Point3D = primal::Point; - const auto& testname = ::testing::UnitTest::GetInstance()->current_test_info()->name(); const std::string shape_template = R"( @@ -1780,9 +1758,6 @@ dimensions: 3 TEST_F(SamplingShaperTest3D, tet_doubling_projector) { - using Point2D = primal::Point; - using Point3D = primal::Point; - const auto& testname = ::testing::UnitTest::GetInstance()->current_test_info()->name(); const std::string shape_template = R"( @@ -1832,9 +1807,6 @@ dimensions: 3 TEST_F(SamplingShaperTest3D, circle_2D_projector) { - using Point2D = primal::Point; - using Point3D = primal::Point; - const auto& testname = ::testing::UnitTest::GetInstance()->current_test_info()->name(); constexpr double radius = 1.5; @@ -1894,9 +1866,6 @@ dimensions: 2 TEST_F(SamplingShaperTest3D, contour_and_stl_3D) { - using Point2D = primal::Point; - using Point3D = primal::Point; - const auto& testname = ::testing::UnitTest::GetInstance()->current_test_info()->name(); constexpr double radius = 1.5; @@ -1970,9 +1939,6 @@ dimensions: 2 TEST_F(SamplingShaperTest2D, shape_proe_tet_with_2D_projection) { - using Point2D = primal::Point; - using Point3D = primal::Point; - const auto& testname = ::testing::UnitTest::GetInstance()->current_test_info()->name(); const std::string shape_template = R"( From 31649b9be25a238b157561f8a5c2b42e33942d20 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 28 Apr 2026 09:52:54 -0700 Subject: [PATCH 145/986] make style --- src/axom/core/tests/core_static_array.hpp | 2 +- src/axom/core/tests/numerics_transforms.hpp | 5 +---- src/axom/klee/tests/klee_io.cpp | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/axom/core/tests/core_static_array.hpp b/src/axom/core/tests/core_static_array.hpp index 4fda3d7597..58e7b9d119 100644 --- a/src/axom/core/tests/core_static_array.hpp +++ b/src/axom/core/tests/core_static_array.hpp @@ -25,7 +25,7 @@ struct DevicePair AXOM_HOST_DEVICE explicit DevicePair(int value) : first(value), second(-value) { } - AXOM_HOST_DEVICE DevicePair(const DevicePair &obj) : first(obj.first), second(obj.second) { } + AXOM_HOST_DEVICE DevicePair(const DevicePair& obj) : first(obj.first), second(obj.second) { } AXOM_HOST_DEVICE DevicePair& operator=(const DevicePair& other) { diff --git a/src/axom/core/tests/numerics_transforms.hpp b/src/axom/core/tests/numerics_transforms.hpp index e48b17f170..7905613b30 100644 --- a/src/axom/core/tests/numerics_transforms.hpp +++ b/src/axom/core/tests/numerics_transforms.hpp @@ -28,10 +28,7 @@ void expect_matrix_near(const axom::numerics::Matrix& actual, } } -void expect_vector_near(const double* actual, - const double* expected, - int size, - double tolerance = 1e-12) +void expect_vector_near(const double* actual, const double* expected, int size, double tolerance = 1e-12) { for(int i = 0; i < size; ++i) { diff --git a/src/axom/klee/tests/klee_io.cpp b/src/axom/klee/tests/klee_io.cpp index c3e9776195..25f1d7b37a 100644 --- a/src/axom/klee/tests/klee_io.cpp +++ b/src/axom/klee/tests/klee_io.cpp @@ -47,7 +47,7 @@ ShapeSet readShapeSetFromString(const std::string &input) std::istringstream istream(input); return klee::readShapeSet(istream); } -} // end namespace +} // end namespace TEST(IOTest, readShapeSet_noShapes) { From 4e14e6dd7f7a52ee24dd3107883f7c5189be5df4 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Tue, 28 Apr 2026 15:06:51 -0700 Subject: [PATCH 146/986] Document AXOM_USE_XXX, ENABLE_XXX combinations --- src/cmake/AxomConfig.cmake | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/cmake/AxomConfig.cmake b/src/cmake/AxomConfig.cmake index bf023570b4..fea39381cb 100644 --- a/src/cmake/AxomConfig.cmake +++ b/src/cmake/AxomConfig.cmake @@ -12,8 +12,25 @@ message(STATUS "Configuring Axom version ${AXOM_VERSION_FULL}") ## Add a definition to the generated config file for each library dependency -## (optional and built-in) that we might need to know about in the code. We -## check for vars of the form _FOUND or ENABLE_ +## (optional and built-in) that we might need to know about in the code. +## +## We check for vars of the form _FOUND or ENABLE_: +## ENABLE_ = ON && _FOUND = TRUE --> AXOM_USE_ defined +## ENABLE_ = ON && _FOUND = FALSE --> AXOM_USE_ defined +## ENABLE_ = ON && _FOUND undefined --> AXOM_USE_ defined +## ENABLE_ = OFF && _FOUND = TRUE --> AXOM_USE_ undefined +## ENABLE_ = OFF && _FOUND = FALSE --> AXOM_USE_ undefined +## ENABLE_ = OFF && _FOUND undefined --> AXOM_USE_ undefined +## ENABLE_ undefined && _FOUND = TRUE --> AXOM_USE_ defined +## ENABLE_ undefined && _FOUND = FALSE --> AXOM_USE_ undefined +## ENABLE_ undefined && _FOUND undefined --> AXOM_USE_ undefined +## +## Checks first if ENABLE_ is defined ON or OFF to determine if Axom +## will be configured with or without the dependency. +## Allows Axom to be configured without a dependency, even when _FOUND +## is defined by that dependency's find_package(). +## If ENABLE_ is undefined, Axom checks if _FOUND is a true value. +## set(TPL_DEPS ADIAK C2C CALIPER CAMP CLI11 CONDUIT CUDA FMT HIP HDF5 LUA MFEM MPI OPENMP OPENCASCADE RAJA SCR SOL SPARSEHASH UMPIRE ZLIB) foreach(dep ${TPL_DEPS}) if( (DEFINED ENABLE_${dep} AND ENABLE_${dep}) From 47341bdc763f9432708206bc08e37a1769dd89a0 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Tue, 28 Apr 2026 15:19:05 -0700 Subject: [PATCH 147/986] Propagate --expt-relaxed-constexpr flag to CUDA host-configs and spack recipe --- host-configs/rzvector-toss_4_x86_64_ib-gcc@13.3.1_cuda.cmake | 2 +- host-configs/rzvector-toss_4_x86_64_ib-llvm@19.1.3_cuda.cmake | 2 +- scripts/spack/packages/axom/package.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/host-configs/rzvector-toss_4_x86_64_ib-gcc@13.3.1_cuda.cmake b/host-configs/rzvector-toss_4_x86_64_ib-gcc@13.3.1_cuda.cmake index c7c5bc2f42..89cb21e7c5 100644 --- a/host-configs/rzvector-toss_4_x86_64_ib-gcc@13.3.1_cuda.cmake +++ b/host-configs/rzvector-toss_4_x86_64_ib-gcc@13.3.1_cuda.cmake @@ -81,7 +81,7 @@ set(ENABLE_CUDA ON CACHE BOOL "") set(CMAKE_CUDA_SEPARABLE_COMPILATION ON CACHE BOOL "") -set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -restrict --expt-extended-lambda " CACHE STRING "" FORCE) +set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -restrict --expt-extended-lambda --expt-relaxed-constexpr " CACHE STRING "" FORCE) # nvcc does not like gtest's 'pthreads' flag diff --git a/host-configs/rzvector-toss_4_x86_64_ib-llvm@19.1.3_cuda.cmake b/host-configs/rzvector-toss_4_x86_64_ib-llvm@19.1.3_cuda.cmake index c4d502e09a..88fa95deb8 100644 --- a/host-configs/rzvector-toss_4_x86_64_ib-llvm@19.1.3_cuda.cmake +++ b/host-configs/rzvector-toss_4_x86_64_ib-llvm@19.1.3_cuda.cmake @@ -83,7 +83,7 @@ set(ENABLE_CUDA ON CACHE BOOL "") set(CMAKE_CUDA_SEPARABLE_COMPILATION ON CACHE BOOL "") -set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -restrict --expt-extended-lambda " CACHE STRING "" FORCE) +set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -restrict --expt-extended-lambda --expt-relaxed-constexpr " CACHE STRING "" FORCE) # nvcc does not like gtest's 'pthreads' flag diff --git a/scripts/spack/packages/axom/package.py b/scripts/spack/packages/axom/package.py index 18763ed045..b54ae62ce7 100644 --- a/scripts/spack/packages/axom/package.py +++ b/scripts/spack/packages/axom/package.py @@ -435,7 +435,7 @@ def initconfig_hardware_entries(self): entries.append(cmake_cache_option("CMAKE_CUDA_SEPARABLE_COMPILATION", True)) # CUDA_FLAGS - cudaflags = "${CMAKE_CUDA_FLAGS} -restrict --expt-extended-lambda " + cudaflags = "${CMAKE_CUDA_FLAGS} -restrict --expt-extended-lambda --expt-relaxed-constexpr " # Pass through any cxxflags to the host compiler via nvcc's Xcompiler flag host_cxx_flags = spec.compiler_flags["cxxflags"] From e35d4c1a42a6a1e564bce19db853c70115598f8e Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 28 Apr 2026 15:42:17 -0700 Subject: [PATCH 148/986] Added 2D constructor overloads for klee Scale class. --- src/axom/klee/GeometryOperators.cpp | 11 ++++++ src/axom/klee/GeometryOperators.hpp | 34 +++++++++++++++++-- .../klee/tests/klee_geometry_operators.cpp | 17 ++++++++++ 3 files changed, 59 insertions(+), 3 deletions(-) diff --git a/src/axom/klee/GeometryOperators.cpp b/src/axom/klee/GeometryOperators.cpp index 422f35d6eb..625b95aad2 100644 --- a/src/axom/klee/GeometryOperators.cpp +++ b/src/axom/klee/GeometryOperators.cpp @@ -107,6 +107,10 @@ numerics::Matrix Rotation::toMatrix() const void Rotation::accept(GeometryOperatorVisitor &visitor) const { visitor.visit(*this); } +Scale::Scale(double xFactor, double yFactor, const TransformableGeometryProperties &startProperties) + : Scale(xFactor, yFactor, 1., startProperties) +{ } + Scale::Scale(double xFactor, double yFactor, double zFactor, @@ -118,6 +122,13 @@ Scale::Scale(double xFactor, , m_center {0., 0., 0.} { } +Scale::Scale(double xFactor, + double yFactor, + const primal::Point2D ¢er, + const TransformableGeometryProperties &startProperties) + : Scale(xFactor, yFactor, 1., primal::Point3D({center[0], center[1], 0.}), startProperties) +{ } + Scale::Scale(double xFactor, double yFactor, double zFactor, diff --git a/src/axom/klee/GeometryOperators.hpp b/src/axom/klee/GeometryOperators.hpp index 3f26a96611..9bf58049c4 100644 --- a/src/axom/klee/GeometryOperators.hpp +++ b/src/axom/klee/GeometryOperators.hpp @@ -203,6 +203,17 @@ class Rotation : public MatrixOperator class Scale : public MatrixOperator { public: + /** + * Create a new Scale operator. + * + * \param xFactor the amount by which to scale in the x direction + * \param yFactor the amount by which to scale in the y direction + * \param startProperties the initial properties, as in the parent class. + * + * \note The scaling factor used for the 3rd dimension is 1. + */ + Scale(double xFactor, double yFactor, const TransformableGeometryProperties &startProperties); + /** * Create a new Scale operator. * @@ -210,13 +221,29 @@ class Scale : public MatrixOperator * \param yFactor the amount by which to scale in the y direction * \param zFactor the amount by which to scale in the z direction * \param startProperties the initial properties, as in the parent class. - * If the number of dimensions is 2, zFactor should be 1.0, but this is not enforced. + * + * \note If the number of dimensions is 2, zFactor should be 1.0, but this is not enforced. */ Scale(double xFactor, double yFactor, double zFactor, const TransformableGeometryProperties &startProperties); + /** + * Create a new Scale operator. + * + * \param xFactor the amount by which to scale in the x direction + * \param yFactor the amount by which to scale in the y direction + * \param center The center relative to which the scaling is performed. + * \param startProperties the initial properties, as in the parent class. + * + * \note The scaling factor used for the 3rd dimension is 1. + */ + Scale(double xFactor, + double yFactor, + const primal::Point2D ¢er, + const TransformableGeometryProperties &startProperties); + /** * Create a new Scale operator. * @@ -224,8 +251,9 @@ class Scale : public MatrixOperator * \param yFactor the amount by which to scale in the y direction * \param zFactor the amount by which to scale in the z direction * \param center The center relative to which the scaling is performed. - * \param startProperties the initial properties, as in the parent class. - * If the number of dimensions is 2, zFactor should be 1.0, but this is not enforced. + * \param startProperties the initial properties, as in the parent class. + * + * \note If the number of dimensions is 2, zFactor should be 1.0, but this is not enforced. */ Scale(double xFactor, double yFactor, diff --git a/src/axom/klee/tests/klee_geometry_operators.cpp b/src/axom/klee/tests/klee_geometry_operators.cpp index 30d6b5c8e2..ed934e0297 100644 --- a/src/axom/klee/tests/klee_geometry_operators.cpp +++ b/src/axom/klee/tests/klee_geometry_operators.cpp @@ -48,6 +48,7 @@ using ::testing::Matcher; using ::testing::Ref; using ::testing::Return; +using primal::Point2D; using primal::Point3D; using primal::Vector3D; @@ -246,6 +247,22 @@ TEST(Scale, basics) EXPECT_DOUBLE_EQ(0.5, scale2.getCenter()[0]); EXPECT_DOUBLE_EQ(0.5, scale2.getCenter()[1]); EXPECT_DOUBLE_EQ(0.5, scale2.getCenter()[2]); + + Scale scale3 {3, 2, {Dimensions::Two, LengthUnit::cm}}; + EXPECT_DOUBLE_EQ(3, scale3.getXFactor()); + EXPECT_DOUBLE_EQ(2, scale3.getYFactor()); + EXPECT_DOUBLE_EQ(1, scale3.getZFactor()); + EXPECT_DOUBLE_EQ(0., scale3.getCenter()[0]); + EXPECT_DOUBLE_EQ(0., scale3.getCenter()[1]); + EXPECT_DOUBLE_EQ(0., scale3.getCenter()[2]); + + Scale scale4 {3, 2, Point2D {0.5, 0.5}, {Dimensions::Two, LengthUnit::cm}}; + EXPECT_DOUBLE_EQ(3, scale4.getXFactor()); + EXPECT_DOUBLE_EQ(2, scale4.getYFactor()); + EXPECT_DOUBLE_EQ(1, scale4.getZFactor()); + EXPECT_DOUBLE_EQ(0.5, scale4.getCenter()[0]); + EXPECT_DOUBLE_EQ(0.5, scale4.getCenter()[1]); + EXPECT_DOUBLE_EQ(0., scale4.getCenter()[2]); } TEST(Scale, toMatrix) From c515821a3bebe8265c9906c20d45c5c90418d769 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 28 Apr 2026 16:05:50 -0700 Subject: [PATCH 149/986] Moved some code from a quest test to a primal slice operator. Added segment-segment intersection. Added tests. --- RELEASE-NOTES.md | 1 + src/axom/primal/CMakeLists.txt | 2 + .../operators/detail/intersect_impl.hpp | 166 ++++++++++++++++++ .../primal/operators/detail/slice_impl.hpp | 64 +++++++ src/axom/primal/operators/intersect.hpp | 17 ++ src/axom/primal/operators/slice.hpp | 42 +++++ src/axom/primal/tests/CMakeLists.txt | 1 + src/axom/primal/tests/primal_intersect.cpp | 36 ++++ src/axom/primal/tests/primal_slice.cpp | 143 +++++++++++++++ .../quest/tests/quest_sampling_shaper.cpp | 43 +---- 10 files changed, 473 insertions(+), 42 deletions(-) create mode 100644 src/axom/primal/operators/detail/slice_impl.hpp create mode 100644 src/axom/primal/operators/slice.hpp create mode 100644 src/axom/primal/tests/primal_slice.cpp diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index b060e18ef1..5a66ebf62c 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -47,6 +47,7 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ leverage error-controlled approximation and a spatial index (BVH). - Slic: Adds new Slic macros that allow you to selectively print messages once per call-site. For example, `SLIC_INFO_ONCE(msg)` and `SLIC_INFO_ROOT_IF_ONCE(EXP, msg)`. +- Primal: Adds a new `slice()` operator to slice tetrahedron with a plane, producing a polygon. ### Changed - Primal: Axom's polygon clipping was modified to handle some corner cases. diff --git a/src/axom/primal/CMakeLists.txt b/src/axom/primal/CMakeLists.txt index 6fb85e2205..5172c7fa2b 100644 --- a/src/axom/primal/CMakeLists.txt +++ b/src/axom/primal/CMakeLists.txt @@ -67,6 +67,7 @@ set( primal_headers operators/in_polygon.hpp operators/in_sphere.hpp operators/is_convex.hpp + operators/slice.hpp operators/split.hpp operators/winding_number.hpp @@ -81,6 +82,7 @@ set( primal_headers operators/detail/intersect_patch_impl.hpp operators/detail/intersect_impl.hpp operators/detail/intersect_ray_impl.hpp + operators/detail/slice_impl.hpp operators/detail/winding_number_2d_impl.hpp operators/detail/winding_number_2d_memoization.hpp operators/detail/winding_number_3d_impl.hpp diff --git a/src/axom/primal/operators/detail/intersect_impl.hpp b/src/axom/primal/operators/detail/intersect_impl.hpp index 97537a4b20..4aa9ddb809 100644 --- a/src/axom/primal/operators/detail/intersect_impl.hpp +++ b/src/axom/primal/operators/detail/intersect_impl.hpp @@ -134,6 +134,172 @@ bool TriangleIntersection2D(const Triangle2& t1, const Triangle2& t2, bool inclu //------------------------------ IMPLEMENTATIONS ------------------------------ +/*! + * \brief Intersect 2 3D line segments and return true if they intersect. + * + * \param P The first line segment. + * \param Q The second line segment. + * \param[out] intersection The intersection point where the segments intersect. + * + * \return True if the line segments intersect; False otherwise. + */ +template +AXOM_HOST_DEVICE bool intersect_segment_segment(const Segment& P, + const Segment& Q, + Point& intersection, + const T EPS = static_cast(1e-08)) +{ + using Vector3D = primal::Vector; + + // Use the standard segment-segment closest-points formulation on the + // segment directions u and v. We only report an intersection when the + // clamped closest points coincide within the supplied tolerance. + const auto u = P.target() - P.source(); + const auto v = Q.target() - Q.source(); + const auto w = P.source() - Q.source(); + + const T a = u.dot(u); + const T b = u.dot(v); + const T c = v.dot(v); + const T d = u.dot(w); + const T e = v.dot(w); + const T D = a * c - b * b; + + intersection = Point(); + + // Handle degenerate segments first so the general path can assume both + // segments have nonzero length. + if(axom::utilities::isNearlyEqual(a, T {0}, EPS) && + axom::utilities::isNearlyEqual(c, T {0}, EPS)) + { + intersection = P.source(); + return P.source().isNearlyEqual(Q.source(), EPS); + } + + if(axom::utilities::isNearlyEqual(a, T {0}, EPS)) + { + const T t = axom::utilities::clampVal(e / c, T {0}, T {1}); + const auto qPoint = Q.at(t); + if(P.source().isNearlyEqual(qPoint, EPS)) + { + intersection = P.source(); + return true; + } + + return false; + } + + if(axom::utilities::isNearlyEqual(c, T {0}, EPS)) + { + const T s = axom::utilities::clampVal(-d / a, T {0}, T {1}); + const auto pPoint = P.at(s); + if(Q.source().isNearlyEqual(pPoint, EPS)) + { + intersection = Q.source(); + return true; + } + + return false; + } + + if(axom::utilities::isNearlyEqual(D, T {0}, EPS)) + { + const auto uxw = Vector3D::cross_product(u, w); + // Parallel segments intersect only if they are also collinear. + // Compare the point-to-line distance against EPS instead of relying on + // Vector::is_zero()'s default tolerance. + if(uxw.squared_norm() > EPS * EPS * a) + { + return false; + } + + // Collinear overlap reduces to a 1D interval overlap on P's + // parametrization. Return the first overlapping point on P. + const T t0 = (Q.source() - P.source()).dot(u) / a; + const T t1 = (Q.target() - P.source()).dot(u) / a; + const T overlapBeg = axom::utilities::max(T {0}, axom::utilities::min(t0, t1)); + const T overlapEnd = axom::utilities::min(T {1}, axom::utilities::max(t0, t1)); + + if(overlapBeg <= overlapEnd + EPS) + { + intersection = P.at(overlapBeg); + return true; + } + + return false; + } + + T sN = (b * e - c * d); + T tN = (a * e - b * d); + T sD = D; + T tD = D; + + // Clamp the unconstrained closest point on each supporting line back to the + // finite segment domain [0,1] x [0,1]. + if(sN < T {0}) + { + sN = T {0}; + tN = e; + tD = c; + } + else if(sN > sD) + { + sN = sD; + tN = e + b; + tD = c; + } + + if(tN < T {0}) + { + tN = T {0}; + if(-d < T {0}) + { + sN = T {0}; + } + else if(-d > a) + { + sN = sD; + } + else + { + sN = -d; + sD = a; + } + } + else if(tN > tD) + { + tN = tD; + if((-d + b) < T {0}) + { + sN = T {0}; + } + else if((-d + b) > a) + { + sN = sD; + } + else + { + sN = -d + b; + sD = a; + } + } + + const T sc = axom::utilities::isNearlyEqual(sN, T {0}, EPS) ? T {0} : sN / sD; + const T tc = axom::utilities::isNearlyEqual(tN, T {0}, EPS) ? T {0} : tN / tD; + + const auto pPoint = P.at(sc); + const auto qPoint = Q.at(tc); + // For skew segments, the closest points generally differ. Only accept the + // result when they collapse to the same point within tolerance. + if(pPoint.isNearlyEqual(qPoint, EPS)) + { + intersection = pPoint; + return true; + } + + return false; +} + /*! @{ @name 3D triangle-triangle intersection */ /*! diff --git a/src/axom/primal/operators/detail/slice_impl.hpp b/src/axom/primal/operators/detail/slice_impl.hpp new file mode 100644 index 0000000000..36b0983d61 --- /dev/null +++ b/src/axom/primal/operators/detail/slice_impl.hpp @@ -0,0 +1,64 @@ +// 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_PRIMAL_SLICE_IMPL_HPP_ +#define AXOM_PRIMAL_SLICE_IMPL_HPP_ + +namespace axom +{ +namespace primal +{ +namespace detail +{ + +/*! + * \brief Slices a 3D tetrahedron with a plane and returns the resulting + * polygon. + * + * \param [in] tet The tetrahedron to slice + * \param [in] plane The slicing plane + * + * \return The polygon obtained from slicing a tetrahedron with a plane. + */ +template +AXOM_HOST_DEVICE primal::Polygon slice_tet_plane(const primal::Tetrahedron& tet, + const primal::Plane& plane) +{ + Polygon intersectionPolygon; + + // find intersection vertices + for(int i = 0; i < 4; ++i) + { + for(int j = i + 1; j < 4; ++j) + { + Segment edge(tet[i], tet[j]); + T t {}; + if(primal::intersect(plane, edge, t)) + { + intersectionPolygon.addVertex(edge.at(t)); + } + } + } + SLIC_ASSERT(intersectionPolygon.numVertices() <= 4); + + // fix the polygon if it bowties + if(intersectionPolygon.numVertices() == 4) + { + Segment seg1(intersectionPolygon[0], intersectionPolygon[1]); + Segment seg2(intersectionPolygon[2], intersectionPolygon[3]); + Point sp; + if(!primal::intersect(seg1, seg2, sp)) + { + axom::utilities::swap(intersectionPolygon[2], intersectionPolygon[3]); + } + } + return intersectionPolygon; +} + +} // namespace detail +} // namespace primal +} // namespace axom +#endif diff --git a/src/axom/primal/operators/intersect.hpp b/src/axom/primal/operators/intersect.hpp index 680decd719..ec88df9483 100644 --- a/src/axom/primal/operators/intersect.hpp +++ b/src/axom/primal/operators/intersect.hpp @@ -44,6 +44,23 @@ namespace axom { namespace primal { + +/*! + * \brief Determines if two 3D segments intersect. + * \param [in] P A 3D line segment + * \param [in] Q A 3D line segment + * \param [out] intersection Intersection point of P and Q. + * \return true iff P intersects with Q, otherwise, false. + */ +template +AXOM_HOST_DEVICE bool intersect(const Segment& P, + const Segment& Q, + Point& intersection, + const T EPS = static_cast(1e-08)) +{ + return detail::intersect_segment_segment(P, Q, intersection, EPS); +} + /// \name Triangle Intersection Routines /// \accelerated /// @{ diff --git a/src/axom/primal/operators/slice.hpp b/src/axom/primal/operators/slice.hpp new file mode 100644 index 0000000000..e042a6f0dd --- /dev/null +++ b/src/axom/primal/operators/slice.hpp @@ -0,0 +1,42 @@ +// 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_PRIMAL_SLICE_HPP_ +#define AXOM_PRIMAL_SLICE_HPP_ +#include "axom/core/utilities/Utilities.hpp" + +#include "axom/primal/geometry/Point.hpp" +#include "axom/primal/geometry/Polygon.hpp" +#include "axom/primal/geometry/Plane.hpp" +#include "axom/primal/geometry/Tetrahedron.hpp" + +#include "axom/primal/operators/intersect.hpp" +#include "axom/primal/operators/detail/slice_impl.hpp" + +namespace axom +{ +namespace primal +{ + +/*! + * \brief Slices a 3D tetrahedron with a plane and returns the resulting + * polygon. + * + * \param [in] tet The tetrahedron to slice + * \param [in] plane The slicing plane + * + * \return The polygon obtained from slicing a tetrahedron with a plane. + */ +template +AXOM_HOST_DEVICE primal::Polygon slice(const primal::Tetrahedron& tet, + const primal::Plane& plane) +{ + return detail::slice_tet_plane(tet, plane); +} + +} // namespace primal +} // namespace axom +#endif diff --git a/src/axom/primal/tests/CMakeLists.txt b/src/axom/primal/tests/CMakeLists.txt index 9546c68d37..ec9fec1d86 100644 --- a/src/axom/primal/tests/CMakeLists.txt +++ b/src/axom/primal/tests/CMakeLists.txt @@ -38,6 +38,7 @@ set( primal_tests primal_rational_bezier.cpp primal_ray_intersect.cpp primal_segment.cpp + primal_slice.cpp primal_sphere.cpp primal_solid_angle.cpp primal_split.cpp diff --git a/src/axom/primal/tests/primal_intersect.cpp b/src/axom/primal/tests/primal_intersect.cpp index 39e29ce41b..08188788a5 100644 --- a/src/axom/primal/tests/primal_intersect.cpp +++ b/src/axom/primal/tests/primal_intersect.cpp @@ -281,6 +281,42 @@ TEST(primal_intersect, more_ray_segment_intersection) } } +TEST(primal_intersect, segment_segment_intersection) +{ + using Point3D = primal::Point; + using Segment3D = primal::Segment; + + Point3D intersection; + + EXPECT_TRUE(primal::intersect(Segment3D(Point3D {0., 0., 0.}, Point3D {1., 1., 0.}), + Segment3D(Point3D {0., 1., 0.}, Point3D {1., 0., 0.}), + intersection)); + EXPECT_TRUE(intersection.isNearlyEqual(Point3D {0.5, 0.5, 0.}, 1e-12)); + + EXPECT_TRUE(primal::intersect(Segment3D(Point3D {0., 0., 0.}, Point3D {1., 0., 0.}), + Segment3D(Point3D {1., 0., 0.}, Point3D {1., 1., 0.}), + intersection)); + EXPECT_TRUE(intersection.isNearlyEqual(Point3D {1., 0., 0.}, 1e-12)); + + EXPECT_TRUE(primal::intersect(Segment3D(Point3D {0., 0., 0.}, Point3D {2., 0., 0.}), + Segment3D(Point3D {1., 0., 0.}, Point3D {3., 0., 0.}), + intersection)); + EXPECT_TRUE(intersection.isNearlyEqual(Point3D {1., 0., 0.}, 1e-12)); + + EXPECT_TRUE(primal::intersect(Segment3D(Point3D {1., 0., 0.}, Point3D {1., 0., 0.}), + Segment3D(Point3D {0., 0., 0.}, Point3D {2., 0., 0.}), + intersection)); + EXPECT_TRUE(intersection.isNearlyEqual(Point3D {1., 0., 0.}, 1e-12)); + + EXPECT_FALSE(primal::intersect(Segment3D(Point3D {0., 0., 0.}, Point3D {1., 0., 0.}), + Segment3D(Point3D {2., 0., 0.}, Point3D {3., 0., 0.}), + intersection)); + + EXPECT_FALSE(primal::intersect(Segment3D(Point3D {0., 0., 0.}, Point3D {1., 0., 0.}), + Segment3D(Point3D {0.5, -1., 1.}, Point3D {0.5, 1., 1.}), + intersection)); +} + TEST(primal_intersect, triangle_empty_aabb_intersection) { constexpr int DIM = 3; diff --git a/src/axom/primal/tests/primal_slice.cpp b/src/axom/primal/tests/primal_slice.cpp new file mode 100644 index 0000000000..8a44f774f8 --- /dev/null +++ b/src/axom/primal/tests/primal_slice.cpp @@ -0,0 +1,143 @@ +// 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/Array.hpp" +#include "axom/core/execution/execution_space.hpp" +#include "axom/core/execution/for_all.hpp" + +#include "axom/primal/geometry/Plane.hpp" +#include "axom/primal/geometry/Point.hpp" +#include "axom/primal/geometry/Tetrahedron.hpp" +#include "axom/primal/operators/slice.hpp" + +#include + +namespace primal = axom::primal; + +namespace +{ + +constexpr double EPS = 1e-10; + +using Point3D = primal::Point; + +template +void expect_vertex_set(const PolygonType& poly, const std::array& expected) +{ + ASSERT_EQ(poly.numVertices(), N); + + bool matched[N] = {false}; + + for(std::size_t i = 0; i < N; ++i) + { + bool found = false; + for(std::size_t j = 0; j < N; ++j) + { + if(!matched[j] && poly[i].isNearlyEqual(expected[j], EPS)) + { + matched[j] = true; + found = true; + break; + } + } + + EXPECT_TRUE(found) << "Unexpected polygon vertex: " << poly[i]; + } +} + +template +void check_slice_policy() +{ + using TetType = primal::Tetrahedron; + using PlaneType = primal::Plane; + using PolygonType = primal::Polygon; + + const TetType tet {Point3D {-1., -1., -1.}, + Point3D {1., 1., -1.}, + Point3D {-1., -1., 1.}, + Point3D {-1., 1., 1.}}; + const PlaneType plane({0., 0., 1.}, 0.); + + const int host_allocator = axom::execution_space::allocatorID(); + const int kernel_allocator = axom::execution_space::allocatorID(); + + axom::Array polys_device(1, 1, kernel_allocator); + auto polys_view = polys_device.view(); + + axom::Array areas_device(1, 1, kernel_allocator); + auto areas_view = areas_device.view(); + + axom::for_all( + 1, + AXOM_LAMBDA(int i) { + polys_view[i] = primal::slice(tet, plane); + areas_view[i] = polys_view[i].area(); + }); + + axom::Array polys_host(polys_device, host_allocator); + axom::Array areas_host(areas_device, host_allocator); + + EXPECT_EQ(polys_host[0].numVertices(), 4); + EXPECT_NEAR(areas_host[0], 1., EPS); + expect_vertex_set(polys_host[0], + std::array {Point3D {-1., -1., 0.}, + Point3D {-1., 0., 0.}, + Point3D {0., 0., 0.}, + Point3D {0., 1., 0.}}); +} + +} // namespace + +TEST(primal_slice, tet_plane_slice_dynamic) +{ + using TetType = primal::Tetrahedron; + using PlaneType = primal::Plane; + + const TetType tet { + Point3D {0., 0., 0.}, Point3D {1., 0., 0.}, Point3D {0., 1., 0.}, Point3D {0., 0., 1.}}; + + const auto poly = primal::slice(tet, PlaneType({0., 0., 1.}, 0.25)); + EXPECT_EQ(poly.numVertices(), 3); + EXPECT_NEAR(poly.area(), 0.28125, EPS); + expect_vertex_set(poly, + std::array {Point3D {0., 0., 0.25}, + Point3D {0.75, 0., 0.25}, + Point3D {0., 0.75, 0.25}}); + + const auto empty_poly = primal::slice(tet, PlaneType({0., 0., 1.}, 2.)); + EXPECT_EQ(empty_poly.numVertices(), 0); +} + +TEST(primal_slice, tet_plane_slice_seq) { check_slice_policy(); } + +#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) + + #ifdef AXOM_USE_OPENMP +TEST(primal_slice, tet_plane_slice_omp) { check_slice_policy(); } + #endif + + #ifdef AXOM_USE_CUDA +AXOM_CUDA_TEST(primal_slice, tet_plane_slice_cuda) +{ + check_slice_policy>(); +} + #endif + + #ifdef AXOM_USE_HIP +TEST(primal_slice, tet_plane_slice_hip) { check_slice_policy>(); } + #endif + +#endif + +int main(int argc, char* argv[]) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/src/axom/quest/tests/quest_sampling_shaper.cpp b/src/axom/quest/tests/quest_sampling_shaper.cpp index 474a90e0d1..e56e21a85a 100644 --- a/src/axom/quest/tests/quest_sampling_shaper.cpp +++ b/src/axom/quest/tests/quest_sampling_shaper.cpp @@ -145,47 +145,6 @@ struct PlaneProjector23 } }; -// Utility function to slice a tetrahedron along a plane -primal::Polygon slice(const primal::Tetrahedron& tet, - const primal::Plane& plane) -{ - primal::Polygon intersectionPolygon; - - // find intersection vertices - for(int i = 0; i < 4; ++i) - { - for(int j = i + 1; j < 4; ++j) - { - primal::Segment edge(tet[i], tet[j]); - double t {}; - if(primal::intersect(plane, edge, t)) - { - intersectionPolygon.addVertex(edge.at(t)); - } - } - } - SLIC_ASSERT(intersectionPolygon.numVertices() <= 4); - - // fix the polygon if it bowties - if(intersectionPolygon.numVertices() == 4) - { - // note: using BezierCurve since Axom doesn't currently have intersect(segment, segment) - primal::BezierCurve seg1(1); - seg1[0] = Point2D(intersectionPolygon[0][0], intersectionPolygon[0][1]); - seg1[1] = Point2D(intersectionPolygon[1][0], intersectionPolygon[1][1]); - primal::BezierCurve seg2(1); - seg2[0] = Point2D(intersectionPolygon[2][0], intersectionPolygon[2][1]); - seg2[1] = Point2D(intersectionPolygon[3][0], intersectionPolygon[3][1]); - axom::Array sp, tp; - - if(!primal::intersect(seg1, seg2, sp, tp)) - { - axom::utilities::swap(intersectionPolygon[2], intersectionPolygon[3]); - } - } - return intersectionPolygon; -} - } // namespace /// Test fixture for SamplingShaper tests on MFEM meshes @@ -2032,7 +1991,7 @@ dimensions: 3 this->initializeShaping(shape_file.getPath()); primal::Plane plane({0, 0, 1}, z); - const auto polygon = slice(tet, plane); + const auto polygon = primal::slice(tet, plane); const double intersectionArea = polygon.area(); SLIC_INFO(axom::fmt::format("Area of intersection polygon: {}", intersectionArea)); From 176b4aa4276b724ba6c38cc4b4bc7ab2cfe31370 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 28 Apr 2026 16:06:20 -0700 Subject: [PATCH 150/986] make style --- src/axom/primal/operators/detail/intersect_impl.hpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/axom/primal/operators/detail/intersect_impl.hpp b/src/axom/primal/operators/detail/intersect_impl.hpp index 4aa9ddb809..7e5a561cc4 100644 --- a/src/axom/primal/operators/detail/intersect_impl.hpp +++ b/src/axom/primal/operators/detail/intersect_impl.hpp @@ -169,8 +169,7 @@ AXOM_HOST_DEVICE bool intersect_segment_segment(const Segment& P, // Handle degenerate segments first so the general path can assume both // segments have nonzero length. - if(axom::utilities::isNearlyEqual(a, T {0}, EPS) && - axom::utilities::isNearlyEqual(c, T {0}, EPS)) + if(axom::utilities::isNearlyEqual(a, T {0}, EPS) && axom::utilities::isNearlyEqual(c, T {0}, EPS)) { intersection = P.source(); return P.source().isNearlyEqual(Q.source(), EPS); From 70bc7ca0f295b5f1cbd763be6fb6df04b7c5c64e Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 28 Apr 2026 16:40:36 -0700 Subject: [PATCH 151/986] make style --- src/axom/primal/operators/detail/slice_impl.hpp | 5 +++-- src/axom/primal/tests/primal_slice.cpp | 11 +++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/axom/primal/operators/detail/slice_impl.hpp b/src/axom/primal/operators/detail/slice_impl.hpp index 36b0983d61..618098496e 100644 --- a/src/axom/primal/operators/detail/slice_impl.hpp +++ b/src/axom/primal/operators/detail/slice_impl.hpp @@ -24,8 +24,9 @@ namespace detail * \return The polygon obtained from slicing a tetrahedron with a plane. */ template -AXOM_HOST_DEVICE primal::Polygon slice_tet_plane(const primal::Tetrahedron& tet, - const primal::Plane& plane) +AXOM_HOST_DEVICE primal::Polygon slice_tet_plane( + const primal::Tetrahedron& tet, + const primal::Plane& plane) { Polygon intersectionPolygon; diff --git a/src/axom/primal/tests/primal_slice.cpp b/src/axom/primal/tests/primal_slice.cpp index 8a44f774f8..a17fa513f4 100644 --- a/src/axom/primal/tests/primal_slice.cpp +++ b/src/axom/primal/tests/primal_slice.cpp @@ -100,8 +100,10 @@ TEST(primal_slice, tet_plane_slice_dynamic) using TetType = primal::Tetrahedron; using PlaneType = primal::Plane; - const TetType tet { - Point3D {0., 0., 0.}, Point3D {1., 0., 0.}, Point3D {0., 1., 0.}, Point3D {0., 0., 1.}}; + const TetType tet {Point3D {0., 0., 0.}, + Point3D {1., 0., 0.}, + Point3D {0., 1., 0.}, + Point3D {0., 0., 1.}}; const auto poly = primal::slice(tet, PlaneType({0., 0., 1.}, 0.25)); EXPECT_EQ(poly.numVertices(), 3); @@ -124,10 +126,7 @@ TEST(primal_slice, tet_plane_slice_omp) { check_slice_policy(); #endif #ifdef AXOM_USE_CUDA -AXOM_CUDA_TEST(primal_slice, tet_plane_slice_cuda) -{ - check_slice_policy>(); -} +AXOM_CUDA_TEST(primal_slice, tet_plane_slice_cuda) { check_slice_policy>(); } #endif #ifdef AXOM_USE_HIP From 95ccbb9e7e3e28918ff1846e504116a6ef577c39 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Tue, 31 Mar 2026 16:37:30 -0700 Subject: [PATCH 152/986] Add mpi4py to +python dependency and necessary hooks --- scripts/spack/packages/axom/package.py | 3 ++- src/cmake/thirdparty/SetupAxomThirdParty.cmake | 5 +++-- src/tools/run_python_with_axom.sh.in | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/scripts/spack/packages/axom/package.py b/scripts/spack/packages/axom/package.py index b54ae62ce7..126ef9867d 100644 --- a/scripts/spack/packages/axom/package.py +++ b/scripts/spack/packages/axom/package.py @@ -282,6 +282,7 @@ class Axom(CachedCMakePackage, CudaPackage, ROCmPackage): depends_on("py-nanobind@2.7.0") depends_on("py-pytest") depends_on("py-numpy") + depends_on("py-mpi4py") depends_on("conduit+python") # Devtools @@ -770,7 +771,7 @@ def initconfig_package_entries(self): if spec.satisfies("+python"): # pytest requires pluggy and iniconfig - for dep in ("py-nanobind", "py-pytest", "py-numpy", "py-pluggy", "py-iniconfig"): + for dep in ("py-nanobind", "py-pytest", "py-numpy", "py-pluggy", "py-iniconfig", "py-mpi4py"): if spec.satisfies("^{0}".format(dep)): dep_dir = get_spec_path(spec, dep, path_replacements, use_lib=True) py_libdir = join_path(dep_dir, f"python{spec['python'].version.up_to(2)}", "site-packages") diff --git a/src/cmake/thirdparty/SetupAxomThirdParty.cmake b/src/cmake/thirdparty/SetupAxomThirdParty.cmake index e063779f94..430e7ba938 100644 --- a/src/cmake/thirdparty/SetupAxomThirdParty.cmake +++ b/src/cmake/thirdparty/SetupAxomThirdParty.cmake @@ -353,13 +353,14 @@ if((NOT PY_ENV_IMPORT_CODE EQUAL 0) OR NOT PY_NUMPY_DIR OR NOT PY_PYTEST_DIR OR NOT PY_PLUGGY_DIR - OR NOT PY_INICONFIG_DIR)) + OR NOT PY_INICONFIG_DIR + OR NOT PY_MPI4PY_DIR)) message(FATAL_ERROR "Axom's python extensions require nanobind, numpy, pytest, and conduit." "\nThe python library installation paths " "(and pytest's dependencies pluggy and iniconfig) " "can be specified with CMake variables: " - "PY_NANOBIND_DIR, CONDUIT_PYTHON_MODULE_DIR, PY_NUMPY_DIR, PY_PYTEST_DIR, PY_PLUGGY_DIR, PY_INICONFIG_DIR ") + "PY_NANOBIND_DIR, CONDUIT_PYTHON_MODULE_DIR, PY_NUMPY_DIR, PY_PYTEST_DIR, PY_PLUGGY_DIR, PY_INICONFIG_DIR, PY_MPI4PY_DIR ") endif() # "cannot allocate memory in static TLS block" on blueos with cuda and/or clang. diff --git a/src/tools/run_python_with_axom.sh.in b/src/tools/run_python_with_axom.sh.in index 152e406ecc..f12ffa08a5 100755 --- a/src/tools/run_python_with_axom.sh.in +++ b/src/tools/run_python_with_axom.sh.in @@ -10,4 +10,4 @@ ## Convenience script that runs python interpreter with Axom extension(s) in ## the PYTHONPATH. ##----------------------------------------------------------------------------- -env PYTHONPATH=@_PYEXT_DIR@:@CONDUIT_PYTHON_MODULE_DIR@:@PY_NANOBIND_DIR@:@PY_NUMPY_DIR@:@PY_PYTEST_DIR@:@PY_PLUGGY_DIR@:@PY_INICONFIG_DIR@:$PYTHONPATH @Python_EXECUTABLE@ "$@" +env PYTHONPATH=@_PYEXT_DIR@:@CONDUIT_PYTHON_MODULE_DIR@:@PY_NANOBIND_DIR@:@PY_NUMPY_DIR@:@PY_PYTEST_DIR@:@PY_PLUGGY_DIR@:@PY_INICONFIG_DIR@:@PY_MPI4PY_DIR@:$PYTHONPATH @Python_EXECUTABLE@ "$@" From 110d0bb92e696a2711fa2bce811fac3d84f4a24b Mon Sep 17 00:00:00 2001 From: Brian Han Date: Tue, 31 Mar 2026 16:38:25 -0700 Subject: [PATCH 153/986] Attempt to fix quest python interface - segfaulting on signed_distance_init --- src/axom/quest/interface/python/pyQUESTmodule.cpp | 6 +++--- src/axom/quest/interface/python/quest_test.py.in | 2 +- src/axom/quest/interface/python/setup.py.in | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/axom/quest/interface/python/pyQUESTmodule.cpp b/src/axom/quest/interface/python/pyQUESTmodule.cpp index 275553173e..f7812b4c4c 100644 --- a/src/axom/quest/interface/python/pyQUESTmodule.cpp +++ b/src/axom/quest/interface/python/pyQUESTmodule.cpp @@ -863,9 +863,9 @@ initquest(void) struct module_state *st = GETSTATE(m); // enum axom::quest::SignedDistExec - PyModule_AddIntConstant(m, "CPU", axom::quest::CPU); - PyModule_AddIntConstant(m, "OpenMP", axom::quest::OpenMP); - PyModule_AddIntConstant(m, "GPU", axom::quest::GPU); + PyModule_AddIntConstant(m, "CPU", static_cast(axom::quest::SignedDistExec::CPU)); + PyModule_AddIntConstant(m, "OpenMP", static_cast(axom::quest::SignedDistExec::OpenMP)); + PyModule_AddIntConstant(m, "GPU", static_cast(axom::quest::SignedDistExec::GPU)); PY_error_obj = PyErr_NewException((char *)error_name, nullptr, nullptr); if(PY_error_obj == nullptr) return RETVAL; diff --git a/src/axom/quest/interface/python/quest_test.py.in b/src/axom/quest/interface/python/quest_test.py.in index 65e472db14..75cbf7d862 100644 --- a/src/axom/quest/interface/python/quest_test.py.in +++ b/src/axom/quest/interface/python/quest_test.py.in @@ -12,7 +12,7 @@ from __future__ import print_function import quest from mpi4py import MPI -quest.signed_distance_init("@CMAKE_SOURCE_DIR@/axom/quest/data/naca0012.stl", +quest.signed_distance_init("@CMAKE_SOURCE_DIR@/data/quest/naca0012.stl", MPI.Comm) if not quest.signed_distance_initialized(): diff --git a/src/axom/quest/interface/python/setup.py.in b/src/axom/quest/interface/python/setup.py.in index 6adfed1a1f..1e1cc9c568 100644 --- a/src/axom/quest/interface/python/setup.py.in +++ b/src/axom/quest/interface/python/setup.py.in @@ -23,7 +23,7 @@ quest = Extension( os.path.join(binarydir, 'include'), os.path.join(mpidir, 'include')], extra_compile_args=['-std=c++11'], # gcc flag - libraries=['quest'], + libraries=['axom_quest'], library_dirs=[libdir], # rpath needed to find libquest.so during import extra_link_args=["-Wl,-rpath," + libdir] From 24ff9f921d73fcc5c0986f3fe537981108a92185 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Thu, 2 Apr 2026 11:37:20 -0700 Subject: [PATCH 154/986] -fPIC fix for linking pysidre against MPI --- scripts/spack/configs/toss_4_x86_64_ib/spack.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scripts/spack/configs/toss_4_x86_64_ib/spack.yaml b/scripts/spack/configs/toss_4_x86_64_ib/spack.yaml index c21897f732..8727e7aeaf 100644 --- a/scripts/spack/configs/toss_4_x86_64_ib/spack.yaml +++ b/scripts/spack/configs/toss_4_x86_64_ib/spack.yaml @@ -74,6 +74,10 @@ spack: c: /usr/tce/packages/gcc/gcc-13.3.1/bin/gcc cxx: /usr/tce/packages/gcc/gcc-13.3.1/bin/g++ fortran: /usr/tce/packages/gcc/gcc-13.3.1/bin/gfortran + flags: + cflags: -fPIC + cxxflags: -fPIC + fflags: -fPIC llvm: externals: - spec: llvm@19.1.3+clang~lld~lldb @@ -83,6 +87,8 @@ spack: c: /usr/tce/packages/clang/clang-19.1.3-magic/bin/clang cxx: /usr/tce/packages/clang/clang-19.1.3-magic/bin/clang++ flags: + cflags: -fPIC + cxxflags: -fPIC fflags: -fPIC intel-oneapi-compilers: externals: From 54ce669d0aa055e1e6b137585eb3f5cfd36152a4 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Thu, 2 Apr 2026 11:49:02 -0700 Subject: [PATCH 155/986] codex 2nd attempt at convert_sidre_protocol.py --- src/axom/sidre/nanobind_sidre.cpp | 119 +++++++++- src/axom/sidre/tests/CMakeLists.txt | 1 + .../sidre/tests/sidre_convert_protocol_Py.py | 81 +++++++ src/axom/sidre/tests/sidre_view_Py.py | 35 +++ src/tools/CMakeLists.txt | 15 ++ src/tools/convert_sidre_protocol.py | 216 ++++++++++++++++++ 6 files changed, 456 insertions(+), 11 deletions(-) create mode 100644 src/axom/sidre/tests/sidre_convert_protocol_Py.py create mode 100644 src/tools/convert_sidre_protocol.py diff --git a/src/axom/sidre/nanobind_sidre.cpp b/src/axom/sidre/nanobind_sidre.cpp index a9132745ff..23ad6ec56b 100644 --- a/src/axom/sidre/nanobind_sidre.cpp +++ b/src/axom/sidre/nanobind_sidre.cpp @@ -16,6 +16,9 @@ #include "core/View.hpp" #include "core/DataStore.hpp" #include "core/Group.hpp" +#if defined(AXOM_USE_MPI) + #include "spio/IOManager.hpp" +#endif // Separate Conduit header for python functionality #include "conduit_python.hpp" @@ -31,20 +34,49 @@ namespace sidre // Helper to map TypeID to nanobind dtype nb::dlpack::dtype typeIDToDtype(DataTypeId id) { - switch(id) + if(id == INT8_ID) + { + return nb::dtype(); + } + if(id == INT16_ID) + { + return nb::dtype(); + } + if(id == INT32_ID || id == INT_ID) + { + return nb::dtype(); + } + if(id == INT64_ID) + { + return nb::dtype(); + } + if(id == UINT8_ID) + { + return nb::dtype(); + } + if(id == UINT16_ID) + { + return nb::dtype(); + } + if(id == UINT32_ID || id == UINT_ID) + { + return nb::dtype(); + } + if(id == UINT64_ID) + { + return nb::dtype(); + } + if(id == FLOAT32_ID || id == FLOAT_ID) + { + return nb::dtype(); + } + if(id == FLOAT64_ID || id == DOUBLE_ID) { - case INT32_ID: - return nb::dtype(); - case INT64_ID: - return nb::dtype(); - - // DOUBLE_ID also has same value - case FLOAT64_ID: - return nb::dtype(); - default: - SLIC_ERROR("DataTypeId unsupported for numpy"); return nb::dtype(); } + + SLIC_ERROR("DataTypeId unsupported for numpy"); + return nb::dtype(); } /*! @@ -224,6 +256,12 @@ NB_MODULE(pysidre, m_sidre) m_sidre.attr("AXOM_USE_HDF5") = false; #endif +#if defined(AXOM_USE_MPI) + m_sidre.attr("AXOM_ENABLE_MPI") = true; +#else + m_sidre.attr("AXOM_ENABLE_MPI") = false; +#endif + // Bind the DataTypeId enum (TypeID alias) nb::enum_(m_sidre, "TypeID") .value("NO_TYPE_ID", NO_TYPE_ID) @@ -514,6 +552,22 @@ NB_MODULE(pysidre, m_sidre) nb::overload_cast(&View::attachBuffer), nb::rv_policy::reference, "Describe the data view and attach Buffer object") + .def( + "replaceDataWithBuffer", + [](View& self, TypeID type, IndexType num_elems, Buffer* buffer) { + if(self.hasBuffer()) + { + self.attachBuffer(nullptr); + } + else if(self.isExternal()) + { + self.setExternalDataPtr(nullptr); + } + + return self.attachBuffer(type, num_elems, buffer); + }, + nb::rv_policy::reference, + "Replace the View's current array data with a newly attached Buffer.") .def("clear", &View::clear, "Clear data and metadata from the View.") .def("apply", nb::overload_cast<>(&View::apply), "Apply the View's description to its data.") @@ -1105,6 +1159,49 @@ NB_MODULE(pysidre, m_sidre) nb::rv_policy::reference, "Return default value of Attribute as Node reference.") .def("getTypeID", &Attribute::getTypeID, "Return type of Attribute."); + +#if defined(AXOM_USE_MPI) + nb::class_(m_sidre, "IOManager") + .def( + nb::new_([](bool use_scr) { return new IOManager(MPI_COMM_WORLD, use_scr); }), + nb::arg("use_scr") = false) + .def( + "write", + &IOManager::write, + nb::arg("group"), + nb::arg("num_files"), + nb::arg("file_base"), + nb::arg("protocol"), + nb::arg("tree_pattern") = "datagroup") + .def( + "read", + nb::overload_cast(&IOManager::read), + nb::arg("group"), + nb::arg("root_file"), + nb::arg("protocol"), + nb::arg("preserve_contents") = false) + .def( + "read", + nb::overload_cast(&IOManager::read), + nb::arg("group"), + nb::arg("root_file"), + nb::arg("preserve_contents") = false) + .def( + "loadExternalData", + nb::overload_cast(&IOManager::loadExternalData), + nb::arg("group"), + nb::arg("root_file")) + .def( + "loadExternalData", + nb::overload_cast(&IOManager::loadExternalData), + nb::arg("parent_group"), + nb::arg("load_group"), + nb::arg("root_file")) + .def("getNumFilesFromRoot", &IOManager::getNumFilesFromRoot, nb::arg("root_file")) + .def("getNumGroupsFromRoot", &IOManager::getNumGroupsFromRoot, nb::arg("root_file")) + .def_static("correspondingRelayProtocol", &IOManager::correspondingRelayProtocol); +#endif + } } /* end namespace sidre */ diff --git a/src/axom/sidre/tests/CMakeLists.txt b/src/axom/sidre/tests/CMakeLists.txt index 53c153e580..6fdd85a484 100644 --- a/src/axom/sidre/tests/CMakeLists.txt +++ b/src/axom/sidre/tests/CMakeLists.txt @@ -56,6 +56,7 @@ set(python_sidre_tests sidre_view_Py.py sidre_external_Py.py sidre_attribute_Py.py + sidre_convert_protocol_Py.py ) set(sidre_gtests_depends_on sidre fmt gtest) diff --git a/src/axom/sidre/tests/sidre_convert_protocol_Py.py b/src/axom/sidre/tests/sidre_convert_protocol_Py.py new file mode 100644 index 0000000000..c16f5cd34d --- /dev/null +++ b/src/axom/sidre/tests/sidre_convert_protocol_Py.py @@ -0,0 +1,81 @@ +# 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) + +import os +from pathlib import Path +import subprocess +import sys +import tempfile + +import pysidre + + +def _build_dir() -> Path: + return Path(pysidre.__file__).resolve().parents[1] + + +def _repo_dir() -> Path: + return _build_dir().parent + + +def _run_tool(command, cwd): + result = subprocess.run( + command, + cwd=cwd, + env=os.environ.copy(), + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + check=False, + ) + assert result.returncode == 0, result.stdout + + +def _compare_conversion(strip_value=None): + build_dir = _build_dir() + repo_dir = _repo_dir() + input_root = repo_dir / "data" / "quest" / "box_2D_r3.root" + cpp_tool = build_dir / "bin" / "convert_sidre_protocol" + py_tool = repo_dir / "src" / "tools" / "convert_sidre_protocol.py" + + with tempfile.TemporaryDirectory() as cpp_tmp, tempfile.TemporaryDirectory() as py_tmp: + cpp_args = [ + str(cpp_tool), + "--input", + str(input_root), + "--output", + "converted", + ] + py_args = [ + sys.executable, + str(py_tool), + "--input", + str(input_root), + "--output", + "converted", + ] + + if strip_value is not None: + cpp_args.extend(["--strip", str(strip_value)]) + py_args.extend(["--strip", str(strip_value)]) + + _run_tool(cpp_args, cpp_tmp) + _run_tool(py_args, py_tmp) + + assert (Path(cpp_tmp) / "converted.root").read_bytes() == ( + Path(py_tmp) / "converted.root" + ).read_bytes() + assert (Path(cpp_tmp) / "converted_0000000.json").read_bytes() == ( + Path(py_tmp) / "converted_0000000.json" + ).read_bytes() + + +def test_convert_sidre_protocol_matches_cpp(): + _compare_conversion() + + +def test_convert_sidre_protocol_strip_matches_cpp(): + _compare_conversion(strip_value=3) diff --git a/src/axom/sidre/tests/sidre_view_Py.py b/src/axom/sidre/tests/sidre_view_Py.py index 19c08ccec4..50c44b70c2 100644 --- a/src/axom/sidre/tests/sidre_view_Py.py +++ b/src/axom/sidre/tests/sidre_view_Py.py @@ -177,6 +177,41 @@ def test_int_buffer_from_view(): assert dv.getTotalBytes() == NUM_BYTES_INT_32 * elem_count +def test_view_dtype_support(): + ds = pysidre.DataStore() + root = ds.getRoot() + + dtype_pairs = [ + (pysidre.TypeID.INT8_ID, np.int8), + (pysidre.TypeID.UINT16_ID, np.uint16), + (pysidre.TypeID.FLOAT32_ID, np.float32), + ] + + for idx, (type_id, expected_dtype) in enumerate(dtype_pairs): + view = root.createViewAndAllocate(f"dtype_{idx}", type_id, 4) + assert view.getDataArray().dtype == np.dtype(expected_dtype) + + +def test_replace_data_with_buffer_from_external(): + ds = pysidre.DataStore() + root = ds.getRoot() + + external = np.array([1, 2], dtype=np.int32) + view = root.createView("external", pysidre.TypeID.INT32_ID, 2, external) + assert view.isExternal() + + replacement = ds.createBuffer(pysidre.TypeID.INT32_ID, 4) + replacement.allocate() + replacement_data = replacement.getDataArray() + replacement_data[:] = [3, 4, 5, 6] + + view.replaceDataWithBuffer(pysidre.TypeID.INT32_ID, 4, replacement) + + assert view.hasBuffer() + assert not view.isExternal() + assert list(view.getDataArray()) == [3, 4, 5, 6] + + def test_int_array_multi_view(): ds = pysidre.DataStore() root = ds.getRoot() diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index dd44524aa9..734b0d9871 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -193,6 +193,21 @@ endif() # Based on Conduit's run_python_with_conduit.sh.in script. #------------------------------------------------------------------------------ if(NANOBIND_FOUND) + set(_python_tools + convert_sidre_protocol.py) + + foreach(_tool ${_python_tools}) + axom_configure_file ("${CMAKE_CURRENT_SOURCE_DIR}/${_tool}" + "${PROJECT_BINARY_DIR}/bin/${_tool}" COPYONLY) + + axom_configure_file ("${CMAKE_CURRENT_SOURCE_DIR}/${_tool}" + "${CMAKE_INSTALL_PREFIX}/bin/${_tool}" COPYONLY) + endforeach() + + install(PROGRAMS ${_python_tools} + DESTINATION bin) + + unset(_python_tools) # gen python helper to build directory set(_PYEXT_DIR ${PROJECT_BINARY_DIR}/lib) diff --git a/src/tools/convert_sidre_protocol.py b/src/tools/convert_sidre_protocol.py new file mode 100644 index 0000000000..0d02887dd6 --- /dev/null +++ b/src/tools/convert_sidre_protocol.py @@ -0,0 +1,216 @@ +# 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) + +"""Convert a Sidre datastore to another supported protocol.""" + +from __future__ import annotations + +import argparse +import math +from pathlib import Path + +import numpy as np +from mpi4py import MPI +import pysidre + + +VALID_PROTOCOLS = ( + "json", + "sidre_hdf5", + "sidre_conduit_json", + "sidre_json", + "conduit_hdf5", + "conduit_bin", + "conduit_json", +) + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description="Sidre protocol converter") + parser.add_argument( + "-i", + "--input", + required=True, + help="Filename of input sidre-hdf5 datastore", + ) + parser.add_argument( + "-o", + "--output", + required=True, + help="Filename of output datastore (without extension)", + ) + parser.add_argument( + "-p", + "--protocol", + default="json", + choices=VALID_PROTOCOLS, + help="Desired protocol for output datastore", + ) + parser.add_argument( + "-s", + "--strip", + type=positive_int, + default=None, + help="If provided, output arrays will be stripped to first N entries", + ) + parser.add_argument( + "-v", + "--verbose", + action="store_true", + help="Sets output to verbose", + ) + return parser.parse_args() + + +def positive_int(value: str) -> int: + parsed = int(value) + if parsed <= 0: + raise argparse.ArgumentTypeError("strip value must be positive") + return parsed + + +def log(enabled: bool, message: str) -> None: + if enabled: + print(message) + + +def iter_views(group: pysidre.Group): + idx = group.getFirstValidViewIndex() + while pysidre.indexIsValid(idx): + yield group.getView(idx) + idx = group.getNextValidViewIndex(idx) + + +def iter_groups(group: pysidre.Group): + idx = group.getFirstValidGroupIndex() + while pysidre.indexIsValid(idx): + yield group.getGroup(idx) + idx = group.getNextValidGroupIndex(idx) + + +def allocate_external_data(group: pysidre.Group, holders: list[np.ndarray], verbose: bool) -> None: + for view in iter_views(group): + if view.isExternal(): + log( + verbose, + f"Allocating external storage for {view.getPathName()} " + f"({view.getNumElements()} elements, {view.getTotalBytes()} bytes)", + ) + storage = np.zeros(view.getTotalBytes(), dtype=np.uint8) + view.setExternalData(storage) + holders.append(storage) + + for child in iter_groups(group): + allocate_external_data(child, holders, verbose) + + +def filler_value(dtype: np.dtype): + if np.issubdtype(dtype, np.floating): + return math.nan + return 0 + + +def modify_final_values( + view: pysidre.View, + original_size: int, + retained_size: int | None = None, +) -> None: + flattened = np.asarray(view.getDataArray()).reshape(-1) + if retained_size is None: + retained = flattened + else: + retained = flattened[:retained_size] + + retained_size = int(retained.size) + datastore = view.getOwningGroup().getDataStore() + type_id = view.getTypeID() + new_size = retained_size + 3 + buffer = datastore.createBuffer(type_id, new_size) + buffer.allocate() + + new_array = np.asarray(buffer.getDataArray()).reshape(-1) + np.copyto(new_array[:2], np.asarray([original_size, retained_size]), casting="unsafe") + new_array[2] = filler_value(new_array.dtype) + if retained_size > 0: + np.copyto(new_array[3:], retained, casting="unsafe") + + view.replaceDataWithBuffer(type_id, new_size, buffer) + + +def truncate_bulk_data(group: pysidre.Group, max_size: int, verbose: bool) -> None: + for view in iter_views(group): + if not (view.hasBuffer() or view.isExternal()): + continue + + original_size = view.getNumElements() + retained_size = min(max_size, original_size) + + if view.hasBuffer() and original_size > retained_size: + view.apply(retained_size, view.getOffset(), view.getStride()) + + log(verbose, f"Truncating view {view.getPathName()} from {original_size} to {retained_size}") + modify_final_values(view, original_size, retained_size) + + for child in iter_groups(group): + truncate_bulk_data(child, max_size, verbose) + + +def add_strip_note(root: pysidre.Group, num_elements: int) -> None: + note = ( + "This datastore was created by axom's 'convert_sidre_protocol' utility " + f"with option '--strip {num_elements}'. To simplify debugging, the bulk " + f"data in this datastore has been truncated to have at most {num_elements} " + "original values per array. Three values have been prepended to each " + "array: the size of the original array, the number of retained elements " + "and a zero/Nan." + ) + root.createViewString("Note", note) + + +def main() -> int: + args = parse_args() + + if not pysidre.AXOM_ENABLE_MPI: + raise RuntimeError("pysidre.IOManager bindings require an MPI-enabled Axom build") + + initialized_mpi = False + if not MPI.Is_initialized(): + MPI.Init() + initialized_mpi = True + + input_path = Path(args.input) + manager = pysidre.IOManager() + datastore = pysidre.DataStore() + root = datastore.getRoot() + + log(args.verbose, f"Loading datastore from {input_path}") + manager.read(root, str(input_path)) + num_files = manager.getNumFilesFromRoot(str(input_path)) + + log(args.verbose, "Loading external data from datastore") + external_holders: list[np.ndarray] = [] + allocate_external_data(root, external_holders, args.verbose) + manager.loadExternalData(root, str(input_path)) + + if args.strip is not None: + log(args.verbose, f"Truncating views to at most {args.strip} elements") + truncate_bulk_data(root, args.strip, args.verbose) + add_strip_note(root, args.strip) + + log( + args.verbose, + f"Writing out datastore in '{args.protocol}' protocol to file(s) with base name {args.output}", + ) + manager.write(root, num_files, args.output, args.protocol) + + if initialized_mpi and not MPI.Is_finalized(): + MPI.Finalize() + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 666520e5714e651b8cbf475f99575fdf30ecf298 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Thu, 2 Apr 2026 13:51:27 -0700 Subject: [PATCH 156/986] Run styling --- src/axom/sidre/nanobind_sidre.cpp | 65 +++++++++---------- .../sidre/tests/sidre_convert_protocol_Py.py | 11 ++-- src/tools/convert_sidre_protocol.py | 19 +++--- 3 files changed, 42 insertions(+), 53 deletions(-) diff --git a/src/axom/sidre/nanobind_sidre.cpp b/src/axom/sidre/nanobind_sidre.cpp index 23ad6ec56b..b9b978841e 100644 --- a/src/axom/sidre/nanobind_sidre.cpp +++ b/src/axom/sidre/nanobind_sidre.cpp @@ -1162,46 +1162,39 @@ NB_MODULE(pysidre, m_sidre) #if defined(AXOM_USE_MPI) nb::class_(m_sidre, "IOManager") - .def( - nb::new_([](bool use_scr) { return new IOManager(MPI_COMM_WORLD, use_scr); }), - nb::arg("use_scr") = false) - .def( - "write", - &IOManager::write, - nb::arg("group"), - nb::arg("num_files"), - nb::arg("file_base"), - nb::arg("protocol"), - nb::arg("tree_pattern") = "datagroup") - .def( - "read", - nb::overload_cast(&IOManager::read), - nb::arg("group"), - nb::arg("root_file"), - nb::arg("protocol"), - nb::arg("preserve_contents") = false) - .def( - "read", - nb::overload_cast(&IOManager::read), - nb::arg("group"), - nb::arg("root_file"), - nb::arg("preserve_contents") = false) - .def( - "loadExternalData", - nb::overload_cast(&IOManager::loadExternalData), - nb::arg("group"), - nb::arg("root_file")) - .def( - "loadExternalData", - nb::overload_cast(&IOManager::loadExternalData), - nb::arg("parent_group"), - nb::arg("load_group"), - nb::arg("root_file")) + .def(nb::new_([](bool use_scr) { return new IOManager(MPI_COMM_WORLD, use_scr); }), + nb::arg("use_scr") = false) + .def("write", + &IOManager::write, + nb::arg("group"), + nb::arg("num_files"), + nb::arg("file_base"), + nb::arg("protocol"), + nb::arg("tree_pattern") = "datagroup") + .def("read", + nb::overload_cast(&IOManager::read), + nb::arg("group"), + nb::arg("root_file"), + nb::arg("protocol"), + nb::arg("preserve_contents") = false) + .def("read", + nb::overload_cast(&IOManager::read), + nb::arg("group"), + nb::arg("root_file"), + nb::arg("preserve_contents") = false) + .def("loadExternalData", + nb::overload_cast(&IOManager::loadExternalData), + nb::arg("group"), + nb::arg("root_file")) + .def("loadExternalData", + nb::overload_cast(&IOManager::loadExternalData), + nb::arg("parent_group"), + nb::arg("load_group"), + nb::arg("root_file")) .def("getNumFilesFromRoot", &IOManager::getNumFilesFromRoot, nb::arg("root_file")) .def("getNumGroupsFromRoot", &IOManager::getNumGroupsFromRoot, nb::arg("root_file")) .def_static("correspondingRelayProtocol", &IOManager::correspondingRelayProtocol); #endif - } } /* end namespace sidre */ diff --git a/src/axom/sidre/tests/sidre_convert_protocol_Py.py b/src/axom/sidre/tests/sidre_convert_protocol_Py.py index c16f5cd34d..8f79ba2e01 100644 --- a/src/axom/sidre/tests/sidre_convert_protocol_Py.py +++ b/src/axom/sidre/tests/sidre_convert_protocol_Py.py @@ -65,12 +65,11 @@ def _compare_conversion(strip_value=None): _run_tool(cpp_args, cpp_tmp) _run_tool(py_args, py_tmp) - assert (Path(cpp_tmp) / "converted.root").read_bytes() == ( - Path(py_tmp) / "converted.root" - ).read_bytes() - assert (Path(cpp_tmp) / "converted_0000000.json").read_bytes() == ( - Path(py_tmp) / "converted_0000000.json" - ).read_bytes() + assert (Path(cpp_tmp) / "converted.root").read_bytes() == (Path(py_tmp) / + "converted.root").read_bytes() + assert (Path(cpp_tmp) / + "converted_0000000.json").read_bytes() == (Path(py_tmp) / + "converted_0000000.json").read_bytes() def test_convert_sidre_protocol_matches_cpp(): diff --git a/src/tools/convert_sidre_protocol.py b/src/tools/convert_sidre_protocol.py index 0d02887dd6..89fb1ba4f0 100644 --- a/src/tools/convert_sidre_protocol.py +++ b/src/tools/convert_sidre_protocol.py @@ -3,7 +3,6 @@ # files for dates and other details. # # SPDX-License-Identifier: (BSD-3-Clause) - """Convert a Sidre datastore to another supported protocol.""" from __future__ import annotations @@ -16,7 +15,6 @@ from mpi4py import MPI import pysidre - VALID_PROTOCOLS = ( "json", "sidre_hdf5", @@ -151,7 +149,8 @@ def truncate_bulk_data(group: pysidre.Group, max_size: int, verbose: bool) -> No if view.hasBuffer() and original_size > retained_size: view.apply(retained_size, view.getOffset(), view.getStride()) - log(verbose, f"Truncating view {view.getPathName()} from {original_size} to {retained_size}") + log(verbose, + f"Truncating view {view.getPathName()} from {original_size} to {retained_size}") modify_final_values(view, original_size, retained_size) for child in iter_groups(group): @@ -159,14 +158,12 @@ def truncate_bulk_data(group: pysidre.Group, max_size: int, verbose: bool) -> No def add_strip_note(root: pysidre.Group, num_elements: int) -> None: - note = ( - "This datastore was created by axom's 'convert_sidre_protocol' utility " - f"with option '--strip {num_elements}'. To simplify debugging, the bulk " - f"data in this datastore has been truncated to have at most {num_elements} " - "original values per array. Three values have been prepended to each " - "array: the size of the original array, the number of retained elements " - "and a zero/Nan." - ) + note = ("This datastore was created by axom's 'convert_sidre_protocol' utility " + f"with option '--strip {num_elements}'. To simplify debugging, the bulk " + f"data in this datastore has been truncated to have at most {num_elements} " + "original values per array. Three values have been prepended to each " + "array: the size of the original array, the number of retained elements " + "and a zero/Nan.") root.createViewString("Note", note) From af8420bc7416dfd2f499835651bcd35f4e07ec9a Mon Sep 17 00:00:00 2001 From: Brian Han Date: Tue, 7 Apr 2026 09:57:06 -0700 Subject: [PATCH 157/986] Remove cpp/py based unit test --- src/axom/sidre/tests/CMakeLists.txt | 1 - .../sidre/tests/sidre_convert_protocol_Py.py | 80 ------------------- 2 files changed, 81 deletions(-) delete mode 100644 src/axom/sidre/tests/sidre_convert_protocol_Py.py diff --git a/src/axom/sidre/tests/CMakeLists.txt b/src/axom/sidre/tests/CMakeLists.txt index 6fdd85a484..53c153e580 100644 --- a/src/axom/sidre/tests/CMakeLists.txt +++ b/src/axom/sidre/tests/CMakeLists.txt @@ -56,7 +56,6 @@ set(python_sidre_tests sidre_view_Py.py sidre_external_Py.py sidre_attribute_Py.py - sidre_convert_protocol_Py.py ) set(sidre_gtests_depends_on sidre fmt gtest) diff --git a/src/axom/sidre/tests/sidre_convert_protocol_Py.py b/src/axom/sidre/tests/sidre_convert_protocol_Py.py deleted file mode 100644 index 8f79ba2e01..0000000000 --- a/src/axom/sidre/tests/sidre_convert_protocol_Py.py +++ /dev/null @@ -1,80 +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) - -import os -from pathlib import Path -import subprocess -import sys -import tempfile - -import pysidre - - -def _build_dir() -> Path: - return Path(pysidre.__file__).resolve().parents[1] - - -def _repo_dir() -> Path: - return _build_dir().parent - - -def _run_tool(command, cwd): - result = subprocess.run( - command, - cwd=cwd, - env=os.environ.copy(), - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - text=True, - check=False, - ) - assert result.returncode == 0, result.stdout - - -def _compare_conversion(strip_value=None): - build_dir = _build_dir() - repo_dir = _repo_dir() - input_root = repo_dir / "data" / "quest" / "box_2D_r3.root" - cpp_tool = build_dir / "bin" / "convert_sidre_protocol" - py_tool = repo_dir / "src" / "tools" / "convert_sidre_protocol.py" - - with tempfile.TemporaryDirectory() as cpp_tmp, tempfile.TemporaryDirectory() as py_tmp: - cpp_args = [ - str(cpp_tool), - "--input", - str(input_root), - "--output", - "converted", - ] - py_args = [ - sys.executable, - str(py_tool), - "--input", - str(input_root), - "--output", - "converted", - ] - - if strip_value is not None: - cpp_args.extend(["--strip", str(strip_value)]) - py_args.extend(["--strip", str(strip_value)]) - - _run_tool(cpp_args, cpp_tmp) - _run_tool(py_args, py_tmp) - - assert (Path(cpp_tmp) / "converted.root").read_bytes() == (Path(py_tmp) / - "converted.root").read_bytes() - assert (Path(cpp_tmp) / - "converted_0000000.json").read_bytes() == (Path(py_tmp) / - "converted_0000000.json").read_bytes() - - -def test_convert_sidre_protocol_matches_cpp(): - _compare_conversion() - - -def test_convert_sidre_protocol_strip_matches_cpp(): - _compare_conversion(strip_value=3) From a5719f3f6e1b65a5db8db44c11952dcc2b58b945 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Tue, 7 Apr 2026 11:25:56 -0700 Subject: [PATCH 158/986] Remove dupe install logic --- src/tools/CMakeLists.txt | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index 734b0d9871..efd2c28606 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -204,9 +204,6 @@ if(NANOBIND_FOUND) "${CMAKE_INSTALL_PREFIX}/bin/${_tool}" COPYONLY) endforeach() - install(PROGRAMS ${_python_tools} - DESTINATION bin) - unset(_python_tools) # gen python helper to build directory From 7326ae50a6c058676f60f769f1ff414ce1cf41a9 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Tue, 7 Apr 2026 13:31:55 -0700 Subject: [PATCH 159/986] Fix for both fPIC and precise flag for intel --- scripts/spack/configs/toss_4_x86_64_ib/spack.yaml | 6 +++++- scripts/spack/packages/axom/package.py | 3 --- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/scripts/spack/configs/toss_4_x86_64_ib/spack.yaml b/scripts/spack/configs/toss_4_x86_64_ib/spack.yaml index 8727e7aeaf..fe29440ba3 100644 --- a/scripts/spack/configs/toss_4_x86_64_ib/spack.yaml +++ b/scripts/spack/configs/toss_4_x86_64_ib/spack.yaml @@ -99,7 +99,11 @@ spack: c: /usr/tce/packages/intel/intel-2025.2.0-magic/bin/icx cxx: /usr/tce/packages/intel/intel-2025.2.0-magic/bin/icpx fortran: /usr/tce/packages/intel/intel-2025.2.0-magic/bin/ifx - flags: {} + flags: + cflags: -fPIC + # Addresses floating point issues (default is fast) + cxxflags: -fPIC -fp-model=precise + fflags: -fPIC environment: {} extra_rpaths: [] diff --git a/scripts/spack/packages/axom/package.py b/scripts/spack/packages/axom/package.py index 126ef9867d..0784bc9f16 100644 --- a/scripts/spack/packages/axom/package.py +++ b/scripts/spack/packages/axom/package.py @@ -416,9 +416,6 @@ def initconfig_compiler_entries(self): entries.append(cmake_cache_string("CMAKE_CXX_FLAGS_DEBUG", "-O1 -g")) if spec.satisfies("%oneapi"): - # Addresses floating point issues (default is fast) - entries.append(cmake_cache_string("CMAKE_CXX_FLAGS", "-fp-model=precise")) - # Disable intrusive warning: # icpx: remark: note that use of '-g' without any optimization-level # option will turn off most compiler optimizations similar to use of From 01af625c719064dbdea0d6fda261c1ecfcecf23e Mon Sep 17 00:00:00 2001 From: Brian Han Date: Wed, 8 Apr 2026 13:23:10 -0700 Subject: [PATCH 160/986] Add regex-based test for convert_sidre_protocol.py --- src/tools/CMakeLists.txt | 52 +++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index efd2c28606..795214c79f 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -187,24 +187,13 @@ if(AXOM_ENABLE_QUEST) endif() endif() -#------------------------------------------------------------------------------ -# run_python_with_axom.sh.in is a utility that allows running python with -# nanobind-generated extensions in the PYTHONPATH. -# Based on Conduit's run_python_with_conduit.sh.in script. -#------------------------------------------------------------------------------ -if(NANOBIND_FOUND) - set(_python_tools - convert_sidre_protocol.py) - - foreach(_tool ${_python_tools}) - axom_configure_file ("${CMAKE_CURRENT_SOURCE_DIR}/${_tool}" - "${PROJECT_BINARY_DIR}/bin/${_tool}" COPYONLY) - axom_configure_file ("${CMAKE_CURRENT_SOURCE_DIR}/${_tool}" - "${CMAKE_INSTALL_PREFIX}/bin/${_tool}" COPYONLY) - endforeach() - - unset(_python_tools) +if(NANOBIND_FOUND) + #-------------------------------------------------------------------------- + # run_python_with_axom.sh.in is a utility that allows running python with + # nanobind-generated extensions in the PYTHONPATH. + # Based on Conduit's run_python_with_conduit.sh.in script. + #-------------------------------------------------------------------------- # gen python helper to build directory set(_PYEXT_DIR ${PROJECT_BINARY_DIR}/lib) @@ -226,4 +215,33 @@ if(NANOBIND_FOUND) NAME run_python_with_axom_build COMMAND ${PROJECT_BINARY_DIR}/bin/run_python_with_axom.sh -c "import pysidre, nanobind, conduit, numpy, pytest") endif() + + #-------------------------------------------------------------------------- + # convert_sidre_protocol.py is a python version of the + # convert_sidre_protocol.cpp utility + #-------------------------------------------------------------------------- + axom_configure_file ("${CMAKE_CURRENT_SOURCE_DIR}/convert_sidre_protocol.py" + "${PROJECT_BINARY_DIR}/bin/convert_sidre_protocol.py" COPYONLY) + + axom_configure_file ("${CMAKE_CURRENT_SOURCE_DIR}/convert_sidre_protocol.py" + "${CMAKE_INSTALL_PREFIX}/bin/convert_sidre_protocol.py" COPYONLY) + + if(AXOM_ENABLE_TESTS AND AXOM_DATA_DIR) + + set(box_dir "${AXOM_DATA_DIR}/quest/box_2D_r3.root") + + set(_testname "convert_sidre_protocol_py") + axom_add_test( + NAME ${_testname} + COMMAND ${PROJECT_BINARY_DIR}/bin/run_python_with_axom.sh + ${PROJECT_BINARY_DIR}/bin/convert_sidre_protocol.py + --input ${box_dir} + --output csp_output + --protocol json + --verbose + ) + + set_tests_properties(${_testname} PROPERTIES + PASS_REGULAR_EXPRESSION "Writing out datastore") + endif() endif() From 05986687b4d280b51a497fb5dd436686e54d56da Mon Sep 17 00:00:00 2001 From: Brian Han Date: Wed, 8 Apr 2026 15:03:59 -0700 Subject: [PATCH 161/986] Codex changes that replace custom replaceDataWithBuffer python function with more precise bindings --- src/axom/sidre/nanobind_sidre.cpp | 42 +++++++++++++++------------ src/axom/sidre/tests/sidre_view_Py.py | 33 +++++++++++++++++++-- src/tools/convert_sidre_protocol.py | 7 ++++- 3 files changed, 60 insertions(+), 22 deletions(-) diff --git a/src/axom/sidre/nanobind_sidre.cpp b/src/axom/sidre/nanobind_sidre.cpp index b9b978841e..0976927e5c 100644 --- a/src/axom/sidre/nanobind_sidre.cpp +++ b/src/axom/sidre/nanobind_sidre.cpp @@ -543,31 +543,23 @@ NB_MODULE(pysidre, m_sidre) .def("attachBuffer", nb::overload_cast(&View::attachBuffer), nb::rv_policy::reference, - "Attach a Buffer object to the View.") + "Attach a Buffer object to the View.", + nb::arg("buffer").none()) .def("attachBuffer", nb::overload_cast(&View::attachBuffer), nb::rv_policy::reference, - "Describe the data view and attach Buffer object.") + "Describe the data view and attach Buffer object.", + nb::arg("type"), + nb::arg("num_elems"), + nb::arg("buffer").none()) .def("attachBuffer", nb::overload_cast(&View::attachBuffer), nb::rv_policy::reference, - "Describe the data view and attach Buffer object") - .def( - "replaceDataWithBuffer", - [](View& self, TypeID type, IndexType num_elems, Buffer* buffer) { - if(self.hasBuffer()) - { - self.attachBuffer(nullptr); - } - else if(self.isExternal()) - { - self.setExternalDataPtr(nullptr); - } - - return self.attachBuffer(type, num_elems, buffer); - }, - nb::rv_policy::reference, - "Replace the View's current array data with a newly attached Buffer.") + "Describe the data view and attach Buffer object", + nb::arg("type"), + nb::arg("ndims"), + nb::arg("shape"), + nb::arg("buffer").none()) .def("clear", &View::clear, "Clear data and metadata from the View.") .def("apply", nb::overload_cast<>(&View::apply), "Apply the View's description to its data.") @@ -610,6 +602,18 @@ NB_MODULE(pysidre, m_sidre) "Set the View to hold a string value.", nb::arg("value").noconvert(), nb::arg("allocID") = INVALID_ALLOCATOR_ID) + .def( + "setExternalData", + [](View& self, nb::object external_ptr) { + if(external_ptr.is_none()) + { + return self.setExternalDataPtr(nullptr); + } + return self.setExternalDataPtr(nb::cast>(external_ptr).data()); + }, + nb::rv_policy::reference, + "Set the View to hold undescribed external data (numpy array).", + nb::arg("external_ptr").none()) .def( "setExternalData", [](View& self, const nb::ndarray<>& external_ptr) { diff --git a/src/axom/sidre/tests/sidre_view_Py.py b/src/axom/sidre/tests/sidre_view_Py.py index 50c44b70c2..e8def9d38e 100644 --- a/src/axom/sidre/tests/sidre_view_Py.py +++ b/src/axom/sidre/tests/sidre_view_Py.py @@ -192,26 +192,55 @@ def test_view_dtype_support(): assert view.getDataArray().dtype == np.dtype(expected_dtype) -def test_replace_data_with_buffer_from_external(): +def test_detach_external_and_attach_buffer(): ds = pysidre.DataStore() root = ds.getRoot() external = np.array([1, 2], dtype=np.int32) view = root.createView("external", pysidre.TypeID.INT32_ID, 2, external) assert view.isExternal() + assert not view.hasBuffer() + + view.setExternalData(None) + assert view.isEmpty() replacement = ds.createBuffer(pysidre.TypeID.INT32_ID, 4) replacement.allocate() replacement_data = replacement.getDataArray() replacement_data[:] = [3, 4, 5, 6] - view.replaceDataWithBuffer(pysidre.TypeID.INT32_ID, 4, replacement) + view.attachBuffer(pysidre.TypeID.INT32_ID, 4, replacement) assert view.hasBuffer() assert not view.isExternal() assert list(view.getDataArray()) == [3, 4, 5, 6] +def test_detach_buffer_and_attach_buffer(): + ds = pysidre.DataStore() + root = ds.getRoot() + + original = ds.createBuffer(pysidre.TypeID.INT32_ID, 2) + original.allocate() + original.getDataArray()[:] = [1, 2] + + view = root.createView("buffered", pysidre.TypeID.INT32_ID, 2, original) + assert view.hasBuffer() + + view.attachBuffer(None) + assert view.isEmpty() + assert not view.hasBuffer() + + replacement = ds.createBuffer(pysidre.TypeID.INT32_ID, 4) + replacement.allocate() + replacement.getDataArray()[:] = [3, 4, 5, 6] + + view.attachBuffer(pysidre.TypeID.INT32_ID, 4, replacement) + + assert view.hasBuffer() + assert list(view.getDataArray()) == [3, 4, 5, 6] + + def test_int_array_multi_view(): ds = pysidre.DataStore() root = ds.getRoot() diff --git a/src/tools/convert_sidre_protocol.py b/src/tools/convert_sidre_protocol.py index 89fb1ba4f0..9a0cab09bf 100644 --- a/src/tools/convert_sidre_protocol.py +++ b/src/tools/convert_sidre_protocol.py @@ -135,7 +135,12 @@ def modify_final_values( if retained_size > 0: np.copyto(new_array[3:], retained, casting="unsafe") - view.replaceDataWithBuffer(type_id, new_size, buffer) + if view.hasBuffer(): + view.attachBuffer(None) + elif view.isExternal(): + view.setExternalData(None) + + view.attachBuffer(type_id, new_size, buffer) def truncate_bulk_data(group: pysidre.Group, max_size: int, verbose: bool) -> None: From 9d37194b42dca5d62e3eecb6b60c17c6cb0aa37b Mon Sep 17 00:00:00 2001 From: Brian Han Date: Wed, 8 Apr 2026 16:00:33 -0700 Subject: [PATCH 162/986] Add comments --- src/tools/convert_sidre_protocol.py | 81 +++++++++++++++++++++++++---- 1 file changed, 71 insertions(+), 10 deletions(-) diff --git a/src/tools/convert_sidre_protocol.py b/src/tools/convert_sidre_protocol.py index 9a0cab09bf..3721e44212 100644 --- a/src/tools/convert_sidre_protocol.py +++ b/src/tools/convert_sidre_protocol.py @@ -3,7 +3,22 @@ # files for dates and other details. # # SPDX-License-Identifier: (BSD-3-Clause) -"""Convert a Sidre datastore to another supported protocol.""" +"""Convert a Sidre datastore from the sidre_hdf5 protocol to another protocol. + +Users must supply a path to a sidre_hdf5 rootfile and base name for +the output datastores. Optional command line arguments include +a ``--protocol`` option (the default is ``json``) +and a ``--strip`` option to truncate the array data to at most N elements. +The strip option also prepends each array with its original size, the new +size and a filler entry of 0 for integer arrays or nan for floating point +arrays. E.g. if the array had 6 entries [1.01, 2.02, 3.03, 4.04, 5.05, 6.06] +and the user passed in ``--strip 3``, the array would be converted to +[6, 3, nan, 1.01, 2.02, 3.03]. + +The strip option is intended as a temporary solution to truncating +a dataset to allow easier debugging. In the future, the conversion and +truncation/display functionality may be separated into distinct utilities. +""" from __future__ import annotations @@ -89,7 +104,17 @@ def iter_groups(group: pysidre.Group): idx = group.getNextValidGroupIndex(idx) +# +# Allocate storage for external data of the input datastore. +# +# Iterates recursively through the views and groups of the provided group to +# find the external data views and allocates the required storage within the +# holders list. +# +# Also initializes the data in each allocated array to zeros. +# def allocate_external_data(group: pysidre.Group, holders: list[np.ndarray], verbose: bool) -> None: + # for each view for view in iter_views(group): if view.isExternal(): log( @@ -101,6 +126,7 @@ def allocate_external_data(group: pysidre.Group, holders: list[np.ndarray], verb view.setExternalData(storage) holders.append(storage) + # for each group for child in iter_groups(group): allocate_external_data(child, holders, verbose) @@ -111,6 +137,17 @@ def filler_value(dtype: np.dtype): return 0 +# +# Shift the data to the right by three elements. +# +# The new first value will be the size of the original array. +# The next value will be the number of retained elements and the +# third value will be 0 for integer data and Nan for float data. +# This is followed by the values in the truncated original dataset. +# +# This function creates a copy of the data since there could be +# several views in the original dataset pointing to the same memory. +# def modify_final_values( view: pysidre.View, original_size: int, @@ -123,18 +160,25 @@ def modify_final_values( retained = flattened[:retained_size] retained_size = int(retained.size) + + # Create a new buffer for copied data. datastore = view.getOwningGroup().getDataStore() type_id = view.getTypeID() new_size = retained_size + 3 buffer = datastore.createBuffer(type_id, new_size) buffer.allocate() + # Explicitly set the first two elements and copy elements over. new_array = np.asarray(buffer.getDataArray()).reshape(-1) np.copyto(new_array[:2], np.asarray([original_size, retained_size]), casting="unsafe") new_array[2] = filler_value(new_array.dtype) if retained_size > 0: np.copyto(new_array[3:], retained, casting="unsafe") + # Update view's buffer to the new data. + # The C++ utility uses detachBuffer() here because this path is valid for + # buffer-backed views. In Python we also need to support external views, so + # we make the state transition explicit before attaching the new buffer. if view.hasBuffer(): view.attachBuffer(None) elif view.isExternal(): @@ -143,21 +187,38 @@ def modify_final_values( view.attachBuffer(type_id, new_size, buffer) +# +# Recursively traverse views and groups in group and truncate views to +# have at most max_size elements. +# +# Within the truncated arrays, the first element will be the size of the +# original array, the second will be the number of retained elements and the +# third will be 0 for integers or nan for floating points. +# This will be followed by at most the first max_size elements of the +# original array. +# def truncate_bulk_data(group: pysidre.Group, max_size: int, verbose: bool) -> None: + # for each view for view in iter_views(group): - if not (view.hasBuffer() or view.isExternal()): - continue + is_array = view.hasBuffer() or view.isExternal() - original_size = view.getNumElements() - retained_size = min(max_size, original_size) + if is_array: + original_size = view.getNumElements() + retained_size = min(max_size, original_size) - if view.hasBuffer() and original_size > retained_size: - view.apply(retained_size, view.getOffset(), view.getStride()) + if view.hasBuffer() and original_size > retained_size: + view.apply(retained_size, view.getOffset(), view.getStride()) + elif view.isExternal() and original_size > retained_size: + data = np.asarray(view.getDataArray()).reshape(-1) + view.setExternalData(view.getTypeID(), retained_size, data) - log(verbose, - f"Truncating view {view.getPathName()} from {original_size} to {retained_size}") - modify_final_values(view, original_size, retained_size) + log( + verbose, + f"Truncating view {view.getPathName()} from {original_size} to {retained_size}", + ) + modify_final_values(view, original_size, retained_size) + # for each group for child in iter_groups(group): truncate_bulk_data(child, max_size, verbose) From fb7557ef3d038e5655cb16e496141dfd17957109 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Wed, 8 Apr 2026 16:34:07 -0700 Subject: [PATCH 163/986] Match verbosity --- src/tools/convert_sidre_protocol.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/tools/convert_sidre_protocol.py b/src/tools/convert_sidre_protocol.py index 3721e44212..f965c8f896 100644 --- a/src/tools/convert_sidre_protocol.py +++ b/src/tools/convert_sidre_protocol.py @@ -85,7 +85,11 @@ def positive_int(value: str) -> int: return parsed -def log(enabled: bool, message: str) -> None: +def log_info(message: str) -> None: + print(message) + + +def log_debug(enabled: bool, message: str) -> None: if enabled: print(message) @@ -117,7 +121,7 @@ def allocate_external_data(group: pysidre.Group, holders: list[np.ndarray], verb # for each view for view in iter_views(group): if view.isExternal(): - log( + log_debug( verbose, f"Allocating external storage for {view.getPathName()} " f"({view.getNumElements()} elements, {view.getTotalBytes()} bytes)", @@ -212,7 +216,7 @@ def truncate_bulk_data(group: pysidre.Group, max_size: int, verbose: bool) -> No data = np.asarray(view.getDataArray()).reshape(-1) view.setExternalData(view.getTypeID(), retained_size, data) - log( + log_debug( verbose, f"Truncating view {view.getPathName()} from {original_size} to {retained_size}", ) @@ -249,22 +253,21 @@ def main() -> int: datastore = pysidre.DataStore() root = datastore.getRoot() - log(args.verbose, f"Loading datastore from {input_path}") + log_info(f"Loading datastore from {input_path}") manager.read(root, str(input_path)) num_files = manager.getNumFilesFromRoot(str(input_path)) - log(args.verbose, "Loading external data from datastore") + log_info("Loading external data from datastore") external_holders: list[np.ndarray] = [] allocate_external_data(root, external_holders, args.verbose) manager.loadExternalData(root, str(input_path)) if args.strip is not None: - log(args.verbose, f"Truncating views to at most {args.strip} elements") + log_info(f"Truncating views to at most {args.strip} elements.") truncate_bulk_data(root, args.strip, args.verbose) add_strip_note(root, args.strip) - log( - args.verbose, + log_info( f"Writing out datastore in '{args.protocol}' protocol to file(s) with base name {args.output}", ) manager.write(root, num_files, args.output, args.protocol) From 2ff88df0f268af16a17add770e804c3962e38a1e Mon Sep 17 00:00:00 2001 From: Brian Han Date: Tue, 14 Apr 2026 10:19:53 -0700 Subject: [PATCH 164/986] Use nanobind init ctor, add function descriptions --- src/axom/sidre/nanobind_sidre.cpp | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/axom/sidre/nanobind_sidre.cpp b/src/axom/sidre/nanobind_sidre.cpp index 0976927e5c..581609b1fd 100644 --- a/src/axom/sidre/nanobind_sidre.cpp +++ b/src/axom/sidre/nanobind_sidre.cpp @@ -1166,10 +1166,13 @@ NB_MODULE(pysidre, m_sidre) #if defined(AXOM_USE_MPI) nb::class_(m_sidre, "IOManager") - .def(nb::new_([](bool use_scr) { return new IOManager(MPI_COMM_WORLD, use_scr); }), - nb::arg("use_scr") = false) + .def( + "__init__", + [](IOManager* self, bool use_scr) { new(self) IOManager(MPI_COMM_WORLD, use_scr); }, + nb::arg("use_scr") = false) .def("write", &IOManager::write, + "Write a Group to output files.", nb::arg("group"), nb::arg("num_files"), nb::arg("file_base"), @@ -1177,27 +1180,39 @@ NB_MODULE(pysidre, m_sidre) nb::arg("tree_pattern") = "datagroup") .def("read", nb::overload_cast(&IOManager::read), + "Read from input file.", nb::arg("group"), nb::arg("root_file"), nb::arg("protocol"), nb::arg("preserve_contents") = false) .def("read", nb::overload_cast(&IOManager::read), + "Read from a root file.", nb::arg("group"), nb::arg("root_file"), nb::arg("preserve_contents") = false) .def("loadExternalData", nb::overload_cast(&IOManager::loadExternalData), + "Load external data into a group.", nb::arg("group"), nb::arg("root_file")) .def("loadExternalData", nb::overload_cast(&IOManager::loadExternalData), + "Piecewise load of external data into a group.", nb::arg("parent_group"), nb::arg("load_group"), nb::arg("root_file")) - .def("getNumFilesFromRoot", &IOManager::getNumFilesFromRoot, nb::arg("root_file")) - .def("getNumGroupsFromRoot", &IOManager::getNumGroupsFromRoot, nb::arg("root_file")) - .def_static("correspondingRelayProtocol", &IOManager::correspondingRelayProtocol); + .def("getNumFilesFromRoot", + &IOManager::getNumFilesFromRoot, + "Gets the number of files in the dataset from the specified root file.", + nb::arg("root_file")) + .def("getNumGroupsFromRoot", + &IOManager::getNumGroupsFromRoot, + "Gets the number of groups in the dataset from the specified root file.", + nb::arg("root_file")) + .def_static("correspondingRelayProtocol", + &IOManager::correspondingRelayProtocol, + "Finds conduit relay protocol corresponding to a sidre protocol."); #endif } From e8a5d50b382b8851ac9f1ac01b1d6cc9eec487c8 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Tue, 14 Apr 2026 11:25:34 -0700 Subject: [PATCH 165/986] Cleanup one-off functions --- src/tools/convert_sidre_protocol.py | 77 ++++++++++------------------- 1 file changed, 26 insertions(+), 51 deletions(-) diff --git a/src/tools/convert_sidre_protocol.py b/src/tools/convert_sidre_protocol.py index f965c8f896..74bf5fb9bf 100644 --- a/src/tools/convert_sidre_protocol.py +++ b/src/tools/convert_sidre_protocol.py @@ -3,7 +3,8 @@ # files for dates and other details. # # SPDX-License-Identifier: (BSD-3-Clause) -"""Convert a Sidre datastore from the sidre_hdf5 protocol to another protocol. +""" +Convert a Sidre datastore from the sidre_hdf5 protocol to another protocol. Users must supply a path to a sidre_hdf5 rootfile and base name for the output datastores. Optional command line arguments include @@ -22,6 +23,7 @@ from __future__ import annotations +import sys import argparse import math from pathlib import Path @@ -65,7 +67,7 @@ def parse_args() -> argparse.Namespace: parser.add_argument( "-s", "--strip", - type=positive_int, + type=int, default=None, help="If provided, output arrays will be stripped to first N entries", ) @@ -78,22 +80,6 @@ def parse_args() -> argparse.Namespace: return parser.parse_args() -def positive_int(value: str) -> int: - parsed = int(value) - if parsed <= 0: - raise argparse.ArgumentTypeError("strip value must be positive") - return parsed - - -def log_info(message: str) -> None: - print(message) - - -def log_debug(enabled: bool, message: str) -> None: - if enabled: - print(message) - - def iter_views(group: pysidre.Group): idx = group.getFirstValidViewIndex() while pysidre.indexIsValid(idx): @@ -121,11 +107,10 @@ def allocate_external_data(group: pysidre.Group, holders: list[np.ndarray], verb # for each view for view in iter_views(group): if view.isExternal(): - log_debug( - verbose, - f"Allocating external storage for {view.getPathName()} " - f"({view.getNumElements()} elements, {view.getTotalBytes()} bytes)", - ) + if verbose: + print( + f"Allocating external storage for {view.getPathName()} " + f"({view.getNumElements()} elements, {view.getTotalBytes()} bytes)", ) storage = np.zeros(view.getTotalBytes(), dtype=np.uint8) view.setExternalData(storage) holders.append(storage) @@ -135,12 +120,6 @@ def allocate_external_data(group: pysidre.Group, holders: list[np.ndarray], verb allocate_external_data(child, holders, verbose) -def filler_value(dtype: np.dtype): - if np.issubdtype(dtype, np.floating): - return math.nan - return 0 - - # # Shift the data to the right by three elements. # @@ -175,7 +154,7 @@ def modify_final_values( # Explicitly set the first two elements and copy elements over. new_array = np.asarray(buffer.getDataArray()).reshape(-1) np.copyto(new_array[:2], np.asarray([original_size, retained_size]), casting="unsafe") - new_array[2] = filler_value(new_array.dtype) + new_array[2] = math.nan if np.issubdtype(new_array.dtype, np.floating) else 0 if retained_size > 0: np.copyto(new_array[3:], retained, casting="unsafe") @@ -216,10 +195,10 @@ def truncate_bulk_data(group: pysidre.Group, max_size: int, verbose: bool) -> No data = np.asarray(view.getDataArray()).reshape(-1) view.setExternalData(view.getTypeID(), retained_size, data) - log_debug( - verbose, - f"Truncating view {view.getPathName()} from {original_size} to {retained_size}", - ) + if verbose: + print( + f"Truncating view {view.getPathName()} from {original_size} to {retained_size}", + ) modify_final_values(view, original_size, retained_size) # for each group @@ -227,16 +206,6 @@ def truncate_bulk_data(group: pysidre.Group, max_size: int, verbose: bool) -> No truncate_bulk_data(child, max_size, verbose) -def add_strip_note(root: pysidre.Group, num_elements: int) -> None: - note = ("This datastore was created by axom's 'convert_sidre_protocol' utility " - f"with option '--strip {num_elements}'. To simplify debugging, the bulk " - f"data in this datastore has been truncated to have at most {num_elements} " - "original values per array. Three values have been prepended to each " - "array: the size of the original array, the number of retained elements " - "and a zero/Nan.") - root.createViewString("Note", note) - - def main() -> int: args = parse_args() @@ -253,21 +222,27 @@ def main() -> int: datastore = pysidre.DataStore() root = datastore.getRoot() - log_info(f"Loading datastore from {input_path}") + print(f"Loading datastore from {input_path}") manager.read(root, str(input_path)) num_files = manager.getNumFilesFromRoot(str(input_path)) - log_info("Loading external data from datastore") + print("Loading external data from datastore") external_holders: list[np.ndarray] = [] allocate_external_data(root, external_holders, args.verbose) manager.loadExternalData(root, str(input_path)) if args.strip is not None: - log_info(f"Truncating views to at most {args.strip} elements.") + print(f"Truncating views to at most {args.strip} elements.") truncate_bulk_data(root, args.strip, args.verbose) - add_strip_note(root, args.strip) - - log_info( + note = ("This datastore was created by axom's 'convert_sidre_protocol' utility " + f"with option '--strip {args.strip}'. To simplify debugging, the bulk " + f"data in this datastore has been truncated to have at most {args.strip} " + "original values per array. Three values have been prepended to each " + "array: the size of the original array, the number of retained elements " + "and a zero/Nan.") + root.createViewString("Note", note) + + print( f"Writing out datastore in '{args.protocol}' protocol to file(s) with base name {args.output}", ) manager.write(root, num_files, args.output, args.protocol) @@ -279,4 +254,4 @@ def main() -> int: if __name__ == "__main__": - raise SystemExit(main()) + sys.exit(main()) From 2782397f1143ac4197d5643490c30ce716a7a883 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Wed, 22 Apr 2026 15:31:52 -0700 Subject: [PATCH 166/986] Update RZ host-configs --- ...toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake | 22 +++--- ...x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake | 22 +++--- ...x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake | 22 +++--- ...tor-toss_4_x86_64_ib-gcc@13.3.1_cuda.cmake | 52 +++++++------ ...or-toss_4_x86_64_ib-llvm@19.1.3_cuda.cmake | 52 +++++++------ ...toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake | 22 +++--- ...x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake | 22 +++--- ...x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake | 22 +++--- ...zwhippet-toss_4_x86_64_ib-gcc@13.3.1.cmake | 72 +++++++++--------- ...4_ib-intel-oneapi-compilers@2025.2.0.cmake | 52 +++++++------ ...whippet-toss_4_x86_64_ib-llvm@19.1.3.cmake | 74 ++++++++++--------- 11 files changed, 237 insertions(+), 197 deletions(-) diff --git a/host-configs/rzadams-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake b/host-configs/rzadams-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake index a8fba5af7e..2f449c7a36 100644 --- a/host-configs/rzadams-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake +++ b/host-configs/rzadams-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake @@ -4,13 +4,13 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.29.2/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/blt-0.7.1-af45hpsxdxploxhqqi4irjmy4vz2omqx;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/c2c-1.8.0-cpdkv2uxkhs3qmzqakt4sht3vuiucdwt;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-wytd46zvuquxg2rqf3zec5ldbmjeaat6;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/conduit-0.9.5-pbrrhvrsmnewby6q3u35ceppt5fxme2p;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/lua-5.4.8-y6z3polqakbuhn6nh6muri3rjxqmiioa;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/mfem-4.9.0-wpbbiugqvdw7bylkmyyf2quazcoptzah;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/py-nanobind-2.7.0-ieb2bzuz435ydpyiq2uj36yyqjbrffml;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/adiak-0.4.0-lzgexbo5qyzepgju2acyy6d2qft443tb;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/libunwind-1.8.3-zvteigxlopfo5tkdr2n2pglgosg42bah;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/hdf5-1.8.23-to3lvqwzxrqzad65zbvnbtvyt4k63uxe;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/parmetis-4.0.3-bajtzsmahjg2wvcjzo5gfb2hr5oj2elc;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/py-numpy-2.4.2-xw5ysy75zfqlalmbmtl5phd2qip43hlo;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/hypre-2.27.0-knkmxfbf3xcut2wmamjsmhrqf7u7xmi7;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-z3eibnxgk3jeplydlcizrhurpibzqzqv;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/umpire-2025.12.0-eqokbtuch3pwifb225w2i3rrft5ddcgv;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/zlib-1.3.1-f3sdiqmy2whvmdn2fxeol3k7oqwouays;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/metis-5.1.0-v4d34x7btqhotktvu5bgk4iwk6h2lumb;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-eplsxvli6nnrgdl4l3hl7cizytginfhp;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/fmt-11.0.2-nzzmwf4nofjv46yeugehjo64opvfbbzr;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cce-tce/cce-20.0.0;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.32-rocmcc-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/blt-0.7.1-af45hpsxdxploxhqqi4irjmy4vz2omqx;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/c2c-1.8.0-cpdkv2uxkhs3qmzqakt4sht3vuiucdwt;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-wytd46zvuquxg2rqf3zec5ldbmjeaat6;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/conduit-0.9.5-pbrrhvrsmnewby6q3u35ceppt5fxme2p;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/lua-5.4.8-y6z3polqakbuhn6nh6muri3rjxqmiioa;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/mfem-4.9.0-wpbbiugqvdw7bylkmyyf2quazcoptzah;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/py-nanobind-2.7.0-ieb2bzuz435ydpyiq2uj36yyqjbrffml;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/adiak-0.4.0-lzgexbo5qyzepgju2acyy6d2qft443tb;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/libunwind-1.8.3-zvteigxlopfo5tkdr2n2pglgosg42bah;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/hdf5-1.8.23-to3lvqwzxrqzad65zbvnbtvyt4k63uxe;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/parmetis-4.0.3-bajtzsmahjg2wvcjzo5gfb2hr5oj2elc;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/py-mpi4py-4.1.1-e5tepnokpccpvjfp6lwnwfi5laakuhdh;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/py-numpy-2.4.2-xw5ysy75zfqlalmbmtl5phd2qip43hlo;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/hypre-2.27.0-knkmxfbf3xcut2wmamjsmhrqf7u7xmi7;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-z3eibnxgk3jeplydlcizrhurpibzqzqv;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/umpire-2025.12.0-eqokbtuch3pwifb225w2i3rrft5ddcgv;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/zlib-1.3.1-f3sdiqmy2whvmdn2fxeol3k7oqwouays;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/metis-5.1.0-v4d34x7btqhotktvu5bgk4iwk6h2lumb;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-eplsxvli6nnrgdl4l3hl7cizytginfhp;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/fmt-11.0.2-nzzmwf4nofjv46yeugehjo64opvfbbzr;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cce-tce/cce-20.0.0;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.32-rocmcc-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/axom-develop-xz7g7s5cdkdr6lixuhs23vyuqqodocrg/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/axom-develop-xz7g7s5cdkdr6lixuhs23vyuqqodocrg/lib64;;/opt/cray/pe/cce/20.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/axom-develop-mdr65pqpz5pq5uhe57wjeu6vp5q3kuoa/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/axom-develop-mdr65pqpz5pq5uhe57wjeu6vp5q3kuoa/lib64;;/opt/cray/pe/cce/20.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/axom-develop-xz7g7s5cdkdr6lixuhs23vyuqqodocrg/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0/axom-develop-xz7g7s5cdkdr6lixuhs23vyuqqodocrg/lib64;;/opt/cray/pe/cce/20.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/axom-develop-mdr65pqpz5pq5uhe57wjeu6vp5q3kuoa/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/axom-develop-mdr65pqpz5pq5uhe57wjeu6vp5q3kuoa/lib64;;/opt/cray/pe/cce/20.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") @@ -21,11 +21,11 @@ set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/cce/craycc" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/cce/craycc" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/cce/case-insensitive/crayCC" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/cce/case-insensitive/crayCC" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/cce/crayftn" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/cce/crayftn" CACHE PATH "") else() @@ -104,7 +104,7 @@ set(BLT_OPENMP_LINK_FLAGS "$<$>:-fopenmp=libomp> # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/cce-20.0.0" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0" CACHE PATH "") set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-pbrrhvrsmnewby6q3u35ceppt5fxme2p" CACHE PATH "") @@ -156,12 +156,14 @@ set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-ieb2bzuz435ydpyiq2uj36yyqjbrffml/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5/lib/python3.13/site-packages" CACHE PATH "") set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-xw5ysy75zfqlalmbmtl5phd2qip43hlo/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/py-pluggy-1.6.0-n4ojmtqyawxz6gipk64pup3ahwz3xqpu/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/py-pluggy-1.6.0-n4ojmtqyawxz6gipk64pup3ahwz3xqpu/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/py-iniconfig-2.1.0-lf3yvch65se22jr7b5unfoxtse4ugkrk/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/py-iniconfig-2.1.0-lf3yvch65se22jr7b5unfoxtse4ugkrk/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-e5tepnokpccpvjfp6lwnwfi5laakuhdh/lib/python3.13/site-packages" CACHE PATH "") diff --git a/host-configs/rzadams-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake b/host-configs/rzadams-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake index acdca3b154..01aaac36a5 100644 --- a/host-configs/rzadams-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake +++ b/host-configs/rzadams-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake @@ -4,13 +4,13 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.29.2/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.3.1/adiak-0.4.0-c4srnrz5apjetx74dt6xjah63o3a7xcx;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.3.1/blt-0.7.1-kzmxfjvxm4drr2qhspckvwee6mrlc3e5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.3.1/c2c-1.8.0-lm64gc55hpxilmtsw7geirys3q4wacu5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.3.1/conduit-0.9.5-wswspowegvp5byl5inc2cikljzkcqg64;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.3.1/lua-5.4.8-j7ngytniswhoa536kqfzarcraarkicbm;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.3.1/mfem-4.9.0-xwg5un3mlbeog3j4voeltekra43ix3ga;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.3.1/py-nanobind-2.7.0-q2kwozph3gwgfyjnx43jp2gkbaghwxoj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.3.1/hdf5-1.8.23-rb4cjthx4wz4wnpaizwmm2jycijdthqm;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.3.1/parmetis-4.0.3-ng66tt5fmjyfoiuuxyl4rxqv4ompawmd;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.3.1/py-numpy-2.4.2-zcxbrxijjc3miotq6s3kq446afrgycuu;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.3.1/hypre-2.27.0-6m2jwyzqbksxgmdznvh54ytysxyme7jy;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.3.1/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-wcyqyggju4u2ornjjnamgdgfbqjrzsir;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.3.1/umpire-2025.12.0-5w5v22edixuttxwueaagiobpjzn5mcgr;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.3.1/zlib-1.3.1-5w5muvpvct6uruxrddscblsiyizl3uoc;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.3.1/metis-5.1.0-4punmdkye6rovr3u7ph7bgj66qim77du;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.3.1/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-jnalmgzqx2nn2afq4gwhyjda3u6yub7y;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.3.1/fmt-11.0.2-opsfnufdiedpfpob5wgbvhcrhyhzfkaq;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.3.1;/opt/rocm-6.3.1;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.3.1;/opt/rocm-6.3.1;/opt/rocm-6.3.1" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1/adiak-0.4.0-c4srnrz5apjetx74dt6xjah63o3a7xcx;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1/blt-0.7.1-kzmxfjvxm4drr2qhspckvwee6mrlc3e5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1/c2c-1.8.0-lm64gc55hpxilmtsw7geirys3q4wacu5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1/conduit-0.9.5-wswspowegvp5byl5inc2cikljzkcqg64;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1/lua-5.4.8-j7ngytniswhoa536kqfzarcraarkicbm;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1/mfem-4.9.0-xwg5un3mlbeog3j4voeltekra43ix3ga;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1/py-nanobind-2.7.0-q2kwozph3gwgfyjnx43jp2gkbaghwxoj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1/hdf5-1.8.23-rb4cjthx4wz4wnpaizwmm2jycijdthqm;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1/parmetis-4.0.3-ng66tt5fmjyfoiuuxyl4rxqv4ompawmd;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1/py-mpi4py-4.1.1-s4a64moil7qmgczpelmmb344qxxavrzk;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1/py-numpy-2.4.2-zcxbrxijjc3miotq6s3kq446afrgycuu;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1/hypre-2.27.0-6m2jwyzqbksxgmdznvh54ytysxyme7jy;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-wcyqyggju4u2ornjjnamgdgfbqjrzsir;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1/umpire-2025.12.0-5w5v22edixuttxwueaagiobpjzn5mcgr;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1/zlib-1.3.1-5w5muvpvct6uruxrddscblsiyizl3uoc;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1/metis-5.1.0-4punmdkye6rovr3u7ph7bgj66qim77du;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-jnalmgzqx2nn2afq4gwhyjda3u6yub7y;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1/fmt-11.0.2-opsfnufdiedpfpob5wgbvhcrhyhzfkaq;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.3.1;/opt/rocm-6.3.1;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.3.1;/opt/rocm-6.3.1;/opt/rocm-6.3.1" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.3.1/axom-develop-qrojk4ujrhd5b734s2yc73n5u3kvcgqa/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.3.1/axom-develop-qrojk4ujrhd5b734s2yc73n5u3kvcgqa/lib64;;/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1/axom-develop-4srm7we3wjp7l4cfcshzov5nr52jna4r/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1/axom-develop-4srm7we3wjp7l4cfcshzov5nr52jna4r/lib64;;/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.3.1/axom-develop-qrojk4ujrhd5b734s2yc73n5u3kvcgqa/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.3.1/axom-develop-qrojk4ujrhd5b734s2yc73n5u3kvcgqa/lib64;;/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1/axom-develop-4srm7we3wjp7l4cfcshzov5nr52jna4r/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1/axom-develop-4srm7we3wjp7l4cfcshzov5nr52jna4r/lib64;;/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") @@ -21,11 +21,11 @@ set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdclang" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdclang" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdclang++" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdclang++" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdflang" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdflang" CACHE PATH "") else() @@ -98,7 +98,7 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.3.1" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1" CACHE PATH "") set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-wswspowegvp5byl5inc2cikljzkcqg64" CACHE PATH "") @@ -150,12 +150,14 @@ set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-q2kwozph3gwgfyjnx43jp2gkbaghwxoj/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5/lib/python3.13/site-packages" CACHE PATH "") set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-zcxbrxijjc3miotq6s3kq446afrgycuu/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/py-pluggy-1.6.0-n4ojmtqyawxz6gipk64pup3ahwz3xqpu/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/py-pluggy-1.6.0-n4ojmtqyawxz6gipk64pup3ahwz3xqpu/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/py-iniconfig-2.1.0-lf3yvch65se22jr7b5unfoxtse4ugkrk/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/py-iniconfig-2.1.0-lf3yvch65se22jr7b5unfoxtse4ugkrk/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-s4a64moil7qmgczpelmmb344qxxavrzk/lib/python3.13/site-packages" CACHE PATH "") diff --git a/host-configs/rzadams-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake b/host-configs/rzadams-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake index 4579e5266d..c7f0986432 100644 --- a/host-configs/rzadams-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake +++ b/host-configs/rzadams-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake @@ -4,13 +4,13 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.29.2/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/blt-0.7.1-4kxhk7nbuiv5oh2ymh7sdimnmor36hmo;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/c2c-1.8.0-h5kio7nseyazg4pzgvj4kxanb3plhbxm;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-rmlwpdacdtpzf4u72ctyroj72soqzf3f;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/conduit-0.9.5-zqywrasos2vbears5hvdwxhulm3xox6q;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/lua-5.4.8-gp4kdewsy4uiy6flsx342lgogcm7fym2;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/mfem-4.9.0-6cgokeizyrjzsjzwmtf35p5jvhnws7n5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/py-nanobind-2.7.0-2kp35jcpb73xuujvcz6kmg4q2npek4gg;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/adiak-0.4.0-cropyu42jr4q6k3zar6mkma6gc2vp6tk;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/libunwind-1.8.3-plszp43gkd75kzjcvge3qxrtb6j2budl;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/hdf5-1.8.23-mgic2kxojd5zeivhaz473zzmzrg3gdqe;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/parmetis-4.0.3-hd2qoczrh3vw3ejm6msitqdgecjk7xwe;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/py-numpy-2.4.2-a3mo27shjtpliwewbm5cci2kf37crrab;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/hypre-2.27.0-4nrl5egspbxmnkpzfym5udby6uruhn3i;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-v74yskgcb7tsgho6bdush5jdlbigkttf;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/umpire-2025.12.0-jft5rocv5ev4b57f3664b2kagyfjra2e;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/zlib-1.3.1-slqujeue3l44x3pgqbu7e2pw3lh2mj5i;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/metis-5.1.0-puzeddu6ufhz5vpxnw7i3i2i6slsvuuj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-zadbjpethvnlhpqwx6vm6ahphk42a4gq;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/fmt-11.0.2-lpnox72rqcneew5hucwzq4nmms4at525;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/blt-0.7.1-4kxhk7nbuiv5oh2ymh7sdimnmor36hmo;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/c2c-1.8.0-h5kio7nseyazg4pzgvj4kxanb3plhbxm;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-rmlwpdacdtpzf4u72ctyroj72soqzf3f;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/conduit-0.9.5-zqywrasos2vbears5hvdwxhulm3xox6q;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/lua-5.4.8-gp4kdewsy4uiy6flsx342lgogcm7fym2;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/mfem-4.9.0-6cgokeizyrjzsjzwmtf35p5jvhnws7n5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/py-nanobind-2.7.0-2kp35jcpb73xuujvcz6kmg4q2npek4gg;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/adiak-0.4.0-cropyu42jr4q6k3zar6mkma6gc2vp6tk;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/libunwind-1.8.3-plszp43gkd75kzjcvge3qxrtb6j2budl;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/hdf5-1.8.23-mgic2kxojd5zeivhaz473zzmzrg3gdqe;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/parmetis-4.0.3-hd2qoczrh3vw3ejm6msitqdgecjk7xwe;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/py-mpi4py-4.1.1-wimx6qtzmouefjmhidpsho6iipv234ay;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/py-numpy-2.4.2-a3mo27shjtpliwewbm5cci2kf37crrab;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/hypre-2.27.0-4nrl5egspbxmnkpzfym5udby6uruhn3i;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-v74yskgcb7tsgho6bdush5jdlbigkttf;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/umpire-2025.12.0-jft5rocv5ev4b57f3664b2kagyfjra2e;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/zlib-1.3.1-slqujeue3l44x3pgqbu7e2pw3lh2mj5i;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/metis-5.1.0-puzeddu6ufhz5vpxnw7i3i2i6slsvuuj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-zadbjpethvnlhpqwx6vm6ahphk42a4gq;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/fmt-11.0.2-lpnox72rqcneew5hucwzq4nmms4at525;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/axom-develop-i4n5ay2l5l2hludfn6z5vyvfwk57p6hx/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/axom-develop-i4n5ay2l5l2hludfn6z5vyvfwk57p6hx/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/axom-develop-iczg7quatuysr5e2ggwwzdsnmdzw7odk/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/axom-develop-iczg7quatuysr5e2ggwwzdsnmdzw7odk/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/axom-develop-i4n5ay2l5l2hludfn6z5vyvfwk57p6hx/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3/axom-develop-i4n5ay2l5l2hludfn6z5vyvfwk57p6hx/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/axom-develop-iczg7quatuysr5e2ggwwzdsnmdzw7odk/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/axom-develop-iczg7quatuysr5e2ggwwzdsnmdzw7odk/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") @@ -21,11 +21,11 @@ set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdclang" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdclang" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdclang++" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdclang++" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdflang" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdflang" CACHE PATH "") else() @@ -98,7 +98,7 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/llvm-amdgpu-6.4.3" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3" CACHE PATH "") set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-zqywrasos2vbears5hvdwxhulm3xox6q" CACHE PATH "") @@ -150,12 +150,14 @@ set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-2kp35jcpb73xuujvcz6kmg4q2npek4gg/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5/lib/python3.13/site-packages" CACHE PATH "") set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-a3mo27shjtpliwewbm5cci2kf37crrab/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/py-pluggy-1.6.0-n4ojmtqyawxz6gipk64pup3ahwz3xqpu/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/py-pluggy-1.6.0-n4ojmtqyawxz6gipk64pup3ahwz3xqpu/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_03_30/none-none/py-iniconfig-2.1.0-lf3yvch65se22jr7b5unfoxtse4ugkrk/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/py-iniconfig-2.1.0-lf3yvch65se22jr7b5unfoxtse4ugkrk/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-wimx6qtzmouefjmhidpsho6iipv234ay/lib/python3.13/site-packages" CACHE PATH "") diff --git a/host-configs/rzvector-toss_4_x86_64_ib-gcc@13.3.1_cuda.cmake b/host-configs/rzvector-toss_4_x86_64_ib-gcc@13.3.1_cuda.cmake index 89cb21e7c5..8af38036da 100644 --- a/host-configs/rzvector-toss_4_x86_64_ib-gcc@13.3.1_cuda.cmake +++ b/host-configs/rzvector-toss_4_x86_64_ib-gcc@13.3.1_cuda.cmake @@ -4,28 +4,28 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.26.3/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/gcc-13.3.1/blt-0.7.1-yiu2olkumeazbrto7x4v2dv6qbefhf5a;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/gcc-13.3.1/c2c-1.8.0-jkclfdpl7igj3ur3ufnpt6dupjfvotpc;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/gcc-13.3.1/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-qekykotizhycp2p4deoltaa227sajuvj;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/gcc-13.3.1/conduit-0.9.5-xyt2d2ot4eozuhrjv7mccoe7tw6ef7ui;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/gcc-13.3.1/mfem-4.9.0-kjei2ccah6sei4jplqabrucxltyq4zfb;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/gcc-13.3.1/py-nanobind-2.7.0-szozdpwzge7ah4bsilit6fijezxqbfop;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/gcc-13.3.1/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-vmsmvq25y56dkomrba3g63ls5nuga6xm;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/gcc-13.3.1/umpire-2025.12.0-kjyb6uox2o7qtfna22uqzxrqck7lukbt;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/gcc-13.3.1/adiak-0.4.0-i5bahteezqmswdv2ulpfvfogmuue463q;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/gcc-13.3.1/libunwind-1.8.3-pwdmczcnf7vugfly5zk7eztefnn7kv75;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/gcc-13.3.1/hdf5-1.8.23-2pnsuxpt6r2wmdc6sqyku6ki6na4gcfl;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/gcc-13.3.1/parmetis-4.0.3-tt2yxrbsrccyglhjqcs2y2nspmt4qoqx;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/gcc-13.3.1/py-numpy-2.4.2-z4yapdkn22w6adfrl7ibucqiokgrwb7f;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/gcc-13.3.1/hypre-2.27.0-bs56wsg2yagtwcz5q5a67ov4tln54p5i;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/gcc-13.3.1/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-fm7mimsvemux42yrsmsvqw6tl4kugyzy;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/gcc-13.3.1/fmt-11.0.2-ggfpxocredbjrldu7ar33cjedhmkm3da;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/gcc-13.3.1/metis-5.1.0-3jtvhjwnhqqaf5v2na7f675qgvlusyx3;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/none-none/gcc-runtime-13.3.1-zsobnkwck2glzcbukupgvgbiwwhdonys;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.26.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66xzj4wm5kfqxyc4vgox7gb3;/usr/tce/packages/cuda/cuda-12.9.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/doxygen-1.15.0;/usr/tce/packages/gcc/gcc-13.3.1;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-gcc-13.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1/blt-0.7.1-lj5bghgkodlzmfs75wimnoxuui2quc3a;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1/c2c-1.8.0-tyixdhhwshu35d2vuh6gw5bk3b3k7nqz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-apockdkmc2slp4nexkn6sbspz7pibric;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1/conduit-0.9.5-vlm6nb2rwwmvnxtnxw5p3o27rzsap6vt;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1/mfem-4.9.0-dyhiljftw2yufyry2ybddjlzrst7usbg;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1/py-nanobind-2.7.0-e42etfacct5pxga65kryysakw52u4c63;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-hjzgnbnbz4sifpanhmuqhuuydzmctbbl;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1/umpire-2025.12.0-yxkn4g7yhresecqk2dkjivbahvu3qhpp;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1/adiak-0.4.0-nqiuo5ltc24jpawzi6mlp3xtmngcopiu;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1/libunwind-1.8.3-a7zvz3euhmbhk4as6h6j66ytabzwztxb;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1/hdf5-1.8.23-zyfoez4ztydoqfeubcamjypyhprjpuca;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1/parmetis-4.0.3-sxnyn5chjyiyowzk32dsynslufrofyho;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1/py-mpi4py-4.1.1-l3h5jmmg773bd5oer7makte3ln6rn7xv;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1/py-numpy-2.4.2-to6o5czwkczlh46d6gkubixw5bhzxakl;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1/hypre-2.27.0-3ckgmamfpdf3ng74h76dziarqz63godz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-bt6ckjhqcfr3gjiu76sdhabhppjhjd6t;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1/fmt-11.0.2-5wsybhdkos4xu5i7thboag6245lzc6jj;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1/metis-5.1.0-3qhomb7zt2n7nh46kmc2egkzmcd3tafm;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/none-none/gcc-runtime-13.3.1-xlvjd7vcdngtnvtxcdyqwtquvenekynf;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.26.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66xzj4wm5kfqxyc4vgox7gb3;/usr/tce/packages/cuda/cuda-12.9.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/doxygen-1.15.0;/usr/tce/packages/gcc/gcc-13.3.1;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-gcc-13.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/gcc-13.3.1/axom-develop-imlx7oqvp6ch6htnn3syya75lumfcxjo/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/gcc-13.3.1/axom-develop-imlx7oqvp6ch6htnn3syya75lumfcxjo/lib64;;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1/axom-develop-fzmegf77c74yozjd72rl5iqynh23wqhx/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1/axom-develop-fzmegf77c74yozjd72rl5iqynh23wqhx/lib64;;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/gcc-13.3.1/axom-develop-imlx7oqvp6ch6htnn3syya75lumfcxjo/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/gcc-13.3.1/axom-develop-imlx7oqvp6ch6htnn3syya75lumfcxjo/lib64;;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1/axom-develop-fzmegf77c74yozjd72rl5iqynh23wqhx/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1/axom-develop-fzmegf77c74yozjd72rl5iqynh23wqhx/lib64;;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ # Compilers #------------------------------------------------------------------------------ -# Compiler Spec: gcc@13.3.1/xrm3n2pex2en2wvm6p4ap62nieoz7kne +# Compiler Spec: gcc@13.3.1/zbmuyoury7g5npf6fa7lwwxbjninyheh #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/gcc" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/gcc" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/g++" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/g++" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/gfortran" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/gfortran" CACHE PATH "") else() @@ -37,6 +37,10 @@ else() endif() +set(CMAKE_C_FLAGS "-fPIC" CACHE STRING "") + +set(CMAKE_CXX_FLAGS "-fPIC" CACHE STRING "") + set(CMAKE_Fortran_FLAGS "-fPIC" CACHE STRING "") set(ENABLE_FORTRAN ON CACHE BOOL "") @@ -81,7 +85,7 @@ set(ENABLE_CUDA ON CACHE BOOL "") set(CMAKE_CUDA_SEPARABLE_COMPILATION ON CACHE BOOL "") -set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -restrict --expt-extended-lambda --expt-relaxed-constexpr " CACHE STRING "" FORCE) +set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -restrict --expt-extended-lambda --expt-relaxed-constexpr -Xcompiler=-fPIC " CACHE STRING "" FORCE) # nvcc does not like gtest's 'pthreads' flag @@ -99,29 +103,29 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/gcc-13.3.1" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1" CACHE PATH "") -set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-xyt2d2ot4eozuhrjv7mccoe7tw6ef7ui" CACHE PATH "") +set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-vlm6nb2rwwmvnxtnxw5p3o27rzsap6vt" CACHE PATH "") -set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-jkclfdpl7igj3ur3ufnpt6dupjfvotpc" CACHE PATH "") +set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-tyixdhhwshu35d2vuh6gw5bk3b3k7nqz" CACHE PATH "") -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-kjei2ccah6sei4jplqabrucxltyq4zfb" CACHE PATH "") +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-dyhiljftw2yufyry2ybddjlzrst7usbg" CACHE PATH "") -set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-2pnsuxpt6r2wmdc6sqyku6ki6na4gcfl" CACHE PATH "") +set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-zyfoez4ztydoqfeubcamjypyhprjpuca" CACHE PATH "") set(LUA_DIR "/usr" CACHE PATH "") -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-vmsmvq25y56dkomrba3g63ls5nuga6xm" CACHE PATH "") +set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-hjzgnbnbz4sifpanhmuqhuuydzmctbbl" CACHE PATH "") -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-kjyb6uox2o7qtfna22uqzxrqck7lukbt" CACHE PATH "") +set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-yxkn4g7yhresecqk2dkjivbahvu3qhpp" CACHE PATH "") # OPENCASCADE not built -set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-i5bahteezqmswdv2ulpfvfogmuue463q" CACHE PATH "") +set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-nqiuo5ltc24jpawzi6mlp3xtmngcopiu" CACHE PATH "") -set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-qekykotizhycp2p4deoltaa227sajuvj" CACHE PATH "") +set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-apockdkmc2slp4nexkn6sbspz7pibric" CACHE PATH "") -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-fm7mimsvemux42yrsmsvqw6tl4kugyzy" CACHE PATH "") +set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-bt6ckjhqcfr3gjiu76sdhabhppjhjd6t" CACHE PATH "") # scr not built @@ -149,14 +153,16 @@ set(CPPCHECK_EXECUTABLE "${DEVTOOLS_ROOT}/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66x set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/kky5oskfhacihjb5gpy7qkeh6edlrtcb/doxygen-1.15.0/bin/doxygen" CACHE PATH "") -set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-szozdpwzge7ah4bsilit6fijezxqbfop/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-e42etfacct5pxga65kryysakw52u4c63/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-to6o5czwkczlh46d6gkubixw5bhzxakl/lib/python3.13/site-packages" CACHE PATH "") -set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-z4yapdkn22w6adfrl7ibucqiokgrwb7f/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/none-none/py-pluggy-1.6.0-6zab4pck6v7johubbhalr2waowbwpjei/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/none-none/py-pluggy-1.6.0-6zab4pck6v7johubbhalr2waowbwpjei/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/none-none/py-iniconfig-2.1.0-xwmbxuhk2oo3dcuizdp3z4hqbpqykkom/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/none-none/py-iniconfig-2.1.0-xwmbxuhk2oo3dcuizdp3z4hqbpqykkom/lib/python3.13/site-packages" CACHE PATH "") +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-l3h5jmmg773bd5oer7makte3ln6rn7xv/lib/python3.13/site-packages" CACHE PATH "") diff --git a/host-configs/rzvector-toss_4_x86_64_ib-llvm@19.1.3_cuda.cmake b/host-configs/rzvector-toss_4_x86_64_ib-llvm@19.1.3_cuda.cmake index 88fa95deb8..8988cc45cf 100644 --- a/host-configs/rzvector-toss_4_x86_64_ib-llvm@19.1.3_cuda.cmake +++ b/host-configs/rzvector-toss_4_x86_64_ib-llvm@19.1.3_cuda.cmake @@ -4,28 +4,28 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.26.3/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/llvm-19.1.3/blt-0.7.1-577uis2w7b3ipf4v4v6y6vogmy52d6ay;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/llvm-19.1.3/c2c-1.8.0-pk3z5zabg2gtkur2c4mqzc5zz7z5f2tv;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/llvm-19.1.3/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-ftzyobuu2a4siddiotqw4652bctzj3ke;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/llvm-19.1.3/conduit-0.9.5-gracxngtjim7b6zjsb5z4kf7qk347eo2;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/llvm-19.1.3/mfem-4.9.0-vvfbyadobvw4sxcdwuulbfs2lneeczet;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/llvm-19.1.3/py-nanobind-2.7.0-6akjhhlga6h3jx75swovfvo76k6rw6fp;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/llvm-19.1.3/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-aibnhmtfonzvsz7owanyr6m3eelmwfvj;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/llvm-19.1.3/umpire-2025.12.0-3xllaqzq537svpt3ie6jhzoaxciuut2i;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/llvm-19.1.3/adiak-0.4.0-27bezbcpd5jxnvs65h3m6u2aheplxbmv;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/llvm-19.1.3/libunwind-1.8.3-ddv7ypujzw2wvlzabw3d42qygcbtofuq;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/llvm-19.1.3/hdf5-1.8.23-b37o2ze3w5sozwz6jmalg4pgg66jcogm;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/llvm-19.1.3/parmetis-4.0.3-slhcesf5fhsucwxtlxxana67jcrligfy;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/llvm-19.1.3/py-numpy-2.4.2-7qsjxyuuh7rh5hccdziewhwqymkmepb3;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/llvm-19.1.3/hypre-2.27.0-l2mxwhcndyhn4pkbe4rthke2rlkmgdbd;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/llvm-19.1.3/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-crik3a76mg7gqb6xsqkijjk3qzlw4mcl;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/llvm-19.1.3/fmt-11.0.2-uougkyc3rcyd7punvwjtcme2zlhehpgz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/none-none/gcc-runtime-13.3.1-zsobnkwck2glzcbukupgvgbiwwhdonys;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/llvm-19.1.3/metis-5.1.0-3jeshrn7tydsgn5zdkf3iirdhw3u3oeq;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.26.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66xzj4wm5kfqxyc4vgox7gb3;/usr/tce/packages/cuda/cuda-12.9.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/doxygen-1.15.0;/usr/tce/packages/gcc/gcc-13.3.1;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-clang-19.1.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3/blt-0.7.1-ux6wzebtj6vtgnayf4gqv2rr62ulwwfc;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3/c2c-1.8.0-wto5u3qsffjhbxirygr7kqqjlg55pyv6;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-lpeeewbzwhzppynnxvwzs6kz7dyblozq;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3/conduit-0.9.5-vqo2x2n6mz2vs4eg7kynwmlpeolsuim5;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3/mfem-4.9.0-6kl7ian5mzf5ypjvbjaghzk7s3ez62nz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3/py-nanobind-2.7.0-zxodixu5egwgeorhpe3ciggwsv3ndfxb;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-7hlq5rt36tbau3kruxvg7y62rs3wgn7k;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3/umpire-2025.12.0-feuig5ip7m5e2b4ejmchtyir77g5dxeb;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3/adiak-0.4.0-do3kop54yldi4hz43usrtnx2zyhvwfsv;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3/libunwind-1.8.3-ck74b2escwzf2xtpiafdvqwmat26uuoe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3/hdf5-1.8.23-vpibouztpyzmgikg4dg2egwf2u53mmpl;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3/parmetis-4.0.3-fs7gps55rp6znnpz3b4zkd35jjkfycfc;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3/py-mpi4py-4.1.1-hrvj5zyy3xo2rbzvbz4ed7kjye7ssgmk;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3/py-numpy-2.4.2-73lpwcid33wlekelrxoh24535lzexwwg;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3/hypre-2.27.0-vksttbhyimswj3oxopuulk5xfa7nkhmk;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-biicbsrzigcqj4oofalk4povvcnibwea;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3/fmt-11.0.2-ekc2idnbqipl2pqb4nugxoa7sj7aigpo;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/none-none/gcc-runtime-13.3.1-xlvjd7vcdngtnvtxcdyqwtquvenekynf;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3/metis-5.1.0-7gsm27yycsippuh2l4fwei4dtvevbqgh;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.26.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66xzj4wm5kfqxyc4vgox7gb3;/usr/tce/packages/cuda/cuda-12.9.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/doxygen-1.15.0;/usr/tce/packages/gcc/gcc-13.3.1;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-clang-19.1.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/llvm-19.1.3/axom-develop-476ivmlyn3vhgofetdkr5fnl2sl627df/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/llvm-19.1.3/axom-develop-476ivmlyn3vhgofetdkr5fnl2sl627df/lib64;;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/llvm-19.1.3-gy2lu5xbi4csr2k47emlajzfs5mlsd4g/lib/x86_64-unknown-linux-gnu;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13;/usr/tce/packages/clang/clang-19.1.3/lib;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3/axom-develop-wxy5sh2abd2fekvyyulaxy3y3zrwcn7w/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3/axom-develop-wxy5sh2abd2fekvyyulaxy3y3zrwcn7w/lib64;;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/llvm-19.1.3-gy2lu5xbi4csr2k47emlajzfs5mlsd4g/lib/x86_64-unknown-linux-gnu;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13;/usr/tce/packages/clang/clang-19.1.3/lib;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/llvm-19.1.3/axom-develop-476ivmlyn3vhgofetdkr5fnl2sl627df/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/llvm-19.1.3/axom-develop-476ivmlyn3vhgofetdkr5fnl2sl627df/lib64;;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/llvm-19.1.3-gy2lu5xbi4csr2k47emlajzfs5mlsd4g/lib/x86_64-unknown-linux-gnu;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13;/usr/tce/packages/clang/clang-19.1.3/lib;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3/axom-develop-wxy5sh2abd2fekvyyulaxy3y3zrwcn7w/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3/axom-develop-wxy5sh2abd2fekvyyulaxy3y3zrwcn7w/lib64;;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/llvm-19.1.3-gy2lu5xbi4csr2k47emlajzfs5mlsd4g/lib/x86_64-unknown-linux-gnu;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13;/usr/tce/packages/clang/clang-19.1.3/lib;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ # Compilers #------------------------------------------------------------------------------ -# Compiler Spec: llvm@19.1.3/4rf6d7atkulu6wz7bucycjwtiuwqfy24 +# Compiler Spec: llvm@19.1.3/m2tcgbxfcdjwdmyrkqwamlljh4qeiie2 #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/clang/clang" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/clang/clang" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/clang/clang++" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/clang/clang++" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/gfortran" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/gfortran" CACHE PATH "") else() @@ -37,6 +37,10 @@ else() endif() +set(CMAKE_C_FLAGS "-fPIC" CACHE STRING "") + +set(CMAKE_CXX_FLAGS "-fPIC" CACHE STRING "") + set(CMAKE_Fortran_FLAGS "-fPIC" CACHE STRING "") set(ENABLE_FORTRAN ON CACHE BOOL "") @@ -83,7 +87,7 @@ set(ENABLE_CUDA ON CACHE BOOL "") set(CMAKE_CUDA_SEPARABLE_COMPILATION ON CACHE BOOL "") -set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -restrict --expt-extended-lambda --expt-relaxed-constexpr " CACHE STRING "" FORCE) +set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -restrict --expt-extended-lambda --expt-relaxed-constexpr -Xcompiler=-fPIC " CACHE STRING "" FORCE) # nvcc does not like gtest's 'pthreads' flag @@ -101,29 +105,29 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/llvm-19.1.3" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3" CACHE PATH "") -set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-gracxngtjim7b6zjsb5z4kf7qk347eo2" CACHE PATH "") +set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-vqo2x2n6mz2vs4eg7kynwmlpeolsuim5" CACHE PATH "") -set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-pk3z5zabg2gtkur2c4mqzc5zz7z5f2tv" CACHE PATH "") +set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-wto5u3qsffjhbxirygr7kqqjlg55pyv6" CACHE PATH "") -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-vvfbyadobvw4sxcdwuulbfs2lneeczet" CACHE PATH "") +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-6kl7ian5mzf5ypjvbjaghzk7s3ez62nz" CACHE PATH "") -set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-b37o2ze3w5sozwz6jmalg4pgg66jcogm" CACHE PATH "") +set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-vpibouztpyzmgikg4dg2egwf2u53mmpl" CACHE PATH "") set(LUA_DIR "/usr" CACHE PATH "") -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-aibnhmtfonzvsz7owanyr6m3eelmwfvj" CACHE PATH "") +set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-7hlq5rt36tbau3kruxvg7y62rs3wgn7k" CACHE PATH "") -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-3xllaqzq537svpt3ie6jhzoaxciuut2i" CACHE PATH "") +set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-feuig5ip7m5e2b4ejmchtyir77g5dxeb" CACHE PATH "") # OPENCASCADE not built -set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-27bezbcpd5jxnvs65h3m6u2aheplxbmv" CACHE PATH "") +set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-do3kop54yldi4hz43usrtnx2zyhvwfsv" CACHE PATH "") -set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-ftzyobuu2a4siddiotqw4652bctzj3ke" CACHE PATH "") +set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-lpeeewbzwhzppynnxvwzs6kz7dyblozq" CACHE PATH "") -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-crik3a76mg7gqb6xsqkijjk3qzlw4mcl" CACHE PATH "") +set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-biicbsrzigcqj4oofalk4povvcnibwea" CACHE PATH "") # scr not built @@ -151,14 +155,16 @@ set(CPPCHECK_EXECUTABLE "${DEVTOOLS_ROOT}/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66x set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/kky5oskfhacihjb5gpy7qkeh6edlrtcb/doxygen-1.15.0/bin/doxygen" CACHE PATH "") -set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-6akjhhlga6h3jx75swovfvo76k6rw6fp/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-zxodixu5egwgeorhpe3ciggwsv3ndfxb/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-73lpwcid33wlekelrxoh24535lzexwwg/lib/python3.13/site-packages" CACHE PATH "") -set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-7qsjxyuuh7rh5hccdziewhwqymkmepb3/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/none-none/py-pluggy-1.6.0-6zab4pck6v7johubbhalr2waowbwpjei/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/none-none/py-pluggy-1.6.0-6zab4pck6v7johubbhalr2waowbwpjei/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/none-none/py-iniconfig-2.1.0-xwmbxuhk2oo3dcuizdp3z4hqbpqykkom/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_13_24_53/none-none/py-iniconfig-2.1.0-xwmbxuhk2oo3dcuizdp3z4hqbpqykkom/lib/python3.13/site-packages" CACHE PATH "") +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-hrvj5zyy3xo2rbzvbz4ed7kjye7ssgmk/lib/python3.13/site-packages" CACHE PATH "") diff --git a/host-configs/rzvernal-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake b/host-configs/rzvernal-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake index 063f3a86e8..e4bfd0146e 100644 --- a/host-configs/rzvernal-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake +++ b/host-configs/rzvernal-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake @@ -4,13 +4,13 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.29.2/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/blt-0.7.1-tj6h3jr6fjdlxux2yd4wvdy7xwxxuh23;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/c2c-1.8.0-xav3pjepsvpzrcqd5dcom4is4rioejzr;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-37ef6g3godgxug45i3u3x26awgovbhxa;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/conduit-0.9.5-oyjekm66om6cx7yndhpv3yumtxgaaroi;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/lua-5.4.8-ty6vxrgtb5lsthp7rrcy7vx3x4yknrms;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/mfem-4.9.0-iiiowpg2cxn346mv67ycscrcrt5eacdk;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/py-nanobind-2.7.0-h7v5ggotyqa5ul7kpyc2yfojjkah4ium;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/adiak-0.4.0-w7lj246x4whjefszgyhapkebe3cjd7bi;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/libunwind-1.8.3-htd47rshpgaa6w73jemf7tycnnlkn5ws;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/hdf5-1.8.23-vy4rnk6awhcfgte2wuwisgp3t3y4gzwy;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/parmetis-4.0.3-jqlganykrtm62xb3ve2eetb3gltn5nki;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/py-numpy-2.4.2-vdrslpslcsdjba3zyryp27z4tmpn3ayu;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/hypre-2.27.0-gjrhbi2v66y2yfxgq7vegqpcawo2b35r;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-iwmhvbuhywrcyi4ojshrizfpccb53g3u;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/umpire-2025.12.0-qkp2thztbvdojybrli7tpxea5duxagkn;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/zlib-1.3.1-6yddik2qxvkwtfcckdhbdgos7awok5za;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/metis-5.1.0-bryzqxg7sgprevvt3ux5np2uxam4wbvp;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-xdstcq3vkkl6gysd7agincseoncdi5nz;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/fmt-11.0.2-vjndv6co4e6qonqazcmklw3pamvmos74;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cce-tce/cce-20.0.0;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.32-rocmcc-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/blt-0.7.1-tj6h3jr6fjdlxux2yd4wvdy7xwxxuh23;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/c2c-1.8.0-xav3pjepsvpzrcqd5dcom4is4rioejzr;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-37ef6g3godgxug45i3u3x26awgovbhxa;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/conduit-0.9.5-oyjekm66om6cx7yndhpv3yumtxgaaroi;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/lua-5.4.8-ty6vxrgtb5lsthp7rrcy7vx3x4yknrms;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/mfem-4.9.0-iiiowpg2cxn346mv67ycscrcrt5eacdk;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/py-nanobind-2.7.0-h7v5ggotyqa5ul7kpyc2yfojjkah4ium;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/adiak-0.4.0-w7lj246x4whjefszgyhapkebe3cjd7bi;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/libunwind-1.8.3-htd47rshpgaa6w73jemf7tycnnlkn5ws;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/hdf5-1.8.23-vy4rnk6awhcfgte2wuwisgp3t3y4gzwy;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/parmetis-4.0.3-jqlganykrtm62xb3ve2eetb3gltn5nki;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/py-mpi4py-4.1.1-gla2ur2g4lb44jyi2tky6p2ynzgseonn;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/py-numpy-2.4.2-vdrslpslcsdjba3zyryp27z4tmpn3ayu;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/hypre-2.27.0-gjrhbi2v66y2yfxgq7vegqpcawo2b35r;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-iwmhvbuhywrcyi4ojshrizfpccb53g3u;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/umpire-2025.12.0-qkp2thztbvdojybrli7tpxea5duxagkn;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/zlib-1.3.1-6yddik2qxvkwtfcckdhbdgos7awok5za;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/metis-5.1.0-bryzqxg7sgprevvt3ux5np2uxam4wbvp;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-xdstcq3vkkl6gysd7agincseoncdi5nz;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/fmt-11.0.2-vjndv6co4e6qonqazcmklw3pamvmos74;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cce-tce/cce-20.0.0;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.32-rocmcc-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/axom-develop-2xptn5vzdbqfiuuctci6jdklfwydwete/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/axom-develop-2xptn5vzdbqfiuuctci6jdklfwydwete/lib64;;/opt/cray/pe/cce/20.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/axom-develop-scv2k3ekpxlqvhgfmb6tt3qfk6vez3q5/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/axom-develop-scv2k3ekpxlqvhgfmb6tt3qfk6vez3q5/lib64;;/opt/cray/pe/cce/20.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/axom-develop-2xptn5vzdbqfiuuctci6jdklfwydwete/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0/axom-develop-2xptn5vzdbqfiuuctci6jdklfwydwete/lib64;;/opt/cray/pe/cce/20.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/axom-develop-scv2k3ekpxlqvhgfmb6tt3qfk6vez3q5/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/axom-develop-scv2k3ekpxlqvhgfmb6tt3qfk6vez3q5/lib64;;/opt/cray/pe/cce/20.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") @@ -21,11 +21,11 @@ set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/cce/craycc" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/cce/craycc" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/cce/case-insensitive/crayCC" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/cce/case-insensitive/crayCC" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/cce/crayftn" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/cce/crayftn" CACHE PATH "") else() @@ -104,7 +104,7 @@ set(BLT_OPENMP_LINK_FLAGS "$<$>:-fopenmp=libomp> # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/cce-20.0.0" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0" CACHE PATH "") set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-oyjekm66om6cx7yndhpv3yumtxgaaroi" CACHE PATH "") @@ -156,12 +156,14 @@ set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-h7v5ggotyqa5ul7kpyc2yfojjkah4ium/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj/lib/python3.13/site-packages" CACHE PATH "") set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-vdrslpslcsdjba3zyryp27z4tmpn3ayu/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/py-pluggy-1.6.0-27aoz44dedjxa6j6r6cavpqyqmpknidb/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/py-pluggy-1.6.0-27aoz44dedjxa6j6r6cavpqyqmpknidb/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/py-iniconfig-2.1.0-7rty7n4wq6kxxrzflm7wbxqzhhc6ph7q/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/py-iniconfig-2.1.0-7rty7n4wq6kxxrzflm7wbxqzhhc6ph7q/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-gla2ur2g4lb44jyi2tky6p2ynzgseonn/lib/python3.13/site-packages" CACHE PATH "") diff --git a/host-configs/rzvernal-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake b/host-configs/rzvernal-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake index 08e87e85df..7319ae5656 100644 --- a/host-configs/rzvernal-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake +++ b/host-configs/rzvernal-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake @@ -4,13 +4,13 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.29.2/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.3.1/adiak-0.4.0-vlugt2gwkx5wzlwudznxg3p2plq76pjq;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.3.1/blt-0.7.1-sv74qxlkpkll7mm6dhfjagyfog254w7c;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.3.1/c2c-1.8.0-tfrrofjzn3rdvyktevespobwrpjai5j4;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.3.1/conduit-0.9.5-vnlbr73jbwhqyvh2q76csdtve3ekpncc;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.3.1/lua-5.4.8-e3i2pbc52bfhrnxbaniektzp3x5jvyxr;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.3.1/mfem-4.9.0-jxeezxksp3c7i53h6dhqf2zz6gnaipub;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.3.1/py-nanobind-2.7.0-4sgpkalsntbmuim5oltb7ue4gggt6rv5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.3.1/hdf5-1.8.23-liive5kzeooveetbjhil4ic7hi3ccsg4;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.3.1/parmetis-4.0.3-fiyw3adearn3nmxjifcqtuzwy24fshp5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.3.1/py-numpy-2.4.2-ij66enxl4hmrmwua5bsec2yslylneqbw;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.3.1/hypre-2.27.0-cnukrpfl32kl5q6ud4wozdqbgtqoo7jx;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.3.1/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-5c3lqbqcjmekrwbfeg6ogptz7ssj3n4y;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.3.1/umpire-2025.12.0-i2dwv2l2jra74eazijtvprcx7acsupx4;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.3.1/zlib-1.3.1-dsduxyfnks4bhrkpbskff35l5y36ofp7;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.3.1/metis-5.1.0-6mtxrj6jbrvyd3xpq4pvluywbndjx42w;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.3.1/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-7nvneljwisxwircaqydhfcl3hl65bmr2;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.3.1/fmt-11.0.2-kw3vpg2ajhrgi7kozhcioz3nrgc6fh3o;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.3.1;/opt/rocm-6.3.1;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.3.1;/opt/rocm-6.3.1;/opt/rocm-6.3.1" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1/adiak-0.4.0-vlugt2gwkx5wzlwudznxg3p2plq76pjq;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1/blt-0.7.1-sv74qxlkpkll7mm6dhfjagyfog254w7c;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1/c2c-1.8.0-tfrrofjzn3rdvyktevespobwrpjai5j4;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1/conduit-0.9.5-vnlbr73jbwhqyvh2q76csdtve3ekpncc;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1/lua-5.4.8-e3i2pbc52bfhrnxbaniektzp3x5jvyxr;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1/mfem-4.9.0-jxeezxksp3c7i53h6dhqf2zz6gnaipub;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1/py-nanobind-2.7.0-4sgpkalsntbmuim5oltb7ue4gggt6rv5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1/hdf5-1.8.23-liive5kzeooveetbjhil4ic7hi3ccsg4;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1/parmetis-4.0.3-fiyw3adearn3nmxjifcqtuzwy24fshp5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1/py-mpi4py-4.1.1-ukxrsa2wugnmhh3glq25muf5hqexaf62;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1/py-numpy-2.4.2-ij66enxl4hmrmwua5bsec2yslylneqbw;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1/hypre-2.27.0-cnukrpfl32kl5q6ud4wozdqbgtqoo7jx;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-5c3lqbqcjmekrwbfeg6ogptz7ssj3n4y;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1/umpire-2025.12.0-i2dwv2l2jra74eazijtvprcx7acsupx4;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1/zlib-1.3.1-dsduxyfnks4bhrkpbskff35l5y36ofp7;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1/metis-5.1.0-6mtxrj6jbrvyd3xpq4pvluywbndjx42w;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-7nvneljwisxwircaqydhfcl3hl65bmr2;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1/fmt-11.0.2-kw3vpg2ajhrgi7kozhcioz3nrgc6fh3o;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.3.1;/opt/rocm-6.3.1;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.3.1;/opt/rocm-6.3.1;/opt/rocm-6.3.1" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.3.1/axom-develop-jx2n4ai2kjdjrg7o6oc6nrugd5kvsk24/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.3.1/axom-develop-jx2n4ai2kjdjrg7o6oc6nrugd5kvsk24/lib64;;/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1/axom-develop-7trkhc3ctsx3bgt6uv2brdgeeyalxomu/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1/axom-develop-7trkhc3ctsx3bgt6uv2brdgeeyalxomu/lib64;;/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.3.1/axom-develop-jx2n4ai2kjdjrg7o6oc6nrugd5kvsk24/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.3.1/axom-develop-jx2n4ai2kjdjrg7o6oc6nrugd5kvsk24/lib64;;/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1/axom-develop-7trkhc3ctsx3bgt6uv2brdgeeyalxomu/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1/axom-develop-7trkhc3ctsx3bgt6uv2brdgeeyalxomu/lib64;;/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") @@ -21,11 +21,11 @@ set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdclang" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdclang" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdclang++" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdclang++" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdflang" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdflang" CACHE PATH "") else() @@ -98,7 +98,7 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.3.1" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1" CACHE PATH "") set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-vnlbr73jbwhqyvh2q76csdtve3ekpncc" CACHE PATH "") @@ -150,12 +150,14 @@ set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-4sgpkalsntbmuim5oltb7ue4gggt6rv5/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj/lib/python3.13/site-packages" CACHE PATH "") set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-ij66enxl4hmrmwua5bsec2yslylneqbw/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/py-pluggy-1.6.0-27aoz44dedjxa6j6r6cavpqyqmpknidb/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/py-pluggy-1.6.0-27aoz44dedjxa6j6r6cavpqyqmpknidb/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/py-iniconfig-2.1.0-7rty7n4wq6kxxrzflm7wbxqzhhc6ph7q/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/py-iniconfig-2.1.0-7rty7n4wq6kxxrzflm7wbxqzhhc6ph7q/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-ukxrsa2wugnmhh3glq25muf5hqexaf62/lib/python3.13/site-packages" CACHE PATH "") diff --git a/host-configs/rzvernal-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake b/host-configs/rzvernal-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake index 8ce11caa53..cfe45cbeea 100644 --- a/host-configs/rzvernal-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake +++ b/host-configs/rzvernal-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake @@ -4,13 +4,13 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.29.2/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/blt-0.7.1-2eahqqwjyauupjq3baxftq7kuv3pvdgp;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/c2c-1.8.0-oxqhr2ybswpnzt74f4iritx263ji3uqy;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-sos5uklbsgaycb776a2ufcyje4y24yls;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/conduit-0.9.5-cxdq4qnoptjq6lry5asb2bqodvm3zjig;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/lua-5.4.8-lsey5vgxvpqf5j3xbrlzswrizuwm65vh;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/mfem-4.9.0-xwok5jck2jjg76k4575wkzgyvgopipz2;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/py-nanobind-2.7.0-3nchqpyclgle5w56it23ozl4ttbnrfjd;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/adiak-0.4.0-cbxp6kv6rm4f7oeczuhbplm3yb3zxb6z;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/libunwind-1.8.3-r4b3cptnf27zp34jbrdtlrf6r6dp6n4l;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/hdf5-1.8.23-wb7u6epuww3tretkcto7jhq2i7pffimg;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/parmetis-4.0.3-d6ed2qesb4yprvpetx3fso35rx6amf5b;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/py-numpy-2.4.2-t7cvohiksz7deeldwxrtqfmahcvanapb;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/hypre-2.27.0-pxqhlqwksiscco65tsz5somb4ctdq3q2;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-i5stpeet653njjadpjsekbvxlqkng2zn;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/umpire-2025.12.0-hpevb7stqsg6dltp3t2tcnzzps7tgywb;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/zlib-1.3.1-ijxa3tdiibemxtng46hrremmbytl2dqq;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/metis-5.1.0-mubwzuka5byhpbzmwaxjhtffhst3oj3q;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-j7my6ilwzwzvbaxzscbvishmj5mgyxnz;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/fmt-11.0.2-unmvs6vefcyfcfnzlie2eag5i2eib35d;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/blt-0.7.1-2eahqqwjyauupjq3baxftq7kuv3pvdgp;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/c2c-1.8.0-oxqhr2ybswpnzt74f4iritx263ji3uqy;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-sos5uklbsgaycb776a2ufcyje4y24yls;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/conduit-0.9.5-cxdq4qnoptjq6lry5asb2bqodvm3zjig;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/lua-5.4.8-lsey5vgxvpqf5j3xbrlzswrizuwm65vh;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/mfem-4.9.0-xwok5jck2jjg76k4575wkzgyvgopipz2;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/py-nanobind-2.7.0-3nchqpyclgle5w56it23ozl4ttbnrfjd;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/adiak-0.4.0-cbxp6kv6rm4f7oeczuhbplm3yb3zxb6z;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/libunwind-1.8.3-r4b3cptnf27zp34jbrdtlrf6r6dp6n4l;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/hdf5-1.8.23-wb7u6epuww3tretkcto7jhq2i7pffimg;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/parmetis-4.0.3-d6ed2qesb4yprvpetx3fso35rx6amf5b;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/py-mpi4py-4.1.1-kwpdge3wqmmxvxlocfdz4mgbc3zfihn6;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/py-numpy-2.4.2-t7cvohiksz7deeldwxrtqfmahcvanapb;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/hypre-2.27.0-pxqhlqwksiscco65tsz5somb4ctdq3q2;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-i5stpeet653njjadpjsekbvxlqkng2zn;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/umpire-2025.12.0-hpevb7stqsg6dltp3t2tcnzzps7tgywb;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/zlib-1.3.1-ijxa3tdiibemxtng46hrremmbytl2dqq;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/metis-5.1.0-mubwzuka5byhpbzmwaxjhtffhst3oj3q;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-j7my6ilwzwzvbaxzscbvishmj5mgyxnz;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/fmt-11.0.2-unmvs6vefcyfcfnzlie2eag5i2eib35d;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/axom-develop-j5cg6odzkrlra4lrnhap3qtdbfgnwvlk/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/axom-develop-j5cg6odzkrlra4lrnhap3qtdbfgnwvlk/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/axom-develop-6a3pinjancledu7q5pzdp5ykhictf5ej/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/axom-develop-6a3pinjancledu7q5pzdp5ykhictf5ej/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/axom-develop-j5cg6odzkrlra4lrnhap3qtdbfgnwvlk/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3/axom-develop-j5cg6odzkrlra4lrnhap3qtdbfgnwvlk/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/axom-develop-6a3pinjancledu7q5pzdp5ykhictf5ej/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/axom-develop-6a3pinjancledu7q5pzdp5ykhictf5ej/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") @@ -21,11 +21,11 @@ set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdclang" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdclang" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdclang++" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdclang++" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdflang" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdflang" CACHE PATH "") else() @@ -98,7 +98,7 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/llvm-amdgpu-6.4.3" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3" CACHE PATH "") set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-cxdq4qnoptjq6lry5asb2bqodvm3zjig" CACHE PATH "") @@ -150,12 +150,14 @@ set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-3nchqpyclgle5w56it23ozl4ttbnrfjd/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj/lib/python3.13/site-packages" CACHE PATH "") set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-t7cvohiksz7deeldwxrtqfmahcvanapb/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/py-pluggy-1.6.0-27aoz44dedjxa6j6r6cavpqyqmpknidb/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/py-pluggy-1.6.0-27aoz44dedjxa6j6r6cavpqyqmpknidb/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_13_07_25/none-none/py-iniconfig-2.1.0-7rty7n4wq6kxxrzflm7wbxqzhhc6ph7q/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/py-iniconfig-2.1.0-7rty7n4wq6kxxrzflm7wbxqzhhc6ph7q/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-kwpdge3wqmmxvxlocfdz4mgbc3zfihn6/lib/python3.13/site-packages" CACHE PATH "") diff --git a/host-configs/rzwhippet-toss_4_x86_64_ib-gcc@13.3.1.cmake b/host-configs/rzwhippet-toss_4_x86_64_ib-gcc@13.3.1.cmake index 8002b8980e..dab70400d2 100644 --- a/host-configs/rzwhippet-toss_4_x86_64_ib-gcc@13.3.1.cmake +++ b/host-configs/rzwhippet-toss_4_x86_64_ib-gcc@13.3.1.cmake @@ -4,28 +4,28 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.26.3/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/gcc-13.3.1/blt-0.7.1-yiu2olkumeazbrto7x4v2dv6qbefhf5a;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/gcc-13.3.1/c2c-1.8.0-jkclfdpl7igj3ur3ufnpt6dupjfvotpc;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/gcc-13.3.1/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-f4kbq52oexscd7dhlwdbffygha5losj5;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/gcc-13.3.1/conduit-0.9.5-xyt2d2ot4eozuhrjv7mccoe7tw6ef7ui;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/gcc-13.3.1/mfem-4.9.0-heih2kd5qpkalflltitlry3g4qpyx5xw;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/gcc-13.3.1/py-nanobind-2.7.0-szozdpwzge7ah4bsilit6fijezxqbfop;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/gcc-13.3.1/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-ktky5uf7luvuf4cnczifj5av5ndbajco;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/gcc-13.3.1/scr-3.0.1-tnom76yaaoeyob7dncuafxebkzf2fbv7;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/gcc-13.3.1/umpire-2025.12.0-m6o7ya6pnrpixhpygzywlzoue3zuuir3;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/gcc-13.3.1/adiak-0.4.0-i5bahteezqmswdv2ulpfvfogmuue463q;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/gcc-13.3.1/libunwind-1.8.3-pwdmczcnf7vugfly5zk7eztefnn7kv75;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/gcc-13.3.1/hdf5-1.8.23-2pnsuxpt6r2wmdc6sqyku6ki6na4gcfl;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/gcc-13.3.1/parmetis-4.0.3-tt2yxrbsrccyglhjqcs2y2nspmt4qoqx;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/gcc-13.3.1/py-numpy-2.4.2-z4yapdkn22w6adfrl7ibucqiokgrwb7f;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/gcc-13.3.1/hypre-2.27.0-vwir3t6mlgbmo3mgfbiayrpbbdxsrsha;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/gcc-13.3.1/axl-0.7.1-yoi3m7zjahalmglnbo33aitshq6cj43q;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/gcc-13.3.1/dtcmp-1.1.5-ipjglgrmgotna4yza37ecwiur3rkx6wf;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/gcc-13.3.1/er-0.3.0-bdvz66gxhiflazj3irsv5vleaphtypqz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/gcc-13.3.1/libyogrt-1.35-qwz3xpqmkwgh5byk2kiy3txbazfwwf3c;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/gcc-13.3.1/spath-0.2.0-pmsauxzx75wu4kyfwjf3ite5dzzaymhv;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/gcc-13.3.1/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-cc5noebugovl4o6ir2d65bx7qizwznef;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/gcc-13.3.1/fmt-11.0.2-ggfpxocredbjrldu7ar33cjedhmkm3da;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/gcc-13.3.1/metis-5.1.0-3jtvhjwnhqqaf5v2na7f675qgvlusyx3;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/gcc-13.3.1/lwgrp-1.0.6-lkotiz6t6q3r5nmgqcfhfvdyukzdnnuu;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/gcc-13.3.1/redset-0.2.0-2kb4kg3ypf7l66vxdt6x2z4oqvimt3bs;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/gcc-13.3.1/shuffile-0.2.0-v2buhpkgx2py4m4bny4lsflaoeqbmqof;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/gcc-13.3.1/rankstr-0.2.0-xkzr7o47vbxkwtxppil7a7i57dhlk5gf;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/gcc-13.3.1/kvtree-1.3.0-py6ds6lmww6eqlhjgb4vilk6ftkxsuud;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/none-none/gcc-runtime-13.3.1-zsobnkwck2glzcbukupgvgbiwwhdonys;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.26.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66xzj4wm5kfqxyc4vgox7gb3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/doxygen-1.15.0;/usr/tce/packages/gcc/gcc-13.3.1;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-gcc-13.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/blt-0.7.1-lj5bghgkodlzmfs75wimnoxuui2quc3a;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/c2c-1.8.0-tyixdhhwshu35d2vuh6gw5bk3b3k7nqz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-bzioyf6catu3cjwaz4cplt7ubtvavgbl;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/conduit-0.9.5-vlm6nb2rwwmvnxtnxw5p3o27rzsap6vt;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/mfem-4.9.0-xodkornev7gcvmi3idsoxtdsnlph4rqv;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/py-nanobind-2.7.0-e42etfacct5pxga65kryysakw52u4c63;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-o23ptcxtve2rphukiakbezevsssiqqmq;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/scr-3.0.1-ov4mwvpcd75glzu2lfuivq3oibr4vvpd;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/umpire-2025.12.0-drla577bebgu5wx6q7y37qxedmgmpcgi;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/adiak-0.4.0-nqiuo5ltc24jpawzi6mlp3xtmngcopiu;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/libunwind-1.8.3-a7zvz3euhmbhk4as6h6j66ytabzwztxb;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/hdf5-1.8.23-zyfoez4ztydoqfeubcamjypyhprjpuca;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/parmetis-4.0.3-sxnyn5chjyiyowzk32dsynslufrofyho;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/py-mpi4py-4.1.1-l3h5jmmg773bd5oer7makte3ln6rn7xv;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/py-numpy-2.4.2-to6o5czwkczlh46d6gkubixw5bhzxakl;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/hypre-2.27.0-qvi4rtn4tbrduxkdpfugf6f5lkzirnel;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/axl-0.7.1-ljgukulclybf6cdbcj7u3w7o63v43zcg;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/dtcmp-1.1.5-taj5rsqbrki5hk2fg5pxkg6xsz5tfjxk;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/er-0.3.0-ozpsif2qsi7or56avv6fxwn3at2hrhli;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/libyogrt-1.35-y4nvgwhpokw2x6lekbubcmxv5wweidg4;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/spath-0.2.0-5zx5r6jnfiqbfh2hfkzvuvqmam6j67dt;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-kjufdwlliz23qzwoy2xygn3w6dsq6tec;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/fmt-11.0.2-5wsybhdkos4xu5i7thboag6245lzc6jj;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/metis-5.1.0-3qhomb7zt2n7nh46kmc2egkzmcd3tafm;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/lwgrp-1.0.6-e2smb3vzymlqmy35nuipgi6cv6o2ezyz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/redset-0.2.0-itxhprv2jiz4uw4oa42jribcmnc6z2xr;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/shuffile-0.2.0-jpcg5fljyjaf3sjmnasiiv4brhw47vcc;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/rankstr-0.2.0-dpmjoa5upfcwzpbvxmhrbgupiqtu4uoz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/kvtree-1.3.0-3tlf6x5raibwnal2uo2l7feyjrqmkrhd;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/gcc-runtime-13.3.1-xlvjd7vcdngtnvtxcdyqwtquvenekynf;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.26.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66xzj4wm5kfqxyc4vgox7gb3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/doxygen-1.15.0;/usr/tce/packages/gcc/gcc-13.3.1;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-gcc-13.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/gcc-13.3.1/axom-develop-5bv7dhwiz6x5ef65d6tovswgfdjtty2l/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/gcc-13.3.1/axom-develop-5bv7dhwiz6x5ef65d6tovswgfdjtty2l/lib64;;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/axom-develop-vfi5qherug7c2n7iyqkyclwbajst4kps/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/axom-develop-vfi5qherug7c2n7iyqkyclwbajst4kps/lib64;;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/gcc-13.3.1/axom-develop-5bv7dhwiz6x5ef65d6tovswgfdjtty2l/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/gcc-13.3.1/axom-develop-5bv7dhwiz6x5ef65d6tovswgfdjtty2l/lib64;;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/axom-develop-vfi5qherug7c2n7iyqkyclwbajst4kps/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/axom-develop-vfi5qherug7c2n7iyqkyclwbajst4kps/lib64;;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ # Compilers #------------------------------------------------------------------------------ -# Compiler Spec: gcc@13.3.1/xrm3n2pex2en2wvm6p4ap62nieoz7kne +# Compiler Spec: gcc@13.3.1/zbmuyoury7g5npf6fa7lwwxbjninyheh #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/gcc" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/gcc" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/g++" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/g++" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/gfortran" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/gfortran" CACHE PATH "") else() @@ -37,6 +37,10 @@ else() endif() +set(CMAKE_C_FLAGS "-fPIC" CACHE STRING "") + +set(CMAKE_CXX_FLAGS "-fPIC" CACHE STRING "") + set(CMAKE_Fortran_FLAGS "-fPIC" CACHE STRING "") set(ENABLE_FORTRAN ON CACHE BOOL "") @@ -73,51 +77,51 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/gcc-13.3.1" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1" CACHE PATH "") -set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-xyt2d2ot4eozuhrjv7mccoe7tw6ef7ui" CACHE PATH "") +set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-vlm6nb2rwwmvnxtnxw5p3o27rzsap6vt" CACHE PATH "") -set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-jkclfdpl7igj3ur3ufnpt6dupjfvotpc" CACHE PATH "") +set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-tyixdhhwshu35d2vuh6gw5bk3b3k7nqz" CACHE PATH "") -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-heih2kd5qpkalflltitlry3g4qpyx5xw" CACHE PATH "") +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-xodkornev7gcvmi3idsoxtdsnlph4rqv" CACHE PATH "") -set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-2pnsuxpt6r2wmdc6sqyku6ki6na4gcfl" CACHE PATH "") +set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-zyfoez4ztydoqfeubcamjypyhprjpuca" CACHE PATH "") set(LUA_DIR "/usr" CACHE PATH "") -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-ktky5uf7luvuf4cnczifj5av5ndbajco" CACHE PATH "") +set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-o23ptcxtve2rphukiakbezevsssiqqmq" CACHE PATH "") -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-m6o7ya6pnrpixhpygzywlzoue3zuuir3" CACHE PATH "") +set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-drla577bebgu5wx6q7y37qxedmgmpcgi" CACHE PATH "") # OPENCASCADE not built -set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-i5bahteezqmswdv2ulpfvfogmuue463q" CACHE PATH "") +set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-nqiuo5ltc24jpawzi6mlp3xtmngcopiu" CACHE PATH "") -set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-f4kbq52oexscd7dhlwdbffygha5losj5" CACHE PATH "") +set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-bzioyf6catu3cjwaz4cplt7ubtvavgbl" CACHE PATH "") -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-cc5noebugovl4o6ir2d65bx7qizwznef" CACHE PATH "") +set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-kjufdwlliz23qzwoy2xygn3w6dsq6tec" CACHE PATH "") -set(SCR_DIR "${TPL_ROOT}/scr-3.0.1-tnom76yaaoeyob7dncuafxebkzf2fbv7" CACHE PATH "") +set(SCR_DIR "${TPL_ROOT}/scr-3.0.1-ov4mwvpcd75glzu2lfuivq3oibr4vvpd" CACHE PATH "") -set(KVTREE_DIR "${TPL_ROOT}/kvtree-1.3.0-py6ds6lmww6eqlhjgb4vilk6ftkxsuud" CACHE PATH "") +set(KVTREE_DIR "${TPL_ROOT}/kvtree-1.3.0-3tlf6x5raibwnal2uo2l7feyjrqmkrhd" CACHE PATH "") -set(DTCMP_DIR "${TPL_ROOT}/dtcmp-1.1.5-ipjglgrmgotna4yza37ecwiur3rkx6wf" CACHE PATH "") +set(DTCMP_DIR "${TPL_ROOT}/dtcmp-1.1.5-taj5rsqbrki5hk2fg5pxkg6xsz5tfjxk" CACHE PATH "") -set(SPATH_DIR "${TPL_ROOT}/spath-0.2.0-pmsauxzx75wu4kyfwjf3ite5dzzaymhv" CACHE PATH "") +set(SPATH_DIR "${TPL_ROOT}/spath-0.2.0-5zx5r6jnfiqbfh2hfkzvuvqmam6j67dt" CACHE PATH "") -set(AXL_DIR "${TPL_ROOT}/axl-0.7.1-yoi3m7zjahalmglnbo33aitshq6cj43q" CACHE PATH "") +set(AXL_DIR "${TPL_ROOT}/axl-0.7.1-ljgukulclybf6cdbcj7u3w7o63v43zcg" CACHE PATH "") -set(LWGRP_DIR "${TPL_ROOT}/lwgrp-1.0.6-lkotiz6t6q3r5nmgqcfhfvdyukzdnnuu" CACHE PATH "") +set(LWGRP_DIR "${TPL_ROOT}/lwgrp-1.0.6-e2smb3vzymlqmy35nuipgi6cv6o2ezyz" CACHE PATH "") -set(ER_DIR "${TPL_ROOT}/er-0.3.0-bdvz66gxhiflazj3irsv5vleaphtypqz" CACHE PATH "") +set(ER_DIR "${TPL_ROOT}/er-0.3.0-ozpsif2qsi7or56avv6fxwn3at2hrhli" CACHE PATH "") -set(RANKSTR_DIR "${TPL_ROOT}/rankstr-0.2.0-xkzr7o47vbxkwtxppil7a7i57dhlk5gf" CACHE PATH "") +set(RANKSTR_DIR "${TPL_ROOT}/rankstr-0.2.0-dpmjoa5upfcwzpbvxmhrbgupiqtu4uoz" CACHE PATH "") -set(REDSET_DIR "${TPL_ROOT}/redset-0.2.0-2kb4kg3ypf7l66vxdt6x2z4oqvimt3bs" CACHE PATH "") +set(REDSET_DIR "${TPL_ROOT}/redset-0.2.0-itxhprv2jiz4uw4oa42jribcmnc6z2xr" CACHE PATH "") -set(SHUFFILE_DIR "${TPL_ROOT}/shuffile-0.2.0-v2buhpkgx2py4m4bny4lsflaoeqbmqof" CACHE PATH "") +set(SHUFFILE_DIR "${TPL_ROOT}/shuffile-0.2.0-jpcg5fljyjaf3sjmnasiiv4brhw47vcc" CACHE PATH "") -set(LIBYOGRT_DIR "${TPL_ROOT}/libyogrt-1.35-qwz3xpqmkwgh5byk2kiy3txbazfwwf3c" CACHE PATH "") +set(LIBYOGRT_DIR "${TPL_ROOT}/libyogrt-1.35-y4nvgwhpokw2x6lekbubcmxv5wweidg4" CACHE PATH "") #------------------------------------------------------------------------------ # Devtools & Python @@ -143,14 +147,16 @@ set(CPPCHECK_EXECUTABLE "${DEVTOOLS_ROOT}/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66x set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/kky5oskfhacihjb5gpy7qkeh6edlrtcb/doxygen-1.15.0/bin/doxygen" CACHE PATH "") -set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-szozdpwzge7ah4bsilit6fijezxqbfop/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-e42etfacct5pxga65kryysakw52u4c63/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-to6o5czwkczlh46d6gkubixw5bhzxakl/lib/python3.13/site-packages" CACHE PATH "") -set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-z4yapdkn22w6adfrl7ibucqiokgrwb7f/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/py-pluggy-1.6.0-6zab4pck6v7johubbhalr2waowbwpjei/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/none-none/py-pluggy-1.6.0-6zab4pck6v7johubbhalr2waowbwpjei/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/py-iniconfig-2.1.0-xwmbxuhk2oo3dcuizdp3z4hqbpqykkom/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/none-none/py-iniconfig-2.1.0-xwmbxuhk2oo3dcuizdp3z4hqbpqykkom/lib/python3.13/site-packages" CACHE PATH "") +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-l3h5jmmg773bd5oer7makte3ln6rn7xv/lib/python3.13/site-packages" CACHE PATH "") diff --git a/host-configs/rzwhippet-toss_4_x86_64_ib-intel-oneapi-compilers@2025.2.0.cmake b/host-configs/rzwhippet-toss_4_x86_64_ib-intel-oneapi-compilers@2025.2.0.cmake index 532384bd0a..67622cf4c5 100644 --- a/host-configs/rzwhippet-toss_4_x86_64_ib-intel-oneapi-compilers@2025.2.0.cmake +++ b/host-configs/rzwhippet-toss_4_x86_64_ib-intel-oneapi-compilers@2025.2.0.cmake @@ -4,28 +4,28 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.26.3/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/intel-oneapi-compilers-2025.2.0/blt-0.7.1-2n76efajatydczwltuk6plxpgoyvu6jj;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/intel-oneapi-compilers-2025.2.0/c2c-1.8.0-rl42nhoa33aux3tluna3bftevqokiiqc;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/intel-oneapi-compilers-2025.2.0/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-ti2c3tm4pc6rma2cedok5hy5vnpxmsj7;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/intel-oneapi-compilers-2025.2.0/conduit-0.9.5-x6snw6rzqee7bhmsc6rezi3oobsb6tba;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/intel-oneapi-compilers-2025.2.0/mfem-4.9.0-exbr44mwmmbii764bvxrgct2ivffacjh;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/gcc-13.3.1/py-nanobind-2.7.0-zg2fpeevizaaykxxnp4butsrujlb54px;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/intel-oneapi-compilers-2025.2.0/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-jz235wsh3nhxjuzqxse57hcojia7x4mj;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/intel-oneapi-compilers-2025.2.0/umpire-2025.12.0-2eti2r3wjt4qs772djlcgf6axs44qmko;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/intel-oneapi-compilers-2025.2.0/adiak-0.4.0-rgjvxwzjod55ialozetvkjyi3hjczviy;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/intel-oneapi-compilers-2025.2.0/libunwind-1.8.3-h42c2h7kzkiaeq63dzvt32wlhcqbcrra;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/intel-oneapi-compilers-2025.2.0/hdf5-1.8.23-74fr7atqhw4jlzxt22xi5szeqpoxcwkh;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/intel-oneapi-compilers-2025.2.0/parmetis-4.0.3-g7bp5ds3u3laspneww3scyujjwebgdzh;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/gcc-13.3.1/py-numpy-2.4.2-z4yapdkn22w6adfrl7ibucqiokgrwb7f;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/intel-oneapi-compilers-2025.2.0/hypre-2.27.0-hgyyzv6j2qosdblkfnupzwgwmujcrtse;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/intel-oneapi-compilers-2025.2.0/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-2gq3ddrhfxqlgzr6zxbvmwpzvtqxqj25;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/intel-oneapi-compilers-2025.2.0/fmt-11.0.2-orofhwltz3p5ckiw2t7gzhkgiifetwjt;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/intel-oneapi-compilers-2025.2.0/metis-5.1.0-zschch2gorb5k7t4rmrtc3aubm663i6u;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/none-none/intel-oneapi-runtime-2025.2.0-mfk4ezunnyprzpfps5pncx2nhttsa4z3;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/none-none/gcc-runtime-13.3.1-zsobnkwck2glzcbukupgvgbiwwhdonys;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.26.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66xzj4wm5kfqxyc4vgox7gb3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/doxygen-1.15.0;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/intel-oneapi-compilers-2025.2.0-ngszoacyp2i5d5nawitlyuh37jswjtie/compiler/2025.2;/usr/tce/packages/intel/intel-2025.2.0;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-intel-2025.2.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/intel-oneapi-compilers-2025.2.0/blt-0.7.1-fnswc6mwjgunle3ushrnp3e6w6s25qra;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/intel-oneapi-compilers-2025.2.0/c2c-1.8.0-fdbbv7fzmylpoctjgdai2kc4xfcz2vqt;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/intel-oneapi-compilers-2025.2.0/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-bheefndoamg47jey4mbq2ofgflz7kvz4;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/intel-oneapi-compilers-2025.2.0/conduit-0.9.5-lqjx3vlbi5prcmle2gsgc7yopbglp3ab;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/intel-oneapi-compilers-2025.2.0/mfem-4.9.0-npcozwxpczgzuya2z6iailvycmbbc6nd;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/py-nanobind-2.7.0-iky6agz5lprxpnf2nffxwgqcgiagqiz5;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/intel-oneapi-compilers-2025.2.0/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-ppbrbxztecp4363lchwihhymqrhv75un;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/intel-oneapi-compilers-2025.2.0/umpire-2025.12.0-tdpnmcmwvlz77qwdeupgufa6s5aswwp5;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/intel-oneapi-compilers-2025.2.0/adiak-0.4.0-gxozhembb6br5wkhuxetug25ndaodstk;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/intel-oneapi-compilers-2025.2.0/libunwind-1.8.3-lgvf5mwbhqt4is2gg3uuafuyxv35lwd7;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/intel-oneapi-compilers-2025.2.0/hdf5-1.8.23-zhxj7u6hk266vklzloqcp3qlahtbta4l;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/intel-oneapi-compilers-2025.2.0/parmetis-4.0.3-dred3k4i2un2bw3w4st5pl2zvpvpfr37;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/intel-oneapi-compilers-2025.2.0/py-mpi4py-4.1.1-7mymbwx2ifuqlotydf6dx6hd6wmz4xhd;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/py-numpy-2.4.2-to6o5czwkczlh46d6gkubixw5bhzxakl;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/intel-oneapi-compilers-2025.2.0/hypre-2.27.0-jtpmchw5qcrtezcac6mp53wlxcncp5ne;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/intel-oneapi-compilers-2025.2.0/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-k4dewbo7oilgt6zjhkze2hyqyc6xn3if;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/intel-oneapi-compilers-2025.2.0/fmt-11.0.2-6ut7qqyjzqqbc6ggi4o7jthpsofhhu65;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/intel-oneapi-compilers-2025.2.0/metis-5.1.0-psuyihfrmzzgjxljxkoqbaqdjs53253r;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/intel-oneapi-runtime-2025.2.0-3ym7ceyrw63moiirt4tqoqubxhkiqv63;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/gcc-runtime-13.3.1-xlvjd7vcdngtnvtxcdyqwtquvenekynf;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.26.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66xzj4wm5kfqxyc4vgox7gb3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/doxygen-1.15.0;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/intel-oneapi-compilers-2025.2.0-ngszoacyp2i5d5nawitlyuh37jswjtie/compiler/2025.2;/usr/tce/packages/intel/intel-2025.2.0;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-intel-2025.2.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/intel-oneapi-compilers-2025.2.0/axom-develop-3l4k45i3jli4otfuyze65smrd7vj43iz/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/intel-oneapi-compilers-2025.2.0/axom-develop-3l4k45i3jli4otfuyze65smrd7vj43iz/lib64;;/usr/tce/packages/intel/intel-2025.2.0/compiler/2025.2/lib;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/intel-oneapi-compilers-2025.2.0-ngszoacyp2i5d5nawitlyuh37jswjtie/compiler/2025.2/lib;/usr/tce/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/intel-oneapi-compilers-2025.2.0/axom-develop-g2h45edliw4q2vgz7cfusag64p3ulopb/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/intel-oneapi-compilers-2025.2.0/axom-develop-g2h45edliw4q2vgz7cfusag64p3ulopb/lib64;;/usr/tce/packages/intel/intel-2025.2.0/compiler/2025.2/lib;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/intel-oneapi-compilers-2025.2.0-ngszoacyp2i5d5nawitlyuh37jswjtie/compiler/2025.2/lib;/usr/tce/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/intel-oneapi-compilers-2025.2.0/axom-develop-3l4k45i3jli4otfuyze65smrd7vj43iz/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/intel-oneapi-compilers-2025.2.0/axom-develop-3l4k45i3jli4otfuyze65smrd7vj43iz/lib64;;/usr/tce/packages/intel/intel-2025.2.0/compiler/2025.2/lib;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/intel-oneapi-compilers-2025.2.0-ngszoacyp2i5d5nawitlyuh37jswjtie/compiler/2025.2/lib;/usr/tce/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/intel-oneapi-compilers-2025.2.0/axom-develop-g2h45edliw4q2vgz7cfusag64p3ulopb/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/intel-oneapi-compilers-2025.2.0/axom-develop-g2h45edliw4q2vgz7cfusag64p3ulopb/lib64;;/usr/tce/packages/intel/intel-2025.2.0/compiler/2025.2/lib;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/intel-oneapi-compilers-2025.2.0-ngszoacyp2i5d5nawitlyuh37jswjtie/compiler/2025.2/lib;/usr/tce/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ # Compilers #------------------------------------------------------------------------------ -# Compiler Spec: intel-oneapi-compilers@2025.2.0/pfgmabeqlm6quu3niafr24bgm7vzeeug +# Compiler Spec: intel-oneapi-compilers@2025.2.0/pure57vovckospvgxd2lr56fpsa636dr #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/oneapi/icx" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/oneapi/icx" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/oneapi/icpx" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/oneapi/icpx" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/oneapi/ifx" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/oneapi/ifx" CACHE PATH "") else() @@ -37,12 +37,14 @@ else() endif() +set(CMAKE_C_FLAGS "-fPIC" CACHE STRING "") + +set(CMAKE_CXX_FLAGS "-fPIC -fp-model=precise" CACHE STRING "") + set(CMAKE_Fortran_FLAGS "-fPIC" CACHE STRING "") set(ENABLE_FORTRAN ON CACHE BOOL "") -set(CMAKE_CXX_FLAGS "-fp-model=precise" CACHE STRING "") - set(CMAKE_CXX_FLAGS_DEBUG "-g -Rno-debug-disables-optimization" CACHE STRING "") #------------------------------------------------------------------------------ @@ -77,29 +79,29 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/intel-oneapi-compilers-2025.2.0" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/intel-oneapi-compilers-2025.2.0" CACHE PATH "") -set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-x6snw6rzqee7bhmsc6rezi3oobsb6tba" CACHE PATH "") +set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-lqjx3vlbi5prcmle2gsgc7yopbglp3ab" CACHE PATH "") -set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-rl42nhoa33aux3tluna3bftevqokiiqc" CACHE PATH "") +set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-fdbbv7fzmylpoctjgdai2kc4xfcz2vqt" CACHE PATH "") -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-exbr44mwmmbii764bvxrgct2ivffacjh" CACHE PATH "") +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-npcozwxpczgzuya2z6iailvycmbbc6nd" CACHE PATH "") -set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-74fr7atqhw4jlzxt22xi5szeqpoxcwkh" CACHE PATH "") +set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-zhxj7u6hk266vklzloqcp3qlahtbta4l" CACHE PATH "") set(LUA_DIR "/usr" CACHE PATH "") -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-jz235wsh3nhxjuzqxse57hcojia7x4mj" CACHE PATH "") +set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-ppbrbxztecp4363lchwihhymqrhv75un" CACHE PATH "") -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-2eti2r3wjt4qs772djlcgf6axs44qmko" CACHE PATH "") +set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-tdpnmcmwvlz77qwdeupgufa6s5aswwp5" CACHE PATH "") # OPENCASCADE not built -set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-rgjvxwzjod55ialozetvkjyi3hjczviy" CACHE PATH "") +set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-gxozhembb6br5wkhuxetug25ndaodstk" CACHE PATH "") -set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-ti2c3tm4pc6rma2cedok5hy5vnpxmsj7" CACHE PATH "") +set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-bheefndoamg47jey4mbq2ofgflz7kvz4" CACHE PATH "") -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-2gq3ddrhfxqlgzr6zxbvmwpzvtqxqj25" CACHE PATH "") +set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-k4dewbo7oilgt6zjhkze2hyqyc6xn3if" CACHE PATH "") # scr not built @@ -127,14 +129,16 @@ set(CPPCHECK_EXECUTABLE "${DEVTOOLS_ROOT}/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66x set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/kky5oskfhacihjb5gpy7qkeh6edlrtcb/doxygen-1.15.0/bin/doxygen" CACHE PATH "") -set(PY_NANOBIND_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/gcc-13.3.1/py-nanobind-2.7.0-zg2fpeevizaaykxxnp4butsrujlb54px/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NANOBIND_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/py-nanobind-2.7.0-iky6agz5lprxpnf2nffxwgqcgiagqiz5/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NUMPY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/py-numpy-2.4.2-to6o5czwkczlh46d6gkubixw5bhzxakl/lib/python3.13/site-packages" CACHE PATH "") -set(PY_NUMPY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/gcc-13.3.1/py-numpy-2.4.2-z4yapdkn22w6adfrl7ibucqiokgrwb7f/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/py-pluggy-1.6.0-6zab4pck6v7johubbhalr2waowbwpjei/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/none-none/py-pluggy-1.6.0-6zab4pck6v7johubbhalr2waowbwpjei/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/py-iniconfig-2.1.0-xwmbxuhk2oo3dcuizdp3z4hqbpqykkom/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_07_34_43/none-none/py-iniconfig-2.1.0-xwmbxuhk2oo3dcuizdp3z4hqbpqykkom/lib/python3.13/site-packages" CACHE PATH "") +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-7mymbwx2ifuqlotydf6dx6hd6wmz4xhd/lib/python3.13/site-packages" CACHE PATH "") diff --git a/host-configs/rzwhippet-toss_4_x86_64_ib-llvm@19.1.3.cmake b/host-configs/rzwhippet-toss_4_x86_64_ib-llvm@19.1.3.cmake index 72b4bda224..1584af7a82 100644 --- a/host-configs/rzwhippet-toss_4_x86_64_ib-llvm@19.1.3.cmake +++ b/host-configs/rzwhippet-toss_4_x86_64_ib-llvm@19.1.3.cmake @@ -4,28 +4,28 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.26.3/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/llvm-19.1.3/blt-0.7.1-577uis2w7b3ipf4v4v6y6vogmy52d6ay;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/llvm-19.1.3/c2c-1.8.0-pk3z5zabg2gtkur2c4mqzc5zz7z5f2tv;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/llvm-19.1.3/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-b7lmqorkofkrib5foi4fzbvd3eh2tbkj;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/llvm-19.1.3/conduit-0.9.5-gracxngtjim7b6zjsb5z4kf7qk347eo2;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/llvm-19.1.3/mfem-4.9.0-yseuuwvugp6qd45xmxp3ok67ibvfzkff;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/llvm-19.1.3/opencascade-7.8.1-6fd4qesv7tghklsteqoqe2mbvayuwgsa;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/llvm-19.1.3/py-nanobind-2.7.0-6akjhhlga6h3jx75swovfvo76k6rw6fp;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/llvm-19.1.3/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-7e5u3r4qt7erhbp67xbqnywbgobidj54;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/llvm-19.1.3/scr-3.0.1-t3vscu7yv2wkvgff46iwigx6fof72kws;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/llvm-19.1.3/umpire-2025.12.0-zbrxcsg5vgv63mpro6ar2rhm7ejx24h5;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/llvm-19.1.3/adiak-0.4.0-27bezbcpd5jxnvs65h3m6u2aheplxbmv;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/llvm-19.1.3/libunwind-1.8.3-ddv7ypujzw2wvlzabw3d42qygcbtofuq;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/llvm-19.1.3/hdf5-1.8.23-b37o2ze3w5sozwz6jmalg4pgg66jcogm;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/llvm-19.1.3/parmetis-4.0.3-slhcesf5fhsucwxtlxxana67jcrligfy;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/llvm-19.1.3/py-numpy-2.4.2-7qsjxyuuh7rh5hccdziewhwqymkmepb3;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/llvm-19.1.3/hypre-2.27.0-qkqgpzdh6t7s2pakhss6qnikhwfltykn;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/none-none/glx-1.4-4kahrsr5vciz372vpdvlmvxahspecwif;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/llvm-19.1.3/tcl-8.6.17-yk7vfdrbz2n7cr2mc3j33ehvukl3kkbz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/llvm-19.1.3/axl-0.7.1-pbcj3g7dqmuiogpb2mawnyuqiyng2vh7;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/llvm-19.1.3/dtcmp-1.1.5-cto5znftnfq2kdwpunaegg6trpraj4ke;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/llvm-19.1.3/er-0.3.0-4xpuy3lnjbc5ii2peposyxlve7exzijq;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/llvm-19.1.3/libyogrt-1.35-n3wzi44wyx5i63jyrn7mozyiec6nake3;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/llvm-19.1.3/spath-0.2.0-owqpu5cpinfj56adgrwimokhecl37hdp;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/llvm-19.1.3/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-ck62ywjq7i3hn5utmvio42ifdegjxgma;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/llvm-19.1.3/fmt-11.0.2-uougkyc3rcyd7punvwjtcme2zlhehpgz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/llvm-19.1.3/metis-5.1.0-3jeshrn7tydsgn5zdkf3iirdhw3u3oeq;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/llvm-19.1.3/lwgrp-1.0.6-fvf5w43ygbspsrft2gbc4tbrgamobo6n;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/llvm-19.1.3/redset-0.2.0-ou4lf63bu5chfljcdkaot6feubhmedoh;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/llvm-19.1.3/shuffile-0.2.0-mbm3htxivpgso6ogxf5jzdsjhzwqltnm;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/none-none/gcc-runtime-13.3.1-zsobnkwck2glzcbukupgvgbiwwhdonys;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/llvm-19.1.3/rankstr-0.2.0-5a5trmqremlbnqljjn6u7cqndylpzuhw;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/llvm-19.1.3/kvtree-1.3.0-hoxrqia25kplgu63o6275e3phsxgldfr;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.26.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66xzj4wm5kfqxyc4vgox7gb3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/doxygen-1.15.0;/usr/tce/packages/gcc/gcc-13.3.1;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-clang-19.1.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/blt-0.7.1-ux6wzebtj6vtgnayf4gqv2rr62ulwwfc;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/c2c-1.8.0-wto5u3qsffjhbxirygr7kqqjlg55pyv6;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-iuatdlntm2zcg2vuygvsc7say6wfx353;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/conduit-0.9.5-vqo2x2n6mz2vs4eg7kynwmlpeolsuim5;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/mfem-4.9.0-jqqjlgxdiebyz7gblihglbgu2jbnqnqc;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/opencascade-7.8.1-pyde26mlsvvcvw75lxvoeshe6r6aobxe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/py-nanobind-2.7.0-zxodixu5egwgeorhpe3ciggwsv3ndfxb;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-ur2yappntfb4cptytvwwwtmsf7irqgxa;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/scr-3.0.1-l7heipi5ftfkbxqiytyxr4legakgjzq2;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/umpire-2025.12.0-l3l75r2uuai7rl3m4e7fpdw446ankcxp;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/adiak-0.4.0-do3kop54yldi4hz43usrtnx2zyhvwfsv;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/libunwind-1.8.3-ck74b2escwzf2xtpiafdvqwmat26uuoe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/hdf5-1.8.23-vpibouztpyzmgikg4dg2egwf2u53mmpl;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/parmetis-4.0.3-fs7gps55rp6znnpz3b4zkd35jjkfycfc;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/py-mpi4py-4.1.1-hrvj5zyy3xo2rbzvbz4ed7kjye7ssgmk;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/py-numpy-2.4.2-73lpwcid33wlekelrxoh24535lzexwwg;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/hypre-2.27.0-zef2jav5gijraygsr2tumdy32outkfxp;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/glx-1.4-4kahrsr5vciz372vpdvlmvxahspecwif;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/tcl-8.6.17-qjflnn6itwshm5i6ckchochc7t2cuw6w;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/axl-0.7.1-z4zopytuytxgn5wds3bnhqpqmfju54h7;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/dtcmp-1.1.5-b6g6n4hdju6ol5g2pcizzey7dthbgapt;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/er-0.3.0-eabe4roqqpiyjenasayt6pupercipzak;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/libyogrt-1.35-5geyvlylxzqsc6g6s6xiq6aakvy2qiu7;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/spath-0.2.0-6sxvboh2q5kmo7q5tcchgao2dgx5g72q;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-omiy5m3jikdlf6gicad34w5vkfz7huex;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/fmt-11.0.2-ekc2idnbqipl2pqb4nugxoa7sj7aigpo;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/metis-5.1.0-7gsm27yycsippuh2l4fwei4dtvevbqgh;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/lwgrp-1.0.6-u4qqk5djarvhyvk3fg7ls4r6ltx4anmr;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/redset-0.2.0-5b3fl74qgvvfturxdgz44rurhjdm4en4;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/shuffile-0.2.0-tpvuy3dcxpakepz77nxygcb7cqjwtwmv;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/gcc-runtime-13.3.1-xlvjd7vcdngtnvtxcdyqwtquvenekynf;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/rankstr-0.2.0-njavwb2qlbesemqc6ac7hjeezjxtydv7;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/kvtree-1.3.0-l3jatuuigf2z2swzxrdzkbrxhdinaqfz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.26.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66xzj4wm5kfqxyc4vgox7gb3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/doxygen-1.15.0;/usr/tce/packages/gcc/gcc-13.3.1;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-clang-19.1.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/llvm-19.1.3/axom-develop-k6ul4e5lvhjk7rvfh2yfz7xncpqxktcq/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/llvm-19.1.3/axom-develop-k6ul4e5lvhjk7rvfh2yfz7xncpqxktcq/lib64;;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/llvm-19.1.3-gy2lu5xbi4csr2k47emlajzfs5mlsd4g/lib/x86_64-unknown-linux-gnu;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13;/usr/tce/packages/clang/clang-19.1.3/lib;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/axom-develop-oab3m23sasflxabp3rqhmbk2cxsnxv4t/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/axom-develop-oab3m23sasflxabp3rqhmbk2cxsnxv4t/lib64;;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/llvm-19.1.3-gy2lu5xbi4csr2k47emlajzfs5mlsd4g/lib/x86_64-unknown-linux-gnu;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13;/usr/tce/packages/clang/clang-19.1.3/lib;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/llvm-19.1.3/axom-develop-k6ul4e5lvhjk7rvfh2yfz7xncpqxktcq/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/llvm-19.1.3/axom-develop-k6ul4e5lvhjk7rvfh2yfz7xncpqxktcq/lib64;;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/llvm-19.1.3-gy2lu5xbi4csr2k47emlajzfs5mlsd4g/lib/x86_64-unknown-linux-gnu;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13;/usr/tce/packages/clang/clang-19.1.3/lib;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/axom-develop-oab3m23sasflxabp3rqhmbk2cxsnxv4t/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/axom-develop-oab3m23sasflxabp3rqhmbk2cxsnxv4t/lib64;;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/llvm-19.1.3-gy2lu5xbi4csr2k47emlajzfs5mlsd4g/lib/x86_64-unknown-linux-gnu;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13;/usr/tce/packages/clang/clang-19.1.3/lib;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ # Compilers #------------------------------------------------------------------------------ -# Compiler Spec: llvm@19.1.3/4rf6d7atkulu6wz7bucycjwtiuwqfy24 +# Compiler Spec: llvm@19.1.3/m2tcgbxfcdjwdmyrkqwamlljh4qeiie2 #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/clang/clang" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/clang/clang" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/clang/clang++" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/clang/clang++" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/gfortran" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/gfortran" CACHE PATH "") else() @@ -37,6 +37,10 @@ else() endif() +set(CMAKE_C_FLAGS "-fPIC" CACHE STRING "") + +set(CMAKE_CXX_FLAGS "-fPIC" CACHE STRING "") + set(CMAKE_Fortran_FLAGS "-fPIC" CACHE STRING "") set(ENABLE_FORTRAN ON CACHE BOOL "") @@ -75,51 +79,51 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/llvm-19.1.3" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3" CACHE PATH "") -set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-gracxngtjim7b6zjsb5z4kf7qk347eo2" CACHE PATH "") +set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-vqo2x2n6mz2vs4eg7kynwmlpeolsuim5" CACHE PATH "") -set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-pk3z5zabg2gtkur2c4mqzc5zz7z5f2tv" CACHE PATH "") +set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-wto5u3qsffjhbxirygr7kqqjlg55pyv6" CACHE PATH "") -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-yseuuwvugp6qd45xmxp3ok67ibvfzkff" CACHE PATH "") +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-jqqjlgxdiebyz7gblihglbgu2jbnqnqc" CACHE PATH "") -set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-b37o2ze3w5sozwz6jmalg4pgg66jcogm" CACHE PATH "") +set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-vpibouztpyzmgikg4dg2egwf2u53mmpl" CACHE PATH "") set(LUA_DIR "/usr" CACHE PATH "") -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-7e5u3r4qt7erhbp67xbqnywbgobidj54" CACHE PATH "") +set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-ur2yappntfb4cptytvwwwtmsf7irqgxa" CACHE PATH "") -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-zbrxcsg5vgv63mpro6ar2rhm7ejx24h5" CACHE PATH "") +set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-l3l75r2uuai7rl3m4e7fpdw446ankcxp" CACHE PATH "") -set(OPENCASCADE_DIR "${TPL_ROOT}/opencascade-7.8.1-6fd4qesv7tghklsteqoqe2mbvayuwgsa" CACHE PATH "") +set(OPENCASCADE_DIR "${TPL_ROOT}/opencascade-7.8.1-pyde26mlsvvcvw75lxvoeshe6r6aobxe" CACHE PATH "") -set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-27bezbcpd5jxnvs65h3m6u2aheplxbmv" CACHE PATH "") +set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-do3kop54yldi4hz43usrtnx2zyhvwfsv" CACHE PATH "") -set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-b7lmqorkofkrib5foi4fzbvd3eh2tbkj" CACHE PATH "") +set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-iuatdlntm2zcg2vuygvsc7say6wfx353" CACHE PATH "") -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-ck62ywjq7i3hn5utmvio42ifdegjxgma" CACHE PATH "") +set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-omiy5m3jikdlf6gicad34w5vkfz7huex" CACHE PATH "") -set(SCR_DIR "${TPL_ROOT}/scr-3.0.1-t3vscu7yv2wkvgff46iwigx6fof72kws" CACHE PATH "") +set(SCR_DIR "${TPL_ROOT}/scr-3.0.1-l7heipi5ftfkbxqiytyxr4legakgjzq2" CACHE PATH "") -set(KVTREE_DIR "${TPL_ROOT}/kvtree-1.3.0-hoxrqia25kplgu63o6275e3phsxgldfr" CACHE PATH "") +set(KVTREE_DIR "${TPL_ROOT}/kvtree-1.3.0-l3jatuuigf2z2swzxrdzkbrxhdinaqfz" CACHE PATH "") -set(DTCMP_DIR "${TPL_ROOT}/dtcmp-1.1.5-cto5znftnfq2kdwpunaegg6trpraj4ke" CACHE PATH "") +set(DTCMP_DIR "${TPL_ROOT}/dtcmp-1.1.5-b6g6n4hdju6ol5g2pcizzey7dthbgapt" CACHE PATH "") -set(SPATH_DIR "${TPL_ROOT}/spath-0.2.0-owqpu5cpinfj56adgrwimokhecl37hdp" CACHE PATH "") +set(SPATH_DIR "${TPL_ROOT}/spath-0.2.0-6sxvboh2q5kmo7q5tcchgao2dgx5g72q" CACHE PATH "") -set(AXL_DIR "${TPL_ROOT}/axl-0.7.1-pbcj3g7dqmuiogpb2mawnyuqiyng2vh7" CACHE PATH "") +set(AXL_DIR "${TPL_ROOT}/axl-0.7.1-z4zopytuytxgn5wds3bnhqpqmfju54h7" CACHE PATH "") -set(LWGRP_DIR "${TPL_ROOT}/lwgrp-1.0.6-fvf5w43ygbspsrft2gbc4tbrgamobo6n" CACHE PATH "") +set(LWGRP_DIR "${TPL_ROOT}/lwgrp-1.0.6-u4qqk5djarvhyvk3fg7ls4r6ltx4anmr" CACHE PATH "") -set(ER_DIR "${TPL_ROOT}/er-0.3.0-4xpuy3lnjbc5ii2peposyxlve7exzijq" CACHE PATH "") +set(ER_DIR "${TPL_ROOT}/er-0.3.0-eabe4roqqpiyjenasayt6pupercipzak" CACHE PATH "") -set(RANKSTR_DIR "${TPL_ROOT}/rankstr-0.2.0-5a5trmqremlbnqljjn6u7cqndylpzuhw" CACHE PATH "") +set(RANKSTR_DIR "${TPL_ROOT}/rankstr-0.2.0-njavwb2qlbesemqc6ac7hjeezjxtydv7" CACHE PATH "") -set(REDSET_DIR "${TPL_ROOT}/redset-0.2.0-ou4lf63bu5chfljcdkaot6feubhmedoh" CACHE PATH "") +set(REDSET_DIR "${TPL_ROOT}/redset-0.2.0-5b3fl74qgvvfturxdgz44rurhjdm4en4" CACHE PATH "") -set(SHUFFILE_DIR "${TPL_ROOT}/shuffile-0.2.0-mbm3htxivpgso6ogxf5jzdsjhzwqltnm" CACHE PATH "") +set(SHUFFILE_DIR "${TPL_ROOT}/shuffile-0.2.0-tpvuy3dcxpakepz77nxygcb7cqjwtwmv" CACHE PATH "") -set(LIBYOGRT_DIR "${TPL_ROOT}/libyogrt-1.35-n3wzi44wyx5i63jyrn7mozyiec6nake3" CACHE PATH "") +set(LIBYOGRT_DIR "${TPL_ROOT}/libyogrt-1.35-5geyvlylxzqsc6g6s6xiq6aakvy2qiu7" CACHE PATH "") #------------------------------------------------------------------------------ # Devtools & Python @@ -145,14 +149,16 @@ set(CPPCHECK_EXECUTABLE "${DEVTOOLS_ROOT}/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66x set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/kky5oskfhacihjb5gpy7qkeh6edlrtcb/doxygen-1.15.0/bin/doxygen" CACHE PATH "") -set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-6akjhhlga6h3jx75swovfvo76k6rw6fp/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-zxodixu5egwgeorhpe3ciggwsv3ndfxb/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-73lpwcid33wlekelrxoh24535lzexwwg/lib/python3.13/site-packages" CACHE PATH "") -set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-7qsjxyuuh7rh5hccdziewhwqymkmepb3/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/py-pluggy-1.6.0-6zab4pck6v7johubbhalr2waowbwpjei/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/none-none/py-pluggy-1.6.0-6zab4pck6v7johubbhalr2waowbwpjei/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/py-iniconfig-2.1.0-xwmbxuhk2oo3dcuizdp3z4hqbpqykkom/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_10_09_31_34/none-none/py-iniconfig-2.1.0-xwmbxuhk2oo3dcuizdp3z4hqbpqykkom/lib/python3.13/site-packages" CACHE PATH "") +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-hrvj5zyy3xo2rbzvbz4ed7kjye7ssgmk/lib/python3.13/site-packages" CACHE PATH "") From ed85cc18f1618b463b186e4c12db2ffd8f944591 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Thu, 23 Apr 2026 08:06:08 -0700 Subject: [PATCH 167/986] Update CZ host-configs --- .../dane-toss_4_x86_64_ib-gcc@13.3.1.cmake | 72 +++++++++--------- ...4_ib-intel-oneapi-compilers@2025.2.0.cmake | 52 +++++++------ .../dane-toss_4_x86_64_ib-llvm@19.1.3.cmake | 74 ++++++++++--------- ...rix-toss_4_x86_64_ib-gcc@13.3.1_cuda.cmake | 52 +++++++------ ...ix-toss_4_x86_64_ib-llvm@19.1.3_cuda.cmake | 52 +++++++------ ...toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake | 22 +++--- ...x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake | 22 +++--- ...x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake | 22 +++--- ...toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake | 22 +++--- ...x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake | 22 +++--- ...x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake | 22 +++--- 11 files changed, 237 insertions(+), 197 deletions(-) diff --git a/host-configs/dane-toss_4_x86_64_ib-gcc@13.3.1.cmake b/host-configs/dane-toss_4_x86_64_ib-gcc@13.3.1.cmake index 0e8f266def..1437c26d2d 100644 --- a/host-configs/dane-toss_4_x86_64_ib-gcc@13.3.1.cmake +++ b/host-configs/dane-toss_4_x86_64_ib-gcc@13.3.1.cmake @@ -4,28 +4,28 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.26.3/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/gcc-13.3.1/blt-0.7.1-yiu2olkumeazbrto7x4v2dv6qbefhf5a;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/gcc-13.3.1/c2c-1.8.0-jkclfdpl7igj3ur3ufnpt6dupjfvotpc;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/gcc-13.3.1/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-f4kbq52oexscd7dhlwdbffygha5losj5;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/gcc-13.3.1/conduit-0.9.5-xyt2d2ot4eozuhrjv7mccoe7tw6ef7ui;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/gcc-13.3.1/mfem-4.9.0-heih2kd5qpkalflltitlry3g4qpyx5xw;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/gcc-13.3.1/py-nanobind-2.7.0-szozdpwzge7ah4bsilit6fijezxqbfop;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/gcc-13.3.1/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-ktky5uf7luvuf4cnczifj5av5ndbajco;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/gcc-13.3.1/scr-3.0.1-tnom76yaaoeyob7dncuafxebkzf2fbv7;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/gcc-13.3.1/umpire-2025.12.0-m6o7ya6pnrpixhpygzywlzoue3zuuir3;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/gcc-13.3.1/adiak-0.4.0-i5bahteezqmswdv2ulpfvfogmuue463q;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/gcc-13.3.1/libunwind-1.8.3-pwdmczcnf7vugfly5zk7eztefnn7kv75;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/gcc-13.3.1/hdf5-1.8.23-2pnsuxpt6r2wmdc6sqyku6ki6na4gcfl;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/gcc-13.3.1/parmetis-4.0.3-tt2yxrbsrccyglhjqcs2y2nspmt4qoqx;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/gcc-13.3.1/py-numpy-2.4.2-z4yapdkn22w6adfrl7ibucqiokgrwb7f;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/gcc-13.3.1/hypre-2.27.0-vwir3t6mlgbmo3mgfbiayrpbbdxsrsha;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/gcc-13.3.1/axl-0.7.1-yoi3m7zjahalmglnbo33aitshq6cj43q;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/gcc-13.3.1/dtcmp-1.1.5-ipjglgrmgotna4yza37ecwiur3rkx6wf;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/gcc-13.3.1/er-0.3.0-bdvz66gxhiflazj3irsv5vleaphtypqz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/gcc-13.3.1/libyogrt-1.35-qwz3xpqmkwgh5byk2kiy3txbazfwwf3c;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/gcc-13.3.1/spath-0.2.0-pmsauxzx75wu4kyfwjf3ite5dzzaymhv;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/gcc-13.3.1/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-cc5noebugovl4o6ir2d65bx7qizwznef;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/gcc-13.3.1/fmt-11.0.2-ggfpxocredbjrldu7ar33cjedhmkm3da;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/gcc-13.3.1/metis-5.1.0-3jtvhjwnhqqaf5v2na7f675qgvlusyx3;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/gcc-13.3.1/lwgrp-1.0.6-lkotiz6t6q3r5nmgqcfhfvdyukzdnnuu;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/gcc-13.3.1/redset-0.2.0-2kb4kg3ypf7l66vxdt6x2z4oqvimt3bs;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/gcc-13.3.1/shuffile-0.2.0-v2buhpkgx2py4m4bny4lsflaoeqbmqof;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/gcc-13.3.1/rankstr-0.2.0-xkzr7o47vbxkwtxppil7a7i57dhlk5gf;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/gcc-13.3.1/kvtree-1.3.0-py6ds6lmww6eqlhjgb4vilk6ftkxsuud;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/none-none/gcc-runtime-13.3.1-zsobnkwck2glzcbukupgvgbiwwhdonys;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.26.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66xzj4wm5kfqxyc4vgox7gb3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/doxygen-1.15.0;/usr/tce/packages/gcc/gcc-13.3.1;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-gcc-13.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/blt-0.7.1-lj5bghgkodlzmfs75wimnoxuui2quc3a;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/c2c-1.8.0-tyixdhhwshu35d2vuh6gw5bk3b3k7nqz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-bzioyf6catu3cjwaz4cplt7ubtvavgbl;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/conduit-0.9.5-vlm6nb2rwwmvnxtnxw5p3o27rzsap6vt;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/mfem-4.9.0-xodkornev7gcvmi3idsoxtdsnlph4rqv;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/py-nanobind-2.7.0-e42etfacct5pxga65kryysakw52u4c63;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-o23ptcxtve2rphukiakbezevsssiqqmq;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/scr-3.0.1-ov4mwvpcd75glzu2lfuivq3oibr4vvpd;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/umpire-2025.12.0-drla577bebgu5wx6q7y37qxedmgmpcgi;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/adiak-0.4.0-nqiuo5ltc24jpawzi6mlp3xtmngcopiu;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/libunwind-1.8.3-a7zvz3euhmbhk4as6h6j66ytabzwztxb;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/hdf5-1.8.23-zyfoez4ztydoqfeubcamjypyhprjpuca;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/parmetis-4.0.3-sxnyn5chjyiyowzk32dsynslufrofyho;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/py-mpi4py-4.1.1-l3h5jmmg773bd5oer7makte3ln6rn7xv;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/py-numpy-2.4.2-to6o5czwkczlh46d6gkubixw5bhzxakl;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/hypre-2.27.0-qvi4rtn4tbrduxkdpfugf6f5lkzirnel;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/axl-0.7.1-ljgukulclybf6cdbcj7u3w7o63v43zcg;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/dtcmp-1.1.5-taj5rsqbrki5hk2fg5pxkg6xsz5tfjxk;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/er-0.3.0-ozpsif2qsi7or56avv6fxwn3at2hrhli;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/libyogrt-1.35-y4nvgwhpokw2x6lekbubcmxv5wweidg4;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/spath-0.2.0-5zx5r6jnfiqbfh2hfkzvuvqmam6j67dt;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-kjufdwlliz23qzwoy2xygn3w6dsq6tec;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/fmt-11.0.2-5wsybhdkos4xu5i7thboag6245lzc6jj;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/metis-5.1.0-3qhomb7zt2n7nh46kmc2egkzmcd3tafm;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/lwgrp-1.0.6-e2smb3vzymlqmy35nuipgi6cv6o2ezyz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/redset-0.2.0-itxhprv2jiz4uw4oa42jribcmnc6z2xr;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/shuffile-0.2.0-jpcg5fljyjaf3sjmnasiiv4brhw47vcc;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/rankstr-0.2.0-dpmjoa5upfcwzpbvxmhrbgupiqtu4uoz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/kvtree-1.3.0-3tlf6x5raibwnal2uo2l7feyjrqmkrhd;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/gcc-runtime-13.3.1-xlvjd7vcdngtnvtxcdyqwtquvenekynf;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.26.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66xzj4wm5kfqxyc4vgox7gb3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/doxygen-1.15.0;/usr/tce/packages/gcc/gcc-13.3.1;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-gcc-13.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/gcc-13.3.1/axom-develop-s37sdvbj7bw3warwbjp4izofxn5mok4c/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/gcc-13.3.1/axom-develop-s37sdvbj7bw3warwbjp4izofxn5mok4c/lib64;;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/axom-develop-vfi5qherug7c2n7iyqkyclwbajst4kps/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/axom-develop-vfi5qherug7c2n7iyqkyclwbajst4kps/lib64;;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/gcc-13.3.1/axom-develop-s37sdvbj7bw3warwbjp4izofxn5mok4c/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/gcc-13.3.1/axom-develop-s37sdvbj7bw3warwbjp4izofxn5mok4c/lib64;;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/axom-develop-vfi5qherug7c2n7iyqkyclwbajst4kps/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/axom-develop-vfi5qherug7c2n7iyqkyclwbajst4kps/lib64;;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ # Compilers #------------------------------------------------------------------------------ -# Compiler Spec: gcc@13.3.1/xrm3n2pex2en2wvm6p4ap62nieoz7kne +# Compiler Spec: gcc@13.3.1/zbmuyoury7g5npf6fa7lwwxbjninyheh #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/gcc" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/gcc" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/g++" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/g++" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/gfortran" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/gfortran" CACHE PATH "") else() @@ -37,6 +37,10 @@ else() endif() +set(CMAKE_C_FLAGS "-fPIC" CACHE STRING "") + +set(CMAKE_CXX_FLAGS "-fPIC" CACHE STRING "") + set(CMAKE_Fortran_FLAGS "-fPIC" CACHE STRING "") set(ENABLE_FORTRAN ON CACHE BOOL "") @@ -73,51 +77,51 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/gcc-13.3.1" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1" CACHE PATH "") -set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-xyt2d2ot4eozuhrjv7mccoe7tw6ef7ui" CACHE PATH "") +set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-vlm6nb2rwwmvnxtnxw5p3o27rzsap6vt" CACHE PATH "") -set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-jkclfdpl7igj3ur3ufnpt6dupjfvotpc" CACHE PATH "") +set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-tyixdhhwshu35d2vuh6gw5bk3b3k7nqz" CACHE PATH "") -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-heih2kd5qpkalflltitlry3g4qpyx5xw" CACHE PATH "") +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-xodkornev7gcvmi3idsoxtdsnlph4rqv" CACHE PATH "") -set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-2pnsuxpt6r2wmdc6sqyku6ki6na4gcfl" CACHE PATH "") +set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-zyfoez4ztydoqfeubcamjypyhprjpuca" CACHE PATH "") set(LUA_DIR "/usr" CACHE PATH "") -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-ktky5uf7luvuf4cnczifj5av5ndbajco" CACHE PATH "") +set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-o23ptcxtve2rphukiakbezevsssiqqmq" CACHE PATH "") -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-m6o7ya6pnrpixhpygzywlzoue3zuuir3" CACHE PATH "") +set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-drla577bebgu5wx6q7y37qxedmgmpcgi" CACHE PATH "") # OPENCASCADE not built -set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-i5bahteezqmswdv2ulpfvfogmuue463q" CACHE PATH "") +set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-nqiuo5ltc24jpawzi6mlp3xtmngcopiu" CACHE PATH "") -set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-f4kbq52oexscd7dhlwdbffygha5losj5" CACHE PATH "") +set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-bzioyf6catu3cjwaz4cplt7ubtvavgbl" CACHE PATH "") -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-cc5noebugovl4o6ir2d65bx7qizwznef" CACHE PATH "") +set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-kjufdwlliz23qzwoy2xygn3w6dsq6tec" CACHE PATH "") -set(SCR_DIR "${TPL_ROOT}/scr-3.0.1-tnom76yaaoeyob7dncuafxebkzf2fbv7" CACHE PATH "") +set(SCR_DIR "${TPL_ROOT}/scr-3.0.1-ov4mwvpcd75glzu2lfuivq3oibr4vvpd" CACHE PATH "") -set(KVTREE_DIR "${TPL_ROOT}/kvtree-1.3.0-py6ds6lmww6eqlhjgb4vilk6ftkxsuud" CACHE PATH "") +set(KVTREE_DIR "${TPL_ROOT}/kvtree-1.3.0-3tlf6x5raibwnal2uo2l7feyjrqmkrhd" CACHE PATH "") -set(DTCMP_DIR "${TPL_ROOT}/dtcmp-1.1.5-ipjglgrmgotna4yza37ecwiur3rkx6wf" CACHE PATH "") +set(DTCMP_DIR "${TPL_ROOT}/dtcmp-1.1.5-taj5rsqbrki5hk2fg5pxkg6xsz5tfjxk" CACHE PATH "") -set(SPATH_DIR "${TPL_ROOT}/spath-0.2.0-pmsauxzx75wu4kyfwjf3ite5dzzaymhv" CACHE PATH "") +set(SPATH_DIR "${TPL_ROOT}/spath-0.2.0-5zx5r6jnfiqbfh2hfkzvuvqmam6j67dt" CACHE PATH "") -set(AXL_DIR "${TPL_ROOT}/axl-0.7.1-yoi3m7zjahalmglnbo33aitshq6cj43q" CACHE PATH "") +set(AXL_DIR "${TPL_ROOT}/axl-0.7.1-ljgukulclybf6cdbcj7u3w7o63v43zcg" CACHE PATH "") -set(LWGRP_DIR "${TPL_ROOT}/lwgrp-1.0.6-lkotiz6t6q3r5nmgqcfhfvdyukzdnnuu" CACHE PATH "") +set(LWGRP_DIR "${TPL_ROOT}/lwgrp-1.0.6-e2smb3vzymlqmy35nuipgi6cv6o2ezyz" CACHE PATH "") -set(ER_DIR "${TPL_ROOT}/er-0.3.0-bdvz66gxhiflazj3irsv5vleaphtypqz" CACHE PATH "") +set(ER_DIR "${TPL_ROOT}/er-0.3.0-ozpsif2qsi7or56avv6fxwn3at2hrhli" CACHE PATH "") -set(RANKSTR_DIR "${TPL_ROOT}/rankstr-0.2.0-xkzr7o47vbxkwtxppil7a7i57dhlk5gf" CACHE PATH "") +set(RANKSTR_DIR "${TPL_ROOT}/rankstr-0.2.0-dpmjoa5upfcwzpbvxmhrbgupiqtu4uoz" CACHE PATH "") -set(REDSET_DIR "${TPL_ROOT}/redset-0.2.0-2kb4kg3ypf7l66vxdt6x2z4oqvimt3bs" CACHE PATH "") +set(REDSET_DIR "${TPL_ROOT}/redset-0.2.0-itxhprv2jiz4uw4oa42jribcmnc6z2xr" CACHE PATH "") -set(SHUFFILE_DIR "${TPL_ROOT}/shuffile-0.2.0-v2buhpkgx2py4m4bny4lsflaoeqbmqof" CACHE PATH "") +set(SHUFFILE_DIR "${TPL_ROOT}/shuffile-0.2.0-jpcg5fljyjaf3sjmnasiiv4brhw47vcc" CACHE PATH "") -set(LIBYOGRT_DIR "${TPL_ROOT}/libyogrt-1.35-qwz3xpqmkwgh5byk2kiy3txbazfwwf3c" CACHE PATH "") +set(LIBYOGRT_DIR "${TPL_ROOT}/libyogrt-1.35-y4nvgwhpokw2x6lekbubcmxv5wweidg4" CACHE PATH "") #------------------------------------------------------------------------------ # Devtools & Python @@ -143,14 +147,16 @@ set(CPPCHECK_EXECUTABLE "${DEVTOOLS_ROOT}/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66x set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/kky5oskfhacihjb5gpy7qkeh6edlrtcb/doxygen-1.15.0/bin/doxygen" CACHE PATH "") -set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-szozdpwzge7ah4bsilit6fijezxqbfop/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-e42etfacct5pxga65kryysakw52u4c63/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-to6o5czwkczlh46d6gkubixw5bhzxakl/lib/python3.13/site-packages" CACHE PATH "") -set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-z4yapdkn22w6adfrl7ibucqiokgrwb7f/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/py-pluggy-1.6.0-6zab4pck6v7johubbhalr2waowbwpjei/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/none-none/py-pluggy-1.6.0-6zab4pck6v7johubbhalr2waowbwpjei/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/py-iniconfig-2.1.0-xwmbxuhk2oo3dcuizdp3z4hqbpqykkom/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/none-none/py-iniconfig-2.1.0-xwmbxuhk2oo3dcuizdp3z4hqbpqykkom/lib/python3.13/site-packages" CACHE PATH "") +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-l3h5jmmg773bd5oer7makte3ln6rn7xv/lib/python3.13/site-packages" CACHE PATH "") diff --git a/host-configs/dane-toss_4_x86_64_ib-intel-oneapi-compilers@2025.2.0.cmake b/host-configs/dane-toss_4_x86_64_ib-intel-oneapi-compilers@2025.2.0.cmake index 941245ea7b..01c07dc010 100644 --- a/host-configs/dane-toss_4_x86_64_ib-intel-oneapi-compilers@2025.2.0.cmake +++ b/host-configs/dane-toss_4_x86_64_ib-intel-oneapi-compilers@2025.2.0.cmake @@ -4,28 +4,28 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.26.3/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/intel-oneapi-compilers-2025.2.0/blt-0.7.1-2n76efajatydczwltuk6plxpgoyvu6jj;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/intel-oneapi-compilers-2025.2.0/c2c-1.8.0-rl42nhoa33aux3tluna3bftevqokiiqc;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/intel-oneapi-compilers-2025.2.0/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-ti2c3tm4pc6rma2cedok5hy5vnpxmsj7;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/intel-oneapi-compilers-2025.2.0/conduit-0.9.5-x6snw6rzqee7bhmsc6rezi3oobsb6tba;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/intel-oneapi-compilers-2025.2.0/mfem-4.9.0-exbr44mwmmbii764bvxrgct2ivffacjh;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/gcc-13.3.1/py-nanobind-2.7.0-zg2fpeevizaaykxxnp4butsrujlb54px;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/intel-oneapi-compilers-2025.2.0/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-jz235wsh3nhxjuzqxse57hcojia7x4mj;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/intel-oneapi-compilers-2025.2.0/umpire-2025.12.0-2eti2r3wjt4qs772djlcgf6axs44qmko;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/intel-oneapi-compilers-2025.2.0/adiak-0.4.0-rgjvxwzjod55ialozetvkjyi3hjczviy;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/intel-oneapi-compilers-2025.2.0/libunwind-1.8.3-h42c2h7kzkiaeq63dzvt32wlhcqbcrra;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/intel-oneapi-compilers-2025.2.0/hdf5-1.8.23-74fr7atqhw4jlzxt22xi5szeqpoxcwkh;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/intel-oneapi-compilers-2025.2.0/parmetis-4.0.3-g7bp5ds3u3laspneww3scyujjwebgdzh;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/gcc-13.3.1/py-numpy-2.4.2-z4yapdkn22w6adfrl7ibucqiokgrwb7f;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/intel-oneapi-compilers-2025.2.0/hypre-2.27.0-hgyyzv6j2qosdblkfnupzwgwmujcrtse;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/intel-oneapi-compilers-2025.2.0/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-2gq3ddrhfxqlgzr6zxbvmwpzvtqxqj25;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/intel-oneapi-compilers-2025.2.0/fmt-11.0.2-orofhwltz3p5ckiw2t7gzhkgiifetwjt;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/intel-oneapi-compilers-2025.2.0/metis-5.1.0-zschch2gorb5k7t4rmrtc3aubm663i6u;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/none-none/intel-oneapi-runtime-2025.2.0-mfk4ezunnyprzpfps5pncx2nhttsa4z3;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/none-none/gcc-runtime-13.3.1-zsobnkwck2glzcbukupgvgbiwwhdonys;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.26.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66xzj4wm5kfqxyc4vgox7gb3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/doxygen-1.15.0;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/intel-oneapi-compilers-2025.2.0-ngszoacyp2i5d5nawitlyuh37jswjtie/compiler/2025.2;/usr/tce/packages/intel/intel-2025.2.0;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-intel-2025.2.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/intel-oneapi-compilers-2025.2.0/blt-0.7.1-fnswc6mwjgunle3ushrnp3e6w6s25qra;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/intel-oneapi-compilers-2025.2.0/c2c-1.8.0-fdbbv7fzmylpoctjgdai2kc4xfcz2vqt;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/intel-oneapi-compilers-2025.2.0/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-bheefndoamg47jey4mbq2ofgflz7kvz4;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/intel-oneapi-compilers-2025.2.0/conduit-0.9.5-lqjx3vlbi5prcmle2gsgc7yopbglp3ab;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/intel-oneapi-compilers-2025.2.0/mfem-4.9.0-npcozwxpczgzuya2z6iailvycmbbc6nd;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/py-nanobind-2.7.0-iky6agz5lprxpnf2nffxwgqcgiagqiz5;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/intel-oneapi-compilers-2025.2.0/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-ppbrbxztecp4363lchwihhymqrhv75un;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/intel-oneapi-compilers-2025.2.0/umpire-2025.12.0-tdpnmcmwvlz77qwdeupgufa6s5aswwp5;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/intel-oneapi-compilers-2025.2.0/adiak-0.4.0-gxozhembb6br5wkhuxetug25ndaodstk;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/intel-oneapi-compilers-2025.2.0/libunwind-1.8.3-lgvf5mwbhqt4is2gg3uuafuyxv35lwd7;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/intel-oneapi-compilers-2025.2.0/hdf5-1.8.23-zhxj7u6hk266vklzloqcp3qlahtbta4l;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/intel-oneapi-compilers-2025.2.0/parmetis-4.0.3-dred3k4i2un2bw3w4st5pl2zvpvpfr37;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/intel-oneapi-compilers-2025.2.0/py-mpi4py-4.1.1-7mymbwx2ifuqlotydf6dx6hd6wmz4xhd;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/py-numpy-2.4.2-to6o5czwkczlh46d6gkubixw5bhzxakl;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/intel-oneapi-compilers-2025.2.0/hypre-2.27.0-jtpmchw5qcrtezcac6mp53wlxcncp5ne;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/intel-oneapi-compilers-2025.2.0/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-k4dewbo7oilgt6zjhkze2hyqyc6xn3if;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/intel-oneapi-compilers-2025.2.0/fmt-11.0.2-6ut7qqyjzqqbc6ggi4o7jthpsofhhu65;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/intel-oneapi-compilers-2025.2.0/metis-5.1.0-psuyihfrmzzgjxljxkoqbaqdjs53253r;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/intel-oneapi-runtime-2025.2.0-3ym7ceyrw63moiirt4tqoqubxhkiqv63;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/gcc-runtime-13.3.1-xlvjd7vcdngtnvtxcdyqwtquvenekynf;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.26.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66xzj4wm5kfqxyc4vgox7gb3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/doxygen-1.15.0;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/intel-oneapi-compilers-2025.2.0-ngszoacyp2i5d5nawitlyuh37jswjtie/compiler/2025.2;/usr/tce/packages/intel/intel-2025.2.0;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-intel-2025.2.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/intel-oneapi-compilers-2025.2.0/axom-develop-3l4k45i3jli4otfuyze65smrd7vj43iz/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/intel-oneapi-compilers-2025.2.0/axom-develop-3l4k45i3jli4otfuyze65smrd7vj43iz/lib64;;/usr/tce/packages/intel/intel-2025.2.0/compiler/2025.2/lib;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/intel-oneapi-compilers-2025.2.0-ngszoacyp2i5d5nawitlyuh37jswjtie/compiler/2025.2/lib;/usr/tce/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/intel-oneapi-compilers-2025.2.0/axom-develop-g2h45edliw4q2vgz7cfusag64p3ulopb/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/intel-oneapi-compilers-2025.2.0/axom-develop-g2h45edliw4q2vgz7cfusag64p3ulopb/lib64;;/usr/tce/packages/intel/intel-2025.2.0/compiler/2025.2/lib;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/intel-oneapi-compilers-2025.2.0-ngszoacyp2i5d5nawitlyuh37jswjtie/compiler/2025.2/lib;/usr/tce/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/intel-oneapi-compilers-2025.2.0/axom-develop-3l4k45i3jli4otfuyze65smrd7vj43iz/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/intel-oneapi-compilers-2025.2.0/axom-develop-3l4k45i3jli4otfuyze65smrd7vj43iz/lib64;;/usr/tce/packages/intel/intel-2025.2.0/compiler/2025.2/lib;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/intel-oneapi-compilers-2025.2.0-ngszoacyp2i5d5nawitlyuh37jswjtie/compiler/2025.2/lib;/usr/tce/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/intel-oneapi-compilers-2025.2.0/axom-develop-g2h45edliw4q2vgz7cfusag64p3ulopb/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/intel-oneapi-compilers-2025.2.0/axom-develop-g2h45edliw4q2vgz7cfusag64p3ulopb/lib64;;/usr/tce/packages/intel/intel-2025.2.0/compiler/2025.2/lib;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/intel-oneapi-compilers-2025.2.0-ngszoacyp2i5d5nawitlyuh37jswjtie/compiler/2025.2/lib;/usr/tce/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ # Compilers #------------------------------------------------------------------------------ -# Compiler Spec: intel-oneapi-compilers@2025.2.0/pfgmabeqlm6quu3niafr24bgm7vzeeug +# Compiler Spec: intel-oneapi-compilers@2025.2.0/pure57vovckospvgxd2lr56fpsa636dr #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/oneapi/icx" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/oneapi/icx" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/oneapi/icpx" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/oneapi/icpx" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/oneapi/ifx" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/oneapi/ifx" CACHE PATH "") else() @@ -37,12 +37,14 @@ else() endif() +set(CMAKE_C_FLAGS "-fPIC" CACHE STRING "") + +set(CMAKE_CXX_FLAGS "-fPIC -fp-model=precise" CACHE STRING "") + set(CMAKE_Fortran_FLAGS "-fPIC" CACHE STRING "") set(ENABLE_FORTRAN ON CACHE BOOL "") -set(CMAKE_CXX_FLAGS "-fp-model=precise" CACHE STRING "") - set(CMAKE_CXX_FLAGS_DEBUG "-g -Rno-debug-disables-optimization" CACHE STRING "") #------------------------------------------------------------------------------ @@ -77,29 +79,29 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/intel-oneapi-compilers-2025.2.0" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/intel-oneapi-compilers-2025.2.0" CACHE PATH "") -set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-x6snw6rzqee7bhmsc6rezi3oobsb6tba" CACHE PATH "") +set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-lqjx3vlbi5prcmle2gsgc7yopbglp3ab" CACHE PATH "") -set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-rl42nhoa33aux3tluna3bftevqokiiqc" CACHE PATH "") +set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-fdbbv7fzmylpoctjgdai2kc4xfcz2vqt" CACHE PATH "") -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-exbr44mwmmbii764bvxrgct2ivffacjh" CACHE PATH "") +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-npcozwxpczgzuya2z6iailvycmbbc6nd" CACHE PATH "") -set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-74fr7atqhw4jlzxt22xi5szeqpoxcwkh" CACHE PATH "") +set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-zhxj7u6hk266vklzloqcp3qlahtbta4l" CACHE PATH "") set(LUA_DIR "/usr" CACHE PATH "") -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-jz235wsh3nhxjuzqxse57hcojia7x4mj" CACHE PATH "") +set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-ppbrbxztecp4363lchwihhymqrhv75un" CACHE PATH "") -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-2eti2r3wjt4qs772djlcgf6axs44qmko" CACHE PATH "") +set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-tdpnmcmwvlz77qwdeupgufa6s5aswwp5" CACHE PATH "") # OPENCASCADE not built -set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-rgjvxwzjod55ialozetvkjyi3hjczviy" CACHE PATH "") +set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-gxozhembb6br5wkhuxetug25ndaodstk" CACHE PATH "") -set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-ti2c3tm4pc6rma2cedok5hy5vnpxmsj7" CACHE PATH "") +set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-bheefndoamg47jey4mbq2ofgflz7kvz4" CACHE PATH "") -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-2gq3ddrhfxqlgzr6zxbvmwpzvtqxqj25" CACHE PATH "") +set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-k4dewbo7oilgt6zjhkze2hyqyc6xn3if" CACHE PATH "") # scr not built @@ -127,14 +129,16 @@ set(CPPCHECK_EXECUTABLE "${DEVTOOLS_ROOT}/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66x set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/kky5oskfhacihjb5gpy7qkeh6edlrtcb/doxygen-1.15.0/bin/doxygen" CACHE PATH "") -set(PY_NANOBIND_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/gcc-13.3.1/py-nanobind-2.7.0-zg2fpeevizaaykxxnp4butsrujlb54px/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NANOBIND_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/py-nanobind-2.7.0-iky6agz5lprxpnf2nffxwgqcgiagqiz5/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NUMPY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/py-numpy-2.4.2-to6o5czwkczlh46d6gkubixw5bhzxakl/lib/python3.13/site-packages" CACHE PATH "") -set(PY_NUMPY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/gcc-13.3.1/py-numpy-2.4.2-z4yapdkn22w6adfrl7ibucqiokgrwb7f/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/py-pluggy-1.6.0-6zab4pck6v7johubbhalr2waowbwpjei/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/none-none/py-pluggy-1.6.0-6zab4pck6v7johubbhalr2waowbwpjei/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/py-iniconfig-2.1.0-xwmbxuhk2oo3dcuizdp3z4hqbpqykkom/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_03_09_14_38/none-none/py-iniconfig-2.1.0-xwmbxuhk2oo3dcuizdp3z4hqbpqykkom/lib/python3.13/site-packages" CACHE PATH "") +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-7mymbwx2ifuqlotydf6dx6hd6wmz4xhd/lib/python3.13/site-packages" CACHE PATH "") diff --git a/host-configs/dane-toss_4_x86_64_ib-llvm@19.1.3.cmake b/host-configs/dane-toss_4_x86_64_ib-llvm@19.1.3.cmake index 2daa90b595..0e1f433159 100644 --- a/host-configs/dane-toss_4_x86_64_ib-llvm@19.1.3.cmake +++ b/host-configs/dane-toss_4_x86_64_ib-llvm@19.1.3.cmake @@ -4,28 +4,28 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.26.3/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/llvm-19.1.3/blt-0.7.1-577uis2w7b3ipf4v4v6y6vogmy52d6ay;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/llvm-19.1.3/c2c-1.8.0-pk3z5zabg2gtkur2c4mqzc5zz7z5f2tv;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/llvm-19.1.3/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-b7lmqorkofkrib5foi4fzbvd3eh2tbkj;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/llvm-19.1.3/conduit-0.9.5-gracxngtjim7b6zjsb5z4kf7qk347eo2;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/llvm-19.1.3/mfem-4.9.0-yseuuwvugp6qd45xmxp3ok67ibvfzkff;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/llvm-19.1.3/opencascade-7.8.1-6fd4qesv7tghklsteqoqe2mbvayuwgsa;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/llvm-19.1.3/py-nanobind-2.7.0-6akjhhlga6h3jx75swovfvo76k6rw6fp;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/llvm-19.1.3/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-7e5u3r4qt7erhbp67xbqnywbgobidj54;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/llvm-19.1.3/scr-3.0.1-t3vscu7yv2wkvgff46iwigx6fof72kws;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/llvm-19.1.3/umpire-2025.12.0-zbrxcsg5vgv63mpro6ar2rhm7ejx24h5;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/llvm-19.1.3/adiak-0.4.0-27bezbcpd5jxnvs65h3m6u2aheplxbmv;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/llvm-19.1.3/libunwind-1.8.3-ddv7ypujzw2wvlzabw3d42qygcbtofuq;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/llvm-19.1.3/hdf5-1.8.23-b37o2ze3w5sozwz6jmalg4pgg66jcogm;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/llvm-19.1.3/parmetis-4.0.3-slhcesf5fhsucwxtlxxana67jcrligfy;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/llvm-19.1.3/py-numpy-2.4.2-7qsjxyuuh7rh5hccdziewhwqymkmepb3;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/llvm-19.1.3/hypre-2.27.0-qkqgpzdh6t7s2pakhss6qnikhwfltykn;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/none-none/glx-1.4-4kahrsr5vciz372vpdvlmvxahspecwif;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/llvm-19.1.3/tcl-8.6.17-yk7vfdrbz2n7cr2mc3j33ehvukl3kkbz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/llvm-19.1.3/axl-0.7.1-pbcj3g7dqmuiogpb2mawnyuqiyng2vh7;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/llvm-19.1.3/dtcmp-1.1.5-cto5znftnfq2kdwpunaegg6trpraj4ke;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/llvm-19.1.3/er-0.3.0-4xpuy3lnjbc5ii2peposyxlve7exzijq;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/llvm-19.1.3/libyogrt-1.35-n3wzi44wyx5i63jyrn7mozyiec6nake3;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/llvm-19.1.3/spath-0.2.0-owqpu5cpinfj56adgrwimokhecl37hdp;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/llvm-19.1.3/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-ck62ywjq7i3hn5utmvio42ifdegjxgma;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/llvm-19.1.3/fmt-11.0.2-uougkyc3rcyd7punvwjtcme2zlhehpgz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/llvm-19.1.3/metis-5.1.0-3jeshrn7tydsgn5zdkf3iirdhw3u3oeq;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/llvm-19.1.3/lwgrp-1.0.6-fvf5w43ygbspsrft2gbc4tbrgamobo6n;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/llvm-19.1.3/redset-0.2.0-ou4lf63bu5chfljcdkaot6feubhmedoh;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/llvm-19.1.3/shuffile-0.2.0-mbm3htxivpgso6ogxf5jzdsjhzwqltnm;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/none-none/gcc-runtime-13.3.1-zsobnkwck2glzcbukupgvgbiwwhdonys;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/llvm-19.1.3/rankstr-0.2.0-5a5trmqremlbnqljjn6u7cqndylpzuhw;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/llvm-19.1.3/kvtree-1.3.0-hoxrqia25kplgu63o6275e3phsxgldfr;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.26.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66xzj4wm5kfqxyc4vgox7gb3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/doxygen-1.15.0;/usr/tce/packages/gcc/gcc-13.3.1;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-clang-19.1.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/blt-0.7.1-ux6wzebtj6vtgnayf4gqv2rr62ulwwfc;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/c2c-1.8.0-wto5u3qsffjhbxirygr7kqqjlg55pyv6;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-iuatdlntm2zcg2vuygvsc7say6wfx353;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/conduit-0.9.5-vqo2x2n6mz2vs4eg7kynwmlpeolsuim5;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/mfem-4.9.0-jqqjlgxdiebyz7gblihglbgu2jbnqnqc;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/opencascade-7.8.1-pyde26mlsvvcvw75lxvoeshe6r6aobxe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/py-nanobind-2.7.0-zxodixu5egwgeorhpe3ciggwsv3ndfxb;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-ur2yappntfb4cptytvwwwtmsf7irqgxa;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/scr-3.0.1-l7heipi5ftfkbxqiytyxr4legakgjzq2;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/umpire-2025.12.0-l3l75r2uuai7rl3m4e7fpdw446ankcxp;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/adiak-0.4.0-do3kop54yldi4hz43usrtnx2zyhvwfsv;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/libunwind-1.8.3-ck74b2escwzf2xtpiafdvqwmat26uuoe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/hdf5-1.8.23-vpibouztpyzmgikg4dg2egwf2u53mmpl;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/parmetis-4.0.3-fs7gps55rp6znnpz3b4zkd35jjkfycfc;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/py-mpi4py-4.1.1-hrvj5zyy3xo2rbzvbz4ed7kjye7ssgmk;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/py-numpy-2.4.2-73lpwcid33wlekelrxoh24535lzexwwg;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/hypre-2.27.0-zef2jav5gijraygsr2tumdy32outkfxp;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/glx-1.4-4kahrsr5vciz372vpdvlmvxahspecwif;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/tcl-8.6.17-qjflnn6itwshm5i6ckchochc7t2cuw6w;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/axl-0.7.1-z4zopytuytxgn5wds3bnhqpqmfju54h7;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/dtcmp-1.1.5-b6g6n4hdju6ol5g2pcizzey7dthbgapt;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/er-0.3.0-eabe4roqqpiyjenasayt6pupercipzak;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/libyogrt-1.35-5geyvlylxzqsc6g6s6xiq6aakvy2qiu7;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/spath-0.2.0-6sxvboh2q5kmo7q5tcchgao2dgx5g72q;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-omiy5m3jikdlf6gicad34w5vkfz7huex;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/fmt-11.0.2-ekc2idnbqipl2pqb4nugxoa7sj7aigpo;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/metis-5.1.0-7gsm27yycsippuh2l4fwei4dtvevbqgh;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/lwgrp-1.0.6-u4qqk5djarvhyvk3fg7ls4r6ltx4anmr;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/redset-0.2.0-5b3fl74qgvvfturxdgz44rurhjdm4en4;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/shuffile-0.2.0-tpvuy3dcxpakepz77nxygcb7cqjwtwmv;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/gcc-runtime-13.3.1-xlvjd7vcdngtnvtxcdyqwtquvenekynf;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/rankstr-0.2.0-njavwb2qlbesemqc6ac7hjeezjxtydv7;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/kvtree-1.3.0-l3jatuuigf2z2swzxrdzkbrxhdinaqfz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.26.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66xzj4wm5kfqxyc4vgox7gb3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/doxygen-1.15.0;/usr/tce/packages/gcc/gcc-13.3.1;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-clang-19.1.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/llvm-19.1.3/axom-develop-nb67hwsw6xydhvmny7vtyu5nzqzrs2o2/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/llvm-19.1.3/axom-develop-nb67hwsw6xydhvmny7vtyu5nzqzrs2o2/lib64;;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/llvm-19.1.3-gy2lu5xbi4csr2k47emlajzfs5mlsd4g/lib/x86_64-unknown-linux-gnu;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13;/usr/tce/packages/clang/clang-19.1.3/lib;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/axom-develop-oab3m23sasflxabp3rqhmbk2cxsnxv4t/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/axom-develop-oab3m23sasflxabp3rqhmbk2cxsnxv4t/lib64;;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/llvm-19.1.3-gy2lu5xbi4csr2k47emlajzfs5mlsd4g/lib/x86_64-unknown-linux-gnu;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13;/usr/tce/packages/clang/clang-19.1.3/lib;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/llvm-19.1.3/axom-develop-nb67hwsw6xydhvmny7vtyu5nzqzrs2o2/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/llvm-19.1.3/axom-develop-nb67hwsw6xydhvmny7vtyu5nzqzrs2o2/lib64;;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/llvm-19.1.3-gy2lu5xbi4csr2k47emlajzfs5mlsd4g/lib/x86_64-unknown-linux-gnu;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13;/usr/tce/packages/clang/clang-19.1.3/lib;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/axom-develop-oab3m23sasflxabp3rqhmbk2cxsnxv4t/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/axom-develop-oab3m23sasflxabp3rqhmbk2cxsnxv4t/lib64;;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/llvm-19.1.3-gy2lu5xbi4csr2k47emlajzfs5mlsd4g/lib/x86_64-unknown-linux-gnu;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13;/usr/tce/packages/clang/clang-19.1.3/lib;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ # Compilers #------------------------------------------------------------------------------ -# Compiler Spec: llvm@19.1.3/4rf6d7atkulu6wz7bucycjwtiuwqfy24 +# Compiler Spec: llvm@19.1.3/m2tcgbxfcdjwdmyrkqwamlljh4qeiie2 #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/clang/clang" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/clang/clang" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/clang/clang++" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/clang/clang++" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/gfortran" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/gfortran" CACHE PATH "") else() @@ -37,6 +37,10 @@ else() endif() +set(CMAKE_C_FLAGS "-fPIC" CACHE STRING "") + +set(CMAKE_CXX_FLAGS "-fPIC" CACHE STRING "") + set(CMAKE_Fortran_FLAGS "-fPIC" CACHE STRING "") set(ENABLE_FORTRAN ON CACHE BOOL "") @@ -75,51 +79,51 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/llvm-19.1.3" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3" CACHE PATH "") -set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-gracxngtjim7b6zjsb5z4kf7qk347eo2" CACHE PATH "") +set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-vqo2x2n6mz2vs4eg7kynwmlpeolsuim5" CACHE PATH "") -set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-pk3z5zabg2gtkur2c4mqzc5zz7z5f2tv" CACHE PATH "") +set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-wto5u3qsffjhbxirygr7kqqjlg55pyv6" CACHE PATH "") -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-yseuuwvugp6qd45xmxp3ok67ibvfzkff" CACHE PATH "") +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-jqqjlgxdiebyz7gblihglbgu2jbnqnqc" CACHE PATH "") -set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-b37o2ze3w5sozwz6jmalg4pgg66jcogm" CACHE PATH "") +set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-vpibouztpyzmgikg4dg2egwf2u53mmpl" CACHE PATH "") set(LUA_DIR "/usr" CACHE PATH "") -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-7e5u3r4qt7erhbp67xbqnywbgobidj54" CACHE PATH "") +set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-ur2yappntfb4cptytvwwwtmsf7irqgxa" CACHE PATH "") -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-zbrxcsg5vgv63mpro6ar2rhm7ejx24h5" CACHE PATH "") +set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-l3l75r2uuai7rl3m4e7fpdw446ankcxp" CACHE PATH "") -set(OPENCASCADE_DIR "${TPL_ROOT}/opencascade-7.8.1-6fd4qesv7tghklsteqoqe2mbvayuwgsa" CACHE PATH "") +set(OPENCASCADE_DIR "${TPL_ROOT}/opencascade-7.8.1-pyde26mlsvvcvw75lxvoeshe6r6aobxe" CACHE PATH "") -set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-27bezbcpd5jxnvs65h3m6u2aheplxbmv" CACHE PATH "") +set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-do3kop54yldi4hz43usrtnx2zyhvwfsv" CACHE PATH "") -set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-b7lmqorkofkrib5foi4fzbvd3eh2tbkj" CACHE PATH "") +set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-iuatdlntm2zcg2vuygvsc7say6wfx353" CACHE PATH "") -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-ck62ywjq7i3hn5utmvio42ifdegjxgma" CACHE PATH "") +set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-omiy5m3jikdlf6gicad34w5vkfz7huex" CACHE PATH "") -set(SCR_DIR "${TPL_ROOT}/scr-3.0.1-t3vscu7yv2wkvgff46iwigx6fof72kws" CACHE PATH "") +set(SCR_DIR "${TPL_ROOT}/scr-3.0.1-l7heipi5ftfkbxqiytyxr4legakgjzq2" CACHE PATH "") -set(KVTREE_DIR "${TPL_ROOT}/kvtree-1.3.0-hoxrqia25kplgu63o6275e3phsxgldfr" CACHE PATH "") +set(KVTREE_DIR "${TPL_ROOT}/kvtree-1.3.0-l3jatuuigf2z2swzxrdzkbrxhdinaqfz" CACHE PATH "") -set(DTCMP_DIR "${TPL_ROOT}/dtcmp-1.1.5-cto5znftnfq2kdwpunaegg6trpraj4ke" CACHE PATH "") +set(DTCMP_DIR "${TPL_ROOT}/dtcmp-1.1.5-b6g6n4hdju6ol5g2pcizzey7dthbgapt" CACHE PATH "") -set(SPATH_DIR "${TPL_ROOT}/spath-0.2.0-owqpu5cpinfj56adgrwimokhecl37hdp" CACHE PATH "") +set(SPATH_DIR "${TPL_ROOT}/spath-0.2.0-6sxvboh2q5kmo7q5tcchgao2dgx5g72q" CACHE PATH "") -set(AXL_DIR "${TPL_ROOT}/axl-0.7.1-pbcj3g7dqmuiogpb2mawnyuqiyng2vh7" CACHE PATH "") +set(AXL_DIR "${TPL_ROOT}/axl-0.7.1-z4zopytuytxgn5wds3bnhqpqmfju54h7" CACHE PATH "") -set(LWGRP_DIR "${TPL_ROOT}/lwgrp-1.0.6-fvf5w43ygbspsrft2gbc4tbrgamobo6n" CACHE PATH "") +set(LWGRP_DIR "${TPL_ROOT}/lwgrp-1.0.6-u4qqk5djarvhyvk3fg7ls4r6ltx4anmr" CACHE PATH "") -set(ER_DIR "${TPL_ROOT}/er-0.3.0-4xpuy3lnjbc5ii2peposyxlve7exzijq" CACHE PATH "") +set(ER_DIR "${TPL_ROOT}/er-0.3.0-eabe4roqqpiyjenasayt6pupercipzak" CACHE PATH "") -set(RANKSTR_DIR "${TPL_ROOT}/rankstr-0.2.0-5a5trmqremlbnqljjn6u7cqndylpzuhw" CACHE PATH "") +set(RANKSTR_DIR "${TPL_ROOT}/rankstr-0.2.0-njavwb2qlbesemqc6ac7hjeezjxtydv7" CACHE PATH "") -set(REDSET_DIR "${TPL_ROOT}/redset-0.2.0-ou4lf63bu5chfljcdkaot6feubhmedoh" CACHE PATH "") +set(REDSET_DIR "${TPL_ROOT}/redset-0.2.0-5b3fl74qgvvfturxdgz44rurhjdm4en4" CACHE PATH "") -set(SHUFFILE_DIR "${TPL_ROOT}/shuffile-0.2.0-mbm3htxivpgso6ogxf5jzdsjhzwqltnm" CACHE PATH "") +set(SHUFFILE_DIR "${TPL_ROOT}/shuffile-0.2.0-tpvuy3dcxpakepz77nxygcb7cqjwtwmv" CACHE PATH "") -set(LIBYOGRT_DIR "${TPL_ROOT}/libyogrt-1.35-n3wzi44wyx5i63jyrn7mozyiec6nake3" CACHE PATH "") +set(LIBYOGRT_DIR "${TPL_ROOT}/libyogrt-1.35-5geyvlylxzqsc6g6s6xiq6aakvy2qiu7" CACHE PATH "") #------------------------------------------------------------------------------ # Devtools & Python @@ -145,14 +149,16 @@ set(CPPCHECK_EXECUTABLE "${DEVTOOLS_ROOT}/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66x set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/kky5oskfhacihjb5gpy7qkeh6edlrtcb/doxygen-1.15.0/bin/doxygen" CACHE PATH "") -set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-6akjhhlga6h3jx75swovfvo76k6rw6fp/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-zxodixu5egwgeorhpe3ciggwsv3ndfxb/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-73lpwcid33wlekelrxoh24535lzexwwg/lib/python3.13/site-packages" CACHE PATH "") -set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-7qsjxyuuh7rh5hccdziewhwqymkmepb3/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/py-pluggy-1.6.0-6zab4pck6v7johubbhalr2waowbwpjei/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/none-none/py-pluggy-1.6.0-6zab4pck6v7johubbhalr2waowbwpjei/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/py-iniconfig-2.1.0-xwmbxuhk2oo3dcuizdp3z4hqbpqykkom/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_11_33_13/none-none/py-iniconfig-2.1.0-xwmbxuhk2oo3dcuizdp3z4hqbpqykkom/lib/python3.13/site-packages" CACHE PATH "") +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-hrvj5zyy3xo2rbzvbz4ed7kjye7ssgmk/lib/python3.13/site-packages" CACHE PATH "") diff --git a/host-configs/matrix-toss_4_x86_64_ib-gcc@13.3.1_cuda.cmake b/host-configs/matrix-toss_4_x86_64_ib-gcc@13.3.1_cuda.cmake index 162852c99d..dd91c265a3 100644 --- a/host-configs/matrix-toss_4_x86_64_ib-gcc@13.3.1_cuda.cmake +++ b/host-configs/matrix-toss_4_x86_64_ib-gcc@13.3.1_cuda.cmake @@ -4,28 +4,28 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.26.3/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/gcc-13.3.1/blt-0.7.1-yiu2olkumeazbrto7x4v2dv6qbefhf5a;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/gcc-13.3.1/c2c-1.8.0-jkclfdpl7igj3ur3ufnpt6dupjfvotpc;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/gcc-13.3.1/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-qekykotizhycp2p4deoltaa227sajuvj;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/gcc-13.3.1/conduit-0.9.5-xyt2d2ot4eozuhrjv7mccoe7tw6ef7ui;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/gcc-13.3.1/mfem-4.9.0-kjei2ccah6sei4jplqabrucxltyq4zfb;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/gcc-13.3.1/py-nanobind-2.7.0-szozdpwzge7ah4bsilit6fijezxqbfop;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/gcc-13.3.1/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-vmsmvq25y56dkomrba3g63ls5nuga6xm;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/gcc-13.3.1/umpire-2025.12.0-kjyb6uox2o7qtfna22uqzxrqck7lukbt;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/gcc-13.3.1/adiak-0.4.0-i5bahteezqmswdv2ulpfvfogmuue463q;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/gcc-13.3.1/libunwind-1.8.3-pwdmczcnf7vugfly5zk7eztefnn7kv75;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/gcc-13.3.1/hdf5-1.8.23-2pnsuxpt6r2wmdc6sqyku6ki6na4gcfl;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/gcc-13.3.1/parmetis-4.0.3-tt2yxrbsrccyglhjqcs2y2nspmt4qoqx;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/gcc-13.3.1/py-numpy-2.4.2-z4yapdkn22w6adfrl7ibucqiokgrwb7f;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/gcc-13.3.1/hypre-2.27.0-bs56wsg2yagtwcz5q5a67ov4tln54p5i;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/gcc-13.3.1/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-fm7mimsvemux42yrsmsvqw6tl4kugyzy;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/gcc-13.3.1/fmt-11.0.2-ggfpxocredbjrldu7ar33cjedhmkm3da;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/gcc-13.3.1/metis-5.1.0-3jtvhjwnhqqaf5v2na7f675qgvlusyx3;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/none-none/gcc-runtime-13.3.1-zsobnkwck2glzcbukupgvgbiwwhdonys;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.26.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66xzj4wm5kfqxyc4vgox7gb3;/usr/tce/packages/cuda/cuda-12.9.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/doxygen-1.15.0;/usr/tce/packages/gcc/gcc-13.3.1;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-gcc-13.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1/blt-0.7.1-lj5bghgkodlzmfs75wimnoxuui2quc3a;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1/c2c-1.8.0-tyixdhhwshu35d2vuh6gw5bk3b3k7nqz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-apockdkmc2slp4nexkn6sbspz7pibric;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1/conduit-0.9.5-vlm6nb2rwwmvnxtnxw5p3o27rzsap6vt;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1/mfem-4.9.0-dyhiljftw2yufyry2ybddjlzrst7usbg;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1/py-nanobind-2.7.0-e42etfacct5pxga65kryysakw52u4c63;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-hjzgnbnbz4sifpanhmuqhuuydzmctbbl;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1/umpire-2025.12.0-yxkn4g7yhresecqk2dkjivbahvu3qhpp;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1/adiak-0.4.0-nqiuo5ltc24jpawzi6mlp3xtmngcopiu;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1/libunwind-1.8.3-a7zvz3euhmbhk4as6h6j66ytabzwztxb;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1/hdf5-1.8.23-zyfoez4ztydoqfeubcamjypyhprjpuca;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1/parmetis-4.0.3-sxnyn5chjyiyowzk32dsynslufrofyho;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1/py-mpi4py-4.1.1-l3h5jmmg773bd5oer7makte3ln6rn7xv;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1/py-numpy-2.4.2-to6o5czwkczlh46d6gkubixw5bhzxakl;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1/hypre-2.27.0-3ckgmamfpdf3ng74h76dziarqz63godz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-bt6ckjhqcfr3gjiu76sdhabhppjhjd6t;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1/fmt-11.0.2-5wsybhdkos4xu5i7thboag6245lzc6jj;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1/metis-5.1.0-3qhomb7zt2n7nh46kmc2egkzmcd3tafm;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/none-none/gcc-runtime-13.3.1-xlvjd7vcdngtnvtxcdyqwtquvenekynf;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.26.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66xzj4wm5kfqxyc4vgox7gb3;/usr/tce/packages/cuda/cuda-12.9.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/doxygen-1.15.0;/usr/tce/packages/gcc/gcc-13.3.1;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-gcc-13.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/gcc-13.3.1/axom-develop-5sjqzzwpctcjengeuc7nl7pz6j66smdo/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/gcc-13.3.1/axom-develop-5sjqzzwpctcjengeuc7nl7pz6j66smdo/lib64;;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1/axom-develop-fzmegf77c74yozjd72rl5iqynh23wqhx/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1/axom-develop-fzmegf77c74yozjd72rl5iqynh23wqhx/lib64;;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/gcc-13.3.1/axom-develop-5sjqzzwpctcjengeuc7nl7pz6j66smdo/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/gcc-13.3.1/axom-develop-5sjqzzwpctcjengeuc7nl7pz6j66smdo/lib64;;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1/axom-develop-fzmegf77c74yozjd72rl5iqynh23wqhx/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1/axom-develop-fzmegf77c74yozjd72rl5iqynh23wqhx/lib64;;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ # Compilers #------------------------------------------------------------------------------ -# Compiler Spec: gcc@13.3.1/xrm3n2pex2en2wvm6p4ap62nieoz7kne +# Compiler Spec: gcc@13.3.1/zbmuyoury7g5npf6fa7lwwxbjninyheh #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/gcc" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/gcc" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/g++" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/g++" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/gfortran" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/gfortran" CACHE PATH "") else() @@ -37,6 +37,10 @@ else() endif() +set(CMAKE_C_FLAGS "-fPIC" CACHE STRING "") + +set(CMAKE_CXX_FLAGS "-fPIC" CACHE STRING "") + set(CMAKE_Fortran_FLAGS "-fPIC" CACHE STRING "") set(ENABLE_FORTRAN ON CACHE BOOL "") @@ -81,7 +85,7 @@ set(ENABLE_CUDA ON CACHE BOOL "") set(CMAKE_CUDA_SEPARABLE_COMPILATION ON CACHE BOOL "") -set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -restrict --expt-extended-lambda --expt-relaxed-constexpr " CACHE STRING "" FORCE) +set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -restrict --expt-extended-lambda --expt-relaxed-constexpr -Xcompiler=-fPIC " CACHE STRING "" FORCE) # nvcc does not like gtest's 'pthreads' flag @@ -99,29 +103,29 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/gcc-13.3.1" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1" CACHE PATH "") -set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-xyt2d2ot4eozuhrjv7mccoe7tw6ef7ui" CACHE PATH "") +set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-vlm6nb2rwwmvnxtnxw5p3o27rzsap6vt" CACHE PATH "") -set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-jkclfdpl7igj3ur3ufnpt6dupjfvotpc" CACHE PATH "") +set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-tyixdhhwshu35d2vuh6gw5bk3b3k7nqz" CACHE PATH "") -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-kjei2ccah6sei4jplqabrucxltyq4zfb" CACHE PATH "") +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-dyhiljftw2yufyry2ybddjlzrst7usbg" CACHE PATH "") -set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-2pnsuxpt6r2wmdc6sqyku6ki6na4gcfl" CACHE PATH "") +set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-zyfoez4ztydoqfeubcamjypyhprjpuca" CACHE PATH "") set(LUA_DIR "/usr" CACHE PATH "") -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-vmsmvq25y56dkomrba3g63ls5nuga6xm" CACHE PATH "") +set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-hjzgnbnbz4sifpanhmuqhuuydzmctbbl" CACHE PATH "") -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-kjyb6uox2o7qtfna22uqzxrqck7lukbt" CACHE PATH "") +set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-yxkn4g7yhresecqk2dkjivbahvu3qhpp" CACHE PATH "") # OPENCASCADE not built -set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-i5bahteezqmswdv2ulpfvfogmuue463q" CACHE PATH "") +set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-nqiuo5ltc24jpawzi6mlp3xtmngcopiu" CACHE PATH "") -set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-qekykotizhycp2p4deoltaa227sajuvj" CACHE PATH "") +set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-apockdkmc2slp4nexkn6sbspz7pibric" CACHE PATH "") -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-fm7mimsvemux42yrsmsvqw6tl4kugyzy" CACHE PATH "") +set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-bt6ckjhqcfr3gjiu76sdhabhppjhjd6t" CACHE PATH "") # scr not built @@ -149,14 +153,16 @@ set(CPPCHECK_EXECUTABLE "${DEVTOOLS_ROOT}/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66x set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/kky5oskfhacihjb5gpy7qkeh6edlrtcb/doxygen-1.15.0/bin/doxygen" CACHE PATH "") -set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-szozdpwzge7ah4bsilit6fijezxqbfop/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-e42etfacct5pxga65kryysakw52u4c63/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-to6o5czwkczlh46d6gkubixw5bhzxakl/lib/python3.13/site-packages" CACHE PATH "") -set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-z4yapdkn22w6adfrl7ibucqiokgrwb7f/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/none-none/py-pluggy-1.6.0-6zab4pck6v7johubbhalr2waowbwpjei/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/none-none/py-pluggy-1.6.0-6zab4pck6v7johubbhalr2waowbwpjei/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/none-none/py-iniconfig-2.1.0-xwmbxuhk2oo3dcuizdp3z4hqbpqykkom/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/none-none/py-iniconfig-2.1.0-xwmbxuhk2oo3dcuizdp3z4hqbpqykkom/lib/python3.13/site-packages" CACHE PATH "") +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-l3h5jmmg773bd5oer7makte3ln6rn7xv/lib/python3.13/site-packages" CACHE PATH "") diff --git a/host-configs/matrix-toss_4_x86_64_ib-llvm@19.1.3_cuda.cmake b/host-configs/matrix-toss_4_x86_64_ib-llvm@19.1.3_cuda.cmake index fcb7db0261..c707a14df8 100644 --- a/host-configs/matrix-toss_4_x86_64_ib-llvm@19.1.3_cuda.cmake +++ b/host-configs/matrix-toss_4_x86_64_ib-llvm@19.1.3_cuda.cmake @@ -4,28 +4,28 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.26.3/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/llvm-19.1.3/blt-0.7.1-577uis2w7b3ipf4v4v6y6vogmy52d6ay;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/llvm-19.1.3/c2c-1.8.0-pk3z5zabg2gtkur2c4mqzc5zz7z5f2tv;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/llvm-19.1.3/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-ftzyobuu2a4siddiotqw4652bctzj3ke;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/llvm-19.1.3/conduit-0.9.5-gracxngtjim7b6zjsb5z4kf7qk347eo2;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/llvm-19.1.3/mfem-4.9.0-vvfbyadobvw4sxcdwuulbfs2lneeczet;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/llvm-19.1.3/py-nanobind-2.7.0-6akjhhlga6h3jx75swovfvo76k6rw6fp;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/llvm-19.1.3/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-aibnhmtfonzvsz7owanyr6m3eelmwfvj;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/llvm-19.1.3/umpire-2025.12.0-3xllaqzq537svpt3ie6jhzoaxciuut2i;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/llvm-19.1.3/adiak-0.4.0-27bezbcpd5jxnvs65h3m6u2aheplxbmv;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/llvm-19.1.3/libunwind-1.8.3-ddv7ypujzw2wvlzabw3d42qygcbtofuq;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/llvm-19.1.3/hdf5-1.8.23-b37o2ze3w5sozwz6jmalg4pgg66jcogm;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/llvm-19.1.3/parmetis-4.0.3-slhcesf5fhsucwxtlxxana67jcrligfy;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/llvm-19.1.3/py-numpy-2.4.2-7qsjxyuuh7rh5hccdziewhwqymkmepb3;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/llvm-19.1.3/hypre-2.27.0-l2mxwhcndyhn4pkbe4rthke2rlkmgdbd;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/llvm-19.1.3/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-crik3a76mg7gqb6xsqkijjk3qzlw4mcl;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/llvm-19.1.3/fmt-11.0.2-uougkyc3rcyd7punvwjtcme2zlhehpgz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/none-none/gcc-runtime-13.3.1-zsobnkwck2glzcbukupgvgbiwwhdonys;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/llvm-19.1.3/metis-5.1.0-3jeshrn7tydsgn5zdkf3iirdhw3u3oeq;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.26.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66xzj4wm5kfqxyc4vgox7gb3;/usr/tce/packages/cuda/cuda-12.9.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/doxygen-1.15.0;/usr/tce/packages/gcc/gcc-13.3.1;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-clang-19.1.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3/blt-0.7.1-ux6wzebtj6vtgnayf4gqv2rr62ulwwfc;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3/c2c-1.8.0-wto5u3qsffjhbxirygr7kqqjlg55pyv6;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-lpeeewbzwhzppynnxvwzs6kz7dyblozq;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3/conduit-0.9.5-vqo2x2n6mz2vs4eg7kynwmlpeolsuim5;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3/mfem-4.9.0-6kl7ian5mzf5ypjvbjaghzk7s3ez62nz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3/py-nanobind-2.7.0-zxodixu5egwgeorhpe3ciggwsv3ndfxb;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-7hlq5rt36tbau3kruxvg7y62rs3wgn7k;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3/umpire-2025.12.0-feuig5ip7m5e2b4ejmchtyir77g5dxeb;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3/adiak-0.4.0-do3kop54yldi4hz43usrtnx2zyhvwfsv;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3/libunwind-1.8.3-ck74b2escwzf2xtpiafdvqwmat26uuoe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3/hdf5-1.8.23-vpibouztpyzmgikg4dg2egwf2u53mmpl;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3/parmetis-4.0.3-fs7gps55rp6znnpz3b4zkd35jjkfycfc;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3/py-mpi4py-4.1.1-hrvj5zyy3xo2rbzvbz4ed7kjye7ssgmk;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3/py-numpy-2.4.2-73lpwcid33wlekelrxoh24535lzexwwg;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3/hypre-2.27.0-vksttbhyimswj3oxopuulk5xfa7nkhmk;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-biicbsrzigcqj4oofalk4povvcnibwea;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3/fmt-11.0.2-ekc2idnbqipl2pqb4nugxoa7sj7aigpo;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/none-none/gcc-runtime-13.3.1-xlvjd7vcdngtnvtxcdyqwtquvenekynf;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3/metis-5.1.0-7gsm27yycsippuh2l4fwei4dtvevbqgh;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.26.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66xzj4wm5kfqxyc4vgox7gb3;/usr/tce/packages/cuda/cuda-12.9.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/doxygen-1.15.0;/usr/tce/packages/gcc/gcc-13.3.1;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-clang-19.1.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/llvm-19.1.3/axom-develop-so3wm2mhso745nxfh2c4gcma5vgtynxi/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/llvm-19.1.3/axom-develop-so3wm2mhso745nxfh2c4gcma5vgtynxi/lib64;;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/llvm-19.1.3-gy2lu5xbi4csr2k47emlajzfs5mlsd4g/lib/x86_64-unknown-linux-gnu;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13;/usr/tce/packages/clang/clang-19.1.3/lib;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3/axom-develop-wxy5sh2abd2fekvyyulaxy3y3zrwcn7w/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3/axom-develop-wxy5sh2abd2fekvyyulaxy3y3zrwcn7w/lib64;;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/llvm-19.1.3-gy2lu5xbi4csr2k47emlajzfs5mlsd4g/lib/x86_64-unknown-linux-gnu;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13;/usr/tce/packages/clang/clang-19.1.3/lib;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/llvm-19.1.3/axom-develop-so3wm2mhso745nxfh2c4gcma5vgtynxi/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/llvm-19.1.3/axom-develop-so3wm2mhso745nxfh2c4gcma5vgtynxi/lib64;;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/llvm-19.1.3-gy2lu5xbi4csr2k47emlajzfs5mlsd4g/lib/x86_64-unknown-linux-gnu;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13;/usr/tce/packages/clang/clang-19.1.3/lib;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3/axom-develop-wxy5sh2abd2fekvyyulaxy3y3zrwcn7w/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3/axom-develop-wxy5sh2abd2fekvyyulaxy3y3zrwcn7w/lib64;;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/llvm-19.1.3-gy2lu5xbi4csr2k47emlajzfs5mlsd4g/lib/x86_64-unknown-linux-gnu;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13;/usr/tce/packages/clang/clang-19.1.3/lib;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ # Compilers #------------------------------------------------------------------------------ -# Compiler Spec: llvm@19.1.3/4rf6d7atkulu6wz7bucycjwtiuwqfy24 +# Compiler Spec: llvm@19.1.3/m2tcgbxfcdjwdmyrkqwamlljh4qeiie2 #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/clang/clang" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/clang/clang" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/clang/clang++" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/clang/clang++" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/gfortran" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/gfortran" CACHE PATH "") else() @@ -37,6 +37,10 @@ else() endif() +set(CMAKE_C_FLAGS "-fPIC" CACHE STRING "") + +set(CMAKE_CXX_FLAGS "-fPIC" CACHE STRING "") + set(CMAKE_Fortran_FLAGS "-fPIC" CACHE STRING "") set(ENABLE_FORTRAN ON CACHE BOOL "") @@ -83,7 +87,7 @@ set(ENABLE_CUDA ON CACHE BOOL "") set(CMAKE_CUDA_SEPARABLE_COMPILATION ON CACHE BOOL "") -set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -restrict --expt-extended-lambda --expt-relaxed-constexpr " CACHE STRING "" FORCE) +set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -restrict --expt-extended-lambda --expt-relaxed-constexpr -Xcompiler=-fPIC " CACHE STRING "" FORCE) # nvcc does not like gtest's 'pthreads' flag @@ -101,29 +105,29 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/llvm-19.1.3" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3" CACHE PATH "") -set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-gracxngtjim7b6zjsb5z4kf7qk347eo2" CACHE PATH "") +set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-vqo2x2n6mz2vs4eg7kynwmlpeolsuim5" CACHE PATH "") -set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-pk3z5zabg2gtkur2c4mqzc5zz7z5f2tv" CACHE PATH "") +set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-wto5u3qsffjhbxirygr7kqqjlg55pyv6" CACHE PATH "") -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-vvfbyadobvw4sxcdwuulbfs2lneeczet" CACHE PATH "") +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-6kl7ian5mzf5ypjvbjaghzk7s3ez62nz" CACHE PATH "") -set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-b37o2ze3w5sozwz6jmalg4pgg66jcogm" CACHE PATH "") +set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-vpibouztpyzmgikg4dg2egwf2u53mmpl" CACHE PATH "") set(LUA_DIR "/usr" CACHE PATH "") -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-aibnhmtfonzvsz7owanyr6m3eelmwfvj" CACHE PATH "") +set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-7hlq5rt36tbau3kruxvg7y62rs3wgn7k" CACHE PATH "") -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-3xllaqzq537svpt3ie6jhzoaxciuut2i" CACHE PATH "") +set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-feuig5ip7m5e2b4ejmchtyir77g5dxeb" CACHE PATH "") # OPENCASCADE not built -set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-27bezbcpd5jxnvs65h3m6u2aheplxbmv" CACHE PATH "") +set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-do3kop54yldi4hz43usrtnx2zyhvwfsv" CACHE PATH "") -set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-ftzyobuu2a4siddiotqw4652bctzj3ke" CACHE PATH "") +set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-lpeeewbzwhzppynnxvwzs6kz7dyblozq" CACHE PATH "") -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-crik3a76mg7gqb6xsqkijjk3qzlw4mcl" CACHE PATH "") +set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-biicbsrzigcqj4oofalk4povvcnibwea" CACHE PATH "") # scr not built @@ -151,14 +155,16 @@ set(CPPCHECK_EXECUTABLE "${DEVTOOLS_ROOT}/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66x set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/kky5oskfhacihjb5gpy7qkeh6edlrtcb/doxygen-1.15.0/bin/doxygen" CACHE PATH "") -set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-6akjhhlga6h3jx75swovfvo76k6rw6fp/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-zxodixu5egwgeorhpe3ciggwsv3ndfxb/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-73lpwcid33wlekelrxoh24535lzexwwg/lib/python3.13/site-packages" CACHE PATH "") -set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-7qsjxyuuh7rh5hccdziewhwqymkmepb3/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/none-none/py-pluggy-1.6.0-6zab4pck6v7johubbhalr2waowbwpjei/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/none-none/py-pluggy-1.6.0-6zab4pck6v7johubbhalr2waowbwpjei/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/none-none/py-iniconfig-2.1.0-xwmbxuhk2oo3dcuizdp3z4hqbpqykkom/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_03_11_13_12_38/none-none/py-iniconfig-2.1.0-xwmbxuhk2oo3dcuizdp3z4hqbpqykkom/lib/python3.13/site-packages" CACHE PATH "") +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-hrvj5zyy3xo2rbzvbz4ed7kjye7ssgmk/lib/python3.13/site-packages" CACHE PATH "") diff --git a/host-configs/tioga-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake b/host-configs/tioga-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake index 3733dd79f9..a5afa28ab3 100644 --- a/host-configs/tioga-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake +++ b/host-configs/tioga-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake @@ -4,13 +4,13 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.29.2/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/blt-0.7.1-tj6h3jr6fjdlxux2yd4wvdy7xwxxuh23;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/c2c-1.8.0-xav3pjepsvpzrcqd5dcom4is4rioejzr;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-37ef6g3godgxug45i3u3x26awgovbhxa;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/conduit-0.9.5-oyjekm66om6cx7yndhpv3yumtxgaaroi;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/lua-5.4.8-ty6vxrgtb5lsthp7rrcy7vx3x4yknrms;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/mfem-4.9.0-iiiowpg2cxn346mv67ycscrcrt5eacdk;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/py-nanobind-2.7.0-h7v5ggotyqa5ul7kpyc2yfojjkah4ium;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/adiak-0.4.0-w7lj246x4whjefszgyhapkebe3cjd7bi;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/libunwind-1.8.3-htd47rshpgaa6w73jemf7tycnnlkn5ws;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/hdf5-1.8.23-vy4rnk6awhcfgte2wuwisgp3t3y4gzwy;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/parmetis-4.0.3-jqlganykrtm62xb3ve2eetb3gltn5nki;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/py-numpy-2.4.2-vdrslpslcsdjba3zyryp27z4tmpn3ayu;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/hypre-2.27.0-gjrhbi2v66y2yfxgq7vegqpcawo2b35r;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-iwmhvbuhywrcyi4ojshrizfpccb53g3u;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/umpire-2025.12.0-qkp2thztbvdojybrli7tpxea5duxagkn;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/zlib-1.3.1-6yddik2qxvkwtfcckdhbdgos7awok5za;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/metis-5.1.0-bryzqxg7sgprevvt3ux5np2uxam4wbvp;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-xdstcq3vkkl6gysd7agincseoncdi5nz;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/fmt-11.0.2-vjndv6co4e6qonqazcmklw3pamvmos74;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cce-tce/cce-20.0.0;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.32-rocmcc-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/blt-0.7.1-tj6h3jr6fjdlxux2yd4wvdy7xwxxuh23;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/c2c-1.8.0-xav3pjepsvpzrcqd5dcom4is4rioejzr;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-37ef6g3godgxug45i3u3x26awgovbhxa;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/conduit-0.9.5-oyjekm66om6cx7yndhpv3yumtxgaaroi;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/lua-5.4.8-ty6vxrgtb5lsthp7rrcy7vx3x4yknrms;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/mfem-4.9.0-iiiowpg2cxn346mv67ycscrcrt5eacdk;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/py-nanobind-2.7.0-h7v5ggotyqa5ul7kpyc2yfojjkah4ium;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/adiak-0.4.0-w7lj246x4whjefszgyhapkebe3cjd7bi;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/libunwind-1.8.3-htd47rshpgaa6w73jemf7tycnnlkn5ws;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/hdf5-1.8.23-vy4rnk6awhcfgte2wuwisgp3t3y4gzwy;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/parmetis-4.0.3-jqlganykrtm62xb3ve2eetb3gltn5nki;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/py-mpi4py-4.1.1-gla2ur2g4lb44jyi2tky6p2ynzgseonn;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/py-numpy-2.4.2-vdrslpslcsdjba3zyryp27z4tmpn3ayu;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/hypre-2.27.0-gjrhbi2v66y2yfxgq7vegqpcawo2b35r;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-iwmhvbuhywrcyi4ojshrizfpccb53g3u;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/umpire-2025.12.0-qkp2thztbvdojybrli7tpxea5duxagkn;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/zlib-1.3.1-6yddik2qxvkwtfcckdhbdgos7awok5za;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/metis-5.1.0-bryzqxg7sgprevvt3ux5np2uxam4wbvp;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-xdstcq3vkkl6gysd7agincseoncdi5nz;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/fmt-11.0.2-vjndv6co4e6qonqazcmklw3pamvmos74;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cce-tce/cce-20.0.0;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.32-rocmcc-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/axom-develop-2xptn5vzdbqfiuuctci6jdklfwydwete/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/axom-develop-2xptn5vzdbqfiuuctci6jdklfwydwete/lib64;;/opt/cray/pe/cce/20.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/axom-develop-scv2k3ekpxlqvhgfmb6tt3qfk6vez3q5/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/axom-develop-scv2k3ekpxlqvhgfmb6tt3qfk6vez3q5/lib64;;/opt/cray/pe/cce/20.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/axom-develop-2xptn5vzdbqfiuuctci6jdklfwydwete/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0/axom-develop-2xptn5vzdbqfiuuctci6jdklfwydwete/lib64;;/opt/cray/pe/cce/20.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/axom-develop-scv2k3ekpxlqvhgfmb6tt3qfk6vez3q5/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/axom-develop-scv2k3ekpxlqvhgfmb6tt3qfk6vez3q5/lib64;;/opt/cray/pe/cce/20.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") @@ -21,11 +21,11 @@ set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/cce/craycc" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/cce/craycc" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/cce/case-insensitive/crayCC" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/cce/case-insensitive/crayCC" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/cce/crayftn" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/cce/crayftn" CACHE PATH "") else() @@ -104,7 +104,7 @@ set(BLT_OPENMP_LINK_FLAGS "$<$>:-fopenmp=libomp> # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/cce-20.0.0" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0" CACHE PATH "") set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-oyjekm66om6cx7yndhpv3yumtxgaaroi" CACHE PATH "") @@ -156,12 +156,14 @@ set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-h7v5ggotyqa5ul7kpyc2yfojjkah4ium/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj/lib/python3.13/site-packages" CACHE PATH "") set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-vdrslpslcsdjba3zyryp27z4tmpn3ayu/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/py-pluggy-1.6.0-27aoz44dedjxa6j6r6cavpqyqmpknidb/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/py-pluggy-1.6.0-27aoz44dedjxa6j6r6cavpqyqmpknidb/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/py-iniconfig-2.1.0-7rty7n4wq6kxxrzflm7wbxqzhhc6ph7q/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/py-iniconfig-2.1.0-7rty7n4wq6kxxrzflm7wbxqzhhc6ph7q/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-gla2ur2g4lb44jyi2tky6p2ynzgseonn/lib/python3.13/site-packages" CACHE PATH "") diff --git a/host-configs/tioga-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake b/host-configs/tioga-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake index d9a623034b..63b059e020 100644 --- a/host-configs/tioga-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake +++ b/host-configs/tioga-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake @@ -4,13 +4,13 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.29.2/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.3.1/adiak-0.4.0-vlugt2gwkx5wzlwudznxg3p2plq76pjq;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.3.1/blt-0.7.1-sv74qxlkpkll7mm6dhfjagyfog254w7c;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.3.1/c2c-1.8.0-tfrrofjzn3rdvyktevespobwrpjai5j4;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.3.1/conduit-0.9.5-vnlbr73jbwhqyvh2q76csdtve3ekpncc;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.3.1/lua-5.4.8-e3i2pbc52bfhrnxbaniektzp3x5jvyxr;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.3.1/mfem-4.9.0-jxeezxksp3c7i53h6dhqf2zz6gnaipub;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.3.1/py-nanobind-2.7.0-4sgpkalsntbmuim5oltb7ue4gggt6rv5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.3.1/hdf5-1.8.23-liive5kzeooveetbjhil4ic7hi3ccsg4;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.3.1/parmetis-4.0.3-fiyw3adearn3nmxjifcqtuzwy24fshp5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.3.1/py-numpy-2.4.2-ij66enxl4hmrmwua5bsec2yslylneqbw;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.3.1/hypre-2.27.0-cnukrpfl32kl5q6ud4wozdqbgtqoo7jx;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.3.1/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-5c3lqbqcjmekrwbfeg6ogptz7ssj3n4y;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.3.1/umpire-2025.12.0-i2dwv2l2jra74eazijtvprcx7acsupx4;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.3.1/zlib-1.3.1-dsduxyfnks4bhrkpbskff35l5y36ofp7;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.3.1/metis-5.1.0-6mtxrj6jbrvyd3xpq4pvluywbndjx42w;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.3.1/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-7nvneljwisxwircaqydhfcl3hl65bmr2;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.3.1/fmt-11.0.2-kw3vpg2ajhrgi7kozhcioz3nrgc6fh3o;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.3.1;/opt/rocm-6.3.1;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.3.1;/opt/rocm-6.3.1;/opt/rocm-6.3.1" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1/adiak-0.4.0-vlugt2gwkx5wzlwudznxg3p2plq76pjq;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1/blt-0.7.1-sv74qxlkpkll7mm6dhfjagyfog254w7c;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1/c2c-1.8.0-tfrrofjzn3rdvyktevespobwrpjai5j4;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1/conduit-0.9.5-vnlbr73jbwhqyvh2q76csdtve3ekpncc;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1/lua-5.4.8-e3i2pbc52bfhrnxbaniektzp3x5jvyxr;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1/mfem-4.9.0-jxeezxksp3c7i53h6dhqf2zz6gnaipub;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1/py-nanobind-2.7.0-4sgpkalsntbmuim5oltb7ue4gggt6rv5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1/hdf5-1.8.23-liive5kzeooveetbjhil4ic7hi3ccsg4;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1/parmetis-4.0.3-fiyw3adearn3nmxjifcqtuzwy24fshp5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1/py-mpi4py-4.1.1-ukxrsa2wugnmhh3glq25muf5hqexaf62;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1/py-numpy-2.4.2-ij66enxl4hmrmwua5bsec2yslylneqbw;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1/hypre-2.27.0-cnukrpfl32kl5q6ud4wozdqbgtqoo7jx;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-5c3lqbqcjmekrwbfeg6ogptz7ssj3n4y;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1/umpire-2025.12.0-i2dwv2l2jra74eazijtvprcx7acsupx4;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1/zlib-1.3.1-dsduxyfnks4bhrkpbskff35l5y36ofp7;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1/metis-5.1.0-6mtxrj6jbrvyd3xpq4pvluywbndjx42w;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-7nvneljwisxwircaqydhfcl3hl65bmr2;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1/fmt-11.0.2-kw3vpg2ajhrgi7kozhcioz3nrgc6fh3o;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.3.1;/opt/rocm-6.3.1;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.3.1;/opt/rocm-6.3.1;/opt/rocm-6.3.1" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.3.1/axom-develop-jx2n4ai2kjdjrg7o6oc6nrugd5kvsk24/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.3.1/axom-develop-jx2n4ai2kjdjrg7o6oc6nrugd5kvsk24/lib64;;/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1/axom-develop-7trkhc3ctsx3bgt6uv2brdgeeyalxomu/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1/axom-develop-7trkhc3ctsx3bgt6uv2brdgeeyalxomu/lib64;;/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.3.1/axom-develop-jx2n4ai2kjdjrg7o6oc6nrugd5kvsk24/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.3.1/axom-develop-jx2n4ai2kjdjrg7o6oc6nrugd5kvsk24/lib64;;/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1/axom-develop-7trkhc3ctsx3bgt6uv2brdgeeyalxomu/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1/axom-develop-7trkhc3ctsx3bgt6uv2brdgeeyalxomu/lib64;;/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") @@ -21,11 +21,11 @@ set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdclang" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdclang" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdclang++" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdclang++" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdflang" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdflang" CACHE PATH "") else() @@ -98,7 +98,7 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.3.1" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1" CACHE PATH "") set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-vnlbr73jbwhqyvh2q76csdtve3ekpncc" CACHE PATH "") @@ -150,12 +150,14 @@ set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-4sgpkalsntbmuim5oltb7ue4gggt6rv5/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj/lib/python3.13/site-packages" CACHE PATH "") set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-ij66enxl4hmrmwua5bsec2yslylneqbw/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/py-pluggy-1.6.0-27aoz44dedjxa6j6r6cavpqyqmpknidb/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/py-pluggy-1.6.0-27aoz44dedjxa6j6r6cavpqyqmpknidb/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/py-iniconfig-2.1.0-7rty7n4wq6kxxrzflm7wbxqzhhc6ph7q/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/py-iniconfig-2.1.0-7rty7n4wq6kxxrzflm7wbxqzhhc6ph7q/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-ukxrsa2wugnmhh3glq25muf5hqexaf62/lib/python3.13/site-packages" CACHE PATH "") diff --git a/host-configs/tioga-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake b/host-configs/tioga-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake index bfd1af74c7..44fb13835a 100644 --- a/host-configs/tioga-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake +++ b/host-configs/tioga-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake @@ -4,13 +4,13 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.29.2/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/blt-0.7.1-2eahqqwjyauupjq3baxftq7kuv3pvdgp;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/c2c-1.8.0-oxqhr2ybswpnzt74f4iritx263ji3uqy;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-sos5uklbsgaycb776a2ufcyje4y24yls;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/conduit-0.9.5-cxdq4qnoptjq6lry5asb2bqodvm3zjig;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/lua-5.4.8-lsey5vgxvpqf5j3xbrlzswrizuwm65vh;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/mfem-4.9.0-xwok5jck2jjg76k4575wkzgyvgopipz2;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/py-nanobind-2.7.0-3nchqpyclgle5w56it23ozl4ttbnrfjd;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/adiak-0.4.0-cbxp6kv6rm4f7oeczuhbplm3yb3zxb6z;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/libunwind-1.8.3-r4b3cptnf27zp34jbrdtlrf6r6dp6n4l;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/hdf5-1.8.23-wb7u6epuww3tretkcto7jhq2i7pffimg;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/parmetis-4.0.3-d6ed2qesb4yprvpetx3fso35rx6amf5b;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/py-numpy-2.4.2-t7cvohiksz7deeldwxrtqfmahcvanapb;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/hypre-2.27.0-pxqhlqwksiscco65tsz5somb4ctdq3q2;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-i5stpeet653njjadpjsekbvxlqkng2zn;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/umpire-2025.12.0-hpevb7stqsg6dltp3t2tcnzzps7tgywb;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/zlib-1.3.1-ijxa3tdiibemxtng46hrremmbytl2dqq;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/metis-5.1.0-mubwzuka5byhpbzmwaxjhtffhst3oj3q;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-j7my6ilwzwzvbaxzscbvishmj5mgyxnz;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/fmt-11.0.2-unmvs6vefcyfcfnzlie2eag5i2eib35d;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/blt-0.7.1-2eahqqwjyauupjq3baxftq7kuv3pvdgp;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/c2c-1.8.0-oxqhr2ybswpnzt74f4iritx263ji3uqy;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-sos5uklbsgaycb776a2ufcyje4y24yls;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/conduit-0.9.5-cxdq4qnoptjq6lry5asb2bqodvm3zjig;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/lua-5.4.8-lsey5vgxvpqf5j3xbrlzswrizuwm65vh;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/mfem-4.9.0-xwok5jck2jjg76k4575wkzgyvgopipz2;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/py-nanobind-2.7.0-3nchqpyclgle5w56it23ozl4ttbnrfjd;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/adiak-0.4.0-cbxp6kv6rm4f7oeczuhbplm3yb3zxb6z;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/libunwind-1.8.3-r4b3cptnf27zp34jbrdtlrf6r6dp6n4l;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/hdf5-1.8.23-wb7u6epuww3tretkcto7jhq2i7pffimg;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/parmetis-4.0.3-d6ed2qesb4yprvpetx3fso35rx6amf5b;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/py-mpi4py-4.1.1-kwpdge3wqmmxvxlocfdz4mgbc3zfihn6;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/py-numpy-2.4.2-t7cvohiksz7deeldwxrtqfmahcvanapb;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/hypre-2.27.0-pxqhlqwksiscco65tsz5somb4ctdq3q2;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-i5stpeet653njjadpjsekbvxlqkng2zn;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/umpire-2025.12.0-hpevb7stqsg6dltp3t2tcnzzps7tgywb;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/zlib-1.3.1-ijxa3tdiibemxtng46hrremmbytl2dqq;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/metis-5.1.0-mubwzuka5byhpbzmwaxjhtffhst3oj3q;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-j7my6ilwzwzvbaxzscbvishmj5mgyxnz;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/fmt-11.0.2-unmvs6vefcyfcfnzlie2eag5i2eib35d;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/axom-develop-j5cg6odzkrlra4lrnhap3qtdbfgnwvlk/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/axom-develop-j5cg6odzkrlra4lrnhap3qtdbfgnwvlk/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/axom-develop-6a3pinjancledu7q5pzdp5ykhictf5ej/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/axom-develop-6a3pinjancledu7q5pzdp5ykhictf5ej/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/axom-develop-j5cg6odzkrlra4lrnhap3qtdbfgnwvlk/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3/axom-develop-j5cg6odzkrlra4lrnhap3qtdbfgnwvlk/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/axom-develop-6a3pinjancledu7q5pzdp5ykhictf5ej/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/axom-develop-6a3pinjancledu7q5pzdp5ykhictf5ej/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") @@ -21,11 +21,11 @@ set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdclang" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdclang" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdclang++" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdclang++" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdflang" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdflang" CACHE PATH "") else() @@ -98,7 +98,7 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/llvm-amdgpu-6.4.3" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3" CACHE PATH "") set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-cxdq4qnoptjq6lry5asb2bqodvm3zjig" CACHE PATH "") @@ -150,12 +150,14 @@ set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-3nchqpyclgle5w56it23ozl4ttbnrfjd/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj/lib/python3.13/site-packages" CACHE PATH "") set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-t7cvohiksz7deeldwxrtqfmahcvanapb/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/py-pluggy-1.6.0-27aoz44dedjxa6j6r6cavpqyqmpknidb/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/py-pluggy-1.6.0-27aoz44dedjxa6j6r6cavpqyqmpknidb/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_13_17/none-none/py-iniconfig-2.1.0-7rty7n4wq6kxxrzflm7wbxqzhhc6ph7q/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/py-iniconfig-2.1.0-7rty7n4wq6kxxrzflm7wbxqzhhc6ph7q/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-kwpdge3wqmmxvxlocfdz4mgbc3zfihn6/lib/python3.13/site-packages" CACHE PATH "") diff --git a/host-configs/tuolumne-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake b/host-configs/tuolumne-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake index 16d26ee5a8..f62de2da76 100644 --- a/host-configs/tuolumne-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake +++ b/host-configs/tuolumne-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake @@ -4,13 +4,13 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.29.2/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/blt-0.7.1-af45hpsxdxploxhqqi4irjmy4vz2omqx;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/c2c-1.8.0-cpdkv2uxkhs3qmzqakt4sht3vuiucdwt;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-wytd46zvuquxg2rqf3zec5ldbmjeaat6;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/conduit-0.9.5-pbrrhvrsmnewby6q3u35ceppt5fxme2p;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/lua-5.4.8-y6z3polqakbuhn6nh6muri3rjxqmiioa;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/mfem-4.9.0-wpbbiugqvdw7bylkmyyf2quazcoptzah;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/py-nanobind-2.7.0-ieb2bzuz435ydpyiq2uj36yyqjbrffml;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/adiak-0.4.0-lzgexbo5qyzepgju2acyy6d2qft443tb;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/libunwind-1.8.3-zvteigxlopfo5tkdr2n2pglgosg42bah;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/hdf5-1.8.23-to3lvqwzxrqzad65zbvnbtvyt4k63uxe;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/parmetis-4.0.3-bajtzsmahjg2wvcjzo5gfb2hr5oj2elc;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/py-numpy-2.4.2-xw5ysy75zfqlalmbmtl5phd2qip43hlo;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/hypre-2.27.0-knkmxfbf3xcut2wmamjsmhrqf7u7xmi7;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-z3eibnxgk3jeplydlcizrhurpibzqzqv;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/umpire-2025.12.0-eqokbtuch3pwifb225w2i3rrft5ddcgv;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/zlib-1.3.1-f3sdiqmy2whvmdn2fxeol3k7oqwouays;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/metis-5.1.0-v4d34x7btqhotktvu5bgk4iwk6h2lumb;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-eplsxvli6nnrgdl4l3hl7cizytginfhp;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/fmt-11.0.2-nzzmwf4nofjv46yeugehjo64opvfbbzr;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cce-tce/cce-20.0.0;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.32-rocmcc-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/blt-0.7.1-af45hpsxdxploxhqqi4irjmy4vz2omqx;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/c2c-1.8.0-cpdkv2uxkhs3qmzqakt4sht3vuiucdwt;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-wytd46zvuquxg2rqf3zec5ldbmjeaat6;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/conduit-0.9.5-pbrrhvrsmnewby6q3u35ceppt5fxme2p;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/lua-5.4.8-y6z3polqakbuhn6nh6muri3rjxqmiioa;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/mfem-4.9.0-wpbbiugqvdw7bylkmyyf2quazcoptzah;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/py-nanobind-2.7.0-ieb2bzuz435ydpyiq2uj36yyqjbrffml;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/adiak-0.4.0-lzgexbo5qyzepgju2acyy6d2qft443tb;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/libunwind-1.8.3-zvteigxlopfo5tkdr2n2pglgosg42bah;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/hdf5-1.8.23-to3lvqwzxrqzad65zbvnbtvyt4k63uxe;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/parmetis-4.0.3-bajtzsmahjg2wvcjzo5gfb2hr5oj2elc;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/py-mpi4py-4.1.1-e5tepnokpccpvjfp6lwnwfi5laakuhdh;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/py-numpy-2.4.2-xw5ysy75zfqlalmbmtl5phd2qip43hlo;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/hypre-2.27.0-knkmxfbf3xcut2wmamjsmhrqf7u7xmi7;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-z3eibnxgk3jeplydlcizrhurpibzqzqv;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/umpire-2025.12.0-eqokbtuch3pwifb225w2i3rrft5ddcgv;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/zlib-1.3.1-f3sdiqmy2whvmdn2fxeol3k7oqwouays;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/metis-5.1.0-v4d34x7btqhotktvu5bgk4iwk6h2lumb;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-eplsxvli6nnrgdl4l3hl7cizytginfhp;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/fmt-11.0.2-nzzmwf4nofjv46yeugehjo64opvfbbzr;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cce-tce/cce-20.0.0;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.32-rocmcc-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/axom-develop-xz7g7s5cdkdr6lixuhs23vyuqqodocrg/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/axom-develop-xz7g7s5cdkdr6lixuhs23vyuqqodocrg/lib64;;/opt/cray/pe/cce/20.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/axom-develop-mdr65pqpz5pq5uhe57wjeu6vp5q3kuoa/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/axom-develop-mdr65pqpz5pq5uhe57wjeu6vp5q3kuoa/lib64;;/opt/cray/pe/cce/20.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/axom-develop-xz7g7s5cdkdr6lixuhs23vyuqqodocrg/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0/axom-develop-xz7g7s5cdkdr6lixuhs23vyuqqodocrg/lib64;;/opt/cray/pe/cce/20.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/axom-develop-mdr65pqpz5pq5uhe57wjeu6vp5q3kuoa/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/axom-develop-mdr65pqpz5pq5uhe57wjeu6vp5q3kuoa/lib64;;/opt/cray/pe/cce/20.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") @@ -21,11 +21,11 @@ set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/cce/craycc" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/cce/craycc" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/cce/case-insensitive/crayCC" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/cce/case-insensitive/crayCC" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/cce/crayftn" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/cce/crayftn" CACHE PATH "") else() @@ -104,7 +104,7 @@ set(BLT_OPENMP_LINK_FLAGS "$<$>:-fopenmp=libomp> # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/cce-20.0.0" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0" CACHE PATH "") set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-pbrrhvrsmnewby6q3u35ceppt5fxme2p" CACHE PATH "") @@ -156,12 +156,14 @@ set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-ieb2bzuz435ydpyiq2uj36yyqjbrffml/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5/lib/python3.13/site-packages" CACHE PATH "") set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-xw5ysy75zfqlalmbmtl5phd2qip43hlo/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/py-pluggy-1.6.0-n4ojmtqyawxz6gipk64pup3ahwz3xqpu/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/py-pluggy-1.6.0-n4ojmtqyawxz6gipk64pup3ahwz3xqpu/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/py-iniconfig-2.1.0-lf3yvch65se22jr7b5unfoxtse4ugkrk/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/py-iniconfig-2.1.0-lf3yvch65se22jr7b5unfoxtse4ugkrk/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-e5tepnokpccpvjfp6lwnwfi5laakuhdh/lib/python3.13/site-packages" CACHE PATH "") diff --git a/host-configs/tuolumne-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake b/host-configs/tuolumne-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake index bd822962a6..64fff8bd85 100644 --- a/host-configs/tuolumne-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake +++ b/host-configs/tuolumne-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake @@ -4,13 +4,13 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.29.2/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.3.1/adiak-0.4.0-c4srnrz5apjetx74dt6xjah63o3a7xcx;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.3.1/blt-0.7.1-kzmxfjvxm4drr2qhspckvwee6mrlc3e5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.3.1/c2c-1.8.0-lm64gc55hpxilmtsw7geirys3q4wacu5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.3.1/conduit-0.9.5-wswspowegvp5byl5inc2cikljzkcqg64;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.3.1/lua-5.4.8-j7ngytniswhoa536kqfzarcraarkicbm;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.3.1/mfem-4.9.0-xwg5un3mlbeog3j4voeltekra43ix3ga;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.3.1/py-nanobind-2.7.0-q2kwozph3gwgfyjnx43jp2gkbaghwxoj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.3.1/hdf5-1.8.23-rb4cjthx4wz4wnpaizwmm2jycijdthqm;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.3.1/parmetis-4.0.3-ng66tt5fmjyfoiuuxyl4rxqv4ompawmd;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.3.1/py-numpy-2.4.2-zcxbrxijjc3miotq6s3kq446afrgycuu;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.3.1/hypre-2.27.0-6m2jwyzqbksxgmdznvh54ytysxyme7jy;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.3.1/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-wcyqyggju4u2ornjjnamgdgfbqjrzsir;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.3.1/umpire-2025.12.0-5w5v22edixuttxwueaagiobpjzn5mcgr;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.3.1/zlib-1.3.1-5w5muvpvct6uruxrddscblsiyizl3uoc;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.3.1/metis-5.1.0-4punmdkye6rovr3u7ph7bgj66qim77du;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.3.1/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-jnalmgzqx2nn2afq4gwhyjda3u6yub7y;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.3.1/fmt-11.0.2-opsfnufdiedpfpob5wgbvhcrhyhzfkaq;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.3.1;/opt/rocm-6.3.1;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.3.1;/opt/rocm-6.3.1;/opt/rocm-6.3.1" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1/adiak-0.4.0-c4srnrz5apjetx74dt6xjah63o3a7xcx;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1/blt-0.7.1-kzmxfjvxm4drr2qhspckvwee6mrlc3e5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1/c2c-1.8.0-lm64gc55hpxilmtsw7geirys3q4wacu5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1/conduit-0.9.5-wswspowegvp5byl5inc2cikljzkcqg64;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1/lua-5.4.8-j7ngytniswhoa536kqfzarcraarkicbm;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1/mfem-4.9.0-xwg5un3mlbeog3j4voeltekra43ix3ga;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1/py-nanobind-2.7.0-q2kwozph3gwgfyjnx43jp2gkbaghwxoj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1/hdf5-1.8.23-rb4cjthx4wz4wnpaizwmm2jycijdthqm;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1/parmetis-4.0.3-ng66tt5fmjyfoiuuxyl4rxqv4ompawmd;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1/py-mpi4py-4.1.1-s4a64moil7qmgczpelmmb344qxxavrzk;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1/py-numpy-2.4.2-zcxbrxijjc3miotq6s3kq446afrgycuu;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1/hypre-2.27.0-6m2jwyzqbksxgmdznvh54ytysxyme7jy;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-wcyqyggju4u2ornjjnamgdgfbqjrzsir;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1/umpire-2025.12.0-5w5v22edixuttxwueaagiobpjzn5mcgr;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1/zlib-1.3.1-5w5muvpvct6uruxrddscblsiyizl3uoc;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1/metis-5.1.0-4punmdkye6rovr3u7ph7bgj66qim77du;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-jnalmgzqx2nn2afq4gwhyjda3u6yub7y;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1/fmt-11.0.2-opsfnufdiedpfpob5wgbvhcrhyhzfkaq;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.3.1;/opt/rocm-6.3.1;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.3.1;/opt/rocm-6.3.1;/opt/rocm-6.3.1" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.3.1/axom-develop-qrojk4ujrhd5b734s2yc73n5u3kvcgqa/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.3.1/axom-develop-qrojk4ujrhd5b734s2yc73n5u3kvcgqa/lib64;;/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1/axom-develop-4srm7we3wjp7l4cfcshzov5nr52jna4r/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1/axom-develop-4srm7we3wjp7l4cfcshzov5nr52jna4r/lib64;;/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.3.1/axom-develop-qrojk4ujrhd5b734s2yc73n5u3kvcgqa/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.3.1/axom-develop-qrojk4ujrhd5b734s2yc73n5u3kvcgqa/lib64;;/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1/axom-develop-4srm7we3wjp7l4cfcshzov5nr52jna4r/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1/axom-develop-4srm7we3wjp7l4cfcshzov5nr52jna4r/lib64;;/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") @@ -21,11 +21,11 @@ set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdclang" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdclang" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdclang++" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdclang++" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdflang" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdflang" CACHE PATH "") else() @@ -98,7 +98,7 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.3.1" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1" CACHE PATH "") set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-wswspowegvp5byl5inc2cikljzkcqg64" CACHE PATH "") @@ -150,12 +150,14 @@ set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-q2kwozph3gwgfyjnx43jp2gkbaghwxoj/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5/lib/python3.13/site-packages" CACHE PATH "") set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-zcxbrxijjc3miotq6s3kq446afrgycuu/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/py-pluggy-1.6.0-n4ojmtqyawxz6gipk64pup3ahwz3xqpu/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/py-pluggy-1.6.0-n4ojmtqyawxz6gipk64pup3ahwz3xqpu/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/py-iniconfig-2.1.0-lf3yvch65se22jr7b5unfoxtse4ugkrk/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/py-iniconfig-2.1.0-lf3yvch65se22jr7b5unfoxtse4ugkrk/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-s4a64moil7qmgczpelmmb344qxxavrzk/lib/python3.13/site-packages" CACHE PATH "") diff --git a/host-configs/tuolumne-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake b/host-configs/tuolumne-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake index 62b6d60407..e20913a2d6 100644 --- a/host-configs/tuolumne-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake +++ b/host-configs/tuolumne-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake @@ -4,13 +4,13 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.29.2/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/blt-0.7.1-4kxhk7nbuiv5oh2ymh7sdimnmor36hmo;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/c2c-1.8.0-h5kio7nseyazg4pzgvj4kxanb3plhbxm;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-rmlwpdacdtpzf4u72ctyroj72soqzf3f;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/conduit-0.9.5-zqywrasos2vbears5hvdwxhulm3xox6q;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/lua-5.4.8-gp4kdewsy4uiy6flsx342lgogcm7fym2;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/mfem-4.9.0-6cgokeizyrjzsjzwmtf35p5jvhnws7n5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/py-nanobind-2.7.0-2kp35jcpb73xuujvcz6kmg4q2npek4gg;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/adiak-0.4.0-cropyu42jr4q6k3zar6mkma6gc2vp6tk;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/libunwind-1.8.3-plszp43gkd75kzjcvge3qxrtb6j2budl;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/hdf5-1.8.23-mgic2kxojd5zeivhaz473zzmzrg3gdqe;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/parmetis-4.0.3-hd2qoczrh3vw3ejm6msitqdgecjk7xwe;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/py-numpy-2.4.2-a3mo27shjtpliwewbm5cci2kf37crrab;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/hypre-2.27.0-4nrl5egspbxmnkpzfym5udby6uruhn3i;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-v74yskgcb7tsgho6bdush5jdlbigkttf;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/umpire-2025.12.0-jft5rocv5ev4b57f3664b2kagyfjra2e;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/zlib-1.3.1-slqujeue3l44x3pgqbu7e2pw3lh2mj5i;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/metis-5.1.0-puzeddu6ufhz5vpxnw7i3i2i6slsvuuj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-zadbjpethvnlhpqwx6vm6ahphk42a4gq;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/fmt-11.0.2-lpnox72rqcneew5hucwzq4nmms4at525;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/blt-0.7.1-4kxhk7nbuiv5oh2ymh7sdimnmor36hmo;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/c2c-1.8.0-h5kio7nseyazg4pzgvj4kxanb3plhbxm;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-rmlwpdacdtpzf4u72ctyroj72soqzf3f;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/conduit-0.9.5-zqywrasos2vbears5hvdwxhulm3xox6q;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/lua-5.4.8-gp4kdewsy4uiy6flsx342lgogcm7fym2;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/mfem-4.9.0-6cgokeizyrjzsjzwmtf35p5jvhnws7n5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/py-nanobind-2.7.0-2kp35jcpb73xuujvcz6kmg4q2npek4gg;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/adiak-0.4.0-cropyu42jr4q6k3zar6mkma6gc2vp6tk;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/libunwind-1.8.3-plszp43gkd75kzjcvge3qxrtb6j2budl;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/hdf5-1.8.23-mgic2kxojd5zeivhaz473zzmzrg3gdqe;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/parmetis-4.0.3-hd2qoczrh3vw3ejm6msitqdgecjk7xwe;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/py-mpi4py-4.1.1-wimx6qtzmouefjmhidpsho6iipv234ay;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/py-numpy-2.4.2-a3mo27shjtpliwewbm5cci2kf37crrab;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/hypre-2.27.0-4nrl5egspbxmnkpzfym5udby6uruhn3i;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-v74yskgcb7tsgho6bdush5jdlbigkttf;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/umpire-2025.12.0-jft5rocv5ev4b57f3664b2kagyfjra2e;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/zlib-1.3.1-slqujeue3l44x3pgqbu7e2pw3lh2mj5i;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/metis-5.1.0-puzeddu6ufhz5vpxnw7i3i2i6slsvuuj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-zadbjpethvnlhpqwx6vm6ahphk42a4gq;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/fmt-11.0.2-lpnox72rqcneew5hucwzq4nmms4at525;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/axom-develop-i4n5ay2l5l2hludfn6z5vyvfwk57p6hx/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/axom-develop-i4n5ay2l5l2hludfn6z5vyvfwk57p6hx/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/axom-develop-iczg7quatuysr5e2ggwwzdsnmdzw7odk/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/axom-develop-iczg7quatuysr5e2ggwwzdsnmdzw7odk/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/axom-develop-i4n5ay2l5l2hludfn6z5vyvfwk57p6hx/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3/axom-develop-i4n5ay2l5l2hludfn6z5vyvfwk57p6hx/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/axom-develop-iczg7quatuysr5e2ggwwzdsnmdzw7odk/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/axom-develop-iczg7quatuysr5e2ggwwzdsnmdzw7odk/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") @@ -21,11 +21,11 @@ set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdclang" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdclang" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdclang++" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdclang++" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdflang" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdflang" CACHE PATH "") else() @@ -98,7 +98,7 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/llvm-amdgpu-6.4.3" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3" CACHE PATH "") set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-zqywrasos2vbears5hvdwxhulm3xox6q" CACHE PATH "") @@ -150,12 +150,14 @@ set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-2kp35jcpb73xuujvcz6kmg4q2npek4gg/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5/lib/python3.13/site-packages" CACHE PATH "") set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-a3mo27shjtpliwewbm5cci2kf37crrab/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/py-pluggy-1.6.0-n4ojmtqyawxz6gipk64pup3ahwz3xqpu/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/py-pluggy-1.6.0-n4ojmtqyawxz6gipk64pup3ahwz3xqpu/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_20_15_15_19/none-none/py-iniconfig-2.1.0-lf3yvch65se22jr7b5unfoxtse4ugkrk/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/py-iniconfig-2.1.0-lf3yvch65se22jr7b5unfoxtse4ugkrk/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-wimx6qtzmouefjmhidpsho6iipv234ay/lib/python3.13/site-packages" CACHE PATH "") From 0b0b0f049c6c88d4f4295baadd969dc99949e4ce Mon Sep 17 00:00:00 2001 From: Brian Han Date: Thu, 30 Apr 2026 14:19:10 -0700 Subject: [PATCH 168/986] Add conflict to ensure +mpi when +python --- scripts/spack/packages/axom/package.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/spack/packages/axom/package.py b/scripts/spack/packages/axom/package.py index 0784bc9f16..55e6b0df94 100644 --- a/scripts/spack/packages/axom/package.py +++ b/scripts/spack/packages/axom/package.py @@ -346,6 +346,9 @@ class Axom(CachedCMakePackage, CudaPackage, ROCmPackage): conflicts("^blt@:0.3.6", when="+rocm") + # python interface requires mpi + conflicts("~mpi", when="+python") + def flag_handler(self, name, flags): if self.spec.satisfies("%cce") and name == "fflags": flags.append("-ef") From 0020f45bd9bd859f351856a9ddfeab19a5ef1409 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 30 Apr 2026 16:47:18 -0700 Subject: [PATCH 169/986] Better handling of colinear segment intersections. --- .../primal/operators/detail/intersect_impl.hpp | 4 ++++ src/axom/primal/operators/intersect.hpp | 6 ++++++ src/axom/primal/tests/primal_intersect.cpp | 16 ++++++++++++---- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/axom/primal/operators/detail/intersect_impl.hpp b/src/axom/primal/operators/detail/intersect_impl.hpp index 7e5a561cc4..c743c6b364 100644 --- a/src/axom/primal/operators/detail/intersect_impl.hpp +++ b/src/axom/primal/operators/detail/intersect_impl.hpp @@ -140,6 +140,10 @@ bool TriangleIntersection2D(const Triangle2& t1, const Triangle2& t2, bool inclu * \param P The first line segment. * \param Q The second line segment. * \param[out] intersection The intersection point where the segments intersect. + * For collinear overlapping segments, this stores the overlap endpoint with + * the smaller parameter on \a P. + * \param EPS Tolerance used to detect degeneracy, collinearity, and + * coincidence of the candidate intersection points. * * \return True if the line segments intersect; False otherwise. */ diff --git a/src/axom/primal/operators/intersect.hpp b/src/axom/primal/operators/intersect.hpp index ec88df9483..84b0f946f9 100644 --- a/src/axom/primal/operators/intersect.hpp +++ b/src/axom/primal/operators/intersect.hpp @@ -50,7 +50,13 @@ namespace primal * \param [in] P A 3D line segment * \param [in] Q A 3D line segment * \param [out] intersection Intersection point of P and Q. + * When the segments are collinear and overlap over a nonzero interval, + * \a intersection is set to the first point of the overlap encountered when + * moving from \a P.source() to \a P.target(). + * \param [in] EPS Tolerance used in the segment-segment intersection test. * \return true iff P intersects with Q, otherwise, false. + * \note The value of \a intersection should be ignored when this function + * returns false. */ template AXOM_HOST_DEVICE bool intersect(const Segment& P, diff --git a/src/axom/primal/tests/primal_intersect.cpp b/src/axom/primal/tests/primal_intersect.cpp index 08188788a5..195555791e 100644 --- a/src/axom/primal/tests/primal_intersect.cpp +++ b/src/axom/primal/tests/primal_intersect.cpp @@ -287,26 +287,34 @@ TEST(primal_intersect, segment_segment_intersection) using Segment3D = primal::Segment; Point3D intersection; + constexpr double EPS = 1e-12; EXPECT_TRUE(primal::intersect(Segment3D(Point3D {0., 0., 0.}, Point3D {1., 1., 0.}), Segment3D(Point3D {0., 1., 0.}, Point3D {1., 0., 0.}), intersection)); - EXPECT_TRUE(intersection.isNearlyEqual(Point3D {0.5, 0.5, 0.}, 1e-12)); + EXPECT_TRUE(intersection.isNearlyEqual(Point3D {0.5, 0.5, 0.}, EPS)); EXPECT_TRUE(primal::intersect(Segment3D(Point3D {0., 0., 0.}, Point3D {1., 0., 0.}), Segment3D(Point3D {1., 0., 0.}, Point3D {1., 1., 0.}), intersection)); - EXPECT_TRUE(intersection.isNearlyEqual(Point3D {1., 0., 0.}, 1e-12)); + EXPECT_TRUE(intersection.isNearlyEqual(Point3D {1., 0., 0.}, EPS)); + // Overlapping collinear segments return the overlap endpoint with the + // smaller parameter on the first segment argument. EXPECT_TRUE(primal::intersect(Segment3D(Point3D {0., 0., 0.}, Point3D {2., 0., 0.}), Segment3D(Point3D {1., 0., 0.}, Point3D {3., 0., 0.}), intersection)); - EXPECT_TRUE(intersection.isNearlyEqual(Point3D {1., 0., 0.}, 1e-12)); + EXPECT_TRUE(intersection.isNearlyEqual(Point3D {1., 0., 0.}, EPS)); + + EXPECT_TRUE(primal::intersect(Segment3D(Point3D {3., 0., 0.}, Point3D {1., 0., 0.}), + Segment3D(Point3D {0., 0., 0.}, Point3D {2., 0., 0.}), + intersection)); + EXPECT_TRUE(intersection.isNearlyEqual(Point3D {2., 0., 0.}, EPS)); EXPECT_TRUE(primal::intersect(Segment3D(Point3D {1., 0., 0.}, Point3D {1., 0., 0.}), Segment3D(Point3D {0., 0., 0.}, Point3D {2., 0., 0.}), intersection)); - EXPECT_TRUE(intersection.isNearlyEqual(Point3D {1., 0., 0.}, 1e-12)); + EXPECT_TRUE(intersection.isNearlyEqual(Point3D {1., 0., 0.}, EPS)); EXPECT_FALSE(primal::intersect(Segment3D(Point3D {0., 0., 0.}, Point3D {1., 0., 0.}), Segment3D(Point3D {2., 0., 0.}, Point3D {3., 0., 0.}), From ce1f24503e8a4cc3d0ccf7bb70bffa557d2a7a8c Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 30 Apr 2026 16:50:05 -0700 Subject: [PATCH 170/986] Improved segment intersect testing --- src/axom/primal/tests/primal_intersect.cpp | 31 ++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/axom/primal/tests/primal_intersect.cpp b/src/axom/primal/tests/primal_intersect.cpp index 195555791e..ed07703e46 100644 --- a/src/axom/primal/tests/primal_intersect.cpp +++ b/src/axom/primal/tests/primal_intersect.cpp @@ -289,37 +289,64 @@ TEST(primal_intersect, segment_segment_intersection) Point3D intersection; constexpr double EPS = 1e-12; + // Proper crossing at an interior point of both segments. EXPECT_TRUE(primal::intersect(Segment3D(Point3D {0., 0., 0.}, Point3D {1., 1., 0.}), Segment3D(Point3D {0., 1., 0.}, Point3D {1., 0., 0.}), intersection)); EXPECT_TRUE(intersection.isNearlyEqual(Point3D {0.5, 0.5, 0.}, EPS)); + // Endpoint of segment 1 coincides with an endpoint of segment 2. EXPECT_TRUE(primal::intersect(Segment3D(Point3D {0., 0., 0.}, Point3D {1., 0., 0.}), Segment3D(Point3D {1., 0., 0.}, Point3D {1., 1., 0.}), intersection)); EXPECT_TRUE(intersection.isNearlyEqual(Point3D {1., 0., 0.}, EPS)); - // Overlapping collinear segments return the overlap endpoint with the - // smaller parameter on the first segment argument. + // Endpoint of segment 1 coincides with an interior point of segment 2. + EXPECT_TRUE(primal::intersect(Segment3D(Point3D {1., 0., 0.}, Point3D {2., 0., 0.}), + Segment3D(Point3D {0., 0., 0.}, Point3D {3., 0., 0.}), + intersection)); + EXPECT_TRUE(intersection.isNearlyEqual(Point3D {1., 0., 0.}, EPS)); + + // The two segments are exactly the same. + EXPECT_TRUE(primal::intersect(Segment3D(Point3D {0., 0., 0.}, Point3D {2., 0., 0.}), + Segment3D(Point3D {0., 0., 0.}, Point3D {2., 0., 0.}), + intersection)); + EXPECT_TRUE(intersection.isNearlyEqual(Point3D {0., 0., 0.}, EPS)); + + // The two segments are exactly the same, but the second segment swaps + // endpoints. + EXPECT_TRUE(primal::intersect(Segment3D(Point3D {0., 0., 0.}, Point3D {2., 0., 0.}), + Segment3D(Point3D {2., 0., 0.}, Point3D {0., 0., 0.}), + intersection)); + EXPECT_TRUE(intersection.isNearlyEqual(Point3D {0., 0., 0.}, EPS)); + + // Collinear segments partially overlap; the representative intersection + // point is the first point in the overlap encountered along segment 1. EXPECT_TRUE(primal::intersect(Segment3D(Point3D {0., 0., 0.}, Point3D {2., 0., 0.}), Segment3D(Point3D {1., 0., 0.}, Point3D {3., 0., 0.}), intersection)); EXPECT_TRUE(intersection.isNearlyEqual(Point3D {1., 0., 0.}, EPS)); + // The same partial overlap with segment 1 reversed verifies that the + // representative point follows segment 1's orientation. EXPECT_TRUE(primal::intersect(Segment3D(Point3D {3., 0., 0.}, Point3D {1., 0., 0.}), Segment3D(Point3D {0., 0., 0.}, Point3D {2., 0., 0.}), intersection)); EXPECT_TRUE(intersection.isNearlyEqual(Point3D {2., 0., 0.}, EPS)); + // Degenerate point-segment intersection. EXPECT_TRUE(primal::intersect(Segment3D(Point3D {1., 0., 0.}, Point3D {1., 0., 0.}), Segment3D(Point3D {0., 0., 0.}, Point3D {2., 0., 0.}), intersection)); EXPECT_TRUE(intersection.isNearlyEqual(Point3D {1., 0., 0.}, EPS)); + // Collinear segments with a gap do not intersect. EXPECT_FALSE(primal::intersect(Segment3D(Point3D {0., 0., 0.}, Point3D {1., 0., 0.}), Segment3D(Point3D {2., 0., 0.}, Point3D {3., 0., 0.}), intersection)); + // Skew segments with closest approach away from the shared plane do not + // intersect. EXPECT_FALSE(primal::intersect(Segment3D(Point3D {0., 0., 0.}, Point3D {1., 0., 0.}), Segment3D(Point3D {0.5, -1., 1.}, Point3D {0.5, 1., 1.}), intersection)); From 93bb5f2fb88ea6e4fd9ddc2cc44c2925b512a202 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 30 Apr 2026 17:05:50 -0700 Subject: [PATCH 171/986] More segment-segment tests --- src/axom/primal/tests/primal_intersect.cpp | 151 +++++++++++++++++++++ 1 file changed, 151 insertions(+) diff --git a/src/axom/primal/tests/primal_intersect.cpp b/src/axom/primal/tests/primal_intersect.cpp index ed07703e46..6a05a58af3 100644 --- a/src/axom/primal/tests/primal_intersect.cpp +++ b/src/axom/primal/tests/primal_intersect.cpp @@ -334,17 +334,48 @@ TEST(primal_intersect, segment_segment_intersection) intersection)); EXPECT_TRUE(intersection.isNearlyEqual(Point3D {2., 0., 0.}, EPS)); + // Collinear segments that only touch at one endpoint still intersect at + // that shared endpoint. + EXPECT_TRUE(primal::intersect(Segment3D(Point3D {0., 0., 0.}, Point3D {1., 0., 0.}), + Segment3D(Point3D {1., 0., 0.}, Point3D {3., 0., 0.}), + intersection)); + EXPECT_TRUE(intersection.isNearlyEqual(Point3D {1., 0., 0.}, EPS)); + // Degenerate point-segment intersection. EXPECT_TRUE(primal::intersect(Segment3D(Point3D {1., 0., 0.}, Point3D {1., 0., 0.}), Segment3D(Point3D {0., 0., 0.}, Point3D {2., 0., 0.}), intersection)); EXPECT_TRUE(intersection.isNearlyEqual(Point3D {1., 0., 0.}, EPS)); + // Both degenerate segments intersect when they collapse to the same point. + EXPECT_TRUE(primal::intersect(Segment3D(Point3D {1., 2., 3.}, Point3D {1., 2., 3.}), + Segment3D(Point3D {1., 2., 3.}, Point3D {1., 2., 3.}), + intersection)); + EXPECT_TRUE(intersection.isNearlyEqual(Point3D {1., 2., 3.}, EPS)); + + // Both degenerate segments do not intersect when they collapse to different + // points. + EXPECT_FALSE(primal::intersect(Segment3D(Point3D {1., 2., 3.}, Point3D {1., 2., 3.}), + Segment3D(Point3D {1., 2., 4.}, Point3D {1., 2., 4.}), + intersection)); + + // A degenerate point segment does not intersect a segment that does not + // contain that point. + EXPECT_FALSE(primal::intersect(Segment3D(Point3D {2., 1., 0.}, Point3D {2., 1., 0.}), + Segment3D(Point3D {0., 0., 0.}, Point3D {2., 0., 0.}), + intersection)); + // Collinear segments with a gap do not intersect. EXPECT_FALSE(primal::intersect(Segment3D(Point3D {0., 0., 0.}, Point3D {1., 0., 0.}), Segment3D(Point3D {2., 0., 0.}, Point3D {3., 0., 0.}), intersection)); + // Parallel segments in the same plane do not intersect when they are not + // collinear. + EXPECT_FALSE(primal::intersect(Segment3D(Point3D {0., 0., 0.}, Point3D {2., 0., 0.}), + Segment3D(Point3D {0., 1., 0.}, Point3D {2., 1., 0.}), + intersection)); + // Skew segments with closest approach away from the shared plane do not // intersect. EXPECT_FALSE(primal::intersect(Segment3D(Point3D {0., 0., 0.}, Point3D {1., 0., 0.}), @@ -2719,6 +2750,100 @@ void check_plane_seg_intersect() axom::setDefaultAllocator(current_allocator); } +template +void check_segment_segment_intersect_policy() +{ + const int DIM = 3; + using PointType = primal::Point; + using SegmentType = primal::Segment; + + umpire::ResourceManager& rm = umpire::ResourceManager::getInstance(); + + const int current_allocator = axom::getDefaultAllocatorID(); + + umpire::Allocator allocator = + (axom::execution_space::onDevice() + ? rm.getAllocator(umpire::resource::Device) + : rm.getAllocator(axom::execution_space::allocatorID())); + + axom::setDefaultAllocator(allocator.getId()); + + const int result_allocator = + (axom::execution_space::onDevice() + ? rm.getAllocator(umpire::resource::Unified).getId() + : axom::execution_space::allocatorID()); + + PointType* intersections = axom::allocate(6, result_allocator); + bool* res = axom::allocate(6, result_allocator); + + axom::for_all( + 6, + AXOM_LAMBDA(int i) { + SegmentType P; + SegmentType Q; + + // Proper crossing at an interior point of both segments. + if(i == 0) + { + P = SegmentType(PointType {0., 0., 0.}, PointType {1., 1., 0.}); + Q = SegmentType(PointType {0., 1., 0.}, PointType {1., 0., 0.}); + } + + // Endpoint of segment 1 coincides with an endpoint of segment 2. + if(i == 1) + { + P = SegmentType(PointType {0., 0., 0.}, PointType {1., 0., 0.}); + Q = SegmentType(PointType {1., 0., 0.}, PointType {1., 1., 0.}); + } + + // Collinear segments partially overlap. + if(i == 2) + { + P = SegmentType(PointType {0., 0., 0.}, PointType {2., 0., 0.}); + Q = SegmentType(PointType {1., 0., 0.}, PointType {3., 0., 0.}); + } + + // Collinear segments that only touch at one endpoint. + if(i == 3) + { + P = SegmentType(PointType {0., 0., 0.}, PointType {1., 0., 0.}); + Q = SegmentType(PointType {1., 0., 0.}, PointType {3., 0., 0.}); + } + + // Parallel but non-collinear segments in the same plane. + if(i == 4) + { + P = SegmentType(PointType {0., 0., 0.}, PointType {2., 0., 0.}); + Q = SegmentType(PointType {0., 1., 0.}, PointType {2., 1., 0.}); + } + + // Skew segments in different planes. + if(i == 5) + { + P = SegmentType(PointType {0., 0., 0.}, PointType {1., 0., 0.}); + Q = SegmentType(PointType {0.5, -1., 1.}, PointType {0.5, 1., 1.}); + } + + res[i] = axom::primal::intersect(P, Q, intersections[i]); + }); + + EXPECT_TRUE(res[0]); + EXPECT_TRUE(res[1]); + EXPECT_TRUE(res[2]); + EXPECT_TRUE(res[3]); + EXPECT_FALSE(res[4]); + EXPECT_FALSE(res[5]); + + EXPECT_TRUE(intersections[0].isNearlyEqual(PointType {0.5, 0.5, 0.}, 1e-12)); + EXPECT_TRUE(intersections[1].isNearlyEqual(PointType {1., 0., 0.}, 1e-12)); + EXPECT_TRUE(intersections[2].isNearlyEqual(PointType {1., 0., 0.}, 1e-12)); + EXPECT_TRUE(intersections[3].isNearlyEqual(PointType {1., 0., 0.}, 1e-12)); + + axom::deallocate(intersections); + axom::deallocate(res); + axom::setDefaultAllocator(current_allocator); +} + TEST(primal_intersect, plane_bb_test_intersection_sequential) { check_plane_bb_intersect(); @@ -2729,6 +2854,11 @@ TEST(primal_intersect, plane_seg_test_intersection_sequential) check_plane_seg_intersect(); } +TEST(primal_intersect, segment_segment_test_intersection_sequential) +{ + check_segment_segment_intersect_policy(); +} + #ifdef AXOM_USE_OPENMP TEST(primal_intersect, plane_bb_test_intersection_omp) { @@ -2739,6 +2869,11 @@ TEST(primal_intersect, plane_seg_test_intersection_omp) { check_plane_seg_intersect(); } + +TEST(primal_intersect, segment_segment_test_intersection_omp) +{ + check_segment_segment_intersect_policy(); +} #endif /* AXOM_USE_OPENMP */ #ifdef AXOM_USE_CUDA @@ -2757,6 +2892,14 @@ AXOM_CUDA_TEST(primal_intersect, plane_seg_test_intersection_cuda) check_plane_seg_intersect(); } + +AXOM_CUDA_TEST(primal_intersect, segment_segment_test_intersection_cuda) +{ + constexpr int BLOCK_SIZE = 256; + using exec = axom::CUDA_EXEC; + + check_segment_segment_intersect_policy(); +} #endif /* AXOM_USE_CUDA */ #ifdef AXOM_USE_HIP @@ -2775,6 +2918,14 @@ TEST(primal_intersect, plane_seg_test_intersection_hip) check_plane_seg_intersect(); } + +TEST(primal_intersect, segment_segment_test_intersection_hip) +{ + constexpr int BLOCK_SIZE = 256; + using exec = axom::HIP_EXEC; + + check_segment_segment_intersect_policy(); +} #endif /* AXOM_USE_HIP */ #endif /* AXOM_USE_RAJA && AXOM_USE_UMPIRE */ From c5952192e27da486c3ba28c948f311aef347e481 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 30 Apr 2026 17:15:02 -0700 Subject: [PATCH 172/986] Improve tet-plane slice robustness --- .../primal/operators/detail/slice_impl.hpp | 55 ++++++++- src/axom/primal/tests/primal_slice.cpp | 113 ++++++++++++++++++ 2 files changed, 165 insertions(+), 3 deletions(-) diff --git a/src/axom/primal/operators/detail/slice_impl.hpp b/src/axom/primal/operators/detail/slice_impl.hpp index 618098496e..f1c43c71d4 100644 --- a/src/axom/primal/operators/detail/slice_impl.hpp +++ b/src/axom/primal/operators/detail/slice_impl.hpp @@ -14,6 +14,33 @@ namespace primal namespace detail { +template +AXOM_HOST_DEVICE bool polygon_has_vertex(const PolygonType& poly, + const typename PolygonType::PointType& pt, + double eps = 1e-10) +{ + for(int i = 0; i < poly.numVertices(); ++i) + { + if(poly[i].isNearlyEqual(pt, eps)) + { + return true; + } + } + + return false; +} + +template +AXOM_HOST_DEVICE void add_unique_vertex(PolygonType& poly, + const typename PolygonType::PointType& pt, + double eps = 1e-10) +{ + if(!polygon_has_vertex(poly, pt, eps)) + { + poly.addVertex(pt); + } +} + /*! * \brief Slices a 3D tetrahedron with a plane and returns the resulting * polygon. @@ -36,10 +63,32 @@ AXOM_HOST_DEVICE primal::Polygon slice_tet_plane( for(int j = i + 1; j < 4; ++j) { Segment edge(tet[i], tet[j]); - T t {}; - if(primal::intersect(plane, edge, t)) + const T sourceDistance = plane.signedDistance(edge.source()); + const T targetDistance = plane.signedDistance(edge.target()); + const bool sourceOnPlane = axom::utilities::isNearlyEqual(sourceDistance, T {0}); + const bool targetOnPlane = axom::utilities::isNearlyEqual(targetDistance, T {0}); + + if(sourceOnPlane && targetOnPlane) + { + add_unique_vertex(intersectionPolygon, edge.source()); + add_unique_vertex(intersectionPolygon, edge.target()); + } + else if(sourceOnPlane) + { + add_unique_vertex(intersectionPolygon, edge.source()); + } + else if(targetOnPlane) + { + add_unique_vertex(intersectionPolygon, edge.target()); + } + else if((sourceDistance < T {0} && targetDistance > T {0}) || + (sourceDistance > T {0} && targetDistance < T {0})) { - intersectionPolygon.addVertex(edge.at(t)); + T t {}; + if(primal::intersect(plane, edge, t)) + { + add_unique_vertex(intersectionPolygon, edge.at(t)); + } } } } diff --git a/src/axom/primal/tests/primal_slice.cpp b/src/axom/primal/tests/primal_slice.cpp index a17fa513f4..2d049e2aa8 100644 --- a/src/axom/primal/tests/primal_slice.cpp +++ b/src/axom/primal/tests/primal_slice.cpp @@ -93,6 +93,75 @@ void check_slice_policy() Point3D {0., 1., 0.}}); } +template +void check_slice_degenerate_policy() +{ + using TetType = primal::Tetrahedron; + using PlaneType = primal::Plane; + using PolygonType = primal::Polygon; + + const TetType tet {Point3D {0., 0., 0.}, + Point3D {1., 0., 0.}, + Point3D {0., 1., 0.}, + Point3D {0., 0., 1.}}; + + const int host_allocator = axom::execution_space::allocatorID(); + const int kernel_allocator = axom::execution_space::allocatorID(); + + axom::Array polys_device(3, 3, kernel_allocator); + auto polys_view = polys_device.view(); + + axom::Array areas_device(3, 3, kernel_allocator); + auto areas_view = areas_device.view(); + + axom::for_all( + 3, + AXOM_LAMBDA(int i) { + PlaneType plane; + + // The plane intersects the tetrahedron only at vertex (0,0,0). + if(i == 0) + { + plane = PlaneType({1., 1., 1.}, 0.); + } + + // The plane intersects the tetrahedron only along the edge from + // (0,0,0) to (1,0,0). + if(i == 1) + { + plane = PlaneType({0., 1., 1.}, 0.); + } + + // The plane coincides with the face z = 0 of the tetrahedron. + if(i == 2) + { + plane = PlaneType({0., 0., 1.}, 0.); + } + + polys_view[i] = primal::slice(tet, plane); + areas_view[i] = polys_view[i].area(); + }); + + axom::Array polys_host(polys_device, host_allocator); + axom::Array areas_host(areas_device, host_allocator); + + EXPECT_EQ(polys_host[0].numVertices(), 1); + EXPECT_NEAR(areas_host[0], 0., EPS); + expect_vertex_set(polys_host[0], std::array {Point3D {0., 0., 0.}}); + + EXPECT_EQ(polys_host[1].numVertices(), 2); + EXPECT_NEAR(areas_host[1], 0., EPS); + expect_vertex_set(polys_host[1], + std::array {Point3D {0., 0., 0.}, Point3D {1., 0., 0.}}); + + EXPECT_EQ(polys_host[2].numVertices(), 3); + EXPECT_NEAR(areas_host[2], 0.5, EPS); + expect_vertex_set(polys_host[2], + std::array {Point3D {0., 0., 0.}, + Point3D {1., 0., 0.}, + Point3D {0., 1., 0.}}); +} + } // namespace TEST(primal_slice, tet_plane_slice_dynamic) @@ -117,20 +186,64 @@ TEST(primal_slice, tet_plane_slice_dynamic) EXPECT_EQ(empty_poly.numVertices(), 0); } +TEST(primal_slice, tet_plane_slice_degenerate_dynamic) +{ + using TetType = primal::Tetrahedron; + using PlaneType = primal::Plane; + + const TetType tet {Point3D {0., 0., 0.}, + Point3D {1., 0., 0.}, + Point3D {0., 1., 0.}, + Point3D {0., 0., 1.}}; + + // The plane intersects the tetrahedron only at a single vertex. + const auto vertex_poly = primal::slice(tet, PlaneType({1., 1., 1.}, 0.)); + EXPECT_EQ(vertex_poly.numVertices(), 1); + EXPECT_NEAR(vertex_poly.area(), 0., EPS); + expect_vertex_set(vertex_poly, std::array {Point3D {0., 0., 0.}}); + + // The plane intersects the tetrahedron only along a single edge. + const auto edge_poly = primal::slice(tet, PlaneType({0., 1., 1.}, 0.)); + EXPECT_EQ(edge_poly.numVertices(), 2); + EXPECT_NEAR(edge_poly.area(), 0., EPS); + expect_vertex_set(edge_poly, + std::array {Point3D {0., 0., 0.}, Point3D {1., 0., 0.}}); + + // The plane coincides with one face of the tetrahedron. + const auto face_poly = primal::slice(tet, PlaneType({0., 0., 1.}, 0.)); + EXPECT_EQ(face_poly.numVertices(), 3); + EXPECT_NEAR(face_poly.area(), 0.5, EPS); + expect_vertex_set(face_poly, + std::array {Point3D {0., 0., 0.}, + Point3D {1., 0., 0.}, + Point3D {0., 1., 0.}}); +} + TEST(primal_slice, tet_plane_slice_seq) { check_slice_policy(); } +TEST(primal_slice, tet_plane_slice_degenerate_seq) { check_slice_degenerate_policy(); } + #if defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) #ifdef AXOM_USE_OPENMP TEST(primal_slice, tet_plane_slice_omp) { check_slice_policy(); } + +TEST(primal_slice, tet_plane_slice_degenerate_omp) { check_slice_degenerate_policy(); } #endif #ifdef AXOM_USE_CUDA AXOM_CUDA_TEST(primal_slice, tet_plane_slice_cuda) { check_slice_policy>(); } + +AXOM_CUDA_TEST(primal_slice, tet_plane_slice_degenerate_cuda) +{ + check_slice_degenerate_policy>(); +} #endif #ifdef AXOM_USE_HIP TEST(primal_slice, tet_plane_slice_hip) { check_slice_policy>(); } + +TEST(primal_slice, tet_plane_slice_degenerate_hip) { check_slice_degenerate_policy>(); } #endif #endif From c31b746f2948275f77ae312226040f002ecbd4df Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 30 Apr 2026 17:16:52 -0700 Subject: [PATCH 173/986] Improve doxygen comments. --- src/axom/primal/operators/slice.hpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/axom/primal/operators/slice.hpp b/src/axom/primal/operators/slice.hpp index e042a6f0dd..70ba91c03d 100644 --- a/src/axom/primal/operators/slice.hpp +++ b/src/axom/primal/operators/slice.hpp @@ -29,6 +29,15 @@ namespace primal * \param [in] plane The slicing plane * * \return The polygon obtained from slicing a tetrahedron with a plane. + * When the plane intersects the tetrahedron in a nondegenerate cross + * section, the return value is a triangle or quadrilateral. Degenerate + * intersections are also represented as polygons: a plane that touches the + * tetrahedron at a single vertex returns a one-vertex polygon, and a plane + * that intersects the tetrahedron only along one edge returns a two-vertex + * polygon. + * + * \note For nondegenerate intersections, the polygon vertices are intended to + * be ordered so that the polygon normal is aligned with the plane normal. */ template AXOM_HOST_DEVICE primal::Polygon slice(const primal::Tetrahedron& tet, From 1568b4110b3a938316cdc9c234ca01c3acf75a3a Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 30 Apr 2026 17:18:26 -0700 Subject: [PATCH 174/986] Document EPS parameter. --- src/axom/primal/operators/detail/intersect_impl.hpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/axom/primal/operators/detail/intersect_impl.hpp b/src/axom/primal/operators/detail/intersect_impl.hpp index c743c6b364..37da1bf01b 100644 --- a/src/axom/primal/operators/detail/intersect_impl.hpp +++ b/src/axom/primal/operators/detail/intersect_impl.hpp @@ -142,8 +142,14 @@ bool TriangleIntersection2D(const Triangle2& t1, const Triangle2& t2, bool inclu * \param[out] intersection The intersection point where the segments intersect. * For collinear overlapping segments, this stores the overlap endpoint with * the smaller parameter on \a P. - * \param EPS Tolerance used to detect degeneracy, collinearity, and - * coincidence of the candidate intersection points. + * \param EPS Tolerance used in several geometric comparisons in this routine. + * It is used to: + * - detect whether either segment is degenerate, + * - detect whether the segment directions are parallel, + * - test whether parallel segments are collinear, + * - allow a small tolerance when checking whether collinear overlap exists, + * - decide whether the closest points computed on the two segments should be + * treated as the same intersection point. * * \return True if the line segments intersect; False otherwise. */ From c7ee9b54b1c5fbbdbd4609d6c107bd543c570737 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 30 Apr 2026 17:30:38 -0700 Subject: [PATCH 175/986] Improve comments --- .../operators/detail/intersect_impl.hpp | 49 ++++++++++++++++--- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/src/axom/primal/operators/detail/intersect_impl.hpp b/src/axom/primal/operators/detail/intersect_impl.hpp index 37da1bf01b..3bcbaa31de 100644 --- a/src/axom/primal/operators/detail/intersect_impl.hpp +++ b/src/axom/primal/operators/detail/intersect_impl.hpp @@ -161,13 +161,25 @@ AXOM_HOST_DEVICE bool intersect_segment_segment(const Segment& P, { using Vector3D = primal::Vector; - // Use the standard segment-segment closest-points formulation on the - // segment directions u and v. We only report an intersection when the - // clamped closest points coincide within the supplied tolerance. + // Use the standard closest-points formulation for two segments: + // + // P(s) = P0 + s u + // Q(t) = Q0 + t v + // + // with 0 <= s,t <= 1. The unconstrained closest points on the infinite + // supporting lines satisfy a small 2x2 system built from dot products of + // u, v, and the offset between the segment origins. We then clamp that + // answer back to the finite segment domains. If the resulting closest + // points coincide within tolerance, we report an intersection. const auto u = P.target() - P.source(); const auto v = Q.target() - Q.source(); const auto w = P.source() - Q.source(); + // Dot-product coefficients for the closest-points system. + // a = u.u, c = v.v are squared segment lengths + // b = u.v couples the two segment directions + // d = u.w, e = v.w capture the relative offset of the segment origins + // D = ac - b^2 is the determinant of the 2x2 system const T a = u.dot(u); const T b = u.dot(v); const T c = v.dot(v); @@ -181,12 +193,16 @@ AXOM_HOST_DEVICE bool intersect_segment_segment(const Segment& P, // segments have nonzero length. if(axom::utilities::isNearlyEqual(a, T {0}, EPS) && axom::utilities::isNearlyEqual(c, T {0}, EPS)) { + // Both segments collapse to points, so the only possible intersection is + // the points coinciding. intersection = P.source(); return P.source().isNearlyEqual(Q.source(), EPS); } if(axom::utilities::isNearlyEqual(a, T {0}, EPS)) { + // P collapses to a point. Project that point onto Q, clamp to Q's finite + // extent, and check whether the closest point on Q is the same point. const T t = axom::utilities::clampVal(e / c, T {0}, T {1}); const auto qPoint = Q.at(t); if(P.source().isNearlyEqual(qPoint, EPS)) @@ -200,6 +216,7 @@ AXOM_HOST_DEVICE bool intersect_segment_segment(const Segment& P, if(axom::utilities::isNearlyEqual(c, T {0}, EPS)) { + // Q collapses to a point. This is the symmetric point-on-segment test. const T s = axom::utilities::clampVal(-d / a, T {0}, T {1}); const auto pPoint = P.at(s); if(Q.source().isNearlyEqual(pPoint, EPS)) @@ -213,6 +230,11 @@ AXOM_HOST_DEVICE bool intersect_segment_segment(const Segment& P, if(axom::utilities::isNearlyEqual(D, T {0}, EPS)) { + // D near zero means the segment directions are parallel or nearly so, so + // the 2x2 closest-points system is singular. In that case there are only + // two possibilities: the segments are on distinct parallel lines and do + // not intersect, or they are collinear and we can reduce the problem to a + // 1D overlap test on one segment. const auto uxw = Vector3D::cross_product(u, w); // Parallel segments intersect only if they are also collinear. // Compare the point-to-line distance against EPS instead of relying on @@ -243,16 +265,24 @@ AXOM_HOST_DEVICE bool intersect_segment_segment(const Segment& P, T sD = D; T tD = D; - // Clamp the unconstrained closest point on each supporting line back to the - // finite segment domain [0,1] x [0,1]. + // sN / sD and tN / tD are the unconstrained closest-point parameters on the + // infinite supporting lines. The rest of this block clamps that answer back + // to the finite segment box [0,1] x [0,1]. Whenever one parameter is pushed + // to an endpoint, the other parameter must be recomputed against that fixed + // endpoint so we still get the closest pair of points on the actual + // segments. if(sN < T {0}) { + // Closest point on P is before P.source(), so clamp to P.source() and + // recompute Q's parameter against that endpoint. sN = T {0}; tN = e; tD = c; } else if(sN > sD) { + // Closest point on P is past P.target(), so clamp to P.target() and + // recompute Q's parameter against that endpoint. sN = sD; tN = e + b; tD = c; @@ -260,6 +290,8 @@ AXOM_HOST_DEVICE bool intersect_segment_segment(const Segment& P, if(tN < T {0}) { + // Closest point on Q is before Q.source(), so clamp to Q.source() and + // recompute P's parameter against that endpoint. tN = T {0}; if(-d < T {0}) { @@ -277,6 +309,8 @@ AXOM_HOST_DEVICE bool intersect_segment_segment(const Segment& P, } else if(tN > tD) { + // Closest point on Q is past Q.target(), so clamp to Q.target() and + // recompute P's parameter against that endpoint. tN = tD; if((-d + b) < T {0}) { @@ -298,8 +332,9 @@ AXOM_HOST_DEVICE bool intersect_segment_segment(const Segment& P, const auto pPoint = P.at(sc); const auto qPoint = Q.at(tc); - // For skew segments, the closest points generally differ. Only accept the - // result when they collapse to the same point within tolerance. + // For skew or separated segments, the closest points generally differ even + // after clamping. We only have an intersection when the closest pair of + // points on the finite segments collapses to the same point. if(pPoint.isNearlyEqual(qPoint, EPS)) { intersection = pPoint; From 1dcdbbe656c16911176a671997ed0176225e9f5f Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 30 Apr 2026 17:31:33 -0700 Subject: [PATCH 176/986] Improved support for polygon orientation. --- .../primal/operators/detail/slice_impl.hpp | 14 ++ src/axom/primal/tests/primal_slice.cpp | 160 +++++++++++++++--- 2 files changed, 151 insertions(+), 23 deletions(-) diff --git a/src/axom/primal/operators/detail/slice_impl.hpp b/src/axom/primal/operators/detail/slice_impl.hpp index f1c43c71d4..65570d6d3a 100644 --- a/src/axom/primal/operators/detail/slice_impl.hpp +++ b/src/axom/primal/operators/detail/slice_impl.hpp @@ -57,6 +57,11 @@ AXOM_HOST_DEVICE primal::Polygon slice_tet_plane( { Polygon intersectionPolygon; + if(!plane.isValid()) + { + return intersectionPolygon; + } + // find intersection vertices for(int i = 0; i < 4; ++i) { @@ -105,6 +110,15 @@ AXOM_HOST_DEVICE primal::Polygon slice_tet_plane( axom::utilities::swap(intersectionPolygon[2], intersectionPolygon[3]); } } + + // For nondegenerate slices, orient the polygon so its normal follows the + // slicing plane normal. + if(intersectionPolygon.numVertices() >= 3 && + intersectionPolygon.normal().dot(plane.getNormal()) < T {0}) + { + intersectionPolygon.reverseOrientation(); + } + return intersectionPolygon; } diff --git a/src/axom/primal/tests/primal_slice.cpp b/src/axom/primal/tests/primal_slice.cpp index 2d049e2aa8..46038dd8ea 100644 --- a/src/axom/primal/tests/primal_slice.cpp +++ b/src/axom/primal/tests/primal_slice.cpp @@ -9,6 +9,7 @@ #include "axom/config.hpp" #include "axom/core/Array.hpp" +#include "axom/core/StackArray.hpp" #include "axom/core/execution/execution_space.hpp" #include "axom/core/execution/for_all.hpp" @@ -17,8 +18,6 @@ #include "axom/primal/geometry/Tetrahedron.hpp" #include "axom/primal/operators/slice.hpp" -#include - namespace primal = axom::primal; namespace @@ -28,17 +27,17 @@ constexpr double EPS = 1e-10; using Point3D = primal::Point; -template -void expect_vertex_set(const PolygonType& poly, const std::array& expected) +template +void expect_vertex_set(const PolygonType& poly, const axom::StackArray& expected) { ASSERT_EQ(poly.numVertices(), N); bool matched[N] = {false}; - for(std::size_t i = 0; i < N; ++i) + for(int i = 0; i < N; ++i) { bool found = false; - for(std::size_t j = 0; j < N; ++j) + for(int j = 0; j < N; ++j) { if(!matched[j] && poly[i].isNearlyEqual(expected[j], EPS)) { @@ -52,6 +51,13 @@ void expect_vertex_set(const PolygonType& poly, const std::array& ex } } +template +void expect_normal_aligned(const PolygonType& poly, const PlaneType& plane) +{ + ASSERT_GE(poly.numVertices(), 3); + EXPECT_GT(poly.normal().dot(plane.getNormal()), 0.); +} + template void check_slice_policy() { @@ -87,10 +93,11 @@ void check_slice_policy() EXPECT_EQ(polys_host[0].numVertices(), 4); EXPECT_NEAR(areas_host[0], 1., EPS); expect_vertex_set(polys_host[0], - std::array {Point3D {-1., -1., 0.}, - Point3D {-1., 0., 0.}, - Point3D {0., 0., 0.}, - Point3D {0., 1., 0.}}); + axom::StackArray {{Point3D {-1., -1., 0.}, + Point3D {-1., 0., 0.}, + Point3D {0., 0., 0.}, + Point3D {0., 1., 0.}}}); + expect_normal_aligned(polys_host[0], plane); } template @@ -147,19 +154,20 @@ void check_slice_degenerate_policy() EXPECT_EQ(polys_host[0].numVertices(), 1); EXPECT_NEAR(areas_host[0], 0., EPS); - expect_vertex_set(polys_host[0], std::array {Point3D {0., 0., 0.}}); + expect_vertex_set(polys_host[0], axom::StackArray {{Point3D {0., 0., 0.}}}); EXPECT_EQ(polys_host[1].numVertices(), 2); EXPECT_NEAR(areas_host[1], 0., EPS); expect_vertex_set(polys_host[1], - std::array {Point3D {0., 0., 0.}, Point3D {1., 0., 0.}}); + axom::StackArray {{Point3D {0., 0., 0.}, Point3D {1., 0., 0.}}}); EXPECT_EQ(polys_host[2].numVertices(), 3); EXPECT_NEAR(areas_host[2], 0.5, EPS); expect_vertex_set(polys_host[2], - std::array {Point3D {0., 0., 0.}, - Point3D {1., 0., 0.}, - Point3D {0., 1., 0.}}); + axom::StackArray {{Point3D {0., 0., 0.}, + Point3D {1., 0., 0.}, + Point3D {0., 1., 0.}}}); + expect_normal_aligned(polys_host[2], PlaneType({0., 0., 1.}, 0.)); } } // namespace @@ -178,9 +186,10 @@ TEST(primal_slice, tet_plane_slice_dynamic) EXPECT_EQ(poly.numVertices(), 3); EXPECT_NEAR(poly.area(), 0.28125, EPS); expect_vertex_set(poly, - std::array {Point3D {0., 0., 0.25}, - Point3D {0.75, 0., 0.25}, - Point3D {0., 0.75, 0.25}}); + axom::StackArray {{Point3D {0., 0., 0.25}, + Point3D {0.75, 0., 0.25}, + Point3D {0., 0.75, 0.25}}}); + expect_normal_aligned(poly, PlaneType({0., 0., 1.}, 0.25)); const auto empty_poly = primal::slice(tet, PlaneType({0., 0., 1.}, 2.)); EXPECT_EQ(empty_poly.numVertices(), 0); @@ -200,23 +209,128 @@ TEST(primal_slice, tet_plane_slice_degenerate_dynamic) const auto vertex_poly = primal::slice(tet, PlaneType({1., 1., 1.}, 0.)); EXPECT_EQ(vertex_poly.numVertices(), 1); EXPECT_NEAR(vertex_poly.area(), 0., EPS); - expect_vertex_set(vertex_poly, std::array {Point3D {0., 0., 0.}}); + expect_vertex_set(vertex_poly, axom::StackArray {{Point3D {0., 0., 0.}}}); // The plane intersects the tetrahedron only along a single edge. const auto edge_poly = primal::slice(tet, PlaneType({0., 1., 1.}, 0.)); EXPECT_EQ(edge_poly.numVertices(), 2); EXPECT_NEAR(edge_poly.area(), 0., EPS); expect_vertex_set(edge_poly, - std::array {Point3D {0., 0., 0.}, Point3D {1., 0., 0.}}); + axom::StackArray {{Point3D {0., 0., 0.}, Point3D {1., 0., 0.}}}); // The plane coincides with one face of the tetrahedron. const auto face_poly = primal::slice(tet, PlaneType({0., 0., 1.}, 0.)); EXPECT_EQ(face_poly.numVertices(), 3); EXPECT_NEAR(face_poly.area(), 0.5, EPS); expect_vertex_set(face_poly, - std::array {Point3D {0., 0., 0.}, - Point3D {1., 0., 0.}, - Point3D {0., 1., 0.}}); + axom::StackArray {{Point3D {0., 0., 0.}, + Point3D {1., 0., 0.}, + Point3D {0., 1., 0.}}}); + expect_normal_aligned(face_poly, PlaneType({0., 0., 1.}, 0.)); +} + +TEST(primal_slice, tet_plane_slice_orientation_follows_plane) +{ + using TetType = primal::Tetrahedron; + using PlaneType = primal::Plane; + + const TetType tet {Point3D {-1., -1., -1.}, + Point3D {1., 1., -1.}, + Point3D {-1., -1., 1.}, + Point3D {-1., 1., 1.}}; + + // Flipping the slicing plane should preserve the vertex set while reversing + // the polygon orientation to keep the polygon normal aligned with the plane. + const PlaneType plane_pos({0., 0., 1.}, 0.); + const PlaneType plane_neg({0., 0., -1.}, 0.); + + const auto poly_pos = primal::slice(tet, plane_pos); + const auto poly_neg = primal::slice(tet, plane_neg); + + expect_vertex_set(poly_pos, + axom::StackArray {{Point3D {-1., -1., 0.}, + Point3D {-1., 0., 0.}, + Point3D {0., 0., 0.}, + Point3D {0., 1., 0.}}}); + expect_vertex_set(poly_neg, + axom::StackArray {{Point3D {-1., -1., 0.}, + Point3D {-1., 0., 0.}, + Point3D {0., 0., 0.}, + Point3D {0., 1., 0.}}}); + expect_normal_aligned(poly_pos, plane_pos); + expect_normal_aligned(poly_neg, plane_neg); + EXPECT_LT(poly_pos.normal().dot(poly_neg.normal()), 0.); +} + +TEST(primal_slice, tet_plane_slice_near_boundary_tolerance) +{ + using TetType = primal::Tetrahedron; + using PlaneType = primal::Plane; + + const TetType tet {Point3D {0., 0., 0.}, + Point3D {1., 0., 0.}, + Point3D {0., 1., 0.}, + Point3D {0., 0., 1.}}; + + // A plane within the current fuzzy zero tolerance of the base face behaves + // like the coincident-face case and returns that face. + const auto fuzzy_face = primal::slice(tet, PlaneType({0., 0., 1.}, 5e-11)); + EXPECT_EQ(fuzzy_face.numVertices(), 3); + EXPECT_NEAR(fuzzy_face.area(), 0.5, EPS); + expect_vertex_set(fuzzy_face, + axom::StackArray {{Point3D {0., 0., 0.}, + Point3D {1., 0., 0.}, + Point3D {0., 1., 0.}}}); + + // Moving the plane clearly away from that fuzzy interval produces the + // expected small triangle strictly above the base face instead. + const auto nearby_slice = primal::slice(tet, PlaneType({0., 0., 1.}, 1e-6)); + EXPECT_EQ(nearby_slice.numVertices(), 3); + EXPECT_NEAR(nearby_slice.area(), 0.4999990000005, EPS); + expect_vertex_set(nearby_slice, + axom::StackArray {{Point3D {0., 0., 1e-6}, + Point3D {0.999999, 0., 1e-6}, + Point3D {0., 0.999999, 1e-6}}}); + expect_normal_aligned(nearby_slice, PlaneType({0., 0., 1.}, 1e-6)); +} + +TEST(primal_slice, tet_plane_slice_degenerate_tet_repeated_vertex) +{ + using TetType = primal::Tetrahedron; + using PlaneType = primal::Plane; + + const TetType degenerate_tet {Point3D {0., 0., 0.}, + Point3D {1., 0., 0.}, + Point3D {0., 1., 0.}, + Point3D {0., 0., 0.}}; + + // A tetrahedron with a repeated vertex degenerates to a triangle; slicing by + // that triangle's supporting plane should still return the unique face + // vertices rather than duplicates. + const auto poly = primal::slice(degenerate_tet, PlaneType({0., 0., 1.}, 0.)); + EXPECT_EQ(poly.numVertices(), 3); + EXPECT_NEAR(poly.area(), 0.5, EPS); + expect_vertex_set(poly, + axom::StackArray {{Point3D {0., 0., 0.}, + Point3D {1., 0., 0.}, + Point3D {0., 1., 0.}}}); + expect_normal_aligned(poly, PlaneType({0., 0., 1.}, 0.)); +} + +TEST(primal_slice, tet_plane_slice_invalid_plane) +{ + using TetType = primal::Tetrahedron; + using PlaneType = primal::Plane; + + const TetType tet {Point3D {0., 0., 0.}, + Point3D {1., 0., 0.}, + Point3D {0., 1., 0.}, + Point3D {0., 0., 1.}}; + + // An invalid plane with zero normal does not define a meaningful slice, so + // the result should be empty. + const auto poly = primal::slice(tet, PlaneType()); + EXPECT_EQ(poly.numVertices(), 0); } TEST(primal_slice, tet_plane_slice_seq) { check_slice_policy(); } From a3154079a4d1a775d5dcc035f0ce5dcf9049ea25 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 30 Apr 2026 17:44:00 -0700 Subject: [PATCH 177/986] Slice tet-plane optimization. --- .../primal/operators/detail/slice_impl.hpp | 65 +++++++++++-------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/src/axom/primal/operators/detail/slice_impl.hpp b/src/axom/primal/operators/detail/slice_impl.hpp index 65570d6d3a..6d54a255ba 100644 --- a/src/axom/primal/operators/detail/slice_impl.hpp +++ b/src/axom/primal/operators/detail/slice_impl.hpp @@ -56,44 +56,53 @@ AXOM_HOST_DEVICE primal::Polygon slice_tet_plane( const primal::Plane& plane) { Polygon intersectionPolygon; + const int tetEdges[6][2] = {{0, 1}, {0, 2}, {0, 3}, {1, 2}, {1, 3}, {2, 3}}; + T signedDistances[4]; + bool onPlane[4]; if(!plane.isValid()) { return intersectionPolygon; } - // find intersection vertices + // Compute the signed distance of each tet vertex to the plane once. for(int i = 0; i < 4; ++i) { - for(int j = i + 1; j < 4; ++j) - { - Segment edge(tet[i], tet[j]); - const T sourceDistance = plane.signedDistance(edge.source()); - const T targetDistance = plane.signedDistance(edge.target()); - const bool sourceOnPlane = axom::utilities::isNearlyEqual(sourceDistance, T {0}); - const bool targetOnPlane = axom::utilities::isNearlyEqual(targetDistance, T {0}); + signedDistances[i] = plane.signedDistance(tet[i]); + onPlane[i] = axom::utilities::isNearlyEqual(signedDistances[i], T {0}); + } - if(sourceOnPlane && targetOnPlane) - { - add_unique_vertex(intersectionPolygon, edge.source()); - add_unique_vertex(intersectionPolygon, edge.target()); - } - else if(sourceOnPlane) - { - add_unique_vertex(intersectionPolygon, edge.source()); - } - else if(targetOnPlane) - { - add_unique_vertex(intersectionPolygon, edge.target()); - } - else if((sourceDistance < T {0} && targetDistance > T {0}) || - (sourceDistance > T {0} && targetDistance < T {0})) + // Check each tet edge for a crossing or an endpoint that lies on the plane. + for(int i = 0; i < 6; ++i) + { + const int v0 = tetEdges[i][0]; + const int v1 = tetEdges[i][1]; + const T sourceDistance = signedDistances[v0]; + const T targetDistance = signedDistances[v1]; + const bool sourceOnPlane = onPlane[v0]; + const bool targetOnPlane = onPlane[v1]; + + if(sourceOnPlane && targetOnPlane) + { + add_unique_vertex(intersectionPolygon, tet[v0]); + add_unique_vertex(intersectionPolygon, tet[v1]); + } + else if(sourceOnPlane) + { + add_unique_vertex(intersectionPolygon, tet[v0]); + } + else if(targetOnPlane) + { + add_unique_vertex(intersectionPolygon, tet[v1]); + } + else if((sourceDistance < T {0} && targetDistance > T {0}) || + (sourceDistance > T {0} && targetDistance < T {0})) + { + Segment edge(tet[v0], tet[v1]); + T t {}; + if(primal::intersect(plane, edge, t)) { - T t {}; - if(primal::intersect(plane, edge, t)) - { - add_unique_vertex(intersectionPolygon, edge.at(t)); - } + add_unique_vertex(intersectionPolygon, edge.at(t)); } } } From 73a706e5b636cda9746603154017664ec952f00c Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 30 Apr 2026 17:47:42 -0700 Subject: [PATCH 178/986] make style --- src/axom/primal/tests/primal_intersect.cpp | 7 +- src/axom/primal/tests/primal_slice.cpp | 75 +++++++++++----------- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/src/axom/primal/tests/primal_intersect.cpp b/src/axom/primal/tests/primal_intersect.cpp index 6a05a58af3..70e8d62f68 100644 --- a/src/axom/primal/tests/primal_intersect.cpp +++ b/src/axom/primal/tests/primal_intersect.cpp @@ -2768,10 +2768,9 @@ void check_segment_segment_intersect_policy() axom::setDefaultAllocator(allocator.getId()); - const int result_allocator = - (axom::execution_space::onDevice() - ? rm.getAllocator(umpire::resource::Unified).getId() - : axom::execution_space::allocatorID()); + const int result_allocator = (axom::execution_space::onDevice() + ? rm.getAllocator(umpire::resource::Unified).getId() + : axom::execution_space::allocatorID()); PointType* intersections = axom::allocate(6, result_allocator); bool* res = axom::allocate(6, result_allocator); diff --git a/src/axom/primal/tests/primal_slice.cpp b/src/axom/primal/tests/primal_slice.cpp index 46038dd8ea..2bb408ad2e 100644 --- a/src/axom/primal/tests/primal_slice.cpp +++ b/src/axom/primal/tests/primal_slice.cpp @@ -92,11 +92,10 @@ void check_slice_policy() EXPECT_EQ(polys_host[0].numVertices(), 4); EXPECT_NEAR(areas_host[0], 1., EPS); - expect_vertex_set(polys_host[0], - axom::StackArray {{Point3D {-1., -1., 0.}, - Point3D {-1., 0., 0.}, - Point3D {0., 0., 0.}, - Point3D {0., 1., 0.}}}); + expect_vertex_set( + polys_host[0], + axom::StackArray { + {Point3D {-1., -1., 0.}, Point3D {-1., 0., 0.}, Point3D {0., 0., 0.}, Point3D {0., 1., 0.}}}); expect_normal_aligned(polys_host[0], plane); } @@ -164,9 +163,8 @@ void check_slice_degenerate_policy() EXPECT_EQ(polys_host[2].numVertices(), 3); EXPECT_NEAR(areas_host[2], 0.5, EPS); expect_vertex_set(polys_host[2], - axom::StackArray {{Point3D {0., 0., 0.}, - Point3D {1., 0., 0.}, - Point3D {0., 1., 0.}}}); + axom::StackArray { + {Point3D {0., 0., 0.}, Point3D {1., 0., 0.}, Point3D {0., 1., 0.}}}); expect_normal_aligned(polys_host[2], PlaneType({0., 0., 1.}, 0.)); } @@ -186,9 +184,8 @@ TEST(primal_slice, tet_plane_slice_dynamic) EXPECT_EQ(poly.numVertices(), 3); EXPECT_NEAR(poly.area(), 0.28125, EPS); expect_vertex_set(poly, - axom::StackArray {{Point3D {0., 0., 0.25}, - Point3D {0.75, 0., 0.25}, - Point3D {0., 0.75, 0.25}}}); + axom::StackArray { + {Point3D {0., 0., 0.25}, Point3D {0.75, 0., 0.25}, Point3D {0., 0.75, 0.25}}}); expect_normal_aligned(poly, PlaneType({0., 0., 1.}, 0.25)); const auto empty_poly = primal::slice(tet, PlaneType({0., 0., 1.}, 2.)); @@ -223,9 +220,8 @@ TEST(primal_slice, tet_plane_slice_degenerate_dynamic) EXPECT_EQ(face_poly.numVertices(), 3); EXPECT_NEAR(face_poly.area(), 0.5, EPS); expect_vertex_set(face_poly, - axom::StackArray {{Point3D {0., 0., 0.}, - Point3D {1., 0., 0.}, - Point3D {0., 1., 0.}}}); + axom::StackArray { + {Point3D {0., 0., 0.}, Point3D {1., 0., 0.}, Point3D {0., 1., 0.}}}); expect_normal_aligned(face_poly, PlaneType({0., 0., 1.}, 0.)); } @@ -247,16 +243,14 @@ TEST(primal_slice, tet_plane_slice_orientation_follows_plane) const auto poly_pos = primal::slice(tet, plane_pos); const auto poly_neg = primal::slice(tet, plane_neg); - expect_vertex_set(poly_pos, - axom::StackArray {{Point3D {-1., -1., 0.}, - Point3D {-1., 0., 0.}, - Point3D {0., 0., 0.}, - Point3D {0., 1., 0.}}}); - expect_vertex_set(poly_neg, - axom::StackArray {{Point3D {-1., -1., 0.}, - Point3D {-1., 0., 0.}, - Point3D {0., 0., 0.}, - Point3D {0., 1., 0.}}}); + expect_vertex_set( + poly_pos, + axom::StackArray { + {Point3D {-1., -1., 0.}, Point3D {-1., 0., 0.}, Point3D {0., 0., 0.}, Point3D {0., 1., 0.}}}); + expect_vertex_set( + poly_neg, + axom::StackArray { + {Point3D {-1., -1., 0.}, Point3D {-1., 0., 0.}, Point3D {0., 0., 0.}, Point3D {0., 1., 0.}}}); expect_normal_aligned(poly_pos, plane_pos); expect_normal_aligned(poly_neg, plane_neg); EXPECT_LT(poly_pos.normal().dot(poly_neg.normal()), 0.); @@ -278,19 +272,18 @@ TEST(primal_slice, tet_plane_slice_near_boundary_tolerance) EXPECT_EQ(fuzzy_face.numVertices(), 3); EXPECT_NEAR(fuzzy_face.area(), 0.5, EPS); expect_vertex_set(fuzzy_face, - axom::StackArray {{Point3D {0., 0., 0.}, - Point3D {1., 0., 0.}, - Point3D {0., 1., 0.}}}); + axom::StackArray { + {Point3D {0., 0., 0.}, Point3D {1., 0., 0.}, Point3D {0., 1., 0.}}}); // Moving the plane clearly away from that fuzzy interval produces the // expected small triangle strictly above the base face instead. const auto nearby_slice = primal::slice(tet, PlaneType({0., 0., 1.}, 1e-6)); EXPECT_EQ(nearby_slice.numVertices(), 3); EXPECT_NEAR(nearby_slice.area(), 0.4999990000005, EPS); - expect_vertex_set(nearby_slice, - axom::StackArray {{Point3D {0., 0., 1e-6}, - Point3D {0.999999, 0., 1e-6}, - Point3D {0., 0.999999, 1e-6}}}); + expect_vertex_set( + nearby_slice, + axom::StackArray { + {Point3D {0., 0., 1e-6}, Point3D {0.999999, 0., 1e-6}, Point3D {0., 0.999999, 1e-6}}}); expect_normal_aligned(nearby_slice, PlaneType({0., 0., 1.}, 1e-6)); } @@ -311,9 +304,8 @@ TEST(primal_slice, tet_plane_slice_degenerate_tet_repeated_vertex) EXPECT_EQ(poly.numVertices(), 3); EXPECT_NEAR(poly.area(), 0.5, EPS); expect_vertex_set(poly, - axom::StackArray {{Point3D {0., 0., 0.}, - Point3D {1., 0., 0.}, - Point3D {0., 1., 0.}}}); + axom::StackArray { + {Point3D {0., 0., 0.}, Point3D {1., 0., 0.}, Point3D {0., 1., 0.}}}); expect_normal_aligned(poly, PlaneType({0., 0., 1.}, 0.)); } @@ -335,14 +327,20 @@ TEST(primal_slice, tet_plane_slice_invalid_plane) TEST(primal_slice, tet_plane_slice_seq) { check_slice_policy(); } -TEST(primal_slice, tet_plane_slice_degenerate_seq) { check_slice_degenerate_policy(); } +TEST(primal_slice, tet_plane_slice_degenerate_seq) +{ + check_slice_degenerate_policy(); +} #if defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) #ifdef AXOM_USE_OPENMP TEST(primal_slice, tet_plane_slice_omp) { check_slice_policy(); } -TEST(primal_slice, tet_plane_slice_degenerate_omp) { check_slice_degenerate_policy(); } +TEST(primal_slice, tet_plane_slice_degenerate_omp) +{ + check_slice_degenerate_policy(); +} #endif #ifdef AXOM_USE_CUDA @@ -357,7 +355,10 @@ AXOM_CUDA_TEST(primal_slice, tet_plane_slice_degenerate_cuda) #ifdef AXOM_USE_HIP TEST(primal_slice, tet_plane_slice_hip) { check_slice_policy>(); } -TEST(primal_slice, tet_plane_slice_degenerate_hip) { check_slice_degenerate_policy>(); } +TEST(primal_slice, tet_plane_slice_degenerate_hip) +{ + check_slice_degenerate_policy>(); +} #endif #endif From ffdbed95df24dd4a7a7cfc5afdfcf9bfcdb1d59f Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 30 Apr 2026 18:03:58 -0700 Subject: [PATCH 179/986] Changes to comments in new tests. --- src/axom/primal/tests/primal_intersect.cpp | 18 +++++++++--------- src/axom/primal/tests/primal_slice.cpp | 15 ++++++++------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/axom/primal/tests/primal_intersect.cpp b/src/axom/primal/tests/primal_intersect.cpp index 70e8d62f68..66fd2c2bca 100644 --- a/src/axom/primal/tests/primal_intersect.cpp +++ b/src/axom/primal/tests/primal_intersect.cpp @@ -295,33 +295,33 @@ TEST(primal_intersect, segment_segment_intersection) intersection)); EXPECT_TRUE(intersection.isNearlyEqual(Point3D {0.5, 0.5, 0.}, EPS)); - // Endpoint of segment 1 coincides with an endpoint of segment 2. + // An endpoint of segment 1 coincides with an endpoint of segment 2. EXPECT_TRUE(primal::intersect(Segment3D(Point3D {0., 0., 0.}, Point3D {1., 0., 0.}), Segment3D(Point3D {1., 0., 0.}, Point3D {1., 1., 0.}), intersection)); EXPECT_TRUE(intersection.isNearlyEqual(Point3D {1., 0., 0.}, EPS)); - // Endpoint of segment 1 coincides with an interior point of segment 2. + // An endpoint of segment 1 coincides with an internal point of segment 2. EXPECT_TRUE(primal::intersect(Segment3D(Point3D {1., 0., 0.}, Point3D {2., 0., 0.}), Segment3D(Point3D {0., 0., 0.}, Point3D {3., 0., 0.}), intersection)); EXPECT_TRUE(intersection.isNearlyEqual(Point3D {1., 0., 0.}, EPS)); - // The two segments are exactly the same. + // Overlap case: both segments are the same. EXPECT_TRUE(primal::intersect(Segment3D(Point3D {0., 0., 0.}, Point3D {2., 0., 0.}), Segment3D(Point3D {0., 0., 0.}, Point3D {2., 0., 0.}), intersection)); EXPECT_TRUE(intersection.isNearlyEqual(Point3D {0., 0., 0.}, EPS)); - // The two segments are exactly the same, but the second segment swaps - // endpoints. + // Overlap case: both segments are the same, but the second swaps endpoints. EXPECT_TRUE(primal::intersect(Segment3D(Point3D {0., 0., 0.}, Point3D {2., 0., 0.}), Segment3D(Point3D {2., 0., 0.}, Point3D {0., 0., 0.}), intersection)); EXPECT_TRUE(intersection.isNearlyEqual(Point3D {0., 0., 0.}, EPS)); - // Collinear segments partially overlap; the representative intersection - // point is the first point in the overlap encountered along segment 1. + // Overlap case: partial overlap b/w the segments. The representative + // intersection point is the first point in the overlap encountered along + // segment 1. EXPECT_TRUE(primal::intersect(Segment3D(Point3D {0., 0., 0.}, Point3D {2., 0., 0.}), Segment3D(Point3D {1., 0., 0.}, Point3D {3., 0., 0.}), intersection)); @@ -2788,14 +2788,14 @@ void check_segment_segment_intersect_policy() Q = SegmentType(PointType {0., 1., 0.}, PointType {1., 0., 0.}); } - // Endpoint of segment 1 coincides with an endpoint of segment 2. + // An endpoint of segment 1 coincides with an endpoint of segment 2. if(i == 1) { P = SegmentType(PointType {0., 0., 0.}, PointType {1., 0., 0.}); Q = SegmentType(PointType {1., 0., 0.}, PointType {1., 1., 0.}); } - // Collinear segments partially overlap. + // Overlap case: partial overlap b/w the segments. if(i == 2) { P = SegmentType(PointType {0., 0., 0.}, PointType {2., 0., 0.}); diff --git a/src/axom/primal/tests/primal_slice.cpp b/src/axom/primal/tests/primal_slice.cpp index 2bb408ad2e..9c317e93fa 100644 --- a/src/axom/primal/tests/primal_slice.cpp +++ b/src/axom/primal/tests/primal_slice.cpp @@ -125,20 +125,20 @@ void check_slice_degenerate_policy() AXOM_LAMBDA(int i) { PlaneType plane; - // The plane intersects the tetrahedron only at vertex (0,0,0). + // Edge case: the plane intersects the tet on a vertex. if(i == 0) { plane = PlaneType({1., 1., 1.}, 0.); } - // The plane intersects the tetrahedron only along the edge from + // Edge case: the plane intersects the tet on an edge, here the edge from // (0,0,0) to (1,0,0). if(i == 1) { plane = PlaneType({0., 1., 1.}, 0.); } - // The plane coincides with the face z = 0 of the tetrahedron. + // Edge case: the plane intersects the tet on a face, here the face z = 0. if(i == 2) { plane = PlaneType({0., 0., 1.}, 0.); @@ -202,20 +202,20 @@ TEST(primal_slice, tet_plane_slice_degenerate_dynamic) Point3D {0., 1., 0.}, Point3D {0., 0., 1.}}; - // The plane intersects the tetrahedron only at a single vertex. + // Edge case: the plane intersects the tet on a vertex. const auto vertex_poly = primal::slice(tet, PlaneType({1., 1., 1.}, 0.)); EXPECT_EQ(vertex_poly.numVertices(), 1); EXPECT_NEAR(vertex_poly.area(), 0., EPS); expect_vertex_set(vertex_poly, axom::StackArray {{Point3D {0., 0., 0.}}}); - // The plane intersects the tetrahedron only along a single edge. + // Edge case: the plane intersects the tet on an edge. const auto edge_poly = primal::slice(tet, PlaneType({0., 1., 1.}, 0.)); EXPECT_EQ(edge_poly.numVertices(), 2); EXPECT_NEAR(edge_poly.area(), 0., EPS); expect_vertex_set(edge_poly, axom::StackArray {{Point3D {0., 0., 0.}, Point3D {1., 0., 0.}}}); - // The plane coincides with one face of the tetrahedron. + // Edge case: the plane intersects the tet on a face. const auto face_poly = primal::slice(tet, PlaneType({0., 0., 1.}, 0.)); EXPECT_EQ(face_poly.numVertices(), 3); EXPECT_NEAR(face_poly.area(), 0.5, EPS); @@ -236,7 +236,8 @@ TEST(primal_slice, tet_plane_slice_orientation_follows_plane) Point3D {-1., 1., 1.}}; // Flipping the slicing plane should preserve the vertex set while reversing - // the polygon orientation to keep the polygon normal aligned with the plane. + // the polygon orientation so the Polygon orientation matches that of the + // Plane. const PlaneType plane_pos({0., 0., 1.}, 0.); const PlaneType plane_neg({0., 0., -1.}, 0.); From 61bc7fccb4d2063de7401d071b91e78400434b09 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 4 May 2026 12:17:52 -0700 Subject: [PATCH 180/986] Change SamplingShaper so it supports various quadrature functions with anisotrophy. --- src/axom/quest/SamplingShaper.hpp | 104 +++++++++----- .../quest/detail/shaping/InOutSampler.hpp | 10 +- .../quest/detail/shaping/PrimitiveSampler.hpp | 10 +- .../detail/shaping/WindingNumberSampler.hpp | 12 +- .../quest/detail/shaping/shaping_helpers.cpp | 135 +++++++++++++++--- .../quest/detail/shaping/shaping_helpers.hpp | 25 +++- src/axom/quest/examples/shaping_driver.cpp | 97 ++++++++++++- .../quest/tests/quest_sampling_shaper.cpp | 97 ++++++++++++- .../lesson_04/quest_sampling_shaper.cpp | 10 +- 9 files changed, 411 insertions(+), 89 deletions(-) diff --git a/src/axom/quest/SamplingShaper.hpp b/src/axom/quest/SamplingShaper.hpp index 710947457e..0f77e05e50 100644 --- a/src/axom/quest/SamplingShaper.hpp +++ b/src/axom/quest/SamplingShaper.hpp @@ -159,7 +159,42 @@ class SamplingShaper : public Shaper void setSamplingMethod(SamplingMethod samplingMethod) { m_samplingMethod = samplingMethod; } - void setQuadratureOrder(int quadratureOrder) { m_quadratureOrder = quadratureOrder; } + void setQuadratureType(int qtype) + { + if(qtype >= static_cast(mfem::Quadrature1D::Invalid) && + qtype <= static_cast(mfem::Quadrature1D::ClosedGL)) + { + m_quadratureType = qtype; + } + else + { + SLIC_ERROR(axom::fmt::format("Invalid quadrature type value {}", qtype)); + } + } + + void setSamplingResolution(int sampleRes) + { + SLIC_ASSERT(sampleRes > 0); + m_sampleResolution[0] = sampleRes; + m_sampleResolution[1] = sampleRes; + m_sampleResolution[2] = sampleRes; + } + + void setSamplingResolution(int sampleRes[3]) + { + SLIC_ASSERT(sampleRes[0] > 0); + SLIC_ASSERT(sampleRes[1] > 0); + SLIC_ASSERT(sampleRes[2] > 0); + m_sampleResolution[0] = sampleRes[0]; + m_sampleResolution[1] = sampleRes[1]; + m_sampleResolution[2] = sampleRes[2]; + } + + // Deprecated backward compatibility method + [[deprecated]] void setQuadratureOrder(int order) + { + setSamplingResolution(order); + } void setVolumeFractionOrder(int volfracOrder) { m_volfracOrder = volfracOrder; } @@ -519,7 +554,7 @@ class SamplingShaper : public Shaper // ensure we have a starting quadrature field for the positions if(!m_inoutShapeQFuncs.Has("positions")) { - shaping::generatePositionsQFunction(mesh, m_inoutShapeQFuncs, m_quadratureOrder); + shaping::generatePositionsQFunction(mesh, m_inoutShapeQFuncs, m_sampleResolution, m_quadratureType); } auto* positionsQSpace = m_inoutShapeQFuncs.Get("positions")->GetSpace(); @@ -621,7 +656,7 @@ class SamplingShaper : public Shaper private: // Handles 2D or 3D shaping for compatible samplers, based on the template and associated parameter template - void runShapeQueryImplSampler(SamplerType* shaper) + void runShapeQueryImplSampler(SamplerType* sampler) { // Sample the InOut field at the mesh quadrature points const int meshDim = m_dc->GetMesh()->Dimension(); @@ -633,32 +668,36 @@ class SamplingShaper : public Shaper case 2: if(meshDim == 2) { - shaper->template sampleInOutField<2, 2>(m_dc, + sampler->template sampleInOutField<2, 2>(m_dc, m_inoutShapeQFuncs, - m_quadratureOrder, + m_sampleResolution, + m_quadratureType, m_projector22); } else if(meshDim == 3) { - shaper->template sampleInOutField<3, 2>(m_dc, + sampler->template sampleInOutField<3, 2>(m_dc, m_inoutShapeQFuncs, - m_quadratureOrder, + m_sampleResolution, + m_quadratureType, m_projector32); } break; case 3: if(meshDim == 2) { - shaper->template sampleInOutField<2, 3>(m_dc, + sampler->template sampleInOutField<2, 3>(m_dc, m_inoutShapeQFuncs, - m_quadratureOrder, + m_sampleResolution, + m_quadratureType, m_projector23); } else if(meshDim == 3) { - shaper->template sampleInOutField<3, 3>(m_dc, + sampler->template sampleInOutField<3, 3>(m_dc, m_inoutShapeQFuncs, - m_quadratureOrder, + m_sampleResolution, + m_quadratureType, m_projector33); } break; @@ -670,15 +709,13 @@ class SamplingShaper : public Shaper case 2: if(meshDim == 2) { - shaper->template computeVolumeFractionsBaseline<2, 2>(m_dc, - m_quadratureOrder, + sampler->template computeVolumeFractionsBaseline<2, 2>(m_dc, m_volfracOrder, m_projector22); } else if(meshDim == 3) { - shaper->template computeVolumeFractionsBaseline<3, 2>(m_dc, - m_quadratureOrder, + sampler->template computeVolumeFractionsBaseline<3, 2>(m_dc, m_volfracOrder, m_projector32); } @@ -686,15 +723,13 @@ class SamplingShaper : public Shaper case 3: if(meshDim == 2) { - shaper->template computeVolumeFractionsBaseline<2, 3>(m_dc, - m_quadratureOrder, + sampler->template computeVolumeFractionsBaseline<2, 3>(m_dc, m_volfracOrder, m_projector23); } else if(meshDim == 3) { - shaper->template computeVolumeFractionsBaseline<3, 3>(m_dc, - m_quadratureOrder, + sampler->template computeVolumeFractionsBaseline<3, 3>(m_dc, m_volfracOrder, m_projector33); } @@ -706,21 +741,21 @@ class SamplingShaper : public Shaper // Handles 2D or 3D shaping for InOutSampler, based on the template and associated parameter template - void runShapeQueryImpl(shaping::InOutSampler* shaper) + void runShapeQueryImpl(shaping::InOutSampler* sampler) { - runShapeQueryImplSampler(shaper); + runShapeQueryImplSampler(sampler); } // Handles 2D or 3D shaping for InOutSampler, based on the template and associated parameter template - void runShapeQueryImpl(shaping::WindingNumberSampler* shaper) + void runShapeQueryImpl(shaping::WindingNumberSampler* sampler) { - runShapeQueryImplSampler(shaper); + runShapeQueryImplSampler(sampler); } // Handles 2D or 3D shaping for PrimitiveSampler, based on the template and associated parameter template - void runShapeQueryImpl(shaping::PrimitiveSampler* shaper) + void runShapeQueryImpl(shaping::PrimitiveSampler* sampler) { // Sample the InOut field at the mesh quadrature points const int meshDim = m_dc->GetMesh()->Dimension(); @@ -735,16 +770,18 @@ class SamplingShaper : public Shaper case 3: if(meshDim == 2) { - shaper->template sampleInOutField<2, 3>(m_dc, + sampler->template sampleInOutField<2, 3>(m_dc, m_inoutShapeQFuncs, - m_quadratureOrder, + m_sampleResolution, + m_quadratureType, m_projector23); } else if(meshDim == 3) { - shaper->template sampleInOutField<3, 3>(m_dc, + sampler->template sampleInOutField<3, 3>(m_dc, m_inoutShapeQFuncs, - m_quadratureOrder, + m_sampleResolution, + m_quadratureType, m_projector33); } break; @@ -782,13 +819,13 @@ class SamplingShaper : public Shaper const int NE = mesh->GetNE(); const auto geom = mesh->GetTypicalElementGeometry(); - auto samples_per_dim = [=](int sampleNQ, mfem::Geometry::Type geom) -> std::string { + auto samples_per_dim = [=](int sampleRes[3], mfem::Geometry::Type geom) -> std::string { switch(geom) { case mfem::Geometry::SQUARE: - return axom::fmt::format(" ({} per dimension)", sqrt(sampleNQ)); + return axom::fmt::format(" ({} * {})", sampleRes[0], sampleRes[1]); case mfem::Geometry::CUBE: - return axom::fmt::format(" ({} per dimension)", std::cbrt(sampleNQ)); + return axom::fmt::format(" ({} * {} * {})", sampleRes[0], sampleRes[1], sampleRes[2]); default: return std::string(); } @@ -800,7 +837,7 @@ class SamplingShaper : public Shaper "In computeVolumeFractions(): num samples per element {}{} | " "sample polynomial order {} | total samples {:L}", sampleNQ, - samples_per_dim(sampleNQ, geom), + samples_per_dim(m_sampleResolution, geom), sampleOrder, sampleSZ)); @@ -995,7 +1032,8 @@ class SamplingShaper : public Shaper shaping::PointProjector<3, 3> m_projector33 {}; shaping::VolFracSampling m_vfSampling {shaping::VolFracSampling::SAMPLE_AT_QPTS}; - int m_quadratureOrder {5}; + int m_quadratureType {static_cast(mfem::Quadrature1D::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/InOutSampler.hpp b/src/axom/quest/detail/shaping/InOutSampler.hpp index 0116715788..a2f87ef1bd 100644 --- a/src/axom/quest/detail/shaping/InOutSampler.hpp +++ b/src/axom/quest/detail/shaping/InOutSampler.hpp @@ -116,7 +116,8 @@ class InOutSampler template std::enable_if_t sampleInOutField(mfem::DataCollection* dc, shaping::QFunctionCollection& inoutQFuncs, - int sampleRes, + int sampleRes[3], + int quadratureType, PointProjector projector = {}) { using PointType = primal::Point; @@ -127,6 +128,7 @@ class InOutSampler dc, inoutQFuncs, sampleRes, + quadratureType, checkInside, projector); } @@ -138,7 +140,8 @@ class InOutSampler template std::enable_if_t sampleInOutField(mfem::DataCollection*, shaping::QFunctionCollection&, - int, + int AXOM_UNUSED_PARAM(sampleRes)[3], + int AXOM_UNUSED_PARAM(quadratureType), PointProjector) { static_assert(ToDim != DIM, @@ -153,7 +156,6 @@ class InOutSampler template std::enable_if_t computeVolumeFractionsBaseline( mfem::DataCollection* dc, - int sampleRes, int outputOrder, PointProjector projector = {}) { @@ -162,7 +164,6 @@ class InOutSampler auto checkInside = [=](const PointType& pt) -> bool { return octree->within(pt); }; shaping::computeVolumeFractionsBaseline(m_shapeName, dc, - sampleRes, outputOrder, checkInside, projector); @@ -175,7 +176,6 @@ class InOutSampler template std::enable_if_t computeVolumeFractionsBaseline( mfem::DataCollection* AXOM_UNUSED_PARAM(dc), - int AXOM_UNUSED_PARAM(sampleRes), 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 9894800051..0ed36f6ac8 100644 --- a/src/axom/quest/detail/shaping/PrimitiveSampler.hpp +++ b/src/axom/quest/detail/shaping/PrimitiveSampler.hpp @@ -165,6 +165,7 @@ class PrimitiveSampler * \param [inout] inoutQFuncs A collection of quadrature functions for the shape and material * inout samples * \param [in] sampleRes The quadrature order at which to sample the inout field + * \param [in] quadratureType The quadrature type to use to construct the sample point locations. * \param [in] projector A callback function to apply to points from the input mesh * before querying them on the spatial index * @@ -175,7 +176,8 @@ class PrimitiveSampler template std::enable_if_t sampleInOutField(mfem::DataCollection* dc, shaping::QFunctionCollection& inoutQFuncs, - int sampleRes, + int sampleRes[3], + int quadratureType, PointProjector projector = {}) { using FromPoint = primal::Point; @@ -193,7 +195,7 @@ class PrimitiveSampler // Generate a Quadrature Function with the geometric positions, if not already available if(!inoutQFuncs.Has("positions")) { - shaping::generatePositionsQFunction(mesh, inoutQFuncs, sampleRes); + shaping::generatePositionsQFunction(mesh, inoutQFuncs, sampleRes, quadratureType); } // Access the positions QFunc and associated QuadratureSpace @@ -288,7 +290,8 @@ class PrimitiveSampler template std::enable_if_t sampleInOutField(mfem::DataCollection*, shaping::QFunctionCollection&, - int, + int AXOM_UNUSED_PARAM(sampleRes)[3], + int AXOM_UNUSED_PARAM(quadratureType), PointProjector) { static_assert(ToDim != DIM, @@ -303,7 +306,6 @@ class PrimitiveSampler */ template void computeVolumeFractionsBaseline(mfem::DataCollection* AXOM_UNUSED_PARAM(dc), - int AXOM_UNUSED_PARAM(sampleRes), 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 e99b6a4467..418bc48562 100644 --- a/src/axom/quest/detail/shaping/WindingNumberSampler.hpp +++ b/src/axom/quest/detail/shaping/WindingNumberSampler.hpp @@ -134,6 +134,7 @@ class WindingNumberSampler * \param [inout] inoutQFuncs A collection of quadrature functions for the shape and material * inout samples * \param [in] sampleRes The quadrature order at which to sample the inout field + * \param [in] quadratureType The quadrature type to use to construct the sample point locations. * \param [in] projector A callback function to apply to points from the input mesh * before querying them on the spatial index * @@ -144,7 +145,8 @@ class WindingNumberSampler template std::enable_if_t sampleInOutField(mfem::DataCollection* dc, shaping::QFunctionCollection& inoutQFuncs, - int sampleRes, + int sampleRes[3], + int quadratureType, PointProjector projector = {}) { static_assert(axom::execution_space::onDevice() == false, @@ -166,7 +168,7 @@ class WindingNumberSampler // Generate a Quadrature Function with the geometric positions, if not already available if(!inoutQFuncs.Has("positions")) { - shaping::generatePositionsQFunction(mesh, inoutQFuncs, sampleRes); + shaping::generatePositionsQFunction(mesh, inoutQFuncs, sampleRes, quadratureType); } // Access the positions QFunc and associated QuadratureSpace @@ -261,7 +263,8 @@ class WindingNumberSampler template std::enable_if_t sampleInOutField(mfem::DataCollection*, shaping::QFunctionCollection&, - int, + int AXOM_UNUSED_PARAM(sampleRes)[3], + int AXOM_UNUSED_PARAM(quadratureType), PointProjector) { static_assert(ToDim != DIM, @@ -276,7 +279,6 @@ class WindingNumberSampler template std::enable_if_t computeVolumeFractionsBaseline( mfem::DataCollection* dc, - int sampleRes, int outputOrder, PointProjector projector = {}) { @@ -295,7 +297,6 @@ class WindingNumberSampler }; shaping::computeVolumeFractionsBaseline(m_shapeName, dc, - sampleRes, outputOrder, checkInside, projector); @@ -308,7 +309,6 @@ class WindingNumberSampler template std::enable_if_t computeVolumeFractionsBaseline( mfem::DataCollection* AXOM_UNUSED_PARAM(dc), - int AXOM_UNUSED_PARAM(sampleRes), int AXOM_UNUSED_PARAM(outputOrder), PointProjector AXOM_UNUSED_PARAM(projector)) { diff --git a/src/axom/quest/detail/shaping/shaping_helpers.cpp b/src/axom/quest/detail/shaping/shaping_helpers.cpp index b9232effa1..0d44c83247 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers.cpp +++ b/src/axom/quest/detail/shaping/shaping_helpers.cpp @@ -135,44 +135,123 @@ void copyShapeIntoMaterial(const mfem::QuadratureFunction* shapeQFunc, } } -/// Generates a quadrature function corresponding to the mesh "positions" field -void generatePositionsQFunction(mfem::Mesh* mesh, QFunctionCollection& inoutQFuncs, int sampleRes) +mfem::QuadratureSpace *makeDefaultQuadratureSpace(mfem::Mesh* mesh, int sampleRes) { SLIC_ASSERT(mesh != nullptr); const int NE = mesh->GetNE(); - const int dim = mesh->Dimension(); if(NE < 1) { SLIC_WARNING("Mesh has no elements!"); - return; + 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; - mfem::QuadratureSpace* sp = new mfem::QuadratureSpace(mesh, sampleOrder); + return new mfem::QuadratureSpace(mesh, sampleOrder); +} + +mfem::QuadratureSpace *makeCustomQuadratureSpace(mfem::Mesh* mesh, int sampleRes[3], int 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 nullptr; + } + + // Make custom integration rule + mfem::IntegrationRule *ir = nullptr, 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 mfem::Quadrature1D::GaussLegendre: + mfem::QuadratureFunctions1D::GaussLegendre(sampleRes[d], &ird[d]); + break; + case mfem::Quadrature1D::GaussLobatto: + mfem::QuadratureFunctions1D::GaussLobatto(sampleRes[d], &ird[d]); + break; + case mfem::Quadrature1D::OpenUniform: + mfem::QuadratureFunctions1D::OpenUniform(sampleRes[d], &ird[d]); + break; + case mfem::Quadrature1D::ClosedUniform: + mfem::QuadratureFunctions1D::ClosedUniform(sampleRes[d], &ird[d]); + break; + case mfem::Quadrature1D::OpenHalfUniform: + mfem::QuadratureFunctions1D::OpenHalfUniform(sampleRes[d], &ird[d]); + break; + case mfem::Quadrature1D::ClosedGL: + mfem::QuadratureFunctions1D::ClosedGL(sampleRes[d], &ird[d]); + break; + default: + SLIC_ERROR(axom::fmt::format("Invalid quadrature type {}.", quadratureType)); + break; + } + } + if(dim == 1) + { + ir = new mfem::IntegrationRule(ird[0]); + } + else if(dim == 2) + { + ir = new mfem::IntegrationRule(ird[0], ird[1]); + } + else if(dim == 3) + { + ir = new mfem::IntegrationRule(ird[0], ird[1], ird[2]); + } + + return new mfem::QuadratureSpace(*mesh, *ir); +} + +/// Generates a quadrature function corresponding to the mesh "positions" field +void generatePositionsQFunction(mfem::Mesh* mesh, QFunctionCollection& inoutQFuncs, + int sampleResolution[3], int quadratureType) +{ + SLIC_ASSERT(mesh != nullptr); + const int NE = mesh->GetNE(); + const int dim = mesh->Dimension(); - // TODO: Should the samples be along a uniform grid - // instead of Guassian quadrature? - // This would need quadrature weights for the uniform - // samples -- Newton-Cotes ? - // With uniform points, we could do HO polynomial fitting - // Using 0s and 1s is non-oscillatory in Bernstein basis + 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 == static_cast(mfem::Quadrature1D::Invalid)) + { + 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(); - const auto* geomFactors = mesh->GetGeometricFactors(ir, mfem::GeometricFactors::COORDINATES); - geomFactors->X.HostRead(); +std::cout << "generatePositionsQFunction: ir.GetNPoints()=" << nq << std::endl; mfem::QuadratureFunction* pos_coef = new mfem::QuadratureFunction(sp, dim); pos_coef->SetOwnsSpace(true); auto pos = mfem::Reshape(pos_coef->HostWrite(), dim, nq, NE); - // Rearrange positions into quadrature function + if(quadratureType == static_cast(mfem::Quadrature1D::Invalid)) { + 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; @@ -180,17 +259,37 @@ void generatePositionsQFunction(mfem::Mesh* mesh, QFunctionCollection& inoutQFun { for(int k = 0; k < nq; ++k) { - //X has dims nqpts x sdim x ne + // 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 custom anisotropic 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); - // Delete the geometric factors associated w/ our custom quadrature rule - mesh->DeleteGeometricFactors(); + 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 wil handle its deletion + // register positions with the QFunction collection, which will handle its deletion inoutQFuncs.Register("positions", pos_coef, true); } diff --git a/src/axom/quest/detail/shaping/shaping_helpers.hpp b/src/axom/quest/detail/shaping/shaping_helpers.hpp index 5410910b35..a189c3a5b8 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers.hpp +++ b/src/axom/quest/detail/shaping/shaping_helpers.hpp @@ -195,9 +195,20 @@ void replaceMaterial(mfem::QuadratureFunction* shapeQFunc, void copyShapeIntoMaterial(const mfem::QuadratureFunction* shapeQFunc, mfem::QuadratureFunction* materialQFunc, bool reuseExisting = true); - -/// Generates a quadrature function corresponding to the mesh positions -void generatePositionsQFunction(mfem::Mesh* mesh, QFunctionCollection& inoutQFuncs, int sampleRes); +/** + * \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. + * \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, int sampleResolution[3], int quadratureType); /** * Implements flux-corrected transport (FCT) to correct the solution obtained @@ -241,6 +252,7 @@ void computeVolumeFractionsIdentity(mfem::DataCollection* dc, * \param [inout] inoutQFuncs A collection of quadrature functions for the shape and material * inout samples * \param [in] sampleRes The quadrature order at which to sample the inout field + * \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 @@ -252,7 +264,8 @@ template void sampleInOutField(const std::string shapeName, mfem::DataCollection* dc, shaping::QFunctionCollection& inoutQFuncs, - int sampleRes, + int sampleRes[3], + int quadratureType, InsideFunc&& checkInside, PointProjector projector = {}) { @@ -271,7 +284,7 @@ void sampleInOutField(const std::string shapeName, // Generate a Quadrature Function with the geometric positions, if not already available if(!inoutQFuncs.Has("positions")) { - shaping::generatePositionsQFunction(mesh, inoutQFuncs, sampleRes); + shaping::generatePositionsQFunction(mesh, inoutQFuncs, sampleRes, quadratureType); } // Access the positions QFunc and associated QuadratureSpace @@ -337,7 +350,6 @@ void sampleInOutField(const std::string shapeName, * * \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] sampleRes The quadrature order at which to sample the inout field * \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 @@ -349,7 +361,6 @@ void sampleInOutField(const std::string shapeName, template void computeVolumeFractionsBaseline(const std::string& shapeName, mfem::DataCollection* dc, - int AXOM_UNUSED_PARAM(sampleRes), int outputOrder, InsideFunc&& checkInside, PointProjector projector = {}) diff --git a/src/axom/quest/examples/shaping_driver.cpp b/src/axom/quest/examples/shaping_driver.cpp index 8210d30d06..8d76b17ce0 100644 --- a/src/axom/quest/examples/shaping_driver.cpp +++ b/src/axom/quest/examples/shaping_driver.cpp @@ -26,6 +26,14 @@ #error Shaping functionality requires Axom to be configured with Conduit or MFEM #endif +#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 "mfem.hpp" #ifdef AXOM_USE_MPI @@ -33,6 +41,7 @@ #endif // C/C++ includes +#include #include #include #include @@ -97,7 +106,9 @@ struct Input ShapingMethod shapingMethod {ShapingMethod::Sampling}; SamplingMethod samplingMethod {SamplingMethod::InOut}; RuntimePolicy policy {RuntimePolicy::seq}; - int quadratureOrder {5}; + std::vector samplingResolution{5, 5, 5}; + // We set quadratureType to Invalid to select the default method. + int quadratureType {static_cast(mfem::Quadrature1D::Invalid)}; int outputOrder {2}; int samplesPerKnotSpan {25}; int refinementLevel {7}; @@ -300,11 +311,12 @@ struct Input ->capture_default_str() ->check(axom::CLI::NonNegativeNumber); - sampling_options->add_option("-q,--quadrature-order", quadratureOrder) + sampling_options->add_option("--sampling-resolution", samplingResolution) ->description( - "Quadrature order for sampling the inout field. \n" + "Sampling resolution per element for the inout field (x,y,[z]). \n" "Determines number of samples per element in determining volume fraction field") - ->capture_default_str() + ->expected(2, 3) + ///->capture_default_str(); ->check(axom::CLI::PositiveNumber); std::map vfsamplingMap { @@ -316,6 +328,21 @@ struct Input "Sampling either at quadrature points or collocated with degrees of freedom") ->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}}; + sampling_options->add_option("-q,--quadrature-type", quadratureType) + ->description( + "Quadrature type. \n" + "Selects the type of quadrature that determines point placement within elements.") + ->capture_default_str() + ->transform(axom::CLI::CheckedTransformer(quadTypeMap, axom::CLI::ignore_case)); } // parameters that only apply to the intersection method @@ -460,6 +487,50 @@ void finalizeLogger() } } +//------------------------------------------------------------------------------ +/// Write the quadrature points as a Blueprint mesh. +void save_quadrature_points(mfem::QuadratureFunction *positions) +{ +#ifdef CONDUIT_RELAY_IO_HDF5_ENABLED +std::cout << "save_quadrature_points(mfem::QuadratureFunction *positions)\n"; + 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(); + +std::cout << "NE=" << positions->GetSpace()->GetMesh()->GetNE() << std::endl; +std::cout << "positions->Size()=" << positions->Size() << std::endl; +std::cout << "positions->GetVDim()=" << positions->GetVDim() << std::endl; +std::cout << "positions->Capacity()=" << positions->Capacity() << std::endl; +std::cout << "npts=" << npts << std::endl; + + 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 +} + //------------------------------------------------------------------------------ int main(int argc, char** argv) { @@ -588,8 +659,15 @@ int main(int argc, char** argv) // Set specific parameters for a SamplingShaper, if appropriate if(auto* samplingShaper = dynamic_cast(shaper)) { + int res[3] = {5, 5, 5}; + for(size_t i = 0; i < std::min(size_t{3}, params.samplingResolution.size()); i++) + { + res[i] = params.samplingResolution[i]; + } + samplingShaper->setSamplingType(params.vfSampling); - samplingShaper->setQuadratureOrder(params.quadratureOrder); + samplingShaper->setSamplingResolution(res); + samplingShaper->setQuadratureType(params.quadratureType); samplingShaper->setVolumeFractionOrder(params.outputOrder); samplingShaper->setSamplingMethod(params.samplingMethod); @@ -750,6 +828,15 @@ int main(int argc, char** argv) { AXOM_ANNOTATE_SCOPE("save shaping results"); shaper->getDC()->Save(); + if(auto* samplingShaper = dynamic_cast(shaper)) + { + mfem::QuadratureFunction *positions = samplingShaper->getShapeQFunction("positions"); + //if(params.quadratureType != static_cast(mfem::Quadrature1D::Invalid)) + if(positions) + { + save_quadrature_points(positions); + } + } } #endif diff --git a/src/axom/quest/tests/quest_sampling_shaper.cpp b/src/axom/quest/tests/quest_sampling_shaper.cpp index 474a90e0d1..50c4a9ceb4 100644 --- a/src/axom/quest/tests/quest_sampling_shaper.cpp +++ b/src/axom/quest/tests/quest_sampling_shaper.cpp @@ -18,6 +18,7 @@ #include "axom/sidre.hpp" #include "axom/slic.hpp" #include "axom/quest/SamplingShaper.hpp" +#include "axom/quest/detail/shaping/shaping_helpers.hpp" #include "axom/quest/util/mesh_helpers.hpp" #ifndef AXOM_USE_MFEM @@ -607,6 +608,34 @@ class SampleTester2D : public SamplingShaperTest } }; +/// Test fixture for SamplingShaper tests on a single curved 2D MFEM element +class CurvedSampleTester2D : public SamplingShaperTest +{ +public: + using Point2D = primal::Point; + using BBox2D = primal::BoundingBox; + +public: + virtual ~CurvedSampleTester2D() { } + + void SetUp() override + { + const int polynomialOrder = 2; + const BBox2D bbox({0., 0.}, {1., 1.}); + const axom::NumericArray celldims {1, 1}; + + auto* mesh = quest::util::make_cartesian_mfem_mesh_2D(bbox, celldims, polynomialOrder); + + m_dc.SetOwnData(true); + m_dc.SetMeshNodesName("positions"); + m_dc.SetMesh(mesh); + +#ifdef AXOM_USE_MPI + m_dc.SetComm(MPI_COMM_WORLD); +#endif + } +}; + //----------------------------------------------------------------------------- TEST_F(SamplingShaperTest2D, check_mesh) @@ -1297,7 +1326,7 @@ dimensions: 2 // set projector from 2D mesh points to 3D query points within STL this->m_shaper->setPointProjector23(Projector23 {}); - this->m_shaper->setQuadratureOrder(8); + this->m_shaper->setSamplingResolution(8); this->runShaping(); @@ -1442,7 +1471,7 @@ Ordering: 1 // Use WindingNumber shaping! this->m_shaper->setSamplingMethod(quest::SamplingShaper::SamplingMethod::WindingNumber); - this->m_shaper->setQuadratureOrder(8); + this->m_shaper->setSamplingResolution(8); this->runShaping(); // Check that the result has a volume fraction field associated with square materials @@ -1877,7 +1906,7 @@ dimensions: 2 this->m_shaper->setPointProjector32(AxisymmetricProjector32 {}); // we need a higher quadrature order to resolve this shape at the (low) testing resolution - this->m_shaper->setQuadratureOrder(8); + this->m_shaper->setSamplingResolution(8); this->runShaping(); @@ -1951,7 +1980,7 @@ dimensions: 2 this->m_shaper->setPointProjector32(AxisymmetricProjector32 {}); // we need a higher quadrature order to resolve this shape at the (low) testing resolution - this->m_shaper->setQuadratureOrder(8); + this->m_shaper->setSamplingResolution(8); this->runShaping(); @@ -2040,7 +2069,7 @@ dimensions: 3 this->m_shaper->setPointProjector23(PlaneProjector23 {z}); // we need a higher quadrature order to resolve this shape at the (low) testing resolution - this->m_shaper->setQuadratureOrder(8); + this->m_shaper->setSamplingResolution(8); this->runShaping(); @@ -2138,7 +2167,7 @@ piece = line(end=start) this->initializeShaping(shape_file.getPath()); this->m_shaper->setVolumeFractionOrder(0); - this->m_shaper->setQuadratureOrder(qorder); + this->m_shaper->setSamplingResolution(qorder); this->runShaping(); @@ -2192,6 +2221,62 @@ piece = line(end=start) //----------------------------------------------------------------------------- +TEST_F(CurvedSampleTester2D, positions_match_curved_mesh_for_anisotropic_custom_quadrature) +{ + auto& mesh = this->getMesh(); + auto* nodes = mesh.GetNodes(); + ASSERT_NE(nodes, nullptr); + + mfem::VectorFunctionCoefficient warp( + 2, + [](const mfem::Vector& x, mfem::Vector& y) { + constexpr double PI_LOCAL = 3.14159265358979323846; + y.SetSize(2); + y[0] = x[0] + 0.08 * std::sin(PI_LOCAL * x[0]) * std::sin(PI_LOCAL * x[1]); + y[1] = x[1] + 0.05 * std::sin(PI_LOCAL * x[0]) * std::sin(0.5 * PI_LOCAL * x[1]); + }); + nodes->ProjectCoefficient(warp); + + int sampleRes[3] = {5, 3, 1}; + quest::shaping::QFunctionCollection qfuncs; + quest::shaping::generatePositionsQFunction( + &mesh, + qfuncs, + sampleRes, + static_cast(mfem::Quadrature1D::OpenUniform)); + + auto* positions = qfuncs.Get("positions"); + ASSERT_NE(positions, nullptr); + + auto* qspace = dynamic_cast(positions->GetSpace()); + ASSERT_NE(qspace, nullptr); + + const auto& ir = qspace->GetElementIntRule(0); + const int nq = ir.GetNPoints(); + const auto pos = mfem::Reshape(positions->HostRead(), 2, nq, mesh.GetNE()); + + mfem::DenseMatrix expected(2, nq); + constexpr double EPS = 1e-12; + EXPECT_EQ(nq, sampleRes[0] * sampleRes[1]); + + for(int e = 0; e < mesh.GetNE(); ++e) + { + auto* transform = qspace->GetTransformation(e); + transform->Transform(ir, expected); + + for(int q = 0; q < nq; ++q) + { + for(int d = 0; d < 2; ++d) + { + EXPECT_NEAR(pos(d, q, e), expected(d, q), EPS) + << axom::fmt::format("Element {}, point {}, component {}", e, q, d); + } + } + } +} + +//----------------------------------------------------------------------------- + TEST_F(SamplingShaperTest2D, loadShape_missing_c2c_file_aborts) { // Tests Klee shape file referencing non-existant c2c file; should fail diff --git a/src/examples/shaping_tutorial/lesson_04/quest_sampling_shaper.cpp b/src/examples/shaping_tutorial/lesson_04/quest_sampling_shaper.cpp index 86b8f14798..00c67cbfe4 100644 --- a/src/examples/shaping_tutorial/lesson_04/quest_sampling_shaper.cpp +++ b/src/examples/shaping_tutorial/lesson_04/quest_sampling_shaper.cpp @@ -72,7 +72,7 @@ struct MeshMetadata std::string background_material; int volume_fraction_order {2}; int mesh_order {1}; - int quadrature_order {5}; + int sampling_resolution {5}; quest::SamplingShaper::SamplingMethod sampling_method {quest::SamplingShaper::SamplingMethod::InOut}; public: @@ -122,7 +122,7 @@ struct MeshMetadata .range(1, std::numeric_limits::max()); mesh_schema.addInt("mesh_order", "Order for mesh nodes (>= 1)") .range(1, std::numeric_limits::max()); - mesh_schema.addInt("quadrature_order", "Order for quadrature (>= 1)") + mesh_schema.addInt("sampling_resolution", "Sampling resolution (>= 1)") .range(1, std::numeric_limits::max()); mesh_schema.addString("sampling_method", "Sampling method ('inout' or 'winding')") @@ -227,9 +227,9 @@ struct FromInlet result.volume_fraction_order = static_cast(input_data["volume_fraction_order"]); } - if(input_data.contains("quadrature_order")) + if(input_data.contains("sampling_resolution")) { - result.quadrature_order = static_cast(input_data["quadrature_order"]); + result.sampling_resolution = static_cast(input_data["sampling_resolution"]); } if(input_data.contains("sampling_method")) @@ -401,7 +401,7 @@ int main(int argc, char** argv) shapeSet, &dc); shaper->setVerbosity(verbose); - shaper->setQuadratureOrder(meta.quadrature_order); + shaper->setSamplingResolution(meta.sampling_resolution); shaper->setVolumeFractionOrder(meta.volume_fraction_order); shaper->setSamplingMethod(meta.sampling_method); From c4cc636f583541d68da81ba9df26621746535f09 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 4 May 2026 16:40:57 -0700 Subject: [PATCH 181/986] Improvements to work around problems with anisotropic sampling. --- src/axom/quest/SamplingShaper.hpp | 58 ++++++++++++++++--- .../quest/detail/shaping/shaping_helpers.cpp | 1 - .../quest/tests/quest_sampling_shaper.cpp | 50 ++++++++++++++++ 3 files changed, 100 insertions(+), 9 deletions(-) diff --git a/src/axom/quest/SamplingShaper.hpp b/src/axom/quest/SamplingShaper.hpp index 0f77e05e50..71e710cf9f 100644 --- a/src/axom/quest/SamplingShaper.hpp +++ b/src/axom/quest/SamplingShaper.hpp @@ -955,14 +955,7 @@ class SamplingShaper : public Shaper b = 0.; b.ReadWrite(); - mfem::QuadratureFunctionCoefficient qfc(*inout); - mfem::DomainLFIntegrator rhs(qfc, &sampleIR); - - mfem::Array elem_marker(fes->GetNE()); - elem_marker.HostWrite(); - elem_marker = 1; - elem_marker.ReadWrite(); - rhs.AssembleDevice(*fes, elem_marker, b); + this->assembleVolumeFractionRHS(*fes, *inout, sampleIR, b); } inout->HostReadWrite(); @@ -1014,6 +1007,55 @@ class SamplingShaper : public Shaper vf->HostReadWrite(); } + bool usesAnisotropicCustomTensorQuadrature(const mfem::Mesh& mesh) const + { + if(m_quadratureType == static_cast(mfem::Quadrature1D::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[1] != 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); + + 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); + } + } + private: shaping::QFunctionCollection m_inoutShapeQFuncs; shaping::QFunctionCollection m_inoutMaterialQFuncs; diff --git a/src/axom/quest/detail/shaping/shaping_helpers.cpp b/src/axom/quest/detail/shaping/shaping_helpers.cpp index 0d44c83247..cbd89431b0 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers.cpp +++ b/src/axom/quest/detail/shaping/shaping_helpers.cpp @@ -240,7 +240,6 @@ void generatePositionsQFunction(mfem::Mesh* mesh, QFunctionCollection& inoutQFun // Assume all elements have the same integration rule const auto& ir = sp->GetElementIntRule(0); const int nq = ir.GetNPoints(); -std::cout << "generatePositionsQFunction: ir.GetNPoints()=" << nq << std::endl; mfem::QuadratureFunction* pos_coef = new mfem::QuadratureFunction(sp, dim); pos_coef->SetOwnsSpace(true); diff --git a/src/axom/quest/tests/quest_sampling_shaper.cpp b/src/axom/quest/tests/quest_sampling_shaper.cpp index 50c4a9ceb4..a10b258b09 100644 --- a/src/axom/quest/tests/quest_sampling_shaper.cpp +++ b/src/axom/quest/tests/quest_sampling_shaper.cpp @@ -2221,6 +2221,56 @@ piece = line(end=start) //----------------------------------------------------------------------------- +TEST_F(SampleTester2D, anisotropic_closeduniform_projection_generates_volume_fractions) +{ + const std::string shape_template = R"( +dimensions: 2 + +shapes: +- name: {} + material: {} + geometry: + format: c2c + path: {} + units: cm +)"; + + const std::string rectangle_contour = R"( +point = start +piece = line(start=(-0.001cm, -0.001cm), end=(-0.001cm, 1.001cm)) +piece = line() +piece = line(start=(1.001cm, 1.001cm), end=(1.001cm, -0.001cm)) +piece = line(end=start) +)"; + + const std::string rect_shape = "rectShape"; + const std::string rect_material = "rectMat"; + const auto& testname = ::testing::UnitTest::GetInstance()->current_test_info()->name(); + + fs::TempFile contour_file(testname, ".contour"); + contour_file.write(rectangle_contour); + + fs::TempFile shape_file(testname, ".yaml"); + shape_file.write(axom::fmt::format(axom::fmt::runtime(shape_template), + rect_shape, + rect_material, + contour_file.getPath())); + + this->validateShapeFile(shape_file.getPath()); + this->initializeShaping(shape_file.getPath()); + + int sampleRes[3] = {3, 5, 1}; + this->m_shaper->setSamplingResolution(sampleRes); + this->m_shaper->setQuadratureType(static_cast(mfem::Quadrature1D::ClosedUniform)); + this->m_shaper->setVolumeFractionOrder(0); + + this->runShaping(); + + this->checkExpectedVolumeFractions(rect_material, 1.0, 1e-12); +} + +//----------------------------------------------------------------------------- + TEST_F(CurvedSampleTester2D, positions_match_curved_mesh_for_anisotropic_custom_quadrature) { auto& mesh = this->getMesh(); From 9fe522700888be6dab6bfe32c024bf2b5db016f9 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 4 May 2026 17:16:31 -0700 Subject: [PATCH 182/986] Updated tutorial --- .../shaping_tutorial/lesson_04/README.md | 20 ++++++++++--------- .../lesson_04/quest_sampling_shaper.cpp | 3 +++ 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/examples/shaping_tutorial/lesson_04/README.md b/src/examples/shaping_tutorial/lesson_04/README.md index 643408db29..a534e51bc4 100644 --- a/src/examples/shaping_tutorial/lesson_04/README.md +++ b/src/examples/shaping_tutorial/lesson_04/README.md @@ -166,11 +166,13 @@ struct MeshMetadata std::string background_material; int volume_fraction_order {2}; int mesh_order {1}; - int quadrature_order {5}; + int sampling_resolution {5}; quest::SamplingShaper::SamplingMethod sampling_method {quest::SamplingShaper::SamplingMethod::InOut}; }; ``` -This allows the user to set the polynomial order of the volume fraction functions via `volume_fraction_order`, the sampling order for quadrature points within each element `quadrature_order`, and the `sampling_method` -- either using an `InOutOctree` over a discretized/linearized representation of the shape, or using an approach based on winding numbers. +This allows the user to set the polynomial order of the volume fraction functions via `volume_fraction_order`, the number of sample points per logical direction within each element via `sampling_resolution`, and the `sampling_method` -- either using an `InOutOctree` over a discretized/linearized representation of the shape, or using an approach based on winding numbers. + +When you want a uniform sample point pattern that spans the full zone, including the element edges, `mfem::Quadrature1D::ClosedUniform` is a useful choice with `SamplingShaper::setQuadratureType()`. Other quadrature families are also available if a different sample point pattern is desired. We also allow the user to specify a "background_material". When specified, a corresponding volume fraction field will be generated and initialized to 1 everywhere. Users can incorporate this into their input with a special "geometry/format" of "none". @@ -191,7 +193,7 @@ The changes to the schema and the MeshMetadata constructor are relatively straig .range(1, std::numeric_limits::max()); mesh_schema.addInt("mesh_order", "Order for mesh nodes (>= 1)") .range(1, std::numeric_limits::max()); - mesh_schema.addInt("quadrature_order", "Order for quadrature (>= 1)") + mesh_schema.addInt("sampling_resolution", "Sampling resolution (>= 1)") .range(1, std::numeric_limits::max()); mesh_schema.addString("sampling_method", "Sampling method ('inout' or 'winding')") @@ -218,9 +220,9 @@ struct FromInlet result.volume_fraction_order = static_cast(input_data["volume_fraction_order"]); } - if(input_data.contains("quadrature_order")) + if(input_data.contains("sampling_resolution")) { - result.quadrature_order = static_cast(input_data["quadrature_order"]); + result.sampling_resolution = static_cast(input_data["sampling_resolution"]); } if(input_data.contains("sampling_method")) @@ -435,7 +437,7 @@ shapes: background_material = "void", volume_fraction_order = 2, mesh_order = 2, - quadrature_order = 5, + sampling_resolution = 5, sampling_method = "inout", } ``` @@ -533,7 +535,7 @@ shapes: background_material = "air", volume_fraction_order = 2, mesh_order = 2, - quadrature_order = 5, + sampling_resolution = 5, sampling_method = "winding", } ``` @@ -608,7 +610,7 @@ This example uses contours stored in MFEM files to approximate the shapes in Pau background_material = "canvas", volume_fraction_order = 2, mesh_order = 2, - quadrature_order = 5, + sampling_resolution = 5, sampling_method = "inout", } ``` @@ -626,7 +628,7 @@ This example uses contours stored in MFEM files to approximate the shapes in Pau ## Wrap up -In this lesson, we covered shaping in Axom, focusing on the `InOutOctree` and `Winding Number` containment tests. We defined mesh metadata (orders, quadrature, sampling method) and created high-order MFEM meshes. We used background materials, replacement rules, and produced matset-aware outputs suitable for MIR in VisIt. We demonstrated the workflow with several examples. Although we didn't focus on it, everything transparently works with MPI, and much of the workflow is GPU-ready (or work is planned to port it). +In this lesson, we covered shaping in Axom, focusing on the `InOutOctree` and `Winding Number` containment tests. We defined mesh metadata (orders, sampling resolution, sampling method) and created high-order MFEM meshes. We used background materials, replacement rules, and produced matset-aware outputs suitable for MIR in VisIt. We demonstrated the workflow with several examples. Although we didn't focus on it, everything transparently works with MPI, and much of the workflow is GPU-ready (or work is planned to port it). ## Technologies Used diff --git a/src/examples/shaping_tutorial/lesson_04/quest_sampling_shaper.cpp b/src/examples/shaping_tutorial/lesson_04/quest_sampling_shaper.cpp index 00c67cbfe4..314339b4d3 100644 --- a/src/examples/shaping_tutorial/lesson_04/quest_sampling_shaper.cpp +++ b/src/examples/shaping_tutorial/lesson_04/quest_sampling_shaper.cpp @@ -402,6 +402,9 @@ int main(int argc, char** argv) &dc); shaper->setVerbosity(verbose); shaper->setSamplingResolution(meta.sampling_resolution); + // This tutorial keeps MFEM's default quadrature family. For uniform sample + // points that cover the whole zone, including its edges, users can also call + // setQuadratureType(static_cast(mfem::Quadrature1D::ClosedUniform)). shaper->setVolumeFractionOrder(meta.volume_fraction_order); shaper->setSamplingMethod(meta.sampling_method); From 583a77a77cd1b8bb03461949abdcf7e6d9c7936f Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 4 May 2026 17:16:47 -0700 Subject: [PATCH 183/986] Added 3D test case --- .../quest/tests/quest_sampling_shaper.cpp | 261 ++++++++++++++++++ 1 file changed, 261 insertions(+) diff --git a/src/axom/quest/tests/quest_sampling_shaper.cpp b/src/axom/quest/tests/quest_sampling_shaper.cpp index a10b258b09..a033ee99a9 100644 --- a/src/axom/quest/tests/quest_sampling_shaper.cpp +++ b/src/axom/quest/tests/quest_sampling_shaper.cpp @@ -36,6 +36,7 @@ #include #include #include +#include namespace klee = axom::klee; namespace primal = axom::primal; @@ -65,6 +66,94 @@ const std::string proe_tet_fmt_str = R"( 1 1 2 3 4 )"; +const std::string oversized_unit_box_stl = R"(solid oversized_unit_box +facet normal 0 0 -1 + outer loop + vertex -0.001 -0.001 -0.001 + vertex 1.001 1.001 -0.001 + vertex 1.001 -0.001 -0.001 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex -0.001 -0.001 -0.001 + vertex -0.001 1.001 -0.001 + vertex 1.001 1.001 -0.001 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex -0.001 -0.001 1.001 + vertex 1.001 -0.001 1.001 + vertex 1.001 1.001 1.001 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex -0.001 -0.001 1.001 + vertex 1.001 1.001 1.001 + vertex -0.001 1.001 1.001 + endloop +endfacet +facet normal 0 -1 0 + outer loop + vertex -0.001 -0.001 -0.001 + vertex 1.001 -0.001 -0.001 + vertex 1.001 -0.001 1.001 + endloop +endfacet +facet normal 0 -1 0 + outer loop + vertex -0.001 -0.001 -0.001 + vertex 1.001 -0.001 1.001 + vertex -0.001 -0.001 1.001 + endloop +endfacet +facet normal 0 1 0 + outer loop + vertex -0.001 1.001 -0.001 + vertex 1.001 1.001 1.001 + vertex 1.001 1.001 -0.001 + endloop +endfacet +facet normal 0 1 0 + outer loop + vertex -0.001 1.001 -0.001 + vertex -0.001 1.001 1.001 + vertex 1.001 1.001 1.001 + endloop +endfacet +facet normal -1 0 0 + outer loop + vertex -0.001 -0.001 -0.001 + vertex -0.001 -0.001 1.001 + vertex -0.001 1.001 1.001 + endloop +endfacet +facet normal -1 0 0 + outer loop + vertex -0.001 -0.001 -0.001 + vertex -0.001 1.001 1.001 + vertex -0.001 1.001 -0.001 + endloop +endfacet +facet normal 1 0 0 + outer loop + vertex 1.001 -0.001 -0.001 + vertex 1.001 1.001 1.001 + vertex 1.001 -0.001 1.001 + endloop +endfacet +facet normal 1 0 0 + outer loop + vertex 1.001 -0.001 -0.001 + vertex 1.001 1.001 -0.001 + vertex 1.001 1.001 1.001 + endloop +endfacet +endsolid oversized_unit_box +)"; + // Set the following to true for verbose output and for saving vis files constexpr bool very_verbose_output = false; @@ -146,6 +235,15 @@ 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)}}; + // Utility function to slice a tetrahedron along a plane primal::Polygon slice(const primal::Tetrahedron& tet, const primal::Plane& plane) @@ -608,6 +706,34 @@ class SampleTester2D : public SamplingShaperTest } }; +/// Test fixture for SamplingShaper tests on a single 3D MFEM hex element +class SampleTester3D : public SamplingShaperTest +{ +public: + using Point3D = primal::Point; + using BBox3D = primal::BoundingBox; + +public: + virtual ~SampleTester3D() { } + + void SetUp() override + { + const int polynomialOrder = 1; + const BBox3D bbox({0, 0, 0}, {1, 1, 1}); + const axom::NumericArray celldims {1, 1, 1}; + + auto* mesh = quest::util::make_cartesian_mfem_mesh_3D(bbox, celldims, polynomialOrder); + + m_dc.SetOwnData(true); + m_dc.SetMeshNodesName("positions"); + m_dc.SetMesh(mesh); + +#ifdef AXOM_USE_MPI + m_dc.SetComm(MPI_COMM_WORLD); +#endif + } +}; + /// Test fixture for SamplingShaper tests on a single curved 2D MFEM element class CurvedSampleTester2D : public SamplingShaperTest { @@ -2271,6 +2397,141 @@ piece = line(end=start) //----------------------------------------------------------------------------- +TEST_F(SampleTester2D, supported_quadrature_types_generate_volume_fractions) +{ + const std::string shape_template = R"( +dimensions: 2 + +shapes: +- name: {} + material: {} + geometry: + format: c2c + path: {} + units: cm +)"; + + const std::string rectangle_contour = R"( +point = start +piece = line(start=(-0.001cm, -0.001cm), end=(-0.001cm, 1.001cm)) +piece = line() +piece = line(start=(1.001cm, 1.001cm), end=(1.001cm, -0.001cm)) +piece = line(end=start) +)"; + + const std::string rect_shape = "rectShape"; + const std::string rect_material = "rectMat"; + const auto& testname = ::testing::UnitTest::GetInstance()->current_test_info()->name(); + + fs::TempFile contour_file(testname, ".contour"); + contour_file.write(rectangle_contour); + + fs::TempFile shape_file(testname, ".yaml"); + shape_file.write(axom::fmt::format(axom::fmt::runtime(shape_template), + rect_shape, + rect_material, + contour_file.getPath())); + + int sampleRes[3] = {3, 5, 1}; + + for(const auto& quadrature : supported_quadrature_types) + { + this->validateShapeFile(shape_file.getPath()); + this->initializeShaping(shape_file.getPath()); + + this->m_shaper->setSamplingResolution(sampleRes); + this->m_shaper->setQuadratureType(quadrature.second); + this->m_shaper->setVolumeFractionOrder(0); + + this->runShaping(); + + this->checkExpectedVolumeFractions(rect_material, 1.0, 1e-12); + + this->resetShaping(); + } +} + +//----------------------------------------------------------------------------- + +TEST_F(SampleTester2D, invalid_quadrature_type_values_abort) +{ + const std::string shape_template = R"( +dimensions: 2 + +shapes: +- name: {} + material: {} + geometry: + format: c2c + path: {} +)"; + + const std::string rect_shape = "rectShape"; + const std::string rect_material = "rectMat"; + const auto& testname = ::testing::UnitTest::GetInstance()->current_test_info()->name(); + + fs::TempFile contour_file(testname, ".contour"); + contour_file.write(unit_circle_contour); + + fs::TempFile shape_file(testname, ".yaml"); + shape_file.write(axom::fmt::format(axom::fmt::runtime(shape_template), + rect_shape, + rect_material, + contour_file.getPath())); + + this->validateShapeFile(shape_file.getPath()); + this->initializeShaping(shape_file.getPath()); + + slic::ScopedAbortToThrow abort_guard; + EXPECT_THROW(m_shaper->setQuadratureType(static_cast(mfem::Quadrature1D::Invalid) - 1), + slic::SlicAbortException); + EXPECT_THROW(m_shaper->setQuadratureType(static_cast(mfem::Quadrature1D::ClosedGL) + 1), + slic::SlicAbortException); +} + +//----------------------------------------------------------------------------- + +TEST_F(SampleTester3D, anisotropic_closeduniform_projection_generates_volume_fractions) +{ + const std::string shape_template = R"( +dimensions: 3 + +shapes: +- name: {} + material: {} + geometry: + format: stl + path: {} +)"; + + const std::string box_shape = "boxShape"; + const std::string box_material = "boxMat"; + const auto& testname = ::testing::UnitTest::GetInstance()->current_test_info()->name(); + + fs::TempFile stl_file(testname, ".stl"); + stl_file.write(oversized_unit_box_stl); + + fs::TempFile shape_file(testname, ".yaml"); + shape_file.write(axom::fmt::format(axom::fmt::runtime(shape_template), + box_shape, + box_material, + stl_file.getPath())); + + this->validateShapeFile(shape_file.getPath()); + this->initializeShaping(shape_file.getPath()); + + int sampleRes[3] = {3, 5, 2}; + this->m_shaper->setSamplingResolution(sampleRes); + this->m_shaper->setQuadratureType(static_cast(mfem::Quadrature1D::ClosedUniform)); + this->m_shaper->setVolumeFractionOrder(0); + + this->runShaping(); + + this->checkExpectedVolumeFractions(box_material, 1.0, 1e-12); +} + +//----------------------------------------------------------------------------- + TEST_F(CurvedSampleTester2D, positions_match_curved_mesh_for_anisotropic_custom_quadrature) { auto& mesh = this->getMesh(); From dfb15141d42d61eef93581001e65bc637734db46 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 4 May 2026 17:17:20 -0700 Subject: [PATCH 184/986] Updated comments to talk about sampling resolution vs quadrature order. --- src/axom/quest/SamplingShaper.hpp | 38 +++++++++++++++++++ .../quest/detail/shaping/PrimitiveSampler.hpp | 5 ++- .../detail/shaping/WindingNumberSampler.hpp | 7 +++- .../quest/detail/shaping/shaping_helpers.hpp | 5 ++- src/axom/quest/examples/shaping_driver.cpp | 8 ---- 5 files changed, 51 insertions(+), 12 deletions(-) diff --git a/src/axom/quest/SamplingShaper.hpp b/src/axom/quest/SamplingShaper.hpp index 71e710cf9f..71ce638062 100644 --- a/src/axom/quest/SamplingShaper.hpp +++ b/src/axom/quest/SamplingShaper.hpp @@ -159,6 +159,21 @@ class SamplingShaper : public Shaper void setSamplingMethod(SamplingMethod samplingMethod) { m_samplingMethod = samplingMethod; } + /*! + * \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]`. + * For uniform point sampling over the full zone, including the element + * edges, `mfem::Quadrature1D::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. + */ void setQuadratureType(int qtype) { if(qtype >= static_cast(mfem::Quadrature1D::Invalid) && @@ -172,6 +187,17 @@ class SamplingShaper : public Shaper } } + /*! + * \brief Sets an isotropic sampling resolution for custom quadrature. + * + * The same positive sample count is used in each logical mesh 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] sampleRes Number of sample points to use per logical + * direction. + */ void setSamplingResolution(int sampleRes) { SLIC_ASSERT(sampleRes > 0); @@ -180,6 +206,18 @@ class SamplingShaper : public Shaper m_sampleResolution[2] = sampleRes; } + /*! + * \brief Sets an anisotropic sampling resolution for custom quadrature. + * + * The entries correspond to the logical `I`, `J`, and `K` directions of the + * reference element. Each entry must be positive. 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] sampleRes Array containing the sample count per logical + * direction. + */ void setSamplingResolution(int sampleRes[3]) { SLIC_ASSERT(sampleRes[0] > 0); diff --git a/src/axom/quest/detail/shaping/PrimitiveSampler.hpp b/src/axom/quest/detail/shaping/PrimitiveSampler.hpp index 0ed36f6ac8..4889cd0760 100644 --- a/src/axom/quest/detail/shaping/PrimitiveSampler.hpp +++ b/src/axom/quest/detail/shaping/PrimitiveSampler.hpp @@ -164,7 +164,10 @@ class PrimitiveSampler * \param [in] dc The data collection 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 quadrature order at which to sample the inout field + * \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] projector A callback function to apply to points from the input mesh * before querying them on the spatial index diff --git a/src/axom/quest/detail/shaping/WindingNumberSampler.hpp b/src/axom/quest/detail/shaping/WindingNumberSampler.hpp index 418bc48562..5a1aa27990 100644 --- a/src/axom/quest/detail/shaping/WindingNumberSampler.hpp +++ b/src/axom/quest/detail/shaping/WindingNumberSampler.hpp @@ -133,8 +133,11 @@ class WindingNumberSampler * \param [in] dc The data collection 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 quadrature order at which to sample the inout field - * \param [in] quadratureType The quadrature type to use to construct the sample point locations. + * \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] projector A callback function to apply to points from the input mesh * before querying them on the spatial index * diff --git a/src/axom/quest/detail/shaping/shaping_helpers.hpp b/src/axom/quest/detail/shaping/shaping_helpers.hpp index a189c3a5b8..57d53d42a0 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers.hpp +++ b/src/axom/quest/detail/shaping/shaping_helpers.hpp @@ -251,7 +251,10 @@ void computeVolumeFractionsIdentity(mfem::DataCollection* dc, * \param [in] dc The data collection 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 quadrature order at which to sample the inout field + * \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 diff --git a/src/axom/quest/examples/shaping_driver.cpp b/src/axom/quest/examples/shaping_driver.cpp index 8d76b17ce0..4c8bee77af 100644 --- a/src/axom/quest/examples/shaping_driver.cpp +++ b/src/axom/quest/examples/shaping_driver.cpp @@ -492,19 +492,11 @@ void finalizeLogger() void save_quadrature_points(mfem::QuadratureFunction *positions) { #ifdef CONDUIT_RELAY_IO_HDF5_ENABLED -std::cout << "save_quadrature_points(mfem::QuadratureFunction *positions)\n"; 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(); - -std::cout << "NE=" << positions->GetSpace()->GetMesh()->GetNE() << std::endl; -std::cout << "positions->Size()=" << positions->Size() << std::endl; -std::cout << "positions->GetVDim()=" << positions->GetVDim() << std::endl; -std::cout << "positions->Capacity()=" << positions->Capacity() << std::endl; -std::cout << "npts=" << npts << std::endl; - 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); From c9bebe53733b868abb0f29f0c96456129b71da4b Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 4 May 2026 17:17:34 -0700 Subject: [PATCH 185/986] Updated RELEASE-NOTES.md --- RELEASE-NOTES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index b060e18ef1..484fcb0e89 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -26,6 +26,7 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ - Primal: Adds `NURBSPatch::isTriviallyTrimmed()` to check if the trimming curves for a patch lie on the patch boundaries - Quest: Adds support for reading mfem files with variable order NURBS curves (requires mfem>4.9). - Quest: Adds OMP support for fast GWN methods for STL/Triangulated STEP input and linearized NURBS Curve input. +- Quest: `SamplingShaper` now supports selecting MFEM quadrature families for custom sample-point generation, including anisotropic per-direction sampling resolution on quadrilateral and hexahedral meshes. ### Removed From 41a52af90224ce9dbd52e32bab13ed7dd5044e2b Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 4 May 2026 17:18:12 -0700 Subject: [PATCH 186/986] make style --- .../quest/detail/shaping/shaping_helpers.hpp | 5 +++- src/axom/quest/examples/shaping_driver.cpp | 26 ++++++++--------- .../quest/tests/quest_sampling_shaper.cpp | 29 ++++++++----------- 3 files changed, 29 insertions(+), 31 deletions(-) diff --git a/src/axom/quest/detail/shaping/shaping_helpers.hpp b/src/axom/quest/detail/shaping/shaping_helpers.hpp index 57d53d42a0..4d4d1f56a2 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers.hpp +++ b/src/axom/quest/detail/shaping/shaping_helpers.hpp @@ -208,7 +208,10 @@ void copyShapeIntoMaterial(const mfem::QuadratureFunction* shapeQFunc, * quadratureType -- the same type per dimension but the sampling * can vary. */ -void generatePositionsQFunction(mfem::Mesh* mesh, QFunctionCollection& inoutQFuncs, int sampleResolution[3], int quadratureType); +void generatePositionsQFunction(mfem::Mesh* mesh, + QFunctionCollection& inoutQFuncs, + int sampleResolution[3], + int quadratureType); /** * Implements flux-corrected transport (FCT) to correct the solution obtained diff --git a/src/axom/quest/examples/shaping_driver.cpp b/src/axom/quest/examples/shaping_driver.cpp index 4c8bee77af..1305da7d4d 100644 --- a/src/axom/quest/examples/shaping_driver.cpp +++ b/src/axom/quest/examples/shaping_driver.cpp @@ -27,11 +27,11 @@ #endif #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 + #ifdef CONDUIT_RELAY_MPI_ENABLED + #include "conduit_relay_mpi_io_blueprint.hpp" + #else + #include "conduit_relay_io_blueprint.hpp" + #endif #endif #include "mfem.hpp" @@ -106,7 +106,7 @@ struct Input ShapingMethod shapingMethod {ShapingMethod::Sampling}; SamplingMethod samplingMethod {SamplingMethod::InOut}; RuntimePolicy policy {RuntimePolicy::seq}; - std::vector samplingResolution{5, 5, 5}; + std::vector samplingResolution {5, 5, 5}; // We set quadratureType to Invalid to select the default method. int quadratureType {static_cast(mfem::Quadrature1D::Invalid)}; int outputOrder {2}; @@ -489,13 +489,13 @@ void finalizeLogger() //------------------------------------------------------------------------------ /// Write the quadrature points as a Blueprint mesh. -void save_quadrature_points(mfem::QuadratureFunction *positions) +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()); + 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"; @@ -515,11 +515,11 @@ void save_quadrature_points(mfem::QuadratureFunction *positions) std::fill(tmp.begin(), tmp.end(), 1); n_mesh["topologies/points/elements/sizes"].set(tmp); -#ifdef CONDUIT_RELAY_MPI_ENABLED + #ifdef CONDUIT_RELAY_MPI_ENABLED conduit::relay::mpi::io::blueprint::save_mesh(n_mesh, "shaping_quadrature", "hdf5", MPI_COMM_WORLD); -#else + #else conduit::relay::io::blueprint::save_mesh(n_mesh, "shaping_quadrature", "hdf5"); -#endif + #endif #endif } @@ -652,7 +652,7 @@ int main(int argc, char** argv) if(auto* samplingShaper = dynamic_cast(shaper)) { int res[3] = {5, 5, 5}; - for(size_t i = 0; i < std::min(size_t{3}, params.samplingResolution.size()); i++) + for(size_t i = 0; i < std::min(size_t {3}, params.samplingResolution.size()); i++) { res[i] = params.samplingResolution[i]; } @@ -822,7 +822,7 @@ int main(int argc, char** argv) shaper->getDC()->Save(); if(auto* samplingShaper = dynamic_cast(shaper)) { - mfem::QuadratureFunction *positions = samplingShaper->getShapeQFunction("positions"); + mfem::QuadratureFunction* positions = samplingShaper->getShapeQFunction("positions"); //if(params.quadratureType != static_cast(mfem::Quadrature1D::Invalid)) if(positions) { diff --git a/src/axom/quest/tests/quest_sampling_shaper.cpp b/src/axom/quest/tests/quest_sampling_shaper.cpp index a033ee99a9..1d4ff21052 100644 --- a/src/axom/quest/tests/quest_sampling_shaper.cpp +++ b/src/axom/quest/tests/quest_sampling_shaper.cpp @@ -2512,10 +2512,8 @@ dimensions: 3 stl_file.write(oversized_unit_box_stl); fs::TempFile shape_file(testname, ".yaml"); - shape_file.write(axom::fmt::format(axom::fmt::runtime(shape_template), - box_shape, - box_material, - stl_file.getPath())); + shape_file.write( + axom::fmt::format(axom::fmt::runtime(shape_template), box_shape, box_material, stl_file.getPath())); this->validateShapeFile(shape_file.getPath()); this->initializeShaping(shape_file.getPath()); @@ -2538,23 +2536,20 @@ TEST_F(CurvedSampleTester2D, positions_match_curved_mesh_for_anisotropic_custom_ auto* nodes = mesh.GetNodes(); ASSERT_NE(nodes, nullptr); - mfem::VectorFunctionCoefficient warp( - 2, - [](const mfem::Vector& x, mfem::Vector& y) { - constexpr double PI_LOCAL = 3.14159265358979323846; - y.SetSize(2); - y[0] = x[0] + 0.08 * std::sin(PI_LOCAL * x[0]) * std::sin(PI_LOCAL * x[1]); - y[1] = x[1] + 0.05 * std::sin(PI_LOCAL * x[0]) * std::sin(0.5 * PI_LOCAL * x[1]); - }); + mfem::VectorFunctionCoefficient warp(2, [](const mfem::Vector& x, mfem::Vector& y) { + constexpr double PI_LOCAL = 3.14159265358979323846; + y.SetSize(2); + y[0] = x[0] + 0.08 * std::sin(PI_LOCAL * x[0]) * std::sin(PI_LOCAL * x[1]); + y[1] = x[1] + 0.05 * std::sin(PI_LOCAL * x[0]) * std::sin(0.5 * PI_LOCAL * x[1]); + }); nodes->ProjectCoefficient(warp); int sampleRes[3] = {5, 3, 1}; quest::shaping::QFunctionCollection qfuncs; - quest::shaping::generatePositionsQFunction( - &mesh, - qfuncs, - sampleRes, - static_cast(mfem::Quadrature1D::OpenUniform)); + quest::shaping::generatePositionsQFunction(&mesh, + qfuncs, + sampleRes, + static_cast(mfem::Quadrature1D::OpenUniform)); auto* positions = qfuncs.Get("positions"); ASSERT_NE(positions, nullptr); From 2c1fbd33c87cb81aa9531e8531afef6daa30863a Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 4 May 2026 17:20:03 -0700 Subject: [PATCH 187/986] make style --- src/axom/quest/SamplingShaper.hpp | 74 +++++++++---------- .../quest/detail/shaping/shaping_helpers.cpp | 15 ++-- 2 files changed, 42 insertions(+), 47 deletions(-) diff --git a/src/axom/quest/SamplingShaper.hpp b/src/axom/quest/SamplingShaper.hpp index 71ce638062..af066b8f73 100644 --- a/src/axom/quest/SamplingShaper.hpp +++ b/src/axom/quest/SamplingShaper.hpp @@ -229,10 +229,7 @@ class SamplingShaper : public Shaper } // Deprecated backward compatibility method - [[deprecated]] void setQuadratureOrder(int order) - { - setSamplingResolution(order); - } + [[deprecated]] void setQuadratureOrder(int order) { setSamplingResolution(order); } void setVolumeFractionOrder(int volfracOrder) { m_volfracOrder = volfracOrder; } @@ -592,7 +589,10 @@ class SamplingShaper : public Shaper // ensure we have a starting quadrature field for the positions if(!m_inoutShapeQFuncs.Has("positions")) { - shaping::generatePositionsQFunction(mesh, m_inoutShapeQFuncs, m_sampleResolution, m_quadratureType); + shaping::generatePositionsQFunction(mesh, + m_inoutShapeQFuncs, + m_sampleResolution, + m_quadratureType); } auto* positionsQSpace = m_inoutShapeQFuncs.Get("positions")->GetSpace(); @@ -707,36 +707,36 @@ class SamplingShaper : public Shaper if(meshDim == 2) { sampler->template sampleInOutField<2, 2>(m_dc, - m_inoutShapeQFuncs, - m_sampleResolution, - m_quadratureType, - m_projector22); + m_inoutShapeQFuncs, + m_sampleResolution, + m_quadratureType, + m_projector22); } else if(meshDim == 3) { sampler->template sampleInOutField<3, 2>(m_dc, - m_inoutShapeQFuncs, - m_sampleResolution, - m_quadratureType, - m_projector32); + m_inoutShapeQFuncs, + m_sampleResolution, + m_quadratureType, + m_projector32); } break; case 3: if(meshDim == 2) { sampler->template sampleInOutField<2, 3>(m_dc, - m_inoutShapeQFuncs, - m_sampleResolution, - m_quadratureType, - m_projector23); + m_inoutShapeQFuncs, + m_sampleResolution, + m_quadratureType, + m_projector23); } else if(meshDim == 3) { sampler->template sampleInOutField<3, 3>(m_dc, - m_inoutShapeQFuncs, - m_sampleResolution, - m_quadratureType, - m_projector33); + m_inoutShapeQFuncs, + m_sampleResolution, + m_quadratureType, + m_projector33); } break; } @@ -747,29 +747,21 @@ 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>(m_dc, m_volfracOrder, m_projector22); } else if(meshDim == 3) { - sampler->template computeVolumeFractionsBaseline<3, 2>(m_dc, - m_volfracOrder, - m_projector32); + sampler->template computeVolumeFractionsBaseline<3, 2>(m_dc, 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>(m_dc, m_volfracOrder, m_projector23); } else if(meshDim == 3) { - sampler->template computeVolumeFractionsBaseline<3, 3>(m_dc, - m_volfracOrder, - m_projector33); + sampler->template computeVolumeFractionsBaseline<3, 3>(m_dc, m_volfracOrder, m_projector33); } break; } @@ -809,18 +801,18 @@ class SamplingShaper : public Shaper if(meshDim == 2) { sampler->template sampleInOutField<2, 3>(m_dc, - m_inoutShapeQFuncs, - m_sampleResolution, - m_quadratureType, - m_projector23); + m_inoutShapeQFuncs, + m_sampleResolution, + m_quadratureType, + m_projector23); } else if(meshDim == 3) { sampler->template sampleInOutField<3, 3>(m_dc, - m_inoutShapeQFuncs, - m_sampleResolution, - m_quadratureType, - m_projector33); + m_inoutShapeQFuncs, + m_sampleResolution, + m_quadratureType, + m_projector33); } break; } diff --git a/src/axom/quest/detail/shaping/shaping_helpers.cpp b/src/axom/quest/detail/shaping/shaping_helpers.cpp index cbd89431b0..9c769ed9e0 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers.cpp +++ b/src/axom/quest/detail/shaping/shaping_helpers.cpp @@ -135,7 +135,7 @@ void copyShapeIntoMaterial(const mfem::QuadratureFunction* shapeQFunc, } } -mfem::QuadratureSpace *makeDefaultQuadratureSpace(mfem::Mesh* mesh, int sampleRes) +mfem::QuadratureSpace* makeDefaultQuadratureSpace(mfem::Mesh* mesh, int sampleRes) { SLIC_ASSERT(mesh != nullptr); const int NE = mesh->GetNE(); @@ -153,7 +153,7 @@ 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], int quadratureType) { SLIC_ASSERT(mesh != nullptr); const int NE = mesh->GetNE(); @@ -169,7 +169,8 @@ mfem::QuadratureSpace *makeCustomQuadratureSpace(mfem::Mesh* mesh, int sampleRes mfem::IntegrationRule *ir = nullptr, 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)); + SLIC_ERROR_IF(sampleRes[d] < 1, + axom::fmt::format("Invalid sample value {} for dimension {}.", sampleRes[d], d)); switch(quadratureType) { case mfem::Quadrature1D::GaussLegendre: @@ -212,8 +213,10 @@ mfem::QuadratureSpace *makeCustomQuadratureSpace(mfem::Mesh* mesh, int sampleRes } /// Generates a quadrature function corresponding to the mesh "positions" field -void generatePositionsQFunction(mfem::Mesh* mesh, QFunctionCollection& inoutQFuncs, - int sampleResolution[3], int quadratureType) +void generatePositionsQFunction(mfem::Mesh* mesh, + QFunctionCollection& inoutQFuncs, + int sampleResolution[3], + int quadratureType) { SLIC_ASSERT(mesh != nullptr); const int NE = mesh->GetNE(); @@ -226,7 +229,7 @@ void generatePositionsQFunction(mfem::Mesh* mesh, QFunctionCollection& inoutQFun } // Make a quadrature space to determine the point locations in each element. - mfem::QuadratureSpace *sp = nullptr; + mfem::QuadratureSpace* sp = nullptr; if(quadratureType == static_cast(mfem::Quadrature1D::Invalid)) { sp = makeDefaultQuadratureSpace(mesh, sampleResolution[0]); From d70337222c798a41373ae6f45afcefe140e0ab34 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 4 May 2026 18:11:18 -0700 Subject: [PATCH 188/986] Changed some SLIC_ASSERT_MSG to SLIC_ERROR_IF so the tests trigger in Release builds. I also added a new test for background material. --- src/axom/quest/SamplingShaper.hpp | 32 ++++++++++++----- src/axom/quest/examples/shaping_driver.cpp | 6 ++-- .../quest/tests/quest_sampling_shaper.cpp | 35 +++++++++++++++++++ 3 files changed, 62 insertions(+), 11 deletions(-) diff --git a/src/axom/quest/SamplingShaper.hpp b/src/axom/quest/SamplingShaper.hpp index af066b8f73..ac7092d411 100644 --- a/src/axom/quest/SamplingShaper.hpp +++ b/src/axom/quest/SamplingShaper.hpp @@ -487,16 +487,26 @@ class SamplingShaper : public Shaper // Get inout qfunc for this shape shapeQFunc = m_inoutShapeQFuncs.Get(axom::fmt::format("inout_{}", shapeName)); - SLIC_ASSERT_MSG(shapeQFunc != nullptr, - axom::fmt::format("Missing inout samples for shape '{}'", 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 = m_inoutMaterialQFuncs.Get(axom::fmt::format("mat_inout_{}", thisMatName)); - SLIC_ASSERT_MSG(shapeQFunc != nullptr, - axom::fmt::format("Missing inout samples for material '{}'", 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 @@ -522,8 +532,11 @@ class SamplingShaper : public Shaper auto* otherMatQFunc = m_inoutMaterialQFuncs.Get(axom::fmt::format("mat_inout_{}", otherMatName)); - SLIC_ASSERT_MSG(otherMatQFunc != nullptr, - axom::fmt::format("Missing inout samples for material '{}'", 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); } @@ -539,8 +552,11 @@ class SamplingShaper : public Shaper { // copy shape data into current material and delete the copy auto* matQFunc = m_inoutMaterialQFuncs.Get(materialQFuncName); - SLIC_ASSERT_MSG(matQFunc != nullptr, - axom::fmt::format("Missing inout samples for material '{}'", thisMatName)); + 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); diff --git a/src/axom/quest/examples/shaping_driver.cpp b/src/axom/quest/examples/shaping_driver.cpp index 1305da7d4d..991dc59908 100644 --- a/src/axom/quest/examples/shaping_driver.cpp +++ b/src/axom/quest/examples/shaping_driver.cpp @@ -316,7 +316,6 @@ struct Input "Sampling resolution per element for the inout field (x,y,[z]). \n" "Determines number of samples per element in determining volume fraction field") ->expected(2, 3) - ///->capture_default_str(); ->check(axom::CLI::PositiveNumber); std::map vfsamplingMap { @@ -820,11 +819,12 @@ int main(int argc, char** argv) { 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)) { mfem::QuadratureFunction* positions = samplingShaper->getShapeQFunction("positions"); - //if(params.quadratureType != static_cast(mfem::Quadrature1D::Invalid)) - if(positions) + if(positions && params.isVerbose()) { save_quadrature_points(positions); } diff --git a/src/axom/quest/tests/quest_sampling_shaper.cpp b/src/axom/quest/tests/quest_sampling_shaper.cpp index 757d47aa75..d737fccbf0 100644 --- a/src/axom/quest/tests/quest_sampling_shaper.cpp +++ b/src/axom/quest/tests/quest_sampling_shaper.cpp @@ -49,6 +49,7 @@ namespace { using Point2D = primal::Point; using Point3D = primal::Point; +const char IGNORE_OUTPUT[] = ".*"; const std::string unit_circle_contour = "piece = circle(origin=(0cm, 0cm), radius=1cm, start=0deg, end=360deg)"; @@ -1086,6 +1087,40 @@ units: cm } } +TEST_F(SamplingShaperTest2D, replacement_background_without_initial_material_aborts) +{ + const auto& testname = ::testing::UnitTest::GetInstance()->current_test_info()->name(); + + const std::string shape_template = R"( +dimensions: 2 +units: cm + +shapes: +- name: background + material: {1} + geometry: + format: none +- name: circle_outer + material: {2} + geometry: + format: c2c + path: {0} + units: cm +)"; + + fs::TempFile contour_file(testname, ".contour"); + contour_file.write(unit_circle_contour); + + fs::TempFile shape_file(testname, ".yaml"); + shape_file.write( + axom::fmt::format(axom::fmt::runtime(shape_template), contour_file.getPath(), "void", "disk")); + + this->validateShapeFile(shape_file.getPath()); + this->initializeShaping(shape_file.getPath()); + + EXPECT_DEATH_IF_SUPPORTED(this->runShaping(), IGNORE_OUTPUT); +} + TEST_F(SamplingShaperTest2D, preshaped_materials) { const std::string& testname = ::testing::UnitTest::GetInstance()->current_test_info()->name(); From dba153c2b8404e587461dae78abbf65768fb6493 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 4 May 2026 18:29:12 -0700 Subject: [PATCH 189/986] Fixes for importing background materials. --- src/axom/quest/SamplingShaper.hpp | 33 ++++++++++--- .../quest/detail/shaping/shaping_helpers.cpp | 40 ++++++++++++--- .../quest/tests/quest_sampling_shaper.cpp | 49 +++++++++++++++++++ 3 files changed, 107 insertions(+), 15 deletions(-) diff --git a/src/axom/quest/SamplingShaper.hpp b/src/axom/quest/SamplingShaper.hpp index ac7092d411..2215cc7b21 100644 --- a/src/axom/quest/SamplingShaper.hpp +++ b/src/axom/quest/SamplingShaper.hpp @@ -630,8 +630,29 @@ class SamplingShaper : public Shaper auto* matQFunc = new mfem::QuadratureFunction(*positionsQSpace); const auto& ir = matQFunc->GetSpace()->GetIntRule(0); - const auto* interp = gf->FESpace()->GetQuadratureInterpolator(ir); - interp->Values(*gf, *matQFunc); + + if(usesCustomTensorQuadrature(*mesh)) + { + // Avoid MFEM's tensor quadrature interpolation path for custom quad/hex + // rules, which infers a single q1d from ir.GetNPoints(). + 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); m_inoutMaterialQFuncs.Register(matName, matQFunc, true); @@ -1053,7 +1074,7 @@ class SamplingShaper : public Shaper vf->HostReadWrite(); } - bool usesAnisotropicCustomTensorQuadrature(const mfem::Mesh& mesh) const + bool usesCustomTensorQuadrature(const mfem::Mesh& mesh) const { if(m_quadratureType == static_cast(mfem::Quadrature1D::Invalid)) { @@ -1063,10 +1084,8 @@ class SamplingShaper : public Shaper 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[1] != m_sampleResolution[2]; + return true; default: return false; } @@ -1080,7 +1099,7 @@ class SamplingShaper : public Shaper mfem::QuadratureFunctionCoefficient qfc(inout); mfem::DomainLFIntegrator rhs(qfc, &sampleIR); - if(usesAnisotropicCustomTensorQuadrature(*fes.GetMesh())) + if(usesCustomTensorQuadrature(*fes.GetMesh())) { mfem::Vector elemVec; mfem::Array elemVDofs; diff --git a/src/axom/quest/detail/shaping/shaping_helpers.cpp b/src/axom/quest/detail/shaping/shaping_helpers.cpp index 9c769ed9e0..67e6dac03f 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers.cpp +++ b/src/axom/quest/detail/shaping/shaping_helpers.cpp @@ -306,9 +306,8 @@ void FCT_correct(const double* M, // Mass matrix // [IN] - M, s, m, y_min, y_max // [INOUT] - xy - constexpr int ND = 64; - using StackArray = axom::StackArray; - SLIC_ASSERT(s <= ND); + constexpr int STACK_CAPACITY = 64; + using StackArray = axom::StackArray; // Q0 solutions can't be adjusted conservatively. It is what it is. if(s == 1) @@ -316,8 +315,35 @@ void FCT_correct(const double* M, // Mass matrix 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); - StackArray ML; for(int r = 0; r < s; ++r) { double dot = 0.; @@ -345,8 +371,6 @@ void FCT_correct(const double* M, // Mass matrix axom::fmt::format("Average ({}) is out of bounds [{},{}]: ", y_avg, y_min - EPS, y_max + EPS)); #endif - StackArray z; - StackArray beta; double sum_beta = 0.; for(int i = 0; i < s; ++i) { @@ -377,8 +401,8 @@ void FCT_correct(const double* M, // Mass matrix // 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; + auto* gp = z; + auto* gm = beta; for(int t = 0; t < s; ++t) { gp[t] = 0.0; diff --git a/src/axom/quest/tests/quest_sampling_shaper.cpp b/src/axom/quest/tests/quest_sampling_shaper.cpp index d737fccbf0..8b43ac28d8 100644 --- a/src/axom/quest/tests/quest_sampling_shaper.cpp +++ b/src/axom/quest/tests/quest_sampling_shaper.cpp @@ -2531,6 +2531,55 @@ dimensions: 3 //----------------------------------------------------------------------------- +TEST_F(SampleTester3D, background_import_with_custom_openuniform_generates_volume_fractions) +{ + const std::string shape_template = R"( +dimensions: 3 + +shapes: +- name: background + material: void + geometry: + format: none +- name: {} + material: {} + geometry: + format: stl + path: {} +)"; + + const std::string box_shape = "boxShape"; + const std::string box_material = "boxMat"; + const auto& testname = ::testing::UnitTest::GetInstance()->current_test_info()->name(); + + fs::TempFile stl_file(testname, ".stl"); + stl_file.write(oversized_unit_box_stl); + + fs::TempFile shape_file(testname, ".yaml"); + shape_file.write( + axom::fmt::format(axom::fmt::runtime(shape_template), box_shape, box_material, stl_file.getPath())); + + std::map initialGridFunctions; + auto* vf = this->registerVolFracGridFunction("init_vf_bg", 0); + this->initializeVolFracGridFunction<3>(vf, [](int, const Point3D&, int) -> double { return 1.; }); + initialGridFunctions["void"] = vf; + + this->validateShapeFile(shape_file.getPath()); + this->initializeShaping(shape_file.getPath(), initialGridFunctions); + + int sampleRes[3] = {3, 4, 5}; + this->m_shaper->setSamplingResolution(sampleRes); + this->m_shaper->setQuadratureType(static_cast(mfem::Quadrature1D::OpenUniform)); + this->m_shaper->setVolumeFractionOrder(4); + + this->runShaping(); + + this->checkExpectedVolumeFractions(box_material, 1.0, 1e-12); + this->checkExpectedVolumeFractions("void", 0.0, 1e-12); +} + +//----------------------------------------------------------------------------- + TEST_F(CurvedSampleTester2D, positions_match_curved_mesh_for_anisotropic_custom_quadrature) { auto& mesh = this->getMesh(); From 17582408ff424c968b19dd1d8a490971746b424b Mon Sep 17 00:00:00 2001 From: Brian Han Date: Tue, 5 May 2026 08:24:55 -0700 Subject: [PATCH 190/986] Update GHA files to only use commit hashes --- .github/workflows/ci-tests.yml | 10 +++++----- .github/workflows/docker_build_tpls.yml | 10 +++++----- .github/workflows/test_windows_tpls.yml | 8 ++++---- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index d671f2cd8f..b437c88227 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -94,7 +94,7 @@ jobs: options: --user root steps: - name: Checkout Axom - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: submodules: recursive - name: Print Matrix Variables @@ -113,7 +113,7 @@ jobs: BUILD_TYPE=${{ matrix.build_type }} \ ./scripts/github-actions/linux-build_and_test.sh - name: Upload Test Results - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 with: name: Test Results ${{ matrix.build_type }} - ${{ matrix.config.job_name }} path: "**/Test.xml" @@ -126,7 +126,7 @@ jobs: CMAKE_EXTRA_FLAGS: '-DAXOM_ENABLE_SIDRE:BOOL=OFF -DAXOM_ENABLE_INLET:BOOL=OFF -DAXOM_ENABLE_KLEE:BOOL=OFF -DAXOM_ENABLE_SINA:BOOL=OFF -DAXOM_ENABLE_MIR:BOOL=OFF -DAXOM_ENABLE_BUMP:BOOL=OFF' steps: - name: Checkout Axom - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: submodules: recursive - name: Windows - CMake @@ -151,7 +151,7 @@ jobs: CMAKE_EXTRA_FLAGS: '-DAXOM_ENABLE_SIDRE:BOOL=OFF -DAXOM_ENABLE_INLET:BOOL=OFF -DAXOM_ENABLE_KLEE:BOOL=OFF -DAXOM_ENABLE_SINA:BOOL=OFF -DAXOM_ENABLE_MIR:BOOL=OFF -DAXOM_ENABLE_BUMP:BOOL=OFF' steps: - name: Checkout Axom - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: submodules: recursive - name: macOS - CMake @@ -187,7 +187,7 @@ jobs: HOST_CONFIG: llvm@19.0.0.cmake steps: - name: Checkout Axom - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: submodules: recursive - name: Check ${{ matrix.check_type }} diff --git a/.github/workflows/docker_build_tpls.yml b/.github/workflows/docker_build_tpls.yml index 54996211b0..c8e6006911 100644 --- a/.github/workflows/docker_build_tpls.yml +++ b/.github/workflows/docker_build_tpls.yml @@ -37,20 +37,20 @@ jobs: id: repo_name - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd #v4 - name: Login to DockerHub - uses: docker/login-action@v3 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push id: docker_build - uses: docker/build-push-action@v5 + uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f #v7 with: push: true tags: ${{ steps.repo_name.outputs.repo_plus_tag }},${{ steps.repo_name.outputs.repo_plus_latest }} @@ -69,7 +69,7 @@ jobs: docker rm extract_hc - name: Upload hostconfig - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 with: name: ${{ matrix.compiler }}_hostconfigs path: ./extracted_hc/export_hostconfig/* diff --git a/.github/workflows/test_windows_tpls.yml b/.github/workflows/test_windows_tpls.yml index 4ac42495d9..b4ad0c2d41 100644 --- a/.github/workflows/test_windows_tpls.yml +++ b/.github/workflows/test_windows_tpls.yml @@ -29,12 +29,12 @@ jobs: steps: - name: Checkout repo w/ submodules - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: submodules: recursive - name: Set up python - uses: actions/setup-python@v5 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 with: python-version: '3.10' @@ -43,7 +43,7 @@ jobs: - name: Run uberenv (${{ matrix.triplet }}) run: python3 ./scripts/uberenv/uberenv.py --triplet ${{ matrix.triplet }} - name: Save Uberenv logs - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 if: ${{ always() }} with: name: uberenv_artifacts_${{ matrix.triplet }}_${{ matrix.cfg }}.zip @@ -73,7 +73,7 @@ jobs: ls ctest -C ${{ matrix.cfg }} --no-compress-output -T Test - name: Save CTest logs - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 if: ${{ always() }} with: name: ctest_artifacts_${{ matrix.triplet }}_${{ matrix.cfg }}.zip From f8a0fdeeb7c9a92d993ad8d091aee20608014a11 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Tue, 5 May 2026 08:32:32 -0700 Subject: [PATCH 191/986] spacing --- .github/workflows/docker_build_tpls.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker_build_tpls.yml b/.github/workflows/docker_build_tpls.yml index c8e6006911..05f7bd108c 100644 --- a/.github/workflows/docker_build_tpls.yml +++ b/.github/workflows/docker_build_tpls.yml @@ -40,7 +40,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd #v4 + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4 - name: Login to DockerHub uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4 @@ -50,7 +50,7 @@ jobs: - name: Build and push id: docker_build - uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f #v7 + uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7 with: push: true tags: ${{ steps.repo_name.outputs.repo_plus_tag }},${{ steps.repo_name.outputs.repo_plus_latest }} From 229fbbdcb99e00dbfb07955be420e0cc1d6742ec Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 5 May 2026 11:09:11 -0700 Subject: [PATCH 192/986] Clean up some memory issues. --- src/axom/quest/SamplingShaper.hpp | 42 ++++++++++++++----- .../quest/detail/shaping/shaping_helpers.cpp | 30 ++++++++++--- .../quest/tests/quest_sampling_shaper.cpp | 2 + 3 files changed, 59 insertions(+), 15 deletions(-) diff --git a/src/axom/quest/SamplingShaper.hpp b/src/axom/quest/SamplingShaper.hpp index 2215cc7b21..64a1ea5f6f 100644 --- a/src/axom/quest/SamplingShaper.hpp +++ b/src/axom/quest/SamplingShaper.hpp @@ -937,17 +937,39 @@ class SamplingShaper : public Shaper (*mass_mat) = 0.; mass_mat->ReadWrite(); - const int sz = mass_mat->TotalSize(); mfem::ConstantCoefficient one_coef(1.0); - mfem::MassIntegrator mass_integrator(one_coef); - - // 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()); + mfem::MassIntegrator mass_integrator(one_coef, &sampleIR); + + if(usesCustomTensorQuadrature(*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 + { + 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()); + } m_inoutTensors.Register(mass_matrix_name, mass_mat, true); } diff --git a/src/axom/quest/detail/shaping/shaping_helpers.cpp b/src/axom/quest/detail/shaping/shaping_helpers.cpp index 67e6dac03f..5184b8584d 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers.cpp +++ b/src/axom/quest/detail/shaping/shaping_helpers.cpp @@ -13,6 +13,8 @@ #include "axom/fmt.hpp" +#include + #if defined(AXOM_USE_MFEM) #include "mfem/linalg/dtensor.hpp" #endif @@ -25,6 +27,23 @@ namespace shaping { #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 + // 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, @@ -166,7 +185,7 @@ mfem::QuadratureSpace* makeCustomQuadratureSpace(mfem::Mesh* mesh, int sampleRes } // Make custom integration rule - mfem::IntegrationRule *ir = nullptr, ird[3]; + mfem::IntegrationRule ird[3]; for(int d = 0; d < dim; d++) { SLIC_ERROR_IF(sampleRes[d] < 1, @@ -196,20 +215,21 @@ mfem::QuadratureSpace* makeCustomQuadratureSpace(mfem::Mesh* mesh, int sampleRes break; } } + std::unique_ptr ir; if(dim == 1) { - ir = new mfem::IntegrationRule(ird[0]); + ir = std::make_unique(ird[0]); } else if(dim == 2) { - ir = new mfem::IntegrationRule(ird[0], ird[1]); + ir = std::make_unique(ird[0], ird[1]); } else if(dim == 3) { - ir = new mfem::IntegrationRule(ird[0], ird[1], ird[2]); + ir = std::make_unique(ird[0], ird[1], ird[2]); } - return new mfem::QuadratureSpace(*mesh, *ir); + return new OwnedQuadratureSpace(*mesh, std::move(ir)); } /// Generates a quadrature function corresponding to the mesh "positions" field diff --git a/src/axom/quest/tests/quest_sampling_shaper.cpp b/src/axom/quest/tests/quest_sampling_shaper.cpp index 8b43ac28d8..e49bc131f8 100644 --- a/src/axom/quest/tests/quest_sampling_shaper.cpp +++ b/src/axom/quest/tests/quest_sampling_shaper.cpp @@ -2629,6 +2629,8 @@ TEST_F(CurvedSampleTester2D, positions_match_curved_mesh_for_anisotropic_custom_ } } } + + qfuncs.DeleteData(true); } //----------------------------------------------------------------------------- From 245c5b5e2e96814cca6ced82ae6783307395d59c Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 5 May 2026 11:13:45 -0700 Subject: [PATCH 193/986] Narrow mfem workarounds to custom anisotropic path. --- src/axom/quest/SamplingShaper.hpp | 18 +++++++------ .../quest/detail/shaping/shaping_helpers.cpp | 25 +++++++++++++++++-- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/src/axom/quest/SamplingShaper.hpp b/src/axom/quest/SamplingShaper.hpp index 64a1ea5f6f..4a7dbfd5db 100644 --- a/src/axom/quest/SamplingShaper.hpp +++ b/src/axom/quest/SamplingShaper.hpp @@ -631,10 +631,12 @@ class SamplingShaper : public Shaper auto* matQFunc = new mfem::QuadratureFunction(*positionsQSpace); const auto& ir = matQFunc->GetSpace()->GetIntRule(0); - if(usesCustomTensorQuadrature(*mesh)) + if(usesAnisotropicCustomTensorQuadrature(*mesh)) { - // Avoid MFEM's tensor quadrature interpolation path for custom quad/hex - // rules, which infers a single q1d from ir.GetNPoints(). + // 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) @@ -940,7 +942,7 @@ class SamplingShaper : public Shaper mfem::ConstantCoefficient one_coef(1.0); mfem::MassIntegrator mass_integrator(one_coef, &sampleIR); - if(usesCustomTensorQuadrature(*fes->GetMesh())) + if(usesAnisotropicCustomTensorQuadrature(*fes->GetMesh())) { mfem::DenseMatrix elemMat; mass_mat->HostWrite(); @@ -1096,7 +1098,7 @@ class SamplingShaper : public Shaper vf->HostReadWrite(); } - bool usesCustomTensorQuadrature(const mfem::Mesh& mesh) const + bool usesAnisotropicCustomTensorQuadrature(const mfem::Mesh& mesh) const { if(m_quadratureType == static_cast(mfem::Quadrature1D::Invalid)) { @@ -1106,8 +1108,10 @@ class SamplingShaper : public Shaper switch(mesh.GetTypicalElementGeometry()) { case mfem::Geometry::SQUARE: + return m_sampleResolution[0] != m_sampleResolution[1]; case mfem::Geometry::CUBE: - return true; + return m_sampleResolution[0] != m_sampleResolution[1] || + m_sampleResolution[0] != m_sampleResolution[2]; default: return false; } @@ -1121,7 +1125,7 @@ class SamplingShaper : public Shaper mfem::QuadratureFunctionCoefficient qfc(inout); mfem::DomainLFIntegrator rhs(qfc, &sampleIR); - if(usesCustomTensorQuadrature(*fes.GetMesh())) + if(usesAnisotropicCustomTensorQuadrature(*fes.GetMesh())) { mfem::Vector elemVec; mfem::Array elemVDofs; diff --git a/src/axom/quest/detail/shaping/shaping_helpers.cpp b/src/axom/quest/detail/shaping/shaping_helpers.cpp index 5184b8584d..037d0aee9c 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers.cpp +++ b/src/axom/quest/detail/shaping/shaping_helpers.cpp @@ -42,6 +42,27 @@ class OwnedQuadratureSpace : public mfem::QuadratureSpace std::unique_ptr m_ir; }; +bool usesAnisotropicCustomTensorQuadrature(const mfem::Mesh& mesh, + const int sampleResolution[3], + int quadratureType) +{ + if(quadratureType == static_cast(mfem::Quadrature1D::Invalid)) + { + return false; + } + + 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; + } +} + } // namespace // Utility function to either return a gf from the dc, or to allocate it through the dc @@ -268,7 +289,7 @@ void generatePositionsQFunction(mfem::Mesh* mesh, pos_coef->SetOwnsSpace(true); auto pos = mfem::Reshape(pos_coef->HostWrite(), dim, nq, NE); - if(quadratureType == static_cast(mfem::Quadrature1D::Invalid)) + if(!usesAnisotropicCustomTensorQuadrature(*mesh, sampleResolution, quadratureType)) { const auto* geomFactors = mesh->GetGeometricFactors(ir, mfem::GeometricFactors::COORDINATES); geomFactors->X.HostRead(); @@ -293,7 +314,7 @@ void generatePositionsQFunction(mfem::Mesh* mesh, else { // MFEM's tensor quadrature interpolation assumes the same number of - // points in each logical dimension. For custom anisotropic tensor-product + // 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) From 5e608119924a35ed3bf22c9bceb7507833e62197 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 5 May 2026 11:45:04 -0700 Subject: [PATCH 194/986] make style --- src/axom/quest/detail/shaping/shaping_helpers.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/axom/quest/detail/shaping/shaping_helpers.cpp b/src/axom/quest/detail/shaping/shaping_helpers.cpp index 037d0aee9c..7dbe7fe0a4 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers.cpp +++ b/src/axom/quest/detail/shaping/shaping_helpers.cpp @@ -56,8 +56,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; } From e8f791d53be1f01d54370b824b7654e155689c7b Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 5 May 2026 15:41:48 -0700 Subject: [PATCH 195/986] Adjust CI test --- src/axom/quest/tests/quest_sampling_shaper.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/axom/quest/tests/quest_sampling_shaper.cpp b/src/axom/quest/tests/quest_sampling_shaper.cpp index e49bc131f8..eedeb143e3 100644 --- a/src/axom/quest/tests/quest_sampling_shaper.cpp +++ b/src/axom/quest/tests/quest_sampling_shaper.cpp @@ -49,7 +49,6 @@ namespace { using Point2D = primal::Point; using Point3D = primal::Point; -const char IGNORE_OUTPUT[] = ".*"; const std::string unit_circle_contour = "piece = circle(origin=(0cm, 0cm), radius=1cm, start=0deg, end=360deg)"; @@ -1118,7 +1117,8 @@ units: cm this->validateShapeFile(shape_file.getPath()); this->initializeShaping(shape_file.getPath()); - EXPECT_DEATH_IF_SUPPORTED(this->runShaping(), IGNORE_OUTPUT); + slic::ScopedAbortToThrow abort_guard; + EXPECT_THROW(this->runShaping(), slic::SlicAbortException); } TEST_F(SamplingShaperTest2D, preshaped_materials) From 0157f35183cc808233821cbe52c683cf729c106f Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 6 May 2026 11:46:01 -0700 Subject: [PATCH 196/986] 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 197/986] 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 198/986] 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 199/986] 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 3eed386922969f494824ba66fd4efb3cfa5c686c Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 6 May 2026 17:43:48 -0700 Subject: [PATCH 200/986] test: do not run -j in testing. --- scripts/github-actions/linux-build_and_test.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/github-actions/linux-build_and_test.sh b/scripts/github-actions/linux-build_and_test.sh index a7a780ffe6..ba885420c7 100755 --- a/scripts/github-actions/linux-build_and_test.sh +++ b/scripts/github-actions/linux-build_and_test.sh @@ -45,7 +45,8 @@ if [[ "$DO_BUILD" == "yes" ]] ; then fi echo "~~~~~~ RUNNING TESTS ~~~~~~~~" - make CTEST_OUTPUT_ON_FAILURE=1 test ARGS='-T Test --output-on-failure -j$NUM_BUILD_PROCS' + #make CTEST_OUTPUT_ON_FAILURE=1 test ARGS='-T Test --output-on-failure -j$NUM_BUILD_PROCS' + make CTEST_OUTPUT_ON_FAILURE=1 test ARGS='-T Test --output-on-failure' if [[ "${DO_BENCHMARKS}" == "yes" ]] ; then echo "~~~~~~ RUNNING BENCHMARKS ~~~~~~~~" From 69e930e0fa068324d466e7fc080a5f7a0c7c3d00 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 6 May 2026 18:33:47 -0700 Subject: [PATCH 201/986] 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 202/986] 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 203/986] 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 204/986] 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 205/986] 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 206/986] 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 207/986] 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 1accac629491badb6a654df18d19bb1eebd0a82e Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Fri, 8 May 2026 16:14:56 -0700 Subject: [PATCH 208/986] Refactor some matsetview dispatch functions to begin support for material-dependent fields --- src/axom/bump/views/dispatch_material.hpp | 236 +++++++++++++----- .../bump/views/dispatch_material_field.hpp | 130 ++++++++++ 2 files changed, 309 insertions(+), 57 deletions(-) create mode 100644 src/axom/bump/views/dispatch_material_field.hpp diff --git a/src/axom/bump/views/dispatch_material.hpp b/src/axom/bump/views/dispatch_material.hpp index 8db64e49f9..f30da3b3bc 100644 --- a/src/axom/bump/views/dispatch_material.hpp +++ b/src/axom/bump/views/dispatch_material.hpp @@ -19,46 +19,29 @@ namespace bump { namespace views { - -/*! - * \brief Make a unibuffer matset view from a Conduit node. - */ -template -struct make_unibuffer_matset +namespace detail { - using MatsetView = UnibufferMaterialView; - /*! - * \brief Wrap the Conduit node as a unibuffer matset view. - * - * \param n_matset The Conduit node that contains the matset. - * - * \return A UnibufferMaterialView. - */ - static MatsetView view(const conduit::Node &n_matset) - { - namespace utils = axom::bump::utilities; - verify(n_matset, "matset"); - MatsetView m; - m.set(utils::make_array_view(n_matset["material_ids"]), - utils::make_array_view(n_matset["volume_fractions"]), - utils::make_array_view(n_matset["sizes"]), - utils::make_array_view(n_matset["offsets"]), - utils::make_array_view(n_matset["indices"])); - return m; - } -}; +inline void verifyMixedField(const conduit::Node &n_field) +{ + SLIC_ERROR_IF(!n_field.has_path("matset_values"), + "The mixed field does not contain matset_values"); +} /*! - * \brief Dispatch a Conduit node containing a unibuffer matset to a function as the appropriate type of matset view. + * \brief Dispatch Conduit nodes containing a unibuffer matset and a values array + * to a function as the appropriate type of matset view. * * \tparam FuncType The function/lambda type that will take the matset. * * \param matset The node that contains the matset. + * \param values The node that contains the values to be used as volume fractions / field. * \param func The function/lambda that will operate on the matset view. + * + * \return true if the dispatch worked, false otherwise. */ template -bool dispatch_material_unibuffer(const conduit::Node &matset, FuncType &&func) +bool dispatch_material_unibuffer_with_values(const conduit::Node &matset, const conduit::Node &values, FuncType &&func) { bool retval = false; verify(matset, "matset"); @@ -70,12 +53,12 @@ bool dispatch_material_unibuffer(const conduit::Node &matset, FuncType &&func) matset["offsets"], matset["indices"], [&](auto material_ids, auto sizes, auto offsets, auto indices) { - floatNodeToArrayView(matset["volume_fractions"], [&](auto volume_fractions) { + floatNodeToArrayView(values, [&](auto typedValues) { using IndexType = typename decltype(material_ids)::value_type; - using FloatType = typename decltype(volume_fractions)::value_type; + using FloatType = typename decltype(typedValues)::value_type; UnibufferMaterialView matsetView; - matsetView.set(material_ids, volume_fractions, sizes, offsets, indices); + matsetView.set(material_ids, typedValues, sizes, offsets, indices); func(matsetView); }); }); @@ -116,20 +99,22 @@ IntElement getMaterialID(const conduit::Node &matset, * \tparam FuncType The function/lambda type that will take the matset. * * \param matset The node that contains the matset. + * \param values_object The node that contains the values to use as volume fractions / field values and indices. * \param func The function/lambda that will operate on the matset view. + * + * \return true if the dispatch worked, false otherwise. */ template -bool dispatch_material_multibuffer(const conduit::Node &matset, FuncType &&func) +bool dispatch_material_multibuffer_with_values(const conduit::Node &matset, const conduit::Node &values_object, FuncType &&func) { bool retval = false; verify(matset, "matset"); if(conduit::blueprint::mesh::matset::is_multi_buffer(matset)) { - const conduit::Node &volume_fractions = matset.fetch_existing("volume_fractions"); - if(volume_fractions.number_of_children() > 0) + if(values_object.number_of_children() > 0) { - const conduit::Node &n_firstValues = volume_fractions[0].fetch_existing("values"); - const conduit::Node &n_firstIndices = volume_fractions[0].fetch_existing("indices"); + const conduit::Node &n_firstValues = values_object[0].fetch_existing("values"); + const conduit::Node &n_firstIndices = values_object[0].fetch_existing("indices"); indexNodeToArrayView(n_firstIndices, [&](auto firstIndices) { floatNodeToArrayView(n_firstValues, [&](auto firstValues) { using IntElement = @@ -141,10 +126,10 @@ bool dispatch_material_multibuffer(const conduit::Node &matset, FuncType &&func) MultiBufferMaterialView matsetView; - for(conduit::index_t i = 0; i < volume_fractions.number_of_children(); i++) + for(conduit::index_t i = 0; i < values_object.number_of_children(); i++) { - const conduit::Node &values = volume_fractions[i].fetch_existing("values"); - const conduit::Node &indices = volume_fractions[i].fetch_existing("indices"); + const conduit::Node &values = values_object[i].fetch_existing("values"); + const conduit::Node &indices = values_object[i].fetch_existing("indices"); const IntElement *indices_ptr = indices.value(); const FloatElement *values_ptr = values.value(); @@ -156,7 +141,7 @@ bool dispatch_material_multibuffer(const conduit::Node &matset, FuncType &&func) // Get the material number if we can. IntElement matno = getMaterialID(matset, - volume_fractions[i].name(), + values_object[i].name(), static_cast(i)); matsetView.add(matno, indices_view, values_view); @@ -177,19 +162,21 @@ bool dispatch_material_multibuffer(const conduit::Node &matset, FuncType &&func) * \tparam FuncType The function/lambda type that will take the matset. * * \param matset The node that contains the matset. + * \param values_object The node that contains the values object to be used as volume fractions / field. * \param func The function/lambda that will operate on the matset view. + * + * \return true if the dispatch worked, false otherwise. */ template -bool dispatch_material_element_dominant(const conduit::Node &matset, FuncType &&func) +bool dispatch_material_element_dominant_with_values(const conduit::Node &matset, const conduit::Node &values_object, FuncType &&func) { bool retval = false; verify(matset, "matset"); if(conduit::blueprint::mesh::matset::is_element_dominant(matset)) { - const conduit::Node &volume_fractions = matset.fetch_existing("volume_fractions"); - if(volume_fractions.number_of_children() > 0) + if(values_object.number_of_children() > 0) { - const conduit::Node &n_firstValues = volume_fractions[0]; + const conduit::Node &n_firstValues = values_object[0]; floatNodeToArrayView(n_firstValues, [&](auto firstValues) { using FloatElement = typename std::remove_const::type; @@ -198,16 +185,16 @@ bool dispatch_material_element_dominant(const conduit::Node &matset, FuncType && ElementDominantMaterialView matsetView; - for(conduit::index_t i = 0; i < volume_fractions.number_of_children(); i++) + for(conduit::index_t i = 0; i < values_object.number_of_children(); i++) { - const conduit::Node &values = volume_fractions[i]; + const conduit::Node &values = values_object[i]; const FloatElement *values_ptr = values.value(); FloatView values_view(const_cast(values_ptr), values.dtype().number_of_elements()); // Get the material number if we can. IntElement matno = - getMaterialID(matset, volume_fractions[i].name(), static_cast(i)); + getMaterialID(matset, values_object[i].name(), static_cast(i)); matsetView.add(matno, values_view); } @@ -226,21 +213,23 @@ bool dispatch_material_element_dominant(const conduit::Node &matset, FuncType && * \tparam FuncType The function/lambda type that will take the matset. * * \param matset The node that contains the matset. + * \param values_object The node that contains the values to use as volume fractions / field values and indices. * \param func The function/lambda that will operate on the matset view. + * + * \return true if the dispatch worked, false otherwise. */ template -bool dispatch_material_material_dominant(const conduit::Node &matset, FuncType &&func) +bool dispatch_material_material_dominant_with_values(const conduit::Node &matset, const conduit::Node &values_object, FuncType &&func) { bool retval = false; verify(matset, "matset"); if(conduit::blueprint::mesh::matset::is_material_dominant(matset)) { - const conduit::Node &volume_fractions = matset.fetch_existing("volume_fractions"); const conduit::Node &element_ids = matset.fetch_existing("element_ids"); - if(volume_fractions.number_of_children() > 0 && - volume_fractions.number_of_children() == element_ids.number_of_children()) + if(values_object.number_of_children() > 0 && + values_object.number_of_children() == element_ids.number_of_children()) { - const conduit::Node &n_firstValues = volume_fractions[0]; + const conduit::Node &n_firstValues = values_object[0]; const conduit::Node &n_firstIndices = element_ids[0]; indexNodeToArrayView(n_firstIndices, [&](auto firstIndices) { @@ -254,10 +243,10 @@ bool dispatch_material_material_dominant(const conduit::Node &matset, FuncType & MaterialDominantMaterialView matsetView; - for(conduit::index_t i = 0; i < volume_fractions.number_of_children(); i++) + for(conduit::index_t i = 0; i < values_object.number_of_children(); i++) { const conduit::Node &indices = element_ids[i]; - const conduit::Node &values = volume_fractions[i]; + const conduit::Node &values = values_object[i]; const IntElement *indices_ptr = indices.value(); const FloatElement *values_ptr = values.value(); @@ -283,6 +272,137 @@ bool dispatch_material_material_dominant(const conduit::Node &matset, FuncType & return retval; } +} // end namespace detail + +/*! + * \brief Make a unibuffer matset view from a Conduit node. + */ +template +struct make_unibuffer_matset +{ + using MatsetView = UnibufferMaterialView; + + /*! + * \brief Wrap the Conduit node as a unibuffer matset view. + * + * \param n_matset The Conduit node that contains the matset. + * + * \return A UnibufferMaterialView. + */ + static MatsetView view(const conduit::Node &n_matset) + { + namespace utils = axom::bump::utilities; + verify(n_matset, "matset"); + MatsetView m; + m.set(utils::make_array_view(n_matset["material_ids"]), + utils::make_array_view(n_matset["volume_fractions"]), + utils::make_array_view(n_matset["sizes"]), + utils::make_array_view(n_matset["offsets"]), + utils::make_array_view(n_matset["indices"])); + return m; + } + + /*! + * \brief Wrap the Conduit matset and field nodes as a unibuffer matset view + * so we can traverse the field using material machinery. The field + * data will be accessible as the volume component in the matset view. + * + * \param n_matset The Conduit node that contains the matset. + * \param n_field The Conduit node that contains the mixed field; its format + * must match that of the matset. + * + * \return A UnibufferMaterialView. + */ + static MatsetView mixedFieldView(const conduit::Node &n_matset, + const conduit::Node &n_field) + { + namespace utils = axom::bump::utilities; + verify(n_matset, "matset"); + detail::verifyMixedField(n_field); + // NOTE: further field length and type checking happens in the MatsetView. + MatsetView m; + m.set(utils::make_array_view(n_matset["material_ids"]), + utils::make_array_view(n_field["matset_values"]), + utils::make_array_view(n_matset["sizes"]), + utils::make_array_view(n_matset["offsets"]), + utils::make_array_view(n_matset["indices"])); + return m; + } +}; + +/*! + * \brief Dispatch a Conduit node containing a unibuffer matset to a function as + * the appropriate type of matset view. + * + * \tparam FuncType The function/lambda type that will take the matset. + * + * \param matset The node that contains the matset. + * \param func The function/lambda that will operate on the matset view. + * + * \return true if the dispatch worked, false otherwise. + */ +template +bool dispatch_material_unibuffer(const conduit::Node &matset, FuncType &&func) +{ + verify(matset, "matset"); + return detail::dispatch_material_unibuffer_with_values(matset, + matset["volume_fractions"], std::forward(func)); +} + +/*! + * \brief Dispatch a Conduit node containing a multibuffer matset to a function as + * the appropriate type of matset view. + * + * \tparam FuncType The function/lambda type that will take the matset. + * + * \param matset The node that contains the matset. + * \param values_object The node that contains the values to use as volume fractions / field values and indices. + * \param func The function/lambda that will operate on the matset view. + * + * \return true if the dispatch worked, false otherwise. + */ +template +bool dispatch_material_multibuffer(const conduit::Node &matset, FuncType &&func) +{ + verify(matset, "matset"); + return detail::dispatch_material_multibuffer_with_values(matset, matset["volume_fractions"], std::forward(func)); +} + +/*! + * \brief Dispatch a Conduit node containing a element-dominant matset to a function as + * the appropriate type of matset view. + * + * \tparam FuncType The function/lambda type that will take the matset. + * + * \param matset The node that contains the matset. + * \param func The function/lambda that will operate on the matset view. + * + * \return true if the dispatch worked, false otherwise. + */ +template +bool dispatch_material_element_dominant(const conduit::Node &matset, FuncType &&func) +{ + verify(matset, "matset"); + return detail::dispatch_material_element_dominant_with_values(matset, matset["volume_fractions"], std::forward(func)); +} + +/*! + * \brief Dispatch a Conduit node containing a material-dominant matset to a function as the appropriate type of matset view. + * + * \tparam FuncType The function/lambda type that will take the matset. + * + * \param matset The node that contains the matset. + * \param func The function/lambda that will operate on the matset view. + * + * \return true if the dispatch worked, false otherwise. + */ +template +bool dispatch_material_material_dominant(const conduit::Node &matset, FuncType &&func) +{ + verify(matset, "matset"); + return detail::dispatch_material_material_dominant_with_values(matset, matset["volume_fractions"], std::forward(func)); +} + /*! * \brief Dispatch a Conduit node containing a matset to a function as the appropriate type of matset view. * @@ -290,6 +410,8 @@ bool dispatch_material_material_dominant(const conduit::Node &matset, FuncType & * * \param matset The node that contains the matset. * \param func The function/lambda that will operate on the matset view. + * + * \return true if the dispatch worked, false otherwise. */ template bool dispatch_material(const conduit::Node &matset, FuncType &&func) @@ -303,11 +425,11 @@ bool dispatch_material(const conduit::Node &matset, FuncType &&func) } if(!retval) { - retval = dispatch_material_element_dominant(matset, std::forward(func)); + retval = dispatch_material_element_dominant(matset, std::forward(func)); } if(!retval) { - retval = dispatch_material_material_dominant(matset, std::forward(func)); + retval = dispatch_material_material_dominant(matset, std::forward(func)); } return retval; } diff --git a/src/axom/bump/views/dispatch_material_field.hpp b/src/axom/bump/views/dispatch_material_field.hpp new file mode 100644 index 0000000000..5aacfb38c6 --- /dev/null +++ b/src/axom/bump/views/dispatch_material_field.hpp @@ -0,0 +1,130 @@ +// 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_DISPATCH_MATERIAL_FIELD_HPP_ +#define AXOM_BUMP_DISPATCH_MATERIAL_FIELD_HPP_ +#include "axom/bump/views/dispatch_material.hpp" + +namespace axom +{ +namespace bump +{ +namespace views +{ + +/*! + * \brief Dispatch Conduit nodes containing a unibuffer matset and a values array + * to a function as the appropriate type of matset view. + * + * \tparam FuncType The function/lambda type that will take the matset. + * + * \param matset The node that contains the matset. + * \param n_field The node that contains the values to be used as volume fractions / field. + * \param func The function/lambda that will operate on the matset view. + */ +template +bool dispatch_material_unibuffer_field(const conduit::Node &matset, const conduit::Node &n_field, FuncType &&func) +{ + verify(matset, "matset"); + detail::verifyMixedField(n_field); + return detail::dispatch_material_unibuffer_with_values(matset, + n_field["matset_values"], std::forward(func)); +} + +/*! + * \brief Dispatch a Conduit node containing a multibuffer matset to a function as + * the appropriate type of matset view. + * + * \tparam FuncType The function/lambda type that will take the matset. + * + * \param matset The node that contains the matset. + * \param n_field The node that contains the values to be used as volume fractions / field. + * \param func The function/lambda that will operate on the matset view. + * + * \return true if the dispatch worked, false otherwise. + */ +template +bool dispatch_material_multibuffer_field(const conduit::Node &matset, const conduit::Node &n_field, FuncType &&func) +{ + verify(matset, "matset"); + detail::verifyMixedField(n_field); + return detail::dispatch_material_multibuffer_with_values(matset, n_field["matset_values"], std::forward(func)); +} + +/*! + * \brief Dispatch Conduit nodes containing a element-dominant matset and related field + * to a function as the appropriate type of matset view. + * + * \tparam FuncType The function/lambda type that will take the matset. + * + * \param matset The node that contains the matset. + * \param n_field The node that contains the values to be used as volume fractions / field. + * \param func The function/lambda that will operate on the matset view. + */ +template +bool dispatch_material_element_dominant_field(const conduit::Node &matset, const conduit::Node n_field, FuncType &&func) +{ + verify(matset, "matset"); + detail::verifyMixedField(n_field); + return detail::dispatch_material_element_dominant_with_values(matset, n_field["matset_values"], std::forward(func)); +} + +/*! + * \brief Dispatch Conduit nodes containing a material-dominant matset and related field + * to a function as the appropriate type of matset view. + * + * \tparam FuncType The function/lambda type that will take the matset. + * + * \param matset The node that contains the matset. + * \param n_field The node that contains the values to be used as volume fractions / field. + * \param func The function/lambda that will operate on the matset view. + */ +template +bool dispatch_material_material_dominant_field(const conduit::Node &matset, const conduit::Node n_field, FuncType &&func) +{ + bool retval = false; + verify(matset, "matset"); + detail::verifyMixedField(n_field); + return detail::dispatch_material_material_dominant_with_values(matset, n_field["matset_values"], std::forward(func)); +} + +/*! + * \brief Dispatch Conduit nodes containing a matset and related field + * to a function as the appropriate type of matset view. The matset will be used + * to access the per-material field data. + * + * \tparam FuncType The function/lambda type that will take the matset. + * + * \param matset The node that contains the matset. + * \param n_field The node that contains the values to be used as volume fractions / field. + * \param func The function/lambda that will operate on the matset view. + */ +template +bool dispatch_material_field(const conduit::Node &matset, const conduit::Node &n_field, FuncType &&func) +{ + bool retval = + dispatch_material_unibuffer_field(matset, std::forward(func)); + if(!retval) + { + retval = + dispatch_material_multibuffer_field(matset, std::forward(func)); + } + if(!retval) + { + retval = dispatch_material_element_dominant_field(matset, std::forward(func)); + } + if(!retval) + { + retval = dispatch_material_material_dominant_field(matset, std::forward(func)); + } + return retval; +} + +} // end namespace views +} // end namespace bump +} // end namespace axom + +#endif From 29c972c4c9c11a551af87786a0356bd64c3040e6 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Fri, 8 May 2026 17:40:49 -0700 Subject: [PATCH 209/986] Added some testing for using matsetviews as ways to look at mixed fields. --- src/axom/bump/CMakeLists.txt | 1 + .../tests/blueprint_testing_data_helpers.hpp | 86 ++++++++++++++++--- src/axom/bump/tests/bump_views.cpp | 33 ++++++- src/axom/bump/views/dispatch_material.hpp | 9 +- .../bump/views/dispatch_material_field.hpp | 9 +- src/axom/mir/tests/mir_elvira2d.cpp | 3 +- src/axom/mir/tests/mir_equiz2d.cpp | 3 +- src/axom/mir/tests/mir_equiz3d.cpp | 3 +- 8 files changed, 121 insertions(+), 26 deletions(-) diff --git a/src/axom/bump/CMakeLists.txt b/src/axom/bump/CMakeLists.txt index d897086be7..69913935c8 100644 --- a/src/axom/bump/CMakeLists.txt +++ b/src/axom/bump/CMakeLists.txt @@ -45,6 +45,7 @@ set(bump_headers views/BasicIndexing.hpp views/dispatch_coordset.hpp views/dispatch_material.hpp + views/dispatch_material_field.hpp views/dispatch_rectilinear_topology.hpp views/dispatch_structured_topology.hpp views/dispatch_topology.hpp diff --git a/src/axom/bump/tests/blueprint_testing_data_helpers.hpp b/src/axom/bump/tests/blueprint_testing_data_helpers.hpp index fc5bbba91e..2a1c22e39a 100644 --- a/src/axom/bump/tests/blueprint_testing_data_helpers.hpp +++ b/src/axom/bump/tests/blueprint_testing_data_helpers.hpp @@ -75,6 +75,15 @@ void braid(const std::string &type, const Dimensions &dims, conduit::Node &mesh) add_distance(mesh); } +// Return the max value for element i in vfA, vfB, vfC. +float make_field_value(const std::vector &vfA, + const std::vector &vfB, + const std::vector &vfC, + size_t i) +{ + return axom::utilities::max(vfA[i], axom::utilities::max(vfB[i], vfC[i])); +} + /*! * \brief Make a new "unibuffer" matset from the input vectors. The unibuffer * matset is a style of matset in Blueprint that combines material ids @@ -85,15 +94,17 @@ void braid(const std::string &type, const Dimensions &dims, conduit::Node &mesh) * \param vfC The volume fractions for material C over all zones in the mesh. * \param matnos The material numbers to use for materials A, B, C. * \param[out] matset The node that will contain the matset. + * \param[out] mfield The node that will contain the mixed field. */ void make_unibuffer(const std::vector &vfA, const std::vector &vfB, const std::vector &vfC, const std::vector &matnos, - conduit::Node &matset) + conduit::Node &matset, + conduit::Node &mfield) { std::vector material_ids; - std::vector volume_fractions; + std::vector volume_fractions, field_values; std::vector sizes(vfA.size(), 0); std::vector offsets(vfA.size(), 0); std::vector indices; @@ -123,6 +134,8 @@ void make_unibuffer(const std::vector &vfA, sizes[i]++; } + field_values.push_back(make_field_value(vfA, vfB, vfC, i)); + offsets[i] = offset; offset += sizes[i]; } @@ -132,6 +145,13 @@ void make_unibuffer(const std::vector &vfA, matset["indices"].set(indices); matset["sizes"].set(sizes); matset["offsets"].set(offsets); + + // Add per-material field values into a mixed field. + if(mfield.has_path("topology")) + { + mfield["values"].set(field_values); + mfield["matset_values"].set(volume_fractions); + } } /*! @@ -142,12 +162,14 @@ void make_unibuffer(const std::vector &vfA, * \param vfC The volume fractions for material C over all zones in the mesh. * \param matnos The material numbers to use for materials A, B, C. * \param[out] matset The node that will contain the matset. + * \param[out] mfield The node that will contain the mixed field. */ void make_multibuffer(const std::vector &vfA, const std::vector &vfB, const std::vector &vfC, const std::vector &AXOM_UNUSED_PARAM(matnos), - conduit::Node &matset) + conduit::Node &matset, + conduit::Node &mfield) { std::vector indices(vfA.size()); std::iota(indices.begin(), indices.end(), 0); @@ -157,6 +179,14 @@ void make_multibuffer(const std::vector &vfA, matset["volume_fractions/B/indices"].set(indices); matset["volume_fractions/C/values"].set(vfC); matset["volume_fractions/C/indices"].set(indices); + + // Add per-material field values into a mixed field. + if(mfield.has_path("topology")) + { + mfield["matset_values/A/values"].set(vfA); + mfield["matset_values/B/values"].set(vfB); + mfield["matset_values/C/values"].set(vfC); + } } /*! @@ -167,16 +197,26 @@ void make_multibuffer(const std::vector &vfA, * \param vfC The volume fractions for material C over all zones in the mesh. * \param matnos The material numbers to use for materials A, B, C. * \param[out] matset The node that will contain the matset. + * \param[out] mfield The node that will contain the mixed field. */ void make_element_dominant(const std::vector &vfA, const std::vector &vfB, const std::vector &vfC, const std::vector &AXOM_UNUSED_PARAM(matnos), - conduit::Node &matset) + conduit::Node &matset, + conduit::Node &mfield) { + // NOTE: These are not sparse. matset["volume_fractions/A"].set(vfA); matset["volume_fractions/B"].set(vfB); matset["volume_fractions/C"].set(vfC); + + if(mfield.has_path("topology")) + { + mfield["matset_values/A"].set(vfA); + mfield["matset_values/B"].set(vfB); + mfield["matset_values/C"].set(vfC); + } } /*! @@ -187,14 +227,16 @@ void make_element_dominant(const std::vector &vfA, * \param vfC The volume fractions for material C over all zones in the mesh. * \param matnos The material numbers to use for materials A, B, C. * \param[out] matset The node that will contain the matset. + * \param[out] mfield The node that will contain the mixed field. */ void make_material_dominant(const std::vector &vfA, const std::vector &vfB, const std::vector &vfC, const std::vector &AXOM_UNUSED_PARAM(matnos), - conduit::Node &matset) + conduit::Node &matset, + conduit::Node &mfield) { - std::vector svfA, svfB, svfC; + std::vector svfA, svfB, svfC; // sparse arrays std::vector ziA, ziB, ziC; const size_t n = vfA.size(); for(size_t zi = 0; zi < n; zi++) @@ -221,6 +263,13 @@ void make_material_dominant(const std::vector &vfA, matset["element_ids/A"].set(ziA); matset["element_ids/B"].set(ziB); matset["element_ids/C"].set(ziC); + + if(mfield.has_path("topology")) + { + mfield["matset_values/A"].set(svfA); + mfield["matset_values/B"].set(svfB); + mfield["matset_values/C"].set(svfC); + } } /*! @@ -230,6 +279,7 @@ void make_material_dominant(const std::vector &vfA, * \param topoName The name of mesh topology. * \param dims The dimensions of the mesh. * \param cleanMats Whether to make a matset of only clean zones (1 mat/zone). + * \param makeMixedField Whether to make a mixed field if !cleanMats. * \param[out] mesh The mesh node to which a matset will be added. * * *--------------* @@ -247,8 +297,10 @@ void make_matset(const std::string &type, const std::string &topoName, const Dimensions &dims, bool cleanMats, + bool makeMixedField, conduit::Node &mesh) { + SLIC_ERROR_IF(cleanMats && makeMixedField, "We cannot make a mixed field when making clean materials."); constexpr int sampling = 10; int midx = sampling * dims[0] / 2; int midy = sampling * dims[1] / 2; @@ -342,6 +394,15 @@ void make_matset(const std::string &type, mesh["fields/vfC/association"] = "element"; mesh["fields/vfC/values"].set(vfC); + conduit::Node mfield; + if(makeMixedField) + { + mfield["topology"] = topoName; + mfield["association"] = "element"; + mfield["matset"] = "mat"; + mfield["volume_dependent"] = "false"; + } + const std::vector matnos {{22, 66, 33}}; conduit::Node &matset = mesh["matsets/mat"]; matset["topology"] = topoName; @@ -352,19 +413,24 @@ void make_matset(const std::string &type, // produce different material types. if(type == "unibuffer") { - make_unibuffer(vfA, vfB, vfC, matnos, matset); + make_unibuffer(vfA, vfB, vfC, matnos, matset, mfield); } else if(type == "multibuffer") { - make_multibuffer(vfA, vfB, vfC, matnos, matset); + make_multibuffer(vfA, vfB, vfC, matnos, matset, mfield); } else if(type == "element_dominant") { - make_element_dominant(vfA, vfB, vfC, matnos, matset); + make_element_dominant(vfA, vfB, vfC, matnos, matset, mfield); } else if(type == "material_dominant") { - make_material_dominant(vfA, vfB, vfC, matnos, matset); + make_material_dominant(vfA, vfB, vfC, matnos, matset, mfield); + } + + if(makeMixedField) + { + mesh["fields/mixed"].move(mfield); } } diff --git a/src/axom/bump/tests/bump_views.cpp b/src/axom/bump/tests/bump_views.cpp index 21f5b43978..6a06f5a337 100644 --- a/src/axom/bump/tests/bump_views.cpp +++ b/src/axom/bump/tests/bump_views.cpp @@ -597,9 +597,10 @@ struct test_braid2d_mat // Create the data const bool cleanMats = false; + const bool makeMixedField = true; conduit::Node hostMesh, deviceMesh; axom::blueprint::testing::data::braid(type, dims, hostMesh); - axom::blueprint::testing::data::make_matset(mattype, "mesh", zoneDims, cleanMats, hostMesh); + axom::blueprint::testing::data::make_matset(mattype, "mesh", zoneDims, cleanMats, makeMixedField, hostMesh); utils::copy(deviceMesh, hostMesh); TestApp.saveVisualization(name + "_orig", hostMesh); @@ -616,25 +617,49 @@ struct test_braid2d_mat utils::make_array_view(deviceMesh["matsets/mat/indices"])); // _bump_views_matsetview_end // clang-format on + SLIC_INFO("unibuffer: matsetView"); test_matsetview(nzones, matsetView, allocatorID); + + // Test mixed field. + const auto mixedFieldView = axom::bump::views::make_unibuffer_matset::mixedFieldView(deviceMesh["matsets/mat"], deviceMesh["fields/mixed"]); + SLIC_INFO("unibuffer: mixedFieldView"); + test_matsetview(nzones, mixedFieldView, allocatorID); } else if(mattype == "multibuffer") { axom::bump::views::dispatch_material_multibuffer( deviceMesh["matsets/mat"], - [&](auto matsetView) { test_matsetview(nzones, matsetView, allocatorID); }); + [&](auto matsetView) { SLIC_INFO("multibuffer: matsetView"); test_matsetview(nzones, matsetView, allocatorID); }); + + // Test mixed field. + axom::bump::views::dispatch_material_multibuffer_field( + deviceMesh["matsets/mat"], + deviceMesh["fields/mixed"], + [&](auto mixedFieldView) { SLIC_INFO("multibuffer: mixedFieldView"); test_matsetview(nzones, mixedFieldView, allocatorID); }); } else if(mattype == "element_dominant") { axom::bump::views::dispatch_material_element_dominant( deviceMesh["matsets/mat"], - [&](auto matsetView) { test_matsetview(nzones, matsetView, allocatorID); }); + [&](auto matsetView) { SLIC_INFO("element_dominant: matsetView"); test_matsetview(nzones, matsetView, allocatorID); }); + + // Test mixed field. + axom::bump::views::dispatch_material_element_dominant_field( + deviceMesh["matsets/mat"], + deviceMesh["fields/mixed"], + [&](auto mixedFieldView) { SLIC_INFO("element_dominant: mixedFieldView"); test_matsetview(nzones, mixedFieldView, allocatorID); }); } else if(mattype == "material_dominant") { axom::bump::views::dispatch_material_material_dominant( deviceMesh["matsets/mat"], - [&](auto matsetView) { test_matsetview(nzones, matsetView, allocatorID); }); + [&](auto matsetView) { SLIC_INFO("material_dominant: matsetView"); test_matsetview(nzones, matsetView, allocatorID); }); + + // Test mixed field. + axom::bump::views::dispatch_material_material_dominant_field( + deviceMesh["matsets/mat"], + deviceMesh["fields/mixed"], + [&](auto mixedFieldView) { SLIC_INFO("material_dominant: mixedFieldView"); test_matsetview(nzones, mixedFieldView, allocatorID); }); } } diff --git a/src/axom/bump/views/dispatch_material.hpp b/src/axom/bump/views/dispatch_material.hpp index f30da3b3bc..5010d29754 100644 --- a/src/axom/bump/views/dispatch_material.hpp +++ b/src/axom/bump/views/dispatch_material.hpp @@ -111,10 +111,11 @@ bool dispatch_material_multibuffer_with_values(const conduit::Node &matset, cons verify(matset, "matset"); if(conduit::blueprint::mesh::matset::is_multi_buffer(matset)) { - if(values_object.number_of_children() > 0) + const conduit::Node &volume_fractions = matset.fetch_existing("volume_fractions"); + if(values_object.number_of_children() > 0 && (volume_fractions.number_of_children() == values_object.number_of_children())) { const conduit::Node &n_firstValues = values_object[0].fetch_existing("values"); - const conduit::Node &n_firstIndices = values_object[0].fetch_existing("indices"); + const conduit::Node &n_firstIndices = volume_fractions[0].fetch_existing("indices"); indexNodeToArrayView(n_firstIndices, [&](auto firstIndices) { floatNodeToArrayView(n_firstValues, [&](auto firstValues) { using IntElement = @@ -129,7 +130,7 @@ bool dispatch_material_multibuffer_with_values(const conduit::Node &matset, cons for(conduit::index_t i = 0; i < values_object.number_of_children(); i++) { const conduit::Node &values = values_object[i].fetch_existing("values"); - const conduit::Node &indices = values_object[i].fetch_existing("indices"); + const conduit::Node &indices = volume_fractions[i].fetch_existing("indices"); const IntElement *indices_ptr = indices.value(); const FloatElement *values_ptr = values.value(); @@ -141,7 +142,7 @@ bool dispatch_material_multibuffer_with_values(const conduit::Node &matset, cons // Get the material number if we can. IntElement matno = getMaterialID(matset, - values_object[i].name(), + volume_fractions[i].name(), static_cast(i)); matsetView.add(matno, indices_view, values_view); diff --git a/src/axom/bump/views/dispatch_material_field.hpp b/src/axom/bump/views/dispatch_material_field.hpp index 5aacfb38c6..6c0e4e5b41 100644 --- a/src/axom/bump/views/dispatch_material_field.hpp +++ b/src/axom/bump/views/dispatch_material_field.hpp @@ -85,7 +85,6 @@ bool dispatch_material_element_dominant_field(const conduit::Node &matset, const template bool dispatch_material_material_dominant_field(const conduit::Node &matset, const conduit::Node n_field, FuncType &&func) { - bool retval = false; verify(matset, "matset"); detail::verifyMixedField(n_field); return detail::dispatch_material_material_dominant_with_values(matset, n_field["matset_values"], std::forward(func)); @@ -106,19 +105,19 @@ template bool dispatch_material_field(const conduit::Node &matset, const conduit::Node &n_field, FuncType &&func) { bool retval = - dispatch_material_unibuffer_field(matset, std::forward(func)); + dispatch_material_unibuffer_field(matset, n_field, std::forward(func)); if(!retval) { retval = - dispatch_material_multibuffer_field(matset, std::forward(func)); + dispatch_material_multibuffer_field(matset, n_field, std::forward(func)); } if(!retval) { - retval = dispatch_material_element_dominant_field(matset, std::forward(func)); + retval = dispatch_material_element_dominant_field(matset, n_field, std::forward(func)); } if(!retval) { - retval = dispatch_material_material_dominant_field(matset, std::forward(func)); + retval = dispatch_material_material_dominant_field(matset, n_field, std::forward(func)); } return retval; } diff --git a/src/axom/mir/tests/mir_elvira2d.cpp b/src/axom/mir/tests/mir_elvira2d.cpp index 7441711f66..2b225ca2e0 100644 --- a/src/axom/mir/tests/mir_elvira2d.cpp +++ b/src/axom/mir/tests/mir_elvira2d.cpp @@ -52,7 +52,8 @@ struct braid2d_mat_test axom::StackArray dims {10, 10}; axom::StackArray zoneDims {dims[0] - 1, dims[1] - 1}; axom::blueprint::testing::data::braid(type, dims, n_mesh); - axom::blueprint::testing::data::make_matset(mattype, "mesh", zoneDims, cleanMats, n_mesh); + const bool makeMixedField = false; // for now + axom::blueprint::testing::data::make_matset(mattype, "mesh", zoneDims, cleanMats, makeMixedField, n_mesh); } // Select a chunk of clean and mixed zones. diff --git a/src/axom/mir/tests/mir_equiz2d.cpp b/src/axom/mir/tests/mir_equiz2d.cpp index 1e759da3aa..0bc7d1a411 100644 --- a/src/axom/mir/tests/mir_equiz2d.cpp +++ b/src/axom/mir/tests/mir_equiz2d.cpp @@ -69,7 +69,8 @@ void braid2d_mat_test(const std::string &type, const std::string domainName = axom::fmt::format("domain_{:07}", dom); conduit::Node &hostDomain = (nDomains > 1) ? hostMesh[domainName] : hostMesh; axom::blueprint::testing::data::braid(type, dims, hostDomain); - axom::blueprint::testing::data::make_matset(mattype, "mesh", zoneDims, cleanMats, hostDomain); + const bool makeMixedField = false; // for now + axom::blueprint::testing::data::make_matset(mattype, "mesh", zoneDims, cleanMats, makeMixedField, hostDomain); TestApp.saveVisualization(name + "_orig", hostDomain); } diff --git a/src/axom/mir/tests/mir_equiz3d.cpp b/src/axom/mir/tests/mir_equiz3d.cpp index aea22da66e..ad5a97800c 100644 --- a/src/axom/mir/tests/mir_equiz3d.cpp +++ b/src/axom/mir/tests/mir_equiz3d.cpp @@ -33,7 +33,8 @@ void braid3d_mat_test(const std::string &type, const std::string &mattype, const const bool cleanMats = false; conduit::Node hostMesh, deviceMesh; axom::blueprint::testing::data::braid(type, dims, hostMesh); - axom::blueprint::testing::data::make_matset(mattype, "mesh", zoneDims, cleanMats, hostMesh); + const bool makeMixedField = false; // for now + axom::blueprint::testing::data::make_matset(mattype, "mesh", zoneDims, cleanMats, makeMixedField, hostMesh); utils::copy(deviceMesh, hostMesh); TestApp.saveVisualization(name + "_orig", hostMesh); From c3811408789bc3e1c0f4c14ae906427ca3596e13 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Fri, 8 May 2026 17:56:41 -0700 Subject: [PATCH 210/986] Change mixvar values for unibuffer version --- src/axom/bump/tests/blueprint_testing_data_helpers.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/axom/bump/tests/blueprint_testing_data_helpers.hpp b/src/axom/bump/tests/blueprint_testing_data_helpers.hpp index 2a1c22e39a..9e9a2cb8ea 100644 --- a/src/axom/bump/tests/blueprint_testing_data_helpers.hpp +++ b/src/axom/bump/tests/blueprint_testing_data_helpers.hpp @@ -81,7 +81,7 @@ float make_field_value(const std::vector &vfA, const std::vector &vfC, size_t i) { - return axom::utilities::max(vfA[i], axom::utilities::max(vfB[i], vfC[i])); + return vfA[i] + vfB[i] + vfC[i]; } /*! From 3d1bc36889f9b571d0d633123f228e1b1102a99c Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 11 May 2026 12:26:33 -0700 Subject: [PATCH 211/986] Added some untested mixed field support for scalars. --- src/axom/bump/MergeMeshes.hpp | 603 ++++++++++++++++++++++----- src/axom/bump/views/MaterialView.hpp | 2 +- 2 files changed, 502 insertions(+), 103 deletions(-) diff --git a/src/axom/bump/MergeMeshes.hpp b/src/axom/bump/MergeMeshes.hpp index ceea86a1bd..79b2be4211 100644 --- a/src/axom/bump/MergeMeshes.hpp +++ b/src/axom/bump/MergeMeshes.hpp @@ -112,7 +112,11 @@ class MergeMeshes { std::string m_topology; std::string m_association; + std::string m_matset; + int m_volume_dependent; int m_dtype; + int m_have_values; + int m_have_matset_values; std::vector m_components; }; @@ -223,7 +227,10 @@ class MergeMeshes * * \param inputs A vector of inputs to be merged. * \param n_options A node containing options. - * \param[out] output The node that will contain the merged mesh. + * \param[out] output The node that will contain the merged mesh. + * + * \note We merge matsets first (for derived classes) so we can use the merged matset + * when we merge mixed fields. */ void mergeInputs(const std::vector &inputs, const conduit::Node &n_options, @@ -231,8 +238,8 @@ class MergeMeshes { mergeCoordset(inputs, output); mergeTopology(inputs, n_options, output); - mergeFields(inputs, output); mergeMatset(inputs, output); + mergeFields(inputs, output); } /*! @@ -1168,6 +1175,7 @@ class MergeMeshes { // Make field information in case some inputs do not have the field. std::map fieldInfo; + const int InvalidVolumeDependent = -1; for(axom::IndexType i = 0; i < n; i++) { if(inputs[i].m_input->has_child("fields")) @@ -1176,21 +1184,55 @@ class MergeMeshes for(conduit::index_t c = 0; c < n_fields.number_of_children(); c++) { const conduit::Node &n_field = n_fields[c]; - const conduit::Node &n_values = n_field.fetch_existing("values"); FieldInformation fi; fi.m_topology = n_field.fetch_existing("topology").as_string(); fi.m_association = n_field.fetch_existing("association").as_string(); - if(n_values.number_of_children() > 0) + fi.m_matset = std::string(); + fi.m_volume_dependent = InvalidVolumeDependent; + fi.m_dtype = -1; + fi.m_have_values = 0; + fi.m_have_matset_values = 0; + + if(n_field.has_path("volume_dependent")) { - for(conduit::index_t comp = 0; comp < n_values.number_of_children(); comp++) + const auto &vd = n_field["volume_dependent"]; + if(vd.dtype().is_string()) + { + fi.m_volume_dependent = (vd.as_string() == "true") ? 1 : 0; + } + else if(vd.dtype().is_number()) { - fi.m_components.push_back(n_values[comp].name()); - fi.m_dtype = n_values[comp].dtype().id(); + fi.m_volume_dependent = (vd.to_int() != 0) ? 1 : 0; } } - else + + // If the field is a material field, save metadata. + if(n_field.has_path("matset") && n_field.has_path("matset_values")) { - fi.m_dtype = n_values.dtype().id(); + fi.m_matset = n_field["matset"].as_string(); + fi.m_have_matset_values = 1; + const conduit::Node &matset_values = n_field["matset_values"]; + fi.m_dtype = matset_values.dtype().is_object() ? matset_values[0].dtype().id() : matset_values.dtype().id(); + + SLIC_ERROR_IF(fi.m_association == "vertex", "Material fields with vertex centering are not supported."); + } + // If the field has values (all do except for some material fields), save metadata. + if(n_field.has_path("values")) + { + fi.m_have_values = 1; + const conduit::Node &n_values = n_field.fetch_existing("values"); + if(n_values.number_of_children() > 0) + { + for(conduit::index_t comp = 0; comp < n_values.number_of_children(); comp++) + { + fi.m_components.push_back(n_values[comp].name()); + fi.m_dtype = n_values[comp].dtype().id(); + } + } + else + { + fi.m_dtype = n_values.dtype().id(); + } } fieldInfo[n_field.name()] = fi; } @@ -1206,45 +1248,68 @@ class MergeMeshes conduit::Node &n_newField = n_newFields[it->first]; n_newField["association"] = it->second.m_association; n_newField["topology"] = it->second.m_topology; - conduit::Node &n_values = n_newField["values"]; + if(!it->second.m_matset.empty()) + { + n_newField["matset"] = it->second.m_matset; // TODO: Is this right? or, am I passing in a new matset name somewhere else in the derived class? + } + if(it->second.m_volume_dependent != InvalidVolumeDependent) + { + n_newField["volume_dependent"] = ((it->second.m_volume_dependent == 1) ? "true" : "false"); + } + // Handle values if present if(it->second.m_components.empty()) { // Scalar - conduit::Node &n_values = n_newField["values"]; - n_values.set_allocator(conduitAllocatorId); - const std::string srcPath("fields/" + it->first + "/values"); - if(it->second.m_association == "element") - { - n_values.set(conduit::DataType(it->second.m_dtype, totalZones)); - copyZonal(inputs, n_values, srcPath); - } - else if(it->second.m_association == "vertex") + if(it->second.m_have_values) { - n_values.set(conduit::DataType(it->second.m_dtype, totalNodes)); - copyNodal(inputs, n_values, srcPath); + const std::string srcPath("fields/" + it->first + "/values"); + conduit::Node &n_values = n_newField["values"]; + n_values.set_allocator(conduitAllocatorId); + if(it->second.m_association == "element") + { + n_values.set(conduit::DataType(it->second.m_dtype, totalZones)); + copyZonal(inputs, n_values, srcPath); + } + else if(it->second.m_association == "vertex") + { + n_values.set(conduit::DataType(it->second.m_dtype, totalNodes)); + copyNodal(inputs, n_values, srcPath); + } } } else { // Vector - for(size_t ci = 0; ci < it->second.m_components.size(); ci++) + if(it->second.m_have_values) { - conduit::Node &n_comp = n_values[it->second.m_components[ci]]; - n_comp.set_allocator(conduitAllocatorId); - const std::string srcPath("fields/" + it->first + "/values/" + - it->second.m_components[ci]); - if(it->second.m_association == "element") + conduit::Node &n_values = n_newField["values"]; + for(size_t ci = 0; ci < it->second.m_components.size(); ci++) { - n_comp.set(conduit::DataType(it->second.m_dtype, totalZones)); - copyZonal(inputs, n_comp, srcPath); - } - else if(it->second.m_association == "vertex") - { - n_comp.set(conduit::DataType(it->second.m_dtype, totalNodes)); - copyNodal(inputs, n_comp, srcPath); + const std::string srcPath("fields/" + it->first + "/values/" + + it->second.m_components[ci]); + conduit::Node &n_comp = n_values[it->second.m_components[ci]]; + n_comp.set_allocator(conduitAllocatorId); + if(it->second.m_association == "element") + { + n_comp.set(conduit::DataType(it->second.m_dtype, totalZones)); + copyZonal(inputs, n_comp, srcPath); + } + else if(it->second.m_association == "vertex") + { + n_comp.set(conduit::DataType(it->second.m_dtype, totalNodes)); + copyNodal(inputs, n_comp, srcPath); + } } } } + // Handle matset_values if present. + if(it->second.m_have_matset_values) + { + // We make a path to the whole source field because mixed fields have + // different representations depending on the matset. + const std::string matSrcPath("fields/" + it->first); + copyMixedField(inputs, n_newField, matSrcPath); + } } } } @@ -1407,6 +1472,20 @@ class MergeMeshes // Do nothing. } + /*! + * \brief Merge a mixed field that exists on the various mesh inputs. + * + * \param inputs A vector of inputs to be merged. + * \param[out] n_field The new field that we're creating. + * \param srcPath The path to the source input's "matset_values" node. + */ + virtual void copyMixedField(const std::vector &AXOM_UNUSED_PARAM(inputs), + conduit::Node &AXOM_UNUSED_PARAM(n_field), + const std::string &AXOM_UNUSED_PARAM(srcPath)) const + { + // Do nothing. + } + int m_allocator_id; }; @@ -1467,6 +1546,22 @@ class DispatchAnyMatset { axom::bump::views::dispatch_material(n_matset, [&](auto matsetView) { func(matsetView); }); } + + /*! + * \brief Takes a matset node and a mixed field node and turns them into a matset view, + * using the mixed field as the volume fractions, and sends the view to the supplied + * function. + * + * \tparam FuncType A callable object/function/lambda. + * + * \param n_matset A node containing any kind of matset. + * \param func The function to invoke on the matset view. + */ + template + void dispatchMixedField(conduit::Node &n_matset, conduit::Node &n_field, FuncType &&func) + { + axom::bump::views::dispatch_material_field(n_matset, n_field, [&](auto matsetView) { func(matsetView); }); + } }; /*! @@ -1523,6 +1618,22 @@ class DispatchTypedUnibufferMatset views::make_unibuffer_matset::view(n_matset); func(matsetView); } + + /*! + * \brief Takes a matset node and a mixed field node and turns them into a matset view, + * using the mixed field as the volume fractions, and sends the view to the supplied + * function. + * + * \tparam FuncType A callable object/function/lambda. + * + * \param n_matset A node containing any kind of matset. + * \param func The function to invoke on the matset view. + */ + template + void dispatchMixedField(conduit::Node &n_matset, conduit::Node &n_field, FuncType &&func) + { + axom::bump::views::dispatch_material_unibuffer_field(n_matset, n_field, [&](auto matsetView) { func(matsetView); }); + } }; /*! @@ -1543,112 +1654,152 @@ class MergeMeshesAndMatsets : public MergeMeshes #if !defined(__CUDACC__) private: #endif - /*! - * \brief Merge matsets that exist on the various mesh inputs. - * - * \param inputs A vector of inputs to be merged. - * \param[out] output The node that will contain the output mesh. - */ - virtual void mergeMatset(const std::vector &inputs, conduit::Node &output) const override - { - AXOM_ANNOTATE_SCOPE("mergeMatset"); - namespace utils = axom::bump::utilities; - const auto conduitAllocatorId = - axom::sidre::ConduitMemory::axomAllocIdToConduit(this->getAllocatorID()); + using FieldInformation = typename MergeMeshes::FieldInformation; - // Make a pass through the inputs and make a list of the material names. - bool hasMatsets = false, defaultMaterial = false; - int nmats = 0; + struct MaterialInfo + { + bool hasMatsets; + bool defaultMaterial; + int nmats; std::map allMats; - std::string matsetName, topoName; + std::string matsetName; + std::string topoName; + axom::IndexType totalZones; + axom::IndexType totalMatCount; + int itype; + int ftype; + }; + + void getMaterialInfo(const std::vector &inputs, MaterialInfo &mi) const + { + // Make a pass through the inputs and make a list of the material names. + mi.hasMatsets = false; + mi.defaultMaterial = false; + mi.nmats = 0; + mi.allMats.clear(); + mi.matsetName.clear(); + mi.topoName.clear(); + mi.totalZones = 0; + mi.totalMatCount = 0; + mi.itype = -1; + mi.ftype = -1; + for(size_t i = 0; i < inputs.size(); i++) { if(inputs[i].m_input->has_path("matsets")) { conduit::Node &n_matsets = inputs[i].m_input->fetch_existing("matsets"); conduit::Node &n_matset = n_matsets[0]; - matsetName = n_matset.name(); - topoName = n_matset.fetch_existing("topology").as_string(); + mi.matsetName = n_matset.name(); + mi.topoName = n_matset.fetch_existing("topology").as_string(); auto matInfo = axom::bump::views::materials(n_matset); for(const auto &info : matInfo) { - if(allMats.find(info.m_name) == allMats.end()) + if(mi.allMats.find(info.m_name) == mi.allMats.end()) { - allMats[info.m_name] = nmats++; + mi.allMats[info.m_name] = mi.nmats++; } } - hasMatsets = true; + mi.hasMatsets = true; } else { - defaultMaterial = true; + mi.defaultMaterial = true; } } + } - if(hasMatsets) + template + void countMaterialSizes(const std::vector &inputs, MaterialInfo &mi) const + { + AXOM_ANNOTATE_SCOPE("sizes"); + namespace utils = axom::bump::utilities; + // Make a pass through the matsets to determine the overall storage. + mi.totalZones = 0; + mi.totalMatCount = 0; + mi.itype = -1; + mi.ftype = -1; + + MaterialDispatchType disp; + for(size_t i = 0; i < inputs.size(); i++) + { + const auto nzones = this->countZones(inputs, i); + mi.totalZones += nzones; + + if(inputs[i].m_input->has_path("matsets")) + { + conduit::Node &n_matsets = inputs[i].m_input->fetch_existing("matsets"); + conduit::Node &n_matset = n_matsets[0]; + axom::IndexType matCount = 0; + auto *This = this; + disp.dispatchMatset(n_matset, [&](auto matsetView) { + // Figure out the types to use for storing the data. + using IType = typename decltype(matsetView)::IndexType; + using FType = typename decltype(matsetView)::FloatType; + mi.itype = utils::cpp2conduit::id; + mi.ftype = utils::cpp2conduit::id; + + matCount = This->mergeMatset_count(matsetView, nzones); + }); + mi.totalMatCount += matCount; + } + else + { + mi.totalMatCount += nzones; + } + } + } + + /*! + * \brief Merge matsets that exist on the various mesh inputs. + * + * \param inputs A vector of inputs to be merged. + * \param[out] output The node that will contain the output mesh. + * + * \note We only create a unibuffer matset for the merged matset. + */ + virtual void mergeMatset(const std::vector &inputs, conduit::Node &output) const override + { + AXOM_ANNOTATE_SCOPE("mergeMatset"); + namespace utils = axom::bump::utilities; + const auto conduitAllocatorId = + axom::sidre::ConduitMemory::axomAllocIdToConduit(this->getAllocatorID()); + + MaterialInfo mi; + getMaterialInfo(inputs, mi); + + if(mi.hasMatsets) { MaterialDispatch disp; - auto *This = this; // One or more inputs did not have a matset. - if(defaultMaterial) allMats["default"] = nmats++; + if(mi.defaultMaterial) mi.allMats["default"] = mi.nmats++; - // Make a pass through the matsets to determine the overall storage. - axom::IndexType totalZones = 0, totalMatCount = 0; - int itype, ftype; - { - AXOM_ANNOTATE_SCOPE("sizes"); - for(size_t i = 0; i < inputs.size(); i++) - { - const auto nzones = this->countZones(inputs, i); - totalZones += nzones; - - if(inputs[i].m_input->has_path("matsets")) - { - conduit::Node &n_matsets = inputs[i].m_input->fetch_existing("matsets"); - conduit::Node &n_matset = n_matsets[0]; - axom::IndexType matCount = 0; - disp.dispatchMatset(n_matset, [&](auto matsetView) { - // Figure out the types to use for storing the data. - using IType = typename decltype(matsetView)::IndexType; - using FType = typename decltype(matsetView)::FloatType; - itype = utils::cpp2conduit::id; - ftype = utils::cpp2conduit::id; - - matCount = This->mergeMatset_count(matsetView, nzones); - }); - totalMatCount += matCount; - } - else - { - totalMatCount += nzones; - } - } - } + countMaterialSizes(inputs, mi); // Allocate AXOM_ANNOTATE_BEGIN("allocate"); - conduit::Node &n_newMatset = output["matsets/" + matsetName]; - n_newMatset["topology"] = topoName; + conduit::Node &n_newMatset = output["matsets/" + mi.matsetName]; + n_newMatset["topology"] = mi.topoName; conduit::Node &n_volume_fractions = n_newMatset["volume_fractions"]; n_volume_fractions.set_allocator(conduitAllocatorId); - n_volume_fractions.set(conduit::DataType(ftype, totalMatCount)); + n_volume_fractions.set(conduit::DataType(mi.ftype, mi.totalMatCount)); conduit::Node &n_material_ids = n_newMatset["material_ids"]; n_material_ids.set_allocator(conduitAllocatorId); - n_material_ids.set(conduit::DataType(itype, totalMatCount)); + n_material_ids.set(conduit::DataType(mi.itype, mi.totalMatCount)); conduit::Node &n_sizes = n_newMatset["sizes"]; n_sizes.set_allocator(conduitAllocatorId); - n_sizes.set(conduit::DataType(itype, totalZones)); + n_sizes.set(conduit::DataType(mi.itype, mi.totalZones)); conduit::Node &n_offsets = n_newMatset["offsets"]; n_offsets.set_allocator(conduitAllocatorId); - n_offsets.set(conduit::DataType(itype, totalZones)); + n_offsets.set(conduit::DataType(mi.itype, mi.totalZones)); conduit::Node &n_indices = n_newMatset["indices"]; n_indices.set_allocator(conduitAllocatorId); - n_indices.set(conduit::DataType(itype, totalMatCount)); + n_indices.set(conduit::DataType(mi.itype, mi.totalMatCount)); AXOM_ANNOTATE_END("allocate"); { @@ -1656,10 +1807,11 @@ class MergeMeshesAndMatsets : public MergeMeshes // Make material_map. conduit::Node &n_material_map = n_newMatset["material_map"]; - for(auto it = allMats.begin(); it != allMats.end(); it++) + for(auto it = mi.allMats.begin(); it != mi.allMats.end(); it++) n_material_map[it->first] = it->second; // Populate + auto *This = this; disp.execute(n_material_ids, n_sizes, n_offsets, @@ -1695,7 +1847,7 @@ class MergeMeshesAndMatsets : public MergeMeshes axom::exclusive_scan(sizesView, offsetsView); // Make indices. - This->mergeMatset_indices(indicesView, totalMatCount); + This->mergeMatset_indices(indicesView, mi.totalMatCount); // Fill in material info. zOffset = 0; @@ -1710,7 +1862,7 @@ class MergeMeshesAndMatsets : public MergeMeshes disp.dispatchMatset(n_matset, [&](auto matsetView) { This->mergeMatset_copy(n_matset, - allMats, + mi.allMats, materialIdsView, offsetsView, volumeFractionsView, @@ -1721,7 +1873,7 @@ class MergeMeshesAndMatsets : public MergeMeshes } else { - const int dmat = allMats["default"]; + const int dmat = mi.allMats["default"]; This->mergeMatset_default(materialIdsView, offsetsView, volumeFractionsView, @@ -1935,6 +2087,253 @@ class MergeMeshesAndMatsets : public MergeMeshes materialIdsView[zoneStart] = matno; }); } + + /*! + * \brief Determine the field information (data type and component paths). + * + * \param inputs The mesh inputs being merged. + * \param srcFieldPath The path to the source field. + * \param[out] fi The field information. We only fill in the dtype and component names. + */ + void determineMixedFieldInformation(const std::vector &inputs, + const std::string &srcFieldPath, + FieldInformation &fi) const + { + /* Various mixed field representations. + + # unibuffer - scalar + mat: + matset_values: [] + + # unibuffer - vector + mat: + matset_values: + x: [] + y: [] + + # multibuffer - scalar + mat: + matset_values: + matA: [] + matB: [] + + # multibuffer - vector + mat: + matset_values: + matA: + x: [] + y: [] + matB: + x: [] + y: [] + */ + fi.m_dtype = -1; + fi.m_components.clear(); + + for(size_t i = 0; i < inputs.size(); i++) + { + if(inputs[i].m_input->has_child(srcFieldPath)) + { + const conduit::Node &n_src_field = inputs[i].m_input->fetch_existing(srcFieldPath); + if(n_src_field.has_child("matset_values")) + { + const conduit::Node &n_matset_values = n_src_field["matset_values"]; + if(n_matset_values.number_of_children() > 0) + { + // multibuffer + for(conduit::index_t ci = 0; ci < n_matset_values.number_of_children(); ci++) + { + const conduit::Node &n_component = n_matset_values[ci]; + + if(n_component.number_of_children() > 0) + { + // n_component is really a material name + fi.m_dtype = n_component[0].dtype().id(); + for(conduit::index_t sci = 0; sci < n_matset_values.number_of_children(); sci++) + { + fi.m_components.push_back(n_component[sci].name()); + } + } + else + { + fi.m_dtype = n_component.dtype().id(); + fi.m_components.push_back(n_component.name()); + } + } + } + else + { + // unibuffer + fi.m_dtype = n_matset_values[0].dtype().id(); + } + break; + } + } + } + } + + /*! + * \brief Merge a mixed field that exists on the various mesh inputs. + * + * \param inputs A vector of inputs to be merged. + * \param[out] n_field The new field that we're creating. + * \param srcPath The path to the source input's mixed field node. + * + * \note This function relies on the materials having been merged first so we + * can use their pre-existing merged arrays. + */ + virtual void copyMixedField(const std::vector &inputs, + conduit::Node &n_field, + const std::string &srcFieldPath) const override + { + AXOM_ANNOTATE_SCOPE("copyMixedField"); + MaterialInfo mi; + getMaterialInfo(inputs, mi); + + if(mi.hasMatsets) + { + const auto conduitAllocatorId = + axom::sidre::ConduitMemory::axomAllocIdToConduit(this->getAllocatorID()); + + // One or more inputs did not have a matset. + if(mi.defaultMaterial) mi.allMats["default"] = mi.nmats++; + + countMaterialSizes(inputs, mi); + const std::string matsetName(mi.matsetName); + n_field["matset"] = matsetName; + + FieldInformation fi; + determineMixedFieldInformation(inputs, srcFieldPath, fi); + SLIC_ERROR_IF(fi.m_dtype == -1, axom::fmt::format("The new mixed field type for {} was not determined.", srcFieldPath)); + SLIC_ERROR_IF(fi.m_components.size() > 0, "Vector mixed vars not supported"); + + // Get the Conduit node for the matset we built previously + conduit::Node *n_matset = const_cast(conduit::blueprint::mesh::utils::find_reference_node(n_field, "matset")); + SLIC_ERROR_IF(n_matset == nullptr, axom::fmt::format("The new matset {} was not found.", mi.matsetName)); + + // Get some parts of the new unibuffer matset. + conduit::Node &n_material_ids = n_matset->fetch_existing("material_ids"); + conduit::Node &n_sizes = n_matset->fetch_existing("sizes"); + conduit::Node &n_offsets = n_matset->fetch_existing("offsets"); + conduit::Node &n_indices = n_matset->fetch_existing("indices"); + + // TODO: handle multiple components. + + // Allocate the new mixed field. + conduit::Node &n_matset_values = n_field["matset_values"]; + n_matset_values.set_allocator(conduitAllocatorId); + n_matset_values.set(conduit::DataType(mi.ftype, mi.totalMatCount)); + + // Dispatch the mixed material we built so we can access its arrays as views. + MaterialDispatch disp; + auto *This = this; + disp.execute(n_material_ids, + n_sizes, + n_offsets, + n_indices, + n_matset_values, + [&](auto AXOM_UNUSED_PARAM(materialIdsView), + auto AXOM_UNUSED_PARAM(sizesView), + auto offsetsView, + auto AXOM_UNUSED_PARAM(indicesView), + auto matsetValuesView) + { + // Iterate over the inputs and copy their mixed field + axom::IndexType zOffset = 0; + for(size_t i = 0; i < inputs.size(); i++) + { + const auto nzones = This->countZones(inputs, i); + + if(inputs[i].m_input->has_child("matsets") && + inputs[i].m_input->has_child(srcFieldPath)) + { + // Get the source matset. + conduit::Node &n_matsets = inputs[i].m_input->fetch_existing("matsets"); + conduit::Node &n_matset = n_matsets.fetch_existing(matsetName); + // Get the source field. + conduit::Node &n_src_field = inputs[i].m_input->fetch_existing(srcFieldPath); + + // Dispatch the source mixed field. + disp.dispatchMixedField(n_matset, n_src_field, [&](auto srcMatsetView) + { + This->mergeMixedField_copy(srcMatsetView, + matsetValuesView, + offsetsView, + nzones, + zOffset); + }); + } + else + { + This->mergeMixedField_default(matsetValuesView, + offsetsView, + nzones, + zOffset); + } + zOffset += nzones; + } + }); + } + } + + /*! + * \brief Copy a mixed field from the \a srcMatsetView into the output \a mixedFieldView. + * + * \param srcMatsetView The input matset view that contains the field, accessible as "volume_fraction". + * \param[out] mixedFieldView The output view that contains the merged field. + * \param offsetsView The offsets view that contains the offsets for the output zones. + * \param nzones The number of zones in the current input. + * \param zOffset The starting offset in the output mixed field view. + */ + template + void mergeMixedField_copy(const MatsetView &srcMatsetView, + MixedFieldView &mixedFieldView, + const OffsetsView &offsetsView, + axom::IndexType nzones, + axom::IndexType zOffset) const + { + axom::for_all( + nzones, + AXOM_LAMBDA(axom::IndexType zoneIndex) + { + // Get this zone's materials. + auto zoneMat = srcMatsetView.beginZone(zoneIndex); + const auto nmats = zoneMat.size(); + + // Store the materials in the new material. + const auto zoneStart = offsetsView[zOffset + zoneIndex]; + for(axom::IndexType mi = 0; mi < nmats; mi++, zoneMat++) + { + const auto destIndex = zoneStart + mi; + // NOTE: We are using the "volume_fraction" from that MatsetView as the mixed field value. + mixedFieldView[destIndex] = zoneMat.volume_fraction(); + } + }); + } + + /*! + * \brief Fill a mixed field with a default value. + * + * \param[out] mixedFieldView The output view that contains the merged field. + * \param offsetsView The offsets view that contains the offsets for the output zones. + * \param nzones The number of zones in the current input. + * \param zOffset The starting offset in the output mixed field view. + */ + template + void mergeMixedField_default(MixedFieldView &mixedFieldView, + const OffsetsView &offsetsView, + axom::IndexType nzones, + axom::IndexType zOffset) const + { + axom::for_all( + nzones, + AXOM_LAMBDA(axom::IndexType zoneIndex) + { + const auto zoneStart = offsetsView[zOffset + zoneIndex]; + mixedFieldView[zoneStart] = 0; + }); + } + }; } // end namespace bump diff --git a/src/axom/bump/views/MaterialView.hpp b/src/axom/bump/views/MaterialView.hpp index 00f72ba2da..d16fc10d8a 100644 --- a/src/axom/bump/views/MaterialView.hpp +++ b/src/axom/bump/views/MaterialView.hpp @@ -239,7 +239,7 @@ class UnibufferMaterialView void AXOM_HOST_DEVICE advance(bool doIncrement) { m_currentIndex += (doIncrement && m_currentIndex < size()) ? 1 : 0; - const auto idx = m_view->m_offsets[m_zoneIndex] + m_currentIndex; + const axom::IndexType idx = m_view->m_offsets[m_zoneIndex] + m_currentIndex; if(idx < m_view->m_indices.size()) { m_index = m_view->m_indices[idx]; From f4ad0e74fcccb4b2a1ac84d33121a64372a8564f Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 11 May 2026 17:09:24 -0700 Subject: [PATCH 212/986] Added code to test merging materials and material fields. --- src/axom/bump/MergeMeshes.hpp | 64 +++++++++++---------- src/axom/bump/tests/bump_mergemeshes.cpp | 71 +++++++++++++++++++++++- 2 files changed, 103 insertions(+), 32 deletions(-) diff --git a/src/axom/bump/MergeMeshes.hpp b/src/axom/bump/MergeMeshes.hpp index 79b2be4211..410048ffde 100644 --- a/src/axom/bump/MergeMeshes.hpp +++ b/src/axom/bump/MergeMeshes.hpp @@ -2129,45 +2129,49 @@ class MergeMeshesAndMatsets : public MergeMeshes */ fi.m_dtype = -1; fi.m_components.clear(); - + + std::string srcFieldMatsetValuesPath(srcFieldPath + "/matset_values"); + for(size_t i = 0; i < inputs.size(); i++) { - if(inputs[i].m_input->has_child(srcFieldPath)) + inputs[i].m_input->print(); + if(inputs[i].m_input->has_path(srcFieldMatsetValuesPath)) { - const conduit::Node &n_src_field = inputs[i].m_input->fetch_existing(srcFieldPath); - if(n_src_field.has_child("matset_values")) + const conduit::Node &n_matset_values = inputs[i].m_input->fetch_existing(srcFieldMatsetValuesPath); +#if 1 +std::cout << "srcFieldPath: " << srcFieldPath << std::endl; +std::cout << "srcFieldMatsetValuesPath: " << srcFieldMatsetValuesPath << std::endl; +n_matset_values.print(); +#endif + if(n_matset_values.number_of_children() > 0) { - const conduit::Node &n_matset_values = n_src_field["matset_values"]; - if(n_matset_values.number_of_children() > 0) + // multibuffer + for(conduit::index_t ci = 0; ci < n_matset_values.number_of_children(); ci++) { - // multibuffer - for(conduit::index_t ci = 0; ci < n_matset_values.number_of_children(); ci++) + const conduit::Node &n_component = n_matset_values[ci]; + + if(n_component.number_of_children() > 0) { - const conduit::Node &n_component = n_matset_values[ci]; - - if(n_component.number_of_children() > 0) - { - // n_component is really a material name - fi.m_dtype = n_component[0].dtype().id(); - for(conduit::index_t sci = 0; sci < n_matset_values.number_of_children(); sci++) - { - fi.m_components.push_back(n_component[sci].name()); - } - } - else + // n_component is really a material name + fi.m_dtype = n_component[0].dtype().id(); + for(conduit::index_t sci = 0; sci < n_matset_values.number_of_children(); sci++) { - fi.m_dtype = n_component.dtype().id(); - fi.m_components.push_back(n_component.name()); + fi.m_components.push_back(n_component[sci].name()); } } + else + { + fi.m_dtype = n_component.dtype().id(); + fi.m_components.push_back(n_component.name()); + } } - else - { - // unibuffer - fi.m_dtype = n_matset_values[0].dtype().id(); - } - break; } + else + { + // unibuffer + fi.m_dtype = n_matset_values.dtype().id(); + } + break; } } } @@ -2209,7 +2213,7 @@ class MergeMeshesAndMatsets : public MergeMeshes // Get the Conduit node for the matset we built previously conduit::Node *n_matset = const_cast(conduit::blueprint::mesh::utils::find_reference_node(n_field, "matset")); - SLIC_ERROR_IF(n_matset == nullptr, axom::fmt::format("The new matset {} was not found.", mi.matsetName)); + SLIC_ERROR_IF(n_matset == nullptr, axom::fmt::format("The new matset {} was not found.", matsetName)); // Get some parts of the new unibuffer matset. conduit::Node &n_material_ids = n_matset->fetch_existing("material_ids"); @@ -2245,7 +2249,7 @@ class MergeMeshesAndMatsets : public MergeMeshes const auto nzones = This->countZones(inputs, i); if(inputs[i].m_input->has_child("matsets") && - inputs[i].m_input->has_child(srcFieldPath)) + inputs[i].m_input->has_path(srcFieldPath)) { // Get the source matset. conduit::Node &n_matsets = inputs[i].m_input->fetch_existing("matsets"); diff --git a/src/axom/bump/tests/bump_mergemeshes.cpp b/src/axom/bump/tests/bump_mergemeshes.cpp index 799d4e592d..822f254254 100644 --- a/src/axom/bump/tests/bump_mergemeshes.cpp +++ b/src/axom/bump/tests/bump_mergemeshes.cpp @@ -55,13 +55,13 @@ struct test_mergemeshes // Execute conduit::Node opts, deviceResult; opts["topology"] = "mesh"; - bump::MergeMeshes mm; + bump::MergeMeshesAndMatsets mm; mm.execute(inputs, opts, deviceResult); // device->host conduit::Node hostResult; utils::copy(hostResult, deviceResult); - +printNode(hostResult); constexpr double tolerance = 1.e-7; conduit::Node expectedResult, info; result(expectedResult); @@ -101,6 +101,25 @@ struct test_mergemeshes topology: mesh association: element values: [0,1,2, 3,4,5] + zonal_mixed: + topology: mesh + association: element + matset: mat + values: [100.1, 101.1, 102.1, 103.25, 104.25, 105.25] + # matset_values encodes 100+zone.mat + matset_values: [100.1, 101.1, 102.1, 103.2,103.3, 104.2,104.3, 105.2,105.3] + matsets: + mat: + material_map: + A: 1 + B: 2 + C: 3 + topology: mesh + material_ids: [1, 1, 1, 2,3, 2,3, 2,3] + volume_fractions: [1., 1., 1., 0.5,0.5, 0.5,0.5, 0.5,0.5] + indices: [0, 1, 2, 3,4, 5,6, 7,8] + sizes: [1, 1, 1, 2, 2, 2] + offsets: [0, 1, 2, 3, 5, 7] domain0001: coordsets: coords: @@ -130,6 +149,25 @@ struct test_mergemeshes topology: mesh association: element values: [0,1,2,3, 4,5,6,7, 8] + zonal_mixed: + topology: mesh + association: element + matset: mat + values: [200.1, 201.15, 202.3, 203.15, 204.1, 205.15, 206.2, 207.15, 208.15] + # matset_values encodes 200+zone.mat + matset_values: [200.1, 201.1,201.2, 202.3, 203.1,203.2, 204.1, 205.1,205.2, 206.2, 207.1,207.2, 208.1,208.2] + matsets: + mat: + material_map: + A: 1 + B: 2 + C: 3 + topology: mesh + material_ids: [1, 1,2, 2, 1,2, 1, 1,2, 2, 1,2, 1,2] + volume_fractions: [1., 0.5,0.5, 1., 0.5,0.5, 1., 0.5,0.5, 1., 0.5,0.5, 0.5,0.5] + indices: [0, 1,2, 3, 4,5, 6, 7,8, 9, 10,11, 12,13] + sizes: [1, 2, 1, 2, 1, 2, 1, 2, 2] + offsets: [0, 1, 3, 4, 6, 7, 9, 10, 12] )xx"; mesh.parse(yaml); } @@ -156,12 +194,40 @@ struct test_mergemeshes quad: 3 tri: 2 shapes: [3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 3] +matsets: + mat: + topology: "mesh" + volume_fractions: [1.0, 1.0, 1.0, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 1.0, 0.5, 0.5, 1.0, 0.5, 0.5, 1.0, 0.5, 0.5, 1.0, 0.5, 0.5, 0.5, 0.5] + material_ids: [0, 0, 0, 1, 2, 1, 2, 1, 2, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1] + sizes: [1, 1, 1, 2, 2, 2, 1, 2, 1, 2, 1, 2, 1, 2, 2] + offsets: [0, 1, 2, 3, 5, 7, 9, 10, 12, 13, 15, 16, 18, 19, 21] + indices: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22] + material_map: + A: 0 + B: 1 + C: 2 +fields: + nodal: + association: "vertex" + topology: "mesh" + values: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2] + zonal: + association: "element" + topology: "mesh" + values: [0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 6, 7, 8] + zonal_mixed: + association: "element" + topology: "mesh" + matset: "mat" + values: [100.1, 101.1, 102.1, 103.25, 104.25, 105.25, 200.1, 201.15, 202.3, 203.15, 204.1, 205.15, 206.2, 207.15, 208.15] + matset_values: [100.1, 101.1, 102.1, 103.2, 103.3, 104.2, 104.3, 105.2, 105.3, 200.1, 201.1, 201.2, 202.3, 203.1, 203.2, 204.1, 205.1, 205.2, 206.2, 207.1, 207.2, 208.1, 208.2] )xx"; mesh.parse(yaml); } }; TEST(bump_mergemeshes, mergemeshes_seq) { test_mergemeshes::test(); } +/* #if defined(AXOM_USE_OPENMP) TEST(bump_mergemeshes, mergemeshes_omp) { test_mergemeshes::test(); } #endif @@ -171,6 +237,7 @@ TEST(bump_mergemeshes, mergemeshes_cuda) { test_mergemeshes::test(); #if defined(AXOM_USE_HIP) TEST(bump_mergemeshes, mergemeshes_hip) { test_mergemeshes::test(); } #endif +*/ //------------------------------------------------------------------------------ void conduit_debug_err_handler(const std::string &s1, const std::string &s2, int i1) From 7eba7afccbbff185ef7e107e48411be0fceaa81f Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 11 May 2026 19:40:44 -0700 Subject: [PATCH 213/986] Debugged merging materials and mixed fields in different matset flavors. --- src/axom/bump/MergeMeshes.hpp | 56 ++++++++--- src/axom/bump/tests/bump_mergemeshes.cpp | 86 ++++++++++++++-- src/axom/bump/views/dispatch_material.hpp | 97 +++++++++++-------- .../bump/views/dispatch_material_field.hpp | 12 ++- 4 files changed, 185 insertions(+), 66 deletions(-) diff --git a/src/axom/bump/MergeMeshes.hpp b/src/axom/bump/MergeMeshes.hpp index 410048ffde..8f1246aa8e 100644 --- a/src/axom/bump/MergeMeshes.hpp +++ b/src/axom/bump/MergeMeshes.hpp @@ -1660,6 +1660,8 @@ class MergeMeshesAndMatsets : public MergeMeshes { bool hasMatsets; bool defaultMaterial; + bool elementDominant; + bool multiBuffer; int nmats; std::map allMats; std::string matsetName; @@ -1670,6 +1672,12 @@ class MergeMeshesAndMatsets : public MergeMeshes int ftype; }; + /*! + * \brief Get the material information we'll need to merge materials. + * + * \param inputes The inputs to be merged. + * \param[out] mi The material information. + */ void getMaterialInfo(const std::vector &inputs, MaterialInfo &mi) const { // Make a pass through the inputs and make a list of the material names. @@ -1683,6 +1691,8 @@ class MergeMeshesAndMatsets : public MergeMeshes mi.totalMatCount = 0; mi.itype = -1; mi.ftype = -1; + mi.elementDominant = true; + mi.multiBuffer = false; for(size_t i = 0; i < inputs.size(); i++) { @@ -1692,6 +1702,8 @@ class MergeMeshesAndMatsets : public MergeMeshes conduit::Node &n_matset = n_matsets[0]; mi.matsetName = n_matset.name(); mi.topoName = n_matset.fetch_existing("topology").as_string(); + mi.elementDominant = conduit::blueprint::mesh::matset::is_element_dominant(n_matset); + mi.multiBuffer = conduit::blueprint::mesh::matset::is_multi_buffer(n_matset) && !n_matset.has_child("material_ids"); auto matInfo = axom::bump::views::materials(n_matset); for(const auto &info : matInfo) { @@ -1709,6 +1721,13 @@ class MergeMeshesAndMatsets : public MergeMeshes } } + /*! + * \brief Counts the size of the materials and stores the results into \a mi. This lets us know + * how big the merged matset will be. + * + * \param inputs The inputs to be merged. + * \param[out] mi The material information. + */ template void countMaterialSizes(const std::vector &inputs, MaterialInfo &mi) const { @@ -2095,8 +2114,9 @@ class MergeMeshesAndMatsets : public MergeMeshes * \param srcFieldPath The path to the source field. * \param[out] fi The field information. We only fill in the dtype and component names. */ - void determineMixedFieldInformation(const std::vector &inputs, + void getMixedFieldInformation(const std::vector &inputs, const std::string &srcFieldPath, + const MaterialInfo &mi, FieldInformation &fi) const { /* Various mixed field representations. @@ -2134,25 +2154,21 @@ class MergeMeshesAndMatsets : public MergeMeshes for(size_t i = 0; i < inputs.size(); i++) { - inputs[i].m_input->print(); if(inputs[i].m_input->has_path(srcFieldMatsetValuesPath)) { const conduit::Node &n_matset_values = inputs[i].m_input->fetch_existing(srcFieldMatsetValuesPath); -#if 1 -std::cout << "srcFieldPath: " << srcFieldPath << std::endl; -std::cout << "srcFieldMatsetValuesPath: " << srcFieldMatsetValuesPath << std::endl; -n_matset_values.print(); -#endif - if(n_matset_values.number_of_children() > 0) + + // NOTE: we only try to populate components for fields that look like vectors + if(mi.multiBuffer) { - // multibuffer for(conduit::index_t ci = 0; ci < n_matset_values.number_of_children(); ci++) { + // n_component is a material name const conduit::Node &n_component = n_matset_values[ci]; if(n_component.number_of_children() > 0) { - // n_component is really a material name + // vector components under the material name fi.m_dtype = n_component[0].dtype().id(); for(conduit::index_t sci = 0; sci < n_matset_values.number_of_children(); sci++) { @@ -2161,15 +2177,29 @@ n_matset_values.print(); } else { + // scalar - the material name fi.m_dtype = n_component.dtype().id(); - fi.m_components.push_back(n_component.name()); } } } else { // unibuffer - fi.m_dtype = n_matset_values.dtype().id(); + if(n_matset_values.number_of_children() > 0) + { + // vector - the component name + for(conduit::index_t ci = 0; ci < n_matset_values.number_of_children(); ci++) + { + const conduit::Node &n_component = n_matset_values[ci]; + fi.m_dtype = n_component.dtype().id(); + fi.m_components.push_back(n_component.name()); + } + } + else + { + // scalar + fi.m_dtype = n_matset_values.dtype().id(); + } } break; } @@ -2207,7 +2237,7 @@ n_matset_values.print(); n_field["matset"] = matsetName; FieldInformation fi; - determineMixedFieldInformation(inputs, srcFieldPath, fi); + getMixedFieldInformation(inputs, srcFieldPath, mi, fi); SLIC_ERROR_IF(fi.m_dtype == -1, axom::fmt::format("The new mixed field type for {} was not determined.", srcFieldPath)); SLIC_ERROR_IF(fi.m_components.size() > 0, "Vector mixed vars not supported"); diff --git a/src/axom/bump/tests/bump_mergemeshes.cpp b/src/axom/bump/tests/bump_mergemeshes.cpp index 822f254254..263bb1bda9 100644 --- a/src/axom/bump/tests/bump_mergemeshes.cpp +++ b/src/axom/bump/tests/bump_mergemeshes.cpp @@ -16,6 +16,11 @@ #include #include +// NOTE: Conduit 0.9.6 and later has macros but Axom is not quite on that version yet. +#include "conduit/conduit_config.h" +#define AXOM_CONDUIT_MAKE_VERSION_VALUE(MAJOR, MINOR, PATCH) (((MAJOR)*10000) + ((MINOR)*100) + (PATCH)) +#define AXOM_CONDUIT_VERSION_VALUE AXOM_CONDUIT_MAKE_VERSION_VALUE(CONDUIT_VERSION_MAJOR, CONDUIT_VERSION_MINOR, CONDUIT_VERSION_PATCH) + namespace bump = axom::bump; namespace utils = axom::bump::utilities; @@ -24,9 +29,19 @@ template struct test_mergemeshes { static void test() + { + std::vector matsetTypes{"unibuffer", "element_dominant", "material_dominant"}; + for(const auto &matsetType : matsetTypes) + { + SLIC_INFO(axom::fmt::format("test({})", matsetType)); + test(matsetType); + } + } + + static void test(const std::string &matsetType) { conduit::Node hostMesh; - create(hostMesh); + create(hostMesh, matsetType); // host->device conduit::Node deviceMesh; @@ -61,7 +76,6 @@ struct test_mergemeshes // device->host conduit::Node hostResult; utils::copy(hostResult, deviceResult); -printNode(hostResult); constexpr double tolerance = 1.e-7; conduit::Node expectedResult, info; result(expectedResult); @@ -73,7 +87,7 @@ printNode(hostResult); EXPECT_TRUE(success); } - static void create(conduit::Node &mesh) + static void create(conduit::Node &mesh, const std::string &matsetType) { const char *yaml = R"xx( domain0000: @@ -161,7 +175,7 @@ printNode(hostResult); material_map: A: 1 B: 2 - C: 3 + #C: 3 topology: mesh material_ids: [1, 1,2, 2, 1,2, 1, 1,2, 2, 1,2, 1,2] volume_fractions: [1., 0.5,0.5, 1., 0.5,0.5, 1., 0.5,0.5, 1., 0.5,0.5, 0.5,0.5] @@ -170,6 +184,68 @@ printNode(hostResult); offsets: [0, 1, 3, 4, 6, 7, 9, 10, 12] )xx"; mesh.parse(yaml); + + for(int dom = 0; dom < 2; dom++) + { + changeMatsetType(mesh[dom], matsetType); + } + } + + static void changeMatsetType(conduit::Node &domain, const std::string &matsetType) + { + // Change the material and field representations + if(matsetType == "element_dominant") + { + conduit::Node domainCopy(domain); + conduit::Node &srcMatset = domainCopy["matsets/mat"]; + conduit::Node &srcField = domainCopy["fields/zonal_mixed"]; + + domain.remove("matsets/mat"); + domain.remove("fields/zonal_mixed"); + + // These functions changed in Conduit 0.9.6 +#if AXOM_CONDUIT_VERSION_VALUE < AXOM_CONDUIT_MAKE_VERSION_VALUE(0,9,6) + conduit::blueprint::mesh::matset::to_multi_buffer_full(srcMatset, + domain["matsets/mat"]); + conduit::blueprint::mesh::field::to_multi_buffer_full(srcMatset, + srcField, + "mat", + domain["fields/zonal_mixed"]); +#else + conduit::blueprint::mesh::matset::to_multi_buffer_by_element(srcMatset, + domain["matsets/mat"]); + conduit::blueprint::mesh::field::to_multi_buffer_by_element(srcMatset, + srcField, + "mat", + domain["fields/zonal_mixed"]); +#endif + // Make sure we preserve the material_map. + if(srcMatset.has_child("material_map")) + { + domain["matsets/mat/material_map"].set(srcMatset["material_map"]); + } + } + else if(matsetType == "material_dominant") + { + conduit::Node domainCopy(domain); + conduit::Node &srcMatset = domainCopy["matsets/mat"]; + conduit::Node &srcField = domainCopy["fields/zonal_mixed"]; + + domain.remove("matsets/mat"); + domain.remove("fields/zonal_mixed"); + + conduit::blueprint::mesh::matset::to_multi_buffer_by_material(srcMatset, + domain["matsets/mat"]); + conduit::blueprint::mesh::field::to_multi_buffer_by_material(srcMatset, + srcField, + "mat", + domain["fields/zonal_mixed"]); + // Make sure we preserve the material_map. + if(srcMatset.has_child("material_map")) + { + domain["matsets/mat/material_map"].set(srcMatset["material_map"]); + } + } } static void result(conduit::Node &mesh) @@ -227,7 +303,6 @@ printNode(hostResult); }; TEST(bump_mergemeshes, mergemeshes_seq) { test_mergemeshes::test(); } -/* #if defined(AXOM_USE_OPENMP) TEST(bump_mergemeshes, mergemeshes_omp) { test_mergemeshes::test(); } #endif @@ -237,7 +312,6 @@ TEST(bump_mergemeshes, mergemeshes_cuda) { test_mergemeshes::test(); #if defined(AXOM_USE_HIP) TEST(bump_mergemeshes, mergemeshes_hip) { test_mergemeshes::test(); } #endif -*/ //------------------------------------------------------------------------------ void conduit_debug_err_handler(const std::string &s1, const std::string &s2, int i1) diff --git a/src/axom/bump/views/dispatch_material.hpp b/src/axom/bump/views/dispatch_material.hpp index 5010d29754..b309be6dfa 100644 --- a/src/axom/bump/views/dispatch_material.hpp +++ b/src/axom/bump/views/dispatch_material.hpp @@ -114,44 +114,49 @@ bool dispatch_material_multibuffer_with_values(const conduit::Node &matset, cons const conduit::Node &volume_fractions = matset.fetch_existing("volume_fractions"); if(values_object.number_of_children() > 0 && (volume_fractions.number_of_children() == values_object.number_of_children())) { - const conduit::Node &n_firstValues = values_object[0].fetch_existing("values"); - const conduit::Node &n_firstIndices = volume_fractions[0].fetch_existing("indices"); - indexNodeToArrayView(n_firstIndices, [&](auto firstIndices) { - floatNodeToArrayView(n_firstValues, [&](auto firstValues) { - using IntElement = - typename std::remove_const::type; - using FloatElement = - typename std::remove_const::type; - using IntView = axom::ArrayView; - using FloatView = axom::ArrayView; - - MultiBufferMaterialView matsetView; - - for(conduit::index_t i = 0; i < values_object.number_of_children(); i++) - { - const conduit::Node &values = values_object[i].fetch_existing("values"); - const conduit::Node &indices = volume_fractions[i].fetch_existing("indices"); - - const IntElement *indices_ptr = indices.value(); - const FloatElement *values_ptr = values.value(); - - IntView indices_view(const_cast(indices_ptr), - indices.dtype().number_of_elements()); - FloatView values_view(const_cast(values_ptr), - values.dtype().number_of_elements()); - - // Get the material number if we can. - IntElement matno = getMaterialID(matset, - volume_fractions[i].name(), - static_cast(i)); - - matsetView.add(matno, indices_view, values_view); - } - - func(matsetView); + const conduit::Node &n_firstValuesObj = values_object[0]; + const conduit::Node &n_firstVolumesObj = volume_fractions[0]; + if(n_firstValuesObj.has_child("values") && n_firstVolumesObj.has_child("indices")) + { + const conduit::Node &n_firstValues = n_firstValuesObj.fetch_existing("values"); + const conduit::Node &n_firstIndices = n_firstVolumesObj.fetch_existing("indices"); + indexNodeToArrayView(n_firstIndices, [&](auto firstIndices) { + floatNodeToArrayView(n_firstValues, [&](auto firstValues) { + using IntElement = + typename std::remove_const::type; + using FloatElement = + typename std::remove_const::type; + using IntView = axom::ArrayView; + using FloatView = axom::ArrayView; + + MultiBufferMaterialView matsetView; + + for(conduit::index_t i = 0; i < values_object.number_of_children(); i++) + { + const conduit::Node &values = values_object[i].fetch_existing("values"); + const conduit::Node &indices = volume_fractions[i].fetch_existing("indices"); + + const IntElement *indices_ptr = indices.value(); + const FloatElement *values_ptr = values.value(); + + IntView indices_view(const_cast(indices_ptr), + indices.dtype().number_of_elements()); + FloatView values_view(const_cast(values_ptr), + values.dtype().number_of_elements()); + + // Get the material number if we can. + IntElement matno = getMaterialID(matset, + volume_fractions[i].name(), + static_cast(i)); + + matsetView.add(matno, indices_view, values_view); + } + + func(matsetView); + }); }); - }); - retval = true; + retval = true; + } } } return retval; @@ -173,7 +178,8 @@ bool dispatch_material_element_dominant_with_values(const conduit::Node &matset, { bool retval = false; verify(matset, "matset"); - if(conduit::blueprint::mesh::matset::is_element_dominant(matset)) + if(conduit::blueprint::mesh::matset::is_multi_buffer(matset) && + conduit::blueprint::mesh::matset::is_element_dominant(matset)) { if(values_object.number_of_children() > 0) { @@ -224,7 +230,8 @@ bool dispatch_material_material_dominant_with_values(const conduit::Node &matset { bool retval = false; verify(matset, "matset"); - if(conduit::blueprint::mesh::matset::is_material_dominant(matset)) + if(conduit::blueprint::mesh::matset::is_multi_buffer(matset) && + conduit::blueprint::mesh::matset::is_material_dominant(matset)) { const conduit::Node &element_ids = matset.fetch_existing("element_ids"); if(values_object.number_of_children() > 0 && @@ -419,19 +426,23 @@ bool dispatch_material(const conduit::Node &matset, FuncType &&func) { bool retval = dispatch_material_unibuffer(matset, std::forward(func)); + // Multibuffer if(!retval) { - retval = - dispatch_material_multibuffer(matset, std::forward(func)); + retval = dispatch_material_element_dominant(matset, std::forward(func)); } if(!retval) { - retval = dispatch_material_element_dominant(matset, std::forward(func)); + retval = dispatch_material_material_dominant(matset, std::forward(func)); } +#if 0 + // NOTE: This one may be obsolete in Blueprint if(!retval) { - retval = dispatch_material_material_dominant(matset, std::forward(func)); + retval = + dispatch_material_multibuffer(matset, std::forward(func)); } +#endif return retval; } diff --git a/src/axom/bump/views/dispatch_material_field.hpp b/src/axom/bump/views/dispatch_material_field.hpp index 6c0e4e5b41..e0dcb37169 100644 --- a/src/axom/bump/views/dispatch_material_field.hpp +++ b/src/axom/bump/views/dispatch_material_field.hpp @@ -106,19 +106,23 @@ bool dispatch_material_field(const conduit::Node &matset, const conduit::Node &n { bool retval = dispatch_material_unibuffer_field(matset, n_field, std::forward(func)); + // Multibuffer if(!retval) { - retval = - dispatch_material_multibuffer_field(matset, n_field, std::forward(func)); + retval = dispatch_material_element_dominant_field(matset, n_field, std::forward(func)); } if(!retval) { - retval = dispatch_material_element_dominant_field(matset, n_field, std::forward(func)); + retval = dispatch_material_material_dominant_field(matset, n_field, std::forward(func)); } +#if 0 + // NOTE: This one may be obsolete in Blueprint if(!retval) { - retval = dispatch_material_material_dominant_field(matset, n_field, std::forward(func)); + retval = + dispatch_material_multibuffer_field(matset, n_field, std::forward(func)); } +#endif return retval; } From fb74f8549383f3af09f68de60404b115c3806375 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 11 May 2026 19:42:27 -0700 Subject: [PATCH 214/986] make style --- src/axom/bump/MergeMeshes.hpp | 127 +++++++++--------- .../tests/blueprint_testing_data_helpers.hpp | 5 +- src/axom/bump/tests/bump_mergemeshes.cpp | 19 ++- src/axom/bump/tests/bump_views.cpp | 44 ++++-- src/axom/bump/views/dispatch_material.hpp | 82 ++++++----- .../bump/views/dispatch_material_field.hpp | 51 +++++-- 6 files changed, 200 insertions(+), 128 deletions(-) diff --git a/src/axom/bump/MergeMeshes.hpp b/src/axom/bump/MergeMeshes.hpp index 8f1246aa8e..208ca927d7 100644 --- a/src/axom/bump/MergeMeshes.hpp +++ b/src/axom/bump/MergeMeshes.hpp @@ -1212,9 +1212,11 @@ class MergeMeshes fi.m_matset = n_field["matset"].as_string(); fi.m_have_matset_values = 1; const conduit::Node &matset_values = n_field["matset_values"]; - fi.m_dtype = matset_values.dtype().is_object() ? matset_values[0].dtype().id() : matset_values.dtype().id(); + fi.m_dtype = matset_values.dtype().is_object() ? matset_values[0].dtype().id() + : matset_values.dtype().id(); - SLIC_ERROR_IF(fi.m_association == "vertex", "Material fields with vertex centering are not supported."); + SLIC_ERROR_IF(fi.m_association == "vertex", + "Material fields with vertex centering are not supported."); } // If the field has values (all do except for some material fields), save metadata. if(n_field.has_path("values")) @@ -1250,7 +1252,8 @@ class MergeMeshes n_newField["topology"] = it->second.m_topology; if(!it->second.m_matset.empty()) { - n_newField["matset"] = it->second.m_matset; // TODO: Is this right? or, am I passing in a new matset name somewhere else in the derived class? + n_newField["matset"] = + it->second.m_matset; // TODO: Is this right? or, am I passing in a new matset name somewhere else in the derived class? } if(it->second.m_volume_dependent != InvalidVolumeDependent) { @@ -1286,7 +1289,7 @@ class MergeMeshes for(size_t ci = 0; ci < it->second.m_components.size(); ci++) { const std::string srcPath("fields/" + it->first + "/values/" + - it->second.m_components[ci]); + it->second.m_components[ci]); conduit::Node &n_comp = n_values[it->second.m_components[ci]]; n_comp.set_allocator(conduitAllocatorId); if(it->second.m_association == "element") @@ -1560,7 +1563,9 @@ class DispatchAnyMatset template void dispatchMixedField(conduit::Node &n_matset, conduit::Node &n_field, FuncType &&func) { - axom::bump::views::dispatch_material_field(n_matset, n_field, [&](auto matsetView) { func(matsetView); }); + axom::bump::views::dispatch_material_field(n_matset, n_field, [&](auto matsetView) { + func(matsetView); + }); } }; @@ -1632,7 +1637,9 @@ class DispatchTypedUnibufferMatset template void dispatchMixedField(conduit::Node &n_matset, conduit::Node &n_field, FuncType &&func) { - axom::bump::views::dispatch_material_unibuffer_field(n_matset, n_field, [&](auto matsetView) { func(matsetView); }); + axom::bump::views::dispatch_material_unibuffer_field(n_matset, n_field, [&](auto matsetView) { + func(matsetView); + }); } }; @@ -1670,7 +1677,7 @@ class MergeMeshesAndMatsets : public MergeMeshes axom::IndexType totalMatCount; int itype; int ftype; - }; + }; /*! * \brief Get the material information we'll need to merge materials. @@ -1703,7 +1710,8 @@ class MergeMeshesAndMatsets : public MergeMeshes mi.matsetName = n_matset.name(); mi.topoName = n_matset.fetch_existing("topology").as_string(); mi.elementDominant = conduit::blueprint::mesh::matset::is_element_dominant(n_matset); - mi.multiBuffer = conduit::blueprint::mesh::matset::is_multi_buffer(n_matset) && !n_matset.has_child("material_ids"); + mi.multiBuffer = conduit::blueprint::mesh::matset::is_multi_buffer(n_matset) && + !n_matset.has_child("material_ids"); auto matInfo = axom::bump::views::materials(n_matset); for(const auto &info : matInfo) { @@ -2115,9 +2123,9 @@ class MergeMeshesAndMatsets : public MergeMeshes * \param[out] fi The field information. We only fill in the dtype and component names. */ void getMixedFieldInformation(const std::vector &inputs, - const std::string &srcFieldPath, - const MaterialInfo &mi, - FieldInformation &fi) const + const std::string &srcFieldPath, + const MaterialInfo &mi, + FieldInformation &fi) const { /* Various mixed field representations. @@ -2156,7 +2164,8 @@ class MergeMeshesAndMatsets : public MergeMeshes { if(inputs[i].m_input->has_path(srcFieldMatsetValuesPath)) { - const conduit::Node &n_matset_values = inputs[i].m_input->fetch_existing(srcFieldMatsetValuesPath); + const conduit::Node &n_matset_values = + inputs[i].m_input->fetch_existing(srcFieldMatsetValuesPath); // NOTE: we only try to populate components for fields that look like vectors if(mi.multiBuffer) @@ -2238,12 +2247,16 @@ class MergeMeshesAndMatsets : public MergeMeshes FieldInformation fi; getMixedFieldInformation(inputs, srcFieldPath, mi, fi); - SLIC_ERROR_IF(fi.m_dtype == -1, axom::fmt::format("The new mixed field type for {} was not determined.", srcFieldPath)); + SLIC_ERROR_IF( + fi.m_dtype == -1, + axom::fmt::format("The new mixed field type for {} was not determined.", srcFieldPath)); SLIC_ERROR_IF(fi.m_components.size() > 0, "Vector mixed vars not supported"); // Get the Conduit node for the matset we built previously - conduit::Node *n_matset = const_cast(conduit::blueprint::mesh::utils::find_reference_node(n_field, "matset")); - SLIC_ERROR_IF(n_matset == nullptr, axom::fmt::format("The new matset {} was not found.", matsetName)); + conduit::Node *n_matset = const_cast( + conduit::blueprint::mesh::utils::find_reference_node(n_field, "matset")); + SLIC_ERROR_IF(n_matset == nullptr, + axom::fmt::format("The new matset {} was not found.", matsetName)); // Get some parts of the new unibuffer matset. conduit::Node &n_material_ids = n_matset->fetch_existing("material_ids"); @@ -2261,52 +2274,43 @@ class MergeMeshesAndMatsets : public MergeMeshes // Dispatch the mixed material we built so we can access its arrays as views. MaterialDispatch disp; auto *This = this; - disp.execute(n_material_ids, - n_sizes, - n_offsets, - n_indices, - n_matset_values, - [&](auto AXOM_UNUSED_PARAM(materialIdsView), - auto AXOM_UNUSED_PARAM(sizesView), - auto offsetsView, - auto AXOM_UNUSED_PARAM(indicesView), - auto matsetValuesView) - { - // Iterate over the inputs and copy their mixed field - axom::IndexType zOffset = 0; - for(size_t i = 0; i < inputs.size(); i++) - { - const auto nzones = This->countZones(inputs, i); - - if(inputs[i].m_input->has_child("matsets") && - inputs[i].m_input->has_path(srcFieldPath)) + disp.execute( + n_material_ids, + n_sizes, + n_offsets, + n_indices, + n_matset_values, + [&](auto AXOM_UNUSED_PARAM(materialIdsView), + auto AXOM_UNUSED_PARAM(sizesView), + auto offsetsView, + auto AXOM_UNUSED_PARAM(indicesView), + auto matsetValuesView) { + // Iterate over the inputs and copy their mixed field + axom::IndexType zOffset = 0; + for(size_t i = 0; i < inputs.size(); i++) { - // Get the source matset. - conduit::Node &n_matsets = inputs[i].m_input->fetch_existing("matsets"); - conduit::Node &n_matset = n_matsets.fetch_existing(matsetName); - // Get the source field. - conduit::Node &n_src_field = inputs[i].m_input->fetch_existing(srcFieldPath); - - // Dispatch the source mixed field. - disp.dispatchMixedField(n_matset, n_src_field, [&](auto srcMatsetView) + const auto nzones = This->countZones(inputs, i); + + if(inputs[i].m_input->has_child("matsets") && inputs[i].m_input->has_path(srcFieldPath)) { - This->mergeMixedField_copy(srcMatsetView, - matsetValuesView, - offsetsView, - nzones, - zOffset); - }); - } - else - { - This->mergeMixedField_default(matsetValuesView, - offsetsView, - nzones, - zOffset); + // Get the source matset. + conduit::Node &n_matsets = inputs[i].m_input->fetch_existing("matsets"); + conduit::Node &n_matset = n_matsets.fetch_existing(matsetName); + // Get the source field. + conduit::Node &n_src_field = inputs[i].m_input->fetch_existing(srcFieldPath); + + // Dispatch the source mixed field. + disp.dispatchMixedField(n_matset, n_src_field, [&](auto srcMatsetView) { + This->mergeMixedField_copy(srcMatsetView, matsetValuesView, offsetsView, nzones, zOffset); + }); + } + else + { + This->mergeMixedField_default(matsetValuesView, offsetsView, nzones, zOffset); + } + zOffset += nzones; } - zOffset += nzones; - } - }); + }); } } @@ -2328,8 +2332,7 @@ class MergeMeshesAndMatsets : public MergeMeshes { axom::for_all( nzones, - AXOM_LAMBDA(axom::IndexType zoneIndex) - { + AXOM_LAMBDA(axom::IndexType zoneIndex) { // Get this zone's materials. auto zoneMat = srcMatsetView.beginZone(zoneIndex); const auto nmats = zoneMat.size(); @@ -2361,13 +2364,11 @@ class MergeMeshesAndMatsets : public MergeMeshes { axom::for_all( nzones, - AXOM_LAMBDA(axom::IndexType zoneIndex) - { + AXOM_LAMBDA(axom::IndexType zoneIndex) { const auto zoneStart = offsetsView[zOffset + zoneIndex]; mixedFieldView[zoneStart] = 0; }); } - }; } // end namespace bump diff --git a/src/axom/bump/tests/blueprint_testing_data_helpers.hpp b/src/axom/bump/tests/blueprint_testing_data_helpers.hpp index 9e9a2cb8ea..f77d1dd9f3 100644 --- a/src/axom/bump/tests/blueprint_testing_data_helpers.hpp +++ b/src/axom/bump/tests/blueprint_testing_data_helpers.hpp @@ -236,7 +236,7 @@ void make_material_dominant(const std::vector &vfA, conduit::Node &matset, conduit::Node &mfield) { - std::vector svfA, svfB, svfC; // sparse arrays + std::vector svfA, svfB, svfC; // sparse arrays std::vector ziA, ziB, ziC; const size_t n = vfA.size(); for(size_t zi = 0; zi < n; zi++) @@ -300,7 +300,8 @@ void make_matset(const std::string &type, bool makeMixedField, conduit::Node &mesh) { - SLIC_ERROR_IF(cleanMats && makeMixedField, "We cannot make a mixed field when making clean materials."); + SLIC_ERROR_IF(cleanMats && makeMixedField, + "We cannot make a mixed field when making clean materials."); constexpr int sampling = 10; int midx = sampling * dims[0] / 2; int midy = sampling * dims[1] / 2; diff --git a/src/axom/bump/tests/bump_mergemeshes.cpp b/src/axom/bump/tests/bump_mergemeshes.cpp index 263bb1bda9..9ecfd7a34a 100644 --- a/src/axom/bump/tests/bump_mergemeshes.cpp +++ b/src/axom/bump/tests/bump_mergemeshes.cpp @@ -18,8 +18,10 @@ // NOTE: Conduit 0.9.6 and later has macros but Axom is not quite on that version yet. #include "conduit/conduit_config.h" -#define AXOM_CONDUIT_MAKE_VERSION_VALUE(MAJOR, MINOR, PATCH) (((MAJOR)*10000) + ((MINOR)*100) + (PATCH)) -#define AXOM_CONDUIT_VERSION_VALUE AXOM_CONDUIT_MAKE_VERSION_VALUE(CONDUIT_VERSION_MAJOR, CONDUIT_VERSION_MINOR, CONDUIT_VERSION_PATCH) +#define AXOM_CONDUIT_MAKE_VERSION_VALUE(MAJOR, MINOR, PATCH) \ + (((MAJOR) * 10000) + ((MINOR) * 100) + (PATCH)) +#define AXOM_CONDUIT_VERSION_VALUE \ + AXOM_CONDUIT_MAKE_VERSION_VALUE(CONDUIT_VERSION_MAJOR, CONDUIT_VERSION_MINOR, CONDUIT_VERSION_PATCH) namespace bump = axom::bump; namespace utils = axom::bump::utilities; @@ -30,7 +32,7 @@ struct test_mergemeshes { static void test() { - std::vector matsetTypes{"unibuffer", "element_dominant", "material_dominant"}; + std::vector matsetTypes {"unibuffer", "element_dominant", "material_dominant"}; for(const auto &matsetType : matsetTypes) { SLIC_INFO(axom::fmt::format("test({})", matsetType)); @@ -204,16 +206,14 @@ struct test_mergemeshes domain.remove("fields/zonal_mixed"); // These functions changed in Conduit 0.9.6 -#if AXOM_CONDUIT_VERSION_VALUE < AXOM_CONDUIT_MAKE_VERSION_VALUE(0,9,6) - conduit::blueprint::mesh::matset::to_multi_buffer_full(srcMatset, - domain["matsets/mat"]); +#if AXOM_CONDUIT_VERSION_VALUE < AXOM_CONDUIT_MAKE_VERSION_VALUE(0, 9, 6) + conduit::blueprint::mesh::matset::to_multi_buffer_full(srcMatset, domain["matsets/mat"]); conduit::blueprint::mesh::field::to_multi_buffer_full(srcMatset, srcField, "mat", domain["fields/zonal_mixed"]); #else - conduit::blueprint::mesh::matset::to_multi_buffer_by_element(srcMatset, - domain["matsets/mat"]); + conduit::blueprint::mesh::matset::to_multi_buffer_by_element(srcMatset, domain["matsets/mat"]); conduit::blueprint::mesh::field::to_multi_buffer_by_element(srcMatset, srcField, "mat", @@ -234,8 +234,7 @@ struct test_mergemeshes domain.remove("matsets/mat"); domain.remove("fields/zonal_mixed"); - conduit::blueprint::mesh::matset::to_multi_buffer_by_material(srcMatset, - domain["matsets/mat"]); + conduit::blueprint::mesh::matset::to_multi_buffer_by_material(srcMatset, domain["matsets/mat"]); conduit::blueprint::mesh::field::to_multi_buffer_by_material(srcMatset, srcField, "mat", diff --git a/src/axom/bump/tests/bump_views.cpp b/src/axom/bump/tests/bump_views.cpp index 6a06f5a337..53192abfee 100644 --- a/src/axom/bump/tests/bump_views.cpp +++ b/src/axom/bump/tests/bump_views.cpp @@ -600,7 +600,12 @@ struct test_braid2d_mat const bool makeMixedField = true; conduit::Node hostMesh, deviceMesh; axom::blueprint::testing::data::braid(type, dims, hostMesh); - axom::blueprint::testing::data::make_matset(mattype, "mesh", zoneDims, cleanMats, makeMixedField, hostMesh); + axom::blueprint::testing::data::make_matset(mattype, + "mesh", + zoneDims, + cleanMats, + makeMixedField, + hostMesh); utils::copy(deviceMesh, hostMesh); TestApp.saveVisualization(name + "_orig", hostMesh); @@ -621,45 +626,64 @@ struct test_braid2d_mat test_matsetview(nzones, matsetView, allocatorID); // Test mixed field. - const auto mixedFieldView = axom::bump::views::make_unibuffer_matset::mixedFieldView(deviceMesh["matsets/mat"], deviceMesh["fields/mixed"]); + const auto mixedFieldView = + axom::bump::views::make_unibuffer_matset::mixedFieldView( + deviceMesh["matsets/mat"], + deviceMesh["fields/mixed"]); SLIC_INFO("unibuffer: mixedFieldView"); test_matsetview(nzones, mixedFieldView, allocatorID); } else if(mattype == "multibuffer") { - axom::bump::views::dispatch_material_multibuffer( - deviceMesh["matsets/mat"], - [&](auto matsetView) { SLIC_INFO("multibuffer: matsetView"); test_matsetview(nzones, matsetView, allocatorID); }); + axom::bump::views::dispatch_material_multibuffer(deviceMesh["matsets/mat"], [&](auto matsetView) { + SLIC_INFO("multibuffer: matsetView"); + test_matsetview(nzones, matsetView, allocatorID); + }); // Test mixed field. axom::bump::views::dispatch_material_multibuffer_field( deviceMesh["matsets/mat"], deviceMesh["fields/mixed"], - [&](auto mixedFieldView) { SLIC_INFO("multibuffer: mixedFieldView"); test_matsetview(nzones, mixedFieldView, allocatorID); }); + [&](auto mixedFieldView) { + SLIC_INFO("multibuffer: mixedFieldView"); + test_matsetview(nzones, mixedFieldView, allocatorID); + }); } else if(mattype == "element_dominant") { axom::bump::views::dispatch_material_element_dominant( deviceMesh["matsets/mat"], - [&](auto matsetView) { SLIC_INFO("element_dominant: matsetView"); test_matsetview(nzones, matsetView, allocatorID); }); + [&](auto matsetView) { + SLIC_INFO("element_dominant: matsetView"); + test_matsetview(nzones, matsetView, allocatorID); + }); // Test mixed field. axom::bump::views::dispatch_material_element_dominant_field( deviceMesh["matsets/mat"], deviceMesh["fields/mixed"], - [&](auto mixedFieldView) { SLIC_INFO("element_dominant: mixedFieldView"); test_matsetview(nzones, mixedFieldView, allocatorID); }); + [&](auto mixedFieldView) { + SLIC_INFO("element_dominant: mixedFieldView"); + test_matsetview(nzones, mixedFieldView, allocatorID); + }); } else if(mattype == "material_dominant") { axom::bump::views::dispatch_material_material_dominant( deviceMesh["matsets/mat"], - [&](auto matsetView) { SLIC_INFO("material_dominant: matsetView"); test_matsetview(nzones, matsetView, allocatorID); }); + [&](auto matsetView) { + SLIC_INFO("material_dominant: matsetView"); + test_matsetview(nzones, matsetView, allocatorID); + }); // Test mixed field. axom::bump::views::dispatch_material_material_dominant_field( deviceMesh["matsets/mat"], deviceMesh["fields/mixed"], - [&](auto mixedFieldView) { SLIC_INFO("material_dominant: mixedFieldView"); test_matsetview(nzones, mixedFieldView, allocatorID); }); + [&](auto mixedFieldView) { + SLIC_INFO("material_dominant: mixedFieldView"); + test_matsetview(nzones, mixedFieldView, allocatorID); + }); } } diff --git a/src/axom/bump/views/dispatch_material.hpp b/src/axom/bump/views/dispatch_material.hpp index b309be6dfa..033d0bc0d3 100644 --- a/src/axom/bump/views/dispatch_material.hpp +++ b/src/axom/bump/views/dispatch_material.hpp @@ -25,7 +25,7 @@ namespace detail inline void verifyMixedField(const conduit::Node &n_field) { SLIC_ERROR_IF(!n_field.has_path("matset_values"), - "The mixed field does not contain matset_values"); + "The mixed field does not contain matset_values"); } /*! @@ -41,27 +41,28 @@ inline void verifyMixedField(const conduit::Node &n_field) * \return true if the dispatch worked, false otherwise. */ template -bool dispatch_material_unibuffer_with_values(const conduit::Node &matset, const conduit::Node &values, FuncType &&func) +bool dispatch_material_unibuffer_with_values(const conduit::Node &matset, + const conduit::Node &values, + FuncType &&func) { bool retval = false; verify(matset, "matset"); if(conduit::blueprint::mesh::matset::is_uni_buffer(matset)) { - indexNodeToArrayViewSame( - matset["material_ids"], - matset["sizes"], - matset["offsets"], - matset["indices"], - [&](auto material_ids, auto sizes, auto offsets, auto indices) { - floatNodeToArrayView(values, [&](auto typedValues) { - using IndexType = typename decltype(material_ids)::value_type; - using FloatType = typename decltype(typedValues)::value_type; - - UnibufferMaterialView matsetView; - matsetView.set(material_ids, typedValues, sizes, offsets, indices); - func(matsetView); - }); - }); + indexNodeToArrayViewSame(matset["material_ids"], + matset["sizes"], + matset["offsets"], + matset["indices"], + [&](auto material_ids, auto sizes, auto offsets, auto indices) { + floatNodeToArrayView(values, [&](auto typedValues) { + using IndexType = typename decltype(material_ids)::value_type; + using FloatType = typename decltype(typedValues)::value_type; + + UnibufferMaterialView matsetView; + matsetView.set(material_ids, typedValues, sizes, offsets, indices); + func(matsetView); + }); + }); retval = true; } return retval; @@ -105,14 +106,17 @@ IntElement getMaterialID(const conduit::Node &matset, * \return true if the dispatch worked, false otherwise. */ template -bool dispatch_material_multibuffer_with_values(const conduit::Node &matset, const conduit::Node &values_object, FuncType &&func) +bool dispatch_material_multibuffer_with_values(const conduit::Node &matset, + const conduit::Node &values_object, + FuncType &&func) { bool retval = false; verify(matset, "matset"); if(conduit::blueprint::mesh::matset::is_multi_buffer(matset)) { const conduit::Node &volume_fractions = matset.fetch_existing("volume_fractions"); - if(values_object.number_of_children() > 0 && (volume_fractions.number_of_children() == values_object.number_of_children())) + if(values_object.number_of_children() > 0 && + (volume_fractions.number_of_children() == values_object.number_of_children())) { const conduit::Node &n_firstValuesObj = values_object[0]; const conduit::Node &n_firstVolumesObj = volume_fractions[0]; @@ -174,7 +178,9 @@ bool dispatch_material_multibuffer_with_values(const conduit::Node &matset, cons * \return true if the dispatch worked, false otherwise. */ template -bool dispatch_material_element_dominant_with_values(const conduit::Node &matset, const conduit::Node &values_object, FuncType &&func) +bool dispatch_material_element_dominant_with_values(const conduit::Node &matset, + const conduit::Node &values_object, + FuncType &&func) { bool retval = false; verify(matset, "matset"); @@ -226,7 +232,9 @@ bool dispatch_material_element_dominant_with_values(const conduit::Node &matset, * \return true if the dispatch worked, false otherwise. */ template -bool dispatch_material_material_dominant_with_values(const conduit::Node &matset, const conduit::Node &values_object, FuncType &&func) +bool dispatch_material_material_dominant_with_values(const conduit::Node &matset, + const conduit::Node &values_object, + FuncType &&func) { bool retval = false; verify(matset, "matset"); @@ -280,7 +288,7 @@ bool dispatch_material_material_dominant_with_values(const conduit::Node &matset return retval; } -} // end namespace detail +} // end namespace detail /*! * \brief Make a unibuffer matset view from a Conduit node. @@ -321,8 +329,7 @@ struct make_unibuffer_matset * * \return A UnibufferMaterialView. */ - static MatsetView mixedFieldView(const conduit::Node &n_matset, - const conduit::Node &n_field) + static MatsetView mixedFieldView(const conduit::Node &n_matset, const conduit::Node &n_field) { namespace utils = axom::bump::utilities; verify(n_matset, "matset"); @@ -353,8 +360,10 @@ template bool dispatch_material_unibuffer(const conduit::Node &matset, FuncType &&func) { verify(matset, "matset"); - return detail::dispatch_material_unibuffer_with_values(matset, - matset["volume_fractions"], std::forward(func)); + return detail::dispatch_material_unibuffer_with_values( + matset, + matset["volume_fractions"], + std::forward(func)); } /*! @@ -373,7 +382,10 @@ template bool dispatch_material_multibuffer(const conduit::Node &matset, FuncType &&func) { verify(matset, "matset"); - return detail::dispatch_material_multibuffer_with_values(matset, matset["volume_fractions"], std::forward(func)); + return detail::dispatch_material_multibuffer_with_values( + matset, + matset["volume_fractions"], + std::forward(func)); } /*! @@ -391,7 +403,9 @@ template bool dispatch_material_element_dominant(const conduit::Node &matset, FuncType &&func) { verify(matset, "matset"); - return detail::dispatch_material_element_dominant_with_values(matset, matset["volume_fractions"], std::forward(func)); + return detail::dispatch_material_element_dominant_with_values(matset, + matset["volume_fractions"], + std::forward(func)); } /*! @@ -408,7 +422,10 @@ template bool dispatch_material_material_dominant(const conduit::Node &matset, FuncType &&func) { verify(matset, "matset"); - return detail::dispatch_material_material_dominant_with_values(matset, matset["volume_fractions"], std::forward(func)); + return detail::dispatch_material_material_dominant_with_values( + matset, + matset["volume_fractions"], + std::forward(func)); } /*! @@ -429,11 +446,14 @@ bool dispatch_material(const conduit::Node &matset, FuncType &&func) // Multibuffer if(!retval) { - retval = dispatch_material_element_dominant(matset, std::forward(func)); + retval = dispatch_material_element_dominant(matset, + std::forward(func)); } if(!retval) { - retval = dispatch_material_material_dominant(matset, std::forward(func)); + retval = + dispatch_material_material_dominant(matset, + std::forward(func)); } #if 0 // NOTE: This one may be obsolete in Blueprint diff --git a/src/axom/bump/views/dispatch_material_field.hpp b/src/axom/bump/views/dispatch_material_field.hpp index e0dcb37169..cf07f347fb 100644 --- a/src/axom/bump/views/dispatch_material_field.hpp +++ b/src/axom/bump/views/dispatch_material_field.hpp @@ -26,12 +26,16 @@ namespace views * \param func The function/lambda that will operate on the matset view. */ template -bool dispatch_material_unibuffer_field(const conduit::Node &matset, const conduit::Node &n_field, FuncType &&func) +bool dispatch_material_unibuffer_field(const conduit::Node &matset, + const conduit::Node &n_field, + FuncType &&func) { verify(matset, "matset"); detail::verifyMixedField(n_field); - return detail::dispatch_material_unibuffer_with_values(matset, - n_field["matset_values"], std::forward(func)); + return detail::dispatch_material_unibuffer_with_values( + matset, + n_field["matset_values"], + std::forward(func)); } /*! @@ -47,11 +51,16 @@ bool dispatch_material_unibuffer_field(const conduit::Node &matset, const condui * \return true if the dispatch worked, false otherwise. */ template -bool dispatch_material_multibuffer_field(const conduit::Node &matset, const conduit::Node &n_field, FuncType &&func) +bool dispatch_material_multibuffer_field(const conduit::Node &matset, + const conduit::Node &n_field, + FuncType &&func) { verify(matset, "matset"); detail::verifyMixedField(n_field); - return detail::dispatch_material_multibuffer_with_values(matset, n_field["matset_values"], std::forward(func)); + return detail::dispatch_material_multibuffer_with_values( + matset, + n_field["matset_values"], + std::forward(func)); } /*! @@ -65,11 +74,16 @@ bool dispatch_material_multibuffer_field(const conduit::Node &matset, const cond * \param func The function/lambda that will operate on the matset view. */ template -bool dispatch_material_element_dominant_field(const conduit::Node &matset, const conduit::Node n_field, FuncType &&func) +bool dispatch_material_element_dominant_field(const conduit::Node &matset, + const conduit::Node n_field, + FuncType &&func) { verify(matset, "matset"); detail::verifyMixedField(n_field); - return detail::dispatch_material_element_dominant_with_values(matset, n_field["matset_values"], std::forward(func)); + return detail::dispatch_material_element_dominant_with_values( + matset, + n_field["matset_values"], + std::forward(func)); } /*! @@ -83,11 +97,16 @@ bool dispatch_material_element_dominant_field(const conduit::Node &matset, const * \param func The function/lambda that will operate on the matset view. */ template -bool dispatch_material_material_dominant_field(const conduit::Node &matset, const conduit::Node n_field, FuncType &&func) +bool dispatch_material_material_dominant_field(const conduit::Node &matset, + const conduit::Node n_field, + FuncType &&func) { verify(matset, "matset"); detail::verifyMixedField(n_field); - return detail::dispatch_material_material_dominant_with_values(matset, n_field["matset_values"], std::forward(func)); + return detail::dispatch_material_material_dominant_with_values( + matset, + n_field["matset_values"], + std::forward(func)); } /*! @@ -105,15 +124,23 @@ template bool dispatch_material_field(const conduit::Node &matset, const conduit::Node &n_field, FuncType &&func) { bool retval = - dispatch_material_unibuffer_field(matset, n_field, std::forward(func)); + dispatch_material_unibuffer_field(matset, + n_field, + std::forward(func)); // Multibuffer if(!retval) { - retval = dispatch_material_element_dominant_field(matset, n_field, std::forward(func)); + retval = + dispatch_material_element_dominant_field(matset, + n_field, + std::forward(func)); } if(!retval) { - retval = dispatch_material_material_dominant_field(matset, n_field, std::forward(func)); + retval = dispatch_material_material_dominant_field( + matset, + n_field, + std::forward(func)); } #if 0 // NOTE: This one may be obsolete in Blueprint From 4c880bf944685d40ab4250c845f44ba07a97e52d Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 12 May 2026 12:29:42 -0700 Subject: [PATCH 215/986] make style --- src/axom/mir/tests/mir_elvira2d.cpp | 9 +++++++-- src/axom/mir/tests/mir_equiz2d.cpp | 9 +++++++-- src/axom/mir/tests/mir_equiz3d.cpp | 9 +++++++-- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/axom/mir/tests/mir_elvira2d.cpp b/src/axom/mir/tests/mir_elvira2d.cpp index 2b225ca2e0..dc020d4779 100644 --- a/src/axom/mir/tests/mir_elvira2d.cpp +++ b/src/axom/mir/tests/mir_elvira2d.cpp @@ -52,8 +52,13 @@ struct braid2d_mat_test axom::StackArray dims {10, 10}; axom::StackArray zoneDims {dims[0] - 1, dims[1] - 1}; axom::blueprint::testing::data::braid(type, dims, n_mesh); - const bool makeMixedField = false; // for now - axom::blueprint::testing::data::make_matset(mattype, "mesh", zoneDims, cleanMats, makeMixedField, n_mesh); + const bool makeMixedField = false; // for now + axom::blueprint::testing::data::make_matset(mattype, + "mesh", + zoneDims, + cleanMats, + makeMixedField, + n_mesh); } // Select a chunk of clean and mixed zones. diff --git a/src/axom/mir/tests/mir_equiz2d.cpp b/src/axom/mir/tests/mir_equiz2d.cpp index 0bc7d1a411..83fffa2c45 100644 --- a/src/axom/mir/tests/mir_equiz2d.cpp +++ b/src/axom/mir/tests/mir_equiz2d.cpp @@ -69,8 +69,13 @@ void braid2d_mat_test(const std::string &type, const std::string domainName = axom::fmt::format("domain_{:07}", dom); conduit::Node &hostDomain = (nDomains > 1) ? hostMesh[domainName] : hostMesh; axom::blueprint::testing::data::braid(type, dims, hostDomain); - const bool makeMixedField = false; // for now - axom::blueprint::testing::data::make_matset(mattype, "mesh", zoneDims, cleanMats, makeMixedField, hostDomain); + const bool makeMixedField = false; // for now + axom::blueprint::testing::data::make_matset(mattype, + "mesh", + zoneDims, + cleanMats, + makeMixedField, + hostDomain); TestApp.saveVisualization(name + "_orig", hostDomain); } diff --git a/src/axom/mir/tests/mir_equiz3d.cpp b/src/axom/mir/tests/mir_equiz3d.cpp index ad5a97800c..1dff31f326 100644 --- a/src/axom/mir/tests/mir_equiz3d.cpp +++ b/src/axom/mir/tests/mir_equiz3d.cpp @@ -33,8 +33,13 @@ void braid3d_mat_test(const std::string &type, const std::string &mattype, const const bool cleanMats = false; conduit::Node hostMesh, deviceMesh; axom::blueprint::testing::data::braid(type, dims, hostMesh); - const bool makeMixedField = false; // for now - axom::blueprint::testing::data::make_matset(mattype, "mesh", zoneDims, cleanMats, makeMixedField, hostMesh); + const bool makeMixedField = false; // for now + axom::blueprint::testing::data::make_matset(mattype, + "mesh", + zoneDims, + cleanMats, + makeMixedField, + hostMesh); utils::copy(deviceMesh, hostMesh); TestApp.saveVisualization(name + "_orig", hostMesh); From c3c643cfa0822d0c7c1b73e06bc87fe51b7a5b19 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 12 May 2026 12:30:29 -0700 Subject: [PATCH 216/986] Improved support for mixed fields and testing --- src/axom/bump/MergeMeshes.hpp | 33 +--- src/axom/bump/tests/bump_mergemeshes.cpp | 192 +++++++++++++++++++++-- 2 files changed, 183 insertions(+), 42 deletions(-) diff --git a/src/axom/bump/MergeMeshes.hpp b/src/axom/bump/MergeMeshes.hpp index 208ca927d7..92fd69cdb9 100644 --- a/src/axom/bump/MergeMeshes.hpp +++ b/src/axom/bump/MergeMeshes.hpp @@ -1252,8 +1252,7 @@ class MergeMeshes n_newField["topology"] = it->second.m_topology; if(!it->second.m_matset.empty()) { - n_newField["matset"] = - it->second.m_matset; // TODO: Is this right? or, am I passing in a new matset name somewhere else in the derived class? + n_newField["matset"] = it->second.m_matset; } if(it->second.m_volume_dependent != InvalidVolumeDependent) { @@ -2127,34 +2126,6 @@ class MergeMeshesAndMatsets : public MergeMeshes const MaterialInfo &mi, FieldInformation &fi) const { - /* Various mixed field representations. - - # unibuffer - scalar - mat: - matset_values: [] - - # unibuffer - vector - mat: - matset_values: - x: [] - y: [] - - # multibuffer - scalar - mat: - matset_values: - matA: [] - matB: [] - - # multibuffer - vector - mat: - matset_values: - matA: - x: [] - y: [] - matB: - x: [] - y: [] - */ fi.m_dtype = -1; fi.m_components.clear(); @@ -2264,8 +2235,6 @@ class MergeMeshesAndMatsets : public MergeMeshes conduit::Node &n_offsets = n_matset->fetch_existing("offsets"); conduit::Node &n_indices = n_matset->fetch_existing("indices"); - // TODO: handle multiple components. - // Allocate the new mixed field. conduit::Node &n_matset_values = n_field["matset_values"]; n_matset_values.set_allocator(conduitAllocatorId); diff --git a/src/axom/bump/tests/bump_mergemeshes.cpp b/src/axom/bump/tests/bump_mergemeshes.cpp index 9ecfd7a34a..3325de83f1 100644 --- a/src/axom/bump/tests/bump_mergemeshes.cpp +++ b/src/axom/bump/tests/bump_mergemeshes.cpp @@ -13,6 +13,8 @@ #include "axom/bump/tests/blueprint_testing_helpers.hpp" #include "axom/bump/tests/blueprint_testing_data_helpers.hpp" +#include + #include #include @@ -26,6 +28,21 @@ namespace bump = axom::bump; namespace utils = axom::bump::utilities; +//#define AXOM_DEBUG_MERGE_MESHES_TEST +#ifdef AXOM_DEBUG_MERGE_MESHES_TEST +void saveMesh(const conduit::Node &n_mesh, const std::string &fileRoot) +{ +#ifdef CONDUIT_RELAY_IO_HDF5_ENABLED + const std::string protocol("hdf5"); + conduit::relay::io::save(n_mesh, fileRoot + ".yaml", "yaml"); +#else + const std::string protocol("yaml"); +#endif + // These save with a ".root" extension + conduit::relay::io::blueprint::save_mesh(n_mesh, fileRoot, protocol); +} +#endif + //------------------------------------------------------------------------------ template struct test_mergemeshes @@ -35,15 +52,22 @@ struct test_mergemeshes std::vector matsetTypes {"unibuffer", "element_dominant", "material_dominant"}; for(const auto &matsetType : matsetTypes) { - SLIC_INFO(axom::fmt::format("test({})", matsetType)); - test(matsetType); + for(int matflags = 3; matflags >= 1; matflags--) + { + SLIC_INFO(axom::fmt::format("test({}, {})", matsetType, matflags)); + test(matsetType, matflags); + } } } - static void test(const std::string &matsetType) + static void test(const std::string &matsetType, int matflags) { conduit::Node hostMesh; - create(hostMesh, matsetType); + create(hostMesh, matsetType, matflags); +#ifdef AXOM_DEBUG_MERGE_MESHES_TEST + const auto preMergeFilename = axom::fmt::format("preMerge_{}_{}", matflags, matsetType); + saveMesh(hostMesh, preMergeFilename); +#endif // host->device conduit::Node deviceMesh; @@ -78,18 +102,31 @@ struct test_mergemeshes // device->host conduit::Node hostResult; utils::copy(hostResult, deviceResult); +#ifdef AXOM_DEBUG_MERGE_MESHES_TEST + const auto postMergeFilename = axom::fmt::format("postMerge_{}_{}", matflags, matsetType); + saveMesh(hostResult, postMergeFilename); +#endif constexpr double tolerance = 1.e-7; conduit::Node expectedResult, info; - result(expectedResult); - bool success = compareConduit(expectedResult, hostResult, tolerance, info); + result(expectedResult, matflags); + bool success = false; + try + { + success = compareConduit(expectedResult, hostResult, tolerance, info); + } + catch(const conduit::Error &e) + { + e.print(); + } if(!success) { info.print(); + printNode(hostResult); } EXPECT_TRUE(success); } - static void create(conduit::Node &mesh, const std::string &matsetType) + static void create(conduit::Node &mesh, const std::string &matsetType, int matflags) { const char *yaml = R"xx( domain0000: @@ -191,6 +228,21 @@ struct test_mergemeshes { changeMatsetType(mesh[dom], matsetType); } + applyMatFlags(mesh, matflags); + } + + /// Remove mixed field and matset on domains according to matflags. This tests merging domains that are missing materials/fields. + static void applyMatFlags(conduit::Node &mesh, int matflags) + { + for(int dom = 0; dom < 2; dom++) + { + if(!axom::utilities::bitIsSet(matflags, dom)) + { + conduit::Node &domain = mesh[dom]; + domain["fields"].remove("zonal_mixed"); + domain.remove("matsets"); + } + } } static void changeMatsetType(conduit::Node &domain, const std::string &matsetType) @@ -247,9 +299,13 @@ struct test_mergemeshes } } - static void result(conduit::Node &mesh) + static void result(conduit::Node &mesh, int matflags) { - const char *yaml = R"xx( + // NOTE: We pass back different baselines for different matflags. The fields and matset change. + // It is simpler to just have a totally separate baseline to parse. + + // Result for matflags=3 - both input domains had the material and the mixed field. + const char *yaml3 = R"xx( coordsets: coords: type: "explicit" @@ -297,7 +353,123 @@ struct test_mergemeshes values: [100.1, 101.1, 102.1, 103.25, 104.25, 105.25, 200.1, 201.15, 202.3, 203.15, 204.1, 205.15, 206.2, 207.15, 208.15] matset_values: [100.1, 101.1, 102.1, 103.2, 103.3, 104.2, 104.3, 105.2, 105.3, 200.1, 201.1, 201.2, 202.3, 203.1, 203.2, 204.1, 205.1, 205.2, 206.2, 207.1, 207.2, 208.1, 208.2] )xx"; - mesh.parse(yaml); + + // Result for matflags=2 - domain 0 lacked the material and domain so we get default values where domain 0's data would be. + const char *yaml2 = R"xx( +coordsets: + coords: + type: "explicit" + values: + x: [0.0, 1.0, 2.0, 3.0, 0.0, 1.0, 2.0, 3.0, 0.0, 1.0, 2.0, 3.0, 0.0, 1.0, 2.0, 3.0, 1.5, 1.5] + y: [0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 2.0, 3.0, 3.0, 3.0, 3.0, 0.5, 1.5] +topologies: + mesh: + type: "unstructured" + coordset: "coords" + elements: + connectivity: [0, 1, 5, 4, 4, 5, 9, 8, 8, 9, 13, 12, 2, 3, 7, 6, 6, 7, 11, 10, 10, 11, 15, 14, 1, 16, 5, 1, 2, 16, 2, 6, 16, 16, 6, 5, 5, 17, 9, 5, 6, 17, 6, 10, 17, 10, 9, 17, 9, 10, 14, 13] + sizes: [4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 4] + offsets: [0, 4, 8, 12, 16, 20, 24, 27, 30, 33, 36, 39, 42, 45, 48] + shape: "mixed" + shape_map: + quad: 3 + tri: 2 + shapes: [3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 3] +matsets: + mat: + topology: "mesh" + volume_fractions: [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.5, 0.5, 1.0, 0.5, 0.5, 1.0, 0.5, 0.5, 1.0, 0.5, 0.5, 0.5, 0.5] + material_ids: [2, 2, 2, 2, 2, 2, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1] + sizes: [1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 2] + offsets: [0, 1, 2, 3, 4, 5, 6, 7, 9, 10, 12, 13, 15, 16, 18] + indices: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] + material_map: + A: 0 + B: 1 + default: 2 +fields: + nodal: + association: "vertex" + topology: "mesh" + values: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2] + zonal: + association: "element" + topology: "mesh" + values: [0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 6, 7, 8] + zonal_mixed: + association: "element" + topology: "mesh" + matset: "mat" + values: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 200.1, 201.15, 202.3, 203.15, 204.1, 205.15, 206.2, 207.15, 208.15] + matset_values: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 200.1, 201.1, 201.2, 202.3, 203.1, 203.2, 204.1, 205.1, 205.2, 206.2, 207.1, 207.2, 208.1, 208.2] +)xx"; + + // Result for matflags=1 - domain 1 lacked the material and domain so we get default values where domain 1's data would be. + const char *yaml1 = R"xx( +coordsets: + coords: + type: "explicit" + values: + x: [0.0, 1.0, 2.0, 3.0, 0.0, 1.0, 2.0, 3.0, 0.0, 1.0, 2.0, 3.0, 0.0, 1.0, 2.0, 3.0, 1.5, 1.5] + y: [0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 2.0, 3.0, 3.0, 3.0, 3.0, 0.5, 1.5] +topologies: + mesh: + type: "unstructured" + coordset: "coords" + elements: + connectivity: [0, 1, 5, 4, 4, 5, 9, 8, 8, 9, 13, 12, 2, 3, 7, 6, 6, 7, 11, 10, 10, 11, 15, 14, 1, 16, 5, 1, 2, 16, 2, 6, 16, 16, 6, 5, 5, 17, 9, 5, 6, 17, 6, 10, 17, 10, 9, 17, 9, 10, 14, 13] + sizes: [4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 4] + offsets: [0, 4, 8, 12, 16, 20, 24, 27, 30, 33, 36, 39, 42, 45, 48] + shape: "mixed" + shape_map: + quad: 3 + tri: 2 + shapes: [3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 3] +matsets: + mat: + topology: "mesh" + volume_fractions: [1.0, 1.0, 1.0, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0] + material_ids: [0, 0, 0, 1, 2, 1, 2, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3] + sizes: [1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1] + offsets: [0, 1, 2, 3, 5, 7, 9, 10, 11, 12, 13, 14, 15, 16, 17] + indices: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17] + material_map: + A: 0 + B: 1 + C: 2 + default: 3 +fields: + nodal: + association: "vertex" + topology: "mesh" + values: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2] + zonal: + association: "element" + topology: "mesh" + values: [0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 6, 7, 8] + zonal_mixed: + association: "element" + topology: "mesh" + matset: "mat" + values: [100.1, 101.1, 102.1, 103.25, 104.25, 105.25, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + matset_values: [100.1, 101.1, 102.1, 103.2, 103.3, 104.2, 104.3, 105.2, 105.3, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] +)xx"; + + switch(matflags) + { + case 3: + mesh.parse(yaml3); + break; + case 2: + mesh.parse(yaml2); + break; + case 1: + mesh.parse(yaml1); + break; + default: + SLIC_ERROR("Unsupported matflags value."); + break; + } } }; From 3372d8abe0128ea892dc444abb284c1167bbc2cc Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 12 May 2026 12:30:52 -0700 Subject: [PATCH 217/986] Re-enable some material cases. --- src/axom/bump/views/dispatch_material.hpp | 2 -- src/axom/bump/views/dispatch_material_field.hpp | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/axom/bump/views/dispatch_material.hpp b/src/axom/bump/views/dispatch_material.hpp index 033d0bc0d3..b8509881f0 100644 --- a/src/axom/bump/views/dispatch_material.hpp +++ b/src/axom/bump/views/dispatch_material.hpp @@ -455,14 +455,12 @@ bool dispatch_material(const conduit::Node &matset, FuncType &&func) dispatch_material_material_dominant(matset, std::forward(func)); } -#if 0 // NOTE: This one may be obsolete in Blueprint if(!retval) { retval = dispatch_material_multibuffer(matset, std::forward(func)); } -#endif return retval; } diff --git a/src/axom/bump/views/dispatch_material_field.hpp b/src/axom/bump/views/dispatch_material_field.hpp index cf07f347fb..0b1f571056 100644 --- a/src/axom/bump/views/dispatch_material_field.hpp +++ b/src/axom/bump/views/dispatch_material_field.hpp @@ -142,14 +142,12 @@ bool dispatch_material_field(const conduit::Node &matset, const conduit::Node &n n_field, std::forward(func)); } -#if 0 // NOTE: This one may be obsolete in Blueprint if(!retval) { retval = dispatch_material_multibuffer_field(matset, n_field, std::forward(func)); } -#endif return retval; } From 59f3be5eddaa0d0422033954ccbb2be919483dd9 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 12 May 2026 12:31:11 -0700 Subject: [PATCH 218/986] Add some documentation about mixed fields in sphinx. --- src/axom/bump/docs/sphinx/bump_views.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/axom/bump/docs/sphinx/bump_views.rst b/src/axom/bump/docs/sphinx/bump_views.rst index 9bd68f515d..ebb7b6fbff 100644 --- a/src/axom/bump/docs/sphinx/bump_views.rst +++ b/src/axom/bump/docs/sphinx/bump_views.rst @@ -175,6 +175,13 @@ methods allow algorithms to query the list of materials for each zone. :end-before: _bump_views_matsetview_end :language: C++ +Mixed (or material-dependent) fields are also supported using BUMP's material views. This is done +because these fields' data are arranged in the same manner as their related matset, leading to +multiple representations. The material views are initialized normally from matset data, except +field values from the field's ``matset_values`` node are used instead of the matset's +``volume_fractions`` node. The ``axom::bump::views::dispatch_material_field()`` function can be +used to simplify handling mixed fields. + ---------- Dispatch ---------- From 0beca85ef530a70005d1e4919a300188d3cd0459 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 12 May 2026 12:31:23 -0700 Subject: [PATCH 219/986] Release notes. --- RELEASE-NOTES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index f656847553..d5d482089f 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -27,6 +27,8 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ - Quest: Adds support for reading mfem files with variable order NURBS curves (requires mfem>4.9). - Quest: Adds OMP support for fast GWN methods for STL/Triangulated STEP input and linearized NURBS Curve input. - Klee: Adds an optional "center" parameter in scale operators that permits scaling relative to a custom center point. +- Bump: The `MergeMeshes` class was enhanced so it supports material-dependent/mixed Blueprint fields that are "element-associated". These fields contain per-material values for the materials in a zone. +- Bump: Added `axom::bump::views::dispatch_material_field()` function (and related functions) for creating a material view of a material-dependent or mixed field. ### Removed From 8a26411dc7fa676f4010283e80626dae337369a3 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 12 May 2026 12:32:58 -0700 Subject: [PATCH 220/986] make style --- src/axom/bump/tests/bump_mergemeshes.cpp | 8 ++++---- src/axom/bump/views/dispatch_material_field.hpp | 4 +++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/axom/bump/tests/bump_mergemeshes.cpp b/src/axom/bump/tests/bump_mergemeshes.cpp index 3325de83f1..d2a0af8d96 100644 --- a/src/axom/bump/tests/bump_mergemeshes.cpp +++ b/src/axom/bump/tests/bump_mergemeshes.cpp @@ -32,12 +32,12 @@ namespace utils = axom::bump::utilities; #ifdef AXOM_DEBUG_MERGE_MESHES_TEST void saveMesh(const conduit::Node &n_mesh, const std::string &fileRoot) { -#ifdef CONDUIT_RELAY_IO_HDF5_ENABLED + #ifdef CONDUIT_RELAY_IO_HDF5_ENABLED const std::string protocol("hdf5"); conduit::relay::io::save(n_mesh, fileRoot + ".yaml", "yaml"); -#else + #else const std::string protocol("yaml"); -#endif + #endif // These save with a ".root" extension conduit::relay::io::blueprint::save_mesh(n_mesh, fileRoot, protocol); } @@ -111,7 +111,7 @@ struct test_mergemeshes result(expectedResult, matflags); bool success = false; try - { + { success = compareConduit(expectedResult, hostResult, tolerance, info); } catch(const conduit::Error &e) diff --git a/src/axom/bump/views/dispatch_material_field.hpp b/src/axom/bump/views/dispatch_material_field.hpp index 0b1f571056..5a683abade 100644 --- a/src/axom/bump/views/dispatch_material_field.hpp +++ b/src/axom/bump/views/dispatch_material_field.hpp @@ -146,7 +146,9 @@ bool dispatch_material_field(const conduit::Node &matset, const conduit::Node &n if(!retval) { retval = - dispatch_material_multibuffer_field(matset, n_field, std::forward(func)); + dispatch_material_multibuffer_field(matset, + n_field, + std::forward(func)); } return retval; } From 3c2e4853a72a315da58d20cae35bb3f91808e384 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 12 May 2026 16:16:31 -0700 Subject: [PATCH 221/986] Remove support for an obsolete matset type that was removed from Blueprint. --- RELEASE-NOTES.md | 1 + src/axom/bump/ExtractZones.hpp | 1 - src/axom/bump/MergeMeshes.hpp | 16 +- src/axom/bump/tests/bump_views.cpp | 111 ------- src/axom/bump/views/MaterialView.hpp | 303 +----------------- src/axom/bump/views/dispatch_material.hpp | 108 +------ .../bump/views/dispatch_material_field.hpp | 36 +-- 7 files changed, 25 insertions(+), 551 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index d5d482089f..75e3e472cf 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -31,6 +31,7 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ - Bump: Added `axom::bump::views::dispatch_material_field()` function (and related functions) for creating a material view of a material-dependent or mixed field. ### Removed +- Bump: Removed `axom::bump::views::MultiBufferMaterialView`, which was a view type for an obsolete flavor of Blueprint matset. ### Deprecated diff --git a/src/axom/bump/ExtractZones.hpp b/src/axom/bump/ExtractZones.hpp index 91657ada3a..d68f282bef 100644 --- a/src/axom/bump/ExtractZones.hpp +++ b/src/axom/bump/ExtractZones.hpp @@ -41,7 +41,6 @@ class ExtractZones * * \param topoView The input topology view. * \param coordsetView The input coordset view. - * \param matsetView The input matset view. */ ExtractZones(const TopologyView &topoView, const CoordsetView &coordsetView) : m_topologyView(topoView) diff --git a/src/axom/bump/MergeMeshes.hpp b/src/axom/bump/MergeMeshes.hpp index 92fd69cdb9..9a52b9e9f3 100644 --- a/src/axom/bump/MergeMeshes.hpp +++ b/src/axom/bump/MergeMeshes.hpp @@ -1726,6 +1726,12 @@ class MergeMeshesAndMatsets : public MergeMeshes mi.defaultMaterial = true; } } + + // One or more inputs did not have a matset. + if(mi.hasMatsets && mi.defaultMaterial) + { + mi.allMats["default"] = mi.nmats++; + } } /*! @@ -1796,11 +1802,6 @@ class MergeMeshesAndMatsets : public MergeMeshes if(mi.hasMatsets) { - MaterialDispatch disp; - - // One or more inputs did not have a matset. - if(mi.defaultMaterial) mi.allMats["default"] = mi.nmats++; - countMaterialSizes(inputs, mi); // Allocate @@ -1837,6 +1838,7 @@ class MergeMeshesAndMatsets : public MergeMeshes n_material_map[it->first] = it->second; // Populate + MaterialDispatch disp; auto *This = this; disp.execute(n_material_ids, n_sizes, @@ -2195,6 +2197,7 @@ class MergeMeshesAndMatsets : public MergeMeshes * * \note This function relies on the materials having been merged first so we * can use their pre-existing merged arrays. + * \note Mixed fields in Blueprint at this time are limited to scalars. */ virtual void copyMixedField(const std::vector &inputs, conduit::Node &n_field, @@ -2209,9 +2212,6 @@ class MergeMeshesAndMatsets : public MergeMeshes const auto conduitAllocatorId = axom::sidre::ConduitMemory::axomAllocIdToConduit(this->getAllocatorID()); - // One or more inputs did not have a matset. - if(mi.defaultMaterial) mi.allMats["default"] = mi.nmats++; - countMaterialSizes(inputs, mi); const std::string matsetName(mi.matsetName); n_field["matset"] = matsetName; diff --git a/src/axom/bump/tests/bump_views.cpp b/src/axom/bump/tests/bump_views.cpp index 53192abfee..befe360e92 100644 --- a/src/axom/bump/tests/bump_views.cpp +++ b/src/axom/bump/tests/bump_views.cpp @@ -633,22 +633,6 @@ struct test_braid2d_mat SLIC_INFO("unibuffer: mixedFieldView"); test_matsetview(nzones, mixedFieldView, allocatorID); } - else if(mattype == "multibuffer") - { - axom::bump::views::dispatch_material_multibuffer(deviceMesh["matsets/mat"], [&](auto matsetView) { - SLIC_INFO("multibuffer: matsetView"); - test_matsetview(nzones, matsetView, allocatorID); - }); - - // Test mixed field. - axom::bump::views::dispatch_material_multibuffer_field( - deviceMesh["matsets/mat"], - deviceMesh["fields/mixed"], - [&](auto mixedFieldView) { - SLIC_INFO("multibuffer: mixedFieldView"); - test_matsetview(nzones, mixedFieldView, allocatorID); - }); - } else if(mattype == "element_dominant") { axom::bump::views::dispatch_material_element_dominant( @@ -851,30 +835,6 @@ TEST(bump_views, matset_unibuffer_hip) } #endif -// Multibuffer -TEST(bump_views, matset_multibuffer_seq) -{ - test_braid2d_mat::test("uniform", "multibuffer", "uniform2d_multibuffer"); -} -#if defined(AXOM_USE_OPENMP) -TEST(bump_views, matset_multibuffer_omp) -{ - test_braid2d_mat::test("uniform", "multibuffer", "uniform2d_multibuffer"); -} -#endif -#if defined(AXOM_USE_CUDA) -TEST(bump_views, matset_multibuffer_cuda) -{ - test_braid2d_mat::test("uniform", "multibuffer", "uniform2d_multibuffer"); -} -#endif -#if defined(AXOM_USE_HIP) -TEST(bump_views, matset_multibuffer_hip) -{ - test_braid2d_mat::test("uniform", "multibuffer", "uniform2d_multibuffer"); -} -#endif - // Element-dominant TEST(bump_views, matset_element_dominant_seq) { @@ -923,77 +883,6 @@ TEST(bump_views, matset_material_dominant_hip) } #endif -TEST(bump_views, matset_multibuffer) -{ - const char *yaml = R"( -matsets: - matset: - topology: topology - volume_fractions: - a: - values: [0, 0, 0, 0.33, 0, 0.3] # [0, 0, 0, a1, 0, a0] - indices: [5, 3] - b: - values: [0, 0.7, 1., 0.67, 0] # [0, b0, b2, b1, 0] - indices: [1, 3, 2] - material_map: # (optional) - a: 0 - b: 1 -)"; - conduit::Node matsets; - matsets.parse(yaml); - const conduit::Node &n_matset = matsets["matsets/matset"]; - axom::bump::views::dispatch_material_multibuffer(n_matset, [&](auto matsetView) { - using IDList = typename decltype(matsetView)::IDList; - using VFList = typename decltype(matsetView)::VFList; - using VFType = typename VFList::value_type; - - EXPECT_EQ(matsetView.numberOfZones(), 3); - EXPECT_EQ(matsetView.numberOfMaterials(0), 2); - EXPECT_EQ(matsetView.numberOfMaterials(1), 2); - EXPECT_EQ(matsetView.numberOfMaterials(2), 1); - - IDList m0, m1, m2; - VFList vf0, vf1, vf2; - matsetView.zoneMaterials(0, m0, vf0); - EXPECT_EQ(m0.size(), 2); - EXPECT_EQ(vf0.size(), 2); - EXPECT_EQ(m0[0], 0); - EXPECT_EQ(m0[1], 1); - EXPECT_EQ(vf0[0], 0.3); - EXPECT_EQ(vf0[1], 0.7); - - VFType vf; - EXPECT_TRUE(matsetView.zoneContainsMaterial(0, 0, vf)); - EXPECT_EQ(vf, 0.3); - EXPECT_TRUE(matsetView.zoneContainsMaterial(0, 1, vf)); - EXPECT_EQ(vf, 0.7); - - matsetView.zoneMaterials(1, m1, vf1); - EXPECT_EQ(m1.size(), 2); - EXPECT_EQ(vf1.size(), 2); - EXPECT_EQ(m1[0], 0); - EXPECT_EQ(m1[1], 1); - EXPECT_EQ(vf1[0], 0.33); - EXPECT_EQ(vf1[1], 0.67); - - EXPECT_TRUE(matsetView.zoneContainsMaterial(1, 0, vf)); - EXPECT_EQ(vf, 0.33); - EXPECT_TRUE(matsetView.zoneContainsMaterial(1, 1, vf)); - EXPECT_EQ(vf, 0.67); - - matsetView.zoneMaterials(2, m2, vf2); - EXPECT_EQ(m2.size(), 1); - EXPECT_EQ(m2[0], 1); - EXPECT_EQ(vf2[0], 1); - - EXPECT_FALSE(matsetView.zoneContainsMaterial(2, 0, vf)); - EXPECT_EQ(vf, 0.); - EXPECT_TRUE(matsetView.zoneContainsMaterial(2, 1, vf)); - EXPECT_EQ(vf, 1.); - }); -} - //------------------------------------------------------------------------------ int main(int argc, char *argv[]) { diff --git a/src/axom/bump/views/MaterialView.hpp b/src/axom/bump/views/MaterialView.hpp index d16fc10d8a..c340ae04b9 100644 --- a/src/axom/bump/views/MaterialView.hpp +++ b/src/axom/bump/views/MaterialView.hpp @@ -53,7 +53,7 @@ MaterialInformation materials(const conduit::Node &matset); //--------------------------------------------------------------------------- /*! - \brief Material view for unibuffer matsets. + \brief Material view for unibuffer element-dominant matsets. \tparam IndexT The integer type used for material data. \tparam FloatT The floating point type used for material data (volume fractions). @@ -293,304 +293,7 @@ class UnibufferMaterialView }; /*! - \brief View for multi-buffer matsets. - - \tparam IndexT The integer type used for material data. - \tparam FloatT The floating point type used for material data (volume fractions). - \tparam MAXMATERIALS The maximum number of materials to support. - - \verbatim - -matsets: - matset: - topology: topology - volume_fractions: - a: - values: [0, 0, 0, a1, 0, a0] - indices: [5, 3] - b: - values: [0, b0, b2, b1, 0] - indices: [1, 3, 2] - material_map: # (optional) - a: 0 - b: 1 - - \endverbatim - */ -template -class MultiBufferMaterialView -{ -public: - using MaterialID = IndexT; - using ZoneIndex = IndexT; - using IndexType = IndexT; - using FloatType = FloatT; - using IDList = StaticArray; - using VFList = StaticArray; - - constexpr static axom::IndexType MaxMaterials = MAXMATERIALS; - constexpr static axom::IndexType InvalidIndex = -1; - - void add(MaterialID matno, - const axom::ArrayView &indices, - const axom::ArrayView &vfs) - { - SLIC_ASSERT(m_size + 1 < MaxMaterials); -#if !defined(AXOM_DEVICE_CODE) - const auto begin = m_matnos.data(); - const auto end = begin + m_size; - SLIC_ERROR_IF(std::find(begin, end, matno) != end, - "Adding a duplicate material number is not allowed."); -#endif - m_indices[m_size] = indices; - m_values[m_size] = vfs; - m_matnos[m_size] = matno; - m_size++; - } - - AXOM_HOST_DEVICE - axom::IndexType numberOfZones() const - { - axom::IndexType nzones = 0; - for(int i = 0; i < m_size; i++) nzones = axom::utilities::max(nzones, m_indices[i].size()); - return nzones; - } - - AXOM_HOST_DEVICE - axom::IndexType numberOfMaterials(ZoneIndex zi) const - { - axom::IndexType nmats = 0; - for(axom::IndexType i = 0; i < m_size; i++) - { - const auto &curIndices = m_indices[i]; - const auto &curValues = m_values[i]; - - if(zi < static_cast(curIndices.size())) - { - const auto idx = curIndices[zi]; - nmats += (curValues[idx] > 0) ? 1 : 0; - } - } - - return nmats; - } - - AXOM_HOST_DEVICE - void zoneMaterials(ZoneIndex zi, IDList &ids, VFList &vfs) const - { - ids.clear(); - vfs.clear(); - - for(axom::IndexType i = 0; i < m_size; i++) - { - const auto &curIndices = m_indices[i]; - const auto &curValues = m_values[i]; - - if(zi < static_cast(curIndices.size())) - { - const auto idx = curIndices[zi]; - if(curValues[idx] > 0) - { - ids.push_back(m_matnos[i]); - vfs.push_back(curValues[idx]); - } - } - } - } - - AXOM_HOST_DEVICE - axom::IndexType zoneMaterials(ZoneIndex zi, - axom::ArrayView &ids, - axom::ArrayView &vfs) const - { - axom::IndexType n = 0; - for(axom::IndexType i = 0; i < m_size; i++) - { - const auto &curIndices = m_indices[i]; - const auto &curValues = m_values[i]; - - if(zi < static_cast(curIndices.size())) - { - const auto idx = curIndices[zi]; - if(curValues[idx] > 0) - { - ids[n] = m_matnos[i]; - vfs[n] = curValues[idx]; - n++; - } - } - } - return n; - } - - AXOM_HOST_DEVICE - bool zoneContainsMaterial(ZoneIndex zi, MaterialID mat) const - { - FloatType tmp {}; - return zoneContainsMaterial(zi, mat, tmp); - } - - AXOM_HOST_DEVICE - bool zoneContainsMaterial(ZoneIndex zi, MaterialID mat, FloatType &vf) const - { - bool found = false; - vf = FloatType {}; - axom::IndexType mi = indexOfMaterialID(mat); - if(mi != InvalidIndex) - { - const auto &curIndices = m_indices[mi]; - const auto &curValues = m_values[mi]; - if(zi < static_cast(curIndices.size())) - { - const auto idx = curIndices[zi]; - vf = curValues[idx]; - found = curValues[idx] > 0; - } - } - return found; - } - - /*! - * \brief An iterator class for iterating over read-only data in a zone. - * The iterator can access material ids and volume fractions for one - * material at a time in the associated zone. - */ - class const_iterator - { - // Let the material view call the const_iterator constructor. - friend class MultiBufferMaterialView; - - public: - /// Get the current material id for the iterator. - MaterialID AXOM_HOST_DEVICE material_id() const - { - SLIC_ASSERT(m_currentIndex < m_view->m_size); - return m_view->m_matnos[m_currentIndex]; - } - /// Get the current volume fraction for the iterator. - FloatType AXOM_HOST_DEVICE volume_fraction() const - { - SLIC_ASSERT(m_currentIndex < m_view->m_size); - const auto &curIndices = m_view->m_indices[m_currentIndex]; - const auto &curValues = m_view->m_values[m_currentIndex]; - const auto idx = curIndices[m_zoneIndex]; - return curValues[idx]; - } - axom::IndexType AXOM_HOST_DEVICE size() const { return m_view->numberOfMaterials(m_zoneIndex); } - ZoneIndex AXOM_HOST_DEVICE zoneIndex() const { return m_zoneIndex; } - void AXOM_HOST_DEVICE operator++() - { - m_currentIndex += (m_currentIndex < m_view->m_size) ? 1 : 0; - advance(); - } - void AXOM_HOST_DEVICE operator++(int) - { - m_currentIndex += (m_currentIndex < m_view->m_size) ? 1 : 0; - advance(); - } - bool AXOM_HOST_DEVICE operator==(const const_iterator &rhs) const - { - return m_currentIndex == rhs.m_currentIndex && m_zoneIndex == rhs.m_zoneIndex && - m_view == rhs.m_view; - } - bool AXOM_HOST_DEVICE operator!=(const const_iterator &rhs) const - { - return m_currentIndex != rhs.m_currentIndex || m_zoneIndex != rhs.m_zoneIndex || - m_view != rhs.m_view; - } - - private: - DISABLE_DEFAULT_CTOR(const_iterator); - - /// Constructor - AXOM_HOST_DEVICE const_iterator(const MultiBufferMaterialView *view, - ZoneIndex zoneIndex, - axom::IndexType currentIndex = 0) - : m_view(view) - , m_zoneIndex(zoneIndex) - , m_currentIndex(currentIndex) - { } - - /// Advance to the next valid material slot for the zone. - void AXOM_HOST_DEVICE advance() - { - while(m_currentIndex < m_view->m_size) - { - const auto &curIndices = m_view->m_indices[m_currentIndex]; - const auto &curValues = m_view->m_values[m_currentIndex]; - - if(m_zoneIndex < static_cast(curIndices.size())) - { - const auto idx = curIndices[m_zoneIndex]; - if(curValues[idx] > 0) - { - break; - } - } - m_currentIndex++; - } - } - - const MultiBufferMaterialView *m_view; - ZoneIndex m_zoneIndex; - axom::IndexType m_currentIndex; - }; - // Let the const_iterator access members. - friend class const_iterator; - - /*! - * \brief Return the iterator for the beginning of a zone's material data. - * - * \param zi The zone index being queried. - * - * \return The iterator for the beginning of a zone's material data. - */ - const_iterator AXOM_HOST_DEVICE beginZone(ZoneIndex zi) const - { - SLIC_ASSERT(zi < static_cast(numberOfZones())); - - auto it = const_iterator(this, zi, 0); - it.advance(); - return it; - } - - /*! - * \brief Return the iterator for the end of a zone's material data. - * - * \param zi The zone index being queried. - * - * \return The iterator for the end of a zone's material data. - */ - const_iterator AXOM_HOST_DEVICE endZone(ZoneIndex zi) const - { - SLIC_ASSERT(zi < static_cast(numberOfZones())); - return const_iterator(this, zi, m_size); - } - -private: - AXOM_HOST_DEVICE - axom::IndexType indexOfMaterialID(MaterialID mat) const - { - axom::IndexType index = InvalidIndex; - for(axom::IndexType mi = 0; mi < m_size; mi++) - { - if(mat == m_matnos[mi]) - { - index = mi; - break; - } - } - return index; - } - - axom::StackArray, MAXMATERIALS> m_values {}; - axom::StackArray, MAXMATERIALS> m_indices {}; - axom::StackArray m_matnos {}; - axom::IndexType m_size {0}; -}; - -/*! - \brief View for element-dominant matsets. + \brief View for multibuffer element-dominant matsets. \tparam IndexT The integer type used for material data. \tparam FloatT The floating point type used for material data (volume fractions). @@ -869,7 +572,7 @@ class ElementDominantMaterialView }; /*! - \brief View for material-dominant matsets. + \brief View for multibuffer material-dominant matsets. \tparam IndexT The integer type used for material data. \tparam FloatT The floating point type used for material data (volume fractions). diff --git a/src/axom/bump/views/dispatch_material.hpp b/src/axom/bump/views/dispatch_material.hpp index b8509881f0..bffba7c4a3 100644 --- a/src/axom/bump/views/dispatch_material.hpp +++ b/src/axom/bump/views/dispatch_material.hpp @@ -29,7 +29,7 @@ inline void verifyMixedField(const conduit::Node &n_field) } /*! - * \brief Dispatch Conduit nodes containing a unibuffer matset and a values array + * \brief Dispatch Conduit nodes containing a unibuffer element-dominant matset and a values array * to a function as the appropriate type of matset view. * * \tparam FuncType The function/lambda type that will take the matset. @@ -95,79 +95,8 @@ IntElement getMaterialID(const conduit::Node &matset, } /*! - * \brief Dispatch a Conduit node containing a multibuffer matset to a function as the appropriate type of matset view. - * - * \tparam FuncType The function/lambda type that will take the matset. - * - * \param matset The node that contains the matset. - * \param values_object The node that contains the values to use as volume fractions / field values and indices. - * \param func The function/lambda that will operate on the matset view. - * - * \return true if the dispatch worked, false otherwise. - */ -template -bool dispatch_material_multibuffer_with_values(const conduit::Node &matset, - const conduit::Node &values_object, - FuncType &&func) -{ - bool retval = false; - verify(matset, "matset"); - if(conduit::blueprint::mesh::matset::is_multi_buffer(matset)) - { - const conduit::Node &volume_fractions = matset.fetch_existing("volume_fractions"); - if(values_object.number_of_children() > 0 && - (volume_fractions.number_of_children() == values_object.number_of_children())) - { - const conduit::Node &n_firstValuesObj = values_object[0]; - const conduit::Node &n_firstVolumesObj = volume_fractions[0]; - if(n_firstValuesObj.has_child("values") && n_firstVolumesObj.has_child("indices")) - { - const conduit::Node &n_firstValues = n_firstValuesObj.fetch_existing("values"); - const conduit::Node &n_firstIndices = n_firstVolumesObj.fetch_existing("indices"); - indexNodeToArrayView(n_firstIndices, [&](auto firstIndices) { - floatNodeToArrayView(n_firstValues, [&](auto firstValues) { - using IntElement = - typename std::remove_const::type; - using FloatElement = - typename std::remove_const::type; - using IntView = axom::ArrayView; - using FloatView = axom::ArrayView; - - MultiBufferMaterialView matsetView; - - for(conduit::index_t i = 0; i < values_object.number_of_children(); i++) - { - const conduit::Node &values = values_object[i].fetch_existing("values"); - const conduit::Node &indices = volume_fractions[i].fetch_existing("indices"); - - const IntElement *indices_ptr = indices.value(); - const FloatElement *values_ptr = values.value(); - - IntView indices_view(const_cast(indices_ptr), - indices.dtype().number_of_elements()); - FloatView values_view(const_cast(values_ptr), - values.dtype().number_of_elements()); - - // Get the material number if we can. - IntElement matno = getMaterialID(matset, - volume_fractions[i].name(), - static_cast(i)); - - matsetView.add(matno, indices_view, values_view); - } - - func(matsetView); - }); - }); - retval = true; - } - } - } - return retval; -} - -/*! - * \brief Dispatch a Conduit node containing a element-dominant matset to a function as the appropriate type of matset view. + * \brief Dispatch a Conduit node containing a multibuffer element-dominant matset + * to a function as the appropriate type of matset view. * * \tparam FuncType The function/lambda type that will take the matset. * @@ -366,28 +295,6 @@ bool dispatch_material_unibuffer(const conduit::Node &matset, FuncType &&func) std::forward(func)); } -/*! - * \brief Dispatch a Conduit node containing a multibuffer matset to a function as - * the appropriate type of matset view. - * - * \tparam FuncType The function/lambda type that will take the matset. - * - * \param matset The node that contains the matset. - * \param values_object The node that contains the values to use as volume fractions / field values and indices. - * \param func The function/lambda that will operate on the matset view. - * - * \return true if the dispatch worked, false otherwise. - */ -template -bool dispatch_material_multibuffer(const conduit::Node &matset, FuncType &&func) -{ - verify(matset, "matset"); - return detail::dispatch_material_multibuffer_with_values( - matset, - matset["volume_fractions"], - std::forward(func)); -} - /*! * \brief Dispatch a Conduit node containing a element-dominant matset to a function as * the appropriate type of matset view. @@ -455,11 +362,12 @@ bool dispatch_material(const conduit::Node &matset, FuncType &&func) dispatch_material_material_dominant(matset, std::forward(func)); } - // NOTE: This one may be obsolete in Blueprint - if(!retval) + // NOTE: Blueprint describes a unibuffer material-dominant matset type but does not technically implement it. + // https://llnl-conduit.readthedocs.io/en/latest/blueprint_mesh.html#material-sets + if(!retval && conduit::blueprint::mesh::matset::is_uni_buffer(matset) && + conduit::blueprint::mesh::matset::is_material_dominant(matset)) { - retval = - dispatch_material_multibuffer(matset, std::forward(func)); + SLIC_ERROR("Unibuffer material dominant matsets are unsupported."); } return retval; } diff --git a/src/axom/bump/views/dispatch_material_field.hpp b/src/axom/bump/views/dispatch_material_field.hpp index 5a683abade..e066d37ee8 100644 --- a/src/axom/bump/views/dispatch_material_field.hpp +++ b/src/axom/bump/views/dispatch_material_field.hpp @@ -38,31 +38,6 @@ bool dispatch_material_unibuffer_field(const conduit::Node &matset, std::forward(func)); } -/*! - * \brief Dispatch a Conduit node containing a multibuffer matset to a function as - * the appropriate type of matset view. - * - * \tparam FuncType The function/lambda type that will take the matset. - * - * \param matset The node that contains the matset. - * \param n_field The node that contains the values to be used as volume fractions / field. - * \param func The function/lambda that will operate on the matset view. - * - * \return true if the dispatch worked, false otherwise. - */ -template -bool dispatch_material_multibuffer_field(const conduit::Node &matset, - const conduit::Node &n_field, - FuncType &&func) -{ - verify(matset, "matset"); - detail::verifyMixedField(n_field); - return detail::dispatch_material_multibuffer_with_values( - matset, - n_field["matset_values"], - std::forward(func)); -} - /*! * \brief Dispatch Conduit nodes containing a element-dominant matset and related field * to a function as the appropriate type of matset view. @@ -142,13 +117,12 @@ bool dispatch_material_field(const conduit::Node &matset, const conduit::Node &n n_field, std::forward(func)); } - // NOTE: This one may be obsolete in Blueprint - if(!retval) + // NOTE: Blueprint describes a unibuffer material-dominant matset type but does not technically implement it. + // https://llnl-conduit.readthedocs.io/en/latest/blueprint_mesh.html#material-sets + if(!retval && conduit::blueprint::mesh::matset::is_uni_buffer(matset) && + conduit::blueprint::mesh::matset::is_material_dominant(matset)) { - retval = - dispatch_material_multibuffer_field(matset, - n_field, - std::forward(func)); + SLIC_ERROR("Unibuffer material dominant matsets are unsupported."); } return retval; } From a4f3cd6e07fd15c6ebacaaf44fe1d8fa1ab45ff4 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 12 May 2026 16:27:26 -0700 Subject: [PATCH 222/986] Run tests sequentially --- scripts/github-actions/linux-build_and_test.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/github-actions/linux-build_and_test.sh b/scripts/github-actions/linux-build_and_test.sh index a7a780ffe6..ba885420c7 100755 --- a/scripts/github-actions/linux-build_and_test.sh +++ b/scripts/github-actions/linux-build_and_test.sh @@ -45,7 +45,8 @@ if [[ "$DO_BUILD" == "yes" ]] ; then fi echo "~~~~~~ RUNNING TESTS ~~~~~~~~" - make CTEST_OUTPUT_ON_FAILURE=1 test ARGS='-T Test --output-on-failure -j$NUM_BUILD_PROCS' + #make CTEST_OUTPUT_ON_FAILURE=1 test ARGS='-T Test --output-on-failure -j$NUM_BUILD_PROCS' + make CTEST_OUTPUT_ON_FAILURE=1 test ARGS='-T Test --output-on-failure' if [[ "${DO_BENCHMARKS}" == "yes" ]] ; then echo "~~~~~~ RUNNING BENCHMARKS ~~~~~~~~" From ee961e88d35827225eaf75c82881214c17efc0e2 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 12 May 2026 16:31:18 -0700 Subject: [PATCH 223/986] Updated docs again --- src/axom/bump/docs/sphinx/bump_views.rst | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/axom/bump/docs/sphinx/bump_views.rst b/src/axom/bump/docs/sphinx/bump_views.rst index ebb7b6fbff..0c42d9a2eb 100644 --- a/src/axom/bump/docs/sphinx/bump_views.rst +++ b/src/axom/bump/docs/sphinx/bump_views.rst @@ -165,10 +165,12 @@ Matsets The BUMP component provides material views to wrap Blueprint matsets behind an interface that supports queries of the matset data without having to care much about its internal representation. -Blueprint provides 4 flavors of matset, each with a different representation. The -``axom::bump::views::UnibufferMaterialView`` class wraps unibuffer matsets, which consist of -several arrays that define materials for each zone in the associated topology. The view's -methods allow algorithms to query the list of materials for each zone. +Blueprint describes 4 flavors of matset, each with a different representation. In practice, +Blueprint supports 4 flavors of matset, unibuffer element-dominant, multibuffer element-dominant, +and multibuffer material-dominant. The ``axom::bump::views::UnibufferMaterialView`` class wraps +unibuffer element-dominant matsets, which consist of several arrays that define materials for +each zone in the associated topology. The view's methods allow algorithms to query the list of +materials for each zone. .. literalinclude:: ../../tests/bump_views.cpp :start-after: _bump_views_matsetview_begin From ed1537ea3328de0b4320e94cce66f496956f6b86 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 12 May 2026 16:32:08 -0700 Subject: [PATCH 224/986] make style --- src/axom/bump/views/dispatch_material.hpp | 2 +- src/axom/bump/views/dispatch_material_field.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/axom/bump/views/dispatch_material.hpp b/src/axom/bump/views/dispatch_material.hpp index bffba7c4a3..5cf5605f40 100644 --- a/src/axom/bump/views/dispatch_material.hpp +++ b/src/axom/bump/views/dispatch_material.hpp @@ -365,7 +365,7 @@ bool dispatch_material(const conduit::Node &matset, FuncType &&func) // NOTE: Blueprint describes a unibuffer material-dominant matset type but does not technically implement it. // https://llnl-conduit.readthedocs.io/en/latest/blueprint_mesh.html#material-sets if(!retval && conduit::blueprint::mesh::matset::is_uni_buffer(matset) && - conduit::blueprint::mesh::matset::is_material_dominant(matset)) + conduit::blueprint::mesh::matset::is_material_dominant(matset)) { SLIC_ERROR("Unibuffer material dominant matsets are unsupported."); } diff --git a/src/axom/bump/views/dispatch_material_field.hpp b/src/axom/bump/views/dispatch_material_field.hpp index e066d37ee8..e1edc2bb2b 100644 --- a/src/axom/bump/views/dispatch_material_field.hpp +++ b/src/axom/bump/views/dispatch_material_field.hpp @@ -120,7 +120,7 @@ bool dispatch_material_field(const conduit::Node &matset, const conduit::Node &n // NOTE: Blueprint describes a unibuffer material-dominant matset type but does not technically implement it. // https://llnl-conduit.readthedocs.io/en/latest/blueprint_mesh.html#material-sets if(!retval && conduit::blueprint::mesh::matset::is_uni_buffer(matset) && - conduit::blueprint::mesh::matset::is_material_dominant(matset)) + conduit::blueprint::mesh::matset::is_material_dominant(matset)) { SLIC_ERROR("Unibuffer material dominant matsets are unsupported."); } From 106c6168428f7b2694f4e5290eb723286e053e9d Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 13 May 2026 18:20:33 -0700 Subject: [PATCH 225/986] Added an include and changed some view parameters from references to value. --- src/axom/bump/MergeMeshes.hpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/axom/bump/MergeMeshes.hpp b/src/axom/bump/MergeMeshes.hpp index 9a52b9e9f3..ebe9523051 100644 --- a/src/axom/bump/MergeMeshes.hpp +++ b/src/axom/bump/MergeMeshes.hpp @@ -12,6 +12,7 @@ #include "axom/bump/views/Shapes.hpp" #include "axom/bump/views/dispatch_material.hpp" +#include "axom/bump/views/dispatch_material_field.hpp" #include "axom/bump/views/dispatch_unstructured_topology.hpp" #include "axom/bump/utilities/conduit_memory.hpp" #include "axom/bump/utilities/conduit_traits.hpp" @@ -2293,9 +2294,9 @@ class MergeMeshesAndMatsets : public MergeMeshes * \param zOffset The starting offset in the output mixed field view. */ template - void mergeMixedField_copy(const MatsetView &srcMatsetView, - MixedFieldView &mixedFieldView, - const OffsetsView &offsetsView, + void mergeMixedField_copy(MatsetView srcMatsetView, + MixedFieldView mixedFieldView, + OffsetsView offsetsView, axom::IndexType nzones, axom::IndexType zOffset) const { @@ -2326,8 +2327,8 @@ class MergeMeshesAndMatsets : public MergeMeshes * \param zOffset The starting offset in the output mixed field view. */ template - void mergeMixedField_default(MixedFieldView &mixedFieldView, - const OffsetsView &offsetsView, + void mergeMixedField_default(MixedFieldView mixedFieldView, + OffsetsView offsetsView, axom::IndexType nzones, axom::IndexType zOffset) const { From 49f6daa5a4d2877a49e85d96c877c760e005b01e Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Tue, 31 Mar 2026 12:01:00 -0700 Subject: [PATCH 226/986] Initial commit --- src/axom/quest/GWNMethods.hpp | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/src/axom/quest/GWNMethods.hpp b/src/axom/quest/GWNMethods.hpp index 0c16c0c5ec..848f201750 100644 --- a/src/axom/quest/GWNMethods.hpp +++ b/src/axom/quest/GWNMethods.hpp @@ -132,14 +132,19 @@ template class DirectGWN2D { public: - using CurveArrayType = axom::Array>; + using BoxType = axom::primal::BoundingBox; + + using CurveType = axom::primal::NURBSCurve; + using CurveArrayType = axom::Array; using NURBSCacheManager = typename axom::primal::nurbs_cache_2d_traits::type; DirectGWN2D() = default; /// \brief Define view for NURBS data. /// If memoization is used, allocate a cache for each curve. - void preprocess(const CurveArrayType& input_curves, bool use_memoization = true) + void preprocess(const CurveArrayType& input_curves, + bool use_direct_eval = true, + bool use_memoization = true) { m_input_curves_view = input_curves.view(); if(m_input_curves_view.size() <= 0) @@ -158,9 +163,21 @@ class DirectGWN2D } timer.stop(); AXOM_ANNOTATE_METADATA("preprocessing_time", timer.elapsed(), ""); - SLIC_INFO(axom::fmt::format("Direct query preprocessing (loading curves{}): {} s", - use_memoization ? " and memoization caches" : "", - timer.elapsedTimeInSec())); + + if(!use_direct_eval) + { + const int ncurves = m_input_curves_view.size(); + axom::Array aabbs(ncurves, ncurves); + auto aabbs_view = aabbs.view(); + + axom::for_all( + ncurves, + AXOM_LAMBDA(axom::IndexType i) { aabbs_view[i] = m_input_curves_view[i].boundingBox(); }); + m_bvh.initialize(aabbs_view, ncurves); + SLIC_INFO(axom::fmt::format("Direct query preprocessing (loading curves{}): {} s", + use_memoization ? " and memoization caches" : "", + timer.elapsedTimeInSec())); + } } /*! @@ -248,8 +265,13 @@ class DirectGWN2D } private: - axom::ArrayView> m_input_curves_view; + // For the input curves/BVH leaf nodes + axom::ArrayView m_input_curves_view; NURBSCacheManager m_nurbs_cache_mgr; + + // Only needed for fast approximation method + axom::Array m_internal_moments; + axom::spin::BVH<2, ExecSpace> m_bvh; }; template From ba1c131328bb85482effe99edb01ab0e22f1008a Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Tue, 31 Mar 2026 15:20:26 -0700 Subject: [PATCH 227/986] First implementation of agglomerated curves --- src/axom/quest/FastApproximateGWN.hpp | 8 ++- src/axom/quest/GWNMethods.hpp | 92 ++++++++++++++++++--------- 2 files changed, 69 insertions(+), 31 deletions(-) diff --git a/src/axom/quest/FastApproximateGWN.hpp b/src/axom/quest/FastApproximateGWN.hpp index e599c6c502..bf1b7f5648 100644 --- a/src/axom/quest/FastApproximateGWN.hpp +++ b/src/axom/quest/FastApproximateGWN.hpp @@ -126,6 +126,11 @@ class GWNMomentData compute_coefficients(); } + /// Construct moments from the endpoints of a 2D segment + explicit GWNMomentData(const axom::primal::NURBSCurve& c) + : GWNMomentData(c.getInitPoint(), c.getEndPoint()) + { } + /// Construct moments from a 2D Segment explicit GWNMomentData(const axom::primal::Segment& s) : GWNMomentData(s.source(), s.target()) @@ -403,7 +408,8 @@ double fast_approximate_winding_number(const primal::Point& query, } }; - if constexpr(std::is_same_v>) + if constexpr(std::is_same_v> || + std::is_same_v>) { auto leaf_gwn = [&query, &gwn, leaf_objects_view, &wt](std::int32_t currentNode, const std::int32_t* leafNodes) -> void { diff --git a/src/axom/quest/GWNMethods.hpp b/src/axom/quest/GWNMethods.hpp index 848f201750..94e50d345f 100644 --- a/src/axom/quest/GWNMethods.hpp +++ b/src/axom/quest/GWNMethods.hpp @@ -128,11 +128,12 @@ void generate_gwn_query_mesh(mfem::DataCollection& dc, ///@{ /// \name Query methods for 2D GWN applications -template +template class DirectGWN2D { public: using BoxType = axom::primal::BoundingBox; + using GWNMoments = axom::quest::GWNMomentData; using CurveType = axom::primal::NURBSCurve; using CurveArrayType = axom::Array; @@ -174,10 +175,21 @@ class DirectGWN2D ncurves, AXOM_LAMBDA(axom::IndexType i) { aabbs_view[i] = m_input_curves_view[i].boundingBox(); }); m_bvh.initialize(aabbs_view, ncurves); - SLIC_INFO(axom::fmt::format("Direct query preprocessing (loading curves{}): {} s", - use_memoization ? " and memoization caches" : "", - timer.elapsedTimeInSec())); + + auto curves_view = m_input_curves_view; + auto compute_moments = [curves_view](std::int32_t currentNode, + const std::int32_t* leafNodes) -> GWNMoments { + const auto idx = leafNodes[currentNode]; + return GWNMoments(curves_view[idx]); // TODO: Avoid repeat normal calculation + }; + + const auto traverser = m_bvh.getTraverser(); + m_internal_moments = traverser.reduce_tree(compute_moments); } + + SLIC_INFO(axom::fmt::format("Direct query preprocessing (loading curves{}): {} s", + use_memoization ? " and memoization caches" : "", + timer.elapsedTimeInSec())); } /*! @@ -218,33 +230,53 @@ class DirectGWN2D AXOM_ANNOTATE_SCOPE("query"); const primal::WindingTolerances tol_copy = tol; - // Use non-memoized form - if(m_nurbs_cache_mgr.empty()) + // Use fast approximation + if(m_bvh.isInitialized()) { - axom::for_all(num_query_points, [=, &winding, &inout](axom::IndexType nidx) { - const auto q = query_point(static_cast(nidx)); - double wn {}; - for(const auto& curve : m_input_curves_view) - { - wn += axom::primal::winding_number(q, curve, tol_copy.edge_tol, tol_copy.EPS); - } - winding[static_cast(nidx)] = wn; - inout[static_cast(nidx)] = std::lround(wn); + const auto traverser = m_bvh.getTraverser(); + const auto internal_moments_view = m_internal_moments.view(); + + axom::for_all(num_query_points, [=, &winding, &inout](axom::IndexType index) { + const double wn = axom::quest::fast_approximate_winding_number(query_point(index), + traverser, + m_input_curves_view, + internal_moments_view, + tol_copy); + winding[static_cast(index)] = wn; + inout[static_cast(index)] = std::lround(wn); }); } - else // Use memoized form + // Use direct formula + else { - const auto cache_mgr_view = m_nurbs_cache_mgr.view(); - axom::for_all(num_query_points, [=, &winding, &inout](axom::IndexType nidx) { - const auto q = query_point(static_cast(nidx)); - const auto caches_view = cache_mgr_view.caches(); - - const double wn = - axom::primal::winding_number(q, caches_view, tol_copy.edge_tol, tol_copy.EPS); - - winding[static_cast(nidx)] = wn; - inout[static_cast(nidx)] = std::lround(wn); - }); + // Use direct, non-memoized form + if(m_nurbs_cache_mgr.empty()) + { + axom::for_all(num_query_points, [=, &winding, &inout](axom::IndexType nidx) { + const auto q = query_point(static_cast(nidx)); + double wn {}; + for(const auto& curve : m_input_curves_view) + { + wn += axom::primal::winding_number(q, curve, tol_copy.edge_tol, tol_copy.EPS); + } + winding[static_cast(nidx)] = wn; + inout[static_cast(nidx)] = std::lround(wn); + }); + } + else // Use direct, memoized form + { + const auto cache_mgr_view = m_nurbs_cache_mgr.view(); + axom::for_all(num_query_points, [=, &winding, &inout](axom::IndexType nidx) { + const auto q = query_point(static_cast(nidx)); + const auto caches_view = cache_mgr_view.caches(); + + const double wn = + axom::primal::winding_number(q, caches_view, tol_copy.edge_tol, tol_copy.EPS); + + winding[static_cast(nidx)] = wn; + inout[static_cast(nidx)] = std::lround(wn); + }); + } } } query_timer.stop(); @@ -266,7 +298,7 @@ class DirectGWN2D private: // For the input curves/BVH leaf nodes - axom::ArrayView m_input_curves_view; + axom::ArrayView m_input_curves_view; NURBSCacheManager m_nurbs_cache_mgr; // Only needed for fast approximation method @@ -274,7 +306,7 @@ class DirectGWN2D axom::spin::BVH<2, ExecSpace> m_bvh; }; -template +template class PolylineGWN2D { public: @@ -610,7 +642,7 @@ class DirectGWN3D NURBSCacheManager m_nurbs_cache_mgr; }; -template +template class TriangleGWN3D { public: From 3f07f2de2577f6fcccc529683ab29371e839a44e Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Tue, 31 Mar 2026 16:43:12 -0700 Subject: [PATCH 228/986] Improve documentation/profiling, add test --- src/axom/quest/GWNMethods.hpp | 114 +++++++----- .../examples/quest_winding_number_2d.cpp | 163 +++++++++--------- src/axom/quest/tests/quest_gwn_methods.cpp | 68 ++++---- 3 files changed, 193 insertions(+), 152 deletions(-) diff --git a/src/axom/quest/GWNMethods.hpp b/src/axom/quest/GWNMethods.hpp index 94e50d345f..44020e2062 100644 --- a/src/axom/quest/GWNMethods.hpp +++ b/src/axom/quest/GWNMethods.hpp @@ -129,7 +129,7 @@ void generate_gwn_query_mesh(mfem::DataCollection& dc, /// \name Query methods for 2D GWN applications template -class DirectGWN2D +class NURBSCurveGWNQuery { public: using BoxType = axom::primal::BoundingBox; @@ -139,12 +139,12 @@ class DirectGWN2D using CurveArrayType = axom::Array; using NURBSCacheManager = typename axom::primal::nurbs_cache_2d_traits::type; - DirectGWN2D() = default; + NURBSCurveGWNQuery() = default; /// \brief Define view for NURBS data. /// If memoization is used, allocate a cache for each curve. void preprocess(const CurveArrayType& input_curves, - bool use_direct_eval = true, + bool use_direct_eval = false, bool use_memoization = true) { m_input_curves_view = input_curves.view(); @@ -155,40 +155,65 @@ class DirectGWN2D } axom::utilities::Timer timer(true); + axom::utilities::Timer stage_timer(false); + + AXOM_ANNOTATE_SCOPE("preprocessing"); + + if(use_memoization) { - AXOM_ANNOTATE_SCOPE("preprocessing"); - if(use_memoization) + stage_timer.start(); { + AXOM_ANNOTATE_SCOPE("cache_initialization"); m_nurbs_cache_mgr = NURBSCacheManager(input_curves); } + stage_timer.stop(); + SLIC_INFO(axom::fmt::format(" Preprocessing stage (cache initialization): {} s", + stage_timer.elapsedTimeInSec())); } - timer.stop(); - AXOM_ANNOTATE_METADATA("preprocessing_time", timer.elapsed(), ""); if(!use_direct_eval) { - const int ncurves = m_input_curves_view.size(); - axom::Array aabbs(ncurves, ncurves); - auto aabbs_view = aabbs.view(); - - axom::for_all( - ncurves, - AXOM_LAMBDA(axom::IndexType i) { aabbs_view[i] = m_input_curves_view[i].boundingBox(); }); - m_bvh.initialize(aabbs_view, ncurves); - - auto curves_view = m_input_curves_view; - auto compute_moments = [curves_view](std::int32_t currentNode, - const std::int32_t* leafNodes) -> GWNMoments { - const auto idx = leafNodes[currentNode]; - return GWNMoments(curves_view[idx]); // TODO: Avoid repeat normal calculation - }; - - const auto traverser = m_bvh.getTraverser(); - m_internal_moments = traverser.reduce_tree(compute_moments); + stage_timer.reset(); + stage_timer.start(); + { + AXOM_ANNOTATE_SCOPE("bvh_initialization"); + const int ncurves = m_input_curves_view.size(); + axom::Array aabbs(ncurves, ncurves); + auto aabbs_view = aabbs.view(); + + axom::for_all( + ncurves, + AXOM_LAMBDA(axom::IndexType i) { aabbs_view[i] = m_input_curves_view[i].boundingBox(); }); + m_bvh.initialize(aabbs_view, ncurves); + } + stage_timer.stop(); + SLIC_INFO(axom::fmt::format(" Preprocessing stage (bvh initialization): {} s", + stage_timer.elapsedTimeInSec())); + + stage_timer.reset(); + stage_timer.start(); + { + AXOM_ANNOTATE_SCOPE("moment_precomputation"); + auto curves_view = m_input_curves_view; + auto compute_moments = [curves_view](std::int32_t currentNode, + const std::int32_t* leafNodes) -> GWNMoments { + const auto idx = leafNodes[currentNode]; + return GWNMoments(curves_view[idx]); // TODO: Avoid repeat normal calculation + }; + + const auto traverser = m_bvh.getTraverser(); + m_internal_moments = traverser.reduce_tree(compute_moments); + } + stage_timer.stop(); + SLIC_INFO(axom::fmt::format(" Preprocessing stage (moment precomputation): {} s", + stage_timer.elapsedTimeInSec())); } - SLIC_INFO(axom::fmt::format("Direct query preprocessing (loading curves{}): {} s", - use_memoization ? " and memoization caches" : "", + timer.stop(); + AXOM_ANNOTATE_METADATA("preprocessing_time", timer.elapsed(), ""); + SLIC_INFO(axom::fmt::format("NURBSCurve query preprocessing (loading curves{}{}): {} s", + use_memoization ? " and caches" : "", + !use_direct_eval ? " and bvh" : "", timer.elapsedTimeInSec())); } @@ -285,9 +310,10 @@ class DirectGWN2D const double ms_per_query = query_timer.elapsedTimeInMilliSec() / num_query_points; SLIC_INFO(axom::fmt::format( axom::utilities::locale(), - "Querying {:L} samples in winding number field with{} memoization took {:.3Lf} seconds" + "Querying {:L} samples in winding number field via {} with{} memoization took {:.3Lf} seconds" " (@ {:.0Lf} queries per second; {:.6Lf} ms per query)", num_query_points, + m_bvh.isInitialized() ? "fast approximation" : "direct evaluation", m_nurbs_cache_mgr.empty() ? "out" : "", query_time_s, num_query_points / query_time_s, @@ -307,7 +333,7 @@ class DirectGWN2D }; template -class PolylineGWN2D +class PolylineGWNQuery { public: using Point2D = axom::primal::Point; @@ -316,12 +342,12 @@ class PolylineGWN2D using SegmentType = axom::primal::Segment; using GWNMoments = axom::quest::GWNMomentData; - PolylineGWN2D() = default; + PolylineGWNQuery() = default; /// \brief Load polyline data into primal::Segments. /// If fast-approximation is used, construct BVH void preprocess(axom::mint::UnstructuredMesh* poly_mesh, - bool useDirectEval) + bool use_direct_eval) { if(poly_mesh == nullptr || poly_mesh->getNumberOfCells() <= 0) { @@ -351,16 +377,16 @@ class PolylineGWN2D }); } stage_timer.stop(); - SLIC_INFO(axom::fmt::format(" Preprocessing stage (extract_segments): {} s", + SLIC_INFO(axom::fmt::format(" Preprocessing stage (segment extraction): {} s", stage_timer.elapsedTimeInSec())); // If direct evaluation is preferred, skip BVH initialization - if(!useDirectEval) + if(!use_direct_eval) { stage_timer.reset(); stage_timer.start(); { - AXOM_ANNOTATE_SCOPE("bvh_init"); + AXOM_ANNOTATE_SCOPE("bvh_initialization"); const int nlines = m_segments.size(); axom::Array aabbs(nlines, nlines); auto aabbs_view = aabbs.view(); @@ -374,13 +400,13 @@ class PolylineGWN2D m_bvh.initialize(aabbs_view, nlines); } stage_timer.stop(); - SLIC_INFO( - axom::fmt::format(" Preprocessing stage (bvh): {} s", stage_timer.elapsedTimeInSec())); + SLIC_INFO(axom::fmt::format(" Preprocessing stage (bvh initialization): {} s", + stage_timer.elapsedTimeInSec())); stage_timer.reset(); stage_timer.start(); { - AXOM_ANNOTATE_SCOPE("moments"); + AXOM_ANNOTATE_SCOPE("moment_precomputation"); const auto segments_view = m_segments.view(); auto compute_moments = [segments_view](std::int32_t currentNode, @@ -393,11 +419,15 @@ class PolylineGWN2D m_internal_moments = traverser.template reduce_tree(compute_moments); } stage_timer.stop(); - SLIC_INFO( - axom::fmt::format(" Preprocessing stage (moments): {} s", stage_timer.elapsedTimeInSec())); + SLIC_INFO(axom::fmt::format(" Preprocessing stage (moment precomputation): {} s", + stage_timer.elapsedTimeInSec())); } + timer.stop(); - SLIC_INFO(axom::fmt::format("Total preprocessing: {} s", timer.elapsedTimeInSec())); + AXOM_ANNOTATE_METADATA("preprocessing_time", timer.elapsed(), ""); + SLIC_INFO(axom::fmt::format("Polyline preprocessing (loading segments{}): {} s", + !use_direct_eval ? " and bvh" : "", + timer.elapsedTimeInSec())); } /*! @@ -886,12 +916,12 @@ template struct gwn_input_traits; template -struct gwn_input_traits> +struct gwn_input_traits> : std::integral_constant { }; template -struct gwn_input_traits> +struct gwn_input_traits> : std::integral_constant { }; diff --git a/src/axom/quest/examples/quest_winding_number_2d.cpp b/src/axom/quest/examples/quest_winding_number_2d.cpp index 5e29371646..5fa41fbd95 100644 --- a/src/axom/quest/examples/quest_winding_number_2d.cpp +++ b/src/axom/quest/examples/quest_winding_number_2d.cpp @@ -263,7 +263,7 @@ class Input { public: std::string inputFile; - std::string outputPrefix = {"winding2d"}; + std::string outputPrefix = { "winding2d" }; bool verbose {false}; std::string annotationMode {"none"}; @@ -274,21 +274,21 @@ class Input axom::runtime_policy::Policy policy = RuntimePolicy::seq; - const std::array valid_algorithms {"direct", "fast-approximation"}; - std::string algorithm {valid_algorithms[1]}; // fast-approximation + const std::array valid_algorithms{ "direct", "fast_approximation" }; + std::string algorithm{ valid_algorithms[1] }; // fast-approximation - bool linearize {false}; - int approximation_order {2}; + bool linearize{ false }; + int approximation_order{ 2 }; bool useUniformLinearization; - int segmentsPerKnotSpan {10}; - double percentError {1.0}; + int segmentsPerKnotSpan{ 10 }; + double percentError{ 1.0 }; // Query mesh parameters std::vector boxMins; std::vector boxMaxs; std::vector boxResolution; - int queryOrder {1}; + int queryOrder{ 1 }; primal::WindingTolerances tol; @@ -329,6 +329,19 @@ class Input ->check(axom::CLI::PositiveNumber) ->capture_default_str(); + app.add_option("--algorithm", algorithm) + ->description( + "Use direct evaluation instead of fast, heirarchical approximation? (significantly " + "slower, slightly more precise)") + ->capture_default_str() + ->check(axom::CLI::IsMember(valid_algorithms)); + app + .add_option("--approximation-order", + approximation_order, + "The order of the Taylor expansion (lower is faster, less precise)") + ->expected(0, 2) + ->capture_default_str(); + #ifdef AXOM_USE_CALIPER app.add_option("--caliper", annotationMode) ->description( @@ -351,43 +364,31 @@ class Input // Options for triangulation of the input STEP file auto* linearize_curves_subcommand = app.add_subcommand("linearize_curves") - ->description("Options for linearizing NURBS curves. Default is ") - ->fallthrough(); + ->description("Options for linearizing NURBS curves. Default is ") + ->fallthrough(); auto* nsegments = linearize_curves_subcommand->add_option("--num-segments", segmentsPerKnotSpan) - ->description( - "Number of segments for each knot span of each input curve for a uniform linearization.") - ->check(axom::CLI::PositiveNumber) - ->capture_default_str(); + ->description( + "Number of segments for each knot span of each input curve for a uniform linearization.") + ->check(axom::CLI::PositiveNumber) + ->capture_default_str(); auto* perror = linearize_curves_subcommand->add_option("--percent-error", percentError) - ->description( - "The percent of error that is acceptable to stop refinement during non-uniform " - "linearization.") - ->check(axom::CLI::Range(0.0f, 100.0f)) - ->capture_default_str(); - linearize_curves_subcommand->add_option("--algorithm", algorithm) ->description( - "Use direct evaluation instead of fast, heirarchical approximation? (significantly " - "slower, slightly more precise)") - ->capture_default_str() - ->check(axom::CLI::IsMember(valid_algorithms)); - linearize_curves_subcommand - ->add_option("--approximation-order", - approximation_order, - "The order of the Taylor expansion (lower is faster, less precise)") - ->expected(0, 2) + "The percent of error that is acceptable to stop refinement during non-uniform " + "linearization.") + ->check(axom::CLI::Range(0.0f, 100.0f)) ->capture_default_str(); auto* query_mesh_subcommand = app.add_subcommand("query_mesh")->description("Options for setting up a query mesh")->fallthrough(); auto* minbb = query_mesh_subcommand->add_option("--min", boxMins) - ->description("Min bounds for box mesh (x,y)") - ->expected(2); + ->description("Min bounds for box mesh (x,y)") + ->expected(2); auto* maxbb = query_mesh_subcommand->add_option("--max", boxMaxs) - ->description("Max bounds for box mesh (x,y)") - ->expected(2); + ->description("Max bounds for box mesh (x,y)") + ->expected(2); query_mesh_subcommand->add_option("--res", boxResolution) ->description("Resolution of the box mesh (i,j)") ->expected(2) @@ -410,44 +411,44 @@ class Input } }; -using GWNQueryType = std::variant, - axom::quest::PolylineGWN2D, - axom::quest::PolylineGWN2D, - axom::quest::PolylineGWN2D +using GWNQueryType = std::variant, + axom::quest::PolylineGWNQuery, + axom::quest::PolylineGWNQuery, + axom::quest::PolylineGWNQuery #if defined(AXOM_USE_RAJA) && defined(AXOM_USE_OPENMP) - , - axom::quest::DirectGWN2D, - axom::quest::PolylineGWN2D, - axom::quest::PolylineGWN2D, - axom::quest::PolylineGWN2D + , + axom::quest::NURBSCurveGWNQuery, + axom::quest::PolylineGWNQuery, + axom::quest::PolylineGWNQuery, + axom::quest::PolylineGWNQuery #endif - >; +>; template GWNQueryType pick_gwn_method(bool linearize_curves, int approximation_order) { - if(linearize_curves) + if (linearize_curves) { - if(approximation_order == 0) + if (approximation_order == 0) { - return axom::quest::PolylineGWN2D {}; + return axom::quest::PolylineGWNQuery {}; } - else if(approximation_order == 1) + else if (approximation_order == 1) { - return axom::quest::PolylineGWN2D {}; + return axom::quest::PolylineGWNQuery {}; } else // approximation_order == 2 { - return axom::quest::PolylineGWN2D {}; + return axom::quest::PolylineGWNQuery {}; } } - return axom::quest::DirectGWN2D {}; + return axom::quest::NURBSCurveGWNQuery {}; } GWNQueryType make_gwn_query(axom::runtime_policy::Policy policy, - bool linearize_curves, - int approximation_order) + bool linearize_curves, + int approximation_order) { #if defined(AXOM_USE_RAJA) && defined(AXOM_USE_OPENMP) if(policy == RuntimePolicy::omp) @@ -467,15 +468,15 @@ int main(int argc, char** argv) // Parse command line arguments into input Input input; - axom::CLI::App app { + axom::CLI::App app{ "Load mesh containing collection of curves" - " and optionally generate a query mesh of winding numbers."}; + " and optionally generate a query mesh of winding numbers." }; try { input.parse(argc, argv, app); } - catch(const axom::CLI::ParseError& e) + catch (const axom::CLI::ParseError& e) { return app.exit(e); } @@ -500,7 +501,7 @@ int main(int argc, char** argv) mfem_reader.setFileName(input.inputFile); const int ret = mfem_reader.read(curves); - if(ret != axom::quest::MFEMReader::READ_SUCCESS) + if (ret != axom::quest::MFEMReader::READ_SUCCESS) { SLIC_ERROR("Failed to read MFEM file."); return 1; @@ -509,13 +510,13 @@ int main(int argc, char** argv) // Linearize the input curves if asked for axom::mint::UnstructuredMesh poly_mesh(2, axom::mint::SEGMENT); - if(input.linearize) + if (input.linearize) { AXOM_ANNOTATE_SCOPE("linearization"); axom::utilities::Timer timer(true); axom::quest::LinearizeCurves lc; - if(input.useUniformLinearization) + if (input.useUniformLinearization) { lc.getLinearMeshUniform(curves.view(), &poly_mesh, input.segmentsPerKnotSpan); } @@ -536,14 +537,14 @@ int main(int argc, char** argv) } // Early return if user didn't set up a query mesh - if(input.boxResolution.empty()) + if (input.boxResolution.empty()) { return 0; } // Extract the curves and compute their bounding boxes along the way BoundingBox2D shape_bbox; - for(const auto& cur : curves) + for (const auto& cur : curves) { shape_bbox.addBox(cur.boundingBox()); } @@ -559,21 +560,21 @@ int main(int argc, char** argv) // Generate the query grid and fields quest::generate_gwn_query_mesh(dc, - shape_bbox, - input.boxMins, - input.boxMaxs, - input.boxResolution, - input.queryOrder); + shape_bbox, + input.boxMins, + input.boxMaxs, + input.boxResolution, + input.queryOrder); // Run the preprocess std::visit( [&](auto& wn) { using T = std::decay_t; - if constexpr(quest::gwn_input_type_v == quest::GWNInputType::Curve) + if constexpr (quest::gwn_input_type_v == quest::GWNInputType::Curve) { - wn.preprocess(curves, input.memoized); + wn.preprocess(curves, input.algorithm == "direct", input.memoized); } - else if constexpr(quest::gwn_input_type_v == quest::GWNInputType::Polyline) + else if constexpr (quest::gwn_input_type_v == quest::GWNInputType::Polyline) { wn.preprocess(&poly_mesh, input.algorithm == "direct"); } @@ -585,7 +586,7 @@ int main(int argc, char** argv) } // Postprocess query results: norms, ranges, and integral statistics - if(input.stats) + if (input.stats) { AXOM_ANNOTATE_SCOPE("postprocess"); @@ -598,14 +599,14 @@ int main(int argc, char** argv) std::int64_t pos_inout_dofs = 0; std::int64_t neg_inout_dofs = 0; - for(int i = 0; i < inout.Size(); ++i) + for (int i = 0; i < inout.Size(); ++i) { const double v = inout[i]; - if(v > 0.0) + if (v > 0.0) { ++pos_inout_dofs; } - else if(v < 0.0) + else if (v < 0.0) { ++neg_inout_dofs; } @@ -613,11 +614,11 @@ int main(int argc, char** argv) SLIC_INFO( axom::fmt::format("WN_STATS: dof_l2={:.6e} dof_linf={:.6e} l2={:.6e} min={:.6e} max={:.6e}", - winding_stats.dof_l2, - winding_stats.dof_linf, - winding_stats.l2, - winding_stats.min, - winding_stats.max)); + winding_stats.dof_l2, + winding_stats.dof_linf, + winding_stats.l2, + winding_stats.min, + winding_stats.max)); SLIC_INFO(axom::fmt::format( "INOUT_STATS: dof_l2={:.6e} dof_linf={:.6e} l2={:.6e} min={:.6e} max={:.6e} volume={:.6e} " @@ -630,13 +631,13 @@ int main(int argc, char** argv) inout_integrals.integral, inout_integrals.domain_volume, (inout_integrals.domain_volume > 0.0 ? inout_integrals.integral / inout_integrals.domain_volume - : 0.0), + : 0.0), pos_inout_dofs, neg_inout_dofs)); } // Save the query mesh and fields to disk using a format that can be viewed in VisIt - if(input.vis) + if (input.vis) { AXOM_ANNOTATE_SCOPE("dump_mesh"); @@ -646,8 +647,8 @@ int main(int argc, char** argv) windingDC.Save(); SLIC_INFO(axom::fmt::format("Outputting generated mesh '{}' to '{}'", - windingDC.GetCollectionName(), - axom::utilities::filesystem::getCWD())); + windingDC.GetCollectionName(), + axom::utilities::filesystem::getCWD())); } return 0; diff --git a/src/axom/quest/tests/quest_gwn_methods.cpp b/src/axom/quest/tests/quest_gwn_methods.cpp index f366b73e3e..d9b0bc67df 100644 --- a/src/axom/quest/tests/quest_gwn_methods.cpp +++ b/src/axom/quest/tests/quest_gwn_methods.cpp @@ -187,12 +187,12 @@ void check_mfem_mesh_linearization() const std::vector resolution {10, 10}; const int query_order = 1; - // Generate three query grids and fields - axom::Array dc(0, 3); - std::string names[] = {"direct", "polyline", "polyline_fast"}; - for(int i = 0; i < 3; ++i) + // Generate query grids and fields + constexpr int num_queries = 6; + axom::Array dc(0, num_queries); + for(int i = 0; i < num_queries; ++i) { - dc.emplace_back(axom::fmt::format("gwn_{}", names[i])); + dc.emplace_back(axom::fmt::format("gwn_method_{}", i)); axom::quest::generate_gwn_query_mesh(dc[i], shape_bbox, std::vector {}, @@ -203,44 +203,54 @@ void check_mfem_mesh_linearization() // Create tolerance object axom::primal::WindingTolerances tol; - constexpr bool useDirectPolyline = true; + constexpr bool useDirectEvaluation = true; + constexpr bool useMemoization = true; - //// Run three different kinds of GWN query //// + //// Run six different kinds of GWN query //// // We expect all three fields to return the same values in this case because // of the specific arrangement of query points and linearization. // In general, discretizing the shape can result in different GWN values // for query points near to individual curves. - // Direct - SLIC_INFO("Testing Direct Evaluation"); - axom::quest::DirectGWN2D gwn_direct {}; - gwn_direct.preprocess(curves); - gwn_direct.query(dc[0], tol); + SLIC_INFO("Testing Curve Evaluation"); + axom::quest::NURBSCurveGWNQuery gwn_curves {}; + gwn_curves.preprocess(curves, useDirectEvaluation, !useMemoization); + gwn_curves.query(dc[0], tol); - // Linearized - SLIC_INFO("Testing Direct Evaluation of Triangulation"); - axom::quest::PolylineGWN2D gwn_polyline {}; - gwn_polyline.preprocess(&poly_mesh, useDirectPolyline); - gwn_polyline.query(dc[1], tol); + axom::quest::NURBSCurveGWNQuery gwn_curves_memoized {}; + gwn_curves_memoized.preprocess(curves, useDirectEvaluation, useMemoization); + gwn_curves_memoized.query(dc[1], tol); - // Linearized, fast approximation - SLIC_INFO("Testing Fast-Approximate Evaluation of Triangulation"); - axom::quest::PolylineGWN2D gwn_polyline_fast {}; - gwn_polyline_fast.preprocess(&poly_mesh, !useDirectPolyline); - gwn_polyline_fast.query(dc[2], tol); + axom::quest::NURBSCurveGWNQuery gwn_curves_fast {}; + gwn_curves_fast.preprocess(curves, !useDirectEvaluation, !useMemoization); + gwn_curves_fast.query(dc[2], tol); - // Compare the in-out values between the three fields + axom::quest::NURBSCurveGWNQuery gwn_curves_fast_memoized {}; + gwn_curves_fast_memoized.preprocess(curves, !useDirectEvaluation, useMemoization); + gwn_curves_fast_memoized.query(dc[3], tol); + + SLIC_INFO("Testing Linearization Evaluation"); + axom::quest::PolylineGWNQuery gwn_polyline {}; + gwn_polyline.preprocess(&poly_mesh, useDirectEvaluation); + gwn_polyline.query(dc[4], tol); + + axom::quest::PolylineGWNQuery gwn_polyline_fast {}; + gwn_polyline_fast.preprocess(&poly_mesh, !useDirectEvaluation); + gwn_polyline_fast.query(dc[5], tol); + + // Compare the in-out values between all fields const auto *query_mesh = dc[0].GetMesh(); const auto num_query_points = query_mesh->GetNodalFESpace()->GetNDofs(); auto &inout_direct = *dc[0].GetField("inout"); - auto &inout_polyline = *dc[1].GetField("inout"); - auto &inout_polyline_fast = *dc[2].GetField("inout"); - - for(int i = 0; i < num_query_points; ++i) + for(int N = 1; N < num_queries; ++N) { - EXPECT_EQ(inout_direct[i], inout_polyline[i]); - EXPECT_EQ(inout_direct[i], inout_polyline_fast[i]); + auto &inout_other = *dc[N].GetField("inout"); + + for(int i = 0; i < num_query_points; ++i) + { + EXPECT_EQ(inout_direct[i], inout_other[i]); + } } } From 84da6af30d371642d21c453de872572db31410db Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Tue, 31 Mar 2026 22:44:03 -0700 Subject: [PATCH 229/986] Added with some broken const views --- .../detail/winding_number_2d_memoization.hpp | 16 +- .../detail/winding_number_3d_memoization.hpp | 20 +- src/axom/quest/FastApproximateGWN.hpp | 37 ++- src/axom/quest/GWNMethods.hpp | 246 +++++++++++++----- .../examples/quest_winding_number_3d.cpp | 220 ++++++++-------- src/axom/quest/tests/quest_gwn_methods.cpp | 65 +++-- 6 files changed, 383 insertions(+), 221 deletions(-) diff --git a/src/axom/primal/operators/detail/winding_number_2d_memoization.hpp b/src/axom/primal/operators/detail/winding_number_2d_memoization.hpp index 5055c6215b..02911a88f0 100644 --- a/src/axom/primal/operators/detail/winding_number_2d_memoization.hpp +++ b/src/axom/primal/operators/detail/winding_number_2d_memoization.hpp @@ -264,16 +264,16 @@ class NURBSCurveCacheManager { using NURBSCache = axom::primal::detail::NURBSCurveGWNCache; using NURBSCacheArray = axom::Array; - using NURBSCacheArrayView = axom::ArrayView; + using NURBSCacheArrayView = axom::ArrayView; - using CurveArrayView = axom::ArrayView>; + using CurveArrayView = axom::ArrayView>; public: NURBSCurveCacheManager() = default; - NURBSCurveCacheManager(const CurveArrayView& curves, double bbExpansionAmount = 0.0) + NURBSCurveCacheManager(CurveArrayView curves, double bbExpansionAmount = 0.0) { - for(const auto& curve : curves) + for(auto& curve : curves) { m_nurbs_caches.push_back(NURBSCache(curve, bbExpansionAmount)); } @@ -289,7 +289,7 @@ class NURBSCurveCacheManager }; /// Return a view of this manager to pass into a device function. - View view() const { return View {m_nurbs_caches.view()}; } + View view() { return View {m_nurbs_caches.view()}; } /// Return if the underlying array is empty bool empty() const { return m_nurbs_caches.empty(); } @@ -312,10 +312,10 @@ class NURBSCurveCacheManagerOMP { using NURBSCache = axom::primal::detail::NURBSCurveGWNCache; using NURBSCachePerThreadArray = axom::Array>; - using NURBSCachePerThreadArrayView = axom::ArrayView>; + using NURBSCachePerThreadArrayView = axom::ArrayView>; using NURBSCacheArrayView = axom::ArrayView; - using CurveArrayView = axom::ArrayView>; + using CurveArrayView = axom::ArrayView>; public: NURBSCurveCacheManagerOMP() = default; @@ -351,7 +351,7 @@ class NURBSCurveCacheManagerOMP }; /// Return a view of this manager to pass into a device function. - View view() const { return View {m_nurbs_caches.view()}; } + View view() { return View {m_nurbs_caches.view()}; } /// Return if the underlying array is empty bool empty() const { return m_nurbs_caches.empty(); } diff --git a/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp b/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp index 6dc8678fd8..76ec6cf37a 100644 --- a/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp +++ b/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp @@ -302,16 +302,16 @@ class NURBSPatchCacheManager { using NURBSCache = axom::primal::detail::NURBSPatchGWNCache; using NURBSCacheArray = axom::Array; - using NURBSCacheArrayView = axom::ArrayView; + using NURBSCacheArrayView = axom::ArrayView; - using PatchArrayView = axom::ArrayView>; + using PatchArrayView = axom::ArrayView>; public: NURBSPatchCacheManager() = default; - NURBSPatchCacheManager(const PatchArrayView& patchs) + NURBSPatchCacheManager(PatchArrayView patchs) { - for(const auto& patch : patchs) + for(auto& patch : patchs) { m_nurbs_caches.push_back(NURBSCache(patch)); } @@ -327,7 +327,7 @@ class NURBSPatchCacheManager }; /// Return a view of this manager to pass into a device function. - View view() const { return View {m_nurbs_caches.view()}; } + View view() { return View {m_nurbs_caches.view()}; } /// Return if the underlying array is empty bool empty() const { return m_nurbs_caches.empty(); } @@ -350,15 +350,15 @@ class NURBSPatchCacheManagerOMP { using NURBSCache = axom::primal::detail::NURBSPatchGWNCache; using NURBSCachePerThreadArray = axom::Array>; - using NURBSCachePerThreadArrayView = axom::ArrayView>; - using NURBSCacheArrayView = axom::ArrayView; + using NURBSCachePerThreadArrayView = axom::ArrayView>; + using NURBSCacheArrayView = axom::ArrayView; - using PatchArrayView = axom::ArrayView>; + using PatchArrayView = axom::ArrayView>; public: NURBSPatchCacheManagerOMP() = default; - NURBSPatchCacheManagerOMP(const PatchArrayView& patches) + NURBSPatchCacheManagerOMP(PatchArrayView patches) { const int nt = omp_get_max_threads(); m_nurbs_caches.resize(nt); @@ -397,7 +397,7 @@ class NURBSPatchCacheManagerOMP }; /// Return a view of this manager to pass into a device function. - View view() const { return View {m_nurbs_caches.view()}; } + View view() { return View {m_nurbs_caches.view()}; } /// Return if the underlying array is empty bool empty() const { return m_nurbs_caches.empty(); } diff --git a/src/axom/quest/FastApproximateGWN.hpp b/src/axom/quest/FastApproximateGWN.hpp index bf1b7f5648..79f98421f2 100644 --- a/src/axom/quest/FastApproximateGWN.hpp +++ b/src/axom/quest/FastApproximateGWN.hpp @@ -126,6 +126,22 @@ class GWNMomentData compute_coefficients(); } + /// Construct moments from a trimmed NURBS surface + explicit GWNMomentData(const axom::primal::NURBSPatch& a_patch) + { + // Track the centroid across the tree, and return the rest of the data + auto patch_data = a_patch.calculateSurfaceMoments(m_order); + + for(int i = 0; i < NumberOfEntries; ++i) rm[i] = patch_data[i]; + + a = patch_data[39]; + ax = patch_data[40]; + ay = patch_data[41]; + az = patch_data[42]; + + compute_coefficients(); + } + /// Construct moments from the endpoints of a 2D segment explicit GWNMomentData(const axom::primal::NURBSCurve& c) : GWNMomentData(c.getInitPoint(), c.getEndPoint()) @@ -409,7 +425,8 @@ double fast_approximate_winding_number(const primal::Point& query, }; if constexpr(std::is_same_v> || - std::is_same_v>) + std::is_same_v> || + std::is_same_v>) { auto leaf_gwn = [&query, &gwn, leaf_objects_view, &wt](std::int32_t currentNode, const std::int32_t* leafNodes) -> void { @@ -430,6 +447,24 @@ double fast_approximate_winding_number(const primal::Point& query, traverser.traverse_tree(query, leaf_gwn, bbContain); } + + if constexpr(std::is_same_v> || + std::is_same_v>) + { + auto leaf_gwn = [&query, &gwn, leaf_objects_view, &wt](std::int32_t currentNode, + const std::int32_t* leafNodes) -> void { + const auto idx = leafNodes[currentNode]; + gwn += axom::primal::winding_number(q, + leaf_objects_view[idx], + tol_copy.edge_tol, + tol_copy.ls_tol, + tol_copy.quad_tol, + tol_copy.disk_size, + tol_copy.EPS); + }; + + traverser.traverse_tree(query, leaf_gwn, bbContain); + } // Support for other leaf types forthcoming... return gwn; diff --git a/src/axom/quest/GWNMethods.hpp b/src/axom/quest/GWNMethods.hpp index 44020e2062..a9b4171ba3 100644 --- a/src/axom/quest/GWNMethods.hpp +++ b/src/axom/quest/GWNMethods.hpp @@ -194,15 +194,14 @@ class NURBSCurveGWNQuery stage_timer.start(); { AXOM_ANNOTATE_SCOPE("moment_precomputation"); - auto curves_view = m_input_curves_view; - auto compute_moments = [curves_view](std::int32_t currentNode, - const std::int32_t* leafNodes) -> GWNMoments { + auto compute_moments = [=](std::int32_t currentNode, + const std::int32_t* leafNodes) -> GWNMoments { const auto idx = leafNodes[currentNode]; - return GWNMoments(curves_view[idx]); // TODO: Avoid repeat normal calculation + return GWNMoments(m_input_curves_view[idx]); }; const auto traverser = m_bvh.getTraverser(); - m_internal_moments = traverser.reduce_tree(compute_moments); + m_internal_moments = traverser.template reduce_tree(compute_moments); } stage_timer.stop(); SLIC_INFO(axom::fmt::format(" Preprocessing stage (moment precomputation): {} s", @@ -261,15 +260,34 @@ class NURBSCurveGWNQuery const auto traverser = m_bvh.getTraverser(); const auto internal_moments_view = m_internal_moments.view(); - axom::for_all(num_query_points, [=, &winding, &inout](axom::IndexType index) { - const double wn = axom::quest::fast_approximate_winding_number(query_point(index), - traverser, - m_input_curves_view, - internal_moments_view, - tol_copy); - winding[static_cast(index)] = wn; - inout[static_cast(index)] = std::lround(wn); - }); + // Use fast-approximate, non-memoized form + if(m_nurbs_cache_mgr.empty()) + { + axom::for_all(num_query_points, [=, &winding, &inout](axom::IndexType index) { + const double wn = axom::quest::fast_approximate_winding_number(query_point(index), + traverser, + m_input_curves_view, + internal_moments_view, + tol_copy); + winding[static_cast(index)] = wn; + inout[static_cast(index)] = std::lround(wn); + }); + } + // Use fast-approximate, memoized form + else + { + const auto cache_mgr_view = m_nurbs_cache_mgr.view(); + axom::for_all(num_query_points, [=, &winding, &inout](axom::IndexType index) { + const auto nurbs_cache_view = cache_mgr_view.cache(); + const double wn = axom::quest::fast_approximate_winding_number(query_point(index), + traverser, + nurbs_cache_view, + internal_moments_view, + tol_copy); + winding[static_cast(index)] = wn; + inout[static_cast(index)] = std::lround(wn); + }); + } } // Use direct formula else @@ -531,19 +549,25 @@ class PolylineGWNQuery ///@{ /// \name Query methods for 3D GWN applications -template -class DirectGWN3D +template +class NURBSPatchGWNQuery { public: - using PatchArrayType = axom::Array>; + using BoxType = axom::primal::BoundingBox; + using GWNMoments = axom::quest::GWNMomentData; + + using PatchType = axom::primal::NURBSPatch; + using PatchArrayType = axom::Array; using NURBSCacheArray = axom::Array>; using NURBSCacheManager = typename axom::primal::nurbs_cache_3d_traits::type; - DirectGWN3D() = default; + NURBSPatchGWNQuery() = default; /// \brief Define view for NURBS data. /// If memoization is used, allocate a cache for each patch. - void preprocess(const PatchArrayType& input_patches, bool use_memoization = true) + void preprocess(const PatchArrayType& input_patches, + bool use_direct_eval = false, + bool use_memoization = true) { m_input_patches_view = input_patches.view(); if(m_input_patches_view.size() <= 0) @@ -553,17 +577,65 @@ class DirectGWN3D } axom::utilities::Timer timer(true); + axom::utilities::Timer stage_timer(false); + + AXOM_ANNOTATE_SCOPE("preprocessing"); + + if(use_memoization) { - AXOM_ANNOTATE_SCOPE("preprocessing"); - if(use_memoization) + stage_timer.start(); { + AXOM_ANNOTATE_SCOPE("cache_initialization"); m_nurbs_cache_mgr = NURBSCacheManager(input_patches); } + stage_timer.stop(); + SLIC_INFO(axom::fmt::format(" Preprocessing stage (cache initialization): {} s", + stage_timer.elapsedTimeInSec())); + } + + if(!use_direct_eval) + { + stage_timer.restart(); + stage_timer.start(); + { + AXOM_ANNOTATE_SCOPE("bvh_initialization"); + const int npatches = m_input_patches_view.size(); + axom::Array aabbs(npatches, npatches); + auto aabbs_view = aabbs.view(); + + axom::for_all( + npatches, + AXOM_LAMBDA(axom::IndexType i) { aabbs_view[i] = m_input_patches_view[i].boundingBox(); }); + m_bvh.initialize(aabbs_view, npatches); + } + stage_timer.stop(); + SLIC_INFO(axom::fmt::format(" Preprocessing stage (bvh initialization): {} s", + stage_timer.elapsedTimeInSec())); + + stage_timer.reset(); + stage_timer.start(); + { + AXOM_ANNOTATE_SCOPE("moment_precomputation"); + auto patches_view = m_input_patches_view; + auto compute_moments = [patches_view](std::int32_t currentNode, + const std::int32_t* leafNodes) -> GWNMoments { + const auto idx = leafNodes[currentNode]; + return GWNMoments(patches_view[idx]); // TODO: Avoid repeat normal calculation + }; + + const auto traverser = m_bvh.getTraverser(); + m_internal_moments = traverser.reduce_tree(compute_moments); + } + stage_timer.stop(); + SLIC_INFO(axom::fmt::format(" Preprocessing stage (moment precomputation): {} s", + stage_timer.elapsedTimeInSec())); } + timer.stop(); AXOM_ANNOTATE_METADATA("preprocessing_time", timer.elapsed(), ""); - SLIC_INFO(axom::fmt::format("Direct query preprocessing (loading surfaces{}): {} s", - use_memoization ? " and memoization caches" : "", + SLIC_INFO(axom::fmt::format("NURBSPatch query preprocessing (loading patches{}{}): {} s", + use_memoization ? " and caches" : "", + !use_direct_eval ? " and bvh" : "", timer.elapsedTimeInSec())); } @@ -608,46 +680,84 @@ class DirectGWN3D { AXOM_ANNOTATE_SCOPE("query"); const primal::WindingTolerances tol_copy = tol; - const auto input_patches_view = m_input_patches_view; - // Use non-memoized form - if(m_nurbs_cache_mgr.empty()) + // Use fast approximation + if(m_bvh.isInitialized()) { - axom::for_all(num_query_points, [=, &winding, &inout](axom::IndexType nidx) { - const auto q = query_point(static_cast(nidx)); - double wn {}; - for(const auto& patch : input_patches_view) - { - wn += axom::primal::winding_number(q, - patch, - tol_copy.edge_tol, - tol_copy.ls_tol, - tol_copy.quad_tol, - tol_copy.disk_size, - tol_copy.EPS); - } - winding[static_cast(nidx)] = wn; - inout[static_cast(nidx)] = std::lround(wn); - }); + const auto traverser = m_bvh.getTraverser(); + auto internal_moments_view = m_internal_moments.view(); + + // Use fast-approximate, non-memoized form + if(m_nurbs_cache_mgr.empty()) + { + axom::for_all(num_query_points, [=, &winding, &inout](axom::IndexType index) { + const double wn = axom::quest::fast_approximate_winding_number(query_point(index), + traverser, + m_input_patches_view, + internal_moments_view, + tol_copy); + winding[static_cast(index)] = wn; + inout[static_cast(index)] = std::lround(wn); + }); + } + // Use fast-approximate, memoized form + else + { + const auto cache_mgr_view = m_nurbs_cache_mgr.view(); + axom::for_all(num_query_points, [=, &winding, &inout](axom::IndexType index) { + const auto nurbs_cache_view = cache_mgr_view.caches(); + const double wn = axom::quest::fast_approximate_winding_number(query_point(index), + traverser, + nurbs_cache_view, + internal_moments_view, + tol_copy); + winding[static_cast(index)] = wn; + inout[static_cast(index)] = std::lround(wn); + }); + } } - else // Use memoized form + // Use direct formula + else { - const auto cache_mgr_view = m_nurbs_cache_mgr.view(); - axom::for_all(num_query_points, [=, &winding, &inout](axom::IndexType nidx) { - const auto q = query_point(static_cast(nidx)); - const auto caches_view = cache_mgr_view.caches(); - - const double wn = axom::primal::winding_number(q, - caches_view, - tol_copy.edge_tol, - tol_copy.ls_tol, - tol_copy.quad_tol, - tol_copy.disk_size, - tol_copy.EPS); - - winding[static_cast(nidx)] = wn; - inout[static_cast(nidx)] = std::lround(wn); - }); + // Use direct, non-memoized form + if(m_nurbs_cache_mgr.empty()) + { + axom::for_all(num_query_points, [=, &winding, &inout](axom::IndexType nidx) { + const auto q = query_point(static_cast(nidx)); + double wn {}; + for(const auto& patch : m_input_patches_view) + { + wn += axom::primal::winding_number(q, + patch, + tol_copy.edge_tol, + tol_copy.ls_tol, + tol_copy.quad_tol, + tol_copy.disk_size, + tol_copy.EPS); + } + winding[static_cast(nidx)] = wn; + inout[static_cast(nidx)] = std::lround(wn); + }); + } + else // Use direct, memoized form + { + const auto cache_mgr_view = m_nurbs_cache_mgr.view(); + axom::for_all(num_query_points, [=, &winding, &inout](axom::IndexType nidx) { + const auto q = query_point(static_cast(nidx)); + const auto caches_view = cache_mgr_view.caches(); + + const double wn = axom::primal::winding_number(q, + caches_view, + tol_copy.edge_tol, + tol_copy.ls_tol, + tol_copy.quad_tol, + tol_copy.disk_size, + tol_copy.EPS); + + winding[static_cast(nidx)] = wn; + inout[static_cast(nidx)] = std::lround(wn); + }); + } } } query_timer.stop(); @@ -656,9 +766,10 @@ class DirectGWN3D const double ms_per_query = query_timer.elapsedTimeInMilliSec() / num_query_points; SLIC_INFO(axom::fmt::format( axom::utilities::locale(), - "Querying {:L} samples in winding number field with{} memoization took {:.3Lf} seconds" + "Querying {:L} samples in winding number field via {} with{} memoization took {:.3Lf} seconds" " (@ {:.0Lf} queries per second; {:.6Lf} ms per query)", num_query_points, + m_bvh.isInitialized() ? "fast approximation" : "direct evaluation", m_nurbs_cache_mgr.empty() ? "out" : "", query_time_s, num_query_points / query_time_s, @@ -668,12 +779,17 @@ class DirectGWN3D } private: - axom::ArrayView> m_input_patches_view; + // For the input curves/BVH leaf nodes + axom::ArrayView m_input_patches_view; NURBSCacheManager m_nurbs_cache_mgr; + + // Only needed for fast approximation method + axom::Array m_internal_moments; + axom::spin::BVH<3, ExecSpace> m_bvh; }; template -class TriangleGWN3D +class TriangleGWNQuery { public: using Point3D = axom::primal::Point; @@ -681,7 +797,7 @@ class TriangleGWN3D using TriangleType = axom::primal::Triangle; using GWNMoments = axom::quest::GWNMomentData; - TriangleGWN3D() = default; + TriangleGWNQuery() = default; /// \brief Load mesh data into primal::Triangles. /// If fast-approximation is used, construct BVH @@ -926,12 +1042,12 @@ struct gwn_input_traits> { }; template -struct gwn_input_traits> +struct gwn_input_traits> : std::integral_constant { }; template -struct gwn_input_traits> +struct gwn_input_traits> : std::integral_constant { }; diff --git a/src/axom/quest/examples/quest_winding_number_3d.cpp b/src/axom/quest/examples/quest_winding_number_3d.cpp index e043e0c2a5..9657859c89 100644 --- a/src/axom/quest/examples/quest_winding_number_3d.cpp +++ b/src/axom/quest/examples/quest_winding_number_3d.cpp @@ -50,31 +50,31 @@ class Input { public: std::string inputFile; - std::string outputPrefix {"winding3d"}; + std::string outputPrefix{ "winding3d" }; - bool verbose {false}; - std::string annotationMode {"none"}; - bool memoized {true}; - bool vis {true}; - bool validate {false}; - bool stats {false}; + bool verbose{ false }; + std::string annotationMode{ "none" }; + bool memoized{ true }; + bool vis{ true }; + bool validate{ false }; + bool stats{ false }; axom::runtime_policy::Policy policy = RuntimePolicy::seq; - const std::array valid_algorithms {"direct", "fast-approximation"}; - std::string algorithm {valid_algorithms[1]}; // fast-approximation + const std::array valid_algorithms{ "direct", "fast-approximation" }; + std::string algorithm{ valid_algorithms[1] }; // fast-approximation - bool triangulate {false}; - double linear_deflection {0.1}; - double angular_deflection {0.5}; - bool deflection_is_relative {false}; - int approximation_order {2}; + bool triangulate{ false }; + double linear_deflection{ 0.1 }; + double angular_deflection{ 0.5 }; + bool deflection_is_relative{ false }; + int approximation_order{ 2 }; std::vector boxMins; std::vector boxMaxs; std::vector boxResolution; - int queryOrder {1}; - double sliceZ {0.0}; + int queryOrder{ 1 }; + double sliceZ{ 0.0 }; primal::WindingTolerances tol; @@ -101,8 +101,8 @@ class Input // Options for triangulation of the input STEP file auto* triangulate_step_subcommand = app.add_subcommand("triangulate_step") - ->description("Options for triangulating NURBS surfaces") - ->fallthrough(); + ->description("Options for triangulating NURBS surfaces") + ->fallthrough(); triangulate_step_subcommand->add_option("--linear-deflection", linear_deflection) ->description( @@ -121,19 +121,6 @@ class Input "Is linear deflection in relative to local edge lengths (true) or mesh units (false)") ->capture_default_str(); - triangulate_step_subcommand->add_option("--algorithm", algorithm) - ->description( - "Use direct evaluation instead of fast, heirarchical approximation? (significantly " - "slower, slightly more precise)") - ->capture_default_str() - ->check(axom::CLI::IsMember(valid_algorithms)); - triangulate_step_subcommand - ->add_option("--expansion-order", - approximation_order, - "The order of the Taylor expansion (lower is faster, less precise)") - ->expected(0, 2) - ->capture_default_str(); - // Options for query tolerances; for now, only expose the line search and quadrature tolerances app.add_option("--ls-tol", tol.ls_tol) ->description("Tolerance for line-surface intersection") @@ -156,6 +143,19 @@ class Input ->check(axom::CLI::PositiveNumber) ->capture_default_str(); + app.add_option("--algorithm", algorithm) + ->description( + "Use direct evaluation instead of fast, heirarchical approximation? (significantly " + "slower, slightly more precise)") + ->capture_default_str() + ->check(axom::CLI::IsMember(valid_algorithms)); + app + .add_option("--expansion-order", + approximation_order, + "The order of the Taylor expansion (lower is faster, less precise)") + ->expected(0, 2) + ->capture_default_str(); + #ifdef AXOM_USE_CALIPER app.add_option("--caliper", annotationMode) ->description( @@ -179,11 +179,11 @@ class Input app.add_subcommand("query_mesh")->description("Options for setting up a query mesh")->fallthrough(); auto* minbb = query_mesh_subcommand->add_option("--min", boxMins) - ->description("Min bounds for box mesh (x,y[,z])") - ->expected(2, 3); + ->description("Min bounds for box mesh (x,y[,z])") + ->expected(2, 3); auto* maxbb = query_mesh_subcommand->add_option("--max", boxMaxs) - ->description("Max bounds for box mesh (x,y[,z])") - ->expected(2, 3); + ->description("Max bounds for box mesh (x,y[,z])") + ->expected(2, 3); query_mesh_subcommand->add_option("--res", boxResolution) ->description("Resolution of the box mesh (i,j[,k])") ->expected(2, 3) @@ -201,21 +201,21 @@ class Input // let's also check that they're consistently sized w/ each other and with the resolution query_mesh_subcommand->callback([&]() { - if(const bool have_box = (minbb->count() > 0 || maxbb->count() > 0); have_box) + if (const bool have_box = (minbb->count() > 0 || maxbb->count() > 0); have_box) { - if(boxMins.size() != boxMaxs.size()) + if (boxMins.size() != boxMaxs.size()) { throw axom::CLI::ValidationError( "--min/--max", axom::fmt::format("must have the same number of values (2 for 2D or 3 for 3D). " - "Got --min={}, --max={}", - boxMins.size(), - boxMaxs.size())); + "Got --min={}, --max={}", + boxMins.size(), + boxMaxs.size())); } - for(size_t d = 0; d < boxMins.size(); ++d) + for (size_t d = 0; d < boxMins.size(); ++d) { - if(boxMins[d] >= boxMaxs[d]) + if (boxMins[d] >= boxMaxs[d]) { throw axom::CLI::ValidationError( "--min/--max", @@ -227,7 +227,7 @@ class Input } } - if(boxResolution.size() != boxMins.size()) + if (boxResolution.size() != boxMins.size()) { throw axom::CLI::ValidationError( "--res", @@ -237,7 +237,7 @@ class Input boxMins.size())); } } - }); + }); app.parse(argc, argv); @@ -245,44 +245,44 @@ class Input } }; -using GWNQueryType = std::variant, - axom::quest::TriangleGWN3D, - axom::quest::TriangleGWN3D, - axom::quest::TriangleGWN3D +using GWNQueryType = std::variant, + axom::quest::TriangleGWNQuery, + axom::quest::TriangleGWNQuery, + axom::quest::TriangleGWNQuery #if defined(AXOM_USE_RAJA) && defined(AXOM_USE_OPENMP) - , - axom::quest::DirectGWN3D, - axom::quest::TriangleGWN3D, - axom::quest::TriangleGWN3D, - axom::quest::TriangleGWN3D + , + axom::quest::NURBSPatchGWNQuery, + axom::quest::TriangleGWNQuery, + axom::quest::TriangleGWNQuery, + axom::quest::TriangleGWNQuery #endif - >; +>; template GWNQueryType pick_gwn_method(bool triangulate, int approximation_order) { - if(triangulate) + if (triangulate) { - if(approximation_order == 0) + if (approximation_order == 0) { - return axom::quest::TriangleGWN3D {}; + return axom::quest::TriangleGWNQuery {}; } - else if(approximation_order == 1) + else if (approximation_order == 1) { - return axom::quest::TriangleGWN3D {}; + return axom::quest::TriangleGWNQuery {}; } else // approximation_order == 2 { - return axom::quest::TriangleGWN3D {}; + return axom::quest::TriangleGWNQuery {}; } } - return axom::quest::DirectGWN3D {}; + return axom::quest::NURBSPatchGWNQuery {}; } GWNQueryType make_gwn_query(axom::runtime_policy::Policy policy, - bool triangulate, - int approximation_order) + bool triangulate, + int approximation_order) { #if defined(AXOM_USE_RAJA) && defined(AXOM_USE_OPENMP) if(policy == RuntimePolicy::omp) @@ -302,15 +302,15 @@ int main(int argc, char** argv) // Parse command line arguments into input Input input; - axom::CLI::App app { + axom::CLI::App app{ "Load a STEP file containing trimmed NURBS patches " - "and optionally generate a query grid of generalized winding numbers."}; + "and optionally generate a query grid of generalized winding numbers." }; try { input.parse(argc, argv, app); } - catch(const axom::CLI::ParseError& e) + catch (const axom::CLI::ParseError& e) { return app.exit(e); } @@ -325,7 +325,7 @@ int main(int argc, char** argv) axom::mint::UnstructuredMesh tri_mesh(3, axom::mint::TRIANGLE); axom::Array patches; - if(axom::utilities::string::endsWith(input.inputFile, ".stl")) + if (axom::utilities::string::endsWith(input.inputFile, ".stl")) { AXOM_ANNOTATE_SCOPE("read_stl"); @@ -335,7 +335,7 @@ int main(int argc, char** argv) axom::utilities::Timer read_timer(true); const int ret = stl_reader.read(); - if(ret != 0) + if (ret != 0) { SLIC_ERROR(axom::fmt::format("Failed to read STL file '{}'", input.inputFile)); return 1; @@ -348,15 +348,15 @@ int main(int argc, char** argv) axom::mint::for_all_nodes( &tri_mesh, AXOM_LAMBDA(axom::IndexType, double x, double y, double z) { - shape_bbox_ptr->addPoint(Point3D {x, y, z}); - }); + shape_bbox_ptr->addPoint(Point3D{ x, y, z }); + }); SLIC_INFO(axom::fmt::format(axom::utilities::locale(), - "Loaded {} triangles in {:.3Lf} seconds", - stl_reader.getNumFaces(), - read_timer.elapsed())); + "Loaded {} triangles in {:.3Lf} seconds", + stl_reader.getNumFaces(), + read_timer.elapsed())); } - else if(axom::utilities::string::endsWith(input.inputFile, ".step")) + else if (axom::utilities::string::endsWith(input.inputFile, ".step")) { AXOM_ANNOTATE_SCOPE("read_step"); @@ -366,7 +366,7 @@ int main(int argc, char** argv) axom::utilities::Timer read_timer(true); const int ret = step_reader.read(input.validate); - if(ret != 0) + if (ret != 0) { SLIC_ERROR(axom::fmt::format("Failed to read STEP file '{}'", input.inputFile)); return 1; @@ -376,7 +376,7 @@ int main(int argc, char** argv) shape_bbox = step_reader.getBRepBoundingBox(); int num_trimming_curves = 0; - for(const auto& patch : step_reader.getPatchArray()) + for (const auto& patch : step_reader.getPatchArray()) { num_trimming_curves += patch.getNumTrimmingCurves(); } @@ -390,17 +390,17 @@ int main(int argc, char** argv) num_trimming_curves, read_timer.elapsed())); - if(input.triangulate) + if (input.triangulate) { read_timer.reset(); read_timer.start(); AXOM_ANNOTATE_SCOPE("triangulation"); const int tc = step_reader.getTriangleMesh(&tri_mesh, - input.linear_deflection, - input.angular_deflection, - input.deflection_is_relative, - /* trimmed */ true); - if(tc != 0) + input.linear_deflection, + input.angular_deflection, + input.deflection_is_relative, + /* trimmed */ true); + if (tc != 0) { SLIC_ERROR("Failed to triangulate STEP geometry."); return 1; @@ -409,12 +409,12 @@ int main(int argc, char** argv) SLIC_INFO( axom::fmt::format(axom::utilities::locale(), - "Triangulated geometry with deflection {} and angular deflection {}" - " containing {:L} triangles in {:.3Lf} seconds", - input.linear_deflection, - input.angular_deflection, - tri_mesh.getNumberOfCells(), - read_timer.elapsed())); + "Triangulated geometry with deflection {} and angular deflection {}" + " containing {:L} triangles in {:.3Lf} seconds", + input.linear_deflection, + input.angular_deflection, + tri_mesh.getNumberOfCells(), + read_timer.elapsed())); } else { @@ -427,7 +427,7 @@ int main(int argc, char** argv) } // Early return if user didn't set up a query mesh - if(input.boxResolution.empty()) + if (input.boxResolution.empty()) { return 0; } @@ -441,21 +441,21 @@ int main(int argc, char** argv) // Generate the query grid and fields quest::generate_gwn_query_mesh(dc, - shape_bbox, - input.boxMins, - input.boxMaxs, - input.boxResolution, - input.queryOrder); + shape_bbox, + input.boxMins, + input.boxMaxs, + input.boxResolution, + input.queryOrder); // Run the preprocess std::visit( [&](auto& wn) { using T = std::decay_t; - if constexpr(quest::gwn_input_type_v == quest::GWNInputType::Surface) + if constexpr (quest::gwn_input_type_v == quest::GWNInputType::Surface) { - wn.preprocess(patches, input.memoized); + wn.preprocess(patches, input.algorithm == "direct", input.memoized); } - else if constexpr(quest::gwn_input_type_v == quest::GWNInputType::Triangulation) + else if constexpr (quest::gwn_input_type_v == quest::GWNInputType::Triangulation) { wn.preprocess(&tri_mesh, input.algorithm == "direct"); } @@ -467,7 +467,7 @@ int main(int argc, char** argv) } // Postprocess query results: norms, ranges, and integral statistics - if(input.stats) + if (input.stats) { AXOM_ANNOTATE_SCOPE("postprocess"); @@ -480,14 +480,14 @@ int main(int argc, char** argv) std::int64_t pos_inout_dofs = 0; std::int64_t neg_inout_dofs = 0; - for(int i = 0; i < inout.Size(); ++i) + for (int i = 0; i < inout.Size(); ++i) { const double v = inout[i]; - if(v > 0.0) + if (v > 0.0) { ++pos_inout_dofs; } - else if(v < 0.0) + else if (v < 0.0) { ++neg_inout_dofs; } @@ -495,11 +495,11 @@ int main(int argc, char** argv) SLIC_INFO( axom::fmt::format("WN_STATS: dof_l2={:.6e} dof_linf={:.6e} l2={:.6e} min={:.6e} max={:.6e}", - winding_stats.dof_l2, - winding_stats.dof_linf, - winding_stats.l2, - winding_stats.min, - winding_stats.max)); + winding_stats.dof_l2, + winding_stats.dof_linf, + winding_stats.l2, + winding_stats.min, + winding_stats.max)); SLIC_INFO(axom::fmt::format( "INOUT_STATS: dof_l2={:.6e} dof_linf={:.6e} l2={:.6e} min={:.6e} max={:.6e} volume={:.6e} " @@ -512,13 +512,13 @@ int main(int argc, char** argv) inout_integrals.integral, inout_integrals.domain_volume, (inout_integrals.domain_volume > 0.0 ? inout_integrals.integral / inout_integrals.domain_volume - : 0.0), + : 0.0), pos_inout_dofs, neg_inout_dofs)); } // Save the query mesh and fields to disk using a format that can be viewed in VisIt - if(input.vis) + if (input.vis) { AXOM_ANNOTATE_SCOPE("dump_mesh"); @@ -528,8 +528,8 @@ int main(int argc, char** argv) windingDC.Save(); SLIC_INFO(axom::fmt::format("Outputting generated mesh '{}' to '{}'", - windingDC.GetCollectionName(), - axom::utilities::filesystem::getCWD())); + windingDC.GetCollectionName(), + axom::utilities::filesystem::getCWD())); } return 0; diff --git a/src/axom/quest/tests/quest_gwn_methods.cpp b/src/axom/quest/tests/quest_gwn_methods.cpp index d9b0bc67df..1168f72995 100644 --- a/src/axom/quest/tests/quest_gwn_methods.cpp +++ b/src/axom/quest/tests/quest_gwn_methods.cpp @@ -285,11 +285,11 @@ void check_step_file_triangulation() const int query_order = 1; // Generate three query grids and fields - axom::Array dc(0, 3); - std::string names[] = {"direct", "tri", "tri_fast"}; - for(int i = 0; i < 3; ++i) + constexpr int num_queries = 6; + axom::Array dc(0, num_queries); + for(int i = 0; i < num_queries; ++i) { - dc.emplace_back(axom::fmt::format("gwn_{}", names[i])); + dc.emplace_back(axom::fmt::format("gwn_method_{}", i)); axom::quest::generate_gwn_query_mesh(dc[i], shape_bbox, std::vector {}, @@ -300,9 +300,10 @@ void check_step_file_triangulation() // Create tolerance object axom::primal::WindingTolerances tol; - constexpr bool useDirectTriangle = true; + constexpr bool useDirectEvaluation = true; + constexpr bool useMemoization = true; - //// Run three different kinds of GWN query //// + //// Run six different kinds of GWN query //// // We expect all three fields to return the same values in this case because // of the specific arrangement of query points and triangulation. // In general, triangulating the shape can result in different GWN values @@ -310,34 +311,44 @@ void check_step_file_triangulation() // Direct SLIC_INFO("Testing Direct Evaluation"); - axom::quest::DirectGWN3D gwn_direct {}; - gwn_direct.preprocess(patches); - gwn_direct.query(dc[0], tol); - - // Triangulated - SLIC_INFO("Testing Direct Evaluation of Polyline"); - axom::quest::TriangleGWN3D gwn_tri {}; - gwn_tri.preprocess(&tri_mesh, useDirectTriangle); - gwn_tri.query(dc[1], tol); - - // Triangulated, fast approximation - SLIC_INFO("Testing Fast-Approximate Evaluation of Polyline"); - axom::quest::TriangleGWN3D gwn_tri_fast {}; - gwn_tri_fast.preprocess(&tri_mesh, !useDirectTriangle); - gwn_tri_fast.query(dc[2], tol); + axom::quest::NURBSPatchGWNQuery gwn_patches {}; + gwn_patches.preprocess(patches, useDirectEvaluation, !useMemoization); + gwn_patches.query(dc[0], tol); + + axom::quest::NURBSPatchGWNQuery gwn_patches_memoized {}; + gwn_patches_memoized.preprocess(patches, useDirectEvaluation, useMemoization); + gwn_patches_memoized.query(dc[1], tol); + + axom::quest::NURBSPatchGWNQuery gwn_patches_fast {}; + gwn_patches_fast.preprocess(patches, !useDirectEvaluation, !useMemoization); + gwn_patches_fast.query(dc[2], tol); + + axom::quest::NURBSPatchGWNQuery gwn_patches_fast_memoized {}; + gwn_patches_fast_memoized.preprocess(patches, !useDirectEvaluation, useMemoization); + gwn_patches_fast_memoized.query(dc[3], tol); + + SLIC_INFO("Testing Linearization Evaluation"); + axom::quest::TriangleGWNQuery gwn_theater {}; + gwn_theater.preprocess(&tri_mesh, useDirectEvaluation); + gwn_theater.query(dc[4], tol); + + axom::quest::TriangleGWNQuery gwn_theater_fast {}; + gwn_theater_fast.preprocess(&tri_mesh, !useDirectEvaluation); + gwn_theater_fast.query(dc[5], tol); // Compare the in-out values between the three fields const auto *query_mesh = dc[0].GetMesh(); const auto num_query_points = query_mesh->GetNodalFESpace()->GetNDofs(); auto &inout_direct = *dc[0].GetField("inout"); - auto &inout_tri = *dc[1].GetField("inout"); - auto &inout_tri_fast = *dc[2].GetField("inout"); - - for(int i = 0; i < num_query_points; ++i) + for(int N = 1; N < num_queries; ++N) { - EXPECT_EQ(inout_direct[i], inout_tri[i]); - EXPECT_EQ(inout_direct[i], inout_tri_fast[i]); + auto &inout_other = *dc[N].GetField("inout"); + + for(int i = 0; i < num_query_points; ++i) + { + EXPECT_EQ(inout_direct[i], inout_other[i]); + } } } #endif From 12a881585b3ad2cad7ed8c911ce4e95fdadfd4f3 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Wed, 1 Apr 2026 01:39:05 -0700 Subject: [PATCH 230/986] Still broken, but closer --- src/axom/primal/geometry/NURBSPatch.hpp | 89 +++++++++++++++++++ .../detail/winding_number_2d_memoization.hpp | 14 +-- .../detail/winding_number_3d_memoization.hpp | 14 +-- src/axom/quest/FastApproximateGWN.hpp | 11 +-- src/axom/quest/GWNMethods.hpp | 24 +++-- src/axom/quest/tests/quest_gwn_methods.cpp | 30 +++---- 6 files changed, 133 insertions(+), 49 deletions(-) diff --git a/src/axom/primal/geometry/NURBSPatch.hpp b/src/axom/primal/geometry/NURBSPatch.hpp index 973b5ccf89..67b5eb7c9c 100644 --- a/src/axom/primal/geometry/NURBSPatch.hpp +++ b/src/axom/primal/geometry/NURBSPatch.hpp @@ -3527,6 +3527,95 @@ class NURBSPatch return ret_vec; } + + template + primal::Vector calculateSurfaceMoments() const + { + // Need to integrate over 4 (for the coordinates and weight of the centroid) + // + 3 (for the zeroth order moments) + // + 9 (for the first order moments) + // + 27 (for the second order moments) + Vector ret(0.0); + + // Number of quadrature points + constexpr int npts = 20; + + // For now, doing this increases the likelihood of bad numerics, + // and is largely redundant after doing the bigger subdivision routine + + //for(const auto& patch : extractTrimmedBezier()) + { + auto& patch = *this; + + auto big_ol_integrand = [&patch](Point2D x) -> Vector { + Vector M(0.0); + + primal::Point eval; + primal::Vector Du, Dv; + patch.evaluateFirstDerivatives(x[0], x[1], eval, Du, Dv); + const auto the_norm = Vector::cross_product(Du, Dv); + + M[0] = the_norm.norm(); + M[1] = eval[0] * the_norm.norm(); + M[2] = eval[1] * the_norm.norm(); + M[3] = eval[2] * the_norm.norm(); + + M[4] = the_norm[0]; + M[5] = the_norm[1]; + M[6] = the_norm[2]; + + if constexpr(ORDER >= 1) + { + M[7] = eval[0] * the_norm[0]; + M[8] = eval[0] * the_norm[1]; + M[9] = eval[0] * the_norm[2]; + M[10] = eval[1] * the_norm[0]; + M[11] = eval[1] * the_norm[1]; + M[12] = eval[1] * the_norm[2]; + M[13] = eval[2] * the_norm[0]; + M[14] = eval[2] * the_norm[1]; + M[15] = eval[2] * the_norm[2]; + + if constexpr(ORDER >= 1) + { + M[16] = eval[0] * eval[0] * the_norm[0]; + M[17] = eval[0] * eval[0] * the_norm[1]; + M[18] = eval[0] * eval[0] * the_norm[2]; + M[19] = eval[0] * eval[1] * the_norm[0]; + M[20] = eval[0] * eval[1] * the_norm[1]; + M[21] = eval[0] * eval[1] * the_norm[2]; + M[22] = eval[0] * eval[2] * the_norm[0]; + M[23] = eval[0] * eval[2] * the_norm[1]; + M[24] = eval[0] * eval[2] * the_norm[2]; + M[25] = eval[1] * eval[0] * the_norm[0]; + M[26] = eval[1] * eval[0] * the_norm[1]; + M[27] = eval[1] * eval[0] * the_norm[2]; + M[28] = eval[1] * eval[1] * the_norm[0]; + M[29] = eval[1] * eval[1] * the_norm[1]; + M[30] = eval[1] * eval[1] * the_norm[2]; + M[31] = eval[1] * eval[2] * the_norm[0]; + M[32] = eval[1] * eval[2] * the_norm[1]; + M[33] = eval[1] * eval[2] * the_norm[2]; + M[34] = eval[2] * eval[0] * the_norm[0]; + M[35] = eval[2] * eval[0] * the_norm[1]; + M[36] = eval[2] * eval[0] * the_norm[2]; + M[37] = eval[2] * eval[1] * the_norm[0]; + M[38] = eval[2] * eval[1] * the_norm[1]; + M[39] = eval[2] * eval[1] * the_norm[2]; + M[40] = eval[2] * eval[2] * the_norm[0]; + M[41] = eval[2] * eval[2] * the_norm[1]; + M[42] = eval[2] * eval[2] * the_norm[2]; + } + } + + return M; + }; + + ret += evaluate_area_integral(patch.getTrimmingCurves(), big_ol_integrand, npts); + } + + return ret; + } //@} ///@{ diff --git a/src/axom/primal/operators/detail/winding_number_2d_memoization.hpp b/src/axom/primal/operators/detail/winding_number_2d_memoization.hpp index 02911a88f0..50daa33b0e 100644 --- a/src/axom/primal/operators/detail/winding_number_2d_memoization.hpp +++ b/src/axom/primal/operators/detail/winding_number_2d_memoization.hpp @@ -264,9 +264,9 @@ class NURBSCurveCacheManager { using NURBSCache = axom::primal::detail::NURBSCurveGWNCache; using NURBSCacheArray = axom::Array; - using NURBSCacheArrayView = axom::ArrayView; + using NURBSCacheArrayView = axom::ArrayView; - using CurveArrayView = axom::ArrayView>; + using CurveArrayView = axom::ArrayView>; public: NURBSCurveCacheManager() = default; @@ -289,7 +289,7 @@ class NURBSCurveCacheManager }; /// Return a view of this manager to pass into a device function. - View view() { return View {m_nurbs_caches.view()}; } + View view() const { return View {m_nurbs_caches.view()}; } /// Return if the underlying array is empty bool empty() const { return m_nurbs_caches.empty(); } @@ -312,15 +312,15 @@ class NURBSCurveCacheManagerOMP { using NURBSCache = axom::primal::detail::NURBSCurveGWNCache; using NURBSCachePerThreadArray = axom::Array>; - using NURBSCachePerThreadArrayView = axom::ArrayView>; + using NURBSCachePerThreadArrayView = axom::ArrayView>; using NURBSCacheArrayView = axom::ArrayView; - using CurveArrayView = axom::ArrayView>; + using CurveArrayView = axom::ArrayView>; public: NURBSCurveCacheManagerOMP() = default; - NURBSCurveCacheManagerOMP(const CurveArrayView& curves, double bbExpansionAmount = 0.0) + NURBSCurveCacheManagerOMP(CurveArrayView curves, double bbExpansionAmount = 0.0) { const int nt = omp_get_max_threads(); m_nurbs_caches.resize(nt); @@ -351,7 +351,7 @@ class NURBSCurveCacheManagerOMP }; /// Return a view of this manager to pass into a device function. - View view() { return View {m_nurbs_caches.view()}; } + View view() const { return View {m_nurbs_caches.view()}; } /// Return if the underlying array is empty bool empty() const { return m_nurbs_caches.empty(); } diff --git a/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp b/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp index 76ec6cf37a..667454a091 100644 --- a/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp +++ b/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp @@ -302,9 +302,9 @@ class NURBSPatchCacheManager { using NURBSCache = axom::primal::detail::NURBSPatchGWNCache; using NURBSCacheArray = axom::Array; - using NURBSCacheArrayView = axom::ArrayView; + using NURBSCacheArrayView = axom::ArrayView; - using PatchArrayView = axom::ArrayView>; + using PatchArrayView = axom::ArrayView>; public: NURBSPatchCacheManager() = default; @@ -327,7 +327,7 @@ class NURBSPatchCacheManager }; /// Return a view of this manager to pass into a device function. - View view() { return View {m_nurbs_caches.view()}; } + View view() const { return View {m_nurbs_caches.view()}; } /// Return if the underlying array is empty bool empty() const { return m_nurbs_caches.empty(); } @@ -350,10 +350,10 @@ class NURBSPatchCacheManagerOMP { using NURBSCache = axom::primal::detail::NURBSPatchGWNCache; using NURBSCachePerThreadArray = axom::Array>; - using NURBSCachePerThreadArrayView = axom::ArrayView>; - using NURBSCacheArrayView = axom::ArrayView; + using NURBSCachePerThreadArrayView = axom::ArrayView>; + using NURBSCacheArrayView = axom::ArrayView; - using PatchArrayView = axom::ArrayView>; + using PatchArrayView = axom::ArrayView>; public: NURBSPatchCacheManagerOMP() = default; @@ -397,7 +397,7 @@ class NURBSPatchCacheManagerOMP }; /// Return a view of this manager to pass into a device function. - View view() { return View {m_nurbs_caches.view()}; } + View view() const { return View {m_nurbs_caches.view()}; } /// Return if the underlying array is empty bool empty() const { return m_nurbs_caches.empty(); } diff --git a/src/axom/quest/FastApproximateGWN.hpp b/src/axom/quest/FastApproximateGWN.hpp index 79f98421f2..f23db14250 100644 --- a/src/axom/quest/FastApproximateGWN.hpp +++ b/src/axom/quest/FastApproximateGWN.hpp @@ -129,15 +129,12 @@ class GWNMomentData /// Construct moments from a trimmed NURBS surface explicit GWNMomentData(const axom::primal::NURBSPatch& a_patch) { - // Track the centroid across the tree, and return the rest of the data - auto patch_data = a_patch.calculateSurfaceMoments(m_order); + const auto patch_data = a_patch.calculateSurfaceMoments(); - for(int i = 0; i < NumberOfEntries; ++i) rm[i] = patch_data[i]; + a = patch_data[0]; + ap = axom::primal::Vector {patch_data[0], patch_data[1], patch_data[2]}; - a = patch_data[39]; - ax = patch_data[40]; - ay = patch_data[41]; - az = patch_data[42]; + for(int i = 0; i < NumberOfEntries; ++i) rm[i] = patch_data[i + 3]; compute_coefficients(); } diff --git a/src/axom/quest/GWNMethods.hpp b/src/axom/quest/GWNMethods.hpp index a9b4171ba3..2bd646ac79 100644 --- a/src/axom/quest/GWNMethods.hpp +++ b/src/axom/quest/GWNMethods.hpp @@ -164,7 +164,7 @@ class NURBSCurveGWNQuery stage_timer.start(); { AXOM_ANNOTATE_SCOPE("cache_initialization"); - m_nurbs_cache_mgr = NURBSCacheManager(input_curves); + m_nurbs_cache_mgr = NURBSCacheManager(m_input_curves_view); } stage_timer.stop(); SLIC_INFO(axom::fmt::format(" Preprocessing stage (cache initialization): {} s", @@ -258,7 +258,7 @@ class NURBSCurveGWNQuery if(m_bvh.isInitialized()) { const auto traverser = m_bvh.getTraverser(); - const auto internal_moments_view = m_internal_moments.view(); + auto internal_moments_view = m_internal_moments.view(); // Use fast-approximate, non-memoized form if(m_nurbs_cache_mgr.empty()) @@ -278,7 +278,7 @@ class NURBSCurveGWNQuery { const auto cache_mgr_view = m_nurbs_cache_mgr.view(); axom::for_all(num_query_points, [=, &winding, &inout](axom::IndexType index) { - const auto nurbs_cache_view = cache_mgr_view.cache(); + const auto nurbs_cache_view = cache_mgr_view.caches(); const double wn = axom::quest::fast_approximate_winding_number(query_point(index), traverser, nurbs_cache_view, @@ -492,7 +492,7 @@ class PolylineGWNQuery if(m_bvh.isInitialized()) { const auto traverser = m_bvh.getTraverser(); - const auto internal_moments_view = m_internal_moments.view(); + auto internal_moments_view = m_internal_moments.view(); axom::for_all(num_query_points, [=, &winding, &inout](axom::IndexType index) { const double wn = axom::quest::fast_approximate_winding_number(query_point(index), @@ -558,7 +558,6 @@ class NURBSPatchGWNQuery using PatchType = axom::primal::NURBSPatch; using PatchArrayType = axom::Array; - using NURBSCacheArray = axom::Array>; using NURBSCacheManager = typename axom::primal::nurbs_cache_3d_traits::type; NURBSPatchGWNQuery() = default; @@ -586,7 +585,7 @@ class NURBSPatchGWNQuery stage_timer.start(); { AXOM_ANNOTATE_SCOPE("cache_initialization"); - m_nurbs_cache_mgr = NURBSCacheManager(input_patches); + m_nurbs_cache_mgr = NURBSCacheManager(m_input_patches_view); } stage_timer.stop(); SLIC_INFO(axom::fmt::format(" Preprocessing stage (cache initialization): {} s", @@ -595,7 +594,7 @@ class NURBSPatchGWNQuery if(!use_direct_eval) { - stage_timer.restart(); + stage_timer.reset(); stage_timer.start(); { AXOM_ANNOTATE_SCOPE("bvh_initialization"); @@ -616,15 +615,14 @@ class NURBSPatchGWNQuery stage_timer.start(); { AXOM_ANNOTATE_SCOPE("moment_precomputation"); - auto patches_view = m_input_patches_view; - auto compute_moments = [patches_view](std::int32_t currentNode, + auto compute_moments = [=](std::int32_t currentNode, const std::int32_t* leafNodes) -> GWNMoments { const auto idx = leafNodes[currentNode]; - return GWNMoments(patches_view[idx]); // TODO: Avoid repeat normal calculation + return GWNMoments(m_input_patches_view[idx]); // TODO: Avoid repeat normal calculation }; const auto traverser = m_bvh.getTraverser(); - m_internal_moments = traverser.reduce_tree(compute_moments); + m_internal_moments = traverser.template reduce_tree(compute_moments); } stage_timer.stop(); SLIC_INFO(axom::fmt::format(" Preprocessing stage (moment precomputation): {} s", @@ -780,7 +778,7 @@ class NURBSPatchGWNQuery private: // For the input curves/BVH leaf nodes - axom::ArrayView m_input_patches_view; + axom::ArrayView m_input_patches_view; NURBSCacheManager m_nurbs_cache_mgr; // Only needed for fast approximation method @@ -958,7 +956,7 @@ class TriangleGWNQuery if(m_bvh.isInitialized()) { const auto traverser = m_bvh.getTraverser(); - const auto internal_moments_view = m_internal_moments.view(); + auto internal_moments_view = m_internal_moments.view(); axom::for_all(num_query_points, [=, &winding, &inout](axom::IndexType index) { const double wn = axom::quest::fast_approximate_winding_number(scaled_query_point(index), diff --git a/src/axom/quest/tests/quest_gwn_methods.cpp b/src/axom/quest/tests/quest_gwn_methods.cpp index 1168f72995..5150bbe6a2 100644 --- a/src/axom/quest/tests/quest_gwn_methods.cpp +++ b/src/axom/quest/tests/quest_gwn_methods.cpp @@ -313,11 +313,11 @@ void check_step_file_triangulation() SLIC_INFO("Testing Direct Evaluation"); axom::quest::NURBSPatchGWNQuery gwn_patches {}; gwn_patches.preprocess(patches, useDirectEvaluation, !useMemoization); - gwn_patches.query(dc[0], tol); + //gwn_patches.query(dc[0], tol); axom::quest::NURBSPatchGWNQuery gwn_patches_memoized {}; gwn_patches_memoized.preprocess(patches, useDirectEvaluation, useMemoization); - gwn_patches_memoized.query(dc[1], tol); + //gwn_patches_memoized.query(dc[1], tol); axom::quest::NURBSPatchGWNQuery gwn_patches_fast {}; gwn_patches_fast.preprocess(patches, !useDirectEvaluation, !useMemoization); @@ -325,16 +325,16 @@ void check_step_file_triangulation() axom::quest::NURBSPatchGWNQuery gwn_patches_fast_memoized {}; gwn_patches_fast_memoized.preprocess(patches, !useDirectEvaluation, useMemoization); - gwn_patches_fast_memoized.query(dc[3], tol); + //gwn_patches_fast_memoized.query(dc[3], tol); SLIC_INFO("Testing Linearization Evaluation"); - axom::quest::TriangleGWNQuery gwn_theater {}; - gwn_theater.preprocess(&tri_mesh, useDirectEvaluation); - gwn_theater.query(dc[4], tol); + axom::quest::TriangleGWNQuery gwn_triangles {}; + gwn_triangles.preprocess(&tri_mesh, useDirectEvaluation); + gwn_triangles.query(dc[4], tol); - axom::quest::TriangleGWNQuery gwn_theater_fast {}; - gwn_theater_fast.preprocess(&tri_mesh, !useDirectEvaluation); - gwn_theater_fast.query(dc[5], tol); + axom::quest::TriangleGWNQuery gwn_triangles_fast {}; + gwn_triangles_fast.preprocess(&tri_mesh, !useDirectEvaluation); + gwn_triangles_fast.query(dc[5], tol); // Compare the in-out values between the three fields const auto *query_mesh = dc[0].GetMesh(); @@ -373,12 +373,12 @@ TEST(quest_gwn_methods, step_file_triangulation) } #endif -#if defined(AXOM_USE_OPENCASCADE) && defined(AXOM_USE_OPENMP) && defined(AXOM_USE_RAJA) -TEST(quest_gwn_methods, step_file_triangulation_omp) -{ - check_step_file_triangulation(); -} -#endif +//#if defined(AXOM_USE_OPENCASCADE) && defined(AXOM_USE_OPENMP) && defined(AXOM_USE_RAJA) +//TEST(quest_gwn_methods, step_file_triangulation_omp) +//{ +// check_step_file_triangulation(); +//} +//#endif //------------------------------------------------------------------------------ int main(int argc, char *argv[]) From a898d6b9552ac5a97acecfdf93cfe9cbcc662d5b Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Wed, 1 Apr 2026 01:53:10 -0700 Subject: [PATCH 231/986] Fix the last const issue --- src/axom/quest/FastApproximateGWN.hpp | 12 ++++++------ src/axom/quest/GWNMethods.hpp | 6 ++---- src/axom/quest/tests/quest_gwn_methods.cpp | 18 +++++++++--------- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/axom/quest/FastApproximateGWN.hpp b/src/axom/quest/FastApproximateGWN.hpp index f23db14250..599978293f 100644 --- a/src/axom/quest/FastApproximateGWN.hpp +++ b/src/axom/quest/FastApproximateGWN.hpp @@ -451,13 +451,13 @@ double fast_approximate_winding_number(const primal::Point& query, auto leaf_gwn = [&query, &gwn, leaf_objects_view, &wt](std::int32_t currentNode, const std::int32_t* leafNodes) -> void { const auto idx = leafNodes[currentNode]; - gwn += axom::primal::winding_number(q, + gwn += axom::primal::winding_number(query, leaf_objects_view[idx], - tol_copy.edge_tol, - tol_copy.ls_tol, - tol_copy.quad_tol, - tol_copy.disk_size, - tol_copy.EPS); + wt.edge_tol, + wt.ls_tol, + wt.quad_tol, + wt.disk_size, + wt.EPS); }; traverser.traverse_tree(query, leaf_gwn, bbContain); diff --git a/src/axom/quest/GWNMethods.hpp b/src/axom/quest/GWNMethods.hpp index 2bd646ac79..29b607aa30 100644 --- a/src/axom/quest/GWNMethods.hpp +++ b/src/axom/quest/GWNMethods.hpp @@ -616,7 +616,7 @@ class NURBSPatchGWNQuery { AXOM_ANNOTATE_SCOPE("moment_precomputation"); auto compute_moments = [=](std::int32_t currentNode, - const std::int32_t* leafNodes) -> GWNMoments { + const std::int32_t* leafNodes) -> GWNMoments { const auto idx = leafNodes[currentNode]; return GWNMoments(m_input_patches_view[idx]); // TODO: Avoid repeat normal calculation }; @@ -645,9 +645,7 @@ class NURBSPatchGWNQuery * \param [in] slice_z If the dc mesh is 2D, the GWN will be evaluated on a slice * parallel to the x-y plane with this offset on the z-axis */ - void query(mfem::DataCollection& dc, - const primal::WindingTolerances& tol, - const double slice_z = 0.0) const + void query(mfem::DataCollection& dc, const primal::WindingTolerances& tol, const double slice_z = 0.0) { if(!dc.HasField("winding") || !dc.HasField("inout")) { diff --git a/src/axom/quest/tests/quest_gwn_methods.cpp b/src/axom/quest/tests/quest_gwn_methods.cpp index 5150bbe6a2..ffb83d9c8c 100644 --- a/src/axom/quest/tests/quest_gwn_methods.cpp +++ b/src/axom/quest/tests/quest_gwn_methods.cpp @@ -313,11 +313,11 @@ void check_step_file_triangulation() SLIC_INFO("Testing Direct Evaluation"); axom::quest::NURBSPatchGWNQuery gwn_patches {}; gwn_patches.preprocess(patches, useDirectEvaluation, !useMemoization); - //gwn_patches.query(dc[0], tol); + gwn_patches.query(dc[0], tol); axom::quest::NURBSPatchGWNQuery gwn_patches_memoized {}; gwn_patches_memoized.preprocess(patches, useDirectEvaluation, useMemoization); - //gwn_patches_memoized.query(dc[1], tol); + gwn_patches_memoized.query(dc[1], tol); axom::quest::NURBSPatchGWNQuery gwn_patches_fast {}; gwn_patches_fast.preprocess(patches, !useDirectEvaluation, !useMemoization); @@ -325,7 +325,7 @@ void check_step_file_triangulation() axom::quest::NURBSPatchGWNQuery gwn_patches_fast_memoized {}; gwn_patches_fast_memoized.preprocess(patches, !useDirectEvaluation, useMemoization); - //gwn_patches_fast_memoized.query(dc[3], tol); + gwn_patches_fast_memoized.query(dc[3], tol); SLIC_INFO("Testing Linearization Evaluation"); axom::quest::TriangleGWNQuery gwn_triangles {}; @@ -373,12 +373,12 @@ TEST(quest_gwn_methods, step_file_triangulation) } #endif -//#if defined(AXOM_USE_OPENCASCADE) && defined(AXOM_USE_OPENMP) && defined(AXOM_USE_RAJA) -//TEST(quest_gwn_methods, step_file_triangulation_omp) -//{ -// check_step_file_triangulation(); -//} -//#endif +#if defined(AXOM_USE_OPENCASCADE) && defined(AXOM_USE_OPENMP) && defined(AXOM_USE_RAJA) +TEST(quest_gwn_methods, step_file_triangulation_omp) +{ + check_step_file_triangulation(); +} +#endif //------------------------------------------------------------------------------ int main(int argc, char *argv[]) From 3abde8ddf2f901c9a49da24f3417488385d35021 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Wed, 1 Apr 2026 11:48:32 -0700 Subject: [PATCH 232/986] Fix issues, testing harness --- .../detail/winding_number_3d_memoization.hpp | 1 - src/axom/quest/FastApproximateGWN.hpp | 6 ++-- src/axom/quest/GWNMethods.hpp | 4 +++ src/axom/quest/tests/quest_gwn_methods.cpp | 34 ++++++++++--------- 4 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp b/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp index 667454a091..93dbc09822 100644 --- a/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp +++ b/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp @@ -369,7 +369,6 @@ class NURBSPatchCacheManagerOMP axom::for_all( patches.size(), AXOM_HOST_LAMBDA(axom::IndexType i) { nurbs_caches_view[0][i] = NURBSCache(patches[i]); }); - SLIC_INFO("Finished the first construction"); // Copy the constructed cache to the other threads' copies (less work than construction) axom::for_all( 1, diff --git a/src/axom/quest/FastApproximateGWN.hpp b/src/axom/quest/FastApproximateGWN.hpp index 599978293f..d29910fff4 100644 --- a/src/axom/quest/FastApproximateGWN.hpp +++ b/src/axom/quest/FastApproximateGWN.hpp @@ -132,9 +132,11 @@ class GWNMomentData const auto patch_data = a_patch.calculateSurfaceMoments(); a = patch_data[0]; - ap = axom::primal::Vector {patch_data[0], patch_data[1], patch_data[2]}; + ap[0] = patch_data[1]; + ap[1] = patch_data[2]; + ap[2] = patch_data[3]; - for(int i = 0; i < NumberOfEntries; ++i) rm[i] = patch_data[i + 3]; + for(int i = 0; i < NumberOfEntries; ++i) rm[i] = patch_data[i + 4]; compute_coefficients(); } diff --git a/src/axom/quest/GWNMethods.hpp b/src/axom/quest/GWNMethods.hpp index 29b607aa30..4763ef4e5d 100644 --- a/src/axom/quest/GWNMethods.hpp +++ b/src/axom/quest/GWNMethods.hpp @@ -686,6 +686,8 @@ class NURBSPatchGWNQuery // Use fast-approximate, non-memoized form if(m_nurbs_cache_mgr.empty()) { + SLIC_WARNING( + "Quest warning: Patch GWN evaluation is prohibitively slow without memoization."); axom::for_all(num_query_points, [=, &winding, &inout](axom::IndexType index) { const double wn = axom::quest::fast_approximate_winding_number(query_point(index), traverser, @@ -718,6 +720,8 @@ class NURBSPatchGWNQuery // Use direct, non-memoized form if(m_nurbs_cache_mgr.empty()) { + SLIC_WARNING( + "Quest warning: Patch GWN evaluation is prohibitively slow without memoization."); axom::for_all(num_query_points, [=, &winding, &inout](axom::IndexType nidx) { const auto q = query_point(static_cast(nidx)); double wn {}; diff --git a/src/axom/quest/tests/quest_gwn_methods.cpp b/src/axom/quest/tests/quest_gwn_methods.cpp index ffb83d9c8c..68deb19795 100644 --- a/src/axom/quest/tests/quest_gwn_methods.cpp +++ b/src/axom/quest/tests/quest_gwn_methods.cpp @@ -285,7 +285,7 @@ void check_step_file_triangulation() const int query_order = 1; // Generate three query grids and fields - constexpr int num_queries = 6; + constexpr int num_queries = 4; axom::Array dc(0, num_queries); for(int i = 0; i < num_queries; ++i) { @@ -303,40 +303,42 @@ void check_step_file_triangulation() constexpr bool useDirectEvaluation = true; constexpr bool useMemoization = true; - //// Run six different kinds of GWN query //// + //// Run four different kinds of GWN query //// // We expect all three fields to return the same values in this case because // of the specific arrangement of query points and triangulation. // In general, triangulating the shape can result in different GWN values // for query points near to individual surfaces. - // Direct - SLIC_INFO("Testing Direct Evaluation"); - axom::quest::NURBSPatchGWNQuery gwn_patches {}; - gwn_patches.preprocess(patches, useDirectEvaluation, !useMemoization); - gwn_patches.query(dc[0], tol); + // Direct patch evaluation is prohibitively slow without memoization. + // Keep interface only for symmetry with NURBSCurve methods + + SLIC_INFO("Testing Patch Evaluation"); + //axom::quest::NURBSPatchGWNQuery gwn_patches {}; + //gwn_patches.preprocess(patches, useDirectEvaluation, !useMemoization); + //gwn_patches.query(dc[0], tol); axom::quest::NURBSPatchGWNQuery gwn_patches_memoized {}; gwn_patches_memoized.preprocess(patches, useDirectEvaluation, useMemoization); - gwn_patches_memoized.query(dc[1], tol); + gwn_patches_memoized.query(dc[0], tol); - axom::quest::NURBSPatchGWNQuery gwn_patches_fast {}; - gwn_patches_fast.preprocess(patches, !useDirectEvaluation, !useMemoization); - gwn_patches_fast.query(dc[2], tol); + //axom::quest::NURBSPatchGWNQuery gwn_patches_fast {}; + //gwn_patches_fast.preprocess(patches, !useDirectEvaluation, !useMemoization); + //gwn_patches_fast.query(dc[2], tol); axom::quest::NURBSPatchGWNQuery gwn_patches_fast_memoized {}; gwn_patches_fast_memoized.preprocess(patches, !useDirectEvaluation, useMemoization); - gwn_patches_fast_memoized.query(dc[3], tol); + gwn_patches_fast_memoized.query(dc[1], tol); - SLIC_INFO("Testing Linearization Evaluation"); + SLIC_INFO("Testing Triangulation Evaluation"); axom::quest::TriangleGWNQuery gwn_triangles {}; gwn_triangles.preprocess(&tri_mesh, useDirectEvaluation); - gwn_triangles.query(dc[4], tol); + gwn_triangles.query(dc[2], tol); axom::quest::TriangleGWNQuery gwn_triangles_fast {}; gwn_triangles_fast.preprocess(&tri_mesh, !useDirectEvaluation); - gwn_triangles_fast.query(dc[5], tol); + gwn_triangles_fast.query(dc[3], tol); - // Compare the in-out values between the three fields + // Compare the in-out values between all fields const auto *query_mesh = dc[0].GetMesh(); const auto num_query_points = query_mesh->GetNodalFESpace()->GetNDofs(); From e04f5b7a1e43f890437e896b3742cf58cc69afa7 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Wed, 1 Apr 2026 14:11:26 -0700 Subject: [PATCH 233/986] Iniital subdivision effort --- src/axom/quest/FastApproximateGWN.hpp | 71 +++++++++++++++++++++++++++ src/axom/quest/GWNMethods.hpp | 36 +++++++++++--- 2 files changed, 100 insertions(+), 7 deletions(-) diff --git a/src/axom/quest/FastApproximateGWN.hpp b/src/axom/quest/FastApproximateGWN.hpp index d29910fff4..b3d3ae5d2f 100644 --- a/src/axom/quest/FastApproximateGWN.hpp +++ b/src/axom/quest/FastApproximateGWN.hpp @@ -469,6 +469,77 @@ double fast_approximate_winding_number(const primal::Point& query, return gwn; } +template +axom::Array> subdivide_curves( + const axom::ArrayView>& input_curves_view, + double bbox_threshold, + int npasses = 10) +{ + using BoxType = primal::BoundingBox; + using NURBSType = primal::NURBSCurve; + using BezierType = primal::BezierCurve; + + // Compute a bounding box of all the curves + axom::Array candidates; + BoxType total_bbox; + + // For NURBSCurves, first do a pass of Bezier extraction + for(auto& curv : input_curves_view) + { + for(auto& bez : curv.extractBezier()) + { + candidates.push_back(bez); + total_bbox.addBox(bez.boundingBox()); + } + } + + // Iterate over all the curves until none have a bounding box + // bigger than threshold * (total_bbox's size) + for(int i = 0; i < npasses; ++i) + { + axom::Array subdivisions; + subdivisions.reserve(candidates.size() * 3 / 2); + + BoxType new_bbox; + + // If any patch is bigger than the threshold, subdivide it, + // and add it to the next level. Repeat as needed. + const double max_range_norm = bbox_threshold * total_bbox.range().norm(); + for(const auto& candidate : candidates) + { + if(candidate.boundingBox().range().norm() < max_range_norm) + { + new_bbox.addBox(candidate.boundingBox()); + subdivisions.push_back(candidate); + continue; + } + + BezierType subcurves[2]; + candidate.split(0.5, subcurves[0], subcurves[1]); + for(int si = 0; si < 2; si++) + { + subdivisions.emplace_back(std::move(subcurves[si])); + new_bbox.addBox(subdivisions.back().boundingBox()); + } + } + + // Break if no additional subdivisions are made + if(candidates.size() == subdivisions.size()) break; + + candidates.swap(subdivisions); + total_bbox = new_bbox; + } + + // Do one final pass to turn the array of candidates into NURBS + axom::Array candidates_nurbs(0, candidates.size()); + for(auto& c : candidates) + { + candidates_nurbs.emplace_back(NURBSType(c)); + } + + return candidates_nurbs; +} + } // end namespace quest } // end namespace axom diff --git a/src/axom/quest/GWNMethods.hpp b/src/axom/quest/GWNMethods.hpp index 4763ef4e5d..5e8389225d 100644 --- a/src/axom/quest/GWNMethods.hpp +++ b/src/axom/quest/GWNMethods.hpp @@ -159,20 +159,20 @@ class NURBSCurveGWNQuery AXOM_ANNOTATE_SCOPE("preprocessing"); - if(use_memoization) + if(!use_direct_eval) { + stage_timer.reset(); stage_timer.start(); { - AXOM_ANNOTATE_SCOPE("cache_initialization"); - m_nurbs_cache_mgr = NURBSCacheManager(m_input_curves_view); + AXOM_ANNOTATE_SCOPE("subdivision"); + m_subdivided_curves = subdivide_curves(m_input_curves_view, 0.1); + m_processed_curves_view = m_subdivided_curves.view(); } + stage_timer.stop(); - SLIC_INFO(axom::fmt::format(" Preprocessing stage (cache initialization): {} s", + SLIC_INFO(axom::fmt::format(" Preprocessing stage (subdivision): {} s", stage_timer.elapsedTimeInSec())); - } - if(!use_direct_eval) - { stage_timer.reset(); stage_timer.start(); { @@ -207,6 +207,24 @@ class NURBSCurveGWNQuery SLIC_INFO(axom::fmt::format(" Preprocessing stage (moment precomputation): {} s", stage_timer.elapsedTimeInSec())); } + else + { + // Without fast-approximation, processing is unnecessary + m_processed_curves_view = m_input_curves_view; + } + + if(use_memoization) + { + stage_timer.reset(); + stage_timer.start(); + { + AXOM_ANNOTATE_SCOPE("cache_initialization"); + m_nurbs_cache_mgr = NURBSCacheManager(m_processed_curves_view); + } + stage_timer.stop(); + SLIC_INFO(axom::fmt::format(" Preprocessing stage (cache initialization): {} s", + stage_timer.elapsedTimeInSec())); + } timer.stop(); AXOM_ANNOTATE_METADATA("preprocessing_time", timer.elapsed(), ""); @@ -345,6 +363,10 @@ class NURBSCurveGWNQuery axom::ArrayView m_input_curves_view; NURBSCacheManager m_nurbs_cache_mgr; + // For preprocessed curves + axom::Array m_subdivided_curves; + axom::ArrayView m_processed_curves_view; + // Only needed for fast approximation method axom::Array m_internal_moments; axom::spin::BVH<2, ExecSpace> m_bvh; From 52f062dd43229810e3dd310c40dcc28346c62ab8 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Thu, 2 Apr 2026 11:23:09 -0700 Subject: [PATCH 234/986] Add 3D subdivision method --- src/axom/primal/geometry/NURBSPatch.hpp | 71 ++++++++++++++++ src/axom/quest/FastApproximateGWN.hpp | 81 +++++++++++++++++++ src/axom/quest/GWNMethods.hpp | 68 +++++++++++----- .../examples/quest_winding_number_3d.cpp | 2 +- src/axom/quest/tests/quest_gwn_methods.cpp | 22 ++++- 5 files changed, 222 insertions(+), 22 deletions(-) diff --git a/src/axom/primal/geometry/NURBSPatch.hpp b/src/axom/primal/geometry/NURBSPatch.hpp index 67b5eb7c9c..49c02dfdca 100644 --- a/src/axom/primal/geometry/NURBSPatch.hpp +++ b/src/axom/primal/geometry/NURBSPatch.hpp @@ -3160,6 +3160,19 @@ class NURBSPatch } } + void clipToCurves() + { + // Take a union of all trimming curve parameter + ParameterBoundingBoxType curve_bbox; + + for(auto& curv : m_trimmingCurves) curve_bbox.addBox(curv.boundingBox()); + + uncheckedClip(curve_bbox.getMin()[0] - 1e-5, + curve_bbox.getMax()[0] + 1e-5, + curve_bbox.getMin()[1] - 1e-5, + curve_bbox.getMax()[1] + 1e-5); + } + ///@} ///@{ @@ -3803,6 +3816,64 @@ class NURBSPatch return true; } + void nearBisectOnLongestAxis(NURBSPatch& p1, NURBSPatch& p2) const + { + double split_val_u = (getNumKnots_u() == 2 * (getDegree_u() + 1)) + ? 0.499 * getMinKnot_u() + 0.501 * getMaxKnot_u() + : getKnots_u()[getNumKnots_u() / 2]; + + double split_val_v = (getNumKnots_v() == 2 * (getDegree_v() + 1)) + ? 0.502 * getMinKnot_v() + 0.498 * getMaxKnot_v() + : getKnots_v()[getNumKnots_v() / 2]; + + auto make_split_candidate = [&](bool split_in_u) { + NURBSPatch patches[2]; + + // Avoid 2D (u,v) bisection of large, slender models which can lead to 4^k patch growth. + // Prefer a 1D split (u or v) that most reduces the max child bbox. + struct Result + { + NURBSPatch patches[2]; + double max_child_range_norm {0.0}; + }; + + Result r {}; + + // Do an `uncheckedSplit`, which doesn't look at trimming curves + if(split_in_u) + { + uncheckedSplit_u(split_val_u, patches[0], patches[1]); + } + else + { + uncheckedSplit_v(split_val_v, patches[0], patches[1]); + } + + r.patches[0] = std::move(patches[0]); + r.patches[1] = std::move(patches[1]); + + // Bounding boxes here are only computed without trimming curves + r.max_child_range_norm = axom::utilities::max(r.patches[0].boundingBox().range().norm(), + r.patches[1].boundingBox().range().norm()); + + return r; + }; + + const auto u_split = make_split_candidate(/*split_in_u*/ true); + const auto v_split = make_split_candidate(/*split_in_u*/ false); + + const bool was_u_better = (u_split.max_child_range_norm <= v_split.max_child_range_norm); + auto* chosen = was_u_better ? &u_split : &v_split; + + // Once we pick the best direction, split the trimming curves + p1 = std::move(chosen->patches[0]); + p2 = std::move(chosen->patches[1]); + splitTrimmingCurves(was_u_better ? split_val_u : split_val_v, + was_u_better, + p1.getTrimmingCurves(), + p2.getTrimmingCurves()); + } + /*! * \brief For a disk of radius r and center (u, v), split a NURBS surface into the portion inside/outside * diff --git a/src/axom/quest/FastApproximateGWN.hpp b/src/axom/quest/FastApproximateGWN.hpp index b3d3ae5d2f..1233f8031c 100644 --- a/src/axom/quest/FastApproximateGWN.hpp +++ b/src/axom/quest/FastApproximateGWN.hpp @@ -540,6 +540,87 @@ axom::Array> subdivide_curves( return candidates_nurbs; } +template +axom::Array> subdivide_patches( + const axom::ArrayView>& input_patches_view, + double bbox_threshold, + int npasses = 10) +{ + using BoxType = primal::BoundingBox; + using NURBSType = primal::NURBSPatch; + + axom::Array candidates; + candidates.reserve(input_patches_view.size() * 3 / 2); + BoxType total_bbox; + + // Create initial array of processed patches, + // beginning by clipping each patch parameter space + // to a bounding box of its trimming curves + // Then compute a bounding box of all the surfaces + for(auto& surf : input_patches_view) + { + // This is where we would do Bezier extraction, if the curve-curve intersection + // routine were more robust :( + //for(auto& bez : surf.extractTrimmedBezier()) + { + auto the_patch = surf; + + if(the_patch.getNumTrimmingCurves() == 0) continue; + + the_patch.normalize(); + the_patch.clipToCurves(); + + // Re-check if the patch is empty after clipping to curve + if(the_patch.getNumTrimmingCurves() == 0) continue; + + candidates.push_back(the_patch); + total_bbox.addBox(the_patch.boundingBox()); + } + } + + // Iterate over all the surfaces until no patch has a bounding box + // bigger than threshold * (total_bbox's size) + for(int i = 0; i < npasses; ++i) + { + axom::Array subdivisions; + subdivisions.reserve(candidates.size() * 3 / 2); + + BoxType new_bbox; + + // If any patch is bigger than the threshold, subdivide it, clip it, + // and add it to the next level. Repeat as needed. + const double max_range_norm = bbox_threshold * total_bbox.range().norm(); + for(const auto& candidate : candidates) + { + if(candidate.boundingBox().range().norm() < max_range_norm) + { + new_bbox.addBox(candidate.boundingBox()); + subdivisions.push_back(candidate); + continue; + } + + NURBSType subpatches[2]; + candidate.nearBisectOnLongestAxis(subpatches[0], subpatches[1]); + for(int si = 0; si < 2; si++) + { + if(subpatches[si].getNumTrimmingCurves() == 0) continue; + + subdivisions.emplace_back(std::move(subpatches[si])); + subdivisions.back().clipToCurves(); + new_bbox.addBox(subdivisions.back().boundingBox()); + } + } + + // Break if no additional subdivisions are made + if(candidates.size() == subdivisions.size()) break; + + candidates.swap(subdivisions); + total_bbox = new_bbox; + } + + return candidates; +} + } // end namespace quest } // end namespace axom diff --git a/src/axom/quest/GWNMethods.hpp b/src/axom/quest/GWNMethods.hpp index 5e8389225d..441fb02466 100644 --- a/src/axom/quest/GWNMethods.hpp +++ b/src/axom/quest/GWNMethods.hpp @@ -165,25 +165,28 @@ class NURBSCurveGWNQuery stage_timer.start(); { AXOM_ANNOTATE_SCOPE("subdivision"); - m_subdivided_curves = subdivide_curves(m_input_curves_view, 0.1); + m_subdivided_curves = subdivide_curves(m_input_curves_view, 2.0); m_processed_curves_view = m_subdivided_curves.view(); } - stage_timer.stop(); - SLIC_INFO(axom::fmt::format(" Preprocessing stage (subdivision): {} s", + SLIC_INFO(axom::fmt::format(" Preprocessing stage (subdivision {} -> {}): {} s", + m_input_curves_view.size(), + m_processed_curves_view.size(), stage_timer.elapsedTimeInSec())); stage_timer.reset(); stage_timer.start(); { AXOM_ANNOTATE_SCOPE("bvh_initialization"); - const int ncurves = m_input_curves_view.size(); + const int ncurves = m_processed_curves_view.size(); axom::Array aabbs(ncurves, ncurves); auto aabbs_view = aabbs.view(); axom::for_all( ncurves, - AXOM_LAMBDA(axom::IndexType i) { aabbs_view[i] = m_input_curves_view[i].boundingBox(); }); + AXOM_LAMBDA(axom::IndexType i) { + aabbs_view[i] = m_processed_curves_view[i].boundingBox(); + }); m_bvh.initialize(aabbs_view, ncurves); } stage_timer.stop(); @@ -197,7 +200,7 @@ class NURBSCurveGWNQuery auto compute_moments = [=](std::int32_t currentNode, const std::int32_t* leafNodes) -> GWNMoments { const auto idx = leafNodes[currentNode]; - return GWNMoments(m_input_curves_view[idx]); + return GWNMoments(m_processed_curves_view[idx]); }; const auto traverser = m_bvh.getTraverser(); @@ -284,7 +287,7 @@ class NURBSCurveGWNQuery axom::for_all(num_query_points, [=, &winding, &inout](axom::IndexType index) { const double wn = axom::quest::fast_approximate_winding_number(query_point(index), traverser, - m_input_curves_view, + m_processed_curves_view, internal_moments_view, tol_copy); winding[static_cast(index)] = wn; @@ -316,7 +319,7 @@ class NURBSCurveGWNQuery axom::for_all(num_query_points, [=, &winding, &inout](axom::IndexType nidx) { const auto q = query_point(static_cast(nidx)); double wn {}; - for(const auto& curve : m_input_curves_view) + for(const auto& curve : m_processed_curves_view) { wn += axom::primal::winding_number(q, curve, tol_copy.edge_tol, tol_copy.EPS); } @@ -602,31 +605,34 @@ class NURBSPatchGWNQuery AXOM_ANNOTATE_SCOPE("preprocessing"); - if(use_memoization) + if(!use_direct_eval) { + stage_timer.reset(); stage_timer.start(); { - AXOM_ANNOTATE_SCOPE("cache_initialization"); - m_nurbs_cache_mgr = NURBSCacheManager(m_input_patches_view); + AXOM_ANNOTATE_SCOPE("subdivision"); + m_subdivided_patches = subdivide_patches(m_input_patches_view, 0.1); + m_processed_patches_view = m_subdivided_patches.view(); } stage_timer.stop(); - SLIC_INFO(axom::fmt::format(" Preprocessing stage (cache initialization): {} s", + SLIC_INFO(axom::fmt::format(" Preprocessing stage (subdivision {} -> {}): {} s", + m_input_patches_view.size(), + m_processed_patches_view.size(), stage_timer.elapsedTimeInSec())); - } - if(!use_direct_eval) - { stage_timer.reset(); stage_timer.start(); { AXOM_ANNOTATE_SCOPE("bvh_initialization"); - const int npatches = m_input_patches_view.size(); + const int npatches = m_processed_patches_view.size(); axom::Array aabbs(npatches, npatches); auto aabbs_view = aabbs.view(); axom::for_all( npatches, - AXOM_LAMBDA(axom::IndexType i) { aabbs_view[i] = m_input_patches_view[i].boundingBox(); }); + AXOM_LAMBDA(axom::IndexType i) { + aabbs_view[i] = m_processed_patches_view[i].boundingBox(); + }); m_bvh.initialize(aabbs_view, npatches); } stage_timer.stop(); @@ -640,7 +646,7 @@ class NURBSPatchGWNQuery auto compute_moments = [=](std::int32_t currentNode, const std::int32_t* leafNodes) -> GWNMoments { const auto idx = leafNodes[currentNode]; - return GWNMoments(m_input_patches_view[idx]); // TODO: Avoid repeat normal calculation + return GWNMoments(m_processed_patches_view[idx]); // TODO: Avoid repeat normal calculation }; const auto traverser = m_bvh.getTraverser(); @@ -650,6 +656,24 @@ class NURBSPatchGWNQuery SLIC_INFO(axom::fmt::format(" Preprocessing stage (moment precomputation): {} s", stage_timer.elapsedTimeInSec())); } + else + { + // Without fast-approximation, processing is unnecessary + m_processed_patches_view = m_input_patches_view; + } + + if(use_memoization) + { + stage_timer.reset(); + stage_timer.start(); + { + AXOM_ANNOTATE_SCOPE("cache_initialization"); + m_nurbs_cache_mgr = NURBSCacheManager(m_processed_patches_view); + } + stage_timer.stop(); + SLIC_INFO(axom::fmt::format(" Preprocessing stage (cache initialization): {} s", + stage_timer.elapsedTimeInSec())); + } timer.stop(); AXOM_ANNOTATE_METADATA("preprocessing_time", timer.elapsed(), ""); @@ -713,7 +737,7 @@ class NURBSPatchGWNQuery axom::for_all(num_query_points, [=, &winding, &inout](axom::IndexType index) { const double wn = axom::quest::fast_approximate_winding_number(query_point(index), traverser, - m_input_patches_view, + m_processed_patches_view, internal_moments_view, tol_copy); winding[static_cast(index)] = wn; @@ -747,7 +771,7 @@ class NURBSPatchGWNQuery axom::for_all(num_query_points, [=, &winding, &inout](axom::IndexType nidx) { const auto q = query_point(static_cast(nidx)); double wn {}; - for(const auto& patch : m_input_patches_view) + for(const auto& patch : m_processed_patches_view) { wn += axom::primal::winding_number(q, patch, @@ -805,6 +829,10 @@ class NURBSPatchGWNQuery axom::ArrayView m_input_patches_view; NURBSCacheManager m_nurbs_cache_mgr; + // For preprocessed patches + axom::Array m_subdivided_patches; + axom::ArrayView m_processed_patches_view; + // Only needed for fast approximation method axom::Array m_internal_moments; axom::spin::BVH<3, ExecSpace> m_bvh; diff --git a/src/axom/quest/examples/quest_winding_number_3d.cpp b/src/axom/quest/examples/quest_winding_number_3d.cpp index 9657859c89..c809248dd1 100644 --- a/src/axom/quest/examples/quest_winding_number_3d.cpp +++ b/src/axom/quest/examples/quest_winding_number_3d.cpp @@ -61,7 +61,7 @@ class Input axom::runtime_policy::Policy policy = RuntimePolicy::seq; - const std::array valid_algorithms{ "direct", "fast-approximation" }; + const std::array valid_algorithms{ "direct", "fast_approximate" }; std::string algorithm{ valid_algorithms[1] }; // fast-approximation bool triangulate{ false }; diff --git a/src/axom/quest/tests/quest_gwn_methods.cpp b/src/axom/quest/tests/quest_gwn_methods.cpp index 68deb19795..478b81379b 100644 --- a/src/axom/quest/tests/quest_gwn_methods.cpp +++ b/src/axom/quest/tests/quest_gwn_methods.cpp @@ -252,6 +252,26 @@ void check_mfem_mesh_linearization() EXPECT_EQ(inout_direct[i], inout_other[i]); } } + + mfem::VisItDataCollection windingDC_0("winding_0", dc[0].GetMesh()); + windingDC_0.RegisterField("winding", dc[0].GetField("winding")); + windingDC_0.RegisterField("inout", dc[0].GetField("inout")); + windingDC_0.Save(); + + mfem::VisItDataCollection windingDC_1("winding_1", dc[1].GetMesh()); + windingDC_1.RegisterField("winding", dc[1].GetField("winding")); + windingDC_1.RegisterField("inout", dc[1].GetField("inout")); + windingDC_1.Save(); + + mfem::VisItDataCollection windingDC_2("winding_2", dc[2].GetMesh()); + windingDC_2.RegisterField("winding", dc[2].GetField("winding")); + windingDC_2.RegisterField("inout", dc[2].GetField("inout")); + windingDC_2.Save(); + + mfem::VisItDataCollection windingDC_3("winding_3", dc[3].GetMesh()); + windingDC_3.RegisterField("winding", dc[3].GetField("winding")); + windingDC_3.RegisterField("inout", dc[3].GetField("inout")); + windingDC_3.Save(); } #ifdef AXOM_USE_OPENCASCADE @@ -315,7 +335,7 @@ void check_step_file_triangulation() SLIC_INFO("Testing Patch Evaluation"); //axom::quest::NURBSPatchGWNQuery gwn_patches {}; //gwn_patches.preprocess(patches, useDirectEvaluation, !useMemoization); - //gwn_patches.query(dc[0], tol); + //gwn_patches.query(dc[0], tol); axom::quest::NURBSPatchGWNQuery gwn_patches_memoized {}; gwn_patches_memoized.preprocess(patches, useDirectEvaluation, useMemoization); From 787b12659860b584345520829d6b6253858873ec Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Thu, 2 Apr 2026 14:30:51 -0700 Subject: [PATCH 235/986] Move GWNMomentData to primal, avoid normal recomputation --- src/axom/primal/CMakeLists.txt | 1 + src/axom/primal/geometry/GWNMomentData.hpp | 385 ++++++++++++++++++ .../detail/winding_number_3d_memoization.hpp | 90 ++-- src/axom/primal/operators/winding_number.hpp | 18 +- 4 files changed, 468 insertions(+), 26 deletions(-) create mode 100644 src/axom/primal/geometry/GWNMomentData.hpp diff --git a/src/axom/primal/CMakeLists.txt b/src/axom/primal/CMakeLists.txt index 5172c7fa2b..b6c23c62ad 100644 --- a/src/axom/primal/CMakeLists.txt +++ b/src/axom/primal/CMakeLists.txt @@ -27,6 +27,7 @@ set( primal_headers geometry/CoordinateTransformer.hpp geometry/Cone.hpp geometry/CurvedPolygon.hpp + geometry/GWNMomentData.hpp geometry/Hexahedron.hpp geometry/KnotVector.hpp geometry/Line.hpp diff --git a/src/axom/primal/geometry/GWNMomentData.hpp b/src/axom/primal/geometry/GWNMomentData.hpp new file mode 100644 index 0000000000..1a17743d70 --- /dev/null +++ b/src/axom/primal/geometry/GWNMomentData.hpp @@ -0,0 +1,385 @@ +// 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) + +/*! + * \file GWNMomentData.hpp + * + * \brief A helper function for managing relevant parameters used in GWN approximation + */ + +#ifndef AXOM_PRIMAL_GWN_MOMENT_DATA_HPP +#define AXOM_PRIMAL_GWN_MOMENT_DATA_HPP + +#include "axom/primal/geometry/Point.hpp" +#include "axom/primal/geometry/Vector.hpp" +#include "axom/primal/geometry/Segment.hpp" +#include "axom/primal/geometry/BezierCurve.hpp" +#include "axom/primal/geometry/NURBSCurve.hpp" +#include "axom/primal/geometry/Triangle.hpp" +#include "axom/primal/geometry/NURBSPatch.hpp" +#include + +namespace axom +{ +namespace primal +{ +namespace internal +{ +/// Computes the total number of moments for a given degree and order, i.e. +/// ndim + ndim^2 + ... + ndim^(ord + 1) +constexpr int get_num_moment_entries(int ndim, int ord) +{ + int n_entries = 0; + for(int i = 0; i < ord + 1; ++i) + { + int power = 1; + for(int j = 0; j < i + 1; ++j) + { + power *= ndim; + } + n_entries += power; + } + return n_entries; +} +} // namespace internal + +/* + * \class GWN Moment Data + * + * \brief A class to compute and store the geometric moment data which parameterizes the + * Taylor expansion of a GWN approximation for a cluster of geometric primitives. + * \tparam T The numeric type + * \tparam NDIMS The number of spatial dimensions of the geometric primitives (2 or 3) + * \tparam ORD The order of the Taylor expansion (0, 1, or 2) + * + * Stores arrays `rm` of raw moments and `ec` of Taylor expansion coefficients, as only the + * latter is used to compute the approximated GWN. Transforming raw moments to expansion coefficients + * requires knowing the center of the expansion, which we take as the centroid of the collection. + * + * Stores double `a` and vector `ap` which are the total unsigned area and weighted centroid of + * the collection of geometric objects. + */ +template +class GWNMomentData +{ + static_assert((NDIMS == 2 || NDIMS == 3), "Must be defined in 2 or 3 dimensions"); + static_assert(0 <= ORD && ORD <= 2, "Only supported for orders 0, 1, or 2"); + + static constexpr int NumberOfEntries = internal::get_num_moment_entries(NDIMS, ORD); + + /// Addition overload to find the sum of two sets of raw moments. + /// TODO: Technically, the raw moments for b1 and b2 can be deallocated after this + /// function is called, which would decrease the memory footprint + friend GWNMomentData operator+(GWNMomentData& b1, GWNMomentData& b2) + { + GWNMomentData b_out; + + b_out.a = b1.a + b2.a; + b_out.ap = b1.ap + b2.ap; + + for(int i = 0; i < NumberOfEntries; ++i) + { + b_out.rm[i] = b1.rm[i] + b2.rm[i]; + } + + b_out.compute_coefficients(); + + return b_out; + } + +public: + GWNMomentData() = default; + + /// Construct moments from a 3D triangle + explicit GWNMomentData(const axom::primal::Triangle& a_tri) + { + static_assert(NDIMS == 3, "GWN Moments for triangles are defined only for 3D"); + + // Track the centroid across the tree, and return the rest of the data + auto centroid = a_tri.centroid(); + a = a_tri.area(); + ap[0] = a * centroid[0]; + ap[1] = a * centroid[1]; + ap[2] = a * centroid[2]; + + auto normal = 0.5 * a_tri.normal(); + rm[0] = normal[0]; + rm[1] = normal[1]; + rm[2] = normal[2]; + + if constexpr(ORD >= 1) + { + int m = 3; + for(int i = 0; i < 9; ++i, ++m) + { + // In tensor product notation, equal to + // centroid \otimes normal + rm[m] = normal[i % 3] * centroid[i / 3]; + } + + if constexpr(ORD >= 2) + { + constexpr auto twlv = 1.0 / 12.0; + const auto ab = axom::primal::Vector {a_tri[0].array() + a_tri[1].array()}; + const auto bc = axom::primal::Vector {a_tri[1].array() + a_tri[2].array()}; + const auto ac = axom::primal::Vector {a_tri[0].array() + a_tri[2].array()}; + + for(int i = 0; i < 27; ++i, ++m) + { + // 1/12 * (ab \otimes ab + bc \otimes bc + ac \otimes ac) \otimes normal + rm[m] = twlv * normal[i % 3] * + (ab[i / 9] * ab[(i / 3) % 3] + bc[i / 9] * bc[(i / 3) % 3] + ac[i / 9] * ac[(i / 3) % 3]); + } + } + } + + compute_coefficients(); + } + + /// Construct moments from a trimmed NURBS surface + explicit GWNMomentData(const axom::primal::NURBSPatch& a_patch) + { + const auto patch_data = a_patch.calculateSurfaceMoments(); + + a = patch_data[0]; + ap[0] = patch_data[1]; + ap[1] = patch_data[2]; + ap[2] = patch_data[3]; + + for(int i = 0; i < NumberOfEntries; ++i) rm[i] = patch_data[i + 4]; + + compute_coefficients(); + } + + /// Construct moments from the endpoints of a 2D segment + explicit GWNMomentData(const axom::primal::NURBSCurve& c) + : GWNMomentData(c.getInitPoint(), c.getEndPoint()) + { } + + /// Construct moments from a 2D Segment + explicit GWNMomentData(const axom::primal::Segment& s) + : GWNMomentData(s.source(), s.target()) + { } + + /// Construct moments from the endpoints of a 2D segment + explicit GWNMomentData(const axom::primal::Point& p0, const axom::primal::Point& p1) + { + static_assert(NDIMS == 2, "GWN Moments for segments are defined only for 2D"); + + const auto x0 = p0[0]; + const auto y0 = p0[1]; + const auto x1 = p1[0]; + const auto y1 = p1[1]; + + // Needed to track the centroid across the tree + const auto dx = x1 - x0; + const auto dy = y0 - y1; // actually -dy since it was more useful. + a = sqrt(dx * dx + dy * dy); + ap[0] = a * 0.5 * (x0 + x1); + ap[1] = a * 0.5 * (y0 + y1); + + const auto x0x0 = x0 * x0; + const auto x0x1 = x0 * x1; + const auto x1x1 = x1 * x1; + + const auto y0y0 = y0 * y0; + const auto y0y1 = y0 * y1; + const auto y1y1 = y1 * y1; + + const auto x0y0 = x0 * y0; + const auto x0y1 = x0 * y1; + const auto x1y0 = x1 * y0; + const auto x1y1 = x1 * y1; + + rm[0] = dy; + rm[1] = dx; + + if constexpr(ORD >= 1) + { + rm[2] = 0.5 * dy * (x0 + x1); + rm[3] = 0.5 * (x1x1 - x0x0); + rm[4] = 0.5 * (y0y0 - y1y1); + rm[5] = 0.5 * (dx) * (y0 + y1); + + if constexpr(ORD == 2) + { + const auto A = (x0x0 + x0x1 + x1x1) / 3.0; + rm[6] = dy * A; + const auto B = (x0y0 + 0.5 * (x0y1 + x1y0) + x1y1) / 3.0; + rm[7] = dy * B; + rm[8] = rm[7]; + const auto C = (y0y0 + y0y1 + y1y1) / 3.0; + rm[9] = dy * C; + rm[10] = dx * A; + rm[11] = dx * B; + rm[12] = rm[11]; + rm[13] = dx * C; + } + } + + compute_coefficients(); + } + + /// Computes the approximated GWN field at the given query. + /// Formulae are taken from "Fast Winding Numbers for Soups and Clouds" by + /// Barill et al. (2018) + double approx_winding_number(axom::primal::Point query) const + { + if(axom::utilities::isNearlyEqual(std::abs(a), 0.0)) return 0.0; + + T terms[3] = {0.0, 0.0, 0.0}; + axom::primal::Vector pq(query, getCenter()); + const double norm = pq.norm(); + + if constexpr(NDIMS == 2) + { + const axom::primal::Vector F1 {ec[0], ec[1]}; + axom::primal::Vector G1 {pq[0], pq[1]}; + + const auto norm_to_2 = norm * norm; + terms[0] = F1.dot(G1) / norm_to_2; + + if constexpr(ORD >= 1) + { + const axom::primal::Vector F2 {ec[2], ec[3], ec[4], ec[5]}; + axom::primal::Vector G2 {pq[0] * pq[0] - pq[1] * pq[1], 2 * pq[0] * pq[1], 0.0, 0.0}; + G2[2] = G2[1]; + G2[3] = -G2[0]; + + const auto norm_to_4 = norm_to_2 * norm * norm; + terms[1] = -F2.dot(G2) / norm_to_4; + + if constexpr(ORD == 2) + { + const axom::primal::Vector + F3 {ec[6], ec[7], ec[8], ec[9], ec[10], ec[11], ec[12], ec[13]}; + const double g0 = 2 * pq[0] * (pq[0] * pq[0] - 3 * pq[1] * pq[1]); + const double g1 = 2 * pq[1] * (3 * pq[0] * pq[0] - pq[1] * pq[1]); + axom::primal::Vector G3 {g0, g1, g1, -g0, g1, -g0, -g0, -g1}; + + const auto norm_to_6 = norm_to_4 * norm * norm; + terms[2] = F3.dot(G3) / norm_to_6; + } + } + + return -0.5 * M_1_PI * (terms[0] + terms[1] + terms[2]); + } + + if constexpr(NDIMS == 3) + { + const axom::primal::Vector F1 {ec[0], ec[1], ec[2]}; + axom::primal::Vector G1 {pq[0], pq[1], pq[2]}; + + const auto norm_to_3 = norm * norm * norm; + terms[0] = F1.dot(G1) / norm_to_3; + + if constexpr(ORD >= 1) + { + const axom::primal::Vector + F2 {ec[3], ec[4], ec[5], ec[6], ec[7], ec[8], ec[9], ec[10], ec[11]}; + // clang-format off + axom::primal::Vector G2_1{ 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0 }; + axom::primal::Vector G2_2{ pq[0] * pq[0], pq[1] * pq[0], pq[2] * pq[0], + pq[0] * pq[1], pq[1] * pq[1], pq[2] * pq[1], + pq[0] * pq[2], pq[1] * pq[2], pq[2] * pq[2] }; + // clang-format on + + const auto norm_to_5 = norm_to_3 * norm * norm; + terms[1] = F2.dot(G2_1 * (1. / norm_to_3) - G2_2 * (3. / norm_to_5)); + + if constexpr(ORD >= 2) + { + const axom::primal::Vector F3 { + ec[12], ec[13], ec[14], ec[15], ec[16], ec[17], ec[18], ec[19], ec[20], + ec[21], ec[22], ec[23], ec[24], ec[25], ec[26], ec[27], ec[28], ec[29], + ec[30], ec[31], ec[32], ec[33], ec[34], ec[35], ec[36], ec[37], ec[38]}; + + // clang-format off + axom::primal::Vector G3_1{ 3 * pq[0], pq[1], pq[2], pq[1], pq[0], 0, pq[2], 0, pq[0], pq[1], pq[0], 0, pq[0],3 * pq[1], pq[2], 0, pq[2], pq[1], pq[2], 0, pq[0], 0, pq[2], pq[1], pq[0], pq[1], 3 * pq[2] }; + axom::primal::Vector G3_2{ pq[0] * pq[0] * pq[0], pq[0] * pq[0] * pq[1], pq[0] * pq[0] * pq[2], pq[0] * pq[1] * pq[0], pq[0] * pq[1] * pq[1], pq[0] * pq[1] * pq[2], pq[0] * pq[2] * pq[0], pq[0] * pq[2] * pq[1], pq[0] * pq[2] * pq[2], + pq[1] * pq[0] * pq[0], pq[1] * pq[0] * pq[1], pq[1] * pq[0] * pq[2], pq[1] * pq[1] * pq[0], pq[1] * pq[1] * pq[1], pq[1] * pq[1] * pq[2], pq[1] * pq[2] * pq[0], pq[1] * pq[2] * pq[1], pq[1] * pq[2] * pq[2], + pq[2] * pq[0] * pq[0], pq[2] * pq[0] * pq[1], pq[2] * pq[0] * pq[2], pq[2] * pq[1] * pq[0], pq[2] * pq[1] * pq[1], pq[2] * pq[1] * pq[2], pq[2] * pq[2] * pq[0], pq[2] * pq[2] * pq[1], pq[2] * pq[2] * pq[2] }; + // clang-format on + + // The formula in [Barill 2018] incorrectly lists (-1. / norm_to_5) + const auto norm_to_7 = norm_to_5 * norm * norm; + terms[2] = F3.dot(G3_1 * (-3. / norm_to_5) + G3_2 * (15. / norm_to_7)); + } + } + + return 0.25 * M_1_PI * (terms[0] + terms[1] + terms[2]); + } + } + + /// Return the center of the Taylor expansion + axom::primal::Point getCenter() const + { + if(a == 0) + { + return axom::primal::Point {}; + } + + return axom::primal::Point((ap / a).array()); + } + + /// Return the normal if computed from 3D data + axom::primal::Vector getNormal() const + { + static_assert(NDIMS == 3, "GWN Moments for triangles are defined only for 3D"); + return axom::primal::Vector {ec[0], ec[1], ec[2]}; + } + +private: + /// Transform raw moments into expansion coefficients + void compute_coefficients() + { + auto p = getCenter(); + + for(int i = 0; i < NDIMS; ++i) + { + ec[i] = rm[i]; + } + + if constexpr(ORD >= 1) + { + int m = NDIMS; + for(int i = 0; i < NDIMS * NDIMS; ++i, ++m) + { + ec[m] = rm[m] - p[i / NDIMS] * rm[i % NDIMS]; + } + + if constexpr(ORD == 2) + { + for(int i = 0; i < NDIMS * NDIMS * NDIMS; ++i, ++m) + { + // Example values for NDIMS = 2 + const int A = (i / NDIMS / NDIMS) % NDIMS; // 0, 0, 0, 0, 1, 1, 1, 1 + const int B = (i / NDIMS) % NDIMS; // 0, 0, 1, 1, 0, 0, 1, 1 + const int C = (i / 1) % NDIMS; // 0, 1, 0, 1, 0, 1, 0, 1 + const int D = i % (NDIMS * NDIMS); // 0, 1, 2, 3, 0, 1, 2, 3 + + ec[m] = 0.5 * + (rm[m] - p[A] * rm[NDIMS + D] - p[B] * rm[(A + 1) * NDIMS + C] + p[A] * p[B] * rm[C]); + } + } + } + } + +public: + // Store accumulated values across the node's children + axom::primal::Vector ap; // Weighted centroid + double a {}; + + // Raw moments + axom::StackArray rm {}; + + // Expansion coefficients + axom::StackArray ec {}; +}; + +} // end namespace primal +} // end namespace axom + +#endif diff --git a/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp b/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp index 93dbc09822..e3b29a8982 100644 --- a/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp +++ b/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp @@ -23,6 +23,7 @@ #include "axom/primal/geometry/Point.hpp" #include "axom/primal/geometry/Vector.hpp" #include "axom/primal/geometry/BoundingBox.hpp" +#include "axom/primal/geometry/GWNMomentData.hpp" #include "axom/primal/operators/is_convex.hpp" @@ -155,32 +156,22 @@ class NURBSPatchGWNCache NURBSPatchGWNCache() = default; /// \brief Initialize the cache with the data for a single NURBS patch - NURBSPatchGWNCache(const NURBSPatch& a_patch) : m_alteredPatch(a_patch) + NURBSPatchGWNCache(const NURBSPatch& a_patch, bool computeNormal = true) + : m_alteredPatch(a_patch) { m_alteredPatch.normalizeBySpan(); + // Make trivially untrimmed if needed // Calculate the average normal for the untrimmed patch if(!m_alteredPatch.isTrimmed()) { - m_averageNormal = m_alteredPatch.calculateUntrimmedPatchNormal(); m_alteredPatch.makeTriviallyTrimmed(); } - else - { - m_averageNormal = m_alteredPatch.calculateTrimmedPatchNormal(); - } - // Cast direction is set to average normal, unless it is near zero - if(m_averageNormal.norm() < 1e-10) - { - // ...unless the average direction is zero - double theta = axom::utilities::random_real(0.0, 2 * M_PI); - double u = axom::utilities::random_real(-1.0, 1.0); - m_castDirection = Vector {sin(theta) * sqrt(1 - u * u), cos(theta) * sqrt(1 - u * u), u}; - } - else + if(computeNormal) { - m_castDirection = m_averageNormal.unitVector(); + setNormal(m_alteredPatch.isTrimmed() ? m_alteredPatch.calculateTrimmedPatchNormal() + : m_alteredPatch.calculateUntrimmedPatchNormal()); } m_pboxDiag = m_alteredPatch.getParameterSpaceDiagonal(); @@ -250,8 +241,26 @@ class NURBSPatchGWNCache ///@{ //! \name Accessors for precomputed data - const Vector& getAverageNormal() const { return m_averageNormal; } + const Vector& getNormal() const { return m_normal; } const Vector& getCastDirection() const { return m_castDirection; } + void setNormal(const Vector& v) + { + m_normal = v; + + // Cast direction is always set to average normal, unless it is near zero + if(m_normal.norm() < 1e-10) + { + // ...unless the average direction is zero + double theta = axom::utilities::random_real(0.0, 2 * M_PI); + double u = axom::utilities::random_real(-1.0, 1.0); + m_castDirection = Vector {sin(theta) * sqrt(1 - u * u), cos(theta) * sqrt(1 - u * u), u}; + } + else + { + m_castDirection = m_normal.unitVector(); + } + } + const BoundingBox& boundingBox() const { return m_bBox; } const OrientedBoundingBox& orientedBoundingBox() const { return m_oBox; } //@} @@ -286,7 +295,7 @@ class NURBSPatchGWNCache // Per patch data BoundingBox m_bBox; OrientedBoundingBox m_oBox; - Vector m_averageNormal, m_castDirection; + Vector m_normal, m_castDirection; double m_pboxDiag; // Per trimming curve data, keyed by (whichRefinementLevel, whichRefinementIndex) @@ -309,11 +318,26 @@ class NURBSPatchCacheManager public: NURBSPatchCacheManager() = default; - NURBSPatchCacheManager(PatchArrayView patchs) + template + NURBSPatchCacheManager(PatchArrayView patches, + axom::ArrayView> moments) { - for(auto& patch : patchs) + SLIC_ASSERT(moments.empty() || moments.size() == patches.size()); + const bool computeNormal = !moments.empty(); + + for(auto& patch : patches) { - m_nurbs_caches.push_back(NURBSCache(patch)); + m_nurbs_caches.push_back(NURBSCache(patch, computeNormal)); + } + + // If we didn't comptue normals in NURBSCache constructor, + // need to get them from the moments + if(!computeNormal) + { + for(int n = 0; n < moments.size(); ++n) + { + m_nurbs_caches[n].setNormal(moments[n].getNormal()); + } } } @@ -358,17 +382,35 @@ class NURBSPatchCacheManagerOMP public: NURBSPatchCacheManagerOMP() = default; - NURBSPatchCacheManagerOMP(PatchArrayView patches) + template + NURBSPatchCacheManagerOMP( + PatchArrayView patches, + axom::ArrayView> moments) { + SLIC_ASSERT(moments.empty() || moments.size() == patches.size()); + const bool computeNormal = !moments.empty(); + const int nt = omp_get_max_threads(); m_nurbs_caches.resize(nt); auto nurbs_caches_view = m_nurbs_caches.view(); - // Make the first one + // Make the first cache nurbs_caches_view[0].resize(patches.size()); axom::for_all( patches.size(), - AXOM_HOST_LAMBDA(axom::IndexType i) { nurbs_caches_view[0][i] = NURBSCache(patches[i]); }); + AXOM_LAMBDA(axom::IndexType i) { + nurbs_caches_view[0][i] = NURBSCache(patches[i], computeNormal); + }); + + // If we didn't comptue normals in NURBSCache constructor, + // need to get them from the moments + if(!computeNormal) + { + axom::for_all( + patches.size(), + AXOM_LAMBDA(axom::IndexType i) { nurbs_caches_view[0][i].setNormal(moments[i].getNormal()); }); + } + // Copy the constructed cache to the other threads' copies (less work than construction) axom::for_all( 1, diff --git a/src/axom/primal/operators/winding_number.hpp b/src/axom/primal/operators/winding_number.hpp index 69db83afb9..0e60184395 100644 --- a/src/axom/primal/operators/winding_number.hpp +++ b/src/axom/primal/operators/winding_number.hpp @@ -566,13 +566,27 @@ double winding_number(const Point& q, return 0; } - const double num = Vec3::scalar_triple_product(a, b, c); - if(axom::utilities::isNearlyEqual(num, 0.0, EPS)) + // Explicitly compute distance from the triangle plane to the point. + // Use the triangle's (query-independent) normal here instead of (b x c), + // which can be zero when the query lies on the line through tri[1] and tri[2]. + const auto tri_normal = Vec3::cross_product(tri[1] - tri[0], tri[2] - tri[0]); + const double tri_normal_norm = tri_normal.norm(); + if(axom::utilities::isNearlyEqual(tri_normal_norm, 0.0, PRIMAL_TINY)) + { + return 0; + } + + if(axom::utilities::isNearlyEqual(Vec3::dot_product(q - tri[0], tri_normal) / tri_normal_norm, + 0.0, + edge_tol)) { isOnFace = true; return 0; } + const auto bxc = Vec3::cross_product(b, c); + const double num = Vec3::dot_product(a, bxc); + const double denom = a_norm * b_norm * c_norm + a_norm * b.dot(c) + b_norm * a.dot(c) + c_norm * a.dot(b); From 7531ce33bf91794705875e9fcc7434d6d51a4caa Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Thu, 2 Apr 2026 16:27:05 -0700 Subject: [PATCH 236/986] Some bug fixes related to threading, improve 3D gwn readability --- src/axom/core/utilities/Utilities.hpp | 32 +- .../detail/winding_number_3d_impl.hpp | 535 +++++++++--------- .../internal/linear_bvh/build_radix_tree.hpp | 37 +- 3 files changed, 328 insertions(+), 276 deletions(-) diff --git a/src/axom/core/utilities/Utilities.hpp b/src/axom/core/utilities/Utilities.hpp index ab55497844..4cd2801f54 100644 --- a/src/axom/core/utilities/Utilities.hpp +++ b/src/axom/core/utilities/Utilities.hpp @@ -22,6 +22,7 @@ #include // for assert() #include // for log2() +#include // for std::uint64_t #include // for random number generator #include // for std::is_floating_point() @@ -243,9 +244,14 @@ inline T random_real(const T& a, const T& b) AXOM_STATIC_ASSERT(std::is_floating_point::value); assert((a < b) && "invalid bounds, a < b"); - static std::random_device rd; - static std::mt19937_64 mt(rd()); - static std::uniform_real_distribution dist(0.0, 1.0); + // Thread-local RNG state: avoids data races when called from threaded code. + thread_local std::mt19937_64 mt([]() { + std::random_device rd; + const std::uint64_t seed_hi = static_cast(rd()) << 32; + const std::uint64_t seed_lo = static_cast(rd()); + return std::mt19937_64(seed_hi ^ seed_lo); + }()); + thread_local std::uniform_real_distribution dist(0.0, 1.0); T temp = dist(mt); return temp * (b - a) + a; @@ -275,10 +281,24 @@ inline T random_real(const T& a, const T& b, unsigned int seed) AXOM_STATIC_ASSERT(std::is_floating_point::value); assert((a < b) && "invalid bounds, a < b"); - static std::mt19937_64 mt(seed); - static std::uniform_real_distribution dist(0.0, 1.0); + // Thread-local RNG state: avoids data races when called from threaded code. + // Also supports switching seeds by re-seeding the engine. + struct SeededRngState + { + explicit SeededRngState(unsigned int s) : mt(s), current_seed(s) { } + std::mt19937_64 mt; + unsigned int current_seed; + }; + + thread_local SeededRngState state(seed); + if(state.current_seed != seed) + { + state.mt.seed(seed); + state.current_seed = seed; + } + thread_local std::uniform_real_distribution dist(0.0, 1.0); - double temp = dist(mt); + T temp = dist(state.mt); return temp * (b - a) + a; } diff --git a/src/axom/primal/operators/detail/winding_number_3d_impl.hpp b/src/axom/primal/operators/detail/winding_number_3d_impl.hpp index 9901e3964a..aee0ad8235 100644 --- a/src/axom/primal/operators/detail/winding_number_3d_impl.hpp +++ b/src/axom/primal/operators/detail/winding_number_3d_impl.hpp @@ -561,7 +561,12 @@ double nurbs_winding_number(const Point& query, const double edge_tol_sq = edge_tol * edge_tol; // Fix the number of quadrature points arbitrarily - constexpr int quad_npts = 15; + constexpr int k_quad_npts = 15; + constexpr double k_bbox_expand_frac = 0.01; + constexpr double k_degenerate_clip_frac = 0.01; + constexpr double k_disk_shrink_on_surface_frac = 0.01; + constexpr int k_max_noncoincident_intersections = 5; + constexpr int k_max_recast_attempts = 128; // Store the winding number double the_gwn = 0.0; @@ -579,318 +584,318 @@ double nurbs_winding_number(const Point& query, return Vector {sin(theta) * sqrt(1 - u * u), cos(theta) * sqrt(1 - u * u), u}; }; - // Rotation matrix for the patch - numerics::Matrix rotator; + // Recasting is implemented as a loop (not recursion) to avoid stack overflows + // in OpenMP thread stacks on pathological intersections. + Vector cast_direction_local = cast_direction; + for(int recast_attempt = 0; recast_attempt < k_max_recast_attempts; ++recast_attempt) + { + const bool can_recast = (recast_attempt + 1 < k_max_recast_attempts); + + auto request_recast = [&]() -> bool { + if(can_recast) + { + cast_direction_local = random_unit(); + return true; + } + return false; + }; - // Lazily allocate space for the patch which contains all surface boundaries, - // and any extra trimming curves added by disk extraction. - // Note: For most query points (relative to a patch bounding box), we exit early - // and can avoid making a deep copy of the surface and trimming curves. - std::optional> nurbs_modified; + the_gwn = 0.0; - // Define vector fields whose curl gives us the winding number - DiscontinuityAxis field_direction = DiscontinuityAxis::rotated; - bool extraTrimming = false; + // Rotation matrix for the patch + numerics::Matrix rotator; - // Generate slightly expanded bounding boxes - auto bBox = nurbs.boundingBox(); - auto oBox = nurbs.orientedBoundingBox(); + // Lazily allocate space for the patch which contains all surface boundaries, + // and any extra trimming curves added by disk extraction. + // Note: For most query points (relative to a patch bounding box), we exit early + // and can avoid making a deep copy of the surface and trimming curves. + std::optional> nurbs_modified; - auto patch_diameter = bBox.range().norm(); + // Define vector fields whose curl gives us the winding number + DiscontinuityAxis field_direction = DiscontinuityAxis::rotated; + bool extraTrimming = false; - bBox.expand(0.01 * patch_diameter); - oBox.expand(0.01 * patch_diameter); + // Generate slightly expanded bounding boxes + auto bBox = nurbs.boundingBox(); + auto oBox = nurbs.orientedBoundingBox(); - // Case 1: Exterior without rotations - if(!bBox.contains(query)) - { - double bestDist = -1.0; + auto patch_diameter = bBox.range().norm(); - if(query[0] <= bBox.getMin()[0]) + bBox.expand(k_bbox_expand_frac * patch_diameter); + oBox.expand(k_bbox_expand_frac * patch_diameter); + + // Case 1: Exterior without rotations + if(!bBox.contains(query)) { - double d = bBox.getMin()[0] - query[0]; - if(d > bestDist) + double best_dist = -1.0; + auto consider_axis = [&](double dist, DiscontinuityAxis axis) { + if(dist > best_dist) + { + best_dist = dist; + field_direction = axis; + } + }; + + if(query[0] <= bBox.getMin()[0]) { - bestDist = d; - field_direction = DiscontinuityAxis::y; + consider_axis(bBox.getMin()[0] - query[0], DiscontinuityAxis::y); } - } - else if(query[0] >= bBox.getMax()[0]) - { - double d = query[0] - bBox.getMax()[0]; - if(d > bestDist) + else if(query[0] >= bBox.getMax()[0]) { - bestDist = d; - field_direction = DiscontinuityAxis::y; + consider_axis(query[0] - bBox.getMax()[0], DiscontinuityAxis::y); } - } - if(query[1] <= bBox.getMin()[1]) - { - double d = bBox.getMin()[1] - query[1]; - if(d > bestDist) + if(query[1] <= bBox.getMin()[1]) { - bestDist = d; - field_direction = DiscontinuityAxis::z; + consider_axis(bBox.getMin()[1] - query[1], DiscontinuityAxis::z); } - } - else if(query[1] >= bBox.getMax()[1]) - { - double d = query[1] - bBox.getMax()[1]; - if(d > bestDist) + else if(query[1] >= bBox.getMax()[1]) { - bestDist = d; - field_direction = DiscontinuityAxis::z; + consider_axis(query[1] - bBox.getMax()[1], DiscontinuityAxis::z); } - } - if(query[2] <= bBox.getMin()[2]) - { - double d = bBox.getMin()[2] - query[2]; - if(d > bestDist) + if(query[2] <= bBox.getMin()[2]) + { + consider_axis(bBox.getMin()[2] - query[2], DiscontinuityAxis::y); + } + else if(query[2] >= bBox.getMax()[2]) { - bestDist = d; - field_direction = DiscontinuityAxis::y; + consider_axis(query[2] - bBox.getMax()[2], DiscontinuityAxis::x); } } - else if(query[2] >= bBox.getMax()[2]) + // Case 1.5: Exterior with rotation + else if(!oBox.contains(query)) { - double d = query[2] - bBox.getMax()[2]; - if(d > bestDist) + // Rotate the patch until the OBB is not directly above/below the query point. + field_direction = DiscontinuityAxis::rotated; + + // Find vector from query to the bounding box + const Point closest = closest_point(query, oBox); + const Vector v0 = Vector(query, closest).unitVector(); + + // Find the direction of a ray perpendicular to that + Vector v1; + if(std::abs(v0[2]) > std::abs(v0[0])) { - bestDist = d; - field_direction = DiscontinuityAxis::x; + v1 = Vector({v0[2], v0[2], -v0[0] - v0[1]}).unitVector(); + } + else + { + v1 = Vector({-v0[1] - v0[2], v0[0], v0[0]}).unitVector(); } - } - } - // Case 1.5: Exterior with rotation - else if(!oBox.contains(query)) - { - /* The following steps rotate the patch until the OBB is /not/ - directly above or below the query point */ - field_direction = DiscontinuityAxis::rotated; - // Find vector from query to the bounding box - const Point closest = closest_point(query, oBox); - const Vector v0 = Vector(query, closest).unitVector(); + // Rotate v0 around v1 until it is perpendicular to the plane spanned by k and v1 + const double ang = (v0[2] < 0 ? 1.0 : -1.0) * + acos(axom::utilities::clampVal( + -(v0[0] * v1[1] - v0[1] * v1[0]) / sqrt(v1[0] * v1[0] + v1[1] * v1[1]), + -1.0, + 1.0)); - // Find the direction of a ray perpendicular to that - Vector v1; - if(std::abs(v0[2]) > std::abs(v0[0])) - { - v1 = Vector({v0[2], v0[2], -v0[0] - v0[1]}).unitVector(); + rotator = numerics::transforms::axisRotation(ang, v1[0], v1[1], v1[2]); } else { - v1 = Vector({-v0[1] - v0[2], v0[0], v0[0]}).unitVector(); - } - - // Rotate v0 around v1 until it is perpendicular to the plane spanned by k and v1 - const double ang = (v0[2] < 0 ? 1.0 : -1.0) * - acos(axom::utilities::clampVal( - -(v0[0] * v1[1] - v0[1] * v1[0]) / sqrt(v1[0] * v1[0] + v1[1] * v1[1]), - -1.0, - 1.0)); - - rotator = numerics::transforms::axisRotation(ang, v1[0], v1[1], v1[2]); - } - else - { - field_direction = DiscontinuityAxis::rotated; - const Line discontinuity_axis(query, cast_direction); - - // Tolerance for what counts as "close to a boundary" in parameter space - T disk_radius = disk_size * nurbs.getParameterSpaceDiagonal(); - - // Compute intersections with the *untrimmed and extrapolated* patch - axom::Array up, vp, tp; - const bool isHalfOpen = false, countUntrimmed = true; - - bool success = true; - nurbs_modified.emplace(nurbs.getControlPoints(), - nurbs.getWeights(), - nurbs.getKnots_u(), - nurbs.getKnots_v()); - nurbs_modified->setTrimmingCurves(nurbs.getTrimmingCurves()); - - intersect(discontinuity_axis, - *nurbs_modified, - tp, - up, - vp, - ls_tol, - EPS, - countUntrimmed, - isHalfOpen, - success); - - if(!success) - { - // Look at the intersection points - int num_noncoincident = 0; - for(int i = 0; i < tp.size(); ++i) + field_direction = DiscontinuityAxis::rotated; + const Line discontinuity_axis(query, cast_direction_local); + bool should_recast = false; + + // Tolerance for what counts as "close to a boundary" in parameter space + T disk_radius = disk_size * nurbs.getParameterSpaceDiagonal(); + + // Compute intersections with the *untrimmed and extrapolated* patch + axom::Array up, vp, tp; + const bool isHalfOpen = false, countUntrimmed = true; + + bool success = true; + nurbs_modified.emplace(nurbs.getControlPoints(), + nurbs.getWeights(), + nurbs.getKnots_u(), + nurbs.getKnots_v()); + nurbs_modified->setTrimmingCurves(nurbs.getTrimmingCurves()); + + intersect(discontinuity_axis, + *nurbs_modified, + tp, + up, + vp, + ls_tol, + EPS, + countUntrimmed, + isHalfOpen, + success); + + if(!success) { - const Point the_point(nurbs_modified->evaluate(up[i], vp[i])); - // If any of the intersection points are coincident with the surface, - // then attempt to clip out all degenerate intersections, and retry - if(squared_distance(query, the_point) <= edge_tol_sq) + // Look at the intersection points + int num_noncoincident = 0; + for(int i = 0; i < tp.size(); ++i) { - NURBSPatch clipped_patch1, clipped_patch2; - - degenerate_surface_processing(nurbs, up, vp, 0.01 * disk_radius, clipped_patch1, clipped_patch2); - - return nurbs_winding_number(query, - clipped_patch1, - cast_direction, - edge_tol, - ls_tol, - quad_tol * 1e-5, - disk_size, - EPS) + - nurbs_winding_number(query, - clipped_patch2, - cast_direction, - edge_tol, - ls_tol, - quad_tol * 1e-5, - disk_size, - EPS); - } - else - { - num_noncoincident++; + const Point the_point(nurbs_modified->evaluate(up[i], vp[i])); + // If any of the intersection points are coincident with the surface, + // then attempt to clip out all degenerate intersections, and retry + if(squared_distance(query, the_point) <= edge_tol_sq) + { + NURBSPatch clipped_patch1, clipped_patch2; + + degenerate_surface_processing(nurbs, + up, + vp, + k_degenerate_clip_frac * disk_radius, + clipped_patch1, + clipped_patch2); + + return nurbs_winding_number(query, + clipped_patch1, + cast_direction_local, + edge_tol, + ls_tol, + quad_tol * 1e-5, + disk_size, + EPS) + + nurbs_winding_number(query, + clipped_patch2, + cast_direction_local, + edge_tol, + ls_tol, + quad_tol * 1e-5, + disk_size, + EPS); + } + else + { + num_noncoincident++; + } + + // If more than 5 (arbitrary) are *not* coincident with the surface, + // re-cast and try again. This is to avoid cases where the point *is* + // coincident with the surface, but the first recorded point of + // intersection is not after multiple re-casts. + if(num_noncoincident > k_max_noncoincident_intersections && request_recast()) + { + should_recast = true; + break; + } } + } - // If more than 5 (arbitrary) are *not* coincident with the surface, - // re-cast and try again. This is to avoid cases where the point *is* - // coincident with the surface, but the first recorded point of - // intersection is not after multiple re-casts. - if(num_noncoincident > 5) - { - const auto new_cast_direction = random_unit(); - return nurbs_winding_number(query, - nurbs, - new_cast_direction, - edge_tol, - ls_tol, - quad_tol, - disk_size, - EPS); - } + if(should_recast) + { + continue; } - } - // If no intersections are recorded, then nothing extra to account for + // If no intersections are recorded, then nothing extra to account for - // Otherwise, account for each discontinuity analytically, - // or recursively through disk subdivision - for(int i = 0; i < up.size(); ++i) - { - // Compute the intersection point on the surface - const Point the_point(nurbs_modified->evaluate(up[i], vp[i])); - const Vector the_normal = nurbs_modified->normal(up[i], vp[i]); + // Otherwise, account for each discontinuity analytically, + // or recursively through disk subdivision + for(int i = 0; i < up.size(); ++i) + { + // Compute the intersection point on the surface + const Point the_point(nurbs_modified->evaluate(up[i], vp[i])); + const Vector the_normal = nurbs_modified->normal(up[i], vp[i]); - // Check for bad intersections, i.e., - // > There normal is poorly defined (cusp) - // > The normal is tangent to the axis of discontinuity - const bool bad_intersection = axom::utilities::isNearlyEqual(the_normal.norm(), 0.0, EPS) || - axom::utilities::isNearlyEqual(the_normal.unitVector().dot(cast_direction), 0.0, EPS); + // Check for bad intersections, i.e., + // > There normal is poorly defined (cusp) + // > The normal is tangent to the axis of discontinuity + const bool bad_intersection = axom::utilities::isNearlyEqual(the_normal.norm(), 0.0, EPS) || + axom::utilities::isNearlyEqual(the_normal.unitVector().dot(cast_direction_local), 0.0, EPS); - const bool isOnSurface = squared_distance(query, the_point) <= edge_tol_sq; + const bool isOnSurface = squared_distance(query, the_point) <= edge_tol_sq; - if(bad_intersection && !isOnSurface) - { - // If a non-coincident ray intersects the surface at a tangent/cusp, - // can recast and try again with the memoized patch - const auto new_cast_direction = random_unit(); - return nurbs_winding_number(query, - nurbs, - new_cast_direction, - edge_tol, - ls_tol, - quad_tol, - disk_size, - EPS); - } + if(bad_intersection && !isOnSurface && request_recast()) + { + // If a non-coincident ray intersects the surface at a tangent/cusp, + // can recast and try again with the memoized patch + should_recast = true; + break; + } - if(isOnSurface) - { - // If the query point is on the surface, then shrink the disk - // to ensure its winding number is known to be near-zero - disk_radius = 0.01 * disk_radius; - } + if(isOnSurface) + { + // If the query point is on the surface, then shrink the disk + // to ensure its winding number is known to be near-zero + disk_radius = k_disk_shrink_on_surface_frac * disk_radius; + } - // Consider a disk around the intersection point via NURBSPatch::diskSplit. - // If the disk intersects any trimming curves, need to do disk subdivision. - // If not, we can compute the winding number without changing the trimming curvse - const bool ignoreInteriorDisk = true; - bool isDiskInside, isDiskOutside; - NURBSPatch the_disk; - - nurbs_modified->diskSplit(up[i], - vp[i], - disk_radius, - the_disk, - *nurbs_modified, - isDiskInside, - isDiskOutside, - ignoreInteriorDisk, - disk_radius); - - extraTrimming = - extraTrimming || (!isDiskInside && !isDiskOutside) || (isDiskInside && !ignoreInteriorDisk); - - if(isOnSurface) - { - // If the query point is on the surface, the contribution of the disk is near-zero - // and we only needed to puncture the larger surface to proceed - continue; - } - else if(!isDiskInside && !isDiskOutside) - { - // If the disk overlapped with the trimming curves, evaluate the winding number for the disk - // with a cast ray that is mostly in the direction of the normal (assuming it's non-zero) - Vector new_cast_direction = the_disk.normal(up[i], vp[i]); - new_cast_direction = (new_cast_direction.norm() < EPS) - ? random_unit() - : (new_cast_direction.unitVector() + 0.1 * random_unit()).unitVector(); - - the_gwn += nurbs_winding_number(query, - the_disk, - new_cast_direction, - edge_tol, - ls_tol, - quad_tol, - disk_size, - EPS); + // Consider a disk around the intersection point via NURBSPatch::diskSplit. + // If the disk intersects any trimming curves, need to do disk subdivision. + // If not, we can compute the winding number without changing the trimming curvse + const bool ignoreInteriorDisk = true; + bool isDiskInside, isDiskOutside; + NURBSPatch the_disk; + + nurbs_modified->diskSplit(up[i], + vp[i], + disk_radius, + the_disk, + *nurbs_modified, + isDiskInside, + isDiskOutside, + ignoreInteriorDisk, + disk_radius); + + extraTrimming = extraTrimming || (!isDiskInside && !isDiskOutside) || + (isDiskInside && !ignoreInteriorDisk); + + if(isOnSurface) + { + // If the query point is on the surface, the contribution of the disk is near-zero + // and we only needed to puncture the larger surface to proceed + continue; + } + else if(!isDiskInside && !isDiskOutside) + { + // If the disk overlapped with the trimming curves, evaluate the winding number for the disk + // with a cast ray that is mostly in the direction of the normal (assuming it's non-zero) + Vector new_cast_direction = the_disk.normal(up[i], vp[i]); + new_cast_direction = (new_cast_direction.norm() < EPS) + ? random_unit() + : (new_cast_direction.unitVector() + 0.1 * random_unit()).unitVector(); + + the_gwn += nurbs_winding_number(query, + the_disk, + new_cast_direction, + edge_tol, + ls_tol, + quad_tol, + disk_size, + EPS); + } + else if(isDiskOutside) + { + // If the disk is entirely outside the trimming curves, can just look at the boundary + continue; + } + else if(isDiskInside) + { + // If the disk is entirely inside the trimming curves, + // need to account for the scalar field discontinuity + const auto the_direction = Vector(query, the_point).unitVector(); + the_gwn += std::copysign(0.5, the_normal.dot(the_direction)); + } } - else if(isDiskOutside) + + if(should_recast) { - // If the disk is entirely outside the trimming curves, can just look at the boundary continue; } - else if(isDiskInside) - { - // If the disk is entirely inside the trimming curves, - // need to account for the scalar field discontinuity - const auto the_direction = Vector(query, the_point).unitVector(); - the_gwn += std::copysign(0.5, the_normal.dot(the_direction)); - } + + // Rotate the patch so that the discontinuity is aligned with the z-axis + const double ang = std::acos(axom::utilities::clampVal(cast_direction_local[2], -1.0, 1.0)); + rotator = + numerics::transforms::axisRotation(ang, cast_direction_local[1], -cast_direction_local[0], 0); } - // Rotate the patch so that the discontinuity is aligned with the z-axis - const double ang = std::acos(axom::utilities::clampVal(cast_direction[2], -1.0, 1.0)); - rotator = numerics::transforms::axisRotation(ang, cast_direction[1], -cast_direction[0], 0); - } + if(extraTrimming) + { + the_gwn += + stokes_gwn_evaluate(query, *nurbs_modified, k_quad_npts, field_direction, rotator, quad_tol); + } + else + { + the_gwn += stokes_gwn_evaluate(query, nurbs, k_quad_npts, field_direction, rotator, quad_tol); + } - if(extraTrimming) - { - the_gwn += - stokes_gwn_evaluate(query, *nurbs_modified, quad_npts, field_direction, rotator, quad_tol); - } - else - { - the_gwn += stokes_gwn_evaluate(query, nurbs, quad_npts, field_direction, rotator, quad_tol); + return the_gwn; } return the_gwn; diff --git a/src/axom/spin/internal/linear_bvh/build_radix_tree.hpp b/src/axom/spin/internal/linear_bvh/build_radix_tree.hpp index 1e6beb87a1..1cbbbdf4ab 100644 --- a/src/axom/spin/internal/linear_bvh/build_radix_tree.hpp +++ b/src/axom/spin/internal/linear_bvh/build_radix_tree.hpp @@ -441,14 +441,31 @@ AXOM_HOST_DEVICE static inline BBoxType sync_load(const BBoxType& box) return BBoxType {min_pt, max_pt}; #else // AXOM_DEVICE_CODE - std::atomic_thread_fence(std::memory_order_acquire); - return box; + using FloatType = typename BBoxType::CoordType; + using PointType = typename BBoxType::PointType; + + constexpr int NDIMS = PointType::DIMENSION; + + PointType min_pt {BBoxType::InvalidMin}; + PointType max_pt {BBoxType::InvalidMax}; + + // Read each component atomically to avoid a racy read of the box struct. + // This mirrors the device path's "poll until non-sentinel" approach. + for(int dim = 0; dim < NDIMS; dim++) + { + while((min_pt[dim] = axom::atomicLoad(const_cast(&box.getMin()[dim]))) == + BBoxType::InvalidMin); + while((max_pt[dim] = axom::atomicLoad(const_cast(&box.getMax()[dim]))) == + BBoxType::InvalidMax); + } + + return BBoxType {min_pt, max_pt}; #endif } //------------------------------------------------------------------------------ // Writes a bounding box to memory, synchronized with another thread's read. -// On the CPU, this is achieved with a release fence. +// On the CPU, we use atomic stores for each component to avoid data races. // On the GPU, this function uses atomicExch to write a value directly to the // L2 cache, thus avoiding potential cache coherency issues between threads. template @@ -469,8 +486,18 @@ AXOM_HOST_DEVICE static inline void sync_store(BBoxType& box, const BBoxType& va axom::atomicExchange(&(max_pt[dim]), value.getMax()[dim]); } #else // __CUDA_ARCH__ || __HIP_DEVICE_COMPILE__ - box = value; - std::atomic_thread_fence(std::memory_order_release); + using PointType = typename BBoxType::PointType; + constexpr int NDIMS = PointType::DIMENSION; + + // Cast away the underlying const so we can directly modify the box data. + PointType& min_pt = const_cast(box.getMin()); + PointType& max_pt = const_cast(box.getMax()); + + for(int dim = 0; dim < NDIMS; dim++) + { + axom::atomicStore(&(min_pt[dim]), value.getMin()[dim]); + axom::atomicStore(&(max_pt[dim]), value.getMax()[dim]); + } #endif } From 6e318861600d52857349e64f1e69fe8060ff7436 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Thu, 2 Apr 2026 16:34:32 -0700 Subject: [PATCH 237/986] Undo move of GWN moment data to primal, pass an array of vectors instead --- src/axom/primal/CMakeLists.txt | 1 - src/axom/primal/geometry/GWNMomentData.hpp | 385 ------------------ .../detail/winding_number_3d_memoization.hpp | 28 +- src/axom/quest/FastApproximateGWN.hpp | 7 + src/axom/quest/GWNMethods.hpp | 15 +- 5 files changed, 33 insertions(+), 403 deletions(-) delete mode 100644 src/axom/primal/geometry/GWNMomentData.hpp diff --git a/src/axom/primal/CMakeLists.txt b/src/axom/primal/CMakeLists.txt index b6c23c62ad..5172c7fa2b 100644 --- a/src/axom/primal/CMakeLists.txt +++ b/src/axom/primal/CMakeLists.txt @@ -27,7 +27,6 @@ set( primal_headers geometry/CoordinateTransformer.hpp geometry/Cone.hpp geometry/CurvedPolygon.hpp - geometry/GWNMomentData.hpp geometry/Hexahedron.hpp geometry/KnotVector.hpp geometry/Line.hpp diff --git a/src/axom/primal/geometry/GWNMomentData.hpp b/src/axom/primal/geometry/GWNMomentData.hpp deleted file mode 100644 index 1a17743d70..0000000000 --- a/src/axom/primal/geometry/GWNMomentData.hpp +++ /dev/null @@ -1,385 +0,0 @@ -// 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) - -/*! - * \file GWNMomentData.hpp - * - * \brief A helper function for managing relevant parameters used in GWN approximation - */ - -#ifndef AXOM_PRIMAL_GWN_MOMENT_DATA_HPP -#define AXOM_PRIMAL_GWN_MOMENT_DATA_HPP - -#include "axom/primal/geometry/Point.hpp" -#include "axom/primal/geometry/Vector.hpp" -#include "axom/primal/geometry/Segment.hpp" -#include "axom/primal/geometry/BezierCurve.hpp" -#include "axom/primal/geometry/NURBSCurve.hpp" -#include "axom/primal/geometry/Triangle.hpp" -#include "axom/primal/geometry/NURBSPatch.hpp" -#include - -namespace axom -{ -namespace primal -{ -namespace internal -{ -/// Computes the total number of moments for a given degree and order, i.e. -/// ndim + ndim^2 + ... + ndim^(ord + 1) -constexpr int get_num_moment_entries(int ndim, int ord) -{ - int n_entries = 0; - for(int i = 0; i < ord + 1; ++i) - { - int power = 1; - for(int j = 0; j < i + 1; ++j) - { - power *= ndim; - } - n_entries += power; - } - return n_entries; -} -} // namespace internal - -/* - * \class GWN Moment Data - * - * \brief A class to compute and store the geometric moment data which parameterizes the - * Taylor expansion of a GWN approximation for a cluster of geometric primitives. - * \tparam T The numeric type - * \tparam NDIMS The number of spatial dimensions of the geometric primitives (2 or 3) - * \tparam ORD The order of the Taylor expansion (0, 1, or 2) - * - * Stores arrays `rm` of raw moments and `ec` of Taylor expansion coefficients, as only the - * latter is used to compute the approximated GWN. Transforming raw moments to expansion coefficients - * requires knowing the center of the expansion, which we take as the centroid of the collection. - * - * Stores double `a` and vector `ap` which are the total unsigned area and weighted centroid of - * the collection of geometric objects. - */ -template -class GWNMomentData -{ - static_assert((NDIMS == 2 || NDIMS == 3), "Must be defined in 2 or 3 dimensions"); - static_assert(0 <= ORD && ORD <= 2, "Only supported for orders 0, 1, or 2"); - - static constexpr int NumberOfEntries = internal::get_num_moment_entries(NDIMS, ORD); - - /// Addition overload to find the sum of two sets of raw moments. - /// TODO: Technically, the raw moments for b1 and b2 can be deallocated after this - /// function is called, which would decrease the memory footprint - friend GWNMomentData operator+(GWNMomentData& b1, GWNMomentData& b2) - { - GWNMomentData b_out; - - b_out.a = b1.a + b2.a; - b_out.ap = b1.ap + b2.ap; - - for(int i = 0; i < NumberOfEntries; ++i) - { - b_out.rm[i] = b1.rm[i] + b2.rm[i]; - } - - b_out.compute_coefficients(); - - return b_out; - } - -public: - GWNMomentData() = default; - - /// Construct moments from a 3D triangle - explicit GWNMomentData(const axom::primal::Triangle& a_tri) - { - static_assert(NDIMS == 3, "GWN Moments for triangles are defined only for 3D"); - - // Track the centroid across the tree, and return the rest of the data - auto centroid = a_tri.centroid(); - a = a_tri.area(); - ap[0] = a * centroid[0]; - ap[1] = a * centroid[1]; - ap[2] = a * centroid[2]; - - auto normal = 0.5 * a_tri.normal(); - rm[0] = normal[0]; - rm[1] = normal[1]; - rm[2] = normal[2]; - - if constexpr(ORD >= 1) - { - int m = 3; - for(int i = 0; i < 9; ++i, ++m) - { - // In tensor product notation, equal to - // centroid \otimes normal - rm[m] = normal[i % 3] * centroid[i / 3]; - } - - if constexpr(ORD >= 2) - { - constexpr auto twlv = 1.0 / 12.0; - const auto ab = axom::primal::Vector {a_tri[0].array() + a_tri[1].array()}; - const auto bc = axom::primal::Vector {a_tri[1].array() + a_tri[2].array()}; - const auto ac = axom::primal::Vector {a_tri[0].array() + a_tri[2].array()}; - - for(int i = 0; i < 27; ++i, ++m) - { - // 1/12 * (ab \otimes ab + bc \otimes bc + ac \otimes ac) \otimes normal - rm[m] = twlv * normal[i % 3] * - (ab[i / 9] * ab[(i / 3) % 3] + bc[i / 9] * bc[(i / 3) % 3] + ac[i / 9] * ac[(i / 3) % 3]); - } - } - } - - compute_coefficients(); - } - - /// Construct moments from a trimmed NURBS surface - explicit GWNMomentData(const axom::primal::NURBSPatch& a_patch) - { - const auto patch_data = a_patch.calculateSurfaceMoments(); - - a = patch_data[0]; - ap[0] = patch_data[1]; - ap[1] = patch_data[2]; - ap[2] = patch_data[3]; - - for(int i = 0; i < NumberOfEntries; ++i) rm[i] = patch_data[i + 4]; - - compute_coefficients(); - } - - /// Construct moments from the endpoints of a 2D segment - explicit GWNMomentData(const axom::primal::NURBSCurve& c) - : GWNMomentData(c.getInitPoint(), c.getEndPoint()) - { } - - /// Construct moments from a 2D Segment - explicit GWNMomentData(const axom::primal::Segment& s) - : GWNMomentData(s.source(), s.target()) - { } - - /// Construct moments from the endpoints of a 2D segment - explicit GWNMomentData(const axom::primal::Point& p0, const axom::primal::Point& p1) - { - static_assert(NDIMS == 2, "GWN Moments for segments are defined only for 2D"); - - const auto x0 = p0[0]; - const auto y0 = p0[1]; - const auto x1 = p1[0]; - const auto y1 = p1[1]; - - // Needed to track the centroid across the tree - const auto dx = x1 - x0; - const auto dy = y0 - y1; // actually -dy since it was more useful. - a = sqrt(dx * dx + dy * dy); - ap[0] = a * 0.5 * (x0 + x1); - ap[1] = a * 0.5 * (y0 + y1); - - const auto x0x0 = x0 * x0; - const auto x0x1 = x0 * x1; - const auto x1x1 = x1 * x1; - - const auto y0y0 = y0 * y0; - const auto y0y1 = y0 * y1; - const auto y1y1 = y1 * y1; - - const auto x0y0 = x0 * y0; - const auto x0y1 = x0 * y1; - const auto x1y0 = x1 * y0; - const auto x1y1 = x1 * y1; - - rm[0] = dy; - rm[1] = dx; - - if constexpr(ORD >= 1) - { - rm[2] = 0.5 * dy * (x0 + x1); - rm[3] = 0.5 * (x1x1 - x0x0); - rm[4] = 0.5 * (y0y0 - y1y1); - rm[5] = 0.5 * (dx) * (y0 + y1); - - if constexpr(ORD == 2) - { - const auto A = (x0x0 + x0x1 + x1x1) / 3.0; - rm[6] = dy * A; - const auto B = (x0y0 + 0.5 * (x0y1 + x1y0) + x1y1) / 3.0; - rm[7] = dy * B; - rm[8] = rm[7]; - const auto C = (y0y0 + y0y1 + y1y1) / 3.0; - rm[9] = dy * C; - rm[10] = dx * A; - rm[11] = dx * B; - rm[12] = rm[11]; - rm[13] = dx * C; - } - } - - compute_coefficients(); - } - - /// Computes the approximated GWN field at the given query. - /// Formulae are taken from "Fast Winding Numbers for Soups and Clouds" by - /// Barill et al. (2018) - double approx_winding_number(axom::primal::Point query) const - { - if(axom::utilities::isNearlyEqual(std::abs(a), 0.0)) return 0.0; - - T terms[3] = {0.0, 0.0, 0.0}; - axom::primal::Vector pq(query, getCenter()); - const double norm = pq.norm(); - - if constexpr(NDIMS == 2) - { - const axom::primal::Vector F1 {ec[0], ec[1]}; - axom::primal::Vector G1 {pq[0], pq[1]}; - - const auto norm_to_2 = norm * norm; - terms[0] = F1.dot(G1) / norm_to_2; - - if constexpr(ORD >= 1) - { - const axom::primal::Vector F2 {ec[2], ec[3], ec[4], ec[5]}; - axom::primal::Vector G2 {pq[0] * pq[0] - pq[1] * pq[1], 2 * pq[0] * pq[1], 0.0, 0.0}; - G2[2] = G2[1]; - G2[3] = -G2[0]; - - const auto norm_to_4 = norm_to_2 * norm * norm; - terms[1] = -F2.dot(G2) / norm_to_4; - - if constexpr(ORD == 2) - { - const axom::primal::Vector - F3 {ec[6], ec[7], ec[8], ec[9], ec[10], ec[11], ec[12], ec[13]}; - const double g0 = 2 * pq[0] * (pq[0] * pq[0] - 3 * pq[1] * pq[1]); - const double g1 = 2 * pq[1] * (3 * pq[0] * pq[0] - pq[1] * pq[1]); - axom::primal::Vector G3 {g0, g1, g1, -g0, g1, -g0, -g0, -g1}; - - const auto norm_to_6 = norm_to_4 * norm * norm; - terms[2] = F3.dot(G3) / norm_to_6; - } - } - - return -0.5 * M_1_PI * (terms[0] + terms[1] + terms[2]); - } - - if constexpr(NDIMS == 3) - { - const axom::primal::Vector F1 {ec[0], ec[1], ec[2]}; - axom::primal::Vector G1 {pq[0], pq[1], pq[2]}; - - const auto norm_to_3 = norm * norm * norm; - terms[0] = F1.dot(G1) / norm_to_3; - - if constexpr(ORD >= 1) - { - const axom::primal::Vector - F2 {ec[3], ec[4], ec[5], ec[6], ec[7], ec[8], ec[9], ec[10], ec[11]}; - // clang-format off - axom::primal::Vector G2_1{ 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0 }; - axom::primal::Vector G2_2{ pq[0] * pq[0], pq[1] * pq[0], pq[2] * pq[0], - pq[0] * pq[1], pq[1] * pq[1], pq[2] * pq[1], - pq[0] * pq[2], pq[1] * pq[2], pq[2] * pq[2] }; - // clang-format on - - const auto norm_to_5 = norm_to_3 * norm * norm; - terms[1] = F2.dot(G2_1 * (1. / norm_to_3) - G2_2 * (3. / norm_to_5)); - - if constexpr(ORD >= 2) - { - const axom::primal::Vector F3 { - ec[12], ec[13], ec[14], ec[15], ec[16], ec[17], ec[18], ec[19], ec[20], - ec[21], ec[22], ec[23], ec[24], ec[25], ec[26], ec[27], ec[28], ec[29], - ec[30], ec[31], ec[32], ec[33], ec[34], ec[35], ec[36], ec[37], ec[38]}; - - // clang-format off - axom::primal::Vector G3_1{ 3 * pq[0], pq[1], pq[2], pq[1], pq[0], 0, pq[2], 0, pq[0], pq[1], pq[0], 0, pq[0],3 * pq[1], pq[2], 0, pq[2], pq[1], pq[2], 0, pq[0], 0, pq[2], pq[1], pq[0], pq[1], 3 * pq[2] }; - axom::primal::Vector G3_2{ pq[0] * pq[0] * pq[0], pq[0] * pq[0] * pq[1], pq[0] * pq[0] * pq[2], pq[0] * pq[1] * pq[0], pq[0] * pq[1] * pq[1], pq[0] * pq[1] * pq[2], pq[0] * pq[2] * pq[0], pq[0] * pq[2] * pq[1], pq[0] * pq[2] * pq[2], - pq[1] * pq[0] * pq[0], pq[1] * pq[0] * pq[1], pq[1] * pq[0] * pq[2], pq[1] * pq[1] * pq[0], pq[1] * pq[1] * pq[1], pq[1] * pq[1] * pq[2], pq[1] * pq[2] * pq[0], pq[1] * pq[2] * pq[1], pq[1] * pq[2] * pq[2], - pq[2] * pq[0] * pq[0], pq[2] * pq[0] * pq[1], pq[2] * pq[0] * pq[2], pq[2] * pq[1] * pq[0], pq[2] * pq[1] * pq[1], pq[2] * pq[1] * pq[2], pq[2] * pq[2] * pq[0], pq[2] * pq[2] * pq[1], pq[2] * pq[2] * pq[2] }; - // clang-format on - - // The formula in [Barill 2018] incorrectly lists (-1. / norm_to_5) - const auto norm_to_7 = norm_to_5 * norm * norm; - terms[2] = F3.dot(G3_1 * (-3. / norm_to_5) + G3_2 * (15. / norm_to_7)); - } - } - - return 0.25 * M_1_PI * (terms[0] + terms[1] + terms[2]); - } - } - - /// Return the center of the Taylor expansion - axom::primal::Point getCenter() const - { - if(a == 0) - { - return axom::primal::Point {}; - } - - return axom::primal::Point((ap / a).array()); - } - - /// Return the normal if computed from 3D data - axom::primal::Vector getNormal() const - { - static_assert(NDIMS == 3, "GWN Moments for triangles are defined only for 3D"); - return axom::primal::Vector {ec[0], ec[1], ec[2]}; - } - -private: - /// Transform raw moments into expansion coefficients - void compute_coefficients() - { - auto p = getCenter(); - - for(int i = 0; i < NDIMS; ++i) - { - ec[i] = rm[i]; - } - - if constexpr(ORD >= 1) - { - int m = NDIMS; - for(int i = 0; i < NDIMS * NDIMS; ++i, ++m) - { - ec[m] = rm[m] - p[i / NDIMS] * rm[i % NDIMS]; - } - - if constexpr(ORD == 2) - { - for(int i = 0; i < NDIMS * NDIMS * NDIMS; ++i, ++m) - { - // Example values for NDIMS = 2 - const int A = (i / NDIMS / NDIMS) % NDIMS; // 0, 0, 0, 0, 1, 1, 1, 1 - const int B = (i / NDIMS) % NDIMS; // 0, 0, 1, 1, 0, 0, 1, 1 - const int C = (i / 1) % NDIMS; // 0, 1, 0, 1, 0, 1, 0, 1 - const int D = i % (NDIMS * NDIMS); // 0, 1, 2, 3, 0, 1, 2, 3 - - ec[m] = 0.5 * - (rm[m] - p[A] * rm[NDIMS + D] - p[B] * rm[(A + 1) * NDIMS + C] + p[A] * p[B] * rm[C]); - } - } - } - } - -public: - // Store accumulated values across the node's children - axom::primal::Vector ap; // Weighted centroid - double a {}; - - // Raw moments - axom::StackArray rm {}; - - // Expansion coefficients - axom::StackArray ec {}; -}; - -} // end namespace primal -} // end namespace axom - -#endif diff --git a/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp b/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp index e3b29a8982..20b39c08b1 100644 --- a/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp +++ b/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp @@ -23,7 +23,6 @@ #include "axom/primal/geometry/Point.hpp" #include "axom/primal/geometry/Vector.hpp" #include "axom/primal/geometry/BoundingBox.hpp" -#include "axom/primal/geometry/GWNMomentData.hpp" #include "axom/primal/operators/is_convex.hpp" @@ -318,12 +317,11 @@ class NURBSPatchCacheManager public: NURBSPatchCacheManager() = default; - template NURBSPatchCacheManager(PatchArrayView patches, - axom::ArrayView> moments) + axom::ArrayView> precomputed_normals) { - SLIC_ASSERT(moments.empty() || moments.size() == patches.size()); - const bool computeNormal = !moments.empty(); + SLIC_ASSERT(precomputed_normals.empty() || precomputed_normals.size() == patches.size()); + const bool computeNormal = !precomputed_normals.empty(); for(auto& patch : patches) { @@ -331,12 +329,12 @@ class NURBSPatchCacheManager } // If we didn't comptue normals in NURBSCache constructor, - // need to get them from the moments + // need to use precomputed values if(!computeNormal) { - for(int n = 0; n < moments.size(); ++n) + for(int n = 0; n < precomputed_normals.size(); ++n) { - m_nurbs_caches[n].setNormal(moments[n].getNormal()); + m_nurbs_caches[n].setNormal(std::move(precomputed_normals[n])); } } } @@ -382,13 +380,11 @@ class NURBSPatchCacheManagerOMP public: NURBSPatchCacheManagerOMP() = default; - template - NURBSPatchCacheManagerOMP( - PatchArrayView patches, - axom::ArrayView> moments) + NURBSPatchCacheManagerOMP(PatchArrayView patches, + axom::ArrayView> precomputed_normals) { - SLIC_ASSERT(moments.empty() || moments.size() == patches.size()); - const bool computeNormal = !moments.empty(); + SLIC_ASSERT(precomputed_normals.empty() || precomputed_normals.size() == patches.size()); + const bool computeNormal = precomputed_normals.empty(); const int nt = omp_get_max_threads(); m_nurbs_caches.resize(nt); @@ -408,7 +404,9 @@ class NURBSPatchCacheManagerOMP { axom::for_all( patches.size(), - AXOM_LAMBDA(axom::IndexType i) { nurbs_caches_view[0][i].setNormal(moments[i].getNormal()); }); + AXOM_LAMBDA(axom::IndexType i) { + nurbs_caches_view[0][i].setNormal(std::move(precomputed_normals[i])); + }); } // Copy the constructed cache to the other threads' copies (less work than construction) diff --git a/src/axom/quest/FastApproximateGWN.hpp b/src/axom/quest/FastApproximateGWN.hpp index 1233f8031c..ed227c1914 100644 --- a/src/axom/quest/FastApproximateGWN.hpp +++ b/src/axom/quest/FastApproximateGWN.hpp @@ -312,6 +312,13 @@ class GWNMomentData return axom::primal::Point((ap / a).array()); } + /// Return the normal if computed from 3D data + axom::primal::Vector getNormal() const + { + static_assert(NDIMS == 3, "GWN Moments for triangles are defined only for 3D"); + return axom::primal::Vector {ec[0], ec[1], ec[2]}; + } + private: /// Transform raw moments into expansion coefficients void compute_coefficients() diff --git a/src/axom/quest/GWNMethods.hpp b/src/axom/quest/GWNMethods.hpp index 441fb02466..17a40b935b 100644 --- a/src/axom/quest/GWNMethods.hpp +++ b/src/axom/quest/GWNMethods.hpp @@ -600,6 +600,9 @@ class NURBSPatchGWNQuery return; } + // To use if normals are precomputed as moments, then used in caches + axom::Array> precomputed_normals; + axom::utilities::Timer timer(true); axom::utilities::Timer stage_timer(false); @@ -643,10 +646,16 @@ class NURBSPatchGWNQuery stage_timer.start(); { AXOM_ANNOTATE_SCOPE("moment_precomputation"); + precomputed_normals.resize(m_processed_patches_view.size()); + auto normals_view = precomputed_normals.view(); + auto compute_moments = [=](std::int32_t currentNode, const std::int32_t* leafNodes) -> GWNMoments { const auto idx = leafNodes[currentNode]; - return GWNMoments(m_processed_patches_view[idx]); // TODO: Avoid repeat normal calculation + const auto leaf_moments = GWNMoments(m_processed_patches_view[idx]); + + normals_view[idx] = leaf_moments.getNormal(); + return leaf_moments; }; const auto traverser = m_bvh.getTraverser(); @@ -668,7 +677,9 @@ class NURBSPatchGWNQuery stage_timer.start(); { AXOM_ANNOTATE_SCOPE("cache_initialization"); - m_nurbs_cache_mgr = NURBSCacheManager(m_processed_patches_view); + + // If internal moments are already allocated, then normals are already precomputed + m_nurbs_cache_mgr = NURBSCacheManager(m_processed_patches_view, precomputed_normals.view()); } stage_timer.stop(); SLIC_INFO(axom::fmt::format(" Preprocessing stage (cache initialization): {} s", From 7c3968ba816cc71fb8050cded48c5bbf10a12803 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Fri, 3 Apr 2026 10:44:36 -0700 Subject: [PATCH 238/986] Fix a template thing --- src/axom/quest/FastApproximateGWN.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/axom/quest/FastApproximateGWN.hpp b/src/axom/quest/FastApproximateGWN.hpp index ed227c1914..203f540e2e 100644 --- a/src/axom/quest/FastApproximateGWN.hpp +++ b/src/axom/quest/FastApproximateGWN.hpp @@ -129,7 +129,7 @@ class GWNMomentData /// Construct moments from a trimmed NURBS surface explicit GWNMomentData(const axom::primal::NURBSPatch& a_patch) { - const auto patch_data = a_patch.calculateSurfaceMoments(); + const auto patch_data = a_patch.template calculateSurfaceMoments(); a = patch_data[0]; ap[0] = patch_data[1]; From 898fab344a2861a9fd2529643db5818b4cf40ee8 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Fri, 3 Apr 2026 17:35:53 -0700 Subject: [PATCH 239/986] bugfixes and omp sad change --- src/axom/primal/geometry/NURBSPatch.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/axom/primal/geometry/NURBSPatch.hpp b/src/axom/primal/geometry/NURBSPatch.hpp index 49c02dfdca..6be2ae2749 100644 --- a/src/axom/primal/geometry/NURBSPatch.hpp +++ b/src/axom/primal/geometry/NURBSPatch.hpp @@ -3541,7 +3541,7 @@ class NURBSPatch return ret_vec; } - template + template = 0 ? 3 : 0) + (ORDER >= 1 ? 9 : 0) + (ORDER >= 2 ? 27 : 0)> primal::Vector calculateSurfaceMoments() const { // Need to integrate over 4 (for the coordinates and weight of the centroid) From ddc6c9b98e824e48ac2d05ab65df0b82d610a637 Mon Sep 17 00:00:00 2001 From: format-robot Date: Wed, 22 Apr 2026 23:11:35 -0700 Subject: [PATCH 240/986] make style --- .../examples/quest_winding_number_2d.cpp | 134 ++++++------- .../examples/quest_winding_number_3d.cpp | 186 +++++++++--------- 2 files changed, 160 insertions(+), 160 deletions(-) diff --git a/src/axom/quest/examples/quest_winding_number_2d.cpp b/src/axom/quest/examples/quest_winding_number_2d.cpp index 5fa41fbd95..f16a17e9e9 100644 --- a/src/axom/quest/examples/quest_winding_number_2d.cpp +++ b/src/axom/quest/examples/quest_winding_number_2d.cpp @@ -263,7 +263,7 @@ class Input { public: std::string inputFile; - std::string outputPrefix = { "winding2d" }; + std::string outputPrefix = {"winding2d"}; bool verbose {false}; std::string annotationMode {"none"}; @@ -274,21 +274,21 @@ class Input axom::runtime_policy::Policy policy = RuntimePolicy::seq; - const std::array valid_algorithms{ "direct", "fast_approximation" }; - std::string algorithm{ valid_algorithms[1] }; // fast-approximation + const std::array valid_algorithms {"direct", "fast_approximation"}; + std::string algorithm {valid_algorithms[1]}; // fast-approximation - bool linearize{ false }; - int approximation_order{ 2 }; + bool linearize {false}; + int approximation_order {2}; bool useUniformLinearization; - int segmentsPerKnotSpan{ 10 }; - double percentError{ 1.0 }; + int segmentsPerKnotSpan {10}; + double percentError {1.0}; // Query mesh parameters std::vector boxMins; std::vector boxMaxs; std::vector boxResolution; - int queryOrder{ 1 }; + int queryOrder {1}; primal::WindingTolerances tol; @@ -337,8 +337,8 @@ class Input ->check(axom::CLI::IsMember(valid_algorithms)); app .add_option("--approximation-order", - approximation_order, - "The order of the Taylor expansion (lower is faster, less precise)") + approximation_order, + "The order of the Taylor expansion (lower is faster, less precise)") ->expected(0, 2) ->capture_default_str(); @@ -364,31 +364,31 @@ class Input // Options for triangulation of the input STEP file auto* linearize_curves_subcommand = app.add_subcommand("linearize_curves") - ->description("Options for linearizing NURBS curves. Default is ") - ->fallthrough(); + ->description("Options for linearizing NURBS curves. Default is ") + ->fallthrough(); auto* nsegments = linearize_curves_subcommand->add_option("--num-segments", segmentsPerKnotSpan) - ->description( - "Number of segments for each knot span of each input curve for a uniform linearization.") - ->check(axom::CLI::PositiveNumber) - ->capture_default_str(); + ->description( + "Number of segments for each knot span of each input curve for a uniform linearization.") + ->check(axom::CLI::PositiveNumber) + ->capture_default_str(); auto* perror = linearize_curves_subcommand->add_option("--percent-error", percentError) - ->description( - "The percent of error that is acceptable to stop refinement during non-uniform " - "linearization.") - ->check(axom::CLI::Range(0.0f, 100.0f)) - ->capture_default_str(); + ->description( + "The percent of error that is acceptable to stop refinement during non-uniform " + "linearization.") + ->check(axom::CLI::Range(0.0f, 100.0f)) + ->capture_default_str(); auto* query_mesh_subcommand = app.add_subcommand("query_mesh")->description("Options for setting up a query mesh")->fallthrough(); auto* minbb = query_mesh_subcommand->add_option("--min", boxMins) - ->description("Min bounds for box mesh (x,y)") - ->expected(2); + ->description("Min bounds for box mesh (x,y)") + ->expected(2); auto* maxbb = query_mesh_subcommand->add_option("--max", boxMaxs) - ->description("Max bounds for box mesh (x,y)") - ->expected(2); + ->description("Max bounds for box mesh (x,y)") + ->expected(2); query_mesh_subcommand->add_option("--res", boxResolution) ->description("Resolution of the box mesh (i,j)") ->expected(2) @@ -412,28 +412,28 @@ class Input }; using GWNQueryType = std::variant, - axom::quest::PolylineGWNQuery, - axom::quest::PolylineGWNQuery, - axom::quest::PolylineGWNQuery + axom::quest::PolylineGWNQuery, + axom::quest::PolylineGWNQuery, + axom::quest::PolylineGWNQuery #if defined(AXOM_USE_RAJA) && defined(AXOM_USE_OPENMP) - , - axom::quest::NURBSCurveGWNQuery, - axom::quest::PolylineGWNQuery, - axom::quest::PolylineGWNQuery, - axom::quest::PolylineGWNQuery + , + axom::quest::NURBSCurveGWNQuery, + axom::quest::PolylineGWNQuery, + axom::quest::PolylineGWNQuery, + axom::quest::PolylineGWNQuery #endif ->; + >; template GWNQueryType pick_gwn_method(bool linearize_curves, int approximation_order) { - if (linearize_curves) + if(linearize_curves) { - if (approximation_order == 0) + if(approximation_order == 0) { return axom::quest::PolylineGWNQuery {}; } - else if (approximation_order == 1) + else if(approximation_order == 1) { return axom::quest::PolylineGWNQuery {}; } @@ -447,8 +447,8 @@ GWNQueryType pick_gwn_method(bool linearize_curves, int approximation_order) } GWNQueryType make_gwn_query(axom::runtime_policy::Policy policy, - bool linearize_curves, - int approximation_order) + bool linearize_curves, + int approximation_order) { #if defined(AXOM_USE_RAJA) && defined(AXOM_USE_OPENMP) if(policy == RuntimePolicy::omp) @@ -468,15 +468,15 @@ int main(int argc, char** argv) // Parse command line arguments into input Input input; - axom::CLI::App app{ + axom::CLI::App app { "Load mesh containing collection of curves" - " and optionally generate a query mesh of winding numbers." }; + " and optionally generate a query mesh of winding numbers."}; try { input.parse(argc, argv, app); } - catch (const axom::CLI::ParseError& e) + catch(const axom::CLI::ParseError& e) { return app.exit(e); } @@ -501,7 +501,7 @@ int main(int argc, char** argv) mfem_reader.setFileName(input.inputFile); const int ret = mfem_reader.read(curves); - if (ret != axom::quest::MFEMReader::READ_SUCCESS) + if(ret != axom::quest::MFEMReader::READ_SUCCESS) { SLIC_ERROR("Failed to read MFEM file."); return 1; @@ -510,13 +510,13 @@ int main(int argc, char** argv) // Linearize the input curves if asked for axom::mint::UnstructuredMesh poly_mesh(2, axom::mint::SEGMENT); - if (input.linearize) + if(input.linearize) { AXOM_ANNOTATE_SCOPE("linearization"); axom::utilities::Timer timer(true); axom::quest::LinearizeCurves lc; - if (input.useUniformLinearization) + if(input.useUniformLinearization) { lc.getLinearMeshUniform(curves.view(), &poly_mesh, input.segmentsPerKnotSpan); } @@ -537,14 +537,14 @@ int main(int argc, char** argv) } // Early return if user didn't set up a query mesh - if (input.boxResolution.empty()) + if(input.boxResolution.empty()) { return 0; } // Extract the curves and compute their bounding boxes along the way BoundingBox2D shape_bbox; - for (const auto& cur : curves) + for(const auto& cur : curves) { shape_bbox.addBox(cur.boundingBox()); } @@ -560,21 +560,21 @@ int main(int argc, char** argv) // Generate the query grid and fields quest::generate_gwn_query_mesh(dc, - shape_bbox, - input.boxMins, - input.boxMaxs, - input.boxResolution, - input.queryOrder); + shape_bbox, + input.boxMins, + input.boxMaxs, + input.boxResolution, + input.queryOrder); // Run the preprocess std::visit( [&](auto& wn) { using T = std::decay_t; - if constexpr (quest::gwn_input_type_v == quest::GWNInputType::Curve) + if constexpr(quest::gwn_input_type_v == quest::GWNInputType::Curve) { wn.preprocess(curves, input.algorithm == "direct", input.memoized); } - else if constexpr (quest::gwn_input_type_v == quest::GWNInputType::Polyline) + else if constexpr(quest::gwn_input_type_v == quest::GWNInputType::Polyline) { wn.preprocess(&poly_mesh, input.algorithm == "direct"); } @@ -586,7 +586,7 @@ int main(int argc, char** argv) } // Postprocess query results: norms, ranges, and integral statistics - if (input.stats) + if(input.stats) { AXOM_ANNOTATE_SCOPE("postprocess"); @@ -599,14 +599,14 @@ int main(int argc, char** argv) std::int64_t pos_inout_dofs = 0; std::int64_t neg_inout_dofs = 0; - for (int i = 0; i < inout.Size(); ++i) + for(int i = 0; i < inout.Size(); ++i) { const double v = inout[i]; - if (v > 0.0) + if(v > 0.0) { ++pos_inout_dofs; } - else if (v < 0.0) + else if(v < 0.0) { ++neg_inout_dofs; } @@ -614,11 +614,11 @@ int main(int argc, char** argv) SLIC_INFO( axom::fmt::format("WN_STATS: dof_l2={:.6e} dof_linf={:.6e} l2={:.6e} min={:.6e} max={:.6e}", - winding_stats.dof_l2, - winding_stats.dof_linf, - winding_stats.l2, - winding_stats.min, - winding_stats.max)); + winding_stats.dof_l2, + winding_stats.dof_linf, + winding_stats.l2, + winding_stats.min, + winding_stats.max)); SLIC_INFO(axom::fmt::format( "INOUT_STATS: dof_l2={:.6e} dof_linf={:.6e} l2={:.6e} min={:.6e} max={:.6e} volume={:.6e} " @@ -631,13 +631,13 @@ int main(int argc, char** argv) inout_integrals.integral, inout_integrals.domain_volume, (inout_integrals.domain_volume > 0.0 ? inout_integrals.integral / inout_integrals.domain_volume - : 0.0), + : 0.0), pos_inout_dofs, neg_inout_dofs)); } // Save the query mesh and fields to disk using a format that can be viewed in VisIt - if (input.vis) + if(input.vis) { AXOM_ANNOTATE_SCOPE("dump_mesh"); @@ -647,8 +647,8 @@ int main(int argc, char** argv) windingDC.Save(); SLIC_INFO(axom::fmt::format("Outputting generated mesh '{}' to '{}'", - windingDC.GetCollectionName(), - axom::utilities::filesystem::getCWD())); + windingDC.GetCollectionName(), + axom::utilities::filesystem::getCWD())); } return 0; diff --git a/src/axom/quest/examples/quest_winding_number_3d.cpp b/src/axom/quest/examples/quest_winding_number_3d.cpp index c809248dd1..c8fba9f93d 100644 --- a/src/axom/quest/examples/quest_winding_number_3d.cpp +++ b/src/axom/quest/examples/quest_winding_number_3d.cpp @@ -50,31 +50,31 @@ class Input { public: std::string inputFile; - std::string outputPrefix{ "winding3d" }; + std::string outputPrefix {"winding3d"}; - bool verbose{ false }; - std::string annotationMode{ "none" }; - bool memoized{ true }; - bool vis{ true }; - bool validate{ false }; - bool stats{ false }; + bool verbose {false}; + std::string annotationMode {"none"}; + bool memoized {true}; + bool vis {true}; + bool validate {false}; + bool stats {false}; axom::runtime_policy::Policy policy = RuntimePolicy::seq; - const std::array valid_algorithms{ "direct", "fast_approximate" }; - std::string algorithm{ valid_algorithms[1] }; // fast-approximation + const std::array valid_algorithms {"direct", "fast_approximate"}; + std::string algorithm {valid_algorithms[1]}; // fast-approximation - bool triangulate{ false }; - double linear_deflection{ 0.1 }; - double angular_deflection{ 0.5 }; - bool deflection_is_relative{ false }; - int approximation_order{ 2 }; + bool triangulate {false}; + double linear_deflection {0.1}; + double angular_deflection {0.5}; + bool deflection_is_relative {false}; + int approximation_order {2}; std::vector boxMins; std::vector boxMaxs; std::vector boxResolution; - int queryOrder{ 1 }; - double sliceZ{ 0.0 }; + int queryOrder {1}; + double sliceZ {0.0}; primal::WindingTolerances tol; @@ -101,8 +101,8 @@ class Input // Options for triangulation of the input STEP file auto* triangulate_step_subcommand = app.add_subcommand("triangulate_step") - ->description("Options for triangulating NURBS surfaces") - ->fallthrough(); + ->description("Options for triangulating NURBS surfaces") + ->fallthrough(); triangulate_step_subcommand->add_option("--linear-deflection", linear_deflection) ->description( @@ -151,8 +151,8 @@ class Input ->check(axom::CLI::IsMember(valid_algorithms)); app .add_option("--expansion-order", - approximation_order, - "The order of the Taylor expansion (lower is faster, less precise)") + approximation_order, + "The order of the Taylor expansion (lower is faster, less precise)") ->expected(0, 2) ->capture_default_str(); @@ -179,11 +179,11 @@ class Input app.add_subcommand("query_mesh")->description("Options for setting up a query mesh")->fallthrough(); auto* minbb = query_mesh_subcommand->add_option("--min", boxMins) - ->description("Min bounds for box mesh (x,y[,z])") - ->expected(2, 3); + ->description("Min bounds for box mesh (x,y[,z])") + ->expected(2, 3); auto* maxbb = query_mesh_subcommand->add_option("--max", boxMaxs) - ->description("Max bounds for box mesh (x,y[,z])") - ->expected(2, 3); + ->description("Max bounds for box mesh (x,y[,z])") + ->expected(2, 3); query_mesh_subcommand->add_option("--res", boxResolution) ->description("Resolution of the box mesh (i,j[,k])") ->expected(2, 3) @@ -201,21 +201,21 @@ class Input // let's also check that they're consistently sized w/ each other and with the resolution query_mesh_subcommand->callback([&]() { - if (const bool have_box = (minbb->count() > 0 || maxbb->count() > 0); have_box) + if(const bool have_box = (minbb->count() > 0 || maxbb->count() > 0); have_box) { - if (boxMins.size() != boxMaxs.size()) + if(boxMins.size() != boxMaxs.size()) { throw axom::CLI::ValidationError( "--min/--max", axom::fmt::format("must have the same number of values (2 for 2D or 3 for 3D). " - "Got --min={}, --max={}", - boxMins.size(), - boxMaxs.size())); + "Got --min={}, --max={}", + boxMins.size(), + boxMaxs.size())); } - for (size_t d = 0; d < boxMins.size(); ++d) + for(size_t d = 0; d < boxMins.size(); ++d) { - if (boxMins[d] >= boxMaxs[d]) + if(boxMins[d] >= boxMaxs[d]) { throw axom::CLI::ValidationError( "--min/--max", @@ -227,7 +227,7 @@ class Input } } - if (boxResolution.size() != boxMins.size()) + if(boxResolution.size() != boxMins.size()) { throw axom::CLI::ValidationError( "--res", @@ -237,7 +237,7 @@ class Input boxMins.size())); } } - }); + }); app.parse(argc, argv); @@ -246,28 +246,28 @@ class Input }; using GWNQueryType = std::variant, - axom::quest::TriangleGWNQuery, - axom::quest::TriangleGWNQuery, - axom::quest::TriangleGWNQuery + axom::quest::TriangleGWNQuery, + axom::quest::TriangleGWNQuery, + axom::quest::TriangleGWNQuery #if defined(AXOM_USE_RAJA) && defined(AXOM_USE_OPENMP) - , - axom::quest::NURBSPatchGWNQuery, - axom::quest::TriangleGWNQuery, - axom::quest::TriangleGWNQuery, - axom::quest::TriangleGWNQuery + , + axom::quest::NURBSPatchGWNQuery, + axom::quest::TriangleGWNQuery, + axom::quest::TriangleGWNQuery, + axom::quest::TriangleGWNQuery #endif ->; + >; template GWNQueryType pick_gwn_method(bool triangulate, int approximation_order) { - if (triangulate) + if(triangulate) { - if (approximation_order == 0) + if(approximation_order == 0) { return axom::quest::TriangleGWNQuery {}; } - else if (approximation_order == 1) + else if(approximation_order == 1) { return axom::quest::TriangleGWNQuery {}; } @@ -281,8 +281,8 @@ GWNQueryType pick_gwn_method(bool triangulate, int approximation_order) } GWNQueryType make_gwn_query(axom::runtime_policy::Policy policy, - bool triangulate, - int approximation_order) + bool triangulate, + int approximation_order) { #if defined(AXOM_USE_RAJA) && defined(AXOM_USE_OPENMP) if(policy == RuntimePolicy::omp) @@ -302,15 +302,15 @@ int main(int argc, char** argv) // Parse command line arguments into input Input input; - axom::CLI::App app{ + axom::CLI::App app { "Load a STEP file containing trimmed NURBS patches " - "and optionally generate a query grid of generalized winding numbers." }; + "and optionally generate a query grid of generalized winding numbers."}; try { input.parse(argc, argv, app); } - catch (const axom::CLI::ParseError& e) + catch(const axom::CLI::ParseError& e) { return app.exit(e); } @@ -325,7 +325,7 @@ int main(int argc, char** argv) axom::mint::UnstructuredMesh tri_mesh(3, axom::mint::TRIANGLE); axom::Array patches; - if (axom::utilities::string::endsWith(input.inputFile, ".stl")) + if(axom::utilities::string::endsWith(input.inputFile, ".stl")) { AXOM_ANNOTATE_SCOPE("read_stl"); @@ -335,7 +335,7 @@ int main(int argc, char** argv) axom::utilities::Timer read_timer(true); const int ret = stl_reader.read(); - if (ret != 0) + if(ret != 0) { SLIC_ERROR(axom::fmt::format("Failed to read STL file '{}'", input.inputFile)); return 1; @@ -348,15 +348,15 @@ int main(int argc, char** argv) axom::mint::for_all_nodes( &tri_mesh, AXOM_LAMBDA(axom::IndexType, double x, double y, double z) { - shape_bbox_ptr->addPoint(Point3D{ x, y, z }); - }); + shape_bbox_ptr->addPoint(Point3D {x, y, z}); + }); SLIC_INFO(axom::fmt::format(axom::utilities::locale(), - "Loaded {} triangles in {:.3Lf} seconds", - stl_reader.getNumFaces(), - read_timer.elapsed())); + "Loaded {} triangles in {:.3Lf} seconds", + stl_reader.getNumFaces(), + read_timer.elapsed())); } - else if (axom::utilities::string::endsWith(input.inputFile, ".step")) + else if(axom::utilities::string::endsWith(input.inputFile, ".step")) { AXOM_ANNOTATE_SCOPE("read_step"); @@ -366,7 +366,7 @@ int main(int argc, char** argv) axom::utilities::Timer read_timer(true); const int ret = step_reader.read(input.validate); - if (ret != 0) + if(ret != 0) { SLIC_ERROR(axom::fmt::format("Failed to read STEP file '{}'", input.inputFile)); return 1; @@ -376,7 +376,7 @@ int main(int argc, char** argv) shape_bbox = step_reader.getBRepBoundingBox(); int num_trimming_curves = 0; - for (const auto& patch : step_reader.getPatchArray()) + for(const auto& patch : step_reader.getPatchArray()) { num_trimming_curves += patch.getNumTrimmingCurves(); } @@ -390,17 +390,17 @@ int main(int argc, char** argv) num_trimming_curves, read_timer.elapsed())); - if (input.triangulate) + if(input.triangulate) { read_timer.reset(); read_timer.start(); AXOM_ANNOTATE_SCOPE("triangulation"); const int tc = step_reader.getTriangleMesh(&tri_mesh, - input.linear_deflection, - input.angular_deflection, - input.deflection_is_relative, - /* trimmed */ true); - if (tc != 0) + input.linear_deflection, + input.angular_deflection, + input.deflection_is_relative, + /* trimmed */ true); + if(tc != 0) { SLIC_ERROR("Failed to triangulate STEP geometry."); return 1; @@ -409,12 +409,12 @@ int main(int argc, char** argv) SLIC_INFO( axom::fmt::format(axom::utilities::locale(), - "Triangulated geometry with deflection {} and angular deflection {}" - " containing {:L} triangles in {:.3Lf} seconds", - input.linear_deflection, - input.angular_deflection, - tri_mesh.getNumberOfCells(), - read_timer.elapsed())); + "Triangulated geometry with deflection {} and angular deflection {}" + " containing {:L} triangles in {:.3Lf} seconds", + input.linear_deflection, + input.angular_deflection, + tri_mesh.getNumberOfCells(), + read_timer.elapsed())); } else { @@ -427,7 +427,7 @@ int main(int argc, char** argv) } // Early return if user didn't set up a query mesh - if (input.boxResolution.empty()) + if(input.boxResolution.empty()) { return 0; } @@ -441,21 +441,21 @@ int main(int argc, char** argv) // Generate the query grid and fields quest::generate_gwn_query_mesh(dc, - shape_bbox, - input.boxMins, - input.boxMaxs, - input.boxResolution, - input.queryOrder); + shape_bbox, + input.boxMins, + input.boxMaxs, + input.boxResolution, + input.queryOrder); // Run the preprocess std::visit( [&](auto& wn) { using T = std::decay_t; - if constexpr (quest::gwn_input_type_v == quest::GWNInputType::Surface) + if constexpr(quest::gwn_input_type_v == quest::GWNInputType::Surface) { wn.preprocess(patches, input.algorithm == "direct", input.memoized); } - else if constexpr (quest::gwn_input_type_v == quest::GWNInputType::Triangulation) + else if constexpr(quest::gwn_input_type_v == quest::GWNInputType::Triangulation) { wn.preprocess(&tri_mesh, input.algorithm == "direct"); } @@ -467,7 +467,7 @@ int main(int argc, char** argv) } // Postprocess query results: norms, ranges, and integral statistics - if (input.stats) + if(input.stats) { AXOM_ANNOTATE_SCOPE("postprocess"); @@ -480,14 +480,14 @@ int main(int argc, char** argv) std::int64_t pos_inout_dofs = 0; std::int64_t neg_inout_dofs = 0; - for (int i = 0; i < inout.Size(); ++i) + for(int i = 0; i < inout.Size(); ++i) { const double v = inout[i]; - if (v > 0.0) + if(v > 0.0) { ++pos_inout_dofs; } - else if (v < 0.0) + else if(v < 0.0) { ++neg_inout_dofs; } @@ -495,11 +495,11 @@ int main(int argc, char** argv) SLIC_INFO( axom::fmt::format("WN_STATS: dof_l2={:.6e} dof_linf={:.6e} l2={:.6e} min={:.6e} max={:.6e}", - winding_stats.dof_l2, - winding_stats.dof_linf, - winding_stats.l2, - winding_stats.min, - winding_stats.max)); + winding_stats.dof_l2, + winding_stats.dof_linf, + winding_stats.l2, + winding_stats.min, + winding_stats.max)); SLIC_INFO(axom::fmt::format( "INOUT_STATS: dof_l2={:.6e} dof_linf={:.6e} l2={:.6e} min={:.6e} max={:.6e} volume={:.6e} " @@ -512,13 +512,13 @@ int main(int argc, char** argv) inout_integrals.integral, inout_integrals.domain_volume, (inout_integrals.domain_volume > 0.0 ? inout_integrals.integral / inout_integrals.domain_volume - : 0.0), + : 0.0), pos_inout_dofs, neg_inout_dofs)); } // Save the query mesh and fields to disk using a format that can be viewed in VisIt - if (input.vis) + if(input.vis) { AXOM_ANNOTATE_SCOPE("dump_mesh"); @@ -528,8 +528,8 @@ int main(int argc, char** argv) windingDC.Save(); SLIC_INFO(axom::fmt::format("Outputting generated mesh '{}' to '{}'", - windingDC.GetCollectionName(), - axom::utilities::filesystem::getCWD())); + windingDC.GetCollectionName(), + axom::utilities::filesystem::getCWD())); } return 0; From b224e7c399d25e7733dffd994e1a06addc8cbb78 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Mon, 11 May 2026 11:33:52 -0700 Subject: [PATCH 241/986] Update for better normal processing --- src/axom/primal/geometry/NURBSPatch.hpp | 101 ++++++++++++++++- .../detail/winding_number_3d_memoization.hpp | 103 +++++++++++------ src/axom/primal/tests/primal_solid_angle.cpp | 107 +++++++++--------- 3 files changed, 219 insertions(+), 92 deletions(-) diff --git a/src/axom/primal/geometry/NURBSPatch.hpp b/src/axom/primal/geometry/NURBSPatch.hpp index 6be2ae2749..c2e05cfbf7 100644 --- a/src/axom/primal/geometry/NURBSPatch.hpp +++ b/src/axom/primal/geometry/NURBSPatch.hpp @@ -3522,25 +3522,116 @@ class NURBSPatch * * \return The calculated mean surface normal */ - VectorType calculateTrimmedPatchNormal(int npts = 20) const + VectorType calculateTrimmedPatchNormal(int npts = 20, bool useBezierExtraction = true) const { SLIC_ASSERT(NDIMS == 3); VectorType ret_vec; - // Split the patch along the unique knot values to improve convergence - for(const auto& nPatch : extractTrimmedBezier()) + if(useBezierExtraction) + { + // Split the patch along the unique knot values to improve convergence + for(const auto& nPatch : extractTrimmedBezier()) + { + // Integrate the surface normal over the patches + ret_vec += evaluate_area_integral( + nPatch.getTrimmingCurves(), + [&nPatch](Point2D x) -> Vector { return nPatch.normal(x[0], x[1]); }, + npts); + } + } + else { // Integrate the surface normal over the patches ret_vec += evaluate_area_integral( - nPatch.getTrimmingCurves(), - [&nPatch](Point2D x) -> Vector { return nPatch.normal(x[0], x[1]); }, + getTrimmingCurves(), + [this](Point2D x) -> Vector { return this->normal(x[0], x[1]); }, npts); } return ret_vec; } + /*! + * \brief Calculate the unsigned surface area and integrated normal for the trimmed patch + * + * \param [in] npts The number of quadrature nodes used in each component integral + * \param [in] useBezierExtraction Set whether to do Bezier extraction on input for + * exponential convergence of general NURBS input + * + * Decomposes the surface into trimmed Bezier components and evaluates the area + * and integrated normal numerically using trimming curves. + * Avoids the redundant geometry processing of computing each value separately + */ + std::pair calculateTrimmedPatchNormalArea(int npts = 20, + bool useBezierExtraction = true) const + { + SLIC_ASSERT(NDIMS == 3); + + double area = 0.0; + VectorType normal {}; + + auto accumulate_patch = [&](const auto& nPatch) { + const auto area_and_normal = evaluate_area_integral( + nPatch.getTrimmingCurves(), + [&nPatch](Point2D x) -> Vector { + primal::Point eval; + primal::Vector Du, Dv; + nPatch.evaluateFirstDerivatives(x[0], x[1], eval, Du, Dv); + const auto n = Vector::cross_product(Du, Dv); + return Vector {n.norm(), n[0], n[1], n[2]}; + }, + npts); + + area += area_and_normal[0]; + normal += VectorType({area_and_normal[1], area_and_normal[2], area_and_normal[3]}); + }; + + if(useBezierExtraction) + { + for(const auto& nPatch : extractTrimmedBezier()) + { + accumulate_patch(nPatch); + } + } + else + { + accumulate_patch(*this); + } + + return {normal, area}; + } + + /*! + * \brief Return a "clean" trimmed representation suitable for moment and GWN calculation. + * + * \param [in] normalize_by_span Normalize the patch parameter space + * \param [in] ensure_trimmed If the patch isn't trimmed, make trivially trimmed + * \param [in] clip_to_curves Clip patch parameter space to AABB of the trimming curves + */ + NURBSPatch cleanedTrimmedRepresentation(bool normalize_by_span = true, + bool ensure_trimmed = true, + bool clip_to_curves = true) const + { + NURBSPatch patch = *this; + if(normalize_by_span) + { + patch.normalizeBySpan(); + } + + if(ensure_trimmed && !patch.isTrimmed()) + { + patch.makeTriviallyTrimmed(); + } + + if(clip_to_curves && patch.getNumTrimmingCurves() > 0) + { + patch.clipToCurves(); + } + + return patch; + } + template = 0 ? 3 : 0) + (ORDER >= 1 ? 9 : 0) + (ORDER >= 2 ? 27 : 0)> primal::Vector calculateSurfaceMoments() const { diff --git a/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp b/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp index 20b39c08b1..dad5c0e8a3 100644 --- a/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp +++ b/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp @@ -156,24 +156,33 @@ class NURBSPatchGWNCache /// \brief Initialize the cache with the data for a single NURBS patch NURBSPatchGWNCache(const NURBSPatch& a_patch, bool computeNormal = true) - : m_alteredPatch(a_patch) + : m_alteredPatch(a_patch.cleanedTrimmedRepresentation()) { - m_alteredPatch.normalizeBySpan(); - - // Make trivially untrimmed if needed - // Calculate the average normal for the untrimmed patch - if(!m_alteredPatch.isTrimmed()) - { - m_alteredPatch.makeTriviallyTrimmed(); - } - if(computeNormal) { - setNormal(m_alteredPatch.isTrimmed() ? m_alteredPatch.calculateTrimmedPatchNormal() - : m_alteredPatch.calculateUntrimmedPatchNormal()); - } + // In a GWN context, the surface has been sufficiently subdivided to + // make additional bezier extraction redundant + const auto normal_area = + m_alteredPatch.calculateTrimmedPatchNormalArea(/*npts*/ 20, /*useBezierExtraction*/ false); - m_pboxDiag = m_alteredPatch.getParameterSpaceDiagonal(); + m_surfaceArea = normal_area.second; + if(m_surfaceArea <= 0.0) + { + // Degenerate or invalid surface: ignore it by clearing trimming curves so + // winding-number evaluation returns early with 0. + m_alteredPatch.setTrimmingCurves({}); + m_curveQuadratureMaps.resize(0); + + m_normal = Vector {}; + m_castDirection = Vector {}; + m_pboxDiag = m_alteredPatch.getParameterSpaceDiagonal(); + m_bBox = m_alteredPatch.boundingBox(); + m_oBox = m_alteredPatch.orientedBoundingBox(); + return; + } + + setNormal(normal_area.first, normal_area.second); + } // Make a bounding box by doing (trimmed) bezier extraction, // splitting the resulting bezier patches in 4, @@ -209,7 +218,10 @@ class NURBSPatchGWNCache m_bBox.addBox(p4.boundingBox()); } + // Expand parameter space so trimming curves aren't on the boundary of the + // untrimmed patch; this reduces near-miss issues in later ray casting. m_alteredPatch.expandParameterSpace(0.05, 0.05); + m_pboxDiag = m_alteredPatch.getParameterSpaceDiagonal(); m_curveQuadratureMaps.resize(m_alteredPatch.getNumTrimmingCurves()); } @@ -242,21 +254,35 @@ class NURBSPatchGWNCache //! \name Accessors for precomputed data const Vector& getNormal() const { return m_normal; } const Vector& getCastDirection() const { return m_castDirection; } - void setNormal(const Vector& v) + double getSurfaceArea() const { return m_surfaceArea; } + + void setNormal(const Vector& v, double sa) { m_normal = v; + m_surfaceArea = sa; + + // Cast direction is always set to average normal, unless it is near zero, + // which is the case for high-symmetry surfaces - // Cast direction is always set to average normal, unless it is near zero - if(m_normal.norm() < 1e-10) + // Scale-invariant symmetry detector: ||\int n dA|| / \int ||n|| dA. + // For symmetric patches (e.g. cylinders), the numerator can be close to 0 due to cancellation. + constexpr double k_dir_eps = 1e-3; + + // Generate a random direction + double theta = axom::utilities::random_real(0.0, 2 * M_PI); + double u = axom::utilities::random_real(-1.0, 1.0); + const auto random_unit = + Vector {sin(theta) * sqrt(1 - u * u), cos(theta) * sqrt(1 - u * u), u}; + + // If the average normal is too small, use the random direction as-is + if((m_surfaceArea <= 0.0) || (m_normal.norm() / m_surfaceArea) < k_dir_eps) { - // ...unless the average direction is zero - double theta = axom::utilities::random_real(0.0, 2 * M_PI); - double u = axom::utilities::random_real(-1.0, 1.0); - m_castDirection = Vector {sin(theta) * sqrt(1 - u * u), cos(theta) * sqrt(1 - u * u), u}; + m_castDirection = random_unit; } + // Otherwise, pick a direction that is *mostly* in the direction of the average normal else { - m_castDirection = m_normal.unitVector(); + m_castDirection = (m_normal.unitVector() + 0.1 * random_unit).unitVector(); } } @@ -295,6 +321,7 @@ class NURBSPatchGWNCache BoundingBox m_bBox; OrientedBoundingBox m_oBox; Vector m_normal, m_castDirection; + double m_surfaceArea; double m_pboxDiag; // Per trimming curve data, keyed by (whichRefinementLevel, whichRefinementIndex) @@ -318,23 +345,27 @@ class NURBSPatchCacheManager NURBSPatchCacheManager() = default; NURBSPatchCacheManager(PatchArrayView patches, - axom::ArrayView> precomputed_normals) + axom::ArrayView> precomputed_normals, + axom::ArrayView precomputed_surface_areas) { - SLIC_ASSERT(precomputed_normals.empty() || precomputed_normals.size() == patches.size()); - const bool computeNormal = !precomputed_normals.empty(); + SLIC_ASSERT(precomputed_normals.empty() || + (precomputed_normals.size() == patches.size() && + precomputed_surface_areas.size() == patches.size())); + + const bool mustComputeNormal = precomputed_normals.empty(); for(auto& patch : patches) { - m_nurbs_caches.push_back(NURBSCache(patch, computeNormal)); + m_nurbs_caches.push_back(NURBSCache(patch, mustComputeNormal)); } // If we didn't comptue normals in NURBSCache constructor, // need to use precomputed values - if(!computeNormal) + if(!mustComputeNormal) { for(int n = 0; n < precomputed_normals.size(); ++n) { - m_nurbs_caches[n].setNormal(std::move(precomputed_normals[n])); + m_nurbs_caches[n].setNormal(precomputed_normals[n], precomputed_surface_areas[n]); } } } @@ -381,10 +412,14 @@ class NURBSPatchCacheManagerOMP NURBSPatchCacheManagerOMP() = default; NURBSPatchCacheManagerOMP(PatchArrayView patches, - axom::ArrayView> precomputed_normals) + axom::ArrayView> precomputed_normals, + axom::ArrayView precomputed_surface_areas) { - SLIC_ASSERT(precomputed_normals.empty() || precomputed_normals.size() == patches.size()); - const bool computeNormal = precomputed_normals.empty(); + SLIC_ASSERT(precomputed_normals.empty() || + (precomputed_normals.size() == patches.size() && + precomputed_surface_areas.size() == patches.size())); + + const bool mustComputeNormal = precomputed_normals.empty(); const int nt = omp_get_max_threads(); m_nurbs_caches.resize(nt); @@ -395,17 +430,17 @@ class NURBSPatchCacheManagerOMP axom::for_all( patches.size(), AXOM_LAMBDA(axom::IndexType i) { - nurbs_caches_view[0][i] = NURBSCache(patches[i], computeNormal); + nurbs_caches_view[0][i] = NURBSCache(patches[i], mustComputeNormal); }); // If we didn't comptue normals in NURBSCache constructor, // need to get them from the moments - if(!computeNormal) + if(!mustComputeNormal) { axom::for_all( patches.size(), AXOM_LAMBDA(axom::IndexType i) { - nurbs_caches_view[0][i].setNormal(std::move(precomputed_normals[i])); + nurbs_caches_view[0][i].setNormal(precomputed_normals[i], precomputed_surface_areas[i]); }); } diff --git a/src/axom/primal/tests/primal_solid_angle.cpp b/src/axom/primal/tests/primal_solid_angle.cpp index 8e9833cf44..7dcc3fa20b 100644 --- a/src/axom/primal/tests/primal_solid_angle.cpp +++ b/src/axom/primal/tests/primal_solid_angle.cpp @@ -615,63 +615,64 @@ TEST(primal_solid_angle, nurbspatch_sphere) //------------------------------------------------------------------------------ TEST(primal_solid_angle, teardrop_regression_test) { - using Point3D = primal::Point; - - const double edge_tol = 1e-6; - const double ls_tol = 1e-10; - const double quad_tol = 1e-5; - const double disk_size = 0.01; - const double EPS = 1e-11; - - // Test the points on a teardrop for which - // the bottom portion is a bicubic sphere, - // the top portion is defined by the solid of revolution of a cubic Bezier curve - const auto teardrop_shape = axom::primal::detail::make_teardrop(); - - auto is_in_teardrop = [](Point3D x) -> bool { - const double radius = std::sqrt(x[0] * x[0] + x[1] * x[1]); - - if(radius > 1.0) - { - return false; - } - else + using Point3D = primal::Point; + + const double edge_tol = 1e-6; + const double ls_tol = 1e-10; + const double quad_tol = 1e-5; + const double disk_size = 0.01; + const double EPS = 1e-11; + + // Test the points on a teardrop for which + // the bottom portion is a bicubic sphere, + // the top portion is defined by the solid of revolution of a cubic Bezier curve + const auto teardrop_shape = axom::primal::detail::make_teardrop(); + + auto is_in_teardrop = [](Point3D x) -> bool { + const double radius = std::sqrt(x[0] * x[0] + x[1] * x[1]); + + if(radius > 1.0) + { + return false; + } + else + { + // The vertical cross-section of the tip is given by 3sin(1/3 * asin(1-2x)) + x - 0.5 + double fun = 3 * std::sin(1.0 / 3.0 * std::asin(1 - 2 * radius)) + radius - 0.5; + return (x[2] <= fun) && + ((x[2] > -1.0) || + std::sqrt(x[0] * x[0] + x[1] * x[1] + (x[2] + 1.0) * (x[2] + 1.0)) <= 1.0); + } + }; + + // Define a grid of points + constexpr int npts = 21; + double x_pts[npts]; + double y_pts[npts]; + double z_pts[npts]; + + axom::numerics::linspace(-1.1, 1.1, x_pts, npts); + axom::numerics::linspace(-1.1, 1.1, y_pts, npts); + axom::numerics::linspace(-2.1, 1.1, z_pts, npts); + + constexpr int tot_npts = npts * npts * npts; + axom::Array query_arr(0, tot_npts); + axom::Array true_containment_arr(0, tot_npts); + + for(int n = 0; n < tot_npts; ++n) { - // The vertical cross-section of the tip is given by 3sin(1/3 * asin(1-2x)) + x - 0.5 - double fun = 3 * std::sin(1.0 / 3.0 * std::asin(1 - 2 * radius)) + radius - 0.5; - return (x[2] <= fun) && - ((x[2] > -1.0) || std::sqrt(x[0] * x[0] + x[1] * x[1] + (x[2] + 1.0) * (x[2] + 1.0) <= 1.0)); - } - }; - - // Define a grid of points - constexpr int npts = 21; - double x_pts[npts]; - double y_pts[npts]; - double z_pts[npts]; - - axom::numerics::linspace(-1.1, 1.1, x_pts, npts); - axom::numerics::linspace(-1.1, 1.1, y_pts, npts); - axom::numerics::linspace(-2.1, 1.1, z_pts, npts); - - constexpr int tot_npts = npts * npts * npts; - axom::Array query_arr(0, tot_npts); - axom::Array true_containment_arr(0, tot_npts); + const Point3D query {x_pts[(n / npts / npts) % npts], y_pts[(n / npts) % npts], z_pts[n % npts]}; - for(int n = 0; n < tot_npts; ++n) - { - const Point3D query {x_pts[(n / npts / npts) % npts], y_pts[(n / npts) % npts], z_pts[n % npts]}; - - query_arr.emplace_back(query); - true_containment_arr.emplace_back(is_in_teardrop(query)); - } + query_arr.emplace_back(query); + true_containment_arr.emplace_back(is_in_teardrop(query)); + } - const auto gwn_array = - axom::primal::winding_number(query_arr, teardrop_shape, edge_tol, ls_tol, quad_tol, disk_size, EPS); + const auto gwn_array = + axom::primal::winding_number(query_arr, teardrop_shape, edge_tol, ls_tol, quad_tol, disk_size, EPS); - for(int n = 0; n < tot_npts; ++n) - { - const bool calc_containment = (std::round(gwn_array[n]) != 0); + for(int n = 0; n < tot_npts; ++n) + { + const bool calc_containment = (std::round(gwn_array[n]) != 0); EXPECT_EQ(calc_containment, true_containment_arr[n]); } } From e37880cd3af478417fb935f9ce55071fd7ac6ca0 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Mon, 11 May 2026 13:21:09 -0700 Subject: [PATCH 242/986] Improve subdivision routine --- src/axom/primal/geometry/NURBSPatch.hpp | 83 +++++++++++++------------ src/axom/quest/FastApproximateGWN.hpp | 45 +++++++++----- src/axom/quest/GWNMethods.hpp | 32 ++++++++-- 3 files changed, 97 insertions(+), 63 deletions(-) diff --git a/src/axom/primal/geometry/NURBSPatch.hpp b/src/axom/primal/geometry/NURBSPatch.hpp index c2e05cfbf7..175d76a7b0 100644 --- a/src/axom/primal/geometry/NURBSPatch.hpp +++ b/src/axom/primal/geometry/NURBSPatch.hpp @@ -3907,7 +3907,16 @@ class NURBSPatch return true; } - void nearBisectOnLongestAxis(NURBSPatch& p1, NURBSPatch& p2) const + /*! + * \brief Split the patch in the longer parametric direction, determined via maximum control-net polyline length. + * + * Rather than calculating It measures the maximum polyline length of the control net in each parametric direction: + * - u-length: max over segments ||P(i+1,j) - P(i,j)|| + * - v-length: max over segments ||P(i,j+1) - P(i,j)|| + * + * \note This heuristic considers only control points (and ignores weights for rational patches). + */ + void nearBisectOnLongerAxis(NURBSPatch& p1, NURBSPatch& p2) const { double split_val_u = (getNumKnots_u() == 2 * (getDegree_u() + 1)) ? 0.499 * getMinKnot_u() + 0.501 * getMaxKnot_u() @@ -3917,52 +3926,44 @@ class NURBSPatch ? 0.502 * getMinKnot_v() + 0.498 * getMaxKnot_v() : getKnots_v()[getNumKnots_v() / 2]; - auto make_split_candidate = [&](bool split_in_u) { - NURBSPatch patches[2]; - - // Avoid 2D (u,v) bisection of large, slender models which can lead to 4^k patch growth. - // Prefer a 1D split (u or v) that most reduces the max child bbox. - struct Result - { - NURBSPatch patches[2]; - double max_child_range_norm {0.0}; - }; + const auto& cps = getControlPoints(); + const auto shape = cps.shape(); + const int nu = shape[0]; + const int nv = shape[1]; - Result r {}; - - // Do an `uncheckedSplit`, which doesn't look at trimming curves - if(split_in_u) + double u_max_poly_len = 0.0; + for(int j = 0; j < nv; ++j) + { + double len = 0.0; + for(int i = 0; i + 1 < nu; ++i) { - uncheckedSplit_u(split_val_u, patches[0], patches[1]); + const auto d = cps(i + 1, j) - cps(i, j); + len += d.norm(); } - else + u_max_poly_len = axom::utilities::max(u_max_poly_len, len); + } + + double v_max_poly_len = 0.0; + for(int i = 0; i < nu; ++i) + { + double len = 0.0; + for(int j = 0; j + 1 < nv; ++j) { - uncheckedSplit_v(split_val_v, patches[0], patches[1]); + const auto d = cps(i, j + 1) - cps(i, j); + len += d.norm(); } + v_max_poly_len = axom::utilities::max(v_max_poly_len, len); + } - r.patches[0] = std::move(patches[0]); - r.patches[1] = std::move(patches[1]); - - // Bounding boxes here are only computed without trimming curves - r.max_child_range_norm = axom::utilities::max(r.patches[0].boundingBox().range().norm(), - r.patches[1].boundingBox().range().norm()); - - return r; - }; - - const auto u_split = make_split_candidate(/*split_in_u*/ true); - const auto v_split = make_split_candidate(/*split_in_u*/ false); - - const bool was_u_better = (u_split.max_child_range_norm <= v_split.max_child_range_norm); - auto* chosen = was_u_better ? &u_split : &v_split; - - // Once we pick the best direction, split the trimming curves - p1 = std::move(chosen->patches[0]); - p2 = std::move(chosen->patches[1]); - splitTrimmingCurves(was_u_better ? split_val_u : split_val_v, - was_u_better, - p1.getTrimmingCurves(), - p2.getTrimmingCurves()); + const bool split_in_u = (u_max_poly_len >= v_max_poly_len); + if(split_in_u) + { + split_u(split_val_u, p1, p2); + } + else + { + split_v(split_val_v, p1, p2); + } } /*! diff --git a/src/axom/quest/FastApproximateGWN.hpp b/src/axom/quest/FastApproximateGWN.hpp index 203f540e2e..e2cf96ecd4 100644 --- a/src/axom/quest/FastApproximateGWN.hpp +++ b/src/axom/quest/FastApproximateGWN.hpp @@ -315,10 +315,17 @@ class GWNMomentData /// Return the normal if computed from 3D data axom::primal::Vector getNormal() const { - static_assert(NDIMS == 3, "GWN Moments for triangles are defined only for 3D"); + static_assert(NDIMS == 3, "Normal vectors are defined only for 3D"); return axom::primal::Vector {ec[0], ec[1], ec[2]}; } + /// Return the surface area if computed from 3D data + double getSurfaceArea() const + { + static_assert(NDIMS == 3, "Surface areas are defined only for 3D"); + return a; + } + private: /// Transform raw moments into expansion coefficients void compute_coefficients() @@ -566,23 +573,18 @@ axom::Array> subdivide_patches( // Then compute a bounding box of all the surfaces for(auto& surf : input_patches_view) { - // This is where we would do Bezier extraction, if the curve-curve intersection - // routine were more robust :( - //for(auto& bez : surf.extractTrimmedBezier()) - { - auto the_patch = surf; + auto the_patch = surf; - if(the_patch.getNumTrimmingCurves() == 0) continue; + if(the_patch.getNumTrimmingCurves() == 0) continue; - the_patch.normalize(); - the_patch.clipToCurves(); + the_patch.normalize(); + the_patch.clipToCurves(); - // Re-check if the patch is empty after clipping to curve - if(the_patch.getNumTrimmingCurves() == 0) continue; + // Re-check if the patch is empty after clipping to curve + if(the_patch.getNumTrimmingCurves() == 0) continue; - candidates.push_back(the_patch); - total_bbox.addBox(the_patch.boundingBox()); - } + candidates.push_back(the_patch); + total_bbox.addBox(the_patch.boundingBox()); } // Iterate over all the surfaces until no patch has a bounding box @@ -590,7 +592,7 @@ axom::Array> subdivide_patches( for(int i = 0; i < npasses; ++i) { axom::Array subdivisions; - subdivisions.reserve(candidates.size() * 3 / 2); + subdivisions.reserve(candidates.size()); BoxType new_bbox; @@ -607,7 +609,7 @@ axom::Array> subdivide_patches( } NURBSType subpatches[2]; - candidate.nearBisectOnLongestAxis(subpatches[0], subpatches[1]); + candidate.nearBisectOnLongerAxis(subpatches[0], subpatches[1]); for(int si = 0; si < 2; si++) { if(subpatches[si].getNumTrimmingCurves() == 0) continue; @@ -623,6 +625,17 @@ axom::Array> subdivide_patches( candidates.swap(subdivisions); total_bbox = new_bbox; + + // Break if over 100,000 surfaces + if(candidates.size() > 1e5) + { + std::cout << axom::fmt::format("Excessive subdivision recorded after {} passes! ({} -> {})", + i, + input_patches_view.size(), + candidates.size()) + << std::endl; + break; + } } return candidates; diff --git a/src/axom/quest/GWNMethods.hpp b/src/axom/quest/GWNMethods.hpp index 17a40b935b..c0189718ac 100644 --- a/src/axom/quest/GWNMethods.hpp +++ b/src/axom/quest/GWNMethods.hpp @@ -165,7 +165,8 @@ class NURBSCurveGWNQuery stage_timer.start(); { AXOM_ANNOTATE_SCOPE("subdivision"); - m_subdivided_curves = subdivide_curves(m_input_curves_view, 2.0); + constexpr double subdivision_bbox_threshold = 0.1; + m_subdivided_curves = subdivide_curves(m_input_curves_view, subdivision_bbox_threshold); m_processed_curves_view = m_subdivided_curves.view(); } stage_timer.stop(); @@ -601,7 +602,8 @@ class NURBSPatchGWNQuery } // To use if normals are precomputed as moments, then used in caches - axom::Array> precomputed_normals; + axom::Array> precomputed_normals {}; + axom::Array precomputed_surface_areas {}; axom::utilities::Timer timer(true); axom::utilities::Timer stage_timer(false); @@ -614,7 +616,8 @@ class NURBSPatchGWNQuery stage_timer.start(); { AXOM_ANNOTATE_SCOPE("subdivision"); - m_subdivided_patches = subdivide_patches(m_input_patches_view, 0.1); + constexpr double subdivision_bbox_threshold = 0.1; + m_subdivided_patches = subdivide_patches(m_input_patches_view, subdivision_bbox_threshold); m_processed_patches_view = m_subdivided_patches.view(); } stage_timer.stop(); @@ -647,7 +650,10 @@ class NURBSPatchGWNQuery { AXOM_ANNOTATE_SCOPE("moment_precomputation"); precomputed_normals.resize(m_processed_patches_view.size()); + precomputed_surface_areas.resize(m_processed_patches_view.size()); + auto normals_view = precomputed_normals.view(); + auto surface_areas_view = precomputed_surface_areas.view(); auto compute_moments = [=](std::int32_t currentNode, const std::int32_t* leafNodes) -> GWNMoments { @@ -655,6 +661,7 @@ class NURBSPatchGWNQuery const auto leaf_moments = GWNMoments(m_processed_patches_view[idx]); normals_view[idx] = leaf_moments.getNormal(); + surface_areas_view[idx] = leaf_moments.getSurfaceArea(); return leaf_moments; }; @@ -667,8 +674,19 @@ class NURBSPatchGWNQuery } else { - // Without fast-approximation, processing is unnecessary - m_processed_patches_view = m_input_patches_view; + // Without fast-approximation, processing is unnecessary, but we still clean the + // the trimmed representation for more precise moment calculation + for(auto& surf : m_input_patches_view) + { + auto cleaned = surf.cleanedTrimmedRepresentation(); + if(cleaned.getNumTrimmingCurves() == 0) + { + continue; + } + + m_subdivided_patches.push_back(std::move(cleaned)); + } + m_processed_patches_view = m_subdivided_patches.view(); } if(use_memoization) @@ -679,7 +697,9 @@ class NURBSPatchGWNQuery AXOM_ANNOTATE_SCOPE("cache_initialization"); // If internal moments are already allocated, then normals are already precomputed - m_nurbs_cache_mgr = NURBSCacheManager(m_processed_patches_view, precomputed_normals.view()); + m_nurbs_cache_mgr = NURBSCacheManager(m_processed_patches_view, + precomputed_normals.view(), + precomputed_surface_areas.view()); } stage_timer.stop(); SLIC_INFO(axom::fmt::format(" Preprocessing stage (cache initialization): {} s", From b337aae677366d71c7655961525bfae61e16496f Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Mon, 11 May 2026 13:51:02 -0700 Subject: [PATCH 243/986] Improve some comments --- src/axom/primal/geometry/NURBSPatch.hpp | 175 ++++++++++-------- src/axom/quest/FastApproximateGWN.hpp | 35 +++- .../examples/quest_winding_number_2d.cpp | 4 +- 3 files changed, 137 insertions(+), 77 deletions(-) diff --git a/src/axom/primal/geometry/NURBSPatch.hpp b/src/axom/primal/geometry/NURBSPatch.hpp index 175d76a7b0..c54fa6d81e 100644 --- a/src/axom/primal/geometry/NURBSPatch.hpp +++ b/src/axom/primal/geometry/NURBSPatch.hpp @@ -3160,19 +3160,25 @@ class NURBSPatch } } - void clipToCurves() + /*! + * \brief Clip the edges of a NURBS surface to the AABB of the trimming curves + * + * \param [in] padding The amount to be left on each side of the AABB after clipping + * + * \sa NURBSPatch::clip() + */ + void clipToCurves(double padding = 1e-5) { // Take a union of all trimming curve parameter ParameterBoundingBoxType curve_bbox; for(auto& curv : m_trimmingCurves) curve_bbox.addBox(curv.boundingBox()); - uncheckedClip(curve_bbox.getMin()[0] - 1e-5, - curve_bbox.getMax()[0] + 1e-5, - curve_bbox.getMin()[1] - 1e-5, - curve_bbox.getMax()[1] + 1e-5); + uncheckedClip(curve_bbox.getMin()[0] - padding, + curve_bbox.getMax()[0] + padding, + curve_bbox.getMin()[1] - padding, + curve_bbox.getMax()[1] + padding); } - ///@} ///@{ @@ -3632,8 +3638,19 @@ class NURBSPatch return patch; } + /*! + * \brief Calculate the surface moments for the trimmed patch for GWN evaluation + * + * \param [in] npts The number of quadrature nodes used in each component integral + * \param [in] useBezierExtraction Set whether to do Bezier extraction on input for + * exponential convergence of general NURBS input + * + * Decomposes the surface into trimmed Bezier components and evaluates up to second + * order moments without recomputing quadrature nodes, avoiding the redundant processing + * of evaluating each separately. + */ template = 0 ? 3 : 0) + (ORDER >= 1 ? 9 : 0) + (ORDER >= 2 ? 27 : 0)> - primal::Vector calculateSurfaceMoments() const + primal::Vector calculateSurfaceMoments(int npts = 20, bool useBezierExtraction = false) const { // Need to integrate over 4 (for the coordinates and weight of the centroid) // + 3 (for the zeroth order moments) @@ -3641,81 +3658,91 @@ class NURBSPatch // + 27 (for the second order moments) Vector ret(0.0); - // Number of quadrature points - constexpr int npts = 20; - - // For now, doing this increases the likelihood of bad numerics, - // and is largely redundant after doing the bigger subdivision routine - - //for(const auto& patch : extractTrimmedBezier()) - { - auto& patch = *this; - - auto big_ol_integrand = [&patch](Point2D x) -> Vector { - Vector M(0.0); + auto big_ol_integrand = [](const auto& patch, Point2D x) -> Vector { + Vector M(0.0); - primal::Point eval; - primal::Vector Du, Dv; - patch.evaluateFirstDerivatives(x[0], x[1], eval, Du, Dv); - const auto the_norm = Vector::cross_product(Du, Dv); + primal::Point eval; + primal::Vector Du, Dv; + patch.evaluateFirstDerivatives(x[0], x[1], eval, Du, Dv); + const auto the_norm = Vector::cross_product(Du, Dv); - M[0] = the_norm.norm(); - M[1] = eval[0] * the_norm.norm(); - M[2] = eval[1] * the_norm.norm(); - M[3] = eval[2] * the_norm.norm(); + M[0] = the_norm.norm(); + M[1] = eval[0] * the_norm.norm(); + M[2] = eval[1] * the_norm.norm(); + M[3] = eval[2] * the_norm.norm(); - M[4] = the_norm[0]; - M[5] = the_norm[1]; - M[6] = the_norm[2]; + M[4] = the_norm[0]; + M[5] = the_norm[1]; + M[6] = the_norm[2]; - if constexpr(ORDER >= 1) + if constexpr(ORDER >= 1) + { + M[7] = eval[0] * the_norm[0]; + M[8] = eval[0] * the_norm[1]; + M[9] = eval[0] * the_norm[2]; + M[10] = eval[1] * the_norm[0]; + M[11] = eval[1] * the_norm[1]; + M[12] = eval[1] * the_norm[2]; + M[13] = eval[2] * the_norm[0]; + M[14] = eval[2] * the_norm[1]; + M[15] = eval[2] * the_norm[2]; + + if constexpr(ORDER >= 2) { - M[7] = eval[0] * the_norm[0]; - M[8] = eval[0] * the_norm[1]; - M[9] = eval[0] * the_norm[2]; - M[10] = eval[1] * the_norm[0]; - M[11] = eval[1] * the_norm[1]; - M[12] = eval[1] * the_norm[2]; - M[13] = eval[2] * the_norm[0]; - M[14] = eval[2] * the_norm[1]; - M[15] = eval[2] * the_norm[2]; - - if constexpr(ORDER >= 1) - { - M[16] = eval[0] * eval[0] * the_norm[0]; - M[17] = eval[0] * eval[0] * the_norm[1]; - M[18] = eval[0] * eval[0] * the_norm[2]; - M[19] = eval[0] * eval[1] * the_norm[0]; - M[20] = eval[0] * eval[1] * the_norm[1]; - M[21] = eval[0] * eval[1] * the_norm[2]; - M[22] = eval[0] * eval[2] * the_norm[0]; - M[23] = eval[0] * eval[2] * the_norm[1]; - M[24] = eval[0] * eval[2] * the_norm[2]; - M[25] = eval[1] * eval[0] * the_norm[0]; - M[26] = eval[1] * eval[0] * the_norm[1]; - M[27] = eval[1] * eval[0] * the_norm[2]; - M[28] = eval[1] * eval[1] * the_norm[0]; - M[29] = eval[1] * eval[1] * the_norm[1]; - M[30] = eval[1] * eval[1] * the_norm[2]; - M[31] = eval[1] * eval[2] * the_norm[0]; - M[32] = eval[1] * eval[2] * the_norm[1]; - M[33] = eval[1] * eval[2] * the_norm[2]; - M[34] = eval[2] * eval[0] * the_norm[0]; - M[35] = eval[2] * eval[0] * the_norm[1]; - M[36] = eval[2] * eval[0] * the_norm[2]; - M[37] = eval[2] * eval[1] * the_norm[0]; - M[38] = eval[2] * eval[1] * the_norm[1]; - M[39] = eval[2] * eval[1] * the_norm[2]; - M[40] = eval[2] * eval[2] * the_norm[0]; - M[41] = eval[2] * eval[2] * the_norm[1]; - M[42] = eval[2] * eval[2] * the_norm[2]; - } + M[16] = eval[0] * eval[0] * the_norm[0]; + M[17] = eval[0] * eval[0] * the_norm[1]; + M[18] = eval[0] * eval[0] * the_norm[2]; + M[19] = eval[0] * eval[1] * the_norm[0]; + M[20] = eval[0] * eval[1] * the_norm[1]; + M[21] = eval[0] * eval[1] * the_norm[2]; + M[22] = eval[0] * eval[2] * the_norm[0]; + M[23] = eval[0] * eval[2] * the_norm[1]; + M[24] = eval[0] * eval[2] * the_norm[2]; + M[25] = eval[1] * eval[0] * the_norm[0]; + M[26] = eval[1] * eval[0] * the_norm[1]; + M[27] = eval[1] * eval[0] * the_norm[2]; + M[28] = eval[1] * eval[1] * the_norm[0]; + M[29] = eval[1] * eval[1] * the_norm[1]; + M[30] = eval[1] * eval[1] * the_norm[2]; + M[31] = eval[1] * eval[2] * the_norm[0]; + M[32] = eval[1] * eval[2] * the_norm[1]; + M[33] = eval[1] * eval[2] * the_norm[2]; + M[34] = eval[2] * eval[0] * the_norm[0]; + M[35] = eval[2] * eval[0] * the_norm[1]; + M[36] = eval[2] * eval[0] * the_norm[2]; + M[37] = eval[2] * eval[1] * the_norm[0]; + M[38] = eval[2] * eval[1] * the_norm[1]; + M[39] = eval[2] * eval[1] * the_norm[2]; + M[40] = eval[2] * eval[2] * the_norm[0]; + M[41] = eval[2] * eval[2] * the_norm[1]; + M[42] = eval[2] * eval[2] * the_norm[2]; } + } - return M; - }; + return M; + }; - ret += evaluate_area_integral(patch.getTrimmingCurves(), big_ol_integrand, npts); + if(useBezierExtraction) + { + for(const auto& patch : extractTrimmedBezier()) + { + ret += evaluate_area_integral( + patch.getTrimmingCurves(), + [&patch, &big_ol_integrand](Point2D x) -> Vector { + return big_ol_integrand(patch, x); + }, + npts); + } + } + else + { + const auto& patch = *this; + ret += evaluate_area_integral( + patch.getTrimmingCurves(), + [&patch, &big_ol_integrand](Point2D x) -> Vector { + return big_ol_integrand(patch, x); + }, + npts); } return ret; diff --git a/src/axom/quest/FastApproximateGWN.hpp b/src/axom/quest/FastApproximateGWN.hpp index e2cf96ecd4..6d61aa5ae6 100644 --- a/src/axom/quest/FastApproximateGWN.hpp +++ b/src/axom/quest/FastApproximateGWN.hpp @@ -483,6 +483,22 @@ double fast_approximate_winding_number(const primal::Point& query, return gwn; } +/*! + * \brief Subdivides an array of NURBS curves to limit their maximum AABB diagonal + * + * \param [in] input_curves_view A const view to the const NURBS input curves + * \param [in] bbox_threshold The maximum AABB diagonal of each subdivided curve, + * as a percent of an AABB of the entire input shape + * \param [in] npasses The maximum number of passes through the input curves before + * an early return + * + * Starts with a collection of Bezier-extracted components, then iterates through the + * curves at most `npasses` times, and subdivides any which have an AABB diagonal + * greater than the threshold. The AABB of the entire shape is based on the union + * of AABB for the subdivisions, which is tighter than that of the original + * + * \return The array of subdivided NURBS curves. + */ template axom::Array> subdivide_curves( const axom::ArrayView>& input_curves_view, @@ -554,6 +570,23 @@ axom::Array> subdivide_curves( return candidates_nurbs; } +/*! + * \brief Subdivides an array of trimmed NURBS surfaces to limit their maximum AABB diagonal + * + * \param [in] input_patches_view A const view to the const NURBS input surfaces + * \param [in] bbox_threshold The maximum AABB diagonal of each subdivided surface, + * as a percent of an AABB of the entire input shape + * \param [in] npasses The maximum number of passes through the input surfaces before + * an early return + * + * Iterates through the curves at most `npasses` times, and subdivides any which have + * an AABB diagonal greater than the threshold. The AABB of the entire shape is based + * on the union of AABB for the subdivisions, which is tighter than that of the original + * Subdivision is performed on the single axis which is determined to be "longer" in physical + * space, as determined by NURBSPatch::nearBisectOnLongerAxis + * + * \return The array of subdivided NURBS surfaces. + */ template axom::Array> subdivide_patches( const axom::ArrayView>& input_patches_view, @@ -564,7 +597,7 @@ axom::Array> subdivide_patches( using NURBSType = primal::NURBSPatch; axom::Array candidates; - candidates.reserve(input_patches_view.size() * 3 / 2); + candidates.reserve(input_patches_view.size()); BoxType total_bbox; // Create initial array of processed patches, diff --git a/src/axom/quest/examples/quest_winding_number_2d.cpp b/src/axom/quest/examples/quest_winding_number_2d.cpp index f16a17e9e9..65a20b506d 100644 --- a/src/axom/quest/examples/quest_winding_number_2d.cpp +++ b/src/axom/quest/examples/quest_winding_number_2d.cpp @@ -274,8 +274,8 @@ class Input axom::runtime_policy::Policy policy = RuntimePolicy::seq; - const std::array valid_algorithms {"direct", "fast_approximation"}; - std::string algorithm {valid_algorithms[1]}; // fast-approximation + const std::array valid_algorithms {"direct", "fast_approximate"}; + std::string algorithm {valid_algorithms[1]}; // fast-approximate bool linearize {false}; int approximation_order {2}; From 6ca670a1f990b6a7c7b323a4a5788bfc1935d750 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Mon, 11 May 2026 14:07:33 -0700 Subject: [PATCH 244/986] Fix style --- src/axom/primal/tests/primal_solid_angle.cpp | 107 +++++++++---------- 1 file changed, 53 insertions(+), 54 deletions(-) diff --git a/src/axom/primal/tests/primal_solid_angle.cpp b/src/axom/primal/tests/primal_solid_angle.cpp index 7dcc3fa20b..da59455f3a 100644 --- a/src/axom/primal/tests/primal_solid_angle.cpp +++ b/src/axom/primal/tests/primal_solid_angle.cpp @@ -615,64 +615,63 @@ TEST(primal_solid_angle, nurbspatch_sphere) //------------------------------------------------------------------------------ TEST(primal_solid_angle, teardrop_regression_test) { - using Point3D = primal::Point; - - const double edge_tol = 1e-6; - const double ls_tol = 1e-10; - const double quad_tol = 1e-5; - const double disk_size = 0.01; - const double EPS = 1e-11; - - // Test the points on a teardrop for which - // the bottom portion is a bicubic sphere, - // the top portion is defined by the solid of revolution of a cubic Bezier curve - const auto teardrop_shape = axom::primal::detail::make_teardrop(); - - auto is_in_teardrop = [](Point3D x) -> bool { - const double radius = std::sqrt(x[0] * x[0] + x[1] * x[1]); - - if(radius > 1.0) - { - return false; - } - else - { - // The vertical cross-section of the tip is given by 3sin(1/3 * asin(1-2x)) + x - 0.5 - double fun = 3 * std::sin(1.0 / 3.0 * std::asin(1 - 2 * radius)) + radius - 0.5; - return (x[2] <= fun) && - ((x[2] > -1.0) || - std::sqrt(x[0] * x[0] + x[1] * x[1] + (x[2] + 1.0) * (x[2] + 1.0)) <= 1.0); - } - }; - - // Define a grid of points - constexpr int npts = 21; - double x_pts[npts]; - double y_pts[npts]; - double z_pts[npts]; - - axom::numerics::linspace(-1.1, 1.1, x_pts, npts); - axom::numerics::linspace(-1.1, 1.1, y_pts, npts); - axom::numerics::linspace(-2.1, 1.1, z_pts, npts); - - constexpr int tot_npts = npts * npts * npts; - axom::Array query_arr(0, tot_npts); - axom::Array true_containment_arr(0, tot_npts); - - for(int n = 0; n < tot_npts; ++n) - { - const Point3D query {x_pts[(n / npts / npts) % npts], y_pts[(n / npts) % npts], z_pts[n % npts]}; + using Point3D = primal::Point; - query_arr.emplace_back(query); - true_containment_arr.emplace_back(is_in_teardrop(query)); - } + const double edge_tol = 1e-6; + const double ls_tol = 1e-10; + const double quad_tol = 1e-5; + const double disk_size = 0.01; + const double EPS = 1e-11; + + // Test the points on a teardrop for which + // the bottom portion is a bicubic sphere, + // the top portion is defined by the solid of revolution of a cubic Bezier curve + const auto teardrop_shape = axom::primal::detail::make_teardrop(); - const auto gwn_array = - axom::primal::winding_number(query_arr, teardrop_shape, edge_tol, ls_tol, quad_tol, disk_size, EPS); + auto is_in_teardrop = [](Point3D x) -> bool { + const double radius = std::sqrt(x[0] * x[0] + x[1] * x[1]); - for(int n = 0; n < tot_npts; ++n) + if(radius > 1.0) + { + return false; + } + else { - const bool calc_containment = (std::round(gwn_array[n]) != 0); + // The vertical cross-section of the tip is given by 3sin(1/3 * asin(1-2x)) + x - 0.5 + double fun = 3 * std::sin(1.0 / 3.0 * std::asin(1 - 2 * radius)) + radius - 0.5; + return (x[2] <= fun) && + ((x[2] > -1.0) || std::sqrt(x[0] * x[0] + x[1] * x[1] + (x[2] + 1.0) * (x[2] + 1.0)) <= 1.0); + } + }; + + // Define a grid of points + constexpr int npts = 21; + double x_pts[npts]; + double y_pts[npts]; + double z_pts[npts]; + + axom::numerics::linspace(-1.1, 1.1, x_pts, npts); + axom::numerics::linspace(-1.1, 1.1, y_pts, npts); + axom::numerics::linspace(-2.1, 1.1, z_pts, npts); + + constexpr int tot_npts = npts * npts * npts; + axom::Array query_arr(0, tot_npts); + axom::Array true_containment_arr(0, tot_npts); + + for(int n = 0; n < tot_npts; ++n) + { + const Point3D query {x_pts[(n / npts / npts) % npts], y_pts[(n / npts) % npts], z_pts[n % npts]}; + + query_arr.emplace_back(query); + true_containment_arr.emplace_back(is_in_teardrop(query)); + } + + const auto gwn_array = + axom::primal::winding_number(query_arr, teardrop_shape, edge_tol, ls_tol, quad_tol, disk_size, EPS); + + for(int n = 0; n < tot_npts; ++n) + { + const bool calc_containment = (std::round(gwn_array[n]) != 0); EXPECT_EQ(calc_containment, true_containment_arr[n]); } } From 3840261f1348f473a729aab8d182f997e1edd650 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Tue, 12 May 2026 10:16:45 -0700 Subject: [PATCH 245/986] Wire new methods into driver scripts --- .../examples/quest_winding_number_2d.cpp | 36 +++++++++++-------- .../examples/quest_winding_number_3d.cpp | 36 +++++++++++-------- 2 files changed, 44 insertions(+), 28 deletions(-) diff --git a/src/axom/quest/examples/quest_winding_number_2d.cpp b/src/axom/quest/examples/quest_winding_number_2d.cpp index 65a20b506d..43151865d8 100644 --- a/src/axom/quest/examples/quest_winding_number_2d.cpp +++ b/src/axom/quest/examples/quest_winding_number_2d.cpp @@ -411,13 +411,17 @@ class Input } }; -using GWNQueryType = std::variant, +using GWNQueryType = std::variant, + axom::quest::NURBSCurveGWNQuery, + axom::quest::NURBSCurveGWNQuery, axom::quest::PolylineGWNQuery, axom::quest::PolylineGWNQuery, axom::quest::PolylineGWNQuery #if defined(AXOM_USE_RAJA) && defined(AXOM_USE_OPENMP) , - axom::quest::NURBSCurveGWNQuery, + axom::quest::NURBSCurveGWNQuery, + axom::quest::NURBSCurveGWNQuery, + axom::quest::NURBSCurveGWNQuery, axom::quest::PolylineGWNQuery, axom::quest::PolylineGWNQuery, axom::quest::PolylineGWNQuery @@ -427,23 +431,27 @@ using GWNQueryType = std::variant GWNQueryType pick_gwn_method(bool linearize_curves, int approximation_order) { - if(linearize_curves) + if(approximation_order == 0) { - if(approximation_order == 0) - { + if(triangulate) return axom::quest::PolylineGWNQuery {}; - } - else if(approximation_order == 1) - { + else + return axom::quest::NURBSCurveGWNQuery {}; + } + else if(approximation_order == 1) + { + if(triangulate) return axom::quest::PolylineGWNQuery {}; - } - else // approximation_order == 2 - { + else + return axom::quest::NURBSCurveGWNQuery {}; + } + else // approximation_order == 2 + { + if(triangulate) return axom::quest::PolylineGWNQuery {}; - } + else + return axom::quest::NURBSCurveGWNQuery {}; } - - return axom::quest::NURBSCurveGWNQuery {}; } GWNQueryType make_gwn_query(axom::runtime_policy::Policy policy, diff --git a/src/axom/quest/examples/quest_winding_number_3d.cpp b/src/axom/quest/examples/quest_winding_number_3d.cpp index c8fba9f93d..f13ef037eb 100644 --- a/src/axom/quest/examples/quest_winding_number_3d.cpp +++ b/src/axom/quest/examples/quest_winding_number_3d.cpp @@ -245,13 +245,17 @@ class Input } }; -using GWNQueryType = std::variant, +using GWNQueryType = std::variant, + axom::quest::NURBSPatchGWNQuery, + axom::quest::NURBSPatchGWNQuery, axom::quest::TriangleGWNQuery, axom::quest::TriangleGWNQuery, axom::quest::TriangleGWNQuery #if defined(AXOM_USE_RAJA) && defined(AXOM_USE_OPENMP) , - axom::quest::NURBSPatchGWNQuery, + axom::quest::NURBSPatchGWNQuery, + axom::quest::NURBSPatchGWNQuery, + axom::quest::NURBSPatchGWNQuery, axom::quest::TriangleGWNQuery, axom::quest::TriangleGWNQuery, axom::quest::TriangleGWNQuery @@ -261,23 +265,27 @@ using GWNQueryType = std::variant GWNQueryType pick_gwn_method(bool triangulate, int approximation_order) { - if(triangulate) + if(approximation_order == 0) { - if(approximation_order == 0) - { + if(triangulate) return axom::quest::TriangleGWNQuery {}; - } - else if(approximation_order == 1) - { + else + return axom::quest::NURBSPatchGWNQuery {}; + } + else if(approximation_order == 1) + { + if(triangulate) return axom::quest::TriangleGWNQuery {}; - } - else // approximation_order == 2 - { + else + return axom::quest::NURBSPatchGWNQuery {}; + } + else // approximation_order == 2 + { + if(triangulate) return axom::quest::TriangleGWNQuery {}; - } + else + return axom::quest::NURBSPatchGWNQuery {}; } - - return axom::quest::NURBSPatchGWNQuery {}; } GWNQueryType make_gwn_query(axom::runtime_policy::Policy policy, From 9bf287967f7b0dc6a9bb38a5781e5e223b5e3d5c Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Tue, 12 May 2026 10:28:03 -0700 Subject: [PATCH 246/986] Fix type traits --- src/axom/quest/GWNMethods.hpp | 8 ++++---- src/axom/quest/examples/quest_winding_number_2d.cpp | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/axom/quest/GWNMethods.hpp b/src/axom/quest/GWNMethods.hpp index c0189718ac..6ff8385be3 100644 --- a/src/axom/quest/GWNMethods.hpp +++ b/src/axom/quest/GWNMethods.hpp @@ -1117,8 +1117,8 @@ struct gwn_input_traits> : std::integral_constant { }; -template -struct gwn_input_traits> +template +struct gwn_input_traits> : std::integral_constant { }; @@ -1127,8 +1127,8 @@ struct gwn_input_traits> : std::integral_constant { }; -template -struct gwn_input_traits> +template +struct gwn_input_traits> : std::integral_constant { }; diff --git a/src/axom/quest/examples/quest_winding_number_2d.cpp b/src/axom/quest/examples/quest_winding_number_2d.cpp index 43151865d8..c2ee101247 100644 --- a/src/axom/quest/examples/quest_winding_number_2d.cpp +++ b/src/axom/quest/examples/quest_winding_number_2d.cpp @@ -433,21 +433,21 @@ GWNQueryType pick_gwn_method(bool linearize_curves, int approximation_order) { if(approximation_order == 0) { - if(triangulate) + if(linearize_curves) return axom::quest::PolylineGWNQuery {}; else return axom::quest::NURBSCurveGWNQuery {}; } else if(approximation_order == 1) { - if(triangulate) + if(linearize_curves) return axom::quest::PolylineGWNQuery {}; else return axom::quest::NURBSCurveGWNQuery {}; } else // approximation_order == 2 { - if(triangulate) + if(linearize_curves) return axom::quest::PolylineGWNQuery {}; else return axom::quest::NURBSCurveGWNQuery {}; From c162393eca7058775b88d6a0dd1747dce7990249 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Thu, 14 May 2026 13:59:31 -0700 Subject: [PATCH 247/986] Remove axom timers in favor of caliper --- src/axom/quest/GWNMethods.hpp | 184 +----------------- .../examples/quest_winding_number_2d.cpp | 4 - .../examples/quest_winding_number_3d.cpp | 28 +-- 3 files changed, 14 insertions(+), 202 deletions(-) diff --git a/src/axom/quest/GWNMethods.hpp b/src/axom/quest/GWNMethods.hpp index 6ff8385be3..a5aa33218f 100644 --- a/src/axom/quest/GWNMethods.hpp +++ b/src/axom/quest/GWNMethods.hpp @@ -154,29 +154,17 @@ class NURBSCurveGWNQuery return; } - axom::utilities::Timer timer(true); - axom::utilities::Timer stage_timer(false); - AXOM_ANNOTATE_SCOPE("preprocessing"); if(!use_direct_eval) { - stage_timer.reset(); - stage_timer.start(); { AXOM_ANNOTATE_SCOPE("subdivision"); constexpr double subdivision_bbox_threshold = 0.1; m_subdivided_curves = subdivide_curves(m_input_curves_view, subdivision_bbox_threshold); m_processed_curves_view = m_subdivided_curves.view(); } - stage_timer.stop(); - SLIC_INFO(axom::fmt::format(" Preprocessing stage (subdivision {} -> {}): {} s", - m_input_curves_view.size(), - m_processed_curves_view.size(), - stage_timer.elapsedTimeInSec())); - - stage_timer.reset(); - stage_timer.start(); + { AXOM_ANNOTATE_SCOPE("bvh_initialization"); const int ncurves = m_processed_curves_view.size(); @@ -190,12 +178,7 @@ class NURBSCurveGWNQuery }); m_bvh.initialize(aabbs_view, ncurves); } - stage_timer.stop(); - SLIC_INFO(axom::fmt::format(" Preprocessing stage (bvh initialization): {} s", - stage_timer.elapsedTimeInSec())); - stage_timer.reset(); - stage_timer.start(); { AXOM_ANNOTATE_SCOPE("moment_precomputation"); auto compute_moments = [=](std::int32_t currentNode, @@ -207,9 +190,6 @@ class NURBSCurveGWNQuery const auto traverser = m_bvh.getTraverser(); m_internal_moments = traverser.template reduce_tree(compute_moments); } - stage_timer.stop(); - SLIC_INFO(axom::fmt::format(" Preprocessing stage (moment precomputation): {} s", - stage_timer.elapsedTimeInSec())); } else { @@ -219,23 +199,9 @@ class NURBSCurveGWNQuery if(use_memoization) { - stage_timer.reset(); - stage_timer.start(); - { - AXOM_ANNOTATE_SCOPE("cache_initialization"); - m_nurbs_cache_mgr = NURBSCacheManager(m_processed_curves_view); - } - stage_timer.stop(); - SLIC_INFO(axom::fmt::format(" Preprocessing stage (cache initialization): {} s", - stage_timer.elapsedTimeInSec())); + AXOM_ANNOTATE_SCOPE("cache_initialization"); + m_nurbs_cache_mgr = NURBSCacheManager(m_processed_curves_view); } - - timer.stop(); - AXOM_ANNOTATE_METADATA("preprocessing_time", timer.elapsed(), ""); - SLIC_INFO(axom::fmt::format("NURBSCurve query preprocessing (loading curves{}{}): {} s", - use_memoization ? " and caches" : "", - !use_direct_eval ? " and bvh" : "", - timer.elapsedTimeInSec())); } /*! @@ -271,7 +237,6 @@ class NURBSCurveGWNQuery return pt; }; - axom::utilities::Timer query_timer(true); { AXOM_ANNOTATE_SCOPE("query"); const primal::WindingTolerances tol_copy = tol; @@ -344,22 +309,6 @@ class NURBSCurveGWNQuery } } } - query_timer.stop(); - - const double query_time_s = query_timer.elapsed(); - const double ms_per_query = query_timer.elapsedTimeInMilliSec() / num_query_points; - SLIC_INFO(axom::fmt::format( - axom::utilities::locale(), - "Querying {:L} samples in winding number field via {} with{} memoization took {:.3Lf} seconds" - " (@ {:.0Lf} queries per second; {:.6Lf} ms per query)", - num_query_points, - m_bvh.isInitialized() ? "fast approximation" : "direct evaluation", - m_nurbs_cache_mgr.empty() ? "out" : "", - query_time_s, - num_query_points / query_time_s, - ms_per_query)); - AXOM_ANNOTATE_METADATA("query_points", num_query_points, ""); - AXOM_ANNOTATE_METADATA("query_time", query_time_s, ""); } private: @@ -399,12 +348,8 @@ class PolylineGWNQuery return; } - axom::utilities::Timer timer(true); - axom::utilities::Timer stage_timer(false); - AXOM_ANNOTATE_SCOPE("preprocessing"); - stage_timer.start(); { AXOM_ANNOTATE_SCOPE("extract_segments"); @@ -420,15 +365,10 @@ class PolylineGWNQuery SegmentType {Point2D {coords(0, 0), coords(1, 0)}, Point2D {coords(0, 1), coords(1, 1)}}; }); } - stage_timer.stop(); - SLIC_INFO(axom::fmt::format(" Preprocessing stage (segment extraction): {} s", - stage_timer.elapsedTimeInSec())); // If direct evaluation is preferred, skip BVH initialization if(!use_direct_eval) { - stage_timer.reset(); - stage_timer.start(); { AXOM_ANNOTATE_SCOPE("bvh_initialization"); const int nlines = m_segments.size(); @@ -443,12 +383,7 @@ class PolylineGWNQuery }); m_bvh.initialize(aabbs_view, nlines); } - stage_timer.stop(); - SLIC_INFO(axom::fmt::format(" Preprocessing stage (bvh initialization): {} s", - stage_timer.elapsedTimeInSec())); - stage_timer.reset(); - stage_timer.start(); { AXOM_ANNOTATE_SCOPE("moment_precomputation"); const auto segments_view = m_segments.view(); @@ -462,16 +397,7 @@ class PolylineGWNQuery const auto traverser = m_bvh.getTraverser(); m_internal_moments = traverser.template reduce_tree(compute_moments); } - stage_timer.stop(); - SLIC_INFO(axom::fmt::format(" Preprocessing stage (moment precomputation): {} s", - stage_timer.elapsedTimeInSec())); } - - timer.stop(); - AXOM_ANNOTATE_METADATA("preprocessing_time", timer.elapsed(), ""); - SLIC_INFO(axom::fmt::format("Polyline preprocessing (loading segments{}): {} s", - !use_direct_eval ? " and bvh" : "", - timer.elapsedTimeInSec())); } /*! @@ -507,7 +433,6 @@ class PolylineGWNQuery return pt; }; - axom::utilities::Timer query_timer(true); { AXOM_ANNOTATE_SCOPE("query"); @@ -547,19 +472,6 @@ class PolylineGWNQuery }); } } - query_timer.stop(); - - const double query_time_s = query_timer.elapsed(); - const double ms_per_query = query_timer.elapsedTimeInMilliSec() / num_query_points; - SLIC_INFO(axom::fmt::format(axom::utilities::locale(), - "Querying {:L} samples in winding number field took {:.3Lf} seconds" - " (@ {:.0Lf} queries per second; {:.5Lf} ms per query)", - num_query_points, - query_time_s, - num_query_points / query_time_s, - ms_per_query)); - AXOM_ANNOTATE_METADATA("query_points", num_query_points, ""); - AXOM_ANNOTATE_METADATA("query_time", query_time_s, ""); } private: @@ -605,29 +517,17 @@ class NURBSPatchGWNQuery axom::Array> precomputed_normals {}; axom::Array precomputed_surface_areas {}; - axom::utilities::Timer timer(true); - axom::utilities::Timer stage_timer(false); - AXOM_ANNOTATE_SCOPE("preprocessing"); if(!use_direct_eval) { - stage_timer.reset(); - stage_timer.start(); { AXOM_ANNOTATE_SCOPE("subdivision"); constexpr double subdivision_bbox_threshold = 0.1; m_subdivided_patches = subdivide_patches(m_input_patches_view, subdivision_bbox_threshold); m_processed_patches_view = m_subdivided_patches.view(); } - stage_timer.stop(); - SLIC_INFO(axom::fmt::format(" Preprocessing stage (subdivision {} -> {}): {} s", - m_input_patches_view.size(), - m_processed_patches_view.size(), - stage_timer.elapsedTimeInSec())); - - stage_timer.reset(); - stage_timer.start(); + { AXOM_ANNOTATE_SCOPE("bvh_initialization"); const int npatches = m_processed_patches_view.size(); @@ -641,12 +541,7 @@ class NURBSPatchGWNQuery }); m_bvh.initialize(aabbs_view, npatches); } - stage_timer.stop(); - SLIC_INFO(axom::fmt::format(" Preprocessing stage (bvh initialization): {} s", - stage_timer.elapsedTimeInSec())); - stage_timer.reset(); - stage_timer.start(); { AXOM_ANNOTATE_SCOPE("moment_precomputation"); precomputed_normals.resize(m_processed_patches_view.size()); @@ -668,9 +563,6 @@ class NURBSPatchGWNQuery const auto traverser = m_bvh.getTraverser(); m_internal_moments = traverser.template reduce_tree(compute_moments); } - stage_timer.stop(); - SLIC_INFO(axom::fmt::format(" Preprocessing stage (moment precomputation): {} s", - stage_timer.elapsedTimeInSec())); } else { @@ -691,27 +583,13 @@ class NURBSPatchGWNQuery if(use_memoization) { - stage_timer.reset(); - stage_timer.start(); - { AXOM_ANNOTATE_SCOPE("cache_initialization"); // If internal moments are already allocated, then normals are already precomputed m_nurbs_cache_mgr = NURBSCacheManager(m_processed_patches_view, precomputed_normals.view(), precomputed_surface_areas.view()); - } - stage_timer.stop(); - SLIC_INFO(axom::fmt::format(" Preprocessing stage (cache initialization): {} s", - stage_timer.elapsedTimeInSec())); } - - timer.stop(); - AXOM_ANNOTATE_METADATA("preprocessing_time", timer.elapsed(), ""); - SLIC_INFO(axom::fmt::format("NURBSPatch query preprocessing (loading patches{}{}): {} s", - use_memoization ? " and caches" : "", - !use_direct_eval ? " and bvh" : "", - timer.elapsedTimeInSec())); } /*! @@ -749,7 +627,6 @@ class NURBSPatchGWNQuery return pt; }; - axom::utilities::Timer query_timer(true); { AXOM_ANNOTATE_SCOPE("query"); const primal::WindingTolerances tol_copy = tol; @@ -837,22 +714,6 @@ class NURBSPatchGWNQuery } } } - query_timer.stop(); - - const double query_time_s = query_timer.elapsed(); - const double ms_per_query = query_timer.elapsedTimeInMilliSec() / num_query_points; - SLIC_INFO(axom::fmt::format( - axom::utilities::locale(), - "Querying {:L} samples in winding number field via {} with{} memoization took {:.3Lf} seconds" - " (@ {:.0Lf} queries per second; {:.6Lf} ms per query)", - num_query_points, - m_bvh.isInitialized() ? "fast approximation" : "direct evaluation", - m_nurbs_cache_mgr.empty() ? "out" : "", - query_time_s, - num_query_points / query_time_s, - ms_per_query)); - AXOM_ANNOTATE_METADATA("query_points", num_query_points, ""); - AXOM_ANNOTATE_METADATA("query_time", query_time_s, ""); } private: @@ -884,9 +745,6 @@ class TriangleGWNQuery /// If fast-approximation is used, construct BVH void preprocess(axom::mint::UnstructuredMesh* tri_mesh, bool useDirectEval) { - axom::utilities::Timer timer(true); - axom::utilities::Timer stage_timer(false); - AXOM_ANNOTATE_SCOPE("preprocessing"); const auto ntris = tri_mesh->getNumberOfCells(); @@ -896,7 +754,6 @@ class TriangleGWNQuery return; } - stage_timer.start(); { AXOM_ANNOTATE_SCOPE("extract_triangles"); @@ -937,15 +794,10 @@ class TriangleGWNQuery (coords(2, 2) - shape_center[2]) / scale}}; }); } - stage_timer.stop(); - SLIC_INFO(axom::fmt::format(" Preprocessing stage (extract_triangles): {} s", - stage_timer.elapsedTimeInSec())); // If direct evaluation is preferred, skip BVH initialization if(!useDirectEval) { - stage_timer.reset(); - stage_timer.start(); { AXOM_ANNOTATE_SCOPE("bvh_init"); axom::Array aabbs(ntris, ntris); @@ -960,12 +812,7 @@ class TriangleGWNQuery }); m_bvh.initialize(aabbs_view, ntris); } - stage_timer.stop(); - SLIC_INFO( - axom::fmt::format(" Preprocessing stage (bvh): {} s", stage_timer.elapsedTimeInSec())); - - stage_timer.reset(); - stage_timer.start(); + { AXOM_ANNOTATE_SCOPE("moments"); const auto triangles_view = m_triangles.view(); @@ -979,14 +826,9 @@ class TriangleGWNQuery const auto traverser = m_bvh.getTraverser(); m_internal_moments = traverser.template reduce_tree(compute_moments); } - stage_timer.stop(); - SLIC_INFO( - axom::fmt::format(" Preprocessing stage (moments): {} s", stage_timer.elapsedTimeInSec())); } - timer.stop(); - - SLIC_INFO(axom::fmt::format("Total preprocessing: {} s", timer.elapsedTimeInSec())); } + /*! * \brief Evaluate the GWN for a query grid at the DOFs of the \a dc query mesh * @@ -1028,7 +870,6 @@ class TriangleGWNQuery return pt; }; - axom::utilities::Timer query_timer(true); { AXOM_ANNOTATE_SCOPE("query"); @@ -1068,19 +909,6 @@ class TriangleGWNQuery }); } } - query_timer.stop(); - - const double query_time_s = query_timer.elapsed(); - const double ms_per_query = query_timer.elapsedTimeInMilliSec() / num_query_points; - SLIC_INFO(axom::fmt::format(axom::utilities::locale(), - "Querying {:L} samples in winding number field took {:.3Lf} seconds" - " (@ {:.0Lf} queries per second; {:.5Lf} ms per query)", - num_query_points, - query_time_s, - num_query_points / query_time_s, - ms_per_query)); - AXOM_ANNOTATE_METADATA("query_points", num_query_points, ""); - AXOM_ANNOTATE_METADATA("query_time", query_time_s, ""); } private: diff --git a/src/axom/quest/examples/quest_winding_number_2d.cpp b/src/axom/quest/examples/quest_winding_number_2d.cpp index c2ee101247..efbd49ae0c 100644 --- a/src/axom/quest/examples/quest_winding_number_2d.cpp +++ b/src/axom/quest/examples/quest_winding_number_2d.cpp @@ -522,7 +522,6 @@ int main(int argc, char** argv) { AXOM_ANNOTATE_SCOPE("linearization"); - axom::utilities::Timer timer(true); axom::quest::LinearizeCurves lc; if(input.useUniformLinearization) { @@ -532,7 +531,6 @@ int main(int argc, char** argv) { lc.getLinearMeshNonUniform(curves.view(), &poly_mesh, input.percentError); } - timer.stop(); SLIC_INFO(axom::fmt::format( axom::utilities::locale(), @@ -540,8 +538,6 @@ int main(int argc, char** argv) curves.size(), input.segmentsPerKnotSpan, poly_mesh.getNumberOfCells())); - SLIC_INFO( - axom::fmt::format("Preprocessing stage (linearization): {} s", timer.elapsedTimeInSec())); } // Early return if user didn't set up a query mesh diff --git a/src/axom/quest/examples/quest_winding_number_3d.cpp b/src/axom/quest/examples/quest_winding_number_3d.cpp index f13ef037eb..a89008f197 100644 --- a/src/axom/quest/examples/quest_winding_number_3d.cpp +++ b/src/axom/quest/examples/quest_winding_number_3d.cpp @@ -340,7 +340,6 @@ int main(int argc, char** argv) axom::quest::STLReader stl_reader; stl_reader.setFileName(input.inputFile); - axom::utilities::Timer read_timer(true); const int ret = stl_reader.read(); if(ret != 0) @@ -350,7 +349,6 @@ int main(int argc, char** argv) } stl_reader.getMesh(&tri_mesh); - read_timer.stop(); BoundingBox3D* shape_bbox_ptr = &shape_bbox; axom::mint::for_all_nodes( @@ -359,10 +357,8 @@ int main(int argc, char** argv) shape_bbox_ptr->addPoint(Point3D {x, y, z}); }); - SLIC_INFO(axom::fmt::format(axom::utilities::locale(), - "Loaded {} triangles in {:.3Lf} seconds", - stl_reader.getNumFaces(), - read_timer.elapsed())); + SLIC_INFO( + axom::fmt::format(axom::utilities::locale(), "Loaded {} triangles", stl_reader.getNumFaces())); } else if(axom::utilities::string::endsWith(input.inputFile, ".step")) { @@ -372,14 +368,12 @@ int main(int argc, char** argv) step_reader.setFileName(input.inputFile); step_reader.setVerbosity(input.verbose); - axom::utilities::Timer read_timer(true); const int ret = step_reader.read(input.validate); if(ret != 0) { SLIC_ERROR(axom::fmt::format("Failed to read STEP file '{}'", input.inputFile)); return 1; } - read_timer.stop(); shape_bbox = step_reader.getBRepBoundingBox(); @@ -391,17 +385,13 @@ int main(int argc, char** argv) SLIC_INFO(step_reader.getBRepStats()); SLIC_INFO(axom::fmt::format("STEP file units: {}", step_reader.getFileUnits())); - SLIC_INFO(axom::fmt::format( - axom::utilities::locale(), - "Loaded {} trimmed NURBS patches (with {} trimming curves) in {:.3Lf} seconds", - step_reader.numPatches(), - num_trimming_curves, - read_timer.elapsed())); + SLIC_INFO(axom::fmt::format(axom::utilities::locale(), + "Loaded {} trimmed NURBS patches (with {} trimming curves)", + step_reader.numPatches(), + num_trimming_curves)); if(input.triangulate) { - read_timer.reset(); - read_timer.start(); AXOM_ANNOTATE_SCOPE("triangulation"); const int tc = step_reader.getTriangleMesh(&tri_mesh, input.linear_deflection, @@ -413,16 +403,14 @@ int main(int argc, char** argv) SLIC_ERROR("Failed to triangulate STEP geometry."); return 1; } - read_timer.stop(); SLIC_INFO( axom::fmt::format(axom::utilities::locale(), "Triangulated geometry with deflection {} and angular deflection {}" - " containing {:L} triangles in {:.3Lf} seconds", + " containing {:L} triangles", input.linear_deflection, input.angular_deflection, - tri_mesh.getNumberOfCells(), - read_timer.elapsed())); + tri_mesh.getNumberOfCells())); } else { From 7e9cd2b41989c87b297b89d802a7eae399dbbd13 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Thu, 14 May 2026 14:54:41 -0700 Subject: [PATCH 248/986] Improve doxygen comments --- src/axom/quest/GWNMethods.hpp | 110 +++++++++++++++++++++++++++++----- 1 file changed, 94 insertions(+), 16 deletions(-) diff --git a/src/axom/quest/GWNMethods.hpp b/src/axom/quest/GWNMethods.hpp index a5aa33218f..ca8021e50e 100644 --- a/src/axom/quest/GWNMethods.hpp +++ b/src/axom/quest/GWNMethods.hpp @@ -128,6 +128,20 @@ void generate_gwn_query_mesh(mfem::DataCollection& dc, ///@{ /// \name Query methods for 2D GWN applications +/*! + * \class NURBSCurveGWNQuery + * + * \tparam ExecSpace The execution space for the algorithm. + * \tparam ORDER If agglomeration is used, this is the order of the Taylor expansion. + * + * \brief Preprocesses NURBSCurve geoemtry for GWN evaluation, + * and performs the calculation on the DOFs of an input MFEM mesh. + * + * Possible evaluation modes are + * `use_direct_eval` : If true, evaluation is done curve-by-curve. + * If false, evaluation is sped up with agglomeration via Taylor-expansion + * `use_memoization` : Caches and re-uses subdivision data for curve evaluations + */ template class NURBSCurveGWNQuery { @@ -141,8 +155,13 @@ class NURBSCurveGWNQuery NURBSCurveGWNQuery() = default; - /// \brief Define view for NURBS data. - /// If memoization is used, allocate a cache for each curve. + /*! + * \brief Process input curves, optionally building a BVH + * + * \param [in] input_curves A view to the input curves + * \param [in] use_direct_eval If false, use accelerated agglomeration algorithm via BVH + * \param [in] use_memoization If true, allocate a per-thread cache for each curve + */ void preprocess(const CurveArrayType& input_curves, bool use_direct_eval = false, bool use_memoization = true) @@ -325,6 +344,19 @@ class NURBSCurveGWNQuery axom::spin::BVH<2, ExecSpace> m_bvh; }; +/*! + * \class PolylineGWNQuery + * + * \tparam ExecSpace The execution space for the algorithm. + * \tparam ORDER If agglomeration is used, this is the order of the Taylor expansion. + * + * \brief Preprocesses a linear mesh for GWN evaluation, + * and performs the calculation on the DOFs of an input MFEM mesh. + * + * Possible evaluation modes are + * `use_direct_eval` : If true, evaluation is done segment-by-segment. + * If false, evaluation is sped up with agglomeration via Taylor-expansion + */ template class PolylineGWNQuery { @@ -337,8 +369,12 @@ class PolylineGWNQuery PolylineGWNQuery() = default; - /// \brief Load polyline data into primal::Segments. - /// If fast-approximation is used, construct BVH + /*! + * \brief Process mint::mesh into axom::Segments, optionally building a BVH + * + * \param [in] poly_mesh The input mesh + * \param [in] use_direct_eval If false, use accelerated agglomeration algorithm via BVH + */ void preprocess(axom::mint::UnstructuredMesh* poly_mesh, bool use_direct_eval) { @@ -487,6 +523,20 @@ class PolylineGWNQuery ///@{ /// \name Query methods for 3D GWN applications +/*! + * \class NURBSPatchGWNQuery + * + * \tparam ExecSpace The execution space for the algorithm. + * \tparam ORDER If agglomeration is used, this is the order of the Taylor expansion. + * + * \brief Preprocesses NURBSPatch geoemtry for GWN evaluation, + * and performs the calculation on the DOFs of an input MFEM mesh. + * + * Possible evaluation modes are + * `use_direct_eval` : If true, evaluation is done patch-by-patch. + * If false, evaluation is sped up with agglomeration via Taylor-expansion + * `use_memoization` : Caches and re-uses trimming curve quadrature data for patch evaluation + */ template class NURBSPatchGWNQuery { @@ -500,8 +550,18 @@ class NURBSPatchGWNQuery NURBSPatchGWNQuery() = default; - /// \brief Define view for NURBS data. - /// If memoization is used, allocate a cache for each patch. + /*! + * \brief Process input patches, optionally building a BVH + * + * Processing involves "cleaning" input surfaces for more robust GWN evaluation by + * - Normalizing the parameter space of each surface according to the number of knot spans + * - Ensuring each input is represented as a trimmed surface + * - Clipping the parameter space of each surface to the visible (i.e. trimmed) portion + * + * \param [in] input_patches A view to the input trimmed NURBS surfaces + * \param [in] use_direct_eval If false, use accelerated agglomeration algorithm via BVH + * \param [in] use_memoization If true, allocate a per-thread cache for each patch + */ void preprocess(const PatchArrayType& input_patches, bool use_direct_eval = false, bool use_memoization = true) @@ -583,12 +643,12 @@ class NURBSPatchGWNQuery if(use_memoization) { - AXOM_ANNOTATE_SCOPE("cache_initialization"); + AXOM_ANNOTATE_SCOPE("cache_initialization"); - // If internal moments are already allocated, then normals are already precomputed - m_nurbs_cache_mgr = NURBSCacheManager(m_processed_patches_view, - precomputed_normals.view(), - precomputed_surface_areas.view()); + // If internal moments are already allocated, then normals are already precomputed + m_nurbs_cache_mgr = NURBSCacheManager(m_processed_patches_view, + precomputed_normals.view(), + precomputed_surface_areas.view()); } } @@ -730,6 +790,19 @@ class NURBSPatchGWNQuery axom::spin::BVH<3, ExecSpace> m_bvh; }; +/*! + * \class TriangleGWNQuery + * + * \tparam ExecSpace The execution space for the algorithm. + * \tparam ORDER If agglomeration is used, this is the order of the Taylor expansion. + * + * \brief Preprocesses a triangle mesh for GWN evaluation, + * and performs the calculation on the DOFs of an input MFEM mesh. + * + * Possible evaluation modes are + * `use_direct_eval` : If true, evaluation is done triangle-by-triangle. + * If false, evaluation is sped up with agglomeration via Taylor-expansion + */ template class TriangleGWNQuery { @@ -741,9 +814,14 @@ class TriangleGWNQuery TriangleGWNQuery() = default; - /// \brief Load mesh data into primal::Triangles. - /// If fast-approximation is used, construct BVH - void preprocess(axom::mint::UnstructuredMesh* tri_mesh, bool useDirectEval) + /*! + * \brief Load triangles from mint::mesh to primal::Triangles, optionally building a BVH + * + * \param [in] tri_mesh The input mesh + * \param [in] use_direct_eval If false, use accelerated agglomeration algorithm via BVH + */ + void preprocess(axom::mint::UnstructuredMesh* tri_mesh, + bool use_direct_eval) { AXOM_ANNOTATE_SCOPE("preprocessing"); @@ -796,7 +874,7 @@ class TriangleGWNQuery } // If direct evaluation is preferred, skip BVH initialization - if(!useDirectEval) + if(!use_direct_eval) { { AXOM_ANNOTATE_SCOPE("bvh_init"); @@ -812,7 +890,7 @@ class TriangleGWNQuery }); m_bvh.initialize(aabbs_view, ntris); } - + { AXOM_ANNOTATE_SCOPE("moments"); const auto triangles_view = m_triangles.view(); From ee35d7543166f20a86431e69c47107c80d6fa797 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Thu, 14 May 2026 16:00:55 -0700 Subject: [PATCH 249/986] Add options for maximum subdivision --- src/axom/quest/FastApproximateGWN.hpp | 67 +++++++++++++++------- src/axom/quest/GWNMethods.hpp | 58 +++++++++++++++++-- src/axom/quest/tests/quest_gwn_methods.cpp | 46 +++------------ 3 files changed, 108 insertions(+), 63 deletions(-) diff --git a/src/axom/quest/FastApproximateGWN.hpp b/src/axom/quest/FastApproximateGWN.hpp index 6d61aa5ae6..24fa23c3df 100644 --- a/src/axom/quest/FastApproximateGWN.hpp +++ b/src/axom/quest/FastApproximateGWN.hpp @@ -489,6 +489,8 @@ double fast_approximate_winding_number(const primal::Point& query, * \param [in] input_curves_view A const view to the const NURBS input curves * \param [in] bbox_threshold The maximum AABB diagonal of each subdivided curve, * as a percent of an AABB of the entire input shape + * \param [in] max_curves The maximum number of curves after which further curves + * will not be subdivided * \param [in] npasses The maximum number of passes through the input curves before * an early return * @@ -503,6 +505,7 @@ template axom::Array> subdivide_curves( const axom::ArrayView>& input_curves_view, double bbox_threshold, + int max_curves = 1e6, int npasses = 10) { using BoxType = primal::BoundingBox; @@ -510,7 +513,7 @@ axom::Array> subdivide_curves( using BezierType = primal::BezierCurve; // Compute a bounding box of all the curves - axom::Array candidates; + axom::Array candidates, subdivisions; BoxType total_bbox; // For NURBSCurves, first do a pass of Bezier extraction @@ -523,21 +526,29 @@ axom::Array> subdivide_curves( } } + SLIC_WARNING_IF( + candidates.size() >= max_curves, + "quest::subdivide_curves: Number of bezier extracted input curves exceeds given maximum"); + // Iterate over all the curves until none have a bounding box // bigger than threshold * (total_bbox's size) - for(int i = 0; i < npasses; ++i) + for(int i = 0; i < npasses && candidates.size() < max_curves; ++i) { - axom::Array subdivisions; - subdivisions.reserve(candidates.size() * 3 / 2); - + subdivisions.clear(); BoxType new_bbox; // If any patch is bigger than the threshold, subdivide it, // and add it to the next level. Repeat as needed. const double max_range_norm = bbox_threshold * total_bbox.range().norm(); - for(const auto& candidate : candidates) + for(int j = 0; j < candidates.size(); ++j) { - if(candidate.boundingBox().range().norm() < max_range_norm) + const auto& candidate = candidates[j]; + const int remaining = candidates.size() - j - 1; + + // Skip the bisect if the surface is already below the threshold, + // or if doind the subdivision will put us above the maximum patch cound + if(candidate.boundingBox().range().norm() < max_range_norm || + subdivisions.size() + 2 + remaining > max_curves) { new_bbox.addBox(candidate.boundingBox()); subdivisions.push_back(candidate); @@ -558,6 +569,13 @@ axom::Array> subdivide_curves( candidates.swap(subdivisions); total_bbox = new_bbox; + + // Break if over the maximum number of surfaces + if(candidates.size() >= max_curves) + { + SLIC_WARNING("quest::subdivide_curves: Number of subdivided curves exceeds given maximum"); + break; + } } // Do one final pass to turn the array of candidates into NURBS @@ -576,6 +594,8 @@ axom::Array> subdivide_curves( * \param [in] input_patches_view A const view to the const NURBS input surfaces * \param [in] bbox_threshold The maximum AABB diagonal of each subdivided surface, * as a percent of an AABB of the entire input shape + * \param [in] max_patches The maximum number of surfaces before additional surfaces + * will not be subdivided * \param [in] npasses The maximum number of passes through the input surfaces before * an early return * @@ -591,12 +611,13 @@ template axom::Array> subdivide_patches( const axom::ArrayView>& input_patches_view, double bbox_threshold, + int max_patches = 1e5, int npasses = 10) { using BoxType = primal::BoundingBox; using NURBSType = primal::NURBSPatch; - axom::Array candidates; + axom::Array candidates, subdivisions; candidates.reserve(input_patches_view.size()); BoxType total_bbox; @@ -620,21 +641,31 @@ axom::Array> subdivide_patches( total_bbox.addBox(the_patch.boundingBox()); } + if(candidates.size() >= max_patches) + { + SLIC_WARNING("quest::subdivide_patches: Number of input patches exceeds given maximum"); + return candidates; + } + // Iterate over all the surfaces until no patch has a bounding box // bigger than threshold * (total_bbox's size) for(int i = 0; i < npasses; ++i) { - axom::Array subdivisions; - subdivisions.reserve(candidates.size()); - + subdivisions.clear(); BoxType new_bbox; // If any patch is bigger than the threshold, subdivide it, clip it, // and add it to the next level. Repeat as needed. const double max_range_norm = bbox_threshold * total_bbox.range().norm(); - for(const auto& candidate : candidates) + for(int j = 0; j < candidates.size(); ++j) { - if(candidate.boundingBox().range().norm() < max_range_norm) + const auto& candidate = candidates[j]; + const int remaining = candidates.size() - j - 1; + + // Skip the bisect if the surface is already below the threshold, + // or if doind the subdivision will put us above the maximum patch cound + if(candidate.boundingBox().range().norm() < max_range_norm || + subdivisions.size() + 2 + remaining > max_patches) { new_bbox.addBox(candidate.boundingBox()); subdivisions.push_back(candidate); @@ -659,14 +690,10 @@ axom::Array> subdivide_patches( candidates.swap(subdivisions); total_bbox = new_bbox; - // Break if over 100,000 surfaces - if(candidates.size() > 1e5) + // Break if over the maximum number of surfaces + if(candidates.size() >= max_patches) { - std::cout << axom::fmt::format("Excessive subdivision recorded after {} passes! ({} -> {})", - i, - input_patches_view.size(), - candidates.size()) - << std::endl; + SLIC_WARNING("quest::subdivide_patches: Number of subdivided patches exceeds given maximum"); break; } } diff --git a/src/axom/quest/GWNMethods.hpp b/src/axom/quest/GWNMethods.hpp index ca8021e50e..ba2a2647f2 100644 --- a/src/axom/quest/GWNMethods.hpp +++ b/src/axom/quest/GWNMethods.hpp @@ -155,6 +155,24 @@ class NURBSCurveGWNQuery NURBSCurveGWNQuery() = default; + ///@{ + /// \name Setters for misc algorithm parameters + void setSubdivisionBboxThreshold(double subdivision_bbox_threshold) + { + m_subdivision_bbox_threshold = subdivision_bbox_threshold; + } + + void setSubdivisionMaxPasses(int subdivision_max_passes) + { + m_subdivision_max_passes = subdivision_max_passes; + } + + void setSubdivisionMaxNumCurves(int subdivision_max_curves) + { + m_subdivision_max_curves = subdivision_max_curves; + } + ///@} + /*! * \brief Process input curves, optionally building a BVH * @@ -179,8 +197,10 @@ class NURBSCurveGWNQuery { { AXOM_ANNOTATE_SCOPE("subdivision"); - constexpr double subdivision_bbox_threshold = 0.1; - m_subdivided_curves = subdivide_curves(m_input_curves_view, subdivision_bbox_threshold); + m_subdivided_curves = subdivide_curves(m_input_curves_view, + m_subdivision_bbox_threshold, + m_subdivision_max_curves, + m_subdivision_max_passes); m_processed_curves_view = m_subdivided_curves.view(); } @@ -342,6 +362,11 @@ class NURBSCurveGWNQuery // Only needed for fast approximation method axom::Array m_internal_moments; axom::spin::BVH<2, ExecSpace> m_bvh; + + // Additional algorithm parameters + double m_subdivision_bbox_threshold {1.0}; + int m_subdivision_max_passes {10}; + int m_subdivision_max_curves {1000000}; }; /*! @@ -550,6 +575,24 @@ class NURBSPatchGWNQuery NURBSPatchGWNQuery() = default; + ///@{ + /// \name Setters for misc algorithm parameters + void setSubdivisionBboxThreshold(double subdivision_bbox_threshold) + { + m_subdivision_bbox_threshold = subdivision_bbox_threshold; + } + + void setSubdivisionMaxPasses(int subdivision_max_passes) + { + m_subdivision_max_passes = subdivision_max_passes; + } + + void setSubdivisionMaxNumPatches(int subdivision_max_patches) + { + m_subdivision_max_patches = subdivision_max_patches; + } + ///@} + /*! * \brief Process input patches, optionally building a BVH * @@ -583,8 +626,10 @@ class NURBSPatchGWNQuery { { AXOM_ANNOTATE_SCOPE("subdivision"); - constexpr double subdivision_bbox_threshold = 0.1; - m_subdivided_patches = subdivide_patches(m_input_patches_view, subdivision_bbox_threshold); + m_subdivided_patches = subdivide_patches(m_input_patches_view, + m_subdivision_bbox_threshold, + m_subdivision_max_patches, + m_subdivision_max_passes); m_processed_patches_view = m_subdivided_patches.view(); } @@ -788,6 +833,11 @@ class NURBSPatchGWNQuery // Only needed for fast approximation method axom::Array m_internal_moments; axom::spin::BVH<3, ExecSpace> m_bvh; + + // Additional algorithm parameters + double m_subdivision_bbox_threshold {1.0}; + int m_subdivision_max_passes {10}; + int m_subdivision_max_patches {10000}; }; /*! diff --git a/src/axom/quest/tests/quest_gwn_methods.cpp b/src/axom/quest/tests/quest_gwn_methods.cpp index 478b81379b..939cd9fe7d 100644 --- a/src/axom/quest/tests/quest_gwn_methods.cpp +++ b/src/axom/quest/tests/quest_gwn_methods.cpp @@ -155,7 +155,7 @@ TEST(quest_gwn_methods, gwn_moment_data_triangle) //------------------------------------------------------------------------------ template -void check_mfem_mesh_linearization() +void check_mfem_mesh_evaluation() { using NURBSCurve2D = axom::primal::NURBSCurve; const std::string fileName = pjoin(AXOM_DATA_DIR, "contours", "svg", "mfem_logo_simp.mesh"); @@ -168,7 +168,7 @@ void check_mfem_mesh_linearization() const int ret = mfem_reader.read(curves); if(ret != 0) { - SLIC_ERROR(axom::fmt::format("Failed to read STEP file '{}'", fileName)); + SLIC_ERROR(axom::fmt::format("Failed to read mesh file '{}'", fileName)); } // Get a linearization of the shape @@ -252,32 +252,12 @@ void check_mfem_mesh_linearization() EXPECT_EQ(inout_direct[i], inout_other[i]); } } - - mfem::VisItDataCollection windingDC_0("winding_0", dc[0].GetMesh()); - windingDC_0.RegisterField("winding", dc[0].GetField("winding")); - windingDC_0.RegisterField("inout", dc[0].GetField("inout")); - windingDC_0.Save(); - - mfem::VisItDataCollection windingDC_1("winding_1", dc[1].GetMesh()); - windingDC_1.RegisterField("winding", dc[1].GetField("winding")); - windingDC_1.RegisterField("inout", dc[1].GetField("inout")); - windingDC_1.Save(); - - mfem::VisItDataCollection windingDC_2("winding_2", dc[2].GetMesh()); - windingDC_2.RegisterField("winding", dc[2].GetField("winding")); - windingDC_2.RegisterField("inout", dc[2].GetField("inout")); - windingDC_2.Save(); - - mfem::VisItDataCollection windingDC_3("winding_3", dc[3].GetMesh()); - windingDC_3.RegisterField("winding", dc[3].GetField("winding")); - windingDC_3.RegisterField("inout", dc[3].GetField("inout")); - windingDC_3.Save(); } #ifdef AXOM_USE_OPENCASCADE //------------------------------------------------------------------------------ template -void check_step_file_triangulation() +void check_step_file_evaluation() { const std::string fileName = pjoin(AXOM_DATA_DIR, "quest", "step", "nut.step"); @@ -376,30 +356,18 @@ void check_step_file_triangulation() #endif //------------------------------------------------------------------------------ -TEST(quest_gwn_methods, mfem_mesh_linearization) -{ - check_mfem_mesh_linearization(); -} +TEST(quest_gwn_methods, mfem_mesh_evaluation) { check_mfem_mesh_evaluation(); } #if defined(AXOM_USE_OPENMP) && defined(AXOM_USE_RAJA) -TEST(quest_gwn_methods, mfem_mesh_linearization_omp) -{ - check_mfem_mesh_linearization(); -} +TEST(quest_gwn_methods, mfem_mesh_evaluation_omp) { check_mfem_mesh_evaluation(); } #endif #ifdef AXOM_USE_OPENCASCADE -TEST(quest_gwn_methods, step_file_triangulation) -{ - check_step_file_triangulation(); -} +TEST(quest_gwn_methods, step_file_evaluation) { check_step_file_evaluation(); } #endif #if defined(AXOM_USE_OPENCASCADE) && defined(AXOM_USE_OPENMP) && defined(AXOM_USE_RAJA) -TEST(quest_gwn_methods, step_file_triangulation_omp) -{ - check_step_file_triangulation(); -} +TEST(quest_gwn_methods, step_file_evaluation_omp) { check_step_file_evaluation(); } #endif //------------------------------------------------------------------------------ From 51827f3f8b84c6134703596d7bae0df6d14640b8 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Thu, 14 May 2026 16:09:32 -0700 Subject: [PATCH 250/986] Fix comments throughout --- src/axom/primal/geometry/NURBSPatch.hpp | 3 ++- .../primal/operators/detail/winding_number_3d_memoization.hpp | 4 ++-- src/axom/quest/FastApproximateGWN.hpp | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/axom/primal/geometry/NURBSPatch.hpp b/src/axom/primal/geometry/NURBSPatch.hpp index c54fa6d81e..6609b4a49e 100644 --- a/src/axom/primal/geometry/NURBSPatch.hpp +++ b/src/axom/primal/geometry/NURBSPatch.hpp @@ -3937,9 +3937,10 @@ class NURBSPatch /*! * \brief Split the patch in the longer parametric direction, determined via maximum control-net polyline length. * - * Rather than calculating It measures the maximum polyline length of the control net in each parametric direction: + * We measure the maximum polyline length of the control net in each parametric direction, * - u-length: max over segments ||P(i+1,j) - P(i,j)|| * - v-length: max over segments ||P(i,j+1) - P(i,j)|| + * and split along the direction with the larger maximum. This approximates the geometric stretch along that axis. * * \note This heuristic considers only control points (and ignores weights for rational patches). */ diff --git a/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp b/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp index dad5c0e8a3..856c1ab112 100644 --- a/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp +++ b/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp @@ -163,7 +163,7 @@ class NURBSPatchGWNCache // In a GWN context, the surface has been sufficiently subdivided to // make additional bezier extraction redundant const auto normal_area = - m_alteredPatch.calculateTrimmedPatchNormalArea(/*npts*/ 20, /*useBezierExtraction*/ false); + m_alteredPatch.calculateTrimmedPatchNormalArea(/*npts*/ 10, /*useBezierExtraction*/ false); m_surfaceArea = normal_area.second; if(m_surfaceArea <= 0.0) @@ -359,7 +359,7 @@ class NURBSPatchCacheManager m_nurbs_caches.push_back(NURBSCache(patch, mustComputeNormal)); } - // If we didn't comptue normals in NURBSCache constructor, + // If we didn't compute normals in NURBSCache constructor, // need to use precomputed values if(!mustComputeNormal) { diff --git a/src/axom/quest/FastApproximateGWN.hpp b/src/axom/quest/FastApproximateGWN.hpp index 24fa23c3df..7ba60a4961 100644 --- a/src/axom/quest/FastApproximateGWN.hpp +++ b/src/axom/quest/FastApproximateGWN.hpp @@ -129,7 +129,8 @@ class GWNMomentData /// Construct moments from a trimmed NURBS surface explicit GWNMomentData(const axom::primal::NURBSPatch& a_patch) { - const auto patch_data = a_patch.template calculateSurfaceMoments(); + const auto patch_data = + a_patch.template calculateSurfaceMoments(/*npts*/ 10, /*useBezierExtraction*/ false); a = patch_data[0]; ap[0] = patch_data[1]; From c144a58d124a5b3822788a115da579407dc5a346 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 14 May 2026 17:48:24 -0700 Subject: [PATCH 251/986] Redesigned mixed-field support so it uses a MixedFieldView that gets its material traversal from a MatsetView::const_iterator and values from trait classes. --- src/axom/bump/CMakeLists.txt | 1 + src/axom/bump/MergeMeshes.hpp | 29 ++-- src/axom/bump/tests/bump_mergemeshes.cpp | 14 +- src/axom/bump/tests/bump_views.cpp | 47 +++-- src/axom/bump/views/MaterialView.hpp | 24 ++- src/axom/bump/views/MixedFieldView.hpp | 164 ++++++++++++++++++ src/axom/bump/views/dispatch_material.hpp | 32 +--- .../bump/views/dispatch_material_field.hpp | 126 +++++++++++--- 8 files changed, 349 insertions(+), 88 deletions(-) create mode 100644 src/axom/bump/views/MixedFieldView.hpp diff --git a/src/axom/bump/CMakeLists.txt b/src/axom/bump/CMakeLists.txt index 69913935c8..03d0690291 100644 --- a/src/axom/bump/CMakeLists.txt +++ b/src/axom/bump/CMakeLists.txt @@ -54,6 +54,7 @@ set(bump_headers views/dispatch_utilities.hpp views/ExplicitCoordsetView.hpp views/MaterialView.hpp + views/MixedFieldView.hpp views/NodeArrayView.hpp views/RectilinearCoordsetView.hpp views/Shapes.hpp diff --git a/src/axom/bump/MergeMeshes.hpp b/src/axom/bump/MergeMeshes.hpp index ebe9523051..462ee2b635 100644 --- a/src/axom/bump/MergeMeshes.hpp +++ b/src/axom/bump/MergeMeshes.hpp @@ -1563,8 +1563,8 @@ class DispatchAnyMatset template void dispatchMixedField(conduit::Node &n_matset, conduit::Node &n_field, FuncType &&func) { - axom::bump::views::dispatch_material_field(n_matset, n_field, [&](auto matsetView) { - func(matsetView); + axom::bump::views::dispatch_material_field(n_matset, n_field, [&](auto matsetView, auto mixedFieldView) { + func(matsetView, mixedFieldView); }); } }; @@ -1637,8 +1637,8 @@ class DispatchTypedUnibufferMatset template void dispatchMixedField(conduit::Node &n_matset, conduit::Node &n_field, FuncType &&func) { - axom::bump::views::dispatch_material_unibuffer_field(n_matset, n_field, [&](auto matsetView) { - func(matsetView); + axom::bump::views::dispatch_material_unibuffer_field(n_matset, n_field, [&](auto matsetView, auto mixedFieldView) { + func(matsetView, mixedFieldView); }); } }; @@ -2268,10 +2268,9 @@ class MergeMeshesAndMatsets : public MergeMeshes conduit::Node &n_matset = n_matsets.fetch_existing(matsetName); // Get the source field. conduit::Node &n_src_field = inputs[i].m_input->fetch_existing(srcFieldPath); - // Dispatch the source mixed field. - disp.dispatchMixedField(n_matset, n_src_field, [&](auto srcMatsetView) { - This->mergeMixedField_copy(srcMatsetView, matsetValuesView, offsetsView, nzones, zOffset); + disp.dispatchMixedField(n_matset, n_src_field, [&](auto srcMatsetView, auto srcMixedFieldView) { + This->mergeMixedField_copy(srcMatsetView, srcMixedFieldView, matsetValuesView, offsetsView, nzones, zOffset); }); } else @@ -2287,15 +2286,17 @@ class MergeMeshesAndMatsets : public MergeMeshes /*! * \brief Copy a mixed field from the \a srcMatsetView into the output \a mixedFieldView. * - * \param srcMatsetView The input matset view that contains the field, accessible as "volume_fraction". - * \param[out] mixedFieldView The output view that contains the merged field. + * \param srcMatsetView The input matset view that is used to traverse materials. + * \param srcMixedFieldView The input mixed field view that contains the field data for the materials. + * \param[out] outputView The output view that contains the merged field. * \param offsetsView The offsets view that contains the offsets for the output zones. * \param nzones The number of zones in the current input. * \param zOffset The starting offset in the output mixed field view. */ - template - void mergeMixedField_copy(MatsetView srcMatsetView, - MixedFieldView mixedFieldView, + template + void mergeMixedField_copy(SourceMatsetView srcMatsetView, + SourceMixedFieldView srcMixedFieldView, + OutputFieldView outputView, OffsetsView offsetsView, axom::IndexType nzones, axom::IndexType zOffset) const @@ -2312,8 +2313,8 @@ class MergeMeshesAndMatsets : public MergeMeshes for(axom::IndexType mi = 0; mi < nmats; mi++, zoneMat++) { const auto destIndex = zoneStart + mi; - // NOTE: We are using the "volume_fraction" from that MatsetView as the mixed field value. - mixedFieldView[destIndex] = zoneMat.volume_fraction(); + // Copy the value for the zoneMat from the mixed field view into the output. + outputView[destIndex] = srcMixedFieldView.value(zoneMat); } }); } diff --git a/src/axom/bump/tests/bump_mergemeshes.cpp b/src/axom/bump/tests/bump_mergemeshes.cpp index d2a0af8d96..61dd6a27db 100644 --- a/src/axom/bump/tests/bump_mergemeshes.cpp +++ b/src/axom/bump/tests/bump_mergemeshes.cpp @@ -206,9 +206,9 @@ struct test_mergemeshes topology: mesh association: element matset: mat - values: [200.1, 201.15, 202.3, 203.15, 204.1, 205.15, 206.2, 207.15, 208.15] - # matset_values encodes 200+zone.mat - matset_values: [200.1, 201.1,201.2, 202.3, 203.1,203.2, 204.1, 205.1,205.2, 206.2, 207.1,207.2, 208.1,208.2] + values: [-200.1, -201.15, -202.3, -203.15, -204.1, -205.15, -206.2, -207.15, -208.15] + # matset_values encodes -200+zone.mat + matset_values: [-200.1, -201.1,-201.2, -202.3, -203.1,-203.2, -204.1, -205.1,-205.2, -206.2, -207.1,-207.2, -208.1,-208.2] matsets: mat: material_map: @@ -350,8 +350,8 @@ struct test_mergemeshes association: "element" topology: "mesh" matset: "mat" - values: [100.1, 101.1, 102.1, 103.25, 104.25, 105.25, 200.1, 201.15, 202.3, 203.15, 204.1, 205.15, 206.2, 207.15, 208.15] - matset_values: [100.1, 101.1, 102.1, 103.2, 103.3, 104.2, 104.3, 105.2, 105.3, 200.1, 201.1, 201.2, 202.3, 203.1, 203.2, 204.1, 205.1, 205.2, 206.2, 207.1, 207.2, 208.1, 208.2] + values: [100.1, 101.1, 102.1, 103.25, 104.25, 105.25, -200.1, -201.15, -202.3, -203.15, -204.1, -205.15, -206.2, -207.15, -208.15] + matset_values: [100.1, 101.1, 102.1, 103.2, 103.3, 104.2, 104.3, 105.2, 105.3, -200.1, -201.1, -201.2, -202.3, -203.1, -203.2, -204.1, -205.1, -205.2, -206.2, -207.1, -207.2, -208.1, -208.2] )xx"; // Result for matflags=2 - domain 0 lacked the material and domain so we get default values where domain 0's data would be. @@ -400,8 +400,8 @@ struct test_mergemeshes association: "element" topology: "mesh" matset: "mat" - values: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 200.1, 201.15, 202.3, 203.15, 204.1, 205.15, 206.2, 207.15, 208.15] - matset_values: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 200.1, 201.1, 201.2, 202.3, 203.1, 203.2, 204.1, 205.1, 205.2, 206.2, 207.1, 207.2, 208.1, 208.2] + values: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -200.1, -201.15, -202.3, -203.15, -204.1, -205.15, -206.2, -207.15, -208.15] + matset_values: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -200.1, -201.1, -201.2, -202.3, -203.1, -203.2, -204.1, -205.1, -205.2, -206.2, -207.1, -207.2, -208.1, -208.2] )xx"; // Result for matflags=1 - domain 1 lacked the material and domain so we get default values where domain 1's data would be. diff --git a/src/axom/bump/tests/bump_views.cpp b/src/axom/bump/tests/bump_views.cpp index befe360e92..5c1af8da6d 100644 --- a/src/axom/bump/tests/bump_views.cpp +++ b/src/axom/bump/tests/bump_views.cpp @@ -586,6 +586,8 @@ TEST(bump_views, strided_structured_seq) { test_strided_structured::test(); } template struct test_braid2d_mat { + struct NoMixedFields {}; + static void test(const std::string &type, const std::string &mattype, const std::string &name) { namespace utils = axom::bump::utilities; @@ -624,14 +626,16 @@ struct test_braid2d_mat // clang-format on SLIC_INFO("unibuffer: matsetView"); test_matsetview(nzones, matsetView, allocatorID); + test_matsetview_iterators(nzones, matsetView, NoMixedFields {}, allocatorID); // Test mixed field. - const auto mixedFieldView = - axom::bump::views::make_unibuffer_matset::mixedFieldView( - deviceMesh["matsets/mat"], - deviceMesh["fields/mixed"]); - SLIC_INFO("unibuffer: mixedFieldView"); - test_matsetview(nzones, mixedFieldView, allocatorID); + axom::bump::views::dispatch_material_unibuffer_field( + deviceMesh["matsets/mat"], + deviceMesh["fields/mixed"], + [&](auto matsetView, auto mixedFieldView) { + SLIC_INFO("element_dominant: mixedFieldView"); + test_matsetview_iterators(nzones, matsetView, mixedFieldView, allocatorID); + }); } else if(mattype == "element_dominant") { @@ -640,15 +644,16 @@ struct test_braid2d_mat [&](auto matsetView) { SLIC_INFO("element_dominant: matsetView"); test_matsetview(nzones, matsetView, allocatorID); + test_matsetview_iterators(nzones, matsetView, NoMixedFields {}, allocatorID); }); // Test mixed field. axom::bump::views::dispatch_material_element_dominant_field( deviceMesh["matsets/mat"], deviceMesh["fields/mixed"], - [&](auto mixedFieldView) { + [&](auto matsetView, auto mixedFieldView) { SLIC_INFO("element_dominant: mixedFieldView"); - test_matsetview(nzones, mixedFieldView, allocatorID); + test_matsetview_iterators(nzones, matsetView, mixedFieldView, allocatorID); }); } else if(mattype == "material_dominant") @@ -658,15 +663,16 @@ struct test_braid2d_mat [&](auto matsetView) { SLIC_INFO("material_dominant: matsetView"); test_matsetview(nzones, matsetView, allocatorID); + test_matsetview_iterators(nzones, matsetView, NoMixedFields {}, allocatorID); }); // Test mixed field. axom::bump::views::dispatch_material_material_dominant_field( deviceMesh["matsets/mat"], deviceMesh["fields/mixed"], - [&](auto mixedFieldView) { + [&](auto matsetView, auto mixedFieldView) { SLIC_INFO("material_dominant: mixedFieldView"); - test_matsetview(nzones, mixedFieldView, allocatorID); + test_matsetview_iterators(nzones, matsetView, mixedFieldView, allocatorID); }); } } @@ -736,13 +742,10 @@ struct test_braid2d_mat { EXPECT_EQ(results[i], resultsHost[i]); } - - // Test iterators. - test_matsetview_iterators(nzones, matsetView, allocatorID); } - template - static void test_matsetview_iterators(axom::IndexType nzones, MatsetView matsetView, int allocatorID) + template + static void test_matsetview_iterators(axom::IndexType nzones, MatsetView matsetView, MatsetFieldView fieldView, int allocatorID) { using ZoneIndex = typename MatsetView::ZoneIndex; // Allocate results array on device. @@ -781,6 +784,20 @@ struct test_braid2d_mat count++; } + // If we passed in a mixed field view, make sure its field contains the same + // values as the volume fractions. That is how the dataset's fields are + // constructed. + if constexpr(!std::is_same_v) + { + int i = 0; + for(auto it = matsetView.beginZone(index); it != end; it++, i++) + { + const auto value = fieldView.value(it); + eq_count += (value == it.volume_fraction()) ? 1 : 0; + count++; + } + } + // Test ArrayView version of zoneMaterials(). using IndexType = typename MatsetView::IndexType; using FloatType = typename MatsetView::FloatType; diff --git a/src/axom/bump/views/MaterialView.hpp b/src/axom/bump/views/MaterialView.hpp index c340ae04b9..2896562b91 100644 --- a/src/axom/bump/views/MaterialView.hpp +++ b/src/axom/bump/views/MaterialView.hpp @@ -45,6 +45,17 @@ using MaterialInformation = std::vector; */ MaterialInformation materials(const conduit::Node &matset); +/*! + * \brief This struct can encode some positional information about the material + * view const_iterators. + */ +struct IteratorIndex +{ + axom::IndexType m_zoneIndex {0}; //!< Which zone we're working on + axom::IndexType m_bufferIndex {0}; //!< Which buffer we're working on + axom::IndexType m_localIndex {0}; //!< The element in the buffer we're working in +}; + //--------------------------------------------------------------------------- // Material views - These objects are meant to wrap Blueprint Matsets behind // an interface that lets us query materials for a single @@ -222,7 +233,10 @@ class UnibufferMaterialView return m_currentIndex != rhs.m_currentIndex || m_zoneIndex != rhs.m_zoneIndex || m_view != rhs.m_view; } - + IteratorIndex AXOM_HOST_DEVICE index() const + { + return IteratorIndex{static_cast(m_zoneIndex), 0, m_index}; + } private: DISABLE_DEFAULT_CTOR(const_iterator); @@ -476,6 +490,10 @@ class ElementDominantMaterialView return m_currentIndex != rhs.m_currentIndex || m_zoneIndex != rhs.m_zoneIndex || m_view != rhs.m_view; } + IteratorIndex AXOM_HOST_DEVICE index() const + { + return IteratorIndex{static_cast(m_zoneIndex), m_currentIndex, m_zoneIndex}; + } private: DISABLE_DEFAULT_CTOR(const_iterator); @@ -788,6 +806,10 @@ class MaterialDominantMaterialView return m_miIndex != rhs.m_miIndex || m_index != rhs.m_index || m_zoneIndex != rhs.m_zoneIndex || m_view != rhs.m_view; } + IteratorIndex AXOM_HOST_DEVICE index() const + { + return IteratorIndex{static_cast(m_zoneIndex), m_miIndex, m_index}; + } private: DISABLE_DEFAULT_CTOR(const_iterator); diff --git a/src/axom/bump/views/MixedFieldView.hpp b/src/axom/bump/views/MixedFieldView.hpp new file mode 100644 index 0000000000..10916c7763 --- /dev/null +++ b/src/axom/bump/views/MixedFieldView.hpp @@ -0,0 +1,164 @@ +// 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_VIEWS_MIXED_FIELD_VIEW_HPP_ +#define AXOM_BUMP_VIEWS_MIXED_FIELD_VIEW_HPP_ + +#include "axom/core/ArrayView.hpp" +#include "axom/core/StaticArray.hpp" +#include "axom/bump/views/MaterialView.hpp" + +namespace axom +{ +namespace bump +{ +namespace views +{ + +// Base template for MixedFieldTraits. These traits are used in conjunction with the +// MixedFieldView to access MatsetView-specific field data. +template +struct MixedFieldTraits +{ +}; + +/*! + * \brief Specialization for UnibufferMaterialView that can store field data organized + * in a compatible manner. + */ +template +struct MixedFieldTraits, FieldT> +{ + using MatsetView = UnibufferMaterialView; + using ValueView = axom::ArrayView; + + /*! + * \brief Set the field values view. + * \param values The field values. + */ + void set(ValueView values) + { + m_values = values; + } + + /*! + * \brief Return the field value for the iterator index. + * + * \param index The IteratorIndex that provides the indices to use for looking up the value. + * + * \return The field value at the provided index. + */ + AXOM_HOST_DEVICE FieldT value(const IteratorIndex &index) const + { + SLIC_ASSERT(axom::utilities::inBounds_0_N(index.m_localIndex, m_values.size())); + return m_values[index.m_localIndex]; + } + + ValueView m_values; +}; + +/*! + * \brief Specialization for ElementDominantMaterialView that can store field data organized + * in a compatible manner. + */ +template +struct MixedFieldTraits, FieldT> +{ + using MatsetView = ElementDominantMaterialView; + using ValueView = axom::ArrayView; + + /*! + * \brief Add the values to the list of buffers. + * \param values The values to be added. + */ + void add(ValueView values) + { + m_values.push_back(values); + } + + /*! + * \brief Return the field value for the iterator index. + * + * \param index The IteratorIndex that provides the indices to use for looking up the value. + * + * \return The field value at the provided index. + */ + AXOM_HOST_DEVICE FieldT value(const IteratorIndex &index) const + { + SLIC_ASSERT(axom::utilities::inBounds_0_N(index.m_bufferIndex, m_values.size())); + SLIC_ASSERT(axom::utilities::inBounds_0_N(index.m_localIndex, m_values[index.m_bufferIndex].size())); + return m_values[index.m_bufferIndex][index.m_localIndex]; + } + + axom::StaticArray m_values; +}; + +/*! + * \brief Specialization for MaterialDominantMaterialView that can store field data organized + * in a compatible manner. + */ +template +struct MixedFieldTraits, FieldT> +{ + using MatsetView = MaterialDominantMaterialView; + using ValueView = axom::ArrayView; + + /*! + * \brief Add the values to the list of buffers. + * \param values The values to be added. + */ + void add(ValueView values) + { + m_values.push_back(values); + } + + /*! + * \brief Return the field value for the iterator index. + * + * \param index The IteratorIndex that provides the indices to use for looking up the value. + * + * \return The field value at the provided index. + */ + AXOM_HOST_DEVICE FieldT value(const IteratorIndex &index) const + { + SLIC_ASSERT(axom::utilities::inBounds_0_N(index.m_bufferIndex, m_values.size())); + SLIC_ASSERT(axom::utilities::inBounds_0_N(index.m_localIndex, m_values[index.m_bufferIndex].size())); + return m_values[index.m_bufferIndex][index.m_localIndex]; + } + + axom::StaticArray m_values; +}; + +/*! + * \param This view enables the user to traverse Blueprint mixed field data using + * data from a MatsetView const_iterator. + */ +template +class MixedFieldView +{ +public: + using Traits = MixedFieldTraits; + + Traits &traits() { return m_traits; } + const Traits &traits() const { return m_traits; } + + /*! + * \brief Given a MatsetView's const_iterator, use it to look up the typed field + * data in the field. + */ + AXOM_HOST_DEVICE FieldT value(const typename MatsetView::const_iterator &it) const + { + return m_traits.value(it.index()); + } + +private: + Traits m_traits; +}; + +} // end namespace views +} // end namespace bump +} // end namespace axom +#endif diff --git a/src/axom/bump/views/dispatch_material.hpp b/src/axom/bump/views/dispatch_material.hpp index 5cf5605f40..5a16ea64c4 100644 --- a/src/axom/bump/views/dispatch_material.hpp +++ b/src/axom/bump/views/dispatch_material.hpp @@ -35,7 +35,7 @@ inline void verifyMixedField(const conduit::Node &n_field) * \tparam FuncType The function/lambda type that will take the matset. * * \param matset The node that contains the matset. - * \param values The node that contains the values to be used as volume fractions / field. + * \param values The node that contains the values to be used as volume fractions. * \param func The function/lambda that will operate on the matset view. * * \return true if the dispatch worked, false otherwise. @@ -101,7 +101,7 @@ IntElement getMaterialID(const conduit::Node &matset, * \tparam FuncType The function/lambda type that will take the matset. * * \param matset The node that contains the matset. - * \param values_object The node that contains the values object to be used as volume fractions / field. + * \param values_object The node that contains the values object to be used as volume fractions. * \param func The function/lambda that will operate on the matset view. * * \return true if the dispatch worked, false otherwise. @@ -155,7 +155,7 @@ bool dispatch_material_element_dominant_with_values(const conduit::Node &matset, * \tparam FuncType The function/lambda type that will take the matset. * * \param matset The node that contains the matset. - * \param values_object The node that contains the values to use as volume fractions / field values and indices. + * \param values_object The node that contains the values to use as volume fractions and indices. * \param func The function/lambda that will operate on the matset view. * * \return true if the dispatch worked, false otherwise. @@ -246,32 +246,6 @@ struct make_unibuffer_matset utils::make_array_view(n_matset["indices"])); return m; } - - /*! - * \brief Wrap the Conduit matset and field nodes as a unibuffer matset view - * so we can traverse the field using material machinery. The field - * data will be accessible as the volume component in the matset view. - * - * \param n_matset The Conduit node that contains the matset. - * \param n_field The Conduit node that contains the mixed field; its format - * must match that of the matset. - * - * \return A UnibufferMaterialView. - */ - static MatsetView mixedFieldView(const conduit::Node &n_matset, const conduit::Node &n_field) - { - namespace utils = axom::bump::utilities; - verify(n_matset, "matset"); - detail::verifyMixedField(n_field); - // NOTE: further field length and type checking happens in the MatsetView. - MatsetView m; - m.set(utils::make_array_view(n_matset["material_ids"]), - utils::make_array_view(n_field["matset_values"]), - utils::make_array_view(n_matset["sizes"]), - utils::make_array_view(n_matset["offsets"]), - utils::make_array_view(n_matset["indices"])); - return m; - } }; /*! diff --git a/src/axom/bump/views/dispatch_material_field.hpp b/src/axom/bump/views/dispatch_material_field.hpp index e1edc2bb2b..b32b304f50 100644 --- a/src/axom/bump/views/dispatch_material_field.hpp +++ b/src/axom/bump/views/dispatch_material_field.hpp @@ -7,6 +7,7 @@ #ifndef AXOM_BUMP_DISPATCH_MATERIAL_FIELD_HPP_ #define AXOM_BUMP_DISPATCH_MATERIAL_FIELD_HPP_ #include "axom/bump/views/dispatch_material.hpp" +#include "axom/bump/views/MixedFieldView.hpp" namespace axom { @@ -14,16 +15,75 @@ namespace bump { namespace views { +namespace detail +{ +/*! + * \brief Dispatch a unibuffer matset_values field. + * + * \tparam MatsetView The type of matset view associated with the field. This has + * implications for the field storage. + * \tparam FuncType The function/lambda type to call on the MixedFieldView. + */ +template +bool dispatch_unibuffer_field(const conduit::Node &n_field, FuncType &&func) +{ + bool rv = false; + detail::verifyMixedField(n_field); + const conduit::Node &matset_values = n_field["matset_values"]; + SLIC_ERROR_IF(!matset_values.dtype().is_number(), "The matset_values must be a number."); + // NOTE: For now support float, double types. + axom::bump::views::floatNodeToArrayView(matset_values, [&](auto valuesView) + { + using FieldT = typename decltype(valuesView)::value_type; + MixedFieldView mixedFieldView; + mixedFieldView.traits().set(valuesView); + func(mixedFieldView); + rv = true; + }); + return rv; +} + +/*! + * \brief Dispatch a multibuffer matset_values field. + * + * \tparam MatsetView The type of matset view associated with the field. This has + * implications for the field storage. + * \tparam FuncType The function/lambda type to call on the MixedFieldView. + */ +template +bool dispatch_multibuffer_field(const conduit::Node &n_field, FuncType &&func) +{ + bool rv = false; + detail::verifyMixedField(n_field); + const conduit::Node &matset_values = n_field["matset_values"]; + SLIC_ERROR_IF(matset_values.number_of_children() <= 1, "Missing fields in matset_values."); + // NOTE: For now support float, double types. + axom::bump::views::floatNodeToArrayView(matset_values[0], [&](auto firstValuesView) + { + using FieldT = typename decltype(firstValuesView)::value_type; + MixedFieldView mixedFieldView; + mixedFieldView.traits().add(firstValuesView); + for(conduit::index_t i = 1; i < matset_values.number_of_children(); i++) + { + mixedFieldView.traits().add(axom::bump::utilities::make_array_view(matset_values[i])); + } + func(mixedFieldView); + rv = true; + }); + return rv; +} + +} // end namespace detail /*! * \brief Dispatch Conduit nodes containing a unibuffer matset and a values array * to a function as the appropriate type of matset view. * - * \tparam FuncType The function/lambda type that will take the matset. + * \tparam FuncType The function/lambda type that will take the matset and mixed field view. * * \param matset The node that contains the matset. * \param n_field The node that contains the values to be used as volume fractions / field. - * \param func The function/lambda that will operate on the matset view. + * \param func The function/lambda that will operate on the matset and mixed field views. */ template bool dispatch_material_unibuffer_field(const conduit::Node &matset, @@ -32,56 +92,78 @@ bool dispatch_material_unibuffer_field(const conduit::Node &matset, { verify(matset, "matset"); detail::verifyMixedField(n_field); - return detail::dispatch_material_unibuffer_with_values( - matset, - n_field["matset_values"], - std::forward(func)); + + auto handleMatset = [&](auto matsetView) + { + using MatsetView = decltype(matsetView); + detail::dispatch_unibuffer_field(n_field, [&](auto mixedFieldView) + { + func(matsetView, mixedFieldView); + }); + }; + + return dispatch_material_unibuffer( + matset, std::forward(handleMatset)); } /*! * \brief Dispatch Conduit nodes containing a element-dominant matset and related field * to a function as the appropriate type of matset view. * - * \tparam FuncType The function/lambda type that will take the matset. + * \tparam FuncType The function/lambda type that will take the matset and mixed field view. * * \param matset The node that contains the matset. * \param n_field The node that contains the values to be used as volume fractions / field. - * \param func The function/lambda that will operate on the matset view. + * \param func The function/lambda that will operate on the matset and mixed field views. */ template bool dispatch_material_element_dominant_field(const conduit::Node &matset, - const conduit::Node n_field, + const conduit::Node &n_field, FuncType &&func) { verify(matset, "matset"); detail::verifyMixedField(n_field); - return detail::dispatch_material_element_dominant_with_values( - matset, - n_field["matset_values"], - std::forward(func)); + auto handleMatset = [&](auto matsetView) + { + using MatsetView = decltype(matsetView); + detail::dispatch_multibuffer_field(n_field, [&](auto mixedFieldView) + { + func(matsetView, mixedFieldView); + }); + }; + + return dispatch_material_element_dominant( + matset, std::forward(handleMatset)); } /*! * \brief Dispatch Conduit nodes containing a material-dominant matset and related field * to a function as the appropriate type of matset view. * - * \tparam FuncType The function/lambda type that will take the matset. + * \tparam FuncType The function/lambda type that will take the matset and mixed field view. * * \param matset The node that contains the matset. * \param n_field The node that contains the values to be used as volume fractions / field. - * \param func The function/lambda that will operate on the matset view. + * \param func The function/lambda that will operate on the matset and mixed field views. */ template bool dispatch_material_material_dominant_field(const conduit::Node &matset, - const conduit::Node n_field, + const conduit::Node &n_field, FuncType &&func) { verify(matset, "matset"); detail::verifyMixedField(n_field); - return detail::dispatch_material_material_dominant_with_values( - matset, - n_field["matset_values"], - std::forward(func)); + auto handleMatset = [&](auto matsetView) + { + using MatsetView = decltype(matsetView); + detail::dispatch_multibuffer_field(n_field, [&](auto mixedFieldView) + { + func(matsetView, mixedFieldView); + }); + }; + + return dispatch_material_material_dominant( + matset, std::forward(handleMatset)); } /*! @@ -89,11 +171,11 @@ bool dispatch_material_material_dominant_field(const conduit::Node &matset, * to a function as the appropriate type of matset view. The matset will be used * to access the per-material field data. * - * \tparam FuncType The function/lambda type that will take the matset. + * \tparam FuncType The function/lambda type that will take the matset and mixed field view. * * \param matset The node that contains the matset. * \param n_field The node that contains the values to be used as volume fractions / field. - * \param func The function/lambda that will operate on the matset view. + * \param func The function/lambda that will operate on the matset and mixed field views. */ template bool dispatch_material_field(const conduit::Node &matset, const conduit::Node &n_field, FuncType &&func) From 294f3bbb49ca349dffa058a90a5018535b69572d Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 14 May 2026 17:53:09 -0700 Subject: [PATCH 252/986] make style --- src/axom/bump/MergeMeshes.hpp | 27 +++++++++----- src/axom/bump/tests/bump_views.cpp | 8 +++- src/axom/bump/views/MaterialView.hpp | 13 ++++--- .../bump/views/dispatch_material_field.hpp | 37 ++++++++----------- 4 files changed, 47 insertions(+), 38 deletions(-) diff --git a/src/axom/bump/MergeMeshes.hpp b/src/axom/bump/MergeMeshes.hpp index 462ee2b635..98b3c2f7f4 100644 --- a/src/axom/bump/MergeMeshes.hpp +++ b/src/axom/bump/MergeMeshes.hpp @@ -1563,9 +1563,10 @@ class DispatchAnyMatset template void dispatchMixedField(conduit::Node &n_matset, conduit::Node &n_field, FuncType &&func) { - axom::bump::views::dispatch_material_field(n_matset, n_field, [&](auto matsetView, auto mixedFieldView) { - func(matsetView, mixedFieldView); - }); + axom::bump::views::dispatch_material_field( + n_matset, + n_field, + [&](auto matsetView, auto mixedFieldView) { func(matsetView, mixedFieldView); }); } }; @@ -1637,9 +1638,10 @@ class DispatchTypedUnibufferMatset template void dispatchMixedField(conduit::Node &n_matset, conduit::Node &n_field, FuncType &&func) { - axom::bump::views::dispatch_material_unibuffer_field(n_matset, n_field, [&](auto matsetView, auto mixedFieldView) { - func(matsetView, mixedFieldView); - }); + axom::bump::views::dispatch_material_unibuffer_field( + n_matset, + n_field, + [&](auto matsetView, auto mixedFieldView) { func(matsetView, mixedFieldView); }); } }; @@ -2269,9 +2271,16 @@ class MergeMeshesAndMatsets : public MergeMeshes // Get the source field. conduit::Node &n_src_field = inputs[i].m_input->fetch_existing(srcFieldPath); // Dispatch the source mixed field. - disp.dispatchMixedField(n_matset, n_src_field, [&](auto srcMatsetView, auto srcMixedFieldView) { - This->mergeMixedField_copy(srcMatsetView, srcMixedFieldView, matsetValuesView, offsetsView, nzones, zOffset); - }); + disp.dispatchMixedField(n_matset, + n_src_field, + [&](auto srcMatsetView, auto srcMixedFieldView) { + This->mergeMixedField_copy(srcMatsetView, + srcMixedFieldView, + matsetValuesView, + offsetsView, + nzones, + zOffset); + }); } else { diff --git a/src/axom/bump/tests/bump_views.cpp b/src/axom/bump/tests/bump_views.cpp index 5c1af8da6d..1ef194f854 100644 --- a/src/axom/bump/tests/bump_views.cpp +++ b/src/axom/bump/tests/bump_views.cpp @@ -586,7 +586,8 @@ TEST(bump_views, strided_structured_seq) { test_strided_structured::test(); } template struct test_braid2d_mat { - struct NoMixedFields {}; + struct NoMixedFields + { }; static void test(const std::string &type, const std::string &mattype, const std::string &name) { @@ -745,7 +746,10 @@ struct test_braid2d_mat } template - static void test_matsetview_iterators(axom::IndexType nzones, MatsetView matsetView, MatsetFieldView fieldView, int allocatorID) + static void test_matsetview_iterators(axom::IndexType nzones, + MatsetView matsetView, + MatsetFieldView fieldView, + int allocatorID) { using ZoneIndex = typename MatsetView::ZoneIndex; // Allocate results array on device. diff --git a/src/axom/bump/views/MaterialView.hpp b/src/axom/bump/views/MaterialView.hpp index 2896562b91..dd3c74ee1d 100644 --- a/src/axom/bump/views/MaterialView.hpp +++ b/src/axom/bump/views/MaterialView.hpp @@ -51,9 +51,9 @@ MaterialInformation materials(const conduit::Node &matset); */ struct IteratorIndex { - axom::IndexType m_zoneIndex {0}; //!< Which zone we're working on - axom::IndexType m_bufferIndex {0}; //!< Which buffer we're working on - axom::IndexType m_localIndex {0}; //!< The element in the buffer we're working in + axom::IndexType m_zoneIndex {0}; //!< Which zone we're working on + axom::IndexType m_bufferIndex {0}; //!< Which buffer we're working on + axom::IndexType m_localIndex {0}; //!< The element in the buffer we're working in }; //--------------------------------------------------------------------------- @@ -235,8 +235,9 @@ class UnibufferMaterialView } IteratorIndex AXOM_HOST_DEVICE index() const { - return IteratorIndex{static_cast(m_zoneIndex), 0, m_index}; + return IteratorIndex {static_cast(m_zoneIndex), 0, m_index}; } + private: DISABLE_DEFAULT_CTOR(const_iterator); @@ -492,7 +493,7 @@ class ElementDominantMaterialView } IteratorIndex AXOM_HOST_DEVICE index() const { - return IteratorIndex{static_cast(m_zoneIndex), m_currentIndex, m_zoneIndex}; + return IteratorIndex {static_cast(m_zoneIndex), m_currentIndex, m_zoneIndex}; } private: @@ -808,7 +809,7 @@ class MaterialDominantMaterialView } IteratorIndex AXOM_HOST_DEVICE index() const { - return IteratorIndex{static_cast(m_zoneIndex), m_miIndex, m_index}; + return IteratorIndex {static_cast(m_zoneIndex), m_miIndex, m_index}; } private: diff --git a/src/axom/bump/views/dispatch_material_field.hpp b/src/axom/bump/views/dispatch_material_field.hpp index b32b304f50..f2d806c7e1 100644 --- a/src/axom/bump/views/dispatch_material_field.hpp +++ b/src/axom/bump/views/dispatch_material_field.hpp @@ -32,8 +32,7 @@ bool dispatch_unibuffer_field(const conduit::Node &n_field, FuncType &&func) const conduit::Node &matset_values = n_field["matset_values"]; SLIC_ERROR_IF(!matset_values.dtype().is_number(), "The matset_values must be a number."); // NOTE: For now support float, double types. - axom::bump::views::floatNodeToArrayView(matset_values, [&](auto valuesView) - { + axom::bump::views::floatNodeToArrayView(matset_values, [&](auto valuesView) { using FieldT = typename decltype(valuesView)::value_type; MixedFieldView mixedFieldView; mixedFieldView.traits().set(valuesView); @@ -58,8 +57,7 @@ bool dispatch_multibuffer_field(const conduit::Node &n_field, FuncType &&func) const conduit::Node &matset_values = n_field["matset_values"]; SLIC_ERROR_IF(matset_values.number_of_children() <= 1, "Missing fields in matset_values."); // NOTE: For now support float, double types. - axom::bump::views::floatNodeToArrayView(matset_values[0], [&](auto firstValuesView) - { + axom::bump::views::floatNodeToArrayView(matset_values[0], [&](auto firstValuesView) { using FieldT = typename decltype(firstValuesView)::value_type; MixedFieldView mixedFieldView; mixedFieldView.traits().add(firstValuesView); @@ -73,7 +71,7 @@ bool dispatch_multibuffer_field(const conduit::Node &n_field, FuncType &&func) return rv; } -} // end namespace detail +} // end namespace detail /*! * \brief Dispatch Conduit nodes containing a unibuffer matset and a values array @@ -93,17 +91,16 @@ bool dispatch_material_unibuffer_field(const conduit::Node &matset, verify(matset, "matset"); detail::verifyMixedField(n_field); - auto handleMatset = [&](auto matsetView) - { + auto handleMatset = [&](auto matsetView) { using MatsetView = decltype(matsetView); - detail::dispatch_unibuffer_field(n_field, [&](auto mixedFieldView) - { + detail::dispatch_unibuffer_field(n_field, [&](auto mixedFieldView) { func(matsetView, mixedFieldView); }); }; - + return dispatch_material_unibuffer( - matset, std::forward(handleMatset)); + matset, + std::forward(handleMatset)); } /*! @@ -123,17 +120,16 @@ bool dispatch_material_element_dominant_field(const conduit::Node &matset, { verify(matset, "matset"); detail::verifyMixedField(n_field); - auto handleMatset = [&](auto matsetView) - { + auto handleMatset = [&](auto matsetView) { using MatsetView = decltype(matsetView); - detail::dispatch_multibuffer_field(n_field, [&](auto mixedFieldView) - { + detail::dispatch_multibuffer_field(n_field, [&](auto mixedFieldView) { func(matsetView, mixedFieldView); }); }; return dispatch_material_element_dominant( - matset, std::forward(handleMatset)); + matset, + std::forward(handleMatset)); } /*! @@ -153,17 +149,16 @@ bool dispatch_material_material_dominant_field(const conduit::Node &matset, { verify(matset, "matset"); detail::verifyMixedField(n_field); - auto handleMatset = [&](auto matsetView) - { + auto handleMatset = [&](auto matsetView) { using MatsetView = decltype(matsetView); - detail::dispatch_multibuffer_field(n_field, [&](auto mixedFieldView) - { + detail::dispatch_multibuffer_field(n_field, [&](auto mixedFieldView) { func(matsetView, mixedFieldView); }); }; return dispatch_material_material_dominant( - matset, std::forward(handleMatset)); + matset, + std::forward(handleMatset)); } /*! From 6f8c960e4eb957c60188be7a6cc6fc8cce1301db Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 14 May 2026 17:58:53 -0700 Subject: [PATCH 253/986] Adjusted docs. --- RELEASE-NOTES.md | 2 +- src/axom/bump/docs/sphinx/bump_views.rst | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 75e3e472cf..6d54d0cb7b 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -28,7 +28,7 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ - Quest: Adds OMP support for fast GWN methods for STL/Triangulated STEP input and linearized NURBS Curve input. - Klee: Adds an optional "center" parameter in scale operators that permits scaling relative to a custom center point. - Bump: The `MergeMeshes` class was enhanced so it supports material-dependent/mixed Blueprint fields that are "element-associated". These fields contain per-material values for the materials in a zone. -- Bump: Added `axom::bump::views::dispatch_material_field()` function (and related functions) for creating a material view of a material-dependent or mixed field. +- Bump: Added `axom::bump::views::dispatch_material_field()` function (and related functions) for creating a material view and a material-dependent or mixed field view. ### Removed - Bump: Removed `axom::bump::views::MultiBufferMaterialView`, which was a view type for an obsolete flavor of Blueprint matset. diff --git a/src/axom/bump/docs/sphinx/bump_views.rst b/src/axom/bump/docs/sphinx/bump_views.rst index 0c42d9a2eb..7affff0113 100644 --- a/src/axom/bump/docs/sphinx/bump_views.rst +++ b/src/axom/bump/docs/sphinx/bump_views.rst @@ -177,12 +177,12 @@ materials for each zone. :end-before: _bump_views_matsetview_end :language: C++ -Mixed (or material-dependent) fields are also supported using BUMP's material views. This is done -because these fields' data are arranged in the same manner as their related matset, leading to -multiple representations. The material views are initialized normally from matset data, except -field values from the field's ``matset_values`` node are used instead of the matset's -``volume_fractions`` node. The ``axom::bump::views::dispatch_material_field()`` function can be -used to simplify handling mixed fields. +Mixed (or material-dependent) fields are supported using `axom::bump::views::MixedFieldView`. +Mixed field data are arranged in the same manner as their related matset, leading to +multiple representations. This class is used in conjunction with a material view with the material +view providing the iterators that are used for traversal and the mixed field view for accessing +the field values. The ``axom::bump::views::dispatch_material_field()`` function can be +used to simplify creating mixed field views. ---------- Dispatch From 280f8ceca1fa8d50de61c69a4249f9c3781f86f9 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Fri, 15 May 2026 08:20:32 -0700 Subject: [PATCH 254/986] Relax mpi4py requirement to only when axom+mpi --- .../thirdparty/SetupAxomThirdParty.cmake | 31 +++++++++++++++++-- src/tools/convert_sidre_protocol.py | 8 ++++- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/cmake/thirdparty/SetupAxomThirdParty.cmake b/src/cmake/thirdparty/SetupAxomThirdParty.cmake index 430e7ba938..213a64c470 100644 --- a/src/cmake/thirdparty/SetupAxomThirdParty.cmake +++ b/src/cmake/thirdparty/SetupAxomThirdParty.cmake @@ -340,6 +340,15 @@ if(EXISTS ${Python_EXECUTABLE}) OUTPUT_QUIET ERROR_QUIET ) + + # Check if python environment contains mpi4py + execute_process( + COMMAND "${CMAKE_COMMAND}" -E env + "${Python_EXECUTABLE}" -c "import mpi4py" + RESULT_VARIABLE MPI4PY_ENV_IMPORT_CODE + OUTPUT_QUIET + ERROR_QUIET + ) endif() # If python environment does not contain required modules, check if @@ -353,14 +362,30 @@ if((NOT PY_ENV_IMPORT_CODE EQUAL 0) OR NOT PY_NUMPY_DIR OR NOT PY_PYTEST_DIR OR NOT PY_PLUGGY_DIR - OR NOT PY_INICONFIG_DIR - OR NOT PY_MPI4PY_DIR)) + OR NOT PY_INICONFIG_DIR)) message(FATAL_ERROR "Axom's python extensions require nanobind, numpy, pytest, and conduit." "\nThe python library installation paths " "(and pytest's dependencies pluggy and iniconfig) " "can be specified with CMake variables: " - "PY_NANOBIND_DIR, CONDUIT_PYTHON_MODULE_DIR, PY_NUMPY_DIR, PY_PYTEST_DIR, PY_PLUGGY_DIR, PY_INICONFIG_DIR, PY_MPI4PY_DIR ") + "PY_NANOBIND_DIR, CONDUIT_PYTHON_MODULE_DIR, PY_NUMPY_DIR, PY_PYTEST_DIR, PY_PLUGGY_DIR, PY_INICONFIG_DIR") +endif() + +# When Axom is configured with MPI, +# if python environment does not contain required mpi4py module, +# check if mpi4py library installation path was provided instead. +if(AXOM_ENABLE_MPI + AND + (NOT MPI4PY_ENV_IMPORT_CODE EQUAL 0) + AND + nanobind_ROOT + AND + (NOT PY_MPI4PY_DIR)) + message(FATAL_ERROR + "Axom's python extension requires mpi4py when Axom library is configured with MPI." + "\nThe mpi4py library installation paths " + "can be specified with CMake variable: " + "PY_MPI4PY_DIR") endif() # "cannot allocate memory in static TLS block" on blueos with cuda and/or clang. diff --git a/src/tools/convert_sidre_protocol.py b/src/tools/convert_sidre_protocol.py index 74bf5fb9bf..cbe8c21e97 100644 --- a/src/tools/convert_sidre_protocol.py +++ b/src/tools/convert_sidre_protocol.py @@ -29,7 +29,6 @@ from pathlib import Path import numpy as np -from mpi4py import MPI import pysidre VALID_PROTOCOLS = ( @@ -212,6 +211,13 @@ def main() -> int: if not pysidre.AXOM_ENABLE_MPI: raise RuntimeError("pysidre.IOManager bindings require an MPI-enabled Axom build") + try: + from mpi4py import MPI + except ImportError as exc: + raise RuntimeError( + "convert_sidre_protocol.py requires mpi4py when Axom is built with MPI support", + ) from exc + initialized_mpi = False if not MPI.Is_initialized(): MPI.Init() From bbc391b3ae568067db749e46555c51f143c7b8e8 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Fri, 15 May 2026 10:23:01 -0700 Subject: [PATCH 255/986] Adjust MPI guards for unit test and spack accordingly --- scripts/spack/packages/axom/package.py | 5 +---- src/tools/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/scripts/spack/packages/axom/package.py b/scripts/spack/packages/axom/package.py index 55e6b0df94..01acb0f81f 100644 --- a/scripts/spack/packages/axom/package.py +++ b/scripts/spack/packages/axom/package.py @@ -282,7 +282,7 @@ class Axom(CachedCMakePackage, CudaPackage, ROCmPackage): depends_on("py-nanobind@2.7.0") depends_on("py-pytest") depends_on("py-numpy") - depends_on("py-mpi4py") + depends_on("py-mpi4py", when="+mpi") depends_on("conduit+python") # Devtools @@ -346,9 +346,6 @@ class Axom(CachedCMakePackage, CudaPackage, ROCmPackage): conflicts("^blt@:0.3.6", when="+rocm") - # python interface requires mpi - conflicts("~mpi", when="+python") - def flag_handler(self, name, flags): if self.spec.satisfies("%cce") and name == "fflags": flags.append("-ef") diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index 795214c79f..6aadb77a9b 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -226,7 +226,7 @@ if(NANOBIND_FOUND) axom_configure_file ("${CMAKE_CURRENT_SOURCE_DIR}/convert_sidre_protocol.py" "${CMAKE_INSTALL_PREFIX}/bin/convert_sidre_protocol.py" COPYONLY) - if(AXOM_ENABLE_TESTS AND AXOM_DATA_DIR) + if(AXOM_ENABLE_MPI AND AXOM_ENABLE_TESTS AND AXOM_DATA_DIR) set(box_dir "${AXOM_DATA_DIR}/quest/box_2D_r3.root") From 3cff5dbb7b69a6346cff8c11c089547e7f1b14ab Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Fri, 15 May 2026 10:54:39 -0700 Subject: [PATCH 256/986] make style with clang host-config --- src/axom/bump/views/MixedFieldView.hpp | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/src/axom/bump/views/MixedFieldView.hpp b/src/axom/bump/views/MixedFieldView.hpp index 10916c7763..7e5f499b4a 100644 --- a/src/axom/bump/views/MixedFieldView.hpp +++ b/src/axom/bump/views/MixedFieldView.hpp @@ -22,8 +22,7 @@ namespace views // MixedFieldView to access MatsetView-specific field data. template struct MixedFieldTraits -{ -}; +{ }; /*! * \brief Specialization for UnibufferMaterialView that can store field data organized @@ -39,10 +38,7 @@ struct MixedFieldTraits, Fie * \brief Set the field values view. * \param values The field values. */ - void set(ValueView values) - { - m_values = values; - } + void set(ValueView values) { m_values = values; } /*! * \brief Return the field value for the iterator index. @@ -74,10 +70,7 @@ struct MixedFieldTraits Date: Fri, 15 May 2026 14:23:27 -0700 Subject: [PATCH 257/986] Fix lambdas to be host --- .../primal/operators/detail/winding_number_3d_memoization.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp b/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp index 856c1ab112..c68403fcd5 100644 --- a/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp +++ b/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp @@ -429,7 +429,7 @@ class NURBSPatchCacheManagerOMP nurbs_caches_view[0].resize(patches.size()); axom::for_all( patches.size(), - AXOM_LAMBDA(axom::IndexType i) { + AXOM_HOST_LAMBDA(axom::IndexType i) { nurbs_caches_view[0][i] = NURBSCache(patches[i], mustComputeNormal); }); @@ -439,7 +439,7 @@ class NURBSPatchCacheManagerOMP { axom::for_all( patches.size(), - AXOM_LAMBDA(axom::IndexType i) { + AXOM_HOST_LAMBDA(axom::IndexType i) { nurbs_caches_view[0][i].setNormal(precomputed_normals[i], precomputed_surface_areas[i]); }); } From e55bd93d3a8af30d5251250714eeb5ccdf4d7dad Mon Sep 17 00:00:00 2001 From: Brian Han Date: Fri, 15 May 2026 15:56:21 -0700 Subject: [PATCH 258/986] Use actual iterable object, add verbose output for rank/file behavior --- src/tools/convert_sidre_protocol.py | 52 ++++++++++++++++++----------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/src/tools/convert_sidre_protocol.py b/src/tools/convert_sidre_protocol.py index cbe8c21e97..0a36242039 100644 --- a/src/tools/convert_sidre_protocol.py +++ b/src/tools/convert_sidre_protocol.py @@ -79,20 +79,6 @@ def parse_args() -> argparse.Namespace: return parser.parse_args() -def iter_views(group: pysidre.Group): - idx = group.getFirstValidViewIndex() - while pysidre.indexIsValid(idx): - yield group.getView(idx) - idx = group.getNextValidViewIndex(idx) - - -def iter_groups(group: pysidre.Group): - idx = group.getFirstValidGroupIndex() - while pysidre.indexIsValid(idx): - yield group.getGroup(idx) - idx = group.getNextValidGroupIndex(idx) - - # # Allocate storage for external data of the input datastore. # @@ -104,7 +90,7 @@ def iter_groups(group: pysidre.Group): # def allocate_external_data(group: pysidre.Group, holders: list[np.ndarray], verbose: bool) -> None: # for each view - for view in iter_views(group): + for view in group.views(): if view.isExternal(): if verbose: print( @@ -115,7 +101,7 @@ def allocate_external_data(group: pysidre.Group, holders: list[np.ndarray], verb holders.append(storage) # for each group - for child in iter_groups(group): + for child in group.groups(): allocate_external_data(child, holders, verbose) @@ -181,7 +167,7 @@ def modify_final_values( # def truncate_bulk_data(group: pysidre.Group, max_size: int, verbose: bool) -> None: # for each view - for view in iter_views(group): + for view in group.views(): is_array = view.hasBuffer() or view.isExternal() if is_array: @@ -201,7 +187,7 @@ def truncate_bulk_data(group: pysidre.Group, max_size: int, verbose: bool) -> No modify_final_values(view, original_size, retained_size) # for each group - for child in iter_groups(group): + for child in group.groups(): truncate_bulk_data(child, max_size, verbose) @@ -223,14 +209,42 @@ def main() -> int: MPI.Init() initialized_mpi = True + comm_size = MPI.COMM_WORLD.Get_size() + input_path = Path(args.input) manager = pysidre.IOManager() datastore = pysidre.DataStore() root = datastore.getRoot() + num_files = manager.getNumFilesFromRoot(str(input_path)) + num_groups = manager.getNumGroupsFromRoot(str(input_path)) + + print( + "Input datastore layout: " + f"{num_groups} rank group(s) across {num_files} file(s); " + f"running on {comm_size} MPI rank(s)", ) + + if comm_size != num_groups: + print( + "Warning: current MPI size does not match the input datastore rank count. " + "sidre_hdf5 supports some rank-count mismatches, but not every layout.", ) + if comm_size < num_groups and num_files != num_groups: + print( + "Warning: this run has fewer MPI ranks than the input datastore. " + "Sidre only supports that case for file-per-rank sidre_hdf5 datasets " + f"(number_of_files == number_of_trees, but here {num_files} != {num_groups}), " + "so IOManager.read() is expected to fail.", ) + elif comm_size < num_groups: + print( + "Warning: this run has fewer MPI ranks than the input datastore, but the " + "dataset is file-per-rank " + f"({num_files} file(s) for {num_groups} rank group(s)), so Sidre can load it. " + "In that reduced-rank case, one output rank may absorb data from multiple input " + "ranks, and the loaded hierarchy may be reshaped under " + "'rank_%07d/sidre_input' groups.", ) + print(f"Loading datastore from {input_path}") manager.read(root, str(input_path)) - num_files = manager.getNumFilesFromRoot(str(input_path)) print("Loading external data from datastore") external_holders: list[np.ndarray] = [] From 119acaf9da73ebbbb85bdf2cd4c7631e1bf54eda Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Fri, 15 May 2026 17:12:35 -0700 Subject: [PATCH 259/986] Revert a script change. --- scripts/github-actions/linux-build_and_test.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/github-actions/linux-build_and_test.sh b/scripts/github-actions/linux-build_and_test.sh index ba885420c7..a7a780ffe6 100755 --- a/scripts/github-actions/linux-build_and_test.sh +++ b/scripts/github-actions/linux-build_and_test.sh @@ -45,8 +45,7 @@ if [[ "$DO_BUILD" == "yes" ]] ; then fi echo "~~~~~~ RUNNING TESTS ~~~~~~~~" - #make CTEST_OUTPUT_ON_FAILURE=1 test ARGS='-T Test --output-on-failure -j$NUM_BUILD_PROCS' - make CTEST_OUTPUT_ON_FAILURE=1 test ARGS='-T Test --output-on-failure' + make CTEST_OUTPUT_ON_FAILURE=1 test ARGS='-T Test --output-on-failure -j$NUM_BUILD_PROCS' if [[ "${DO_BENCHMARKS}" == "yes" ]] ; then echo "~~~~~~ RUNNING BENCHMARKS ~~~~~~~~" From 91fd1250ab1571372890a907339bdc5c0e660533 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Fri, 15 May 2026 17:16:14 -0700 Subject: [PATCH 260/986] Try change to dane config only --- .gitlab/build_dane.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab/build_dane.yml b/.gitlab/build_dane.yml index e5a6f7b4d4..220fb0a20c 100644 --- a/.gitlab/build_dane.yml +++ b/.gitlab/build_dane.yml @@ -9,6 +9,7 @@ .on_dane: variables: SCHEDULER_PARAMETERS: "--reservation=ci --exclusive=user --deadline=now+1hour -N1 -t ${ALLOC_TIME}" + PSM2_KASSIST_MODE: auto tags: - batch - dane From e8144c39c104e21d5f76a05a652e292c0ddd1a5b Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Sat, 16 May 2026 18:47:26 -0700 Subject: [PATCH 261/986] Fix a typo --- src/axom/quest/GWNMethods.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/axom/quest/GWNMethods.hpp b/src/axom/quest/GWNMethods.hpp index ba2a2647f2..ccd0354948 100644 --- a/src/axom/quest/GWNMethods.hpp +++ b/src/axom/quest/GWNMethods.hpp @@ -134,7 +134,7 @@ void generate_gwn_query_mesh(mfem::DataCollection& dc, * \tparam ExecSpace The execution space for the algorithm. * \tparam ORDER If agglomeration is used, this is the order of the Taylor expansion. * - * \brief Preprocesses NURBSCurve geoemtry for GWN evaluation, + * \brief Preprocesses NURBSCurve geometry for GWN evaluation, * and performs the calculation on the DOFs of an input MFEM mesh. * * Possible evaluation modes are @@ -554,7 +554,7 @@ class PolylineGWNQuery * \tparam ExecSpace The execution space for the algorithm. * \tparam ORDER If agglomeration is used, this is the order of the Taylor expansion. * - * \brief Preprocesses NURBSPatch geoemtry for GWN evaluation, + * \brief Preprocesses NURBSPatch geometry for GWN evaluation, * and performs the calculation on the DOFs of an input MFEM mesh. * * Possible evaluation modes are From bc2bb2caec17945dac0b4ab7bf150e0816658be2 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Sun, 17 May 2026 15:25:04 -0700 Subject: [PATCH 262/986] Reduce randomness in ray-casting --- .../primal/operators/detail/winding_number_3d_memoization.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp b/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp index c68403fcd5..b7eed08dc5 100644 --- a/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp +++ b/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp @@ -282,7 +282,7 @@ class NURBSPatchGWNCache // Otherwise, pick a direction that is *mostly* in the direction of the average normal else { - m_castDirection = (m_normal.unitVector() + 0.1 * random_unit).unitVector(); + m_castDirection = (m_normal.unitVector() + 0.01 * random_unit).unitVector(); } } From 0b0c42ff1282a8b8d97b6c57b4893bd1f08b7ec4 Mon Sep 17 00:00:00 2001 From: Dylan Copeland Date: Mon, 18 May 2026 09:13:17 -0700 Subject: [PATCH 263/986] Adding intersection function for NURBSCurves in 2D, with a unit test. --- .../detail/intersect_bezier_impl.hpp | 43 +++++++++++++++++ src/axom/primal/operators/intersect.hpp | 21 ++++++++ src/axom/primal/tests/primal_nurbs_curve.cpp | 48 +++++++++++++++++++ 3 files changed, 112 insertions(+) diff --git a/src/axom/primal/operators/detail/intersect_bezier_impl.hpp b/src/axom/primal/operators/detail/intersect_bezier_impl.hpp index 8fa549fea1..279c67c50d 100644 --- a/src/axom/primal/operators/detail/intersect_bezier_impl.hpp +++ b/src/axom/primal/operators/detail/intersect_bezier_impl.hpp @@ -539,6 +539,49 @@ bool intersect_2d_circle_line(const Sphere &circ, return true; } +template +bool intersect_nurbscurves(const NURBSCurve& n1, + const NURBSCurve& n2, + axom::Array& p1, + axom::Array& p2, + double tol) +{ + // Decompose both NURBS curves into Bezier segments + const auto beziers1 = n1.extractBezier(); + const auto beziers2 = n2.extractBezier(); + + const axom::Array knots1 = n1.getKnots().getUniqueKnots(); + const axom::Array knots2 = n2.getKnots().getUniqueKnots(); + + bool foundIntersection = false; + + // Loop over all Bezier segment pairs (is there a better way?) + for (int i = 0; i < beziers1.size(); ++i) + { + for (int j = 0; j < beziers2.size(); ++j) + { + axom::Array u_local, v_local; + + // Intersect Bezier segment i of n1 with Bezier segment j of n2 + intersect(beziers1[i], beziers2[j], u_local, v_local, tol); + + foundIntersection |= !u_local.empty(); + + // Map local Bezier parameters back to full NURBS parameters + for (int k = 0; k < u_local.size(); ++k) + { + T u_full = axom::utilities::lerp(knots1[i], knots1[i + 1], u_local[k]); + T v_full = axom::utilities::lerp(knots2[j], knots2[j + 1], v_local[k]); + + p1.push_back(u_full); + p2.push_back(v_full); + } + } + } + + return foundIntersection; +} + } // end namespace detail } // end namespace primal } // end namespace axom diff --git a/src/axom/primal/operators/intersect.hpp b/src/axom/primal/operators/intersect.hpp index 84b0f946f9..476d10f47a 100644 --- a/src/axom/primal/operators/intersect.hpp +++ b/src/axom/primal/operators/intersect.hpp @@ -1457,6 +1457,27 @@ bool intersect(const Line& line, return intersect(line, patch, t, u, v, tol, EPS, countUntrimmed, isHalfOpen, success); } +/*! + * \brief Finds the intersection points for two NURBS curves in 2D. + * \param [in] n1 A 2D NURBSCurve. + * \param [in] n2 A 2D NURBSCurve. + * \param [out] p1 The array of parameters for intersection points in n1. + * \param [out] p2 The array of parameters for intersection points in n2. + * \param [in] tol Tolerance used in the segment pair intersection test. + * \return true iff n1 intersects with n2, otherwise, false. + * \note The number of new entries added to p1 and p2 is the number of + * intersections. + */ +template +bool intersect(const NURBSCurve& n1, + const NURBSCurve& n2, + axom::Array& p1, + axom::Array& p2, + double tol = 1.0E-8) +{ + return detail::intersect_nurbscurves(n1, n2, p1, p2, tol); +} + } // namespace primal } // namespace axom diff --git a/src/axom/primal/tests/primal_nurbs_curve.cpp b/src/axom/primal/tests/primal_nurbs_curve.cpp index 2fd06c3242..04f81f894a 100644 --- a/src/axom/primal/tests/primal_nurbs_curve.cpp +++ b/src/axom/primal/tests/primal_nurbs_curve.cpp @@ -14,6 +14,7 @@ #include "axom/slic.hpp" #include "axom/primal/geometry/NURBSCurve.hpp" +#include "axom/primal/operators/intersect.hpp" #include namespace primal = axom::primal; @@ -1200,6 +1201,53 @@ TEST(primal_nurbscurve, linear_segment_constructor) } } +//------------------------------------------------------------------------------ +TEST(primal_nurbscurve, nurbscurve_intersections) +{ + // Define two nurbs curves in 2D intersecting at one point. + constexpr int DIM = 2; + using CoordType = double; + using NURBSCurveType = primal::NURBSCurve; + using Point2D = primal::Point; + + constexpr int max_degree = 3; + + Point2D data1_2d[max_degree + 1] = {Point2D {0.6, 1.2}, + Point2D {1.3, 1.6}, + Point2D {2.9, 2.4}, + Point2D {3.2, 3.5}}; + + Point2D data2_2d[max_degree + 1] = {Point2D {0.5, 3.4}, + Point2D {1.2, 2.3}, + Point2D {2.8, 1.5}, + Point2D {3.1, 1.1}}; + + constexpr double weights[4] = {1.0, 2.0, 3.0, 4.0}; + + constexpr int degree = 3; + constexpr int npts = 4; + + NURBSCurveType curve1(data1_2d, weights, npts, degree); + NURBSCurveType curve2(data2_2d, weights, npts, degree); + + Point2D intersection1, intersection2; + + axom::Array p1, p2; + const bool found = intersect(curve1, curve2, p1, p2); + + const int num_intersections = p1.size(); + EXPECT_TRUE(found && num_intersections == 1 && num_intersections == p2.size()); + + for (int j=0; j Date: Mon, 18 May 2026 11:17:20 -0700 Subject: [PATCH 264/986] clang-format --- .../detail/intersect_bezier_impl.hpp | 42 +++++++++---------- src/axom/primal/tests/primal_nurbs_curve.cpp | 25 ++++++----- 2 files changed, 33 insertions(+), 34 deletions(-) diff --git a/src/axom/primal/operators/detail/intersect_bezier_impl.hpp b/src/axom/primal/operators/detail/intersect_bezier_impl.hpp index 279c67c50d..510367ca09 100644 --- a/src/axom/primal/operators/detail/intersect_bezier_impl.hpp +++ b/src/axom/primal/operators/detail/intersect_bezier_impl.hpp @@ -540,11 +540,11 @@ bool intersect_2d_circle_line(const Sphere &circ, } template -bool intersect_nurbscurves(const NURBSCurve& n1, - const NURBSCurve& n2, - axom::Array& p1, - axom::Array& p2, - double tol) +bool intersect_nurbscurves(const NURBSCurve &n1, + const NURBSCurve &n2, + axom::Array &p1, + axom::Array &p2, + double tol) { // Decompose both NURBS curves into Bezier segments const auto beziers1 = n1.extractBezier(); @@ -556,28 +556,28 @@ bool intersect_nurbscurves(const NURBSCurve& n1, bool foundIntersection = false; // Loop over all Bezier segment pairs (is there a better way?) - for (int i = 0; i < beziers1.size(); ++i) + for(int i = 0; i < beziers1.size(); ++i) + { + for(int j = 0; j < beziers2.size(); ++j) { - for (int j = 0; j < beziers2.size(); ++j) - { - axom::Array u_local, v_local; + axom::Array u_local, v_local; - // Intersect Bezier segment i of n1 with Bezier segment j of n2 - intersect(beziers1[i], beziers2[j], u_local, v_local, tol); + // Intersect Bezier segment i of n1 with Bezier segment j of n2 + intersect(beziers1[i], beziers2[j], u_local, v_local, tol); - foundIntersection |= !u_local.empty(); + foundIntersection |= !u_local.empty(); - // Map local Bezier parameters back to full NURBS parameters - for (int k = 0; k < u_local.size(); ++k) - { - T u_full = axom::utilities::lerp(knots1[i], knots1[i + 1], u_local[k]); - T v_full = axom::utilities::lerp(knots2[j], knots2[j + 1], v_local[k]); + // Map local Bezier parameters back to full NURBS parameters + for(int k = 0; k < u_local.size(); ++k) + { + T u_full = axom::utilities::lerp(knots1[i], knots1[i + 1], u_local[k]); + T v_full = axom::utilities::lerp(knots2[j], knots2[j + 1], v_local[k]); - p1.push_back(u_full); - p2.push_back(v_full); - } - } + p1.push_back(u_full); + p2.push_back(v_full); + } } + } return foundIntersection; } diff --git a/src/axom/primal/tests/primal_nurbs_curve.cpp b/src/axom/primal/tests/primal_nurbs_curve.cpp index 04f81f894a..eda6adb57e 100644 --- a/src/axom/primal/tests/primal_nurbs_curve.cpp +++ b/src/axom/primal/tests/primal_nurbs_curve.cpp @@ -1213,14 +1213,14 @@ TEST(primal_nurbscurve, nurbscurve_intersections) constexpr int max_degree = 3; Point2D data1_2d[max_degree + 1] = {Point2D {0.6, 1.2}, - Point2D {1.3, 1.6}, - Point2D {2.9, 2.4}, - Point2D {3.2, 3.5}}; + Point2D {1.3, 1.6}, + Point2D {2.9, 2.4}, + Point2D {3.2, 3.5}}; Point2D data2_2d[max_degree + 1] = {Point2D {0.5, 3.4}, - Point2D {1.2, 2.3}, - Point2D {2.8, 1.5}, - Point2D {3.1, 1.1}}; + Point2D {1.2, 2.3}, + Point2D {2.8, 1.5}, + Point2D {3.1, 1.1}}; constexpr double weights[4] = {1.0, 2.0, 3.0, 4.0}; @@ -1238,14 +1238,13 @@ TEST(primal_nurbscurve, nurbscurve_intersections) const int num_intersections = p1.size(); EXPECT_TRUE(found && num_intersections == 1 && num_intersections == p2.size()); - for (int j=0; j Date: Mon, 18 May 2026 14:51:52 -0700 Subject: [PATCH 265/986] Undo dane change. --- .gitlab/build_dane.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitlab/build_dane.yml b/.gitlab/build_dane.yml index 220fb0a20c..e5a6f7b4d4 100644 --- a/.gitlab/build_dane.yml +++ b/.gitlab/build_dane.yml @@ -9,7 +9,6 @@ .on_dane: variables: SCHEDULER_PARAMETERS: "--reservation=ci --exclusive=user --deadline=now+1hour -N1 -t ${ALLOC_TIME}" - PSM2_KASSIST_MODE: auto tags: - batch - dane From 123e904fbd9b51626a55d273bf3ea2172b37fe4f Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 18 May 2026 16:56:02 -0700 Subject: [PATCH 266/986] Switch some shaping functions to an ArrayView interface. --- src/axom/quest/SamplingShaper.hpp | 105 +++++++++--------- .../quest/detail/shaping/InOutSampler.hpp | 4 +- .../quest/detail/shaping/PrimitiveSampler.hpp | 4 +- .../detail/shaping/WindingNumberSampler.hpp | 4 +- .../quest/detail/shaping/shaping_helpers.cpp | 34 ++++-- .../quest/detail/shaping/shaping_helpers.hpp | 29 ++++- src/axom/quest/examples/shaping_driver.cpp | 16 ++- .../quest/tests/quest_sampling_shaper.cpp | 16 +-- 8 files changed, 125 insertions(+), 87 deletions(-) diff --git a/src/axom/quest/SamplingShaper.hpp b/src/axom/quest/SamplingShaper.hpp index 4a7dbfd5db..6225746dea 100644 --- a/src/axom/quest/SamplingShaper.hpp +++ b/src/axom/quest/SamplingShaper.hpp @@ -135,7 +135,14 @@ class SamplingShaper : public Shaper const klee::ShapeSet& shapeSet, sidre::MFEMSidreDataCollection* dc) : Shaper(execPolicy, allocatorId, shapeSet, dc) - { } + { + // Initialize the default number of samples based on the mesh dimension. + const int dim = getMeshDimension(); + for(int d = 0; d < dim; d++) + { + m_samplingResolution.push_back(5); + } + } ~SamplingShaper() { @@ -200,10 +207,13 @@ class SamplingShaper : public Shaper */ void setSamplingResolution(int sampleRes) { - SLIC_ASSERT(sampleRes > 0); - m_sampleResolution[0] = sampleRes; - m_sampleResolution[1] = sampleRes; - m_sampleResolution[2] = sampleRes; + SLIC_ERROR_IF(sampleRes < 1, "Invalid sample resolution"); + m_samplingResolution.clear(); + const auto dim = getMeshDimension(); + for(int d = 0; d < dim; d++) + { + m_samplingResolution.push_back(sampleRes); + } } /*! @@ -215,17 +225,20 @@ class SamplingShaper : public Shaper * which in turn determine the quadrature rule used in each logical * direction. * - * \param [in] sampleRes Array containing the sample count per logical - * direction. + * \param [in] sampleRes ArrayView containing the sample count per logical + * direction. The size needs to match the number of + * mesh dimensions. */ - void setSamplingResolution(int sampleRes[3]) + void setSamplingResolution(axom::ArrayView sampleRes) { - SLIC_ASSERT(sampleRes[0] > 0); - SLIC_ASSERT(sampleRes[1] > 0); - SLIC_ASSERT(sampleRes[2] > 0); - m_sampleResolution[0] = sampleRes[0]; - m_sampleResolution[1] = sampleRes[1]; - m_sampleResolution[2] = sampleRes[2]; + const auto dim = getMeshDimension(); + 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]); + } } // Deprecated backward compatibility method @@ -607,7 +620,7 @@ class SamplingShaper : public Shaper { shaping::generatePositionsQFunction(mesh, m_inoutShapeQFuncs, - m_sampleResolution, + m_samplingResolution.view(), m_quadratureType); } auto* positionsQSpace = m_inoutShapeQFuncs.Get("positions")->GetSpace(); @@ -631,7 +644,7 @@ class SamplingShaper : public Shaper auto* matQFunc = new mfem::QuadratureFunction(*positionsQSpace); const auto& ir = matQFunc->GetSpace()->GetIntRule(0); - if(usesAnisotropicCustomTensorQuadrature(*mesh)) + if(shaping::usesAnisotropicCustomTensorQuadrature(*mesh, m_samplingResolution.view(), m_quadratureType)) { // Avoid MFEM's tensor quadrature interpolation path only for // anisotropic custom quad/hex rules. MFEM infers a single q1d from @@ -731,12 +744,18 @@ class SamplingShaper : public Shaper } private: + /// Get the mesh dimension. + int getMeshDimension() const + { + return m_dc->GetMesh()->Dimension(); + } + // Handles 2D or 3D shaping for compatible samplers, based on the template and associated parameter template void runShapeQueryImplSampler(SamplerType* sampler) { // Sample the InOut field at the mesh quadrature points - const int meshDim = m_dc->GetMesh()->Dimension(); + const int meshDim = getMeshDimension(); switch(m_vfSampling) { case shaping::VolFracSampling::SAMPLE_AT_QPTS: @@ -747,7 +766,7 @@ class SamplingShaper : public Shaper { sampler->template sampleInOutField<2, 2>(m_dc, m_inoutShapeQFuncs, - m_sampleResolution, + m_samplingResolution.view(), m_quadratureType, m_projector22); } @@ -755,7 +774,7 @@ class SamplingShaper : public Shaper { sampler->template sampleInOutField<3, 2>(m_dc, m_inoutShapeQFuncs, - m_sampleResolution, + m_samplingResolution.view(), m_quadratureType, m_projector32); } @@ -765,7 +784,7 @@ class SamplingShaper : public Shaper { sampler->template sampleInOutField<2, 3>(m_dc, m_inoutShapeQFuncs, - m_sampleResolution, + m_samplingResolution.view(), m_quadratureType, m_projector23); } @@ -773,7 +792,7 @@ class SamplingShaper : public Shaper { sampler->template sampleInOutField<3, 3>(m_dc, m_inoutShapeQFuncs, - m_sampleResolution, + m_samplingResolution.view(), m_quadratureType, m_projector33); } @@ -827,7 +846,7 @@ class SamplingShaper : public Shaper void runShapeQueryImpl(shaping::PrimitiveSampler* sampler) { // Sample the InOut field at the mesh quadrature points - const int meshDim = m_dc->GetMesh()->Dimension(); + const int meshDim = getMeshDimension(); switch(m_vfSampling) { case shaping::VolFracSampling::SAMPLE_AT_QPTS: @@ -841,7 +860,7 @@ class SamplingShaper : public Shaper { sampler->template sampleInOutField<2, 3>(m_dc, m_inoutShapeQFuncs, - m_sampleResolution, + m_samplingResolution.view(), m_quadratureType, m_projector23); } @@ -849,7 +868,7 @@ class SamplingShaper : public Shaper { sampler->template sampleInOutField<3, 3>(m_dc, m_inoutShapeQFuncs, - m_sampleResolution, + m_samplingResolution.view(), m_quadratureType, m_projector33); } @@ -886,14 +905,13 @@ class SamplingShaper : public Shaper mfem::Mesh* mesh = m_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 geom) -> std::string { - switch(geom) + auto samples_per_dim = [=](axom::ArrayView sampleRes) -> std::string { + switch(sampleRes.size()) { - case mfem::Geometry::SQUARE: + case 2: return axom::fmt::format(" ({} * {})", sampleRes[0], sampleRes[1]); - case mfem::Geometry::CUBE: + case 3: return axom::fmt::format(" ({} * {} * {})", sampleRes[0], sampleRes[1], sampleRes[2]); default: return std::string(); @@ -906,7 +924,7 @@ class SamplingShaper : public Shaper "In computeVolumeFractions(): num samples per element {}{} | " "sample polynomial order {} | total samples {:L}", sampleNQ, - samples_per_dim(m_sampleResolution, geom), + samples_per_dim(m_samplingResolution.view()), sampleOrder, sampleSZ)); @@ -942,7 +960,7 @@ class SamplingShaper : public Shaper mfem::ConstantCoefficient one_coef(1.0); mfem::MassIntegrator mass_integrator(one_coef, &sampleIR); - if(usesAnisotropicCustomTensorQuadrature(*fes->GetMesh())) + if(shaping::usesAnisotropicCustomTensorQuadrature(*fes->GetMesh(), m_samplingResolution.view(), m_quadratureType)) { mfem::DenseMatrix elemMat; mass_mat->HostWrite(); @@ -1098,34 +1116,15 @@ class SamplingShaper : public Shaper vf->HostReadWrite(); } - bool usesAnisotropicCustomTensorQuadrature(const mfem::Mesh& mesh) const - { - if(m_quadratureType == static_cast(mfem::Quadrature1D::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::Vector& b) { mfem::QuadratureFunctionCoefficient qfc(inout); mfem::DomainLFIntegrator rhs(qfc, &sampleIR); - if(usesAnisotropicCustomTensorQuadrature(*fes.GetMesh())) + if(shaping::usesAnisotropicCustomTensorQuadrature(*fes.GetMesh(), m_samplingResolution.view(), m_quadratureType)) { mfem::Vector elemVec; mfem::Array elemVDofs; @@ -1166,7 +1165,7 @@ class SamplingShaper : public Shaper shaping::VolFracSampling m_vfSampling {shaping::VolFracSampling::SAMPLE_AT_QPTS}; int m_quadratureType {static_cast(mfem::Quadrature1D::Invalid)}; - int m_sampleResolution[3] = {5, 5, 5}; + axom::Array m_samplingResolution {}; int m_volfracOrder {2}; SamplingMethod m_samplingMethod {SamplingMethod::InOut}; }; diff --git a/src/axom/quest/detail/shaping/InOutSampler.hpp b/src/axom/quest/detail/shaping/InOutSampler.hpp index a2f87ef1bd..45d8458b0b 100644 --- a/src/axom/quest/detail/shaping/InOutSampler.hpp +++ b/src/axom/quest/detail/shaping/InOutSampler.hpp @@ -116,7 +116,7 @@ class InOutSampler template std::enable_if_t sampleInOutField(mfem::DataCollection* dc, shaping::QFunctionCollection& inoutQFuncs, - int sampleRes[3], + axom::ArrayView sampleRes, int quadratureType, PointProjector projector = {}) { @@ -140,7 +140,7 @@ class InOutSampler template std::enable_if_t sampleInOutField(mfem::DataCollection*, shaping::QFunctionCollection&, - int AXOM_UNUSED_PARAM(sampleRes)[3], + axom::ArrayView AXOM_UNUSED_PARAM(sampleRes), int AXOM_UNUSED_PARAM(quadratureType), PointProjector) { diff --git a/src/axom/quest/detail/shaping/PrimitiveSampler.hpp b/src/axom/quest/detail/shaping/PrimitiveSampler.hpp index 4889cd0760..db010e0658 100644 --- a/src/axom/quest/detail/shaping/PrimitiveSampler.hpp +++ b/src/axom/quest/detail/shaping/PrimitiveSampler.hpp @@ -179,7 +179,7 @@ class PrimitiveSampler template std::enable_if_t sampleInOutField(mfem::DataCollection* dc, shaping::QFunctionCollection& inoutQFuncs, - int sampleRes[3], + axom::ArrayView sampleRes, int quadratureType, PointProjector projector = {}) { @@ -293,7 +293,7 @@ class PrimitiveSampler template std::enable_if_t sampleInOutField(mfem::DataCollection*, shaping::QFunctionCollection&, - int AXOM_UNUSED_PARAM(sampleRes)[3], + axom::ArrayView AXOM_UNUSED_PARAM(sampleRes), int AXOM_UNUSED_PARAM(quadratureType), PointProjector) { diff --git a/src/axom/quest/detail/shaping/WindingNumberSampler.hpp b/src/axom/quest/detail/shaping/WindingNumberSampler.hpp index 5a1aa27990..5b34be6788 100644 --- a/src/axom/quest/detail/shaping/WindingNumberSampler.hpp +++ b/src/axom/quest/detail/shaping/WindingNumberSampler.hpp @@ -148,7 +148,7 @@ class WindingNumberSampler template std::enable_if_t sampleInOutField(mfem::DataCollection* dc, shaping::QFunctionCollection& inoutQFuncs, - int sampleRes[3], + axom::ArrayView sampleRes, int quadratureType, PointProjector projector = {}) { @@ -266,7 +266,7 @@ class WindingNumberSampler template std::enable_if_t sampleInOutField(mfem::DataCollection*, shaping::QFunctionCollection&, - int AXOM_UNUSED_PARAM(sampleRes)[3], + axom::ArrayView AXOM_UNUSED_PARAM(sampleRes), int AXOM_UNUSED_PARAM(quadratureType), PointProjector) { diff --git a/src/axom/quest/detail/shaping/shaping_helpers.cpp b/src/axom/quest/detail/shaping/shaping_helpers.cpp index 7dbe7fe0a4..bddc45d3c1 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers.cpp +++ b/src/axom/quest/detail/shaping/shaping_helpers.cpp @@ -42,8 +42,10 @@ class OwnedQuadratureSpace : public mfem::QuadratureSpace std::unique_ptr m_ir; }; +} // namespace + bool usesAnisotropicCustomTensorQuadrature(const mfem::Mesh& mesh, - const int sampleResolution[3], + axom::ArrayView sampleResolution, int quadratureType) { if(quadratureType == static_cast(mfem::Quadrature1D::Invalid)) @@ -51,19 +53,24 @@ bool usesAnisotropicCustomTensorQuadrature(const mfem::Mesh& mesh, return false; } - switch(mesh.GetTypicalElementGeometry()) + 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) { - 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; + 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(); } -} // namespace - // 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, @@ -192,12 +199,14 @@ 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, axom::ArrayView sampleRes, int 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!"); @@ -255,7 +264,7 @@ mfem::QuadratureSpace* makeCustomQuadratureSpace(mfem::Mesh* mesh, int sampleRes /// Generates a quadrature function corresponding to the mesh "positions" field void generatePositionsQFunction(mfem::Mesh* mesh, QFunctionCollection& inoutQFuncs, - int sampleResolution[3], + axom::ArrayView sampleResolution, int quadratureType) { SLIC_ASSERT(mesh != nullptr); @@ -272,6 +281,7 @@ void generatePositionsQFunction(mfem::Mesh* mesh, mfem::QuadratureSpace* sp = nullptr; if(quadratureType == static_cast(mfem::Quadrature1D::Invalid)) { + SLIC_ERROR_IF(sampleResolution.empty(), "Invalid sampleResolution."); sp = makeDefaultQuadratureSpace(mesh, sampleResolution[0]); } else diff --git a/src/axom/quest/detail/shaping/shaping_helpers.hpp b/src/axom/quest/detail/shaping/shaping_helpers.hpp index 4d4d1f56a2..fbfd05ec63 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers.hpp +++ b/src/axom/quest/detail/shaping/shaping_helpers.hpp @@ -201,7 +201,9 @@ void copyShapeIntoMaterial(const mfem::QuadratureFunction* shapeQFunc, * * \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. + * \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 @@ -210,7 +212,7 @@ void copyShapeIntoMaterial(const mfem::QuadratureFunction* shapeQFunc, */ void generatePositionsQFunction(mfem::Mesh* mesh, QFunctionCollection& inoutQFuncs, - int sampleResolution[3], + axom::ArrayView sampleResolution, int quadratureType); /** @@ -240,6 +242,23 @@ 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 An int containing an mfem::Quadrature1D enum value that selects + * the quadrature type. + * + * \return True if the specified quadrature is anisotropic, false otherwise. + */ +bool usesAnisotropicCustomTensorQuadrature(const mfem::Mesh& mesh, + axom::ArrayView sampleResolution, + int quadratureType); + /*! * \brief Samples the inout field over the indexed geometry, possibly using a * callback function to project the input points (from the computational mesh) @@ -254,7 +273,9 @@ void computeVolumeFractionsIdentity(mfem::DataCollection* dc, * \param [in] dc The data collection 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. + * \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. @@ -270,7 +291,7 @@ template void sampleInOutField(const std::string shapeName, mfem::DataCollection* dc, shaping::QFunctionCollection& inoutQFuncs, - int sampleRes[3], + axom::ArrayView sampleRes, int quadratureType, InsideFunc&& checkInside, PointProjector projector = {}) diff --git a/src/axom/quest/examples/shaping_driver.cpp b/src/axom/quest/examples/shaping_driver.cpp index 991dc59908..fc1e635f40 100644 --- a/src/axom/quest/examples/shaping_driver.cpp +++ b/src/axom/quest/examples/shaping_driver.cpp @@ -315,7 +315,7 @@ struct Input ->description( "Sampling resolution per element for the inout field (x,y,[z]). \n" "Determines number of samples per element in determining volume fraction field") - ->expected(2, 3) + ->expected(1, 3) ->check(axom::CLI::PositiveNumber); std::map vfsamplingMap { @@ -651,13 +651,21 @@ int main(int argc, char** argv) if(auto* samplingShaper = dynamic_cast(shaper)) { int res[3] = {5, 5, 5}; - for(size_t i = 0; i < std::min(size_t {3}, params.samplingResolution.size()); i++) + if(params.samplingResolution.size() == 1) + { + res[0] = res[1] = res[2] = params.samplingResolution[0]; + } + else { - res[i] = params.samplingResolution[i]; + for(size_t i = 0; i < std::min(size_t {3}, params.samplingResolution.size()); i++) + { + res[i] = params.samplingResolution[i]; + } } + axom::ArrayView sampleRes(res, shaper->getDC()->GetMesh()->Dimension()); samplingShaper->setSamplingType(params.vfSampling); - samplingShaper->setSamplingResolution(res); + samplingShaper->setSamplingResolution(sampleRes); samplingShaper->setQuadratureType(params.quadratureType); samplingShaper->setVolumeFractionOrder(params.outputOrder); samplingShaper->setSamplingMethod(params.samplingMethod); diff --git a/src/axom/quest/tests/quest_sampling_shaper.cpp b/src/axom/quest/tests/quest_sampling_shaper.cpp index a7466c5937..4deb2a4269 100644 --- a/src/axom/quest/tests/quest_sampling_shaper.cpp +++ b/src/axom/quest/tests/quest_sampling_shaper.cpp @@ -2345,8 +2345,8 @@ piece = line(end=start) this->validateShapeFile(shape_file.getPath()); this->initializeShaping(shape_file.getPath()); - int sampleRes[3] = {3, 5, 1}; - this->m_shaper->setSamplingResolution(sampleRes); + int sampleRes[3] = {3, 5}; + this->m_shaper->setSamplingResolution(axom::ArrayView{sampleRes, 2}); this->m_shaper->setQuadratureType(static_cast(mfem::Quadrature1D::ClosedUniform)); this->m_shaper->setVolumeFractionOrder(0); @@ -2392,14 +2392,14 @@ piece = line(end=start) rect_material, contour_file.getPath())); - int sampleRes[3] = {3, 5, 1}; + int sampleRes[3] = {3, 5}; for(const auto& quadrature : supported_quadrature_types) { this->validateShapeFile(shape_file.getPath()); this->initializeShaping(shape_file.getPath()); - this->m_shaper->setSamplingResolution(sampleRes); + this->m_shaper->setSamplingResolution(axom::ArrayView{sampleRes, 2}); this->m_shaper->setQuadratureType(quadrature.second); this->m_shaper->setVolumeFractionOrder(0); @@ -2479,7 +2479,7 @@ dimensions: 3 this->initializeShaping(shape_file.getPath()); int sampleRes[3] = {3, 5, 2}; - this->m_shaper->setSamplingResolution(sampleRes); + this->m_shaper->setSamplingResolution(axom::ArrayView{sampleRes, 3}); this->m_shaper->setQuadratureType(static_cast(mfem::Quadrature1D::ClosedUniform)); this->m_shaper->setVolumeFractionOrder(0); @@ -2527,7 +2527,7 @@ dimensions: 3 this->initializeShaping(shape_file.getPath(), initialGridFunctions); int sampleRes[3] = {3, 4, 5}; - this->m_shaper->setSamplingResolution(sampleRes); + this->m_shaper->setSamplingResolution(axom::ArrayView{sampleRes, 3}); this->m_shaper->setQuadratureType(static_cast(mfem::Quadrature1D::OpenUniform)); this->m_shaper->setVolumeFractionOrder(4); @@ -2553,11 +2553,11 @@ TEST_F(CurvedSampleTester2D, positions_match_curved_mesh_for_anisotropic_custom_ }); nodes->ProjectCoefficient(warp); - int sampleRes[3] = {5, 3, 1}; + int sampleRes[3] = {5, 3}; quest::shaping::QFunctionCollection qfuncs; quest::shaping::generatePositionsQFunction(&mesh, qfuncs, - sampleRes, + axom::ArrayView{sampleRes, 2}, static_cast(mfem::Quadrature1D::OpenUniform)); auto* positions = qfuncs.Get("positions"); From ddfceff5ad4964f411c234e8a44455ba643767be Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 18 May 2026 16:56:31 -0700 Subject: [PATCH 267/986] make style --- src/axom/quest/SamplingShaper.hpp | 20 +++++++++++-------- .../quest/detail/shaping/shaping_helpers.cpp | 10 +++++++--- .../quest/tests/quest_sampling_shaper.cpp | 10 +++++----- 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/axom/quest/SamplingShaper.hpp b/src/axom/quest/SamplingShaper.hpp index 6225746dea..7ac291c477 100644 --- a/src/axom/quest/SamplingShaper.hpp +++ b/src/axom/quest/SamplingShaper.hpp @@ -232,7 +232,8 @@ class SamplingShaper : public Shaper void setSamplingResolution(axom::ArrayView sampleRes) { const auto dim = getMeshDimension(); - SLIC_ERROR_IF(static_cast(dim) != sampleRes.size(), "Number of sample resolutions does not match mesh dimension."); + 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++) { @@ -644,7 +645,9 @@ class SamplingShaper : public Shaper auto* matQFunc = new mfem::QuadratureFunction(*positionsQSpace); const auto& ir = matQFunc->GetSpace()->GetIntRule(0); - if(shaping::usesAnisotropicCustomTensorQuadrature(*mesh, m_samplingResolution.view(), m_quadratureType)) + if(shaping::usesAnisotropicCustomTensorQuadrature(*mesh, + m_samplingResolution.view(), + m_quadratureType)) { // Avoid MFEM's tensor quadrature interpolation path only for // anisotropic custom quad/hex rules. MFEM infers a single q1d from @@ -745,10 +748,7 @@ class SamplingShaper : public Shaper private: /// Get the mesh dimension. - int getMeshDimension() const - { - return m_dc->GetMesh()->Dimension(); - } + int getMeshDimension() const { return m_dc->GetMesh()->Dimension(); } // Handles 2D or 3D shaping for compatible samplers, based on the template and associated parameter template @@ -960,7 +960,9 @@ class SamplingShaper : public Shaper mfem::ConstantCoefficient one_coef(1.0); mfem::MassIntegrator mass_integrator(one_coef, &sampleIR); - if(shaping::usesAnisotropicCustomTensorQuadrature(*fes->GetMesh(), m_samplingResolution.view(), m_quadratureType)) + if(shaping::usesAnisotropicCustomTensorQuadrature(*fes->GetMesh(), + m_samplingResolution.view(), + m_quadratureType)) { mfem::DenseMatrix elemMat; mass_mat->HostWrite(); @@ -1124,7 +1126,9 @@ class SamplingShaper : public Shaper mfem::QuadratureFunctionCoefficient qfc(inout); mfem::DomainLFIntegrator rhs(qfc, &sampleIR); - if(shaping::usesAnisotropicCustomTensorQuadrature(*fes.GetMesh(), m_samplingResolution.view(), m_quadratureType)) + if(shaping::usesAnisotropicCustomTensorQuadrature(*fes.GetMesh(), + m_samplingResolution.view(), + m_quadratureType)) { mfem::Vector elemVec; mfem::Array elemVDofs; diff --git a/src/axom/quest/detail/shaping/shaping_helpers.cpp b/src/axom/quest/detail/shaping/shaping_helpers.cpp index bddc45d3c1..f8b169775f 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers.cpp +++ b/src/axom/quest/detail/shaping/shaping_helpers.cpp @@ -54,7 +54,8 @@ bool usesAnisotropicCustomTensorQuadrature(const mfem::Mesh& mesh, } const auto dim = mesh.Dimension(); - SLIC_ERROR_IF(sampleResolution.size() != static_cast(dim), "Sample resolution dimension does not match mesh dimension"); + SLIC_ERROR_IF(sampleResolution.size() != static_cast(dim), + "Sample resolution dimension does not match mesh dimension"); if(mesh.GetNE() > 0) { @@ -199,13 +200,16 @@ mfem::QuadratureSpace* makeDefaultQuadratureSpace(mfem::Mesh* mesh, int sampleRe return new mfem::QuadratureSpace(mesh, sampleOrder); } -mfem::QuadratureSpace* makeCustomQuadratureSpace(mfem::Mesh* mesh, axom::ArrayView sampleRes, int quadratureType) +mfem::QuadratureSpace* makeCustomQuadratureSpace(mfem::Mesh* mesh, + axom::ArrayView sampleRes, + int 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"); + SLIC_ERROR_IF(sampleRes.size() != static_cast(dim), + "Sample resolution dimension does not match mesh dimension"); if(NE < 1) { diff --git a/src/axom/quest/tests/quest_sampling_shaper.cpp b/src/axom/quest/tests/quest_sampling_shaper.cpp index 4deb2a4269..2f2070e9e8 100644 --- a/src/axom/quest/tests/quest_sampling_shaper.cpp +++ b/src/axom/quest/tests/quest_sampling_shaper.cpp @@ -2346,7 +2346,7 @@ piece = line(end=start) this->initializeShaping(shape_file.getPath()); int sampleRes[3] = {3, 5}; - this->m_shaper->setSamplingResolution(axom::ArrayView{sampleRes, 2}); + this->m_shaper->setSamplingResolution(axom::ArrayView {sampleRes, 2}); this->m_shaper->setQuadratureType(static_cast(mfem::Quadrature1D::ClosedUniform)); this->m_shaper->setVolumeFractionOrder(0); @@ -2399,7 +2399,7 @@ piece = line(end=start) this->validateShapeFile(shape_file.getPath()); this->initializeShaping(shape_file.getPath()); - this->m_shaper->setSamplingResolution(axom::ArrayView{sampleRes, 2}); + this->m_shaper->setSamplingResolution(axom::ArrayView {sampleRes, 2}); this->m_shaper->setQuadratureType(quadrature.second); this->m_shaper->setVolumeFractionOrder(0); @@ -2479,7 +2479,7 @@ dimensions: 3 this->initializeShaping(shape_file.getPath()); int sampleRes[3] = {3, 5, 2}; - this->m_shaper->setSamplingResolution(axom::ArrayView{sampleRes, 3}); + this->m_shaper->setSamplingResolution(axom::ArrayView {sampleRes, 3}); this->m_shaper->setQuadratureType(static_cast(mfem::Quadrature1D::ClosedUniform)); this->m_shaper->setVolumeFractionOrder(0); @@ -2527,7 +2527,7 @@ dimensions: 3 this->initializeShaping(shape_file.getPath(), initialGridFunctions); int sampleRes[3] = {3, 4, 5}; - this->m_shaper->setSamplingResolution(axom::ArrayView{sampleRes, 3}); + this->m_shaper->setSamplingResolution(axom::ArrayView {sampleRes, 3}); this->m_shaper->setQuadratureType(static_cast(mfem::Quadrature1D::OpenUniform)); this->m_shaper->setVolumeFractionOrder(4); @@ -2557,7 +2557,7 @@ TEST_F(CurvedSampleTester2D, positions_match_curved_mesh_for_anisotropic_custom_ quest::shaping::QFunctionCollection qfuncs; quest::shaping::generatePositionsQFunction(&mesh, qfuncs, - axom::ArrayView{sampleRes, 2}, + axom::ArrayView {sampleRes, 2}, static_cast(mfem::Quadrature1D::OpenUniform)); auto* positions = qfuncs.Get("positions"); From b07d9c44e802d9d9f447fca68114a768df13d018 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Mon, 11 May 2026 23:37:27 -0700 Subject: [PATCH 268/986] Adds Array::pop_back() --- src/axom/core/Array.hpp | 21 ++++++ src/axom/core/ArrayBase.hpp | 2 +- src/axom/core/tests/core_array.hpp | 111 +++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+), 1 deletion(-) diff --git a/src/axom/core/Array.hpp b/src/axom/core/Array.hpp index 3ad3d54b14..98d2143731 100644 --- a/src/axom/core/Array.hpp +++ b/src/axom/core/Array.hpp @@ -742,6 +742,16 @@ class Array : public ArrayBase>, pro template void emplace_back(Args&&... args); + /*! + * \brief Removes the last element from the Array. + * + * \note The size decreases by 1 and the capacity is unchanged. + * + * \pre DIM == 1 + * \pre array.empty() == false + */ + void pop_back(); + /*! * \brief Push a value to the back of the array. * @@ -1590,6 +1600,17 @@ inline void Array::emplace_back(Args&&... args) m_arrayOps.emplace(m_data, insertIndex, std::forward(args)...); } +//------------------------------------------------------------------------------ +template +inline void Array::pop_back() +{ + static_assert(DIM == 1, "pop_back is only supported for 1D arrays"); + assert(!empty()); + + m_arrayOps.destroy(m_data, m_num_elements - 1, 1); + updateNumElements(m_num_elements - 1); +} + //------------------------------------------------------------------------------ template template diff --git a/src/axom/core/ArrayBase.hpp b/src/axom/core/ArrayBase.hpp index d15c5fb46a..ffb44ad5b8 100644 --- a/src/axom/core/ArrayBase.hpp +++ b/src/axom/core/ArrayBase.hpp @@ -577,7 +577,7 @@ class ArrayBase AXOM_HOST_DEVICE ArrayBase(const StackArray&, int stride = 1) : m_stride(stride) { } AXOM_HOST_DEVICE ArrayBase(const StackArray&, const StackArray& stride) - : m_stride(stride[0]) + : m_stride(static_cast(stride[0])) { } AXOM_HOST_DEVICE ArrayBase(const StackArray&, const MDMapping<1>& mapping) diff --git a/src/axom/core/tests/core_array.hpp b/src/axom/core/tests/core_array.hpp index 1a1350151f..36a9715945 100644 --- a/src/axom/core/tests/core_array.hpp +++ b/src/axom/core/tests/core_array.hpp @@ -456,6 +456,51 @@ void check_resize(axom::Array& v) values = nullptr; } +/*! + * \brief Check that pop_back() updates the size while keeping capacity and + * existing elements intact. + * \param [in] v the Array to check. + */ +template +void check_pop_back(axom::Array& v) +{ + v.resize(0); + v.reserve(4); + + const axom::IndexType capacity = v.capacity(); + const T* data_ptr = v.data(); + + v.push_back(T {1}); + v.push_back(T {2}); + v.push_back(T {3}); + + EXPECT_EQ(v.size(), 3); + EXPECT_EQ(v.capacity(), capacity); + EXPECT_EQ(v.data(), data_ptr); + EXPECT_EQ(v.front(), T {1}); + EXPECT_EQ(v.back(), T {3}); + + v.pop_back(); + EXPECT_EQ(v.size(), 2); + EXPECT_EQ(v.capacity(), capacity); + EXPECT_EQ(v.data(), data_ptr); + EXPECT_EQ(v.front(), T {1}); + EXPECT_EQ(v.back(), T {2}); + + v.pop_back(); + EXPECT_EQ(v.size(), 1); + EXPECT_EQ(v.capacity(), capacity); + EXPECT_EQ(v.data(), data_ptr); + EXPECT_EQ(v.front(), T {1}); + EXPECT_EQ(v.back(), T {1}); + + v.pop_back(); + EXPECT_TRUE(v.empty()); + EXPECT_EQ(v.size(), 0); + EXPECT_EQ(v.capacity(), capacity); + EXPECT_EQ(v.data(), data_ptr); +} + /*! * \brief Check that the insertion into an Array is working properly. * \param [in] v the Array to check. @@ -1168,6 +1213,31 @@ TEST(core_array_DeathTest, checkResize) EXPECT_DEATH_IF_SUPPORTED(::check_resize(v_int), ""); } +//------------------------------------------------------------------------------ +TEST(core_array, checkPopBack) +{ + for(axom::IndexType capacity = 2; capacity <= 512; capacity *= 2) + { + axom::Array v_int(0, capacity); + ::check_pop_back(v_int); + + axom::Array v_double(0, capacity); + ::check_pop_back(v_double); + } +} + +//------------------------------------------------------------------------------ +TEST(core_array_DeathTest, popBackEmpty) +{ + axom::Array v_int(0, 2); +#ifdef NDEBUG + GTEST_SKIP() + << "pop_back() uses assert on empty arrays, so this death test only applies in debug builds."; +#else + EXPECT_DEATH_IF_SUPPORTED(v_int.pop_back(), ""); +#endif +} + //------------------------------------------------------------------------------ TEST(core_array, checkInsert) { @@ -2467,6 +2537,47 @@ TEST(core_array, reserve_nontrivial_reloc_um) } #endif +class PopBackTracked +{ +public: + explicit PopBackTracked(int value = 0) : m_value(value) { } + + PopBackTracked(const PopBackTracked&) = default; + PopBackTracked(PopBackTracked&&) = default; + PopBackTracked& operator=(const PopBackTracked&) = default; + PopBackTracked& operator=(PopBackTracked&&) = default; + + ~PopBackTracked() { ++s_destroyCount; } + + int m_value; + static int s_destroyCount; +}; + +int PopBackTracked::s_destroyCount = 0; + +TEST(core_array, popBackDestroysTrailingElement) +{ + PopBackTracked::s_destroyCount = 0; + + { + axom::Array array(0, 4); + array.emplace_back(1); + array.emplace_back(2); + array.emplace_back(3); + + EXPECT_EQ(array.size(), 3); + EXPECT_EQ(PopBackTracked::s_destroyCount, 0); + + array.pop_back(); + + EXPECT_EQ(array.size(), 2); + EXPECT_EQ(array.back().m_value, 2); + EXPECT_EQ(PopBackTracked::s_destroyCount, 1); + } + + EXPECT_EQ(PopBackTracked::s_destroyCount, 3); +} + class AllocatingDefaultInit { public: From b7c86dd88b426e89a0dcaa0d9e02864b0cd4c3e4 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 12 May 2026 00:44:34 -0700 Subject: [PATCH 269/986] Modernize polynomial solver APIs to use ArrayView --- src/axom/core/examples/core_numerics.cpp | 21 ++-- src/axom/core/numerics/polynomial_solvers.cpp | 105 +++++++++++++----- src/axom/core/numerics/polynomial_solvers.hpp | 71 ++++++++---- .../tests/numerics_polynomial_solvers.hpp | 59 +++++----- 4 files changed, 171 insertions(+), 85 deletions(-) diff --git a/src/axom/core/examples/core_numerics.cpp b/src/axom/core/examples/core_numerics.cpp index f4f498da79..36c655a6a4 100644 --- a/src/axom/core/examples/core_numerics.cpp +++ b/src/axom/core/examples/core_numerics.cpp @@ -9,16 +9,17 @@ */ /* This example code contains snippets used in the Core Sphinx documentation. - * They begin and end with comments such as - * - * timer_start - * timer_end - * - * each prepended with an underscore. - */ + * They begin and end with comments such as + * + * timer_start + * timer_end + * + * each prepended with an underscore. + */ // Axom includes #include "axom/core/Macros.hpp" +#include "axom/core/Array.hpp" #include "axom/core/numerics/eigen_solve.hpp" #include "axom/core/numerics/jacobi_eigensolve.hpp" #include "axom/core/numerics/linear_solve.hpp" @@ -89,10 +90,10 @@ void demoVectorOps() // Find the real roots of a cubic equation. // (x + 2)(x - 1)(2x - 3) = 0 = 2x^3 - x^2 - 7x + 6 has real roots at // x = -2, x = 1, x = 1.5. - double coeff[] = {6., -7., -1., 2.}; - double roots[3]; + axom::Array coeff {6., -7., -1., 2.}; + axom::Array roots {0., 0., 0.}; int numRoots; - int result = numerics::solve_cubic(coeff, roots, numRoots); + int result = numerics::solve_cubic(coeff.view(), roots.view(), numRoots); std::cout << "Root-finding returned " << result << " (should be 0, success)." diff --git a/src/axom/core/numerics/polynomial_solvers.cpp b/src/axom/core/numerics/polynomial_solvers.cpp index fc587ede4e..2461f0015b 100644 --- a/src/axom/core/numerics/polynomial_solvers.cpp +++ b/src/axom/core/numerics/polynomial_solvers.cpp @@ -4,25 +4,26 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#include "axom/core/utilities/Utilities.hpp" // for isNearlyEqual() - +#include "axom/core/utilities/Utilities.hpp" #include "axom/core/numerics/polynomial_solvers.hpp" -// C/C++ includes -#include // for assert() +#include +#include namespace axom { namespace numerics { -//------------------------------------------------------------------------------ -int solve_linear(const double* coeff, double* roots, int& numRoots) +int solve_linear(ArrayView coeff, ArrayView roots, int& numRoots) { + assert(coeff.size() >= 2); + assert(roots.size() >= 1); + int status = -1; // solve ax + b = 0 - double a = coeff[1]; - double b = coeff[0]; + const double a = coeff[1]; + const double b = coeff[0]; if(utilities::isNearlyEqual(a, 0.)) { @@ -50,14 +51,33 @@ int solve_linear(const double* coeff, double* roots, int& numRoots) } //------------------------------------------------------------------------------ -int solve_quadratic(const double* coeff, double* roots, int& numRoots) +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4244) +#endif +int solve_linear(const double* coeff, double* roots, int& numRoots) +{ + return solve_linear(ArrayView(coeff, + axom::StackArray {2}, + axom::StackArray {1}), + ArrayView(roots, + axom::StackArray {1}, + axom::StackArray {1}), + numRoots); +} + +//------------------------------------------------------------------------------ +int solve_quadratic(ArrayView coeff, ArrayView roots, int& numRoots) { + assert(coeff.size() >= 3); + assert(roots.size() >= 2); + int status = -1; // solve ax^2 + bx + c = 0 - double a = coeff[2]; - double b = coeff[1]; - double c = coeff[0]; + const double a = coeff[2]; + const double b = coeff[1]; + const double c = coeff[0]; if(utilities::isNearlyEqual(a, 0.)) { @@ -65,8 +85,8 @@ int solve_quadratic(const double* coeff, double* roots, int& numRoots) return solve_linear(coeff, roots, numRoots); } - double discriminant = b * b - 4 * a * c; - double overtwoa = 1. / (2 * a); + const double discriminant = b * b - 4 * a * c; + const double overtwoa = 1. / (2 * a); if(utilities::isNearlyEqual(discriminant, 0.)) { @@ -85,7 +105,7 @@ int solve_quadratic(const double* coeff, double* roots, int& numRoots) // Two real roots status = 0; numRoots = 2; - double sqrtdisc = std::sqrt(discriminant); + const double sqrtdisc = std::sqrt(discriminant); roots[0] = (-b + sqrtdisc) * overtwoa; roots[1] = (-b - sqrtdisc) * overtwoa; } @@ -93,6 +113,18 @@ int solve_quadratic(const double* coeff, double* roots, int& numRoots) return status; } +//------------------------------------------------------------------------------ +int solve_quadratic(const double* coeff, double* roots, int& numRoots) +{ + return solve_quadratic(ArrayView(coeff, + axom::StackArray {3}, + axom::StackArray {1}), + ArrayView(roots, + axom::StackArray {2}, + axom::StackArray {1}), + numRoots); +} + inline double cuberoot(double x) { // pow(x, y) returns NaN for negative finite x and noninteger y. @@ -107,8 +139,11 @@ inline double cuberoot(double x) } //------------------------------------------------------------------------------ -int solve_cubic(const double* coeff, double* roots, int& numRoots) +int solve_cubic(ArrayView coeff, ArrayView roots, int& numRoots) { + assert(coeff.size() >= 4); + assert(roots.size() >= 3); + int status = -1; // Here I use variable names as presented in Korn & Korn: @@ -125,21 +160,21 @@ int solve_cubic(const double* coeff, double* roots, int& numRoots) } // We normalize by dividing all by the cubic coefficient. - double invcubecoeff = 1. / cubecoeff; + const double invcubecoeff = 1. / cubecoeff; a *= invcubecoeff; b *= invcubecoeff; c *= invcubecoeff; // Note that p and q differ by a multiplicative constant from Korn, // because they're always used with that multiplication. - double p = (-a * a + 3 * b) / 9; // 1/3 Korn's p - double q = (a * (-2 * a * a + 9 * b) - 27 * c) / 54; // -1/2 Korn's q + const double p = (-a * a + 3 * b) / 9; // 1/3 Korn's p + const double q = (a * (-2 * a * a + 9 * b) - 27 * c) / 54; // -1/2 Korn's q - double Q = p * p * p + q * q; // actual discriminant == -108Q + const double Q = p * p * p + q * q; // actual discriminant == -108Q // the term chVar occurs because we've changed variables // (x = y - a/3) and we need to change back to x. const double onethird = 1. / 3.; - double chVar = -a * onethird; + const double chVar = -a * onethird; if(utilities::isNearlyEqual(Q, 0.)) { @@ -154,7 +189,7 @@ int solve_cubic(const double* coeff, double* roots, int& numRoots) } status = 0; - double cuberootq = cuberoot(q); + const double cuberootq = cuberoot(q); roots[0] = chVar + 2 * cuberootq; roots[1] = chVar - cuberootq; roots[2] = roots[1]; @@ -167,9 +202,9 @@ int solve_cubic(const double* coeff, double* roots, int& numRoots) numRoots = 1; status = 0; - double sqrtQ = sqrt(Q); - double A = cuberoot(q + sqrtQ); - double B = cuberoot(q - sqrtQ); + const double sqrtQ = sqrt(Q); + const double A = cuberoot(q + sqrtQ); + const double B = cuberoot(q - sqrtQ); roots[0] = chVar + A + B; roots[1] = 0; @@ -193,8 +228,8 @@ int solve_cubic(const double* coeff, double* roots, int& numRoots) numRoots = 3; status = 0; - double alpha = acos(q / sqrt(-p * p * p)); - double m = 2 * sqrt(-p); + const double alpha = acos(q / sqrt(-p * p * p)); + const double m = 2 * sqrt(-p); roots[0] = chVar + m * cos(alpha * onethird); roots[1] = chVar - m * cos((alpha + M_PI) * onethird); @@ -204,5 +239,21 @@ int solve_cubic(const double* coeff, double* roots, int& numRoots) return status; } +//------------------------------------------------------------------------------ +int solve_cubic(const double* coeff, double* roots, int& numRoots) +{ + return solve_cubic(ArrayView(coeff, + axom::StackArray {4}, + axom::StackArray {1}), + ArrayView(roots, + axom::StackArray {3}, + axom::StackArray {1}), + numRoots); +} + +#ifdef _MSC_VER + #pragma warning(pop) +#endif + } /* end namespace numerics */ } /* end namespace axom */ diff --git a/src/axom/core/numerics/polynomial_solvers.hpp b/src/axom/core/numerics/polynomial_solvers.hpp index b7c52bd778..1cf809acf4 100644 --- a/src/axom/core/numerics/polynomial_solvers.hpp +++ b/src/axom/core/numerics/polynomial_solvers.hpp @@ -7,6 +7,8 @@ #ifndef AXOM_NUMERICS_POLY_SOLVE_HPP_ #define AXOM_NUMERICS_POLY_SOLVE_HPP_ +#include "axom/core/ArrayView.hpp" + /*! * \file polynomial_solve.hpp * The functions declared in this header file find real roots of polynomials @@ -18,7 +20,7 @@ * output array for roots found, and an output int indicating the number * of distinct real roots found. * - * Note that coeff[i] = \f$ a_i \f$. The constant term goes in coeff[0], + * Note that coeff[i] = \f$ a_i \f$. The constant term goes in coeff[0], * the linear in coeff[1], quadratic in coeff[2], and so forth. */ @@ -26,6 +28,7 @@ namespace axom { namespace numerics { + /*! * \brief Find the real root for a linear equation of form \f$ ax + b = 0 \f$. * @@ -34,14 +37,22 @@ namespace numerics * \param [out] roots The real roots of the equation. * \param [out] numRoots The number of distinct, real roots found (max: 1). * If the line lies on the X-axis, numRoots is assigned -1 to indicate - * infinitely many solutions. If the line doesn't intersect the X-axis, + * infinitely many solutions. If the line does not intersect the X-axis, * numRoots is assigned 0. + * * \return 0 for success, or -1 to indicate an inconsistent equation * (all coefficients 0 except for coeff[0]). * - * \pre coeff points to an array of length at least 2. - * \pre roots points to an array of length at least 1. + * \pre coeff.size() >= 2 + * \pre roots.size() >= 1 */ +int solve_linear(ArrayView coeff, ArrayView roots, int& numRoots); + +/*! + * \brief Deprecated pointer-based overload for \ref solve_linear(ArrayView, ArrayView, int&). + */ +[[deprecated("Use solve_linear(axom::ArrayView, axom::ArrayView, int&) instead.")]] int solve_linear(const double* coeff, double* roots, int& numRoots); /*! @@ -53,14 +64,22 @@ int solve_linear(const double* coeff, double* roots, int& numRoots); * \param [out] roots The real roots of the equation. * \param [out] numRoots The number of distinct, real roots found (max: 2). * If the equation degenerates to a line lying on the X-axis, numRoots is - * assigned -1 to indicate infinitely many solutions. If the equation - * doesn't intersect the X-axis, numRoots is assigned 0. + * assigned -1 to indicate infinitely many solutions. If the equation does + * not intersect the X-axis, numRoots is assigned 0. + * * \return 0 for success, or -1 to indicate an inconsistent equation * (all coefficients 0 except for coeff[0]). * - * \pre coeff points to an array of length at least 3. - * \pre roots points to an array of length at least 2. + * \pre coeff.size() >= 3 + * \pre roots.size() >= 2 */ +int solve_quadratic(ArrayView coeff, ArrayView roots, int& numRoots); + +/*! + * \brief Deprecated pointer-based overload for \ref + * solve_quadratic(ArrayView, ArrayView, int&). + */ +[[deprecated("Use solve_quadratic(axom::ArrayView, axom::ArrayView, int&) instead.")]] int solve_quadratic(const double* coeff, double* roots, int& numRoots); /*! @@ -68,7 +87,7 @@ int solve_quadratic(const double* coeff, double* roots, int& numRoots); * find real roots. * * A closed-form solution for cubic equations was published in Cardano's - * *Ars Magna* of 1545. This can be summarized as follows: + * *Ars Magna* of 1545. This can be summarized as follows: * * Start with the cubic equation * @@ -87,13 +106,13 @@ int solve_quadratic(const double* coeff, double* roots, int& numRoots); * \f[ u = \sqrt[3]{\frac{t}{2} \pm \sqrt{\frac{t^2}{4} - 1}}. \f] * * Because the cubic is an odd function, there can be either one, two, or - * three distinct real roots. If there is one distinct real root, the root - * can either have multiplicity 3, or have multiplicity 1 with two complex - * roots. In the case of two real roots, one root will have multiplicity 2. - * If the discriminant \f$ d = -27t^2 - 4(-3^3) \f$ is zero, the equation has - * at least one root with multiplicity greater than 1. If \f$ d > 0, \f$ - * there are three distinct real roots, and if \f$ d > 0, \f$ there is one - * real root. + * three distinct real roots. If there is one distinct real root, the root can + * either have multiplicity 3, or have multiplicity 1 with two complex roots. + * In the case of two real roots, one root will have multiplicity 2. If the + * discriminant \f$ d = -27t^2 - 4(-3^3) \f$ is zero, the equation has at + * least one root with multiplicity greater than 1. If \f$ d > 0, \f$ there + * are three distinct real roots, and if \f$ d < 0, \f$ there is one real + * root. * * See J. Kopp, Efficient numerical diagonalization of hermitian 3x3 matrices, * Int.J.Mod.Phys. C19:523-548, 2008 (https://arxiv.org/abs/physics/0610206) @@ -106,17 +125,25 @@ int solve_quadratic(const double* coeff, double* roots, int& numRoots); * \param [out] roots The real roots of the equation. * \param [out] numRoots The number of distinct, real roots found (max: 3). * If the equation degenerates to a line lying on the X-axis, numRoots is - * assigned -1 to indicate infinitely many solutions. If the equation - * doesn't intersect the X-axis, numRoots is assigned 0. + * assigned -1 to indicate infinitely many solutions. If the equation does + * not intersect the X-axis, numRoots is assigned 0. + * * \return 0 for success, or -1 to indicate an inconsistent equation * (all coefficients 0 except for coeff[0]). * - * \pre coeff points to an array of length at least 4. - * \pre roots points to an array of length at least 3. + * \pre coeff.size() >= 4 + * \pre roots.size() >= 3 + */ +int solve_cubic(ArrayView coeff, ArrayView roots, int& numRoots); + +/*! + * \brief Deprecated pointer-based overload for \ref + * solve_cubic(ArrayView, ArrayView, int&). */ +[[deprecated("Use solve_cubic(axom::ArrayView, axom::ArrayView, int&) instead.")]] int solve_cubic(const double* coeff, double* roots, int& numRoots); -} /* end namespace numerics */ -} /* end namespace axom */ +} // namespace numerics +} // namespace axom #endif // AXOM_NUMERICS_POLY_SOLVE_HPP_ diff --git a/src/axom/core/tests/numerics_polynomial_solvers.hpp b/src/axom/core/tests/numerics_polynomial_solvers.hpp index 9af22d6cbc..87fd3648d3 100644 --- a/src/axom/core/tests/numerics_polynomial_solvers.hpp +++ b/src/axom/core/tests/numerics_polynomial_solvers.hpp @@ -5,15 +5,22 @@ // SPDX-License-Identifier: (BSD-3-Clause) // Axom includes +#include "axom/core/Array.hpp" #include "axom/core/numerics/polynomial_solvers.hpp" #include "axom/core/utilities/Utilities.hpp" // for isNearlyEqual() // Google Test include #include "gtest/gtest.h" -int count_mismatches(double* standard, double* test, int n, double thresh = 1.0e-8); +int count_mismatches(const axom::Array& standard, + const axom::Array& test, + int n, + double thresh = 1.0e-8); -int count_mismatches(double* standard, double* test, int n, double thresh) +int count_mismatches(const axom::Array& standard, + const axom::Array& test, + int n, + double thresh) { int mcount = 0; @@ -30,9 +37,9 @@ int count_mismatches(double* standard, double* test, int n, double thresh) TEST(numerics_polynomial_solvers, solve_linear) { - double coeff[2]; - double roots[1]; - double expected[1]; + axom::Array coeff {0.0, 0.0}; + axom::Array roots {0.0}; + axom::Array expected {0.0}; int n = 1; int rc; @@ -44,7 +51,7 @@ TEST(numerics_polynomial_solvers, solve_linear) coeff[1] = 1; roots[0] = 0; expected[0] = 0; - rc = axom::numerics::solve_linear(coeff, roots, n); + rc = axom::numerics::solve_linear(coeff.view(), roots.view(), n); EXPECT_EQ(rc, 0); EXPECT_EQ(n, 1); EXPECT_EQ(count_mismatches(expected, roots, 1), 0); @@ -56,7 +63,7 @@ TEST(numerics_polynomial_solvers, solve_linear) coeff[1] = 18; roots[0] = 0; expected[0] = 0; - rc = axom::numerics::solve_linear(coeff, roots, n); + rc = axom::numerics::solve_linear(coeff.view(), roots.view(), n); EXPECT_EQ(rc, 0); EXPECT_EQ(n, 1); EXPECT_EQ(count_mismatches(expected, roots, 1), 0); @@ -68,7 +75,7 @@ TEST(numerics_polynomial_solvers, solve_linear) coeff[1] = 0.5; roots[0] = 0; expected[0] = 2; - rc = axom::numerics::solve_linear(coeff, roots, n); + rc = axom::numerics::solve_linear(coeff.view(), roots.view(), n); EXPECT_EQ(rc, 0); EXPECT_EQ(n, 1); EXPECT_EQ(count_mismatches(expected, roots, 1), 0); @@ -80,7 +87,7 @@ TEST(numerics_polynomial_solvers, solve_linear) coeff[1] = -1; roots[0] = 0; expected[0] = 0.5; - rc = axom::numerics::solve_linear(coeff, roots, n); + rc = axom::numerics::solve_linear(coeff.view(), roots.view(), n); EXPECT_EQ(rc, 0); EXPECT_EQ(n, 1); EXPECT_EQ(count_mismatches(expected, roots, 1), 0); @@ -92,7 +99,7 @@ TEST(numerics_polynomial_solvers, solve_linear) coeff[1] = 0; roots[0] = 0; expected[0] = 0; - rc = axom::numerics::solve_linear(coeff, roots, n); + rc = axom::numerics::solve_linear(coeff.view(), roots.view(), n); // rc == 0 because there are real solutions EXPECT_EQ(rc, 0); // n == -1 because there are infinitely many solutions @@ -103,9 +110,9 @@ TEST(numerics_polynomial_solvers, solve_linear) TEST(numerics_polynomial_solvers, solve_quadratic) { - double coeff[3]; - double roots[2]; - double expected[2]; + axom::Array coeff {0.0, 0.0, 0.0}; + axom::Array roots {0.0, 0.0}; + axom::Array expected {0.0, 0.0}; int n = 2; int rc; @@ -121,7 +128,7 @@ TEST(numerics_polynomial_solvers, solve_quadratic) roots[1] = 0; expected[0] = -2.3; expected[1] = -2.3; - rc = axom::numerics::solve_quadratic(coeff, roots, n); + rc = axom::numerics::solve_quadratic(coeff.view(), roots.view(), n); EXPECT_EQ(rc, 0); EXPECT_EQ(n, 1); EXPECT_EQ(count_mismatches(expected, roots, 2), 0); @@ -137,7 +144,7 @@ TEST(numerics_polynomial_solvers, solve_quadratic) roots[1] = 0; expected[0] = 1.5; expected[1] = 1.5; - rc = axom::numerics::solve_quadratic(coeff, roots, n); + rc = axom::numerics::solve_quadratic(coeff.view(), roots.view(), n); EXPECT_EQ(rc, 0); EXPECT_EQ(n, 1); EXPECT_EQ(count_mismatches(expected, roots, 2), 0); @@ -153,7 +160,7 @@ TEST(numerics_polynomial_solvers, solve_quadratic) roots[1] = 0; expected[0] = 2; expected[1] = -0.7; - rc = axom::numerics::solve_quadratic(coeff, roots, n); + rc = axom::numerics::solve_quadratic(coeff.view(), roots.view(), n); EXPECT_EQ(rc, 0); EXPECT_EQ(n, 2); EXPECT_EQ(count_mismatches(expected, roots, 2), 0); @@ -169,7 +176,7 @@ TEST(numerics_polynomial_solvers, solve_quadratic) roots[1] = 0; expected[0] = 0; expected[1] = 0; - rc = axom::numerics::solve_quadratic(coeff, roots, n); + rc = axom::numerics::solve_quadratic(coeff.view(), roots.view(), n); EXPECT_EQ(rc, -1); EXPECT_EQ(n, 0); EXPECT_EQ(count_mismatches(expected, roots, 2), 0); @@ -185,7 +192,7 @@ TEST(numerics_polynomial_solvers, solve_quadratic) roots[1] = 0; expected[0] = 0; expected[1] = 0; - rc = axom::numerics::solve_quadratic(coeff, roots, n); + rc = axom::numerics::solve_quadratic(coeff.view(), roots.view(), n); EXPECT_EQ(rc, -1); EXPECT_EQ(n, 0); EXPECT_EQ(count_mismatches(expected, roots, 2), 0); @@ -194,9 +201,9 @@ TEST(numerics_polynomial_solvers, solve_quadratic) TEST(numerics_polynomial_solvers, solve_cubic) { - double coeff[4]; - double roots[3]; - double expected[3]; + axom::Array coeff {0.0, 0.0, 0.0, 0.0}; + axom::Array roots {0.0, 0.0, 0.0}; + axom::Array expected {0.0, 0.0, 0.0}; int n = 3; int rc; @@ -215,7 +222,7 @@ TEST(numerics_polynomial_solvers, solve_cubic) expected[0] = 1.2; expected[1] = 1.2; expected[2] = 1.2; - rc = axom::numerics::solve_cubic(coeff, roots, n); + rc = axom::numerics::solve_cubic(coeff.view(), roots.view(), n); EXPECT_EQ(rc, 0); EXPECT_EQ(n, 1); EXPECT_EQ(count_mismatches(expected, roots, 3, 5.0e-5), 0); @@ -234,7 +241,7 @@ TEST(numerics_polynomial_solvers, solve_cubic) expected[0] = 2; expected[1] = 0; expected[2] = 0; - rc = axom::numerics::solve_cubic(coeff, roots, n); + rc = axom::numerics::solve_cubic(coeff.view(), roots.view(), n); EXPECT_EQ(rc, 0); EXPECT_EQ(n, 1); EXPECT_EQ(count_mismatches(expected, roots, 3), 0); @@ -253,7 +260,7 @@ TEST(numerics_polynomial_solvers, solve_cubic) expected[0] = 3; expected[1] = -2; expected[2] = -2; - rc = axom::numerics::solve_cubic(coeff, roots, n); + rc = axom::numerics::solve_cubic(coeff.view(), roots.view(), n); EXPECT_EQ(rc, 0); EXPECT_EQ(n, 2); EXPECT_EQ(count_mismatches(expected, roots, 3), 0); @@ -272,7 +279,7 @@ TEST(numerics_polynomial_solvers, solve_cubic) expected[0] = 8; expected[1] = 1; expected[2] = -0.8; - rc = axom::numerics::solve_cubic(coeff, roots, n); + rc = axom::numerics::solve_cubic(coeff.view(), roots.view(), n); EXPECT_EQ(rc, 0); EXPECT_EQ(n, 3); EXPECT_EQ(count_mismatches(expected, roots, 3), 0); @@ -292,7 +299,7 @@ TEST(numerics_polynomial_solvers, solve_cubic) expected[0] = 0.001; expected[1] = -1; expected[2] = -38; - rc = axom::numerics::solve_cubic(coeff, roots, n); + rc = axom::numerics::solve_cubic(coeff.view(), roots.view(), n); EXPECT_EQ(rc, 0); EXPECT_EQ(n, 3); EXPECT_EQ(count_mismatches(expected, roots, 3), 0); From dbafcc4246a020dd49fcc4dbca58fb3f0027ce97 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 12 May 2026 01:16:39 -0700 Subject: [PATCH 270/986] Adds a Durand-Kerner polynomial solver --- src/axom/core/numerics/polynomial_solvers.cpp | 143 ++++++++++++++++++ src/axom/core/numerics/polynomial_solvers.hpp | 95 +++++++++++- .../tests/numerics_polynomial_solvers.hpp | 88 +++++++++++ 3 files changed, 323 insertions(+), 3 deletions(-) diff --git a/src/axom/core/numerics/polynomial_solvers.cpp b/src/axom/core/numerics/polynomial_solvers.cpp index 2461f0015b..f215ac81a3 100644 --- a/src/axom/core/numerics/polynomial_solvers.cpp +++ b/src/axom/core/numerics/polynomial_solvers.cpp @@ -7,13 +7,156 @@ #include "axom/core/utilities/Utilities.hpp" #include "axom/core/numerics/polynomial_solvers.hpp" +#include #include #include +#include namespace axom { namespace numerics { +namespace +{ +using Complex = std::complex; +} + +//------------------------------------------------------------------------------ +axom::Array bernstein_to_monomial(ArrayView bernstein_coeffs) +{ + const axom::IndexType num_coeffs = bernstein_coeffs.size(); + axom::Array monomial_coeffs(num_coeffs, num_coeffs); + for(axom::IndexType i = 0; i < num_coeffs; ++i) + { + monomial_coeffs[i] = bernstein_coeffs[i]; + } + + const int degree = static_cast(bernstein_coeffs.size()) - 1; + + for(int order = degree; order >= 1; --order) + { + for(int j = order; j <= degree; ++j) + { + monomial_coeffs[j] -= monomial_coeffs[j - 1]; + } + } + + for(int k = 0; k <= degree; ++k) + { + monomial_coeffs[k] *= axom::utilities::binomialCoefficient(degree, k); + } + + return monomial_coeffs; +} + +//------------------------------------------------------------------------------ +int effective_polynomial_degree(ArrayView coeffs_ascending, double tol) +{ + int degree = static_cast(coeffs_ascending.size()) - 1; + while(degree > 0 && std::abs(coeffs_ascending[degree]) <= tol) + { + --degree; + } + return degree; +} + +//------------------------------------------------------------------------------ +PolynomialRootResult solve_polynomial_durand_kerner_checked(ArrayView coeffs_ascending, + double tol, + int max_iters) +{ + PolynomialRootResult result; + const int degree = static_cast(coeffs_ascending.size()) - 1; + if(degree <= 0) + { + result.converged = true; + return result; + } + + const int effective_degree = effective_polynomial_degree(coeffs_ascending, tol); + result.effective_degree = effective_degree; + if(effective_degree <= 0) + { + result.converged = true; + return result; + } + + axom::Array coeffs_descending(effective_degree + 1, effective_degree + 1); + for(int i = 0; i <= effective_degree; ++i) + { + coeffs_descending[i] = + coeffs_ascending[effective_degree - i] / coeffs_ascending[effective_degree]; + } + + result.roots.resize(effective_degree); + const Complex seed_center {0.4, 0.9}; + for(int i = 0; i < effective_degree; ++i) + { + result.roots[i] = std::pow(seed_center, i + 1); + } + + for(int iter = 0; iter < max_iters; ++iter) + { + double max_update = 0.0; + for(int i = 0; i < effective_degree; ++i) + { + Complex denom {1.0, 0.0}; + for(int j = 0; j < effective_degree; ++j) + { + if(i != j) + { + denom *= result.roots[i] - result.roots[j]; + } + } + + if(std::abs(denom) <= tol) + { + denom = Complex {tol, tol}; + } + + const Complex update = evaluate_polynomial(coeffs_descending.view(), result.roots[i]) / denom; + result.roots[i] -= update; + max_update = axom::utilities::max(max_update, std::abs(update)); + } + + result.iterations = iter + 1; + result.max_update = max_update; + if(max_update <= tol) + { + result.converged = true; + break; + } + } + + for(const auto& root : result.roots) + { + result.max_residual = + axom::utilities::max(result.max_residual, + std::abs(evaluate_polynomial(coeffs_descending.view(), root))); + } + + result.converged = result.converged && result.max_residual <= 100.0 * tol; + + std::sort(result.roots.begin(), result.roots.end(), [](const Complex& lhs, const Complex& rhs) { + if(lhs.real() != rhs.real()) + { + return lhs.real() < rhs.real(); + } + return lhs.imag() < rhs.imag(); + }); + + return result; +} + +//------------------------------------------------------------------------------ +axom::Array solve_polynomial_durand_kerner(ArrayView coeffs_ascending, + double tol) +{ + const auto result = solve_polynomial_durand_kerner_checked(coeffs_ascending, tol); + return result.converged ? result.roots : axom::Array {}; +} + +//------------------------------------------------------------------------------ int solve_linear(ArrayView coeff, ArrayView roots, int& numRoots) { assert(coeff.size() >= 2); diff --git a/src/axom/core/numerics/polynomial_solvers.hpp b/src/axom/core/numerics/polynomial_solvers.hpp index 1cf809acf4..132552bd2e 100644 --- a/src/axom/core/numerics/polynomial_solvers.hpp +++ b/src/axom/core/numerics/polynomial_solvers.hpp @@ -7,8 +7,11 @@ #ifndef AXOM_NUMERICS_POLY_SOLVE_HPP_ #define AXOM_NUMERICS_POLY_SOLVE_HPP_ +#include "axom/core/Array.hpp" #include "axom/core/ArrayView.hpp" +#include + /*! * \file polynomial_solve.hpp * The functions declared in this header file find real roots of polynomials @@ -29,6 +32,89 @@ namespace axom namespace numerics { +/*! + * \brief Evaluate a polynomial with coefficients stored in descending power order. + * + * \tparam ScalarType The scalar type used for evaluation, e.g. `double` or `std::complex`. + * \param [in] coeffs_descending Polynomial coefficients in descending power order. + * \param [in] x The evaluation point. + * + * \return The polynomial value at `x`. + */ +template +ScalarType evaluate_polynomial(ArrayView coeffs_descending, const ScalarType& x) +{ + ScalarType value {0.0}; + for(double coeff : coeffs_descending) + { + value = value * x + coeff; + } + return value; +} + +/*! + * \brief Convert Bernstein coefficients to monomial coefficients on `[0, 1]`. + * + * Given Bernstein coefficients \f$ b_i \f$ of degree \f$ n \f$, this returns + * monomial coefficients \f$ a_i \f$ such that + * \f$ \sum_i b_i B_i^n(t) = \sum_i a_i t^i \f$. + * + * \param [in] bernstein_coeffs Coefficients in the Bernstein basis. + * + * \return Coefficients in ascending monomial order. + */ +axom::Array bernstein_to_monomial(ArrayView bernstein_coeffs); + +/*! + * \brief Return the effective degree of a polynomial after trimming near-zero + * leading coefficients. + * + * \param [in] coeffs_ascending Polynomial coefficients in ascending power order. + * \param [in] tol Coefficients with magnitude at or below this threshold are + * treated as zero when trimming the highest-degree terms. + * + * \return The largest exponent whose coefficient exceeds `tol`, or `0` if the + * polynomial is constant to within the requested tolerance. + */ +int effective_polynomial_degree(ArrayView coeffs_ascending, double tol = 1e-12); + +/// \brief Result metadata for an all-roots polynomial solve. +struct PolynomialRootResult +{ + axom::Array> roots; + bool converged {false}; + int iterations {0}; + int effective_degree {0}; + double max_update {0.0}; + double max_residual {0.0}; +}; + +/*! + * \brief Approximate all roots of a polynomial using the Durand-Kerner method. + * + * \param [in] coeffs_ascending Polynomial coefficients in ascending power order. + * \param [in] tol Iteration tolerance and effective-zero threshold. + * + * \return The polynomial roots, sorted by real part and then imaginary part, + * or an empty array if the iteration does not converge. + */ +axom::Array> solve_polynomial_durand_kerner(ArrayView coeffs_ascending, + double tol = 1e-12); + +/*! + * \brief Approximate all roots of a polynomial using the Durand-Kerner method, + * including convergence diagnostics. + * + * \param [in] coeffs_ascending Polynomial coefficients in ascending power order. + * \param [in] tol Iteration tolerance and effective-zero threshold. + * \param [in] max_iters Maximum number of Durand-Kerner iterations. + * + * \return The roots and convergence metadata for the solve. + */ +PolynomialRootResult solve_polynomial_durand_kerner_checked(ArrayView coeffs_ascending, + double tol = 1e-12, + int max_iters = 200); + /*! * \brief Find the real root for a linear equation of form \f$ ax + b = 0 \f$. * @@ -52,7 +138,8 @@ int solve_linear(ArrayView coeff, ArrayView roots, int& nu * \brief Deprecated pointer-based overload for \ref solve_linear(ArrayView, ArrayView, int&). */ -[[deprecated("Use solve_linear(axom::ArrayView, axom::ArrayView, int&) instead.")]] +[[deprecated( + "Use solve_linear(axom::ArrayView, axom::ArrayView, int&) instead.")]] int solve_linear(const double* coeff, double* roots, int& numRoots); /*! @@ -79,7 +166,8 @@ int solve_quadratic(ArrayView coeff, ArrayView roots, int& * \brief Deprecated pointer-based overload for \ref * solve_quadratic(ArrayView, ArrayView, int&). */ -[[deprecated("Use solve_quadratic(axom::ArrayView, axom::ArrayView, int&) instead.")]] +[[deprecated( + "Use solve_quadratic(axom::ArrayView, axom::ArrayView, int&) instead.")]] int solve_quadratic(const double* coeff, double* roots, int& numRoots); /*! @@ -140,7 +228,8 @@ int solve_cubic(ArrayView coeff, ArrayView roots, int& num * \brief Deprecated pointer-based overload for \ref * solve_cubic(ArrayView, ArrayView, int&). */ -[[deprecated("Use solve_cubic(axom::ArrayView, axom::ArrayView, int&) instead.")]] +[[deprecated( + "Use solve_cubic(axom::ArrayView, axom::ArrayView, int&) instead.")]] int solve_cubic(const double* coeff, double* roots, int& numRoots); } // namespace numerics diff --git a/src/axom/core/tests/numerics_polynomial_solvers.hpp b/src/axom/core/tests/numerics_polynomial_solvers.hpp index 87fd3648d3..c6f1929f43 100644 --- a/src/axom/core/tests/numerics_polynomial_solvers.hpp +++ b/src/axom/core/tests/numerics_polynomial_solvers.hpp @@ -305,3 +305,91 @@ TEST(numerics_polynomial_solvers, solve_cubic) EXPECT_EQ(count_mismatches(expected, roots, 3), 0); } } + +TEST(numerics_polynomial_solvers, bernstein_to_monomial) +{ + { + SCOPED_TRACE("Quadratic Bernstein conversion"); + const axom::Array bernstein_coeffs {1.0, 2.0, 4.0}; + const axom::Array expected_monomial {1.0, 2.0, 1.0}; + const auto monomial_coeffs = axom::numerics::bernstein_to_monomial(bernstein_coeffs.view()); + + ASSERT_EQ(expected_monomial.size(), monomial_coeffs.size()); + for(axom::IndexType i = 0; i < expected_monomial.size(); ++i) + { + EXPECT_DOUBLE_EQ(expected_monomial[i], monomial_coeffs[i]); + } + } + + { + SCOPED_TRACE("Cubic Bernstein conversion"); + const axom::Array bernstein_coeffs {2.0, 3.0, 5.0, 8.0}; + const axom::Array expected_monomial {2.0, 3.0, 3.0, 0.0}; + const auto monomial_coeffs = axom::numerics::bernstein_to_monomial(bernstein_coeffs.view()); + + ASSERT_EQ(expected_monomial.size(), monomial_coeffs.size()); + for(axom::IndexType i = 0; i < expected_monomial.size(); ++i) + { + EXPECT_DOUBLE_EQ(expected_monomial[i], monomial_coeffs[i]); + } + } +} + +TEST(numerics_polynomial_solvers, evaluate_polynomial) +{ + const axom::Array coeffs_descending {1.0, -3.0, 2.0}; + const std::complex x {1.0, 1.0}; + const std::complex value = axom::numerics::evaluate_polynomial(coeffs_descending.view(), x); + + EXPECT_DOUBLE_EQ(-1.0, value.real()); + EXPECT_DOUBLE_EQ(-1.0, value.imag()); +} + +TEST(numerics_polynomial_solvers, solve_polynomial_durand_kerner) +{ + auto expect_complex_eq = [](const axom::Array>& expected, + const axom::Array>& actual, + double tol = 1e-10) { + ASSERT_EQ(expected.size(), actual.size()); + for(axom::IndexType i = 0; i < expected.size(); ++i) + { + EXPECT_NEAR(expected[i].real(), actual[i].real(), tol); + EXPECT_NEAR(expected[i].imag(), actual[i].imag(), tol); + } + }; + + { + SCOPED_TRACE("Two distinct real roots"); + const axom::Array coeffs_ascending {6.0, -5.0, 1.0}; + axom::Array> expected_roots {std::complex {2.0, 0.0}, + std::complex {3.0, 0.0}}; + const auto result = + axom::numerics::solve_polynomial_durand_kerner_checked(coeffs_ascending.view()); + EXPECT_TRUE(result.converged); + EXPECT_EQ(result.effective_degree, 2); + EXPECT_LE(result.max_residual, 1e-10); + expect_complex_eq(expected_roots, result.roots); + } + + { + SCOPED_TRACE("Purely imaginary roots"); + const axom::Array coeffs_ascending {1.0, 0.0, 1.0}; + axom::Array> expected_roots {std::complex {0.0, -1.0}, + std::complex {0.0, 1.0}}; + const auto result = + axom::numerics::solve_polynomial_durand_kerner_checked(coeffs_ascending.view()); + EXPECT_TRUE(result.converged); + EXPECT_EQ(result.effective_degree, 2); + EXPECT_LE(result.max_residual, 1e-10); + expect_complex_eq(expected_roots, result.roots); + } + + { + SCOPED_TRACE("Trailing zero high-order coefficient is ignored"); + const axom::Array coeffs_ascending {2.0, -3.0, 1.0, 0.0}; + axom::Array> expected_roots {std::complex {1.0, 0.0}, + std::complex {2.0, 0.0}}; + const auto roots = axom::numerics::solve_polynomial_durand_kerner(coeffs_ascending.view()); + expect_complex_eq(expected_roots, roots); + } +} From 8c50d2173d7ddd53ae324d3e7bc1e69d24030364 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 12 May 2026 01:35:22 -0700 Subject: [PATCH 271/986] Use a relative cutoff for the Durand-Kerner solver --- src/axom/core/numerics/polynomial_solvers.cpp | 21 +++++++++- src/axom/core/numerics/polynomial_solvers.hpp | 16 +++++--- .../tests/numerics_polynomial_solvers.hpp | 38 +++++++++++++++++++ 3 files changed, 67 insertions(+), 8 deletions(-) diff --git a/src/axom/core/numerics/polynomial_solvers.cpp b/src/axom/core/numerics/polynomial_solvers.cpp index f215ac81a3..a140d3b106 100644 --- a/src/axom/core/numerics/polynomial_solvers.cpp +++ b/src/axom/core/numerics/polynomial_solvers.cpp @@ -19,7 +19,17 @@ namespace numerics namespace { using Complex = std::complex; + +double coefficient_scale(ArrayView coeffs) +{ + double scale = 0.0; + for(double coeff : coeffs) + { + scale = axom::utilities::max(scale, std::abs(coeff)); + } + return scale; } +} // namespace //------------------------------------------------------------------------------ axom::Array bernstein_to_monomial(ArrayView bernstein_coeffs) @@ -52,8 +62,9 @@ axom::Array bernstein_to_monomial(ArrayView bernstein_coef //------------------------------------------------------------------------------ int effective_polynomial_degree(ArrayView coeffs_ascending, double tol) { + const double trim_threshold = tol * coefficient_scale(coeffs_ascending); int degree = static_cast(coeffs_ascending.size()) - 1; - while(degree > 0 && std::abs(coeffs_ascending[degree]) <= tol) + while(degree > 0 && std::abs(coeffs_ascending[degree]) <= trim_threshold) { --degree; } @@ -81,6 +92,8 @@ PolynomialRootResult solve_polynomial_durand_kerner_checked(ArrayView coeffs_descending(effective_degree + 1, effective_degree + 1); for(int i = 0; i <= effective_degree; ++i) { @@ -109,6 +122,8 @@ PolynomialRootResult solve_polynomial_durand_kerner_checked(ArrayView bernstein_to_monomial(ArrayView bernstein_coef * leading coefficients. * * \param [in] coeffs_ascending Polynomial coefficients in ascending power order. - * \param [in] tol Coefficients with magnitude at or below this threshold are - * treated as zero when trimming the highest-degree terms. + * \param [in] tol Relative trimming tolerance. Coefficients with magnitude at + * or below `tol * max(abs(coeffs_ascending))` are treated as zero when + * trimming the highest-degree terms. * - * \return The largest exponent whose coefficient exceeds `tol`, or `0` if the - * polynomial is constant to within the requested tolerance. + * \return The largest exponent whose coefficient exceeds the scaled trimming + * threshold, or `0` if the polynomial is constant to within the requested + * tolerance. */ int effective_polynomial_degree(ArrayView coeffs_ascending, double tol = 1e-12); @@ -93,7 +95,8 @@ struct PolynomialRootResult * \brief Approximate all roots of a polynomial using the Durand-Kerner method. * * \param [in] coeffs_ascending Polynomial coefficients in ascending power order. - * \param [in] tol Iteration tolerance and effective-zero threshold. + * \param [in] tol Iteration tolerance and relative effective-zero threshold + * used when trimming leading coefficients. * * \return The polynomial roots, sorted by real part and then imaginary part, * or an empty array if the iteration does not converge. @@ -106,7 +109,8 @@ axom::Array> solve_polynomial_durand_kerner(ArrayView coeffs_ascending {-1.0, 3.0, -3.0, 1.0}; + const auto result = + axom::numerics::solve_polynomial_durand_kerner_checked(coeffs_ascending.view()); + const auto roots = axom::numerics::solve_polynomial_durand_kerner(coeffs_ascending.view()); + + EXPECT_TRUE(result.converged); + EXPECT_EQ(result.effective_degree, 3); + EXPECT_LE(result.max_residual, 1e-10); + ASSERT_EQ(result.roots.size(), 3); + ASSERT_EQ(roots.size(), 3); + for(axom::IndexType i = 0; i < roots.size(); ++i) + { + EXPECT_NEAR(roots[i].real(), 1.0, 1e-4); + EXPECT_NEAR(roots[i].imag(), 0.0, 1e-4); + } + } + + { + SCOPED_TRACE("Low-scale polynomial keeps its effective degree"); + const axom::Array coeffs_ascending {1e-20, -2e-20, 1e-20}; + const auto result = + axom::numerics::solve_polynomial_durand_kerner_checked(coeffs_ascending.view()); + const auto roots = axom::numerics::solve_polynomial_durand_kerner(coeffs_ascending.view()); + + EXPECT_TRUE(result.converged); + EXPECT_EQ(result.effective_degree, 2); + EXPECT_LE(result.max_residual, 1e-10); + ASSERT_EQ(result.roots.size(), 2); + ASSERT_EQ(roots.size(), 2); + for(axom::IndexType i = 0; i < roots.size(); ++i) + { + EXPECT_NEAR(roots[i].real(), 1.0, 1e-4); + EXPECT_NEAR(roots[i].imag(), 0.0, 1e-4); + } + } } From 02f72b2b5cf0966e3caf67e956177b1c3aa557ff Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 13 May 2026 00:48:38 -0700 Subject: [PATCH 272/986] Improves comments for Durand-Kerner implementation --- src/axom/core/numerics/polynomial_solvers.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/axom/core/numerics/polynomial_solvers.cpp b/src/axom/core/numerics/polynomial_solvers.cpp index a140d3b106..35452e1320 100644 --- a/src/axom/core/numerics/polynomial_solvers.cpp +++ b/src/axom/core/numerics/polynomial_solvers.cpp @@ -34,12 +34,7 @@ double coefficient_scale(ArrayView coeffs) //------------------------------------------------------------------------------ axom::Array bernstein_to_monomial(ArrayView bernstein_coeffs) { - const axom::IndexType num_coeffs = bernstein_coeffs.size(); - axom::Array monomial_coeffs(num_coeffs, num_coeffs); - for(axom::IndexType i = 0; i < num_coeffs; ++i) - { - monomial_coeffs[i] = bernstein_coeffs[i]; - } + axom::Array monomial_coeffs(bernstein_coeffs); const int degree = static_cast(bernstein_coeffs.size()) - 1; @@ -92,8 +87,8 @@ PolynomialRootResult solve_polynomial_durand_kerner_checked(ArrayView coeffs_descending(effective_degree + 1, effective_degree + 1); for(int i = 0; i <= effective_degree; ++i) { @@ -102,12 +97,15 @@ PolynomialRootResult solve_polynomial_durand_kerner_checked(ArrayView Date: Wed, 13 May 2026 14:37:26 -0700 Subject: [PATCH 273/986] Improves comments and formatting --- src/axom/core/examples/core_numerics.cpp | 1 + src/axom/core/numerics/polynomial_solvers.cpp | 13 +- src/axom/core/numerics/polynomial_solvers.hpp | 5 +- .../tests/numerics_polynomial_solvers.hpp | 222 +++++++----------- 4 files changed, 96 insertions(+), 145 deletions(-) diff --git a/src/axom/core/examples/core_numerics.cpp b/src/axom/core/examples/core_numerics.cpp index 36c655a6a4..56f8df26b6 100644 --- a/src/axom/core/examples/core_numerics.cpp +++ b/src/axom/core/examples/core_numerics.cpp @@ -90,6 +90,7 @@ void demoVectorOps() // Find the real roots of a cubic equation. // (x + 2)(x - 1)(2x - 3) = 0 = 2x^3 - x^2 - 7x + 6 has real roots at // x = -2, x = 1, x = 1.5. + // Coefficients are in ascending power order: [c0, c1, c2, c3] for c0 + c1*x + c2*x^2 + c3*x^3 axom::Array coeff {6., -7., -1., 2.}; axom::Array roots {0., 0., 0.}; int numRoots; diff --git a/src/axom/core/numerics/polynomial_solvers.cpp b/src/axom/core/numerics/polynomial_solvers.cpp index 35452e1320..b2ee47b142 100644 --- a/src/axom/core/numerics/polynomial_solvers.cpp +++ b/src/axom/core/numerics/polynomial_solvers.cpp @@ -73,6 +73,9 @@ PolynomialRootResult solve_polynomial_durand_kerner_checked(ArrayView(coeffs_ascending.size()) - 1; + + // Degree 0 or negative indicates a constant polynomial with no finite roots. + // Empty root array with converged=true signals successful handling of the degenerate case. if(degree <= 0) { result.converged = true; @@ -81,6 +84,9 @@ PolynomialRootResult solve_polynomial_durand_kerner_checked(ArrayView> solve_polynomial_durand_kerner(ArrayView& standard, const axom::Array& test, int n, - double thresh = 1.0e-8); + double thresh = 1e-8); int count_mismatches(const axom::Array& standard, const axom::Array& test, @@ -37,20 +37,16 @@ int count_mismatches(const axom::Array& standard, TEST(numerics_polynomial_solvers, solve_linear) { - axom::Array coeff {0.0, 0.0}; - axom::Array roots {0.0}; - axom::Array expected {0.0}; int n = 1; - int rc; + int rc = -1; // In these tests, we are solving ax + b = 0, so // coeff[1] = a, coeff[0] = b. { SCOPED_TRACE("Line 1 through origin."); - coeff[0] = 0; - coeff[1] = 1; - roots[0] = 0; - expected[0] = 0; + axom::Array coeff {0., 1.}; + axom::Array roots {0.}; + axom::Array expected {0.}; rc = axom::numerics::solve_linear(coeff.view(), roots.view(), n); EXPECT_EQ(rc, 0); EXPECT_EQ(n, 1); @@ -59,10 +55,9 @@ TEST(numerics_polynomial_solvers, solve_linear) { SCOPED_TRACE("Line 2 through origin."); - coeff[0] = 0; - coeff[1] = 18; - roots[0] = 0; - expected[0] = 0; + axom::Array coeff {0., 18.}; + axom::Array roots {0.}; + axom::Array expected {0.}; rc = axom::numerics::solve_linear(coeff.view(), roots.view(), n); EXPECT_EQ(rc, 0); EXPECT_EQ(n, 1); @@ -71,10 +66,9 @@ TEST(numerics_polynomial_solvers, solve_linear) { SCOPED_TRACE("Off origin 1"); - coeff[0] = -1; - coeff[1] = 0.5; - roots[0] = 0; - expected[0] = 2; + axom::Array coeff {-1., 0.5}; + axom::Array roots {0.}; + axom::Array expected {2.}; rc = axom::numerics::solve_linear(coeff.view(), roots.view(), n); EXPECT_EQ(rc, 0); EXPECT_EQ(n, 1); @@ -83,10 +77,9 @@ TEST(numerics_polynomial_solvers, solve_linear) { SCOPED_TRACE("Off origin 2"); - coeff[0] = 0.5; - coeff[1] = -1; - roots[0] = 0; - expected[0] = 0.5; + axom::Array coeff {0.5, -1}; + axom::Array roots {0.}; + axom::Array expected {.5}; rc = axom::numerics::solve_linear(coeff.view(), roots.view(), n); EXPECT_EQ(rc, 0); EXPECT_EQ(n, 1); @@ -95,10 +88,9 @@ TEST(numerics_polynomial_solvers, solve_linear) { SCOPED_TRACE("X-axis"); - coeff[0] = 0; - coeff[1] = 0; - roots[0] = 0; - expected[0] = 0; + axom::Array coeff {0., 0.}; + axom::Array roots {0.}; + axom::Array expected {0.}; rc = axom::numerics::solve_linear(coeff.view(), roots.view(), n); // rc == 0 because there are real solutions EXPECT_EQ(rc, 0); @@ -110,24 +102,17 @@ TEST(numerics_polynomial_solvers, solve_linear) TEST(numerics_polynomial_solvers, solve_quadratic) { - axom::Array coeff {0.0, 0.0, 0.0}; - axom::Array roots {0.0, 0.0}; - axom::Array expected {0.0, 0.0}; int n = 2; - int rc; + int rc = -1; // In these tests, we are solving ax^2 + bx + c = 0, so // coeff[2] = a, coeff[1] = b, coeff[0] = c. { // y = (x + 2.3)(x + 2.3) SCOPED_TRACE("Double root at x = -2.3"); - coeff[0] = 5.29; - coeff[1] = 4.6; - coeff[2] = 1; - roots[0] = 0; - roots[1] = 0; - expected[0] = -2.3; - expected[1] = -2.3; + axom::Array coeff {5.29, 4.6, 1.}; + axom::Array roots {0., 0.}; + axom::Array expected {-2.3, -2.3}; rc = axom::numerics::solve_quadratic(coeff.view(), roots.view(), n); EXPECT_EQ(rc, 0); EXPECT_EQ(n, 1); @@ -137,13 +122,9 @@ TEST(numerics_polynomial_solvers, solve_quadratic) { // y = (-x + 1.5)(x - 1.5) SCOPED_TRACE("Double root at x = 1.5 (opening down)"); - coeff[0] = -2.25; - coeff[1] = 3; - coeff[2] = -1; - roots[0] = 0; - roots[1] = 0; - expected[0] = 1.5; - expected[1] = 1.5; + axom::Array coeff {-2.25, 3., -1.}; + axom::Array roots {0., 0.}; + axom::Array expected {1.5, 1.5}; rc = axom::numerics::solve_quadratic(coeff.view(), roots.view(), n); EXPECT_EQ(rc, 0); EXPECT_EQ(n, 1); @@ -153,13 +134,9 @@ TEST(numerics_polynomial_solvers, solve_quadratic) { // y = 3.2(x + 0.7)(x - 2) SCOPED_TRACE("Roots at -0.7 and 2"); - coeff[0] = -4.48; - coeff[1] = -4.16; - coeff[2] = 3.2; - roots[0] = 0; - roots[1] = 0; - expected[0] = 2; - expected[1] = -0.7; + axom::Array coeff {-4.48, -4.16, 3.2}; + axom::Array roots {0., 0.}; + axom::Array expected {2, -0.7}; rc = axom::numerics::solve_quadratic(coeff.view(), roots.view(), n); EXPECT_EQ(rc, 0); EXPECT_EQ(n, 2); @@ -169,13 +146,9 @@ TEST(numerics_polynomial_solvers, solve_quadratic) { // y = 0.1x^2 + 0.2x + 6 SCOPED_TRACE("No real roots (opening up)"); - coeff[0] = 6; - coeff[1] = 0.2; - coeff[2] = 0.1; - roots[0] = 0; - roots[1] = 0; - expected[0] = 0; - expected[1] = 0; + axom::Array coeff {6, 0.2, 0.1}; + axom::Array roots {0., 0.}; + axom::Array expected {0., 0.}; rc = axom::numerics::solve_quadratic(coeff.view(), roots.view(), n); EXPECT_EQ(rc, -1); EXPECT_EQ(n, 0); @@ -185,13 +158,9 @@ TEST(numerics_polynomial_solvers, solve_quadratic) { // y = -5x^2 + 0.2x -20 SCOPED_TRACE("No real roots (opening up)"); - coeff[0] = 6; - coeff[1] = 0.2; - coeff[2] = 0.1; - roots[0] = 0; - roots[1] = 0; - expected[0] = 0; - expected[1] = 0; + axom::Array coeff {6, 0.2, 0.1}; + axom::Array roots {0., 0.}; + axom::Array expected {0., 0.}; rc = axom::numerics::solve_quadratic(coeff.view(), roots.view(), n); EXPECT_EQ(rc, -1); EXPECT_EQ(n, 0); @@ -201,27 +170,20 @@ TEST(numerics_polynomial_solvers, solve_quadratic) TEST(numerics_polynomial_solvers, solve_cubic) { - axom::Array coeff {0.0, 0.0, 0.0, 0.0}; - axom::Array roots {0.0, 0.0, 0.0}; - axom::Array expected {0.0, 0.0, 0.0}; int n = 3; - int rc; + int rc = -1; // In these tests, we are solving ax^3 + bx^2 + cx + d = 0, so // coeff[3] = a, coeff[2] = b, coeff[1] = c, coeff[0] = d. { // y = (x - 1.2)^3 = 1.0 x^3 - 3.6 x^2 + 4.32 x - 1.728 - SCOPED_TRACE("Triple root at x = 1.2 (NOTE: LOOSE TOLERANCE HERE)"); - coeff[0] = -1.728; - coeff[1] = 4.32; - coeff[2] = -3.6; - coeff[3] = 1; - roots[0] = 0; - roots[1] = 0; - roots[2] = 0; - expected[0] = 1.2; - expected[1] = 1.2; - expected[2] = 1.2; + // Repeated roots have inherently poor numerical conditioning in polynomial solvers. + // We use a looser tolerance here (5e-5 vs default 1e-8) because the discriminant + // is nearly zero and floating-point error accumulates during the triple root calculation. + SCOPED_TRACE("Triple root at x = 1.2 (loose tolerance due to repeated root conditioning)"); + axom::Array coeff {-1.728, 4.32, -3.6, 1}; + axom::Array roots {0., 0., 0.}; + axom::Array expected {1.2, 1.2, 1.2}; rc = axom::numerics::solve_cubic(coeff.view(), roots.view(), n); EXPECT_EQ(rc, 0); EXPECT_EQ(n, 1); @@ -231,16 +193,9 @@ TEST(numerics_polynomial_solvers, solve_cubic) { // y = -x^3 - x + 10 SCOPED_TRACE("Single real root at x = 2"); - coeff[0] = 10; - coeff[1] = -1; - coeff[2] = 0; - coeff[3] = -1; - roots[0] = 0; - roots[1] = 0; - roots[2] = 0; - expected[0] = 2; - expected[1] = 0; - expected[2] = 0; + axom::Array coeff {10, -1, 0, -1}; + axom::Array roots {0., 0., 0.}; + axom::Array expected {2., 0., 0.}; rc = axom::numerics::solve_cubic(coeff.view(), roots.view(), n); EXPECT_EQ(rc, 0); EXPECT_EQ(n, 1); @@ -250,16 +205,9 @@ TEST(numerics_polynomial_solvers, solve_cubic) { // y = x^3 + x^2 - 8*x - 12 SCOPED_TRACE("Two real roots, at x = 3, twice at x = -2"); - coeff[0] = -12; - coeff[1] = -8; - coeff[2] = 1; - coeff[3] = 1; - roots[0] = 0; - roots[1] = 0; - roots[2] = 0; - expected[0] = 3; - expected[1] = -2; - expected[2] = -2; + axom::Array coeff {-12, -8, 1, 1}; + axom::Array roots {0., 0., 0.}; + axom::Array expected {3, -2, -2}; rc = axom::numerics::solve_cubic(coeff.view(), roots.view(), n); EXPECT_EQ(rc, 0); EXPECT_EQ(n, 2); @@ -269,16 +217,9 @@ TEST(numerics_polynomial_solvers, solve_cubic) { // y = (x + 0.8)(-x + 1)(x - 8) = -3x^3 + 24.6x^2 - 2.4x - 19.2 SCOPED_TRACE("Three real roots, at x = -0.8, 1, 8"); - coeff[0] = -19.2; - coeff[1] = -2.4; - coeff[2] = 24.6; - coeff[3] = -3; - roots[0] = 0; - roots[1] = 0; - roots[2] = 0; - expected[0] = 8; - expected[1] = 1; - expected[2] = -0.8; + axom::Array coeff {-19.2, -2.4, 24.6, -3}; + axom::Array roots {0., 0., 0.}; + axom::Array expected {8, 1, -0.8}; rc = axom::numerics::solve_cubic(coeff.view(), roots.view(), n); EXPECT_EQ(rc, 0); EXPECT_EQ(n, 3); @@ -289,16 +230,9 @@ TEST(numerics_polynomial_solvers, solve_cubic) // y = 4.3(x + 38)(x + 1)(x - 0.001) // = 4.3x^3 + 167.6957x^2 + 163.2323x - 0.1634 SCOPED_TRACE("Three real roots, at x = -38, -1, 0.001"); - coeff[0] = -0.1634; - coeff[1] = 163.2323; - coeff[2] = 167.6957; - coeff[3] = 4.3; - roots[0] = 0; - roots[1] = 0; - roots[2] = 0; - expected[0] = 0.001; - expected[1] = -1; - expected[2] = -38; + axom::Array coeff {-0.1634, 163.2323, 167.6957, 4.3}; + axom::Array roots {0., 0., 0.}; + axom::Array expected {0.001, -1, -38}; rc = axom::numerics::solve_cubic(coeff.view(), roots.view(), n); EXPECT_EQ(rc, 0); EXPECT_EQ(n, 3); @@ -310,8 +244,8 @@ TEST(numerics_polynomial_solvers, bernstein_to_monomial) { { SCOPED_TRACE("Quadratic Bernstein conversion"); - const axom::Array bernstein_coeffs {1.0, 2.0, 4.0}; - const axom::Array expected_monomial {1.0, 2.0, 1.0}; + const axom::Array bernstein_coeffs {1., 2., 4.}; + const axom::Array expected_monomial {1., 2., 1.}; const auto monomial_coeffs = axom::numerics::bernstein_to_monomial(bernstein_coeffs.view()); ASSERT_EQ(expected_monomial.size(), monomial_coeffs.size()); @@ -323,8 +257,8 @@ TEST(numerics_polynomial_solvers, bernstein_to_monomial) { SCOPED_TRACE("Cubic Bernstein conversion"); - const axom::Array bernstein_coeffs {2.0, 3.0, 5.0, 8.0}; - const axom::Array expected_monomial {2.0, 3.0, 3.0, 0.0}; + const axom::Array bernstein_coeffs {2., 3., 5., 8.}; + const axom::Array expected_monomial {2., 3., 3., 0.}; const auto monomial_coeffs = axom::numerics::bernstein_to_monomial(bernstein_coeffs.view()); ASSERT_EQ(expected_monomial.size(), monomial_coeffs.size()); @@ -337,12 +271,12 @@ TEST(numerics_polynomial_solvers, bernstein_to_monomial) TEST(numerics_polynomial_solvers, evaluate_polynomial) { - const axom::Array coeffs_descending {1.0, -3.0, 2.0}; - const std::complex x {1.0, 1.0}; + const axom::Array coeffs_descending {1., -3., 2.}; + const std::complex x {1., 1.}; const std::complex value = axom::numerics::evaluate_polynomial(coeffs_descending.view(), x); - EXPECT_DOUBLE_EQ(-1.0, value.real()); - EXPECT_DOUBLE_EQ(-1.0, value.imag()); + EXPECT_DOUBLE_EQ(-1., value.real()); + EXPECT_DOUBLE_EQ(-1., value.imag()); } TEST(numerics_polynomial_solvers, solve_polynomial_durand_kerner) @@ -360,9 +294,9 @@ TEST(numerics_polynomial_solvers, solve_polynomial_durand_kerner) { SCOPED_TRACE("Two distinct real roots"); - const axom::Array coeffs_ascending {6.0, -5.0, 1.0}; - axom::Array> expected_roots {std::complex {2.0, 0.0}, - std::complex {3.0, 0.0}}; + const axom::Array coeffs_ascending {6., -5., 1.}; + axom::Array> expected_roots {std::complex {2., 0.}, + std::complex {3., 0.}}; const auto result = axom::numerics::solve_polynomial_durand_kerner_checked(coeffs_ascending.view()); EXPECT_TRUE(result.converged); @@ -373,9 +307,9 @@ TEST(numerics_polynomial_solvers, solve_polynomial_durand_kerner) { SCOPED_TRACE("Purely imaginary roots"); - const axom::Array coeffs_ascending {1.0, 0.0, 1.0}; - axom::Array> expected_roots {std::complex {0.0, -1.0}, - std::complex {0.0, 1.0}}; + const axom::Array coeffs_ascending {1., 0., 1.}; + axom::Array> expected_roots {std::complex {0., -1.}, + std::complex {0., 1.}}; const auto result = axom::numerics::solve_polynomial_durand_kerner_checked(coeffs_ascending.view()); EXPECT_TRUE(result.converged); @@ -386,16 +320,19 @@ TEST(numerics_polynomial_solvers, solve_polynomial_durand_kerner) { SCOPED_TRACE("Trailing zero high-order coefficient is ignored"); - const axom::Array coeffs_ascending {2.0, -3.0, 1.0, 0.0}; - axom::Array> expected_roots {std::complex {1.0, 0.0}, - std::complex {2.0, 0.0}}; + const axom::Array coeffs_ascending {2., -3., 1., 0.}; + axom::Array> expected_roots {std::complex {1., 0.}, + std::complex {2., 0.}}; const auto roots = axom::numerics::solve_polynomial_durand_kerner(coeffs_ascending.view()); expect_complex_eq(expected_roots, roots); } { + // Durand-Kerner converges slowly near repeated roots due to the denominator + // becoming small (product of differences approaches zero). We use 1e-4 tolerance + // here because the roots cluster rather than separate to machine precision. SCOPED_TRACE("Repeated-root cubic is retained when residual is small"); - const axom::Array coeffs_ascending {-1.0, 3.0, -3.0, 1.0}; + const axom::Array coeffs_ascending {-1., 3., -3., 1.}; const auto result = axom::numerics::solve_polynomial_durand_kerner_checked(coeffs_ascending.view()); const auto roots = axom::numerics::solve_polynomial_durand_kerner(coeffs_ascending.view()); @@ -407,12 +344,15 @@ TEST(numerics_polynomial_solvers, solve_polynomial_durand_kerner) ASSERT_EQ(roots.size(), 3); for(axom::IndexType i = 0; i < roots.size(); ++i) { - EXPECT_NEAR(roots[i].real(), 1.0, 1e-4); - EXPECT_NEAR(roots[i].imag(), 0.0, 1e-4); + EXPECT_NEAR(roots[i].real(), 1., 1e-4); + EXPECT_NEAR(roots[i].imag(), 0., 1e-4); } } { + // Despite tiny coefficients (1e-20 scale), the polynomial (t-1)^2 has the same + // root structure. The repeated root at t=1 still exhibits clustering behavior, + // so we use 1e-4 tolerance to account for the slow Durand-Kerner convergence. SCOPED_TRACE("Low-scale polynomial keeps its effective degree"); const axom::Array coeffs_ascending {1e-20, -2e-20, 1e-20}; const auto result = @@ -426,8 +366,8 @@ TEST(numerics_polynomial_solvers, solve_polynomial_durand_kerner) ASSERT_EQ(roots.size(), 2); for(axom::IndexType i = 0; i < roots.size(); ++i) { - EXPECT_NEAR(roots[i].real(), 1.0, 1e-4); - EXPECT_NEAR(roots[i].imag(), 0.0, 1e-4); + EXPECT_NEAR(roots[i].real(), 1., 1e-4); + EXPECT_NEAR(roots[i].imag(), 0., 1e-4); } } } From 91dcfbfdd42550d5159303626ac61a8b3d6718bf Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 13 May 2026 14:40:51 -0700 Subject: [PATCH 274/986] Updates RELEASE-NOTES --- RELEASE-NOTES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index f656847553..0cc4eee6f2 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -27,10 +27,13 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ - Quest: Adds support for reading mfem files with variable order NURBS curves (requires mfem>4.9). - Quest: Adds OMP support for fast GWN methods for STL/Triangulated STEP input and linearized NURBS Curve input. - Klee: Adds an optional "center" parameter in scale operators that permits scaling relative to a custom center point. +- 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` ### Removed ### Deprecated +- Core: Deprecates the pointer-based interface to linear-, quadratic- and cubic- polynomial solvers in favor of an ArrayView-based interface ### Changed - Updates CMake code check targets to only use checked in files (via `git ls-files`, when available) From 25453724509b48e468f3b2a37b51f658cb541e9d Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 14 May 2026 15:44:10 -0700 Subject: [PATCH 275/986] Adds static_assert for a constrained type --- src/axom/core/numerics/polynomial_solvers.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/axom/core/numerics/polynomial_solvers.hpp b/src/axom/core/numerics/polynomial_solvers.hpp index cef25002d5..0275f921ca 100644 --- a/src/axom/core/numerics/polynomial_solvers.hpp +++ b/src/axom/core/numerics/polynomial_solvers.hpp @@ -11,6 +11,7 @@ #include "axom/core/ArrayView.hpp" #include +#include /*! * \file polynomial_solve.hpp @@ -44,6 +45,10 @@ namespace numerics template ScalarType evaluate_polynomial(ArrayView coeffs_descending, const ScalarType& x) { + static_assert( + std::is_same_v || std::is_same_v>, + "evaluate_polynomial requires ScalarType to be double or std::complex."); + ScalarType value {0.0}; for(double coeff : coeffs_descending) { From a1f685a9d9ce9ebc8d120b16075b761ba0f0555e Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 14 May 2026 16:40:13 -0700 Subject: [PATCH 276/986] Exposes seed value for Durand-Kerner polynomial solver Per PR request. --- src/axom/core/numerics/polynomial_solvers.cpp | 15 ++- src/axom/core/numerics/polynomial_solvers.hpp | 20 ++- .../tests/numerics_polynomial_solvers.hpp | 122 ++++++++++++------ 3 files changed, 108 insertions(+), 49 deletions(-) diff --git a/src/axom/core/numerics/polynomial_solvers.cpp b/src/axom/core/numerics/polynomial_solvers.cpp index b2ee47b142..67add8a000 100644 --- a/src/axom/core/numerics/polynomial_solvers.cpp +++ b/src/axom/core/numerics/polynomial_solvers.cpp @@ -69,7 +69,8 @@ int effective_polynomial_degree(ArrayView coeffs_ascending, double //------------------------------------------------------------------------------ PolynomialRootResult solve_polynomial_durand_kerner_checked(ArrayView coeffs_ascending, double tol, - int max_iters) + int max_iters, + Complex seed) { PolynomialRootResult result; const int degree = static_cast(coeffs_ascending.size()) - 1; @@ -103,11 +104,12 @@ PolynomialRootResult solve_polynomial_durand_kerner_checked(ArrayView solve_polynomial_durand_kerner(ArrayView coeffs_ascending, - double tol) + double tol, + Complex seed) { - const auto result = solve_polynomial_durand_kerner_checked(coeffs_ascending, tol); + const auto result = solve_polynomial_durand_kerner_checked(coeffs_ascending, tol, 200, seed); return result.converged ? result.roots : axom::Array {}; } diff --git a/src/axom/core/numerics/polynomial_solvers.hpp b/src/axom/core/numerics/polynomial_solvers.hpp index 0275f921ca..d8226e632b 100644 --- a/src/axom/core/numerics/polynomial_solvers.hpp +++ b/src/axom/core/numerics/polynomial_solvers.hpp @@ -102,12 +102,17 @@ struct PolynomialRootResult * \param [in] coeffs_ascending Polynomial coefficients in ascending power order. * \param [in] tol Iteration tolerance and relative effective-zero threshold * used when trimming leading coefficients. + * \param [in] seed Seed used to generate deterministic, distinct initial guesses. + * This should be a complex number on or near the unit circle (and not purely real), + * so that `seed^(i+1)` spreads the starting points around a reasonable radius. * * \return The polynomial roots, sorted by real part and then imaginary part, * or an empty array if the iteration does not converge. */ -axom::Array> solve_polynomial_durand_kerner(ArrayView coeffs_ascending, - double tol = 1e-12); +axom::Array> solve_polynomial_durand_kerner( + ArrayView coeffs_ascending, + double tol = 1e-12, + std::complex seed = std::complex {0.4, 0.9}); /*! * \brief Approximate all roots of a polynomial using the Durand-Kerner method, @@ -120,12 +125,17 @@ axom::Array> solve_polynomial_durand_kerner(ArrayView coeffs_ascending, - double tol = 1e-12, - int max_iters = 200); +PolynomialRootResult solve_polynomial_durand_kerner_checked( + ArrayView coeffs_ascending, + double tol = 1e-12, + int max_iters = 200, + std::complex seed = std::complex {0.4, 0.9}); /*! * \brief Find the real root for a linear equation of form \f$ ax + b = 0 \f$. diff --git a/src/axom/core/tests/numerics_polynomial_solvers.hpp b/src/axom/core/tests/numerics_polynomial_solvers.hpp index f0289810b4..73f939277f 100644 --- a/src/axom/core/tests/numerics_polynomial_solvers.hpp +++ b/src/axom/core/tests/numerics_polynomial_solvers.hpp @@ -292,17 +292,35 @@ TEST(numerics_polynomial_solvers, solve_polynomial_durand_kerner) } }; + const axom::Array> seeds {std::complex {0.4, 0.9}, + std::complex {0.8, 0.6}, + std::complex {0.99, 0.1}}; + + auto seed_trace = [](std::complex seed) { + return ::testing::Message() << "Seed=(" << seed.real() << ", " << seed.imag() << ")"; + }; + + constexpr int max_iters = 200; + constexpr double tol = 1e-12; + { SCOPED_TRACE("Two distinct real roots"); const axom::Array coeffs_ascending {6., -5., 1.}; axom::Array> expected_roots {std::complex {2., 0.}, std::complex {3., 0.}}; - const auto result = - axom::numerics::solve_polynomial_durand_kerner_checked(coeffs_ascending.view()); - EXPECT_TRUE(result.converged); - EXPECT_EQ(result.effective_degree, 2); - EXPECT_LE(result.max_residual, 1e-10); - expect_complex_eq(expected_roots, result.roots); + for(const auto& seed : seeds) + { + SCOPED_TRACE(seed_trace(seed)); + const auto result = + axom::numerics::solve_polynomial_durand_kerner_checked(coeffs_ascending.view(), + tol, + max_iters, + seed); + EXPECT_TRUE(result.converged); + EXPECT_EQ(result.effective_degree, 2); + EXPECT_LE(result.max_residual, 1e-10); + expect_complex_eq(expected_roots, result.roots); + } } { @@ -310,12 +328,19 @@ TEST(numerics_polynomial_solvers, solve_polynomial_durand_kerner) const axom::Array coeffs_ascending {1., 0., 1.}; axom::Array> expected_roots {std::complex {0., -1.}, std::complex {0., 1.}}; - const auto result = - axom::numerics::solve_polynomial_durand_kerner_checked(coeffs_ascending.view()); - EXPECT_TRUE(result.converged); - EXPECT_EQ(result.effective_degree, 2); - EXPECT_LE(result.max_residual, 1e-10); - expect_complex_eq(expected_roots, result.roots); + for(const auto& seed : seeds) + { + SCOPED_TRACE(seed_trace(seed)); + const auto result = + axom::numerics::solve_polynomial_durand_kerner_checked(coeffs_ascending.view(), + tol, + max_iters, + seed); + EXPECT_TRUE(result.converged); + EXPECT_EQ(result.effective_degree, 2); + EXPECT_LE(result.max_residual, 1e-10); + expect_complex_eq(expected_roots, result.roots); + } } { @@ -323,8 +348,13 @@ TEST(numerics_polynomial_solvers, solve_polynomial_durand_kerner) const axom::Array coeffs_ascending {2., -3., 1., 0.}; axom::Array> expected_roots {std::complex {1., 0.}, std::complex {2., 0.}}; - const auto roots = axom::numerics::solve_polynomial_durand_kerner(coeffs_ascending.view()); - expect_complex_eq(expected_roots, roots); + for(const auto& seed : seeds) + { + SCOPED_TRACE(seed_trace(seed)); + const auto roots = + axom::numerics::solve_polynomial_durand_kerner(coeffs_ascending.view(), tol, seed); + expect_complex_eq(expected_roots, roots); + } } { @@ -333,19 +363,27 @@ TEST(numerics_polynomial_solvers, solve_polynomial_durand_kerner) // here because the roots cluster rather than separate to machine precision. SCOPED_TRACE("Repeated-root cubic is retained when residual is small"); const axom::Array coeffs_ascending {-1., 3., -3., 1.}; - const auto result = - axom::numerics::solve_polynomial_durand_kerner_checked(coeffs_ascending.view()); - const auto roots = axom::numerics::solve_polynomial_durand_kerner(coeffs_ascending.view()); - - EXPECT_TRUE(result.converged); - EXPECT_EQ(result.effective_degree, 3); - EXPECT_LE(result.max_residual, 1e-10); - ASSERT_EQ(result.roots.size(), 3); - ASSERT_EQ(roots.size(), 3); - for(axom::IndexType i = 0; i < roots.size(); ++i) + for(const auto& seed : seeds) { - EXPECT_NEAR(roots[i].real(), 1., 1e-4); - EXPECT_NEAR(roots[i].imag(), 0., 1e-4); + SCOPED_TRACE(seed_trace(seed)); + const auto result = + axom::numerics::solve_polynomial_durand_kerner_checked(coeffs_ascending.view(), + tol, + max_iters, + seed); + const auto roots = + axom::numerics::solve_polynomial_durand_kerner(coeffs_ascending.view(), tol, seed); + + EXPECT_TRUE(result.converged); + EXPECT_EQ(result.effective_degree, 3); + EXPECT_LE(result.max_residual, 1e-10); + ASSERT_EQ(result.roots.size(), 3); + ASSERT_EQ(roots.size(), 3); + for(axom::IndexType i = 0; i < roots.size(); ++i) + { + EXPECT_NEAR(roots[i].real(), 1., 1e-4); + EXPECT_NEAR(roots[i].imag(), 0., 1e-4); + } } } @@ -355,19 +393,27 @@ TEST(numerics_polynomial_solvers, solve_polynomial_durand_kerner) // so we use 1e-4 tolerance to account for the slow Durand-Kerner convergence. SCOPED_TRACE("Low-scale polynomial keeps its effective degree"); const axom::Array coeffs_ascending {1e-20, -2e-20, 1e-20}; - const auto result = - axom::numerics::solve_polynomial_durand_kerner_checked(coeffs_ascending.view()); - const auto roots = axom::numerics::solve_polynomial_durand_kerner(coeffs_ascending.view()); - - EXPECT_TRUE(result.converged); - EXPECT_EQ(result.effective_degree, 2); - EXPECT_LE(result.max_residual, 1e-10); - ASSERT_EQ(result.roots.size(), 2); - ASSERT_EQ(roots.size(), 2); - for(axom::IndexType i = 0; i < roots.size(); ++i) + for(const auto& seed : seeds) { - EXPECT_NEAR(roots[i].real(), 1., 1e-4); - EXPECT_NEAR(roots[i].imag(), 0., 1e-4); + SCOPED_TRACE(seed_trace(seed)); + const auto result = + axom::numerics::solve_polynomial_durand_kerner_checked(coeffs_ascending.view(), + tol, + max_iters, + seed); + const auto roots = + axom::numerics::solve_polynomial_durand_kerner(coeffs_ascending.view(), tol, seed); + + EXPECT_TRUE(result.converged); + EXPECT_EQ(result.effective_degree, 2); + EXPECT_LE(result.max_residual, 1e-10); + ASSERT_EQ(result.roots.size(), 2); + ASSERT_EQ(roots.size(), 2); + for(axom::IndexType i = 0; i < roots.size(); ++i) + { + EXPECT_NEAR(roots[i].real(), 1., 1e-4); + EXPECT_NEAR(roots[i].imag(), 0., 1e-4); + } } } } From 9ddf55b3bd638d2b4d7e3d1165ce50635aafef27 Mon Sep 17 00:00:00 2001 From: Dylan Copeland Date: Mon, 18 May 2026 20:12:23 -0700 Subject: [PATCH 277/986] Documentation, minor improvements, and more unit tests. --- .../detail/intersect_bezier_impl.hpp | 18 ++++++- src/axom/primal/operators/intersect.hpp | 6 ++- src/axom/primal/tests/primal_nurbs_curve.cpp | 49 ++++++++++++++----- 3 files changed, 59 insertions(+), 14 deletions(-) diff --git a/src/axom/primal/operators/detail/intersect_bezier_impl.hpp b/src/axom/primal/operators/detail/intersect_bezier_impl.hpp index 510367ca09..0d784d87ee 100644 --- a/src/axom/primal/operators/detail/intersect_bezier_impl.hpp +++ b/src/axom/primal/operators/detail/intersect_bezier_impl.hpp @@ -553,6 +553,10 @@ bool intersect_nurbscurves(const NURBSCurve &n1, const axom::Array knots1 = n1.getKnots().getUniqueKnots(); const axom::Array knots2 = n2.getKnots().getUniqueKnots(); + const double sq_tol = tol * tol; + const int ord1 = beziers1[0].getOrder(); + const int ord2 = beziers2[0].getOrder(); + bool foundIntersection = false; // Loop over all Bezier segment pairs (is there a better way?) @@ -563,13 +567,25 @@ bool intersect_nurbscurves(const NURBSCurve &n1, axom::Array u_local, v_local; // Intersect Bezier segment i of n1 with Bezier segment j of n2 - intersect(beziers1[i], beziers2[j], u_local, v_local, tol); + intersect_bezier_curves(beziers1[i], + beziers2[j], + u_local, + v_local, + sq_tol, + ord1, + ord2, + 0.0, + 1.0, + 0.0, + 1.0); foundIntersection |= !u_local.empty(); // Map local Bezier parameters back to full NURBS parameters for(int k = 0; k < u_local.size(); ++k) { + // Knot intervals are simply given by indices i and j, due to the use of + // getUniqueKnots() above to set knots1 and knots2. T u_full = axom::utilities::lerp(knots1[i], knots1[i + 1], u_local[k]); T v_full = axom::utilities::lerp(knots2[j], knots2[j + 1], v_local[k]); diff --git a/src/axom/primal/operators/intersect.hpp b/src/axom/primal/operators/intersect.hpp index 476d10f47a..3feb27c8d3 100644 --- a/src/axom/primal/operators/intersect.hpp +++ b/src/axom/primal/operators/intersect.hpp @@ -1466,7 +1466,11 @@ bool intersect(const Line& line, * \param [in] tol Tolerance used in the segment pair intersection test. * \return true iff n1 intersects with n2, otherwise, false. * \note The number of new entries added to p1 and p2 is the number of - * intersections. + * intersections, and corresponding entries in p1 and p2 are for the same + * intersection. This function checks for intersections of Bezier segments + * of the two NURBS curves. It does not perform simple bounding-box checks + * to quickly determine no intersection, which could be done before + * calling this function for better efficiency in some applications. */ template bool intersect(const NURBSCurve& n1, diff --git a/src/axom/primal/tests/primal_nurbs_curve.cpp b/src/axom/primal/tests/primal_nurbs_curve.cpp index eda6adb57e..afe52aeeff 100644 --- a/src/axom/primal/tests/primal_nurbs_curve.cpp +++ b/src/axom/primal/tests/primal_nurbs_curve.cpp @@ -1161,7 +1161,7 @@ TEST(primal_nurbscurve, circular_arc_constructor) //------------------------------------------------------------------------------ TEST(primal_nurbscurve, linear_segment_constructor) { - // Define a nurbs curve that represents a circle + // Define a nurbs curve that represents a line segment const int DIM = 2; using CoordType = double; using PointType = primal::Point; @@ -1204,7 +1204,6 @@ TEST(primal_nurbscurve, linear_segment_constructor) //------------------------------------------------------------------------------ TEST(primal_nurbscurve, nurbscurve_intersections) { - // Define two nurbs curves in 2D intersecting at one point. constexpr int DIM = 2; using CoordType = double; using NURBSCurveType = primal::NURBSCurve; @@ -1212,15 +1211,16 @@ TEST(primal_nurbscurve, nurbscurve_intersections) constexpr int max_degree = 3; - Point2D data1_2d[max_degree + 1] = {Point2D {0.6, 1.2}, - Point2D {1.3, 1.6}, - Point2D {2.9, 2.4}, - Point2D {3.2, 3.5}}; + // Define two nurbs curves in 2D intersecting at one point. + const Point2D data1_2d[max_degree + 1] = {Point2D {0.6, 1.2}, + Point2D {1.3, 1.6}, + Point2D {2.9, 2.4}, + Point2D {3.2, 3.5}}; - Point2D data2_2d[max_degree + 1] = {Point2D {0.5, 3.4}, - Point2D {1.2, 2.3}, - Point2D {2.8, 1.5}, - Point2D {3.1, 1.1}}; + const Point2D data2_2d[max_degree + 1] = {Point2D {0.5, 3.4}, + Point2D {1.2, 2.3}, + Point2D {2.8, 1.5}, + Point2D {3.1, 1.1}}; constexpr double weights[4] = {1.0, 2.0, 3.0, 4.0}; @@ -1232,9 +1232,8 @@ TEST(primal_nurbscurve, nurbscurve_intersections) Point2D intersection1, intersection2; - axom::Array p1, p2; + axom::Array p1, p2, q1, q2; const bool found = intersect(curve1, curve2, p1, p2); - const int num_intersections = p1.size(); EXPECT_TRUE(found && num_intersections == 1 && num_intersections == p2.size()); @@ -1245,6 +1244,32 @@ TEST(primal_nurbscurve, nurbscurve_intersections) for(int i = 0; i < DIM; ++i) EXPECT_NEAR(intersection1[i], intersection2[i], 1e-8); } + + // Test two curves that do not intersect. + const Point2D data3_2d[max_degree + 1] = {Point2D {0.5, -3.4}, + Point2D {1.2, -2.3}, + Point2D {2.8, -1.5}, + Point2D {3.1, -1.1}}; + + NURBSCurveType curve3(data3_2d, weights, npts, degree); + const bool not_found = !intersect(curve2, curve3, q1, q2); + EXPECT_TRUE(not_found && q1.size() == 0 && q2.size() == 0); +} + +//------------------------------------------------------------------------------ +TEST(primal_nurbscurve, nurbscurve_circle_intersections) +{ + constexpr int DIM = 2; + using CoordType = double; + using NURBSCurveType = primal::NURBSCurve; + + // Test two circles that intersect at two points. + const auto circle1 = NURBSCurveType::make_circular_arc_nurbs(0.0, 2.0 * M_PI, 0.0, 0.0, 1.0); + const auto circle2 = NURBSCurveType::make_circular_arc_nurbs(0.0, 2.0 * M_PI, 1.0, 0.0, 1.0); + + axom::Array p1, p2, q1, q2; + const bool found = intersect(circle1, circle2, p1, p2); + EXPECT_TRUE(found && p1.size() == 2 && p2.size() == 2); } int main(int argc, char* argv[]) From 076c2766b5890869b3bff83256176e613eceee75 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Fri, 15 May 2026 12:36:41 -0700 Subject: [PATCH 278/986] Fixes sign convention for compute_moments The signs were reversed with respect to our evaluate_area_integral convention and to the original Ueda paper. --- src/axom/primal/operators/compute_moments.hpp | 38 +++--- .../primal/tests/primal_compute_moments.cpp | 122 +++++++++++++++++- .../primal/tests/primal_curved_polygon.cpp | 9 +- 3 files changed, 143 insertions(+), 26 deletions(-) diff --git a/src/axom/primal/operators/compute_moments.hpp b/src/axom/primal/operators/compute_moments.hpp index 8fa2b852fc..ce96622578 100644 --- a/src/axom/primal/operators/compute_moments.hpp +++ b/src/axom/primal/operators/compute_moments.hpp @@ -10,7 +10,7 @@ /*! * \file compute_moments.hpp * - * \brief Consists of a set of methods to compute areas/volumes and centroids + * \brief Consists of a set of methods to compute areas/volumes and centroids * for Polygon and CurvedPolygon objects composed of nonrational BezierCurve objects */ @@ -29,13 +29,13 @@ namespace axom namespace primal { /*! - * \brief Calculates the sector area of a planar, nonrational Bezier Curve - * - * The sector area is the area between the curve and the origin. - * The equation and derivation is described in: - * Ueda, K. "Signed area of sectors between spline curves and the origin" - * IEEE International Conference on Information Visualization, 1999. - */ + * \brief Calculates the signed sector area of a planar, nonrational Bezier curve. + * + * The sector area is the signed area between the curve and the origin. + * The equation and derivation are described in: + * Ueda, K. "Signed area of sectors between spline curves and the origin" + * IEEE International Conference on Information Visualization, 1999. + */ template T sector_area(const primal::BezierCurve& curve) { @@ -53,20 +53,22 @@ T sector_area(const primal::BezierCurve& curve) { for(int q = 0; q <= ord; ++q) { - A += weights(p, q) * curve[p][1] * curve[q][0]; + A += weights(p, q) * curve[p][0] * curve[q][1]; } } return A; } /*! - * \brief Calculates the sector centroid of a planar, nonrational Bezier Curve - * - * This is the centroid of the region between the curve and the origin. - * The equation and derivation are generalizations of: - * Ueda, K. "Signed area of sectors between spline curves and the origin" - * IEEE International Conference on Information Visualization, 1999. - */ + * \brief Calculates the area-weighted centroid numerator of a planar, + * nonrational Bezier curve. + * + * This is the first raw moment of the region between the curve and the origin. + * Divide by sector_area() to recover the centroid. + * The equation and derivation are generalizations of: + * Ueda, K. "Signed area of sectors between spline curves and the origin" + * IEEE International Conference on Information Visualization, 1999. + */ template primal::Point sector_centroid(const primal::BezierCurve& curve) { @@ -86,8 +88,8 @@ primal::Point sector_centroid(const primal::BezierCurve& curve) { for(int q = 0; q <= ord; ++q) { - Mx += weights_r(p, q) * curve[p][1] * curve[q][0] * curve[r][0]; - My += weights_r(p, q) * curve[p][1] * curve[q][0] * curve[r][1]; + Mx += weights_r(p, q) * curve[p][0] * curve[q][1] * curve[r][0]; + My += weights_r(p, q) * curve[p][0] * curve[q][1] * curve[r][1]; } } } diff --git a/src/axom/primal/tests/primal_compute_moments.cpp b/src/axom/primal/tests/primal_compute_moments.cpp index 125eec2099..c194ada6db 100644 --- a/src/axom/primal/tests/primal_compute_moments.cpp +++ b/src/axom/primal/tests/primal_compute_moments.cpp @@ -18,11 +18,92 @@ #include "axom/primal/geometry/BezierCurve.hpp" #include "axom/primal/geometry/CurvedPolygon.hpp" #include "axom/primal/operators/compute_moments.hpp" +#include "axom/primal/operators/evaluate_integral_curve.hpp" #include "axom/primal/operators/detail/compute_moments_impl.hpp" +#include + namespace primal = axom::primal; -const double EPS = 2e-15; +constexpr double EPS = 2e-15; + +namespace +{ +using Point2D = primal::Point; +using BezierCurve2D = primal::BezierCurve; +using CurvedPolygon2D = primal::CurvedPolygon; + +struct RawPlanarMoments +{ + double m00 {}; + double m10 {}; + double m01 {}; +}; + +CurvedPolygon2D make_quadratic_curve_loop() +{ + return CurvedPolygon2D(axom::Array { + BezierCurve2D(axom::Array {Point2D {2., 0.}, Point2D {1., 2.}, Point2D {0., 0.}}, 2), + BezierCurve2D(axom::Array {Point2D {0., 0.}, Point2D {1., -2.}, Point2D {2., 0.}}, 2)}); +} + +CurvedPolygon2D make_cubic_curve_loop() +{ + return CurvedPolygon2D(axom::Array { + BezierCurve2D(axom::Array {Point2D {0.6, 1.2}, + Point2D {1.3, 1.6}, + Point2D {2.9, 2.4}, + Point2D {3.2, 3.5}}, + 3), + BezierCurve2D(axom::Array {Point2D {3.2, 3.5}, Point2D {0.6, 1.2}}, 1)}); +} + +RawPlanarMoments compute_ueda_raw_moments(const CurvedPolygon2D& polygon) +{ + const double area = primal::area(polygon); + const Point2D centroid = primal::centroid(polygon); + + RawPlanarMoments moments; + moments.m00 = area; + moments.m10 = area * centroid[0]; + moments.m01 = area * centroid[1]; + return moments; +} + +RawPlanarMoments compute_spectral_raw_moments(const CurvedPolygon2D& polygon, int gauss_points) +{ + auto integrate_spectral = [&polygon, gauss_points](auto&& integrand) { + return primal::evaluate_area_integral(polygon, + std::forward(integrand), + gauss_points, + gauss_points); + }; + + RawPlanarMoments moments; + moments.m00 = integrate_spectral([](Point2D) { return 1.; }); + moments.m10 = integrate_spectral([](const Point2D& x) { return x[0]; }); + moments.m01 = integrate_spectral([](const Point2D& x) { return x[1]; }); + return moments; +} + +void expect_raw_moments_near(const RawPlanarMoments& actual, + const RawPlanarMoments& expected, + double tol) +{ + EXPECT_NEAR(actual.m00, expected.m00, tol); + EXPECT_NEAR(actual.m10, expected.m10, tol); + EXPECT_NEAR(actual.m01, expected.m01, tol); +} + +void expect_raw_moments_negated(const RawPlanarMoments& actual, + const RawPlanarMoments& expected, + double tol) +{ + EXPECT_NEAR(actual.m00, -expected.m00, tol); + EXPECT_NEAR(actual.m10, -expected.m10, tol); + EXPECT_NEAR(actual.m01, -expected.m01, tol); +} +} // namespace //------------------------------------------------------------------------------ TEST(primal_compute_moments, sector_area_cubic) @@ -44,7 +125,7 @@ TEST(primal_compute_moments, sector_area_cubic) BezierCurveType bCurve(data, order); const T area = primal::sector_area(bCurve); - EXPECT_NEAR(.1455, area, EPS); + EXPECT_NEAR(-.1455, area, EPS); } } @@ -66,8 +147,8 @@ TEST(primal_compute_moments, sector_moment_cubic) BezierCurveType bCurve(data, order); PointType M = primal::sector_centroid(bCurve); - EXPECT_NEAR(-.429321428571429, M[0], EPS); - EXPECT_NEAR(-.354010714285715, M[1], EPS); + EXPECT_NEAR(.429321428571429, M[0], EPS); + EXPECT_NEAR(.354010714285715, M[1], EPS); } } @@ -109,6 +190,39 @@ TEST(primal_compute_moments, sector_moment_point) } } +//------------------------------------------------------------------------------ +TEST(primal_compute_moments, spectral_matches_ueda_for_polynomial_bezier_regions) +{ + // Cross-check the closed-form moments against our quadrature on polynomial Bezier regions + const CurvedPolygon2D quadratic_region = make_quadratic_curve_loop(); + const CurvedPolygon2D cubic_region = make_cubic_curve_loop(); + + expect_raw_moments_near(compute_spectral_raw_moments(quadratic_region, 8), + compute_ueda_raw_moments(quadratic_region), + 1e-12); + expect_raw_moments_near(compute_spectral_raw_moments(cubic_region, 20), + compute_ueda_raw_moments(cubic_region), + 1e-10); +} + +//------------------------------------------------------------------------------ +TEST(primal_compute_moments, reversed_orientation_negates_raw_moments) +{ + // Reversing a closed boundary changes the signed measure but not the centroid + const CurvedPolygon2D region = make_cubic_curve_loop(); + CurvedPolygon2D reversed_region = region; + reversed_region.reverseOrientation(); + + const RawPlanarMoments moments = compute_ueda_raw_moments(region); + const RawPlanarMoments reversed_moments = compute_ueda_raw_moments(reversed_region); + expect_raw_moments_negated(reversed_moments, moments, 1e-14); + + const Point2D centroid = primal::centroid(region); + const Point2D reversed_centroid = primal::centroid(reversed_region); + EXPECT_NEAR(reversed_centroid[0], centroid[0], 1e-14); + EXPECT_NEAR(reversed_centroid[1], centroid[1], 1e-14); +} + //------------------------------------------------------------------------------ TEST(primal_compute_moments, sector_weights) { diff --git a/src/axom/primal/tests/primal_curved_polygon.cpp b/src/axom/primal/tests/primal_curved_polygon.cpp index 733cc540c3..9bd1b9b4bb 100644 --- a/src/axom/primal/tests/primal_curved_polygon.cpp +++ b/src/axom/primal/tests/primal_curved_polygon.cpp @@ -12,6 +12,7 @@ #include "gtest/gtest.h" #include "axom/config.hpp" +#include "axom/core.hpp" #include "axom/slic.hpp" #include "axom/primal.hpp" @@ -367,7 +368,7 @@ TEST(primal_curvedpolygon, moments_triangle_linear) axom::Array orders = {1, 1, 1}; CurvedPolygonType bPolygon = createBezierPolygon(CP, orders); - CoordType trueA = -.18; + CoordType trueA = .18; PointType trueC = PointType::make_point(0.3, 1.6); checkMoments(bPolygon, trueA, trueC, 1e-14, 1e-15); @@ -395,7 +396,7 @@ TEST(primal_curvedpolygon, moments_triangle_quadratic) axom::Array orders = {2, 2, 2}; CurvedPolygonType bPolygon = createBezierPolygon(CP, orders); - CoordType trueA = -0.097333333333333; + CoordType trueA = 0.097333333333333; PointType trueC {.294479452054794, 1.548219178082190}; checkMoments(bPolygon, trueA, trueC, 1e-15, 1e-14); @@ -422,7 +423,7 @@ TEST(primal_curvedpolygon, moments_triangle_mixed_order) axom::Array orders = {2, 2, 1}; CurvedPolygonType bPolygon = createBezierPolygon(CP, orders); - CoordType trueA = -.0906666666666666666666; + CoordType trueA = .0906666666666666666666; PointType trueC {.2970147058823527, 1.55764705882353}; checkMoments(bPolygon, trueA, trueC, 1e-14, 1e-14); @@ -447,7 +448,7 @@ TEST(primal_curvedpolygon, moments_quad_all_orders) axom::Array orders = {1, 1, 1, 1}; CurvedPolygonType bPolygon = createBezierPolygon(CPorig, orders); - CoordType trueA = 1.0; + CoordType trueA = -1.0; PointType trueC = PointType::make_point(0.5, 0.5); checkMoments(bPolygon, trueA, trueC, 1e-14, 1e-15); From 276aad97b0a7a4891903dde88f65462da574795c Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Fri, 15 May 2026 12:36:58 -0700 Subject: [PATCH 279/986] Updates RELEASE-NOTES --- RELEASE-NOTES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 0cc4eee6f2..b9e9e30c2a 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,6 +39,7 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ - Updates CMake code check targets to only use checked in files (via `git ls-files`, when available) ### Fixed +- Primal: Fixes signs of `compute_moments` to match orientation convention in `primal::evaluate_area_integral` ## [Version 0.14.0] - Release date 2026-03-31 From d4ef12b7047ec776189aa7880daed4a6b7f29562 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 19 May 2026 10:42:18 -0700 Subject: [PATCH 280/986] Removed a temporary CI setting --- scripts/github-actions/linux-build_and_test.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/github-actions/linux-build_and_test.sh b/scripts/github-actions/linux-build_and_test.sh index ba885420c7..a7a780ffe6 100755 --- a/scripts/github-actions/linux-build_and_test.sh +++ b/scripts/github-actions/linux-build_and_test.sh @@ -45,8 +45,7 @@ if [[ "$DO_BUILD" == "yes" ]] ; then fi echo "~~~~~~ RUNNING TESTS ~~~~~~~~" - #make CTEST_OUTPUT_ON_FAILURE=1 test ARGS='-T Test --output-on-failure -j$NUM_BUILD_PROCS' - make CTEST_OUTPUT_ON_FAILURE=1 test ARGS='-T Test --output-on-failure' + make CTEST_OUTPUT_ON_FAILURE=1 test ARGS='-T Test --output-on-failure -j$NUM_BUILD_PROCS' if [[ "${DO_BENCHMARKS}" == "yes" ]] ; then echo "~~~~~~ RUNNING BENCHMARKS ~~~~~~~~" From abdb66d6470dd9715c2e7194bd9041d892e3ec8d Mon Sep 17 00:00:00 2001 From: Dylan Copeland Date: Tue, 19 May 2026 12:22:22 -0700 Subject: [PATCH 281/986] More comments. --- .../operators/detail/intersect_bezier_impl.hpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/axom/primal/operators/detail/intersect_bezier_impl.hpp b/src/axom/primal/operators/detail/intersect_bezier_impl.hpp index 0d784d87ee..443e75a029 100644 --- a/src/axom/primal/operators/detail/intersect_bezier_impl.hpp +++ b/src/axom/primal/operators/detail/intersect_bezier_impl.hpp @@ -49,10 +49,12 @@ namespace detail * \param [in] t_offset The offset in parameter space for \a c2 * \param [in] t_scale The scale in parameter space for \a c2 * - * Bezier curves can only intersect when their bounding boxes intersect. - * The base case of the recursion is when we can approximate the curves as - * line segments, where we directly find their intersections. Otherwise, - * check for intersections recursively after bisecting one of the curves. + * Bezier curves can only intersect when their bounding boxes intersect. Curves + * intersecting tangentially will not have an intersection found. Intersections + * at curve endpoints may also be ignored. The base case of the recursion is + * when we can approximate the curves as line segments, where we directly find + * their intersections. Otherwise, check for intersections recursively after + * bisecting one of the curves. * * \note A BezierCurve is parametrized in [0,1). The scale and offset parameters * are used to track the local curve parameters during subdivisions @@ -187,7 +189,7 @@ bool intersect_circle_bezier(const Sphere &circle, double c_scale); /*! - * \brief Tests intersection of a line and a cirlce + * \brief Tests intersection of a line and a circle * * \param [in] a, b the endpoints of a segment which defines the line * \param [out] c1, c2, t1, t2 The parametrized curve values (c) and From 7cc0a608c248b882d764aef6248903ad1ebab0b5 Mon Sep 17 00:00:00 2001 From: Kenny Weiss Date: Tue, 12 May 2026 12:26:35 -0700 Subject: [PATCH 282/986] Update ci-tests.yml Disables extra quest regression tests in some Debug CI configs since they were causing the CI to timeout. --- .github/workflows/ci-tests.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index b437c88227..d25287606b 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -58,7 +58,7 @@ jobs: job_name: llvm@19.0.0, shared, no raja host_config: llvm@19.0.0.cmake compiler_image: ${{ needs.set_image_vars.outputs.clang_docker_image }} - cmake_opts: '-DBUILD_SHARED_LIBS=ON -DAXOM_QUEST_ENABLE_EXTRA_REGRESSION_TESTS:BOOL=ON -DAXOM_ENABLE_MIR:BOOL=OFF -DAXOM_ENABLE_BUMP:BOOL=OFF -U RAJA_DIR' + cmake_opts: '-DBUILD_SHARED_LIBS=ON -DAXOM_ENABLE_MIR:BOOL=OFF -DAXOM_ENABLE_BUMP:BOOL=OFF -U RAJA_DIR' do_build: 'yes' do_benchmarks: 'no' - build_type: Debug @@ -66,7 +66,7 @@ jobs: job_name: llvm@19.0.0, shared, no umpire host_config: llvm@19.0.0.cmake compiler_image: ${{ needs.set_image_vars.outputs.clang_docker_image }} - cmake_opts: '-DBUILD_SHARED_LIBS=ON -DAXOM_QUEST_ENABLE_EXTRA_REGRESSION_TESTS:BOOL=ON -U UMPIRE_DIR' + cmake_opts: '-DBUILD_SHARED_LIBS=ON -U UMPIRE_DIR' do_build: 'yes' do_benchmarks: 'no' - build_type: Debug @@ -74,7 +74,7 @@ jobs: job_name: llvm@19.0.0, shared, no raja and umpire host_config: llvm@19.0.0.cmake compiler_image: ${{ needs.set_image_vars.outputs.clang_docker_image }} - cmake_opts: '-DBUILD_SHARED_LIBS=ON -DAXOM_QUEST_ENABLE_EXTRA_REGRESSION_TESTS:BOOL=ON -DAXOM_ENABLE_MIR:BOOL=OFF -DAXOM_ENABLE_BUMP:BOOL=OFF -U RAJA_DIR -U UMPIRE_DIR' + cmake_opts: '-DBUILD_SHARED_LIBS=ON -DAXOM_ENABLE_MIR:BOOL=OFF -DAXOM_ENABLE_BUMP:BOOL=OFF -U RAJA_DIR -U UMPIRE_DIR' do_build: 'yes' do_benchmarks: 'no' - build_type: Debug @@ -82,7 +82,7 @@ jobs: job_name: llvm@19.0.0, shared, no profiling host_config: llvm@19.0.0.cmake compiler_image: ${{ needs.set_image_vars.outputs.clang_docker_image }} - cmake_opts: '-DBUILD_SHARED_LIBS=ON -DAXOM_QUEST_ENABLE_EXTRA_REGRESSION_TESTS:BOOL=ON -U CALIPER_DIR -U ADIAK_DIR' + cmake_opts: '-DBUILD_SHARED_LIBS=ON -U CALIPER_DIR -U ADIAK_DIR' do_build: 'yes' do_benchmarks: 'no' name: ${{ matrix.build_type }} - ${{ matrix.config.job_name }} From 655c87ea9ea63bc4e3328e23e2d31cb371bdedf9 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 14 May 2026 17:08:51 -0700 Subject: [PATCH 283/986] Another stab at reducing CI test time The extra quest regression tests should only run in the "quest regression" job. --- .github/workflows/ci-tests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index d25287606b..e3c6d82e5c 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -49,7 +49,7 @@ jobs: - job_name: llvm@19.0.0, shared, benchmarks, quest regression host_config: llvm@19.0.0.cmake compiler_image: ${{ needs.set_image_vars.outputs.clang_docker_image }} - cmake_opts: '-DBUILD_SHARED_LIBS=ON -DAXOM_QUEST_ENABLE_EXTRA_REGRESSION_TESTS:BOOL=ON -DENABLE_BENCHMARKS:BOOL=ON' + cmake_opts: '-DBUILD_SHARED_LIBS=ON -DENABLE_BENCHMARKS:BOOL=ON' do_build: 'yes' do_benchmarks: 'yes' include: @@ -101,6 +101,7 @@ jobs: run: | echo "build_type ${{ matrix.build_type }}" echo "cmake_opts ${{ matrix.config.cmake_opts }}" + echo "quest_extra_regression_tests ${{ contains(matrix.config.job_name, 'quest regression') && 'ON' || 'OFF' }}" 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 }} @@ -109,7 +110,7 @@ jobs: DO_BUILD=${{ matrix.config.do_build }} \ DO_BENCHMARKS=${{ matrix.config.do_benchmarks }} \ HOST_CONFIG=${{ matrix.config.host_config }} \ - CMAKE_EXTRA_FLAGS=" ${{ matrix.config.cmake_opts }} " \ + CMAKE_EXTRA_FLAGS=" ${{ matrix.config.cmake_opts }} -DAXOM_QUEST_ENABLE_EXTRA_REGRESSION_TESTS:BOOL=${{ contains(matrix.config.job_name, 'quest regression') && 'ON' || 'OFF' }} " \ BUILD_TYPE=${{ matrix.build_type }} \ ./scripts/github-actions/linux-build_and_test.sh - name: Upload Test Results @@ -192,4 +193,3 @@ jobs: submodules: recursive - name: Check ${{ matrix.check_type }} run: ./scripts/github-actions/linux-check.sh - From e5a66d0425bb8cb8bec0ce6344e1420ac2c102c0 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Fri, 6 Mar 2026 15:23:14 -0800 Subject: [PATCH 284/986] wip changes --- scripts/docker/dockerfile_gcc-12_cuda-12 | 2 +- .../configs/docker/ubuntu22_cuda/spack.yaml | 53 ++++++++----------- 2 files changed, 22 insertions(+), 33 deletions(-) diff --git a/scripts/docker/dockerfile_gcc-12_cuda-12 b/scripts/docker/dockerfile_gcc-12_cuda-12 index 16286a0dba..14e2108db6 100644 --- a/scripts/docker/dockerfile_gcc-12_cuda-12 +++ b/scripts/docker/dockerfile_gcc-12_cuda-12 @@ -36,7 +36,7 @@ RUN git clone --recursive --branch $branch https://github.com/LLNL/axom.git axom # Build/install TPLs via spack and then remove the temporary build directory on success RUN cd ${HOME}/axom_repo && python3 ./scripts/uberenv/uberenv.py --spack-env-file=./scripts/spack/configs/docker/ubuntu22_cuda/spack.yaml \ --project-json=.uberenv_config.json \ - --spec="%gcc@12.3.0+mfem+adiak+caliper+cuda cuda_arch=70 ^raja+openmp+cuda ^umpire+openmp+cuda" \ + --spec="+mfem+adiak+caliper+cuda cuda_arch=70 %gcc_12 ^raja+openmp+cuda ^umpire+openmp+cuda" \ --prefix=${HOME}/axom_tpls -k \ && rm -rf ${HOME}/axom_tpls/build_stage ${HOME}/axom_tpls/spack diff --git a/scripts/spack/configs/docker/ubuntu22_cuda/spack.yaml b/scripts/spack/configs/docker/ubuntu22_cuda/spack.yaml index 80c75f7043..8a32a58b35 100644 --- a/scripts/spack/configs/docker/ubuntu22_cuda/spack.yaml +++ b/scripts/spack/configs/docker/ubuntu22_cuda/spack.yaml @@ -22,48 +22,37 @@ spack: - ../defaults.yaml - ../versions.yaml - compilers:: - - compiler: - environment: {} - extra_rpaths: [] - flags: - cflags: -pthread - cxxflags: -pthread - modules: [] - operating_system: ubuntu22.04 - paths: - cc: /usr/bin/gcc - cxx: /usr/bin/g++ - f77: /usr/bin/gfortran - fc: /usr/bin/gfortran - spec: gcc@12.3.0 - target: x86_64 + toolchains: + gcc_12: + - spec: '%c=gcc' + when: '%c' + - spec: '%cxx=gcc' + when: '%cxx' + - spec: '%fortran=gcc' + when: '%fortran' + - spec: '%mpich' + when: '%mpi' packages: all: # This defaults us to machine specific flags of ivybridge which allows # us to run on broadwell as well target: [x86_64] - compiler: [gcc, intel, pgi, clang, xl, nag] providers: - awk: [gawk] blas: [openblas] lapack: [openblas] - daal: [intel-daal] - elf: [elfutils] - golang: [gcc] - ipp: [intel-ipp] - java: [jdk] - mkl: [intel-mkl] - mpe: [mpe2] mpi: [mpich] - opencl: [pocl] - openfoam: [openfoam-com, openfoam-org, foam-extend] - pil: [py-pillow] - scalapack: [netlib-scalapack] - szip: [libszip, libaec] - tbb: [intel-tbb] - jpeg: [libjpeg-turbo, libjpeg] + + #Compiler packages + gcc: + externals: + - spec: gcc@12.3.0 languages:=c,c++,fortran + prefix: /usr/bin + extra_attributes: + compilers: + cc: /usr/bin/gcc + cxx: /usr/bin/g++ + fortran: /usr/bin/gfortran # Spack may grab for mpi & we don't want to use them mpi: From 78b7792a60c103c71b358bb0a2979144983c9da1 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Wed, 22 Apr 2026 14:23:54 -0700 Subject: [PATCH 285/986] Test python variant --- scripts/docker/dockerfile_gcc-13 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/docker/dockerfile_gcc-13 b/scripts/docker/dockerfile_gcc-13 index 1a825951c9..486f2e1e42 100644 --- a/scripts/docker/dockerfile_gcc-13 +++ b/scripts/docker/dockerfile_gcc-13 @@ -29,7 +29,7 @@ RUN git clone --recursive --branch $branch --single-branch --depth 1 https://git # Build/install TPLs via spack and then remove the temporary build directory on success RUN cd axom_repo && python3 ./scripts/uberenv/uberenv.py --spack-env-file=./scripts/spack/configs/docker/ubuntu24/spack.yaml \ --project-json=.uberenv_config.json \ - --spec="+mfem+raja+umpire+adiak+caliper %gcc_13" --prefix=/home/axom/axom_tpls -k \ + --spec="+python+mfem+raja+umpire+adiak+caliper %gcc_13" --prefix=/home/axom/axom_tpls -k \ && rm -rf /home/axom/axom_tpls/build_stage /home/axom/axom_tpls/spack RUN mkdir -p /home/axom/export_hostconfig From d8613302301b6304ed13fb3bac0d746e7f52742d Mon Sep 17 00:00:00 2001 From: Brian Han Date: Thu, 23 Apr 2026 08:11:19 -0700 Subject: [PATCH 286/986] Add +python to %clang docker --- scripts/docker/dockerfile_clang-19 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/docker/dockerfile_clang-19 b/scripts/docker/dockerfile_clang-19 index f9a2ae2f1f..01f04c69aa 100644 --- a/scripts/docker/dockerfile_clang-19 +++ b/scripts/docker/dockerfile_clang-19 @@ -29,7 +29,7 @@ RUN git clone --recursive --branch $branch --single-branch --depth 1 https://git # Build/install TPLs via spack and then remove the temporary build directory on success RUN cd axom_repo && python3 ./scripts/uberenv/uberenv.py --spack-env-file=./scripts/spack/configs/docker/ubuntu24/spack.yaml \ --project-json=.uberenv_config.json \ - --spec="+mfem+raja+umpire+adiak+caliper %clang_19" --prefix=/home/axom/axom_tpls -k \ + --spec="+python+mfem+raja+umpire+adiak+caliper %clang_19" --prefix=/home/axom/axom_tpls -k \ && rm -rf /home/axom/axom_tpls/build_stage /home/axom/axom_tpls/spack RUN mkdir -p /home/axom/export_hostconfig From 60276b8270505390cc1cf2a137957b39e22da2a8 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Thu, 23 Apr 2026 14:42:46 -0700 Subject: [PATCH 287/986] Let spack build cmake --- .../spack/configs/docker/ubuntu22_cuda/spack.yaml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/scripts/spack/configs/docker/ubuntu22_cuda/spack.yaml b/scripts/spack/configs/docker/ubuntu22_cuda/spack.yaml index 8a32a58b35..a4561a90d1 100644 --- a/scripts/spack/configs/docker/ubuntu22_cuda/spack.yaml +++ b/scripts/spack/configs/docker/ubuntu22_cuda/spack.yaml @@ -126,12 +126,14 @@ spack: - spec: "+shared~static" # Globally lock in version of devtools - cmake: - version: [3.23.1] - buildable: false - externals: - - spec: cmake@3.23.1 - prefix: /usr/local + + # RAJA needs cmake 3.24: + # cmake: + # version: [3.23.1] + # buildable: false + # externals: + # - spec: cmake@3.23.1 + # prefix: /usr/local doxygen: version: [1.8.17] buildable: false From 7edded0e288d8abb2982a2040965cac695a90872 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Thu, 23 Apr 2026 15:19:59 -0700 Subject: [PATCH 288/986] phrasing --- scripts/spack/configs/docker/ubuntu22_cuda/spack.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/spack/configs/docker/ubuntu22_cuda/spack.yaml b/scripts/spack/configs/docker/ubuntu22_cuda/spack.yaml index a4561a90d1..426d5b802d 100644 --- a/scripts/spack/configs/docker/ubuntu22_cuda/spack.yaml +++ b/scripts/spack/configs/docker/ubuntu22_cuda/spack.yaml @@ -50,7 +50,7 @@ spack: prefix: /usr/bin extra_attributes: compilers: - cc: /usr/bin/gcc + c: /usr/bin/gcc cxx: /usr/bin/g++ fortran: /usr/bin/gfortran From a06eb079e77f646119eb529338682e39b5b1cf36 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Fri, 24 Apr 2026 10:12:37 -0700 Subject: [PATCH 289/986] gcc-13 +python+vscode dockerfile --- scripts/docker/dockerfile_gcc-13_vscode | 90 +++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 scripts/docker/dockerfile_gcc-13_vscode diff --git a/scripts/docker/dockerfile_gcc-13_vscode b/scripts/docker/dockerfile_gcc-13_vscode new file mode 100644 index 0000000000..a22d134245 --- /dev/null +++ b/scripts/docker/dockerfile_gcc-13_vscode @@ -0,0 +1,90 @@ +# Docker image for Axom tutorial. +# Docker container runs a VS Code server accessible through a web browser. +# Docker and openvscode setup based on the RAJA suite tutorial: +# https://github.com/LLNL/raja-suite-tutorial/tree/main/containers/tutorial + +# This script can be run with the following command: +# docker build --build-arg branch= -t "axom/tpls:gcc-13-vscode" - < dockerfile_gcc-13_vscode + +# Command to launch openvscode server with the resulting docker image: +# docker run --init --restart=always -p 3000:3000 + +FROM ghcr.io/llnl/radiuss:gcc-13-ubuntu-24.04 +ARG branch=develop +ARG USER=axomdev +ENV HOME /home/${USER} + +SHELL ["/bin/bash", "-c"] +RUN sudo apt-get update -y +RUN sudo apt-get install -y supervisor +RUN sudo useradd --create-home --shell /bin/bash ${USER} +RUN sudo apt-get install gettext gfortran-$(gcc -dumpversion) graphviz libopenblas-dev \ + lsb-release lua5.2 lua5.2-dev python3-sphinx locales ssh -fy +RUN sudo locale-gen en_US.utf8 +#RUN sudo useradd -m -s /bin/bash -G sudo axom + +# Install proper doxygen version (should match version in LC host configs) +RUN sudo wget https://github.com/doxygen/doxygen/releases/download/Release_1_9_8/doxygen-1.9.8.linux.bin.tar.gz +RUN sudo tar -xf doxygen-1.9.8.linux.bin.tar.gz +RUN cd doxygen-1.9.8 && sudo make && sudo make install && doxygen --version + +WORKDIR /opt/archives +RUN sudo curl -L https://github.com/gitpod-io/openvscode-server/releases/download/openvscode-server-v1.69.1/openvscode-server-v1.69.1-linux-x64.tar.gz > \ + /opt/archives/openvscode-server-v1.69.1-linux-x64.tar.gz +RUN sudo tar xzf openvscode-server-v1.69.1-linux-x64.tar.gz && sudo chown -R ${USER}:${USER} openvscode-server-v1.69.1-linux-x64 + +WORKDIR ${HOME} +USER ${USER} + +# Set locale for formatting +ENV LANG='en_US.utf8' \ + LANGUAGE='en_US.utf8' \ + LC_ALL='en_US.utf8' + +RUN git clone --recursive --branch $branch --single-branch --depth 1 https://github.com/LLNL/axom.git axom_repo + +# Build/install TPLs via spack and then remove the temporary build directory on success +RUN cd ${HOME}/axom_repo && python3 ./scripts/uberenv/uberenv.py --spack-env-file=./scripts/spack/configs/docker/ubuntu24/spack.yaml \ + --project-json=.uberenv_config.json \ + --spec="+python+mfem+raja+umpire+adiak+caliper %gcc_13" --prefix=${HOME}/axom_tpls -k \ + && rm -rf ${HOME}/axom_tpls/build_stage ${HOME}/axom_tpls/spack + +# Make sure the new hostconfig works with a release build +# Note: having high job slots causes build log to disappear and job to fail +# Omit testing step, hangs at slam_lulesh unit test (same behavior for azure pipeline images, as well) +# Disable link-time optimization from MPI wrapper causing linkage failures +RUN cd ${HOME}/axom_repo && python3 config-build.py -hc *.cmake \ + -bp ${HOME}/axom_repo/build-release \ + -ip ${HOME}/axom_repo/install-release \ + -bt Release \ + && cd ${HOME}/axom_repo/build-release \ + && make -j4 install + +# Install VisIt binary +# Download tarball, installer, test script, perform installation and cleanup +# Note - testing is done with command: ./visit/bin/visit -cli -nowin -s test_visit.py +# (must be done manually; hangs when ran on Apple Silicon/ARM, x86_64/amd64 is okay) +RUN wget https://github.com/visit-dav/visit/releases/download/v3.4.2/visit3_4_2.linux-x86_64-ubuntu24.tar.gz \ + && wget https://github.com/visit-dav/visit/releases/download/v3.4.2/visit-install3_4_2 \ + && wget https://raw.githubusercontent.com/visit-dav/visit/refs/heads/develop/scripts/docker/test_visit.py \ + && chmod 777 visit-install3_4_2 \ + && ./visit-install3_4_2 -c none 3.4.2 linux-x86_64-ubuntu24 ${HOME}/visit \ + && rm -rf visit-install3_4_2 visit3_4_2.linux-x86_64-ubuntu24.tar.gz + +# Larger STL meshes for testing (optional) +# Note: These meshes are large, copied from local directory instead of +# downloaded from Github. +# COPY boxedSphere.stl car.stl porsche.stl ${HOME}/axom_repo/data/quest + + +# Create symlinks for easy access to tutorial material (optional) +# RUN ln -s ${HOME}/axom_repo/install-release/examples/axom/radiuss_tutorial/ ${HOME}/radiuss_tutorial \ +# && ln -s ${HOME}/axom_repo/data/quest ${HOME}/radiuss_tutorial/stl_meshes + +USER root +ADD ./scripts/docker/supervisord.conf /etc/supervisord.conf +RUN sed -i "s/XXX/${USER}/g" /etc/supervisord.conf + +RUN touch /var/log/openvscode-server.log && chown -R ${USER}:${USER} /var/log/openvscode-server.log + +CMD ["/usr/bin/supervisord"] From 21dff74d2cf8a7d0426cc35c79d6db7c631b973c Mon Sep 17 00:00:00 2001 From: Brian Han Date: Tue, 19 May 2026 10:57:11 -0700 Subject: [PATCH 290/986] Run spack's styling for consistency with spack-packages repo --- scripts/spack/packages/axom/package.py | 71 ++++++++++++++++++-------- 1 file changed, 49 insertions(+), 22 deletions(-) diff --git a/scripts/spack/packages/axom/package.py b/scripts/spack/packages/axom/package.py index 01acb0f81f..6ab252195c 100644 --- a/scripts/spack/packages/axom/package.py +++ b/scripts/spack/packages/axom/package.py @@ -15,7 +15,6 @@ ) from spack_repo.builtin.build_systems.cuda import CudaPackage from spack_repo.builtin.build_systems.rocm import ROCmPackage -from spack.error import SpackError # Axom components we expose to Spack. Core is always built and is not listed here. _AXOM_COMPONENTS = ( @@ -420,7 +419,9 @@ def initconfig_compiler_entries(self): # icpx: remark: note that use of '-g' without any optimization-level # option will turn off most compiler optimizations similar to use of # '-O0'; use '-Rno-debug-disables-optimization' to disable this remark - entries.append(cmake_cache_string("CMAKE_CXX_FLAGS_DEBUG", "-g -Rno-debug-disables-optimization")) + entries.append( + cmake_cache_string("CMAKE_CXX_FLAGS_DEBUG", "-g -Rno-debug-disables-optimization") + ) return entries @@ -433,7 +434,9 @@ def initconfig_hardware_entries(self): entries.append(cmake_cache_option("CMAKE_CUDA_SEPARABLE_COMPILATION", True)) # CUDA_FLAGS - cudaflags = "${CMAKE_CUDA_FLAGS} -restrict --expt-extended-lambda --expt-relaxed-constexpr " + cudaflags = ( + "${CMAKE_CUDA_FLAGS} -restrict --expt-extended-lambda --expt-relaxed-constexpr " + ) # Pass through any cxxflags to the host compiler via nvcc's Xcompiler flag host_cxx_flags = spec.compiler_flags["cxxflags"] @@ -465,14 +468,18 @@ def initconfig_hardware_entries(self): # Recommended MPI flags if spec.satisfies("+mpi"): hip_link_flags += "-lxpmem " - hip_link_flags += "-L/opt/cray/pe/mpich/{0}/gtl/lib ".format(spec["mpi"].version.up_to(3)) + hip_link_flags += "-L/opt/cray/pe/mpich/{0}/gtl/lib ".format( + spec["mpi"].version.up_to(3) + ) hip_link_flags += "-Wl,-rpath,/opt/cray/pe/mpich/{0}/gtl/lib ".format( spec["mpi"].version.up_to(3) ) hip_link_flags += "-lmpi_gtl_hsa " if spec.satisfies("^hip@6.0.0:"): - hip_link_flags += "-L{0}/lib/llvm/lib -Wl,-rpath,{0}/lib/llvm/lib ".format(rocm_root) + hip_link_flags += "-L{0}/lib/llvm/lib -Wl,-rpath,{0}/lib/llvm/lib ".format( + rocm_root + ) else: hip_link_flags += "-L{0}/llvm/lib -Wl,-rpath,{0}/llvm/lib ".format(rocm_root) # Only amdclang requires this path; cray compiler fails if this is included @@ -489,8 +496,8 @@ def initconfig_hardware_entries(self): # Additional library path for cray compiler if self.spec.satisfies("%cce"): hip_link_flags += "-L/opt/cray/pe/cce/{0}/cce/x86_64/lib -Wl,-rpath,/opt/cray/pe/cce/{0}/cce/x86_64/lib ".format( - self.spec.compiler.version - ) + self.spec.compiler.version + ) if spec.satisfies("+fortran"): link_remove_list = [] @@ -568,18 +575,16 @@ def initconfig_hardware_entries(self): cmake_cache_string("BLT_OPENMP_LINK_FLAGS", openmp_gen_exp, description) ) - if ( - spec.satisfies("+openmp") - and spec.satisfies("+rocm") - and self.spec.satisfies("%cce") - ): + if spec.satisfies("+openmp") and spec.satisfies("+rocm") and self.spec.satisfies("%cce"): openmp_gen_exp = ( "$<$>:" "-fopenmp=libomp>;$<$:-fopenmp>" ) - description = "Different OpenMP compile & link flags between HIP and CXX compilers (amdclang++)" + description = ( + "Different OpenMP compile & link flags between HIP and CXX compilers (amdclang++)" + ) entries.append( cmake_cache_string("BLT_OPENMP_COMPILE_FLAGS", openmp_gen_exp, description) ) @@ -641,13 +646,15 @@ def initconfig_package_entries(self): if all_components_enabled: print("All axom components enabled") else: - print(f"The following Axom components are enabled: {spec.variants['components'].value}") + print( + f"The following Axom components are enabled: {spec.variants['components'].value}" + ) entries.append("#------------------{0}".format("-" * 60)) entries.append("# Axom components") entries.append("#------------------{0}\n".format("-" * 60)) entries.append(cmake_cache_option("AXOM_ENABLE_ALL_COMPONENTS", False)) - + for comp in spec.variants["components"].value: if comp in _AXOM_COMPONENTS: entries.append(cmake_cache_option(f"AXOM_ENABLE_{comp.upper()}", True)) @@ -659,7 +666,18 @@ def initconfig_package_entries(self): # Try to find the common prefix of the TPL directory. # If found, we will use this in the TPL paths - variant_deps = ["conduit", "c2c", "mfem", "hdf5", "lua", "raja", "umpire", "opencascade", "adiak", "caliper"] + variant_deps = [ + "conduit", + "c2c", + "mfem", + "hdf5", + "lua", + "raja", + "umpire", + "opencascade", + "adiak", + "caliper", + ] for dep in variant_deps: if dep in ["lua"]: # skip entries often outside the common prefix @@ -751,9 +769,7 @@ def initconfig_package_entries(self): if spec.satisfies("^py-yapf"): yapf_bin_dir = get_spec_path(spec, "py-yapf", path_replacements, use_bin=True) - entries.append( - cmake_cache_path("YAPF_EXECUTABLE", pjoin(yapf_bin_dir, "yapf")) - ) + entries.append(cmake_cache_path("YAPF_EXECUTABLE", pjoin(yapf_bin_dir, "yapf"))) if spec.satisfies("^py-shroud"): shroud_bin_dir = get_spec_path(spec, "py-shroud", path_replacements, use_bin=True) @@ -768,11 +784,22 @@ def initconfig_package_entries(self): if spec.satisfies("+python"): # pytest requires pluggy and iniconfig - for dep in ("py-nanobind", "py-pytest", "py-numpy", "py-pluggy", "py-iniconfig", "py-mpi4py"): + for dep in ( + "py-nanobind", + "py-pytest", + "py-numpy", + "py-pluggy", + "py-iniconfig", + "py-mpi4py", + ): if spec.satisfies("^{0}".format(dep)): dep_dir = get_spec_path(spec, dep, path_replacements, use_lib=True) - py_libdir = join_path(dep_dir, f"python{spec['python'].version.up_to(2)}", "site-packages") - entries.append(cmake_cache_path("%s_DIR" % dep.upper().replace("-", "_"), py_libdir)) + py_libdir = join_path( + dep_dir, f"python{spec['python'].version.up_to(2)}", "site-packages" + ) + entries.append( + cmake_cache_path("%s_DIR" % dep.upper().replace("-", "_"), py_libdir) + ) return entries From b681378c8e4eaebbb6cf0eda396c1d27b0652d96 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Tue, 19 May 2026 11:48:23 -0700 Subject: [PATCH 291/986] Update with upstream axom spack recipe - relax variant propagation to conduit dependency --- scripts/spack/packages/axom/package.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/spack/packages/axom/package.py b/scripts/spack/packages/axom/package.py index 6ab252195c..de5845a12f 100644 --- a/scripts/spack/packages/axom/package.py +++ b/scripts/spack/packages/axom/package.py @@ -16,7 +16,7 @@ from spack_repo.builtin.build_systems.cuda import CudaPackage from spack_repo.builtin.build_systems.rocm import ROCmPackage -# Axom components we expose to Spack. Core is always built and is not listed here. +# Axom components we expose to Spack. Core is always built and is not listed here. _AXOM_COMPONENTS = ( "bump", "inlet", @@ -186,9 +186,10 @@ class Axom(CachedCMakePackage, CudaPackage, ROCmPackage): # Libraries # Forward variants to Conduit with when("+conduit"): - for _var in ["fortran", "hdf5", "mpi", "python"]: + for _var in ["hdf5", "mpi"]: depends_on("conduit+{0}".format(_var), when="+{0}".format(_var)) depends_on("conduit~{0}".format(_var), when="~{0}".format(_var)) + depends_on("conduit+fortran", when="+fortran") depends_on("hdf5", when="+hdf5") From c7e9e9ec1d35d2886481570f58f09213c2d8c52a Mon Sep 17 00:00:00 2001 From: Brian Han Date: Tue, 19 May 2026 12:42:46 -0700 Subject: [PATCH 292/986] Update docker host-configs --- host-configs/docker/gcc@13.3.1.cmake | 24 +++++++++++++++++++----- host-configs/docker/llvm@19.0.0.cmake | 24 +++++++++++++++++++----- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/host-configs/docker/gcc@13.3.1.cmake b/host-configs/docker/gcc@13.3.1.cmake index e1e9346de2..9fda04ffda 100644 --- a/host-configs/docker/gcc@13.3.1.cmake +++ b/host-configs/docker/gcc@13.3.1.cmake @@ -4,13 +4,13 @@ # CMake executable path: /usr/local/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/home/axom/axom_tpls/gcc-13.3.1/blt-0.7.1-tp6erawewp4l2ewllhglzso5fnjudoja;/home/axom/axom_tpls/gcc-13.3.1/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-qollf3jglv6wxat4uffls42u2t2biu43;/home/axom/axom_tpls/gcc-13.3.1/conduit-0.9.5-e72kkuzpsp2ct2warb23dw63rjbwyuy7;/home/axom/axom_tpls/gcc-13.3.1/gmake-4.4.1-jclt3ixkhzk7gh4qz7bph3nqno2d2tan;/home/axom/axom_tpls/gcc-13.3.1/mfem-4.9.0-27y23akm3yqlnmn3y4ujuiif7ch3d42x;/home/axom/axom_tpls/gcc-13.3.1/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-xk3wk7pkfxfmczqsud2zgvoaynygae7x;/home/axom/axom_tpls/gcc-13.3.1/umpire-2025.12.0-eg7ottka4ejtla2sshmo5fbnz6ievmht;/home/axom/axom_tpls/gcc-13.3.1/adiak-0.4.0-7tvwkbkbsrz7pg7cmzjbgpakeqwxiwfn;/home/axom/axom_tpls/gcc-13.3.1/elfutils-0.193-yvyizihf3ovzcrz2xspbo3xqpcsgpciw;/home/axom/axom_tpls/gcc-13.3.1/libunwind-1.8.3-dms6jlqrs6yvgwf454ezco5mszpn4fny;/home/axom/axom_tpls/gcc-13.3.1/hdf5-1.8.23-c5fghc2avhw2ujuhxb32akojoz62ulww;/home/axom/axom_tpls/gcc-13.3.1/parmetis-4.0.3-fwggxdxmzfdyu4zuwzps2iy4ku5nlzbq;/home/axom/axom_tpls/gcc-13.3.1/hypre-2.27.0-yukxydlpq2lrtkdzhpyvnyfxo6u62dqh;/home/axom/axom_tpls/gcc-13.3.1/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-gdtpys6nyl4kfgvwd5d46hh7mrx4c64f;/home/axom/axom_tpls/gcc-13.3.1/fmt-11.0.2-auzpmart4pgyllfizbgbrmi7vo5l7imm;/home/axom/axom_tpls/gcc-13.3.1/zstd-1.5.7-ynwtbrjy4fy7fmg4mnoq7inn6j36z2lo;/home/axom/axom_tpls/gcc-13.3.1/metis-5.1.0-ngjvzo5djlp4rtpsh2rro63istec4tks;/home/axom/axom_tpls/gcc-13.3.1/mpich-4.2.0-oafwifl7wossiwmdg3osevy3h42nehm5;/home/axom/axom_tpls/gcc-13.3.1/hwloc-2.12.2-r6jiv64detpwvvobvcpbxqsgbyz4an7b;/home/axom/axom_tpls/gcc-13.3.1/libfabric-2.4.0-glityu7f5kxxo72mbpnt2h54aowe2o7j;/home/axom/axom_tpls/gcc-13.3.1/yaksa-0.4-2nqzb7ap73wiacj77ktkz5qh33s3h7cj;/home/axom/axom_tpls/gcc-13.3.1/libpciaccess-0.17-e7jficwldulh2dbp7l742wedsof3d6h5;/home/axom/axom_tpls/gcc-13.3.1/libxml2-2.13.5-wj6jpg6gipn4cjol77o5bwppiymtifjj;/home/axom/axom_tpls/gcc-13.3.1/ncurses-6.5-20250705-izsjfkqb573w7lny6vvdbt57lctv2j22;/home/axom/axom_tpls/gcc-13.3.1/libiconv-1.18-nwihe6gonhf3rig4qghahxqrdzcpxzp2;/home/axom/axom_tpls/gcc-13.3.1/xz-5.6.3-t6r2wp2e2kybjuus2yzlqap6khgwvw73;/home/axom/axom_tpls/gcc-13.3.1/zlib-ng-2.3.2-xturc74asm73dunfzuwguafjrucw75ae;/home/axom/axom_tpls/none-none/gcc-runtime-13.3.1-ahhevkdxsqek4foiubajeiisj7ryali4;/home/axom/axom_tpls/none-none/compiler-wrapper-1.0-u5fjo4cce7cqt6425ipfxnafausfog7z" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/home/axom/axom_tpls/gcc-13.3.1/blt-0.7.1-tp6erawewp4l2ewllhglzso5fnjudoja;/home/axom/axom_tpls/gcc-13.3.1/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-qollf3jglv6wxat4uffls42u2t2biu43;/home/axom/axom_tpls/gcc-13.3.1/conduit-0.9.5-tov2czaq7ifefmpwa2gtqaqgelclqwge;/home/axom/axom_tpls/gcc-13.3.1/gmake-4.4.1-jclt3ixkhzk7gh4qz7bph3nqno2d2tan;/home/axom/axom_tpls/gcc-13.3.1/mfem-4.9.0-27y23akm3yqlnmn3y4ujuiif7ch3d42x;/home/axom/axom_tpls/gcc-13.3.1/py-nanobind-2.7.0-haxzzbmxto45ae43fjjdfmtgo4l5qhjx;/home/axom/axom_tpls/none-none/py-pytest-9.0.0-lzujihl4aaovgis2uoemzabjpymsjfuj;/home/axom/axom_tpls/gcc-13.3.1/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-xk3wk7pkfxfmczqsud2zgvoaynygae7x;/home/axom/axom_tpls/gcc-13.3.1/umpire-2025.12.0-eg7ottka4ejtla2sshmo5fbnz6ievmht;/home/axom/axom_tpls/gcc-13.3.1/adiak-0.4.0-7tvwkbkbsrz7pg7cmzjbgpakeqwxiwfn;/home/axom/axom_tpls/gcc-13.3.1/elfutils-0.193-yvyizihf3ovzcrz2xspbo3xqpcsgpciw;/home/axom/axom_tpls/gcc-13.3.1/libunwind-1.8.3-dms6jlqrs6yvgwf454ezco5mszpn4fny;/home/axom/axom_tpls/gcc-13.3.1/hdf5-1.8.23-c5fghc2avhw2ujuhxb32akojoz62ulww;/home/axom/axom_tpls/gcc-13.3.1/parmetis-4.0.3-fwggxdxmzfdyu4zuwzps2iy4ku5nlzbq;/home/axom/axom_tpls/gcc-13.3.1/py-mpi4py-4.1.1-n2ebx2enfluwannhglskxq3j5ntbasbp;/home/axom/axom_tpls/gcc-13.3.1/py-numpy-2.4.2-4z3hsqslzknft2priqptynbsotvbetca;/home/axom/axom_tpls/gcc-13.3.1/hypre-2.27.0-yukxydlpq2lrtkdzhpyvnyfxo6u62dqh;/home/axom/axom_tpls/gcc-13.3.1/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-gdtpys6nyl4kfgvwd5d46hh7mrx4c64f;/home/axom/axom_tpls/gcc-13.3.1/fmt-11.0.2-auzpmart4pgyllfizbgbrmi7vo5l7imm;/home/axom/axom_tpls/gcc-13.3.1/zstd-1.5.7-ynwtbrjy4fy7fmg4mnoq7inn6j36z2lo;/home/axom/axom_tpls/gcc-13.3.1/metis-5.1.0-ngjvzo5djlp4rtpsh2rro63istec4tks;/home/axom/axom_tpls/gcc-13.3.1/mpich-4.2.0-oafwifl7wossiwmdg3osevy3h42nehm5;/home/axom/axom_tpls/gcc-13.3.1/hwloc-2.12.2-r6jiv64detpwvvobvcpbxqsgbyz4an7b;/home/axom/axom_tpls/gcc-13.3.1/libfabric-2.4.0-glityu7f5kxxo72mbpnt2h54aowe2o7j;/home/axom/axom_tpls/gcc-13.3.1/yaksa-0.4-2nqzb7ap73wiacj77ktkz5qh33s3h7cj;/home/axom/axom_tpls/gcc-13.3.1/libpciaccess-0.17-e7jficwldulh2dbp7l742wedsof3d6h5;/home/axom/axom_tpls/gcc-13.3.1/libxml2-2.13.5-wj6jpg6gipn4cjol77o5bwppiymtifjj;/home/axom/axom_tpls/gcc-13.3.1/ncurses-6.5-20250705-izsjfkqb573w7lny6vvdbt57lctv2j22;/home/axom/axom_tpls/gcc-13.3.1/libiconv-1.18-nwihe6gonhf3rig4qghahxqrdzcpxzp2;/home/axom/axom_tpls/gcc-13.3.1/xz-5.6.3-t6r2wp2e2kybjuus2yzlqap6khgwvw73;/home/axom/axom_tpls/gcc-13.3.1/zlib-ng-2.3.2-xturc74asm73dunfzuwguafjrucw75ae;/home/axom/axom_tpls/none-none/gcc-runtime-13.3.1-ahhevkdxsqek4foiubajeiisj7ryali4;/home/axom/axom_tpls/none-none/compiler-wrapper-1.0-u5fjo4cce7cqt6425ipfxnafausfog7z" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/home/axom/axom_tpls/gcc-13.3.1/axom-develop-ofy2ds6xm3ggsfnnn2bzwtfoyqooykov/lib;/home/axom/axom_tpls/gcc-13.3.1/axom-develop-ofy2ds6xm3ggsfnnn2bzwtfoyqooykov/lib64;;" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/home/axom/axom_tpls/gcc-13.3.1/axom-develop-hhm5dhgenh2t6dyv3rykj7vvnpmybt3p/lib;/home/axom/axom_tpls/gcc-13.3.1/axom-develop-hhm5dhgenh2t6dyv3rykj7vvnpmybt3p/lib64;;" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/home/axom/axom_tpls/gcc-13.3.1/axom-develop-ofy2ds6xm3ggsfnnn2bzwtfoyqooykov/lib;/home/axom/axom_tpls/gcc-13.3.1/axom-develop-ofy2ds6xm3ggsfnnn2bzwtfoyqooykov/lib64;;" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/home/axom/axom_tpls/gcc-13.3.1/axom-develop-hhm5dhgenh2t6dyv3rykj7vvnpmybt3p/lib;/home/axom/axom_tpls/gcc-13.3.1/axom-develop-hhm5dhgenh2t6dyv3rykj7vvnpmybt3p/lib64;;" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") @@ -77,7 +77,7 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") set(TPL_ROOT "/home/axom/axom_tpls/gcc-13.3.1" CACHE PATH "") -set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-e72kkuzpsp2ct2warb23dw63rjbwyuy7" CACHE PATH "") +set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-tov2czaq7ifefmpwa2gtqaqgelclqwge" CACHE PATH "") # C2C not built @@ -102,13 +102,27 @@ set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main # scr not built #------------------------------------------------------------------------------ -# Devtools +# Devtools & Python #------------------------------------------------------------------------------ # ClangFormat disabled since llvm@19 and devtools not in spec set(ENABLE_CLANGFORMAT OFF CACHE BOOL "") +set(Python_EXECUTABLE "/usr/bin/python3" CACHE PATH "") + set(ENABLE_DOCS OFF CACHE BOOL "") +set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-haxzzbmxto45ae43fjjdfmtgo4l5qhjx/lib/python3.12/site-packages" CACHE PATH "") + +set(PY_PYTEST_DIR "/home/axom/axom_tpls/none-none/py-pytest-9.0.0-lzujihl4aaovgis2uoemzabjpymsjfuj/lib/python3.12/site-packages" CACHE PATH "") + +set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-4z3hsqslzknft2priqptynbsotvbetca/lib/python3.12/site-packages" CACHE PATH "") + +set(PY_PLUGGY_DIR "/home/axom/axom_tpls/none-none/py-pluggy-1.6.0-mpbg4tbgwvzfntupp2kdega47dr2vxi6/lib/python3.12/site-packages" CACHE PATH "") + +set(PY_INICONFIG_DIR "/home/axom/axom_tpls/none-none/py-iniconfig-2.1.0-yhyncq6joox5xckyjtjnj6tuoc35s72m/lib/python3.12/site-packages" CACHE PATH "") + +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-n2ebx2enfluwannhglskxq3j5ntbasbp/lib/python3.12/site-packages" CACHE PATH "") + diff --git a/host-configs/docker/llvm@19.0.0.cmake b/host-configs/docker/llvm@19.0.0.cmake index 515d46d6db..783d9b41bc 100644 --- a/host-configs/docker/llvm@19.0.0.cmake +++ b/host-configs/docker/llvm@19.0.0.cmake @@ -4,13 +4,13 @@ # CMake executable path: /usr/local/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/home/axom/axom_tpls/llvm-19.0.0/blt-0.7.1-p7mm766jfnjcbcnon7lmbmjk52d7nnfy;/home/axom/axom_tpls/llvm-19.0.0/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-qv2wv4xikow5wxrhdem6c2dvkxt2cptf;/home/axom/axom_tpls/llvm-19.0.0/conduit-0.9.5-23be5eyo7aox2o6tzewawhlnjsci3pau;/home/axom/axom_tpls/llvm-19.0.0/gmake-4.4.1-xsybuyd32plkd6jcojnvdqkbwnt44ij6;/home/axom/axom_tpls/llvm-19.0.0/mfem-4.9.0-g3kaipukjvqu677tqztygnik5kzcxhfj;/home/axom/axom_tpls/llvm-19.0.0/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-dkkk27oc7p2gsrq4cslpdsv5qz7g3sbd;/home/axom/axom_tpls/llvm-19.0.0/umpire-2025.12.0-kulqmeumvx2c36r6rzz7lwndhsv3ot4i;/home/axom/axom_tpls/llvm-19.0.0/adiak-0.4.0-idbd67m2y4b4gnuxxbivm7hvesbbl4q5;/home/axom/axom_tpls/llvm-19.0.0/elfutils-0.193-437imlexpxs7pztuf7vh33f4mrvwh2i2;/home/axom/axom_tpls/llvm-19.0.0/libunwind-1.8.3-3tkxrnad3g5t5zjiodqfqne3sqfq5pzn;/home/axom/axom_tpls/llvm-19.0.0/hdf5-1.8.23-mwa7fanurwtkpebryywcggusn4gd7yg2;/home/axom/axom_tpls/llvm-19.0.0/parmetis-4.0.3-oe7i7ur4mu5sqkoet576wwcezbbodolt;/home/axom/axom_tpls/llvm-19.0.0/hypre-2.27.0-phspcafi6ten26qxkokwwij44ygoctxz;/home/axom/axom_tpls/llvm-19.0.0/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-y2k23nuv2eya2soe3fznpcgsgt2k2dai;/home/axom/axom_tpls/llvm-19.0.0/fmt-11.0.2-eutx47cvctyr7hyulz4rv4q4nfg5apzm;/home/axom/axom_tpls/llvm-19.0.0/zstd-1.5.7-ereaou2vj32kdin5v4z23qtd36yk4euw;/home/axom/axom_tpls/llvm-19.0.0/metis-5.1.0-sq3zbc3dla7ya33x67xr2evinhxbb6ey;/home/axom/axom_tpls/llvm-19.0.0/mpich-4.2.0-wxhfezauopwtrdjmf6hx32l2jh4yyb25;/home/axom/axom_tpls/none-none/gcc-runtime-13.3.1-ahhevkdxsqek4foiubajeiisj7ryali4;/home/axom/axom_tpls/llvm-19.0.0/hwloc-2.12.2-mab72jducih2myearbldanjcfr4epqcz;/home/axom/axom_tpls/llvm-19.0.0/libfabric-2.4.0-n7vbijzh3ebt3lahb5hyk7e4smcfyoxz;/home/axom/axom_tpls/llvm-19.0.0/yaksa-0.4-o3gdzqkwwkch27u7cr4yxzzjuewlg327;/home/axom/axom_tpls/llvm-19.0.0/libpciaccess-0.17-2sndua5wqv2xd2jy3ef52ehian4tpd4i;/home/axom/axom_tpls/llvm-19.0.0/libxml2-2.13.5-j3go7vs6gxvkyl7xdh476o3h3aos6zxk;/home/axom/axom_tpls/llvm-19.0.0/ncurses-6.5-20250705-qjve66dkqppk4z75pct2zhcyso2jsjhl;/home/axom/axom_tpls/llvm-19.0.0/libiconv-1.18-wwggylv5rglt7x7teuq3m7egjl27vbp5;/home/axom/axom_tpls/llvm-19.0.0/xz-5.6.3-55t47rdkjzlx64icqzp5lalnd67nsvnm;/home/axom/axom_tpls/llvm-19.0.0/zlib-ng-2.3.2-h74mdgso4hyns3yjgipxrwh3q5ngbbyt;/home/axom/axom_tpls/none-none/compiler-wrapper-1.0-u5fjo4cce7cqt6425ipfxnafausfog7z;/usr/lib/llvm-19" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/home/axom/axom_tpls/llvm-19.0.0/blt-0.7.1-p7mm766jfnjcbcnon7lmbmjk52d7nnfy;/home/axom/axom_tpls/llvm-19.0.0/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-qv2wv4xikow5wxrhdem6c2dvkxt2cptf;/home/axom/axom_tpls/llvm-19.0.0/conduit-0.9.5-vzh2futlihcbvpypnehpznvnqgijdflr;/home/axom/axom_tpls/llvm-19.0.0/gmake-4.4.1-xsybuyd32plkd6jcojnvdqkbwnt44ij6;/home/axom/axom_tpls/llvm-19.0.0/mfem-4.9.0-g3kaipukjvqu677tqztygnik5kzcxhfj;/home/axom/axom_tpls/llvm-19.0.0/py-nanobind-2.7.0-wvrnl66utfn2pr23wovdcp4eko42usc2;/home/axom/axom_tpls/none-none/py-pytest-9.0.0-rrb5ddzqzb7gvgzm7kpaqkye6bdjwsic;/home/axom/axom_tpls/llvm-19.0.0/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-dkkk27oc7p2gsrq4cslpdsv5qz7g3sbd;/home/axom/axom_tpls/llvm-19.0.0/umpire-2025.12.0-kulqmeumvx2c36r6rzz7lwndhsv3ot4i;/home/axom/axom_tpls/llvm-19.0.0/adiak-0.4.0-idbd67m2y4b4gnuxxbivm7hvesbbl4q5;/home/axom/axom_tpls/llvm-19.0.0/elfutils-0.193-437imlexpxs7pztuf7vh33f4mrvwh2i2;/home/axom/axom_tpls/llvm-19.0.0/libunwind-1.8.3-3tkxrnad3g5t5zjiodqfqne3sqfq5pzn;/home/axom/axom_tpls/llvm-19.0.0/hdf5-1.8.23-mwa7fanurwtkpebryywcggusn4gd7yg2;/home/axom/axom_tpls/llvm-19.0.0/parmetis-4.0.3-oe7i7ur4mu5sqkoet576wwcezbbodolt;/home/axom/axom_tpls/llvm-19.0.0/py-mpi4py-4.1.1-vcihkzxcnca4aeslr53dqlijjwhcwuuh;/home/axom/axom_tpls/llvm-19.0.0/py-numpy-2.4.2-3lrivxf4cgsisw7njzzpyihmo7ok2b77;/home/axom/axom_tpls/llvm-19.0.0/hypre-2.27.0-phspcafi6ten26qxkokwwij44ygoctxz;/home/axom/axom_tpls/llvm-19.0.0/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-y2k23nuv2eya2soe3fznpcgsgt2k2dai;/home/axom/axom_tpls/llvm-19.0.0/fmt-11.0.2-eutx47cvctyr7hyulz4rv4q4nfg5apzm;/home/axom/axom_tpls/llvm-19.0.0/zstd-1.5.7-ereaou2vj32kdin5v4z23qtd36yk4euw;/home/axom/axom_tpls/llvm-19.0.0/metis-5.1.0-sq3zbc3dla7ya33x67xr2evinhxbb6ey;/home/axom/axom_tpls/llvm-19.0.0/mpich-4.2.0-wxhfezauopwtrdjmf6hx32l2jh4yyb25;/home/axom/axom_tpls/none-none/gcc-runtime-13.3.1-ahhevkdxsqek4foiubajeiisj7ryali4;/home/axom/axom_tpls/llvm-19.0.0/hwloc-2.12.2-mab72jducih2myearbldanjcfr4epqcz;/home/axom/axom_tpls/llvm-19.0.0/libfabric-2.4.0-n7vbijzh3ebt3lahb5hyk7e4smcfyoxz;/home/axom/axom_tpls/llvm-19.0.0/yaksa-0.4-o3gdzqkwwkch27u7cr4yxzzjuewlg327;/home/axom/axom_tpls/llvm-19.0.0/libpciaccess-0.17-2sndua5wqv2xd2jy3ef52ehian4tpd4i;/home/axom/axom_tpls/llvm-19.0.0/libxml2-2.13.5-j3go7vs6gxvkyl7xdh476o3h3aos6zxk;/home/axom/axom_tpls/llvm-19.0.0/ncurses-6.5-20250705-qjve66dkqppk4z75pct2zhcyso2jsjhl;/home/axom/axom_tpls/llvm-19.0.0/libiconv-1.18-wwggylv5rglt7x7teuq3m7egjl27vbp5;/home/axom/axom_tpls/llvm-19.0.0/xz-5.6.3-55t47rdkjzlx64icqzp5lalnd67nsvnm;/home/axom/axom_tpls/llvm-19.0.0/zlib-ng-2.3.2-h74mdgso4hyns3yjgipxrwh3q5ngbbyt;/home/axom/axom_tpls/none-none/compiler-wrapper-1.0-u5fjo4cce7cqt6425ipfxnafausfog7z;/usr/lib/llvm-19" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/home/axom/axom_tpls/llvm-19.0.0/axom-develop-pue4pbqffz5ostcywjjmbqrpva3p7c2o/lib;/home/axom/axom_tpls/llvm-19.0.0/axom-develop-pue4pbqffz5ostcywjjmbqrpva3p7c2o/lib64;;" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/home/axom/axom_tpls/llvm-19.0.0/axom-develop-qzk4bzyik3p6ybwwp7djapznpldjxhvx/lib;/home/axom/axom_tpls/llvm-19.0.0/axom-develop-qzk4bzyik3p6ybwwp7djapznpldjxhvx/lib64;;" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/home/axom/axom_tpls/llvm-19.0.0/axom-develop-pue4pbqffz5ostcywjjmbqrpva3p7c2o/lib;/home/axom/axom_tpls/llvm-19.0.0/axom-develop-pue4pbqffz5ostcywjjmbqrpva3p7c2o/lib64;;" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/home/axom/axom_tpls/llvm-19.0.0/axom-develop-qzk4bzyik3p6ybwwp7djapznpldjxhvx/lib;/home/axom/axom_tpls/llvm-19.0.0/axom-develop-qzk4bzyik3p6ybwwp7djapznpldjxhvx/lib64;;" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") @@ -79,7 +79,7 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") set(TPL_ROOT "/home/axom/axom_tpls/llvm-19.0.0" CACHE PATH "") -set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-23be5eyo7aox2o6tzewawhlnjsci3pau" CACHE PATH "") +set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-vzh2futlihcbvpypnehpznvnqgijdflr" CACHE PATH "") # C2C not built @@ -104,13 +104,27 @@ set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main # scr not built #------------------------------------------------------------------------------ -# Devtools +# Devtools & Python #------------------------------------------------------------------------------ # ClangFormat disabled since llvm@19 and devtools not in spec set(ENABLE_CLANGFORMAT OFF CACHE BOOL "") +set(Python_EXECUTABLE "/usr/bin/python3" CACHE PATH "") + set(ENABLE_DOCS OFF CACHE BOOL "") +set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-wvrnl66utfn2pr23wovdcp4eko42usc2/lib/python3.12/site-packages" CACHE PATH "") + +set(PY_PYTEST_DIR "/home/axom/axom_tpls/none-none/py-pytest-9.0.0-rrb5ddzqzb7gvgzm7kpaqkye6bdjwsic/lib/python3.12/site-packages" CACHE PATH "") + +set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-3lrivxf4cgsisw7njzzpyihmo7ok2b77/lib/python3.12/site-packages" CACHE PATH "") + +set(PY_PLUGGY_DIR "/home/axom/axom_tpls/none-none/py-pluggy-1.6.0-3rhfyqmyucyrezyg67awukgbpmagwkr4/lib/python3.12/site-packages" CACHE PATH "") + +set(PY_INICONFIG_DIR "/home/axom/axom_tpls/none-none/py-iniconfig-2.1.0-72fwkqdbtz4ngubc37txds5iujbw7keb/lib/python3.12/site-packages" CACHE PATH "") + +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-vcihkzxcnca4aeslr53dqlijjwhcwuuh/lib/python3.12/site-packages" CACHE PATH "") + From f9d1b5f7e361a3ae4ab7754320f003333c8114f4 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Tue, 19 May 2026 12:44:35 -0700 Subject: [PATCH 293/986] Update docker image tags for GHA CI --- .github/workflows/ci-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index e3c6d82e5c..0371952443 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -12,8 +12,8 @@ concurrency: cancel-in-progress: ${{ github.ref != 'refs/heads/develop' }} env: - CLANG_DOCKER_IMAGE: axom/tpls:clang-19_02-17-26_21h-05m - GCC_DOCKER_IMAGE: axom/tpls:gcc-13_02-17-26_21h-05m + CLANG_DOCKER_IMAGE: axom/tpls:clang-19_05-19-26_17h-35m + GCC_DOCKER_IMAGE: axom/tpls:gcc-13_05-19-26_17h-32m jobs: # Hacky solution to reference env variables outside of `run` steps https://stackoverflow.com/a/74217028 From a812569d7936b7b305028987bac6cc23feabe54d Mon Sep 17 00:00:00 2001 From: Dylan Copeland Date: Wed, 20 May 2026 10:29:43 -0700 Subject: [PATCH 294/986] Unit test with multiple self intersections. --- src/axom/primal/tests/primal_nurbs_curve.cpp | 90 +++++++++++++++++++- 1 file changed, 89 insertions(+), 1 deletion(-) diff --git a/src/axom/primal/tests/primal_nurbs_curve.cpp b/src/axom/primal/tests/primal_nurbs_curve.cpp index afe52aeeff..e437585403 100644 --- a/src/axom/primal/tests/primal_nurbs_curve.cpp +++ b/src/axom/primal/tests/primal_nurbs_curve.cpp @@ -1267,11 +1267,99 @@ TEST(primal_nurbscurve, nurbscurve_circle_intersections) const auto circle1 = NURBSCurveType::make_circular_arc_nurbs(0.0, 2.0 * M_PI, 0.0, 0.0, 1.0); const auto circle2 = NURBSCurveType::make_circular_arc_nurbs(0.0, 2.0 * M_PI, 1.0, 0.0, 1.0); - axom::Array p1, p2, q1, q2; + axom::Array p1, p2; const bool found = intersect(circle1, circle2, p1, p2); EXPECT_TRUE(found && p1.size() == 2 && p2.size() == 2); } +primal::NURBSCurve make_cubic_shape() +{ + // Open cubic NURBS curve (degree 3), non-rational. + // 24 control points, 28 knots (n + p + 2: 24 + 3 + 1 = 28). + using Point2D = primal::Point; + + axom::Array weights(24); + for(int i = 0; i < 24; ++i) weights[i] = 1.0; + + return primal::NURBSCurve( + axom::Array { + Point2D {-5.0, +3.6}, Point2D {-3.4, +3.0}, Point2D {-1.8, +2.2}, Point2D {-0.4, +1.0}, + Point2D {+1.0, -0.4}, Point2D {+2.4, -1.6}, Point2D {+3.6, -2.4}, Point2D {+4.4, -1.0}, + Point2D {+3.6, +0.8}, Point2D {+2.0, +2.0}, Point2D {+0.0, +2.8}, Point2D {-2.0, +2.0}, + Point2D {-3.2, +0.8}, Point2D {-2.6, -0.8}, Point2D {-1.0, -1.6}, Point2D {+0.4, -2.2}, + Point2D {+1.6, -2.6}, Point2D {+0.0, -3.4}, Point2D {-2.2, -3.0}, Point2D {-2.8, -1.4}, + Point2D {-1.0, +0.4}, Point2D {+1.8, +1.8}, Point2D {+3.4, +3.0}, Point2D {+5.0, +3.6}}, + weights, + axom::Array {0.0, 0.0, 0.0, 0.0, 0.0476190476, + 0.0952380952, 0.1428571429, 0.1904761905, 0.2380952381, 0.2857142857, + 0.3333333333, 0.3809523810, 0.4285714286, 0.4761904762, 0.5238095238, + 0.5714285714, 0.6190476190, 0.6666666667, 0.7142857143, 0.7619047619, + 0.8095238095, 0.8571428571, 0.9047619048, 0.9523809524, 1.0, + 1.0, 1.0, 1.0}); +} + +primal::NURBSCurve make_ellipse_curve() +{ + // Quadratic rational NURBS ellipse (degree 2), 9-control-point 4-arc construction. + // Center, semi-axes: + // center = (+0.055509, +0.246636) + // semi-axes = (2.506091, 2.506234) (a == b -> circle in this case) + // rotation = +0.0000 degrees + // Corner control-point weights are w = cos(pi/4) = sqrt(2)/2. + using Point2D = primal::Point; + const double w = 1.0 / std::sqrt(2.0); + + return primal::NURBSCurve( + axom::Array {Point2D {+2.5615994286, +0.2466357797}, + Point2D {+2.5615994286, +2.7528699841}, + Point2D {+0.0555086484, +2.7528699841}, + Point2D {-2.4505821318, +2.7528699841}, + Point2D {-2.4505821318, +0.2466357797}, + Point2D {-2.4505821318, -2.2595984246}, + Point2D {+0.0555086484, -2.2595984246}, + Point2D {+2.5615994286, -2.2595984246}, + Point2D {+2.5615994286, +0.2466357797}}, + axom::Array {1.0, w, 1.0, w, 1.0, w, 1.0, w, 1.0}, + axom::Array {0.0, 0.0, 0.0, 0.25, 0.25, 0.5, 0.5, 0.75, 0.75, 1.0, 1.0, 1.0}); +} + +//------------------------------------------------------------------------------ +TEST(primal_nurbscurve, nurbscurve_self_intersections) +{ + constexpr int DIM = 2; + using CoordType = double; + using Point2D = primal::Point; + using NURBSCurveType = primal::NURBSCurve; + + NURBSCurveType curve1 = make_cubic_shape(); + NURBSCurveType curve2 = make_ellipse_curve(); + + axom::Array p1, p2; + const bool found = intersect(curve1, curve2, p1, p2); + EXPECT_TRUE(found && p1.size() == 8 && p2.size() == 8); + + axom::Array intersections {Point2D {-1.7060766733448747, 2.0292202966044366}, + Point2D {2.044376271360025, -1.2782097206437852}, + Point2D {1.884074237582652, 1.9604430112255964}, + Point2D {-2.1644784828526533, -0.9161966686461217}, + Point2D {0.5039968961448462, -2.2191102081421588}}; + + primal::Point int1, int2; + std::array intid = {0, 1, 2, 0, 3, 4, 3, 2}; + + for(int i = 0; i < p1.size(); ++i) + { + int1 = curve1.evaluate(p1[i]); + int2 = curve2.evaluate(p2[i]); + + for(int j = 0; j < DIM; ++j) + { + EXPECT_NEAR(int1[j], int2[j], 1e-8); + EXPECT_NEAR(int1[j], intersections[intid[i]][j], 1e-4); + } + } +} + int main(int argc, char* argv[]) { int result = 0; From 26cd2d56b49a4f7c1cb19eb3dda12ff7dd5afea9 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Wed, 20 May 2026 14:19:32 -0700 Subject: [PATCH 295/986] Keep spec usage consistent --- scripts/spack/packages/axom/package.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/spack/packages/axom/package.py b/scripts/spack/packages/axom/package.py index de5845a12f..f191f31cfe 100644 --- a/scripts/spack/packages/axom/package.py +++ b/scripts/spack/packages/axom/package.py @@ -495,9 +495,9 @@ def initconfig_hardware_entries(self): hip_link_flags += "-lflang -lflangrti " # Additional library path for cray compiler - if self.spec.satisfies("%cce"): + if spec.satisfies("%cce"): hip_link_flags += "-L/opt/cray/pe/cce/{0}/cce/x86_64/lib -Wl,-rpath,/opt/cray/pe/cce/{0}/cce/x86_64/lib ".format( - self.spec.compiler.version + spec.compiler.version ) if spec.satisfies("+fortran"): @@ -576,7 +576,7 @@ def initconfig_hardware_entries(self): cmake_cache_string("BLT_OPENMP_LINK_FLAGS", openmp_gen_exp, description) ) - if spec.satisfies("+openmp") and spec.satisfies("+rocm") and self.spec.satisfies("%cce"): + if spec.satisfies("+openmp") and spec.satisfies("+rocm") and spec.satisfies("%cce"): openmp_gen_exp = ( "$<$>:" "-fopenmp=libomp>;$<$ Date: Wed, 20 May 2026 15:20:29 -0700 Subject: [PATCH 296/986] Clarify RAJA's cmake version requirement --- scripts/spack/configs/docker/ubuntu22_cuda/spack.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/spack/configs/docker/ubuntu22_cuda/spack.yaml b/scripts/spack/configs/docker/ubuntu22_cuda/spack.yaml index 426d5b802d..9911752dec 100644 --- a/scripts/spack/configs/docker/ubuntu22_cuda/spack.yaml +++ b/scripts/spack/configs/docker/ubuntu22_cuda/spack.yaml @@ -127,7 +127,7 @@ spack: # Globally lock in version of devtools - # RAJA needs cmake 3.24: + # RAJA's spack package depends on a newer version of CMake for CUDA than 3.23.1 # cmake: # version: [3.23.1] # buildable: false From 8e66e3d199d32d49017155547b901226ed2da3d6 Mon Sep 17 00:00:00 2001 From: Chris White Date: Wed, 20 May 2026 16:05:39 -0700 Subject: [PATCH 297/986] bump ci deadline --- .gitlab/build_matrix.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab/build_matrix.yml b/.gitlab/build_matrix.yml index 337869d676..284e0c6d65 100644 --- a/.gitlab/build_matrix.yml +++ b/.gitlab/build_matrix.yml @@ -8,7 +8,7 @@ # This is the shared configuration of jobs for matrix .on_matrix: variables: - SCHEDULER_PARAMETERS: "--partition=pci --exclusive=user --deadline=now+1hour -N1 -t ${ALLOC_TIME}" + SCHEDULER_PARAMETERS: "--partition=pci --exclusive=user --deadline=now+3hour -N1 -t ${ALLOC_TIME}" tags: - batch - matrix @@ -25,7 +25,7 @@ # Template .src_build_on_matrix: variables: - ALLOC_TIME: "40" + ALLOC_TIME: "55" extends: [.src_build_script, .on_matrix, .src_workflow] needs: [] From d3668f4b7034ee29795ee23de45511f46ec62cbe Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 20 May 2026 16:13:55 -0700 Subject: [PATCH 298/986] 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 157c22edbcea0f9b960704d981bb9cd6e7a0f46d Mon Sep 17 00:00:00 2001 From: Kenny Weiss Date: Wed, 20 May 2026 16:29:02 -0700 Subject: [PATCH 299/986] Adds comment about intersection test --- src/axom/primal/tests/primal_nurbs_curve.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/axom/primal/tests/primal_nurbs_curve.cpp b/src/axom/primal/tests/primal_nurbs_curve.cpp index e437585403..a10ac8e439 100644 --- a/src/axom/primal/tests/primal_nurbs_curve.cpp +++ b/src/axom/primal/tests/primal_nurbs_curve.cpp @@ -1334,6 +1334,7 @@ TEST(primal_nurbscurve, nurbscurve_self_intersections) NURBSCurveType curve1 = make_cubic_shape(); NURBSCurveType curve2 = make_ellipse_curve(); + // Note: This pair of NURBS curves has eight intersections at five unique intersection points axom::Array p1, p2; const bool found = intersect(curve1, curve2, p1, p2); EXPECT_TRUE(found && p1.size() == 8 && p2.size() == 8); From c7d51858c0c474b56793a47413b9c1afb9ec7e56 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Fri, 22 May 2026 00:15:03 +0000 Subject: [PATCH 300/986] 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 301/986] 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 302/986] 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 303/986] 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 304/986] 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 305/986] 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 306/986] 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 307/986] 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 308/986] 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 309/986] 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 310/986] 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 311/986] 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 312/986] 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 313/986] 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 314/986] 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 315/986] 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 316/986] 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 317/986] 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 318/986] 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 8e4b7c352ef754b2184df1b5cb92f9956ef8db8e Mon Sep 17 00:00:00 2001 From: Brian Han Date: Thu, 28 May 2026 11:42:56 -0700 Subject: [PATCH 319/986] Adds the --resolution alias for input parsing to avoid keyword conflict with flux --- src/axom/quest/examples/CMakeLists.txt | 24 ++++++++++----------- src/axom/quest/examples/shaping_driver.cpp | 2 +- src/axom/quest/tests/CMakeLists.txt | 2 +- src/axom/quest/tests/quest_mesh_clipper.cpp | 2 +- src/tools/CMakeLists.txt | 4 ++-- src/tools/data_collection_util.cpp | 2 +- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/axom/quest/examples/CMakeLists.txt b/src/axom/quest/examples/CMakeLists.txt index b6431e547e..953089528f 100644 --- a/src/axom/quest/examples/CMakeLists.txt +++ b/src/axom/quest/examples/CMakeLists.txt @@ -165,7 +165,7 @@ if(AXOM_ENABLE_MPI AND MFEM_FOUND AND MFEM_USE_MPI COMMAND quest_shaping_driver_ex -i ${shaping_data_dir}/circles.yaml --method sampling - inline_mesh --min -6 -6 --max 6 6 --res 25 25 -d 2 + inline_mesh --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 @@ -177,7 +177,7 @@ if(AXOM_ENABLE_MPI AND MFEM_FOUND AND MFEM_USE_MPI COMMAND quest_shaping_driver_ex -i ${shaping_data_dir}/circles_opposite.yaml --method sampling - inline_mesh --min -6 -6 --max 6 6 --res 25 25 -d 2 + inline_mesh --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 ~65.97 set_tests_properties(${_testname} PROPERTIES @@ -190,7 +190,7 @@ if(AXOM_ENABLE_MPI AND MFEM_FOUND AND MFEM_USE_MPI -i ${shaping_data_dir}/balls_and_jacks.yaml --method sampling --background-material air - inline_mesh --min -10 0 --max 60 50 --res 25 25 -d 2 + inline_mesh --min -10 0 --max 60 50 --resolution 25 25 -d 2 NUM_MPI_TASKS ${_nranks}) # background: 3500; balls: ~373; jacks: ~230; air ~2897 set_tests_properties(${_testname} PROPERTIES @@ -203,7 +203,7 @@ if(AXOM_ENABLE_MPI AND MFEM_FOUND AND MFEM_USE_MPI -i ${shaping_data_dir}/ball_impact.yaml --method sampling --background-material air - inline_mesh --min -10 0 --max 40 30 --res 25 25 -d 2 + inline_mesh --min -10 0 --max 40 30 --resolution 25 25 -d 2 NUM_MPI_TASKS ${_nranks}) # steel ball: semi-cirle of radius 5.5 has area ~47.516 set_tests_properties(${_testname} PROPERTIES @@ -217,7 +217,7 @@ if(AXOM_ENABLE_MPI AND MFEM_FOUND AND MFEM_USE_MPI -i ${shaping_data_dir}/ball_impact.yaml --method sampling --background-material air - inline_mesh --min -30 -30 -10 --max 30 30 40 --res 16 16 16 -d3 + inline_mesh --min -30 -30 -10 --max 30 30 40 --resolution 16 16 16 -d3 NUM_MPI_TASKS ${_nranks}) # steel ball: cirle of radius 5.5 has analytic volume ~696.91 set_tests_properties(${_testname} PROPERTIES @@ -231,7 +231,7 @@ if(AXOM_ENABLE_MPI AND MFEM_FOUND AND MFEM_USE_MPI COMMAND quest_shaping_driver_ex -i ${shaping_data_dir}/flat_star.yaml --method intersection - inline_mesh --min 0 0 --max 1 1 --res 4 4 -d 2 + inline_mesh --min 0 0 --max 1 1 --resolution 4 4 -d 2 NUM_MPI_TASKS ${_nranks}) # steel triangle: star composed of 16 right triangles with # leg length 0.25 has analytic volume 0.5 @@ -265,7 +265,7 @@ if(AXOM_ENABLE_MPI AND MFEM_FOUND AND MFEM_USE_MPI --policy ${_policy} --segments-per-knot-span 50 --method intersection - inline_mesh --min 0 0 --max 3 3 --res 3 3 -d 2 + inline_mesh --min 0 0 --max 3 3 --resolution 3 3 -d 2 NUM_MPI_TASKS ${_nranks} NUM_OMP_THREADS ${_num_threads}) # steel unit semi-circle with an area of pi/2 @@ -283,7 +283,7 @@ if(AXOM_ENABLE_MPI AND MFEM_FOUND AND MFEM_USE_MPI COMMAND quest_shaping_driver_ex -i ${shaping_data_dir}/heroic_roses_mfem.yaml --method sampling - inline_mesh --min 0 0 --max 300 400 --res 150 200 -d 2 + inline_mesh --min 0 0 --max 300 400 --resolution 150 200 -d 2 NUM_MPI_TASKS ${_nranks}) set_tests_properties(${_testname} PROPERTIES PASS_REGULAR_EXPRESSION "Volume of material 'black' is 36,?595.") @@ -295,7 +295,7 @@ if(AXOM_ENABLE_MPI AND MFEM_FOUND AND MFEM_USE_MPI COMMAND quest_shaping_driver_ex -i ${shaping_data_dir}/heroic_roses_mfem_cp.yaml --method sampling --sampling windingnumber - inline_mesh --min 0 0 --max 300 400 --res 150 200 -d 2 + inline_mesh --min 0 0 --max 300 400 --resolution 150 200 -d 2 NUM_MPI_TASKS ${_nranks}) set_tests_properties(${_testname} PROPERTIES PASS_REGULAR_EXPRESSION "Volume of material 'black' is 36,?654.") @@ -308,7 +308,7 @@ if(AXOM_ENABLE_MPI AND MFEM_FOUND AND MFEM_USE_MPI COMMAND quest_shaping_driver_ex -i ${shaping_data_dir}/sphere.yaml --method sampling - inline_mesh --min -6 -6 -6 --max 6 6 6 --res 16 16 16 -d 3 + inline_mesh --min -6 -6 -6 --max 6 6 6 --resolution 16 16 16 -d 3 NUM_MPI_TASKS ${_nranks}) # sphere of radius 5 has volume: 4/3 pi * r^3 ~523.6 # input sphere is discretized and mesh is coarse; accuracy is within ~1% @@ -322,7 +322,7 @@ if(AXOM_ENABLE_MPI AND MFEM_FOUND AND MFEM_USE_MPI -i ${shaping_data_dir}/spheres.yaml --method sampling --background-material void - inline_mesh --min -6 -6 -6 --max 6 6 6 --res 16 16 16 -d 3 + inline_mesh --min -6 -6 -6 --max 6 6 6 --resolution 16 16 16 -d 3 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 @@ -336,7 +336,7 @@ if(AXOM_ENABLE_MPI AND MFEM_FOUND AND MFEM_USE_MPI -i ${shaping_data_dir}/plane.yaml --method sampling --background-material air - inline_mesh --min -430 -45 -35 --max 430 855 210 --res 16 16 16 -d 3 + inline_mesh --min -430 -45 -35 --max 430 855 210 --resolution 16 16 16 -d 3 NUM_MPI_TASKS ${_nranks}) # background: 860*900*245=189,630,000; airplane volume from meshlab: 7,420,578.5 # expected volume when fully resolved: 182,209,421.5 diff --git a/src/axom/quest/examples/shaping_driver.cpp b/src/axom/quest/examples/shaping_driver.cpp index fc1e635f40..283b31f26d 100644 --- a/src/axom/quest/examples/shaping_driver.cpp +++ b/src/axom/quest/examples/shaping_driver.cpp @@ -283,7 +283,7 @@ struct Input ->expected(2, 3) ->required(); - inline_mesh_subcommand->add_option("--res", boxResolution) + inline_mesh_subcommand->add_option("--res, --resolution", boxResolution) ->description("Resolution of the box mesh (i,j[,k])") ->expected(2, 3) ->required(); diff --git a/src/axom/quest/tests/CMakeLists.txt b/src/axom/quest/tests/CMakeLists.txt index d30e7f40ab..66b3d792be 100644 --- a/src/axom/quest/tests/CMakeLists.txt +++ b/src/axom/quest/tests/CMakeLists.txt @@ -297,7 +297,7 @@ if(AXOM_ENABLE_KLEE AND AXOM_ENABLE_SIDRE AND CONDUIT_FOUND AND RAJA_FOUND AND A --scale .99 .99 .99 --dir 8 4 2 --meshType ${_meshType} - inline_mesh --min -2 -2 -2 --max 2 2 2 --res 16 16 16 + inline_mesh --min -2 -2 -2 --max 2 2 2 --resolution 16 16 16 NUM_MPI_TASKS ${_nranks} NUM_OMP_THREADS ${_num_threads}) endforeach() diff --git a/src/axom/quest/tests/quest_mesh_clipper.cpp b/src/axom/quest/tests/quest_mesh_clipper.cpp index 0f9cb60d49..a75be27c15 100644 --- a/src/axom/quest/tests/quest_mesh_clipper.cpp +++ b/src/axom/quest/tests/quest_mesh_clipper.cpp @@ -225,7 +225,7 @@ struct Input ->expected(2, 3) ->required(); - inline_mesh_subcommand->add_option("--res", boxResolution) + inline_mesh_subcommand->add_option("--res, --resolution", boxResolution) ->description("Resolution of the box mesh (i,j[,k])") ->expected(2, 3) ->required(); diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index 6aadb77a9b..498ead9e39 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -140,12 +140,12 @@ if(AXOM_ENABLE_SIDRE AND AXOM_ENABLE_PRIMAL AND MFEM_FOUND) axom_add_test( NAME data_collection_util_box2D - COMMAND data_collection_util --min -1 -1 --max 1 1 --res 16 16 -p 1 + COMMAND data_collection_util --min -1 -1 --max 1 1 --resolution 16 16 -p 1 NUM_MPI_TASKS ${_nranks} ) axom_add_test( NAME data_collection_util_box3D - COMMAND data_collection_util --min -1 -1 -1 --max 1 1 1 --res 16 16 16 -p 1 + COMMAND data_collection_util --min -1 -1 -1 --max 1 1 1 --resolution 16 16 16 -p 1 NUM_MPI_TASKS ${_nranks} ) diff --git a/src/tools/data_collection_util.cpp b/src/tools/data_collection_util.cpp index b4ee531c20..7794e44077 100644 --- a/src/tools/data_collection_util.cpp +++ b/src/tools/data_collection_util.cpp @@ -195,7 +195,7 @@ struct Input minbb->needs(maxbb); maxbb->needs(minbb); - box_options->add_option("--res", boxResolution) + box_options->add_option("--res, --resolution", boxResolution) ->description("Resolution of the box mesh (i,j[,k])") ->expected(2, 3); From c909d9524cedf87fd86cd5bbe266c6414d1bec33 Mon Sep 17 00:00:00 2001 From: Brian Manh Hien Han Date: Fri, 20 Jun 2025 15:05:32 -0700 Subject: [PATCH 320/986] Account for int64 conduit values; remove implicit perfect square check --- .../examples/quest_candidates_example.cpp | 39 ++++++++++++------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/src/axom/quest/examples/quest_candidates_example.cpp b/src/axom/quest/examples/quest_candidates_example.cpp index e13c9a9778..7ec2cbc4d7 100644 --- a/src/axom/quest/examples/quest_candidates_example.cpp +++ b/src/axom/quest/examples/quest_candidates_example.cpp @@ -269,16 +269,6 @@ HexMesh loadBlueprintHexMesh(const std::string& mesh_path, bool verboseOutput = int connectivity_size = (n_load[0]["topologies/topo/elements/connectivity"]).dtype().number_of_elements(); - // Sanity check for number of cells - int cell_calc_from_nodes = std::round(std::pow(std::pow(num_nodes, 1.0 / 3.0) - 1, 3)); - int cell_calc_from_connectivity = connectivity_size / HEX_OFFSET; - if(cell_calc_from_nodes != cell_calc_from_connectivity) - { - SLIC_ERROR("Number of cells is not expected!\n" - << "First calculation is " << cell_calc_from_nodes << " and second calculation is " - << cell_calc_from_connectivity); - } - // extract hexes into an axom::Array auto x_vals_h = axom::ArrayView(n_load[0]["coordsets/coords/values/x"].value(), num_nodes); auto y_vals_h = axom::ArrayView(n_load[0]["coordsets/coords/values/y"].value(), num_nodes); @@ -298,9 +288,32 @@ HexMesh loadBlueprintHexMesh(const std::string& mesh_path, bool verboseOutput = auto z_vals_view = on_device ? z_vals_d.view() : z_vals_h; // Move connectivity information onto device - auto connectivity_h = - axom::ArrayView(n_load[0]["topologies/topo/elements/connectivity"].value(), - connectivity_size); + + int* conn_data = nullptr; + axom::Array temp_conn_data; + int conn_id = n_load[0]["topologies/topo/elements/connectivity"].dtype().id(); + if(conn_id == conduit::DataType::INT32_ID) + { + conn_data = n_load[0]["topologies/topo/elements/connectivity"].as_int32_ptr(); + } + else if(conn_id == conduit::DataType::INT64_ID) + { + temp_conn_data.resize(connectivity_size); + auto conn_int64_data = n_load[0]["topologies/topo/elements/connectivity"].as_int64_ptr(); + for(int i = 0; i < connectivity_size; ++i) + { + temp_conn_data[i] = static_cast(conn_int64_data[i]); + } + conn_data = temp_conn_data.data(); + } + else + { + SLIC_ERROR("Node connectivity data type " + << n_load[0]["topologies/topo/elements/connectivity"].dtype().name() + << " is unsupported!"); + } + + auto connectivity_h = axom::ArrayView(conn_data, connectivity_size); axom::Array connectivity_d = on_device ? axom::Array(connectivity_h, kernel_allocator) : axom::Array(); From cfbd4ea97f69db423f152fc93ae1d29579e35b09 Mon Sep 17 00:00:00 2001 From: Brian Manh Hien Han Date: Tue, 24 Jun 2025 14:52:33 -0700 Subject: [PATCH 321/986] Add +mpi dependency to quest_candidates_examples; can run with MPI, caveat expect one domain per rank --- src/axom/quest/examples/CMakeLists.txt | 4 +- .../examples/quest_candidates_example.cpp | 90 +++++++++++++++---- 2 files changed, 76 insertions(+), 18 deletions(-) diff --git a/src/axom/quest/examples/CMakeLists.txt b/src/axom/quest/examples/CMakeLists.txt index 953089528f..be89d5a95b 100644 --- a/src/axom/quest/examples/CMakeLists.txt +++ b/src/axom/quest/examples/CMakeLists.txt @@ -89,12 +89,12 @@ axom_add_executable( ) # BVH silo example ------------------------------------------------------------ -if (CONDUIT_FOUND AND UMPIRE_FOUND) +if (AXOM_ENABLE_MPI AND CONDUIT_FOUND AND UMPIRE_FOUND) axom_add_executable( NAME quest_candidates_example_ex SOURCES quest_candidates_example.cpp OUTPUT_DIR ${EXAMPLE_OUTPUT_DIRECTORY} - DEPENDS_ON ${quest_example_depends} conduit::conduit umpire + DEPENDS_ON ${quest_example_depends} conduit::conduit conduit::conduit_mpi umpire FOLDER axom/quest/examples ) diff --git a/src/axom/quest/examples/quest_candidates_example.cpp b/src/axom/quest/examples/quest_candidates_example.cpp index 7ec2cbc4d7..c1fae4c8b7 100644 --- a/src/axom/quest/examples/quest_candidates_example.cpp +++ b/src/axom/quest/examples/quest_candidates_example.cpp @@ -24,11 +24,19 @@ #error This example requires axom to be configured with Umpire support #endif +#ifdef AXOM_USE_MPI + #include "mpi.h" +#else + #error This example requires axom to be configured with MPI support +#endif + #ifdef AXOM_USE_CONDUIT #include "conduit_relay.hpp" #include "conduit_blueprint.hpp" + #include "conduit_blueprint_mpi.hpp" + #include "conduit_relay_mpi_io_blueprint.hpp" #else - #error This example requires axom to be configured with Conduit support + #error This example requires axom to be configured with Conduit + MPI support #endif #include "axom/mint.hpp" @@ -48,6 +56,10 @@ using UMesh = axom::mint::UnstructuredMesh; using IndexPair = std::pair; using RuntimePolicy = axom::runtime_policy::Policy; +// MPI globals, set in main(). +int myRank = -1; +int numRanks = -1; + //----------------------------------------------------------------------------- /// Basic RAII utility class for initializing and finalizing slic logger //----------------------------------------------------------------------------- @@ -56,21 +68,21 @@ struct BasicLogger BasicLogger() { namespace slic = axom::slic; - // Initialize the SLIC logger slic::initialize(); - slic::setLoggingMsgLevel(slic::message::Debug); + slic::setLoggingMsgLevel(slic::message::Info); - // Customize logging levels and formatting - const std::string slicFormatStr = "[] \n"; + slic::LogStream* logStream; - slic::addStreamToMsgLevel(new slic::GenericOutputStream(&std::cerr), slic::message::Error); - slic::addStreamToMsgLevel(new slic::GenericOutputStream(&std::cerr, slicFormatStr), - slic::message::Warning); + std::string fmt = "[][]: \n"; +#ifdef AXOM_USE_LUMBERJACK + const int RLIMIT = 8; + logStream = new slic::LumberjackStream(&std::cout, MPI_COMM_WORLD, RLIMIT, fmt); +#else + logStream = new slic::SynchronizedStream(&std::cout, MPI_COMM_WORLD, fmt); +#endif // AXOM_USE_MPI - auto* compactStream = new slic::GenericOutputStream(&std::cout, slicFormatStr); - slic::addStreamToMsgLevel(compactStream, slic::message::Info); - slic::addStreamToMsgLevel(compactStream, slic::message::Debug); + slic::addStreamToAllMsgLevels(logStream); } ~BasicLogger() { axom::slic::finalize(); } @@ -245,8 +257,24 @@ HexMesh loadBlueprintHexMesh(const std::string& mesh_path, bool verboseOutput = // Load Blueprint mesh into Conduit node conduit::Node n_load; + + // Read mesh as single domain first; if partitioned, then reset and reload with MPI conduit::relay::io::blueprint::read_mesh(mesh_path, n_load); + if(conduit::blueprint::mesh::number_of_domains(n_load) > 1) + { + n_load.reset(); + conduit::relay::mpi::io::blueprint::read_mesh(mesh_path, n_load, MPI_COMM_WORLD); + } + + // Requirement that each rank has 1 domain + if(conduit::blueprint::mesh::number_of_domains(n_load) != 1) + { + SLIC_ERROR(axom::fmt::format("Rank {} has {} domains. Must have 1 domain per rank!\n", + myRank, + conduit::blueprint::mesh::number_of_domains(n_load))); + } + // Check if Blueprint mesh conforms conduit::Node n_info; if(conduit::blueprint::verify("mesh", n_load, n_info) == false) @@ -391,8 +419,10 @@ HexMesh loadBlueprintHexMesh(const std::string& mesh_path, bool verboseOutput = } // Write out to vtk for test viewing - SLIC_INFO("Writing out Blueprint mesh to test.vtk for debugging..."); - axom::mint::write_vtk(mesh, "test.vtk"); + std::string vtk_file_name = "test_" + std::to_string(myRank) + ".vtk"; + SLIC_INFO(axom::fmt::format("Writing out Blueprint mesh to {} for debugging...", vtk_file_name)); + + axom::mint::write_vtk(mesh, vtk_file_name); delete mesh; mesh = nullptr; @@ -659,6 +689,10 @@ std::vector findCandidatesImplicit(const HexMesh& insertMesh, int main(int argc, char** argv) { + axom::utilities::raii::MPIWrapper mpi_raii_wrapper(argc, argv); + myRank = mpi_raii_wrapper.my_rank(); + numRanks = mpi_raii_wrapper.num_ranks(); + #if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) using omp_exec = axom::OMP_EXEC; #endif @@ -686,7 +720,18 @@ int main(int argc, char** argv) } catch(const axom::CLI::ParseError& e) { - return app.exit(e); + int retval = -1; + if(myRank == 0) + { + retval = app.exit(e); + } + +#ifdef AXOM_USE_MPI + MPI_Bcast(&retval, 1, MPI_INT, 0, MPI_COMM_WORLD); + MPI_Finalize(); +#endif + + exit(retval); } } @@ -816,6 +861,7 @@ int main(int argc, char** argv) // print first few pairs const int numCandidates = candidatePairs.size(); + if(numCandidates > 0 && params.isVerbose()) { constexpr int MAX_PRINT = 20; @@ -832,8 +878,9 @@ int main(int argc, char** argv) } // Write out candidate pairs - SLIC_INFO("Writing out candidate pairs..."); - std::ofstream outf("candidates.txt"); + std::string candidates_file_name = "candidates_" + std::to_string(myRank) + ".txt"; + SLIC_INFO(axom::fmt::format("Writing out candidate pairs to {}...", candidates_file_name)); + std::ofstream outf(candidates_file_name); outf << candidatePairs.size() << " candidate pairs:" << std::endl; for(unsigned long i = 0; i < candidatePairs.size(); ++i) @@ -841,6 +888,17 @@ int main(int argc, char** argv) outf << candidatePairs[i].first << " " << candidatePairs[i].second << std::endl; } } + + // Print total number of pairs across all ranks + int totalNumCandidates = 0; + MPI_Reduce(&numCandidates, &totalNumCandidates, 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD); + if(myRank == 0) + { + SLIC_INFO(axom::fmt::format(axom::utilities::locale(), + "Mesh had {:L} a total of candidates pairs across all ranks", + totalNumCandidates)); + } + AXOM_ANNOTATE_END("find candidates"); return 0; From c4d4659ad81966686a172606503ba9f19270296a Mon Sep 17 00:00:00 2001 From: Brian Han Date: Tue, 5 May 2026 13:08:37 -0700 Subject: [PATCH 322/986] Make explicit that only the input mesh can be partitioned --- src/axom/quest/examples/CMakeLists.txt | 5 +- .../examples/quest_candidates_example.cpp | 117 +++++++++++++----- 2 files changed, 88 insertions(+), 34 deletions(-) diff --git a/src/axom/quest/examples/CMakeLists.txt b/src/axom/quest/examples/CMakeLists.txt index be89d5a95b..ec343820ba 100644 --- a/src/axom/quest/examples/CMakeLists.txt +++ b/src/axom/quest/examples/CMakeLists.txt @@ -88,7 +88,7 @@ axom_add_executable( FOLDER axom/quest/examples ) -# BVH silo example ------------------------------------------------------------ +# BVH Blueprint candidate example ------------------------------------------------- if (AXOM_ENABLE_MPI AND CONDUIT_FOUND AND UMPIRE_FOUND) axom_add_executable( NAME quest_candidates_example_ex @@ -136,7 +136,7 @@ if (AXOM_ENABLE_MPI AND CONDUIT_FOUND AND UMPIRE_FOUND) # Match either one comma or none for portability set_tests_properties(${_testname} PROPERTIES - PASS_REGULAR_EXPRESSION "Mesh had 6[,]?859 candidates pairs") + PASS_REGULAR_EXPRESSION "Mesh had 6[,]?859 candidate pairs") endforeach() endforeach() endif() @@ -789,4 +789,3 @@ if(OPENCASCADE_FOUND) endif() endif() - diff --git a/src/axom/quest/examples/quest_candidates_example.cpp b/src/axom/quest/examples/quest_candidates_example.cpp index c1fae4c8b7..f9b07a23e8 100644 --- a/src/axom/quest/examples/quest_candidates_example.cpp +++ b/src/axom/quest/examples/quest_candidates_example.cpp @@ -6,7 +6,7 @@ //----------------------------------------------------------------------------- /// -/// file: quest_candidates_examples.cpp +/// file: quest_candidates_example.cpp /// /// This example takes as input two Blueprint unstructured hex meshes, and /// finds the candidates of intersection between the meshes using a @@ -56,9 +56,17 @@ using UMesh = axom::mint::UnstructuredMesh; using IndexPair = std::pair; using RuntimePolicy = axom::runtime_policy::Policy; +namespace +{ // MPI globals, set in main(). int myRank = -1; -int numRanks = -1; + +enum class MeshLoadPolicy +{ + Replicated, + OneDomainPerRankOrReplicated +}; +} // namespace //----------------------------------------------------------------------------- /// Basic RAII utility class for initializing and finalizing slic logger @@ -111,12 +119,16 @@ struct Input void Input::parse(int argc, char** argv, axom::CLI::App& app) { app.add_option("-i, --infile", mesh_file_first) - ->description("The first input Blueprint mesh file to insert into spatial index") + ->description( + "The first input Blueprint mesh file to insert into spatial index.\n" + "May be single-domain on all ranks or partitioned with one domain per rank.") ->required() ->check(axom::CLI::ExistingFile); app.add_option("-q, --queryfile", mesh_file_second) - ->description("The second input Blueprint mesh file to query spatial index") + ->description( + "The second input Blueprint mesh file to query spatial index.\n" + "Must be a single-domain mesh on all ranks.") ->required() ->check(axom::CLI::ExistingFile); @@ -239,7 +251,9 @@ struct HexMesh }; template -HexMesh loadBlueprintHexMesh(const std::string& mesh_path, bool verboseOutput = false) +HexMesh loadBlueprintHexMesh(const std::string& mesh_path, + MeshLoadPolicy meshLoadPolicy, + bool verboseOutput = false) { AXOM_ANNOTATE_SCOPE("load Blueprint hexahedron mesh"); @@ -258,21 +272,34 @@ HexMesh loadBlueprintHexMesh(const std::string& mesh_path, bool verboseOutput = // Load Blueprint mesh into Conduit node conduit::Node n_load; - // Read mesh as single domain first; if partitioned, then reset and reload with MPI + // Read mesh as a regular Blueprint file first. Meshes that allow + // partitioning may then be reloaded with MPI if they are distributed + // across ranks. conduit::relay::io::blueprint::read_mesh(mesh_path, n_load); - if(conduit::blueprint::mesh::number_of_domains(n_load) > 1) + const auto numDomains = conduit::blueprint::mesh::number_of_domains(n_load); + if(numDomains > 1) { + if(meshLoadPolicy == MeshLoadPolicy::Replicated) + { + SLIC_ERROR(axom::fmt::format( + "Mesh '{}' has {} domains. This mesh must be a single-domain mesh available on all ranks.", + mesh_path, + numDomains)); + } + n_load.reset(); conduit::relay::mpi::io::blueprint::read_mesh(mesh_path, n_load, MPI_COMM_WORLD); - } - // Requirement that each rank has 1 domain - if(conduit::blueprint::mesh::number_of_domains(n_load) != 1) - { - SLIC_ERROR(axom::fmt::format("Rank {} has {} domains. Must have 1 domain per rank!\n", - myRank, - conduit::blueprint::mesh::number_of_domains(n_load))); + if(conduit::blueprint::mesh::number_of_domains(n_load) != 1) + { + SLIC_ERROR( + axom::fmt::format("Rank {} has {} local domains for '{}'. Partitioned meshes must provide " + "exactly one domain per rank.", + myRank, + conduit::blueprint::mesh::number_of_domains(n_load), + mesh_path)); + } } // Check if Blueprint mesh conforms @@ -297,6 +324,14 @@ HexMesh loadBlueprintHexMesh(const std::string& mesh_path, bool verboseOutput = int connectivity_size = (n_load[0]["topologies/topo/elements/connectivity"]).dtype().number_of_elements(); + if(connectivity_size % HEX_OFFSET != 0) + { + SLIC_ERROR( + axom::fmt::format("Hex connectivity array has {} entries; expected a multiple of {}.", + connectivity_size, + HEX_OFFSET)); + } + // extract hexes into an axom::Array auto x_vals_h = axom::ArrayView(n_load[0]["coordsets/coords/values/x"].value(), num_nodes); auto y_vals_h = axom::ArrayView(n_load[0]["coordsets/coords/values/y"].value(), num_nodes); @@ -691,7 +726,6 @@ int main(int argc, char** argv) { axom::utilities::raii::MPIWrapper mpi_raii_wrapper(argc, argv); myRank = mpi_raii_wrapper.my_rank(); - numRanks = mpi_raii_wrapper.num_ranks(); #if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) using omp_exec = axom::OMP_EXEC; @@ -728,10 +762,9 @@ int main(int argc, char** argv) #ifdef AXOM_USE_MPI MPI_Bcast(&retval, 1, MPI_INT, 0, MPI_COMM_WORLD); - MPI_Finalize(); #endif - exit(retval); + return retval; } } @@ -753,21 +786,29 @@ int main(int argc, char** argv) { #ifdef AXOM_RUNTIME_POLICY_USE_OPENMP case RuntimePolicy::omp: - insert_mesh = loadBlueprintHexMesh(params.mesh_file_first, params.isVerbose()); + insert_mesh = loadBlueprintHexMesh(params.mesh_file_first, + MeshLoadPolicy::OneDomainPerRankOrReplicated, + params.isVerbose()); break; #endif #ifdef AXOM_RUNTIME_POLICY_USE_CUDA case RuntimePolicy::cuda: - insert_mesh = loadBlueprintHexMesh(params.mesh_file_first, params.isVerbose()); + insert_mesh = loadBlueprintHexMesh(params.mesh_file_first, + MeshLoadPolicy::OneDomainPerRankOrReplicated, + params.isVerbose()); break; #endif #ifdef AXOM_RUNTIME_POLICY_USE_HIP case RuntimePolicy::hip: - insert_mesh = loadBlueprintHexMesh(params.mesh_file_first, params.isVerbose()); + insert_mesh = loadBlueprintHexMesh(params.mesh_file_first, + MeshLoadPolicy::OneDomainPerRankOrReplicated, + params.isVerbose()); break; #endif default: // RuntimePolicy::seq - insert_mesh = loadBlueprintHexMesh(params.mesh_file_first, params.isVerbose()); + insert_mesh = loadBlueprintHexMesh(params.mesh_file_first, + MeshLoadPolicy::OneDomainPerRankOrReplicated, + params.isVerbose()); break; } @@ -780,21 +821,29 @@ int main(int argc, char** argv) { #ifdef AXOM_RUNTIME_POLICY_USE_OPENMP case RuntimePolicy::omp: - query_mesh = loadBlueprintHexMesh(params.mesh_file_second, params.isVerbose()); + query_mesh = loadBlueprintHexMesh(params.mesh_file_second, + MeshLoadPolicy::Replicated, + params.isVerbose()); break; #endif #ifdef AXOM_RUNTIME_POLICY_USE_CUDA case RuntimePolicy::cuda: - query_mesh = loadBlueprintHexMesh(params.mesh_file_second, params.isVerbose()); + query_mesh = loadBlueprintHexMesh(params.mesh_file_second, + MeshLoadPolicy::Replicated, + params.isVerbose()); break; #endif #ifdef AXOM_RUNTIME_POLICY_USE_HIP case RuntimePolicy::hip: - query_mesh = loadBlueprintHexMesh(params.mesh_file_second, params.isVerbose()); + query_mesh = loadBlueprintHexMesh(params.mesh_file_second, + MeshLoadPolicy::Replicated, + params.isVerbose()); break; #endif default: // RuntimePolicy::seq - query_mesh = loadBlueprintHexMesh(params.mesh_file_second, params.isVerbose()); + query_mesh = loadBlueprintHexMesh(params.mesh_file_second, + MeshLoadPolicy::Replicated, + params.isVerbose()); break; } @@ -856,7 +905,7 @@ int main(int argc, char** argv) } SLIC_INFO(axom::fmt::format(axom::utilities::locale(), - "Mesh had {:L} candidates pairs", + "Mesh had {:L} candidate pairs", candidatePairs.size())); // print first few pairs @@ -867,10 +916,11 @@ int main(int argc, char** argv) constexpr int MAX_PRINT = 20; if(numCandidates > MAX_PRINT) { - candidatePairs.resize(MAX_PRINT); + const std::vector previewPairs(candidatePairs.begin(), + candidatePairs.begin() + MAX_PRINT); SLIC_INFO(axom::fmt::format("First {} candidate pairs: {} ...\n", MAX_PRINT, - axom::fmt::join(candidatePairs, ", "))); + axom::fmt::join(previewPairs, ", "))); } else { @@ -882,10 +932,15 @@ int main(int argc, char** argv) SLIC_INFO(axom::fmt::format("Writing out candidate pairs to {}...", candidates_file_name)); std::ofstream outf(candidates_file_name); + if(!outf) + { + SLIC_ERROR(axom::fmt::format("Failed to open '{}' for writing.", candidates_file_name)); + } + outf << candidatePairs.size() << " candidate pairs:" << std::endl; - for(unsigned long i = 0; i < candidatePairs.size(); ++i) + for(const auto& candidatePair : candidatePairs) { - outf << candidatePairs[i].first << " " << candidatePairs[i].second << std::endl; + outf << candidatePair.first << " " << candidatePair.second << '\n'; } } @@ -895,7 +950,7 @@ int main(int argc, char** argv) if(myRank == 0) { SLIC_INFO(axom::fmt::format(axom::utilities::locale(), - "Mesh had {:L} a total of candidates pairs across all ranks", + "Mesh had {:L} candidate pairs total across all ranks", totalNumCandidates)); } From 0d2e147e098cbcfd7b9b6629f58d027cfc11f152 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Tue, 2 Jun 2026 14:45:57 -0700 Subject: [PATCH 323/986] Apply Conduit to_int32_array() suggestion --- src/axom/quest/examples/quest_candidates_example.cpp | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/axom/quest/examples/quest_candidates_example.cpp b/src/axom/quest/examples/quest_candidates_example.cpp index f9b07a23e8..3d33399a11 100644 --- a/src/axom/quest/examples/quest_candidates_example.cpp +++ b/src/axom/quest/examples/quest_candidates_example.cpp @@ -353,7 +353,7 @@ HexMesh loadBlueprintHexMesh(const std::string& mesh_path, // Move connectivity information onto device int* conn_data = nullptr; - axom::Array temp_conn_data; + conduit::Node temp_conn_data; int conn_id = n_load[0]["topologies/topo/elements/connectivity"].dtype().id(); if(conn_id == conduit::DataType::INT32_ID) { @@ -361,13 +361,8 @@ HexMesh loadBlueprintHexMesh(const std::string& mesh_path, } else if(conn_id == conduit::DataType::INT64_ID) { - temp_conn_data.resize(connectivity_size); - auto conn_int64_data = n_load[0]["topologies/topo/elements/connectivity"].as_int64_ptr(); - for(int i = 0; i < connectivity_size; ++i) - { - temp_conn_data[i] = static_cast(conn_int64_data[i]); - } - conn_data = temp_conn_data.data(); + n_load[0]["topologies/topo/elements/connectivity"].to_int32_array(temp_conn_data); + conn_data = temp_conn_data.as_int32_ptr(); } else { From dea94931bbdeaa1308b91814c663bfbdf1a0414c Mon Sep 17 00:00:00 2001 From: Max Yang Date: Tue, 2 Jun 2026 16:34:36 -0700 Subject: [PATCH 324/986] Add Allocator::getSpace() method --- src/axom/core/memory_management.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/axom/core/memory_management.hpp b/src/axom/core/memory_management.hpp index 155e9c7375..3fb93ef238 100644 --- a/src/axom/core/memory_management.hpp +++ b/src/axom/core/memory_management.hpp @@ -307,6 +307,9 @@ struct Allocator /// \brief Returns the allocator ID. int getID() const { return m_id; } + /// \brief Returns the MemorySpace type for the given allocator. + MemorySpace getSpace() const; + private: int m_id; }; @@ -648,6 +651,8 @@ inline bool isDeviceAllocator(int allocator_id) inline bool isDeviceAllocator(int AXOM_UNUSED_PARAM(allocator_id)) { return false; } #endif +inline MemorySpace Allocator::getSpace() const { return axom::detail::getAllocatorSpace(m_id); } + } // namespace axom #endif /* AXOM_MEMORYMANAGEMENT_HPP_ */ From 556990bc1563f270089e1c76609062a656af5343 Mon Sep 17 00:00:00 2001 From: Max Yang Date: Tue, 2 Jun 2026 16:35:04 -0700 Subject: [PATCH 325/986] FlatMap: fix pinned memory support Pinned memory isn't always coherent with the GPU, so we need to construct a temporary map in device-only memory and copy it back. --- src/axom/core/FlatMap.hpp | 1 + src/axom/core/FlatMapUtil.hpp | 42 ++++++++++++++++---- src/axom/core/tests/core_flatmap_for_all.hpp | 4 +- 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/src/axom/core/FlatMap.hpp b/src/axom/core/FlatMap.hpp index c47a608108..9e6d8d5e98 100644 --- a/src/axom/core/FlatMap.hpp +++ b/src/axom/core/FlatMap.hpp @@ -233,6 +233,7 @@ class FlatMap : detail::flat_map::SequentialLookupPolicy::insert(InputIt kv_begin, InputIt kv_end) // Assume that all elements will be inserted into an empty slot. this->reserve(this->size() + num_elems); + FlatMap temp; + bool allocate_temp_map = false; +#if defined(AXOM_USE_CUDA) && defined(AXOM_USE_UMPIRE) + if(this->m_allocator.getSpace() == MemorySpace::Pinned) + { + // Pinned memory is allocated on the CPU, and is not always coherent with respect to the GPU. + // Instead of using system-scope atomics, we just construct a temporary map in device memory + // and copy it back to the pinned space. + axom::Allocator device_allocator {axom::detail::getAllocatorID()}; + temp = FlatMap(*this, device_allocator); + allocate_temp_map = true; + } +#endif + FlatMap& map = allocate_temp_map ? temp : *this; + // Grab some needed internal fields from the flat map. // We're going to be constructing metadata and the K-V pairs directly // in-place. - const int ngroups_pow_2 = this->m_numGroups2; - const auto meta_group = this->m_metadata.view(); - const auto buckets = this->m_buckets.view(); + const int ngroups_pow_2 = map.m_numGroups2; + const auto meta_group = map.m_metadata.view(); + const auto buckets = map.m_buckets.view(); // Construct an array of locks per-group. This guards metadata updates for // each insertion. const IndexType num_groups = 1 << ngroups_pow_2; - Array lock_vec(num_groups, num_groups, this->m_allocator.getID()); + Array lock_vec(num_groups, num_groups, map.m_allocator.getID()); const auto group_locks = lock_vec.view(); // Map bucket slots to k-v pair indices. This is used to deduplicate pairs // with the same key value. - Array key_index_dedup_vec(0, 0, this->m_allocator.getID()); + Array key_index_dedup_vec(0, 0, map.m_allocator.getID()); key_index_dedup_vec.resize(num_groups * GroupBucket::Size, -1); const auto key_index_dedup = key_index_dedup_vec.view(); // Map k-v pair indices to bucket slots. This is essentially the inverse of // the above mapping. - Array key_index_to_bucket_vec(num_elems, num_elems, this->m_allocator.getID()); + Array key_index_to_bucket_vec(num_elems, num_elems, map.m_allocator.getID()); const auto key_index_to_bucket = key_index_to_bucket_vec.view(); axom::ReduceSum total_overwrites(0); @@ -459,8 +474,19 @@ void FlatMap::insert(InputIt kv_begin, InputIt kv_end) } }); - this->m_size += total_inserts.get() - total_overwrites.get(); - this->m_loadCount += total_inserts.get() - total_overwrites.get(); + map.m_size += total_inserts.get() - total_overwrites.get(); + map.m_loadCount += total_inserts.get() - total_overwrites.get(); + +#if defined(AXOM_USE_CUDA) && defined(AXOM_USE_UMPIRE) + if(allocate_temp_map) + { + // Original pinned map is in temp. + axom::Allocator pinned_allocator = temp.getAllocator(); + + // Move new FlatMap to pinned memory. + *this = FlatMap(map, pinned_allocator); + } +#endif } } // namespace axom diff --git a/src/axom/core/tests/core_flatmap_for_all.hpp b/src/axom/core/tests/core_flatmap_for_all.hpp index 1366e43b72..7215996467 100644 --- a/src/axom/core/tests/core_flatmap_for_all.hpp +++ b/src/axom/core/tests/core_flatmap_for_all.hpp @@ -85,10 +85,10 @@ using ViewTypes = ::testing::Types< #if defined(AXOM_USE_RAJA) && defined(AXOM_USE_CUDA) && defined(AXOM_USE_UMPIRE) FlatMapTestParams, axom::CUDA_EXEC<256>, axom::MemorySpace::Device>, FlatMapTestParams, axom::CUDA_EXEC<256>, axom::MemorySpace::Unified>, - // FlatMapTestParams, axom::CUDA_EXEC<256>, axom::MemorySpace::Pinned>, + FlatMapTestParams, axom::CUDA_EXEC<256>, axom::MemorySpace::Pinned>, FlatMapTestParams>, axom::CUDA_EXEC<256>, axom::MemorySpace::Device>, FlatMapTestParams>, axom::CUDA_EXEC<256>, axom::MemorySpace::Unified>, -// FlatMapTestParams>, axom::CUDA_EXEC<256>, axom::MemorySpace::Pinned>, + FlatMapTestParams>, axom::CUDA_EXEC<256>, axom::MemorySpace::Pinned>, #endif #if defined(AXOM_USE_RAJA) && defined(AXOM_USE_HIP) && defined(AXOM_USE_UMPIRE) FlatMapTestParams, axom::HIP_EXEC<256>, axom::MemorySpace::Device>, From 5360825c784e969d44757add30fee19a54416a3d Mon Sep 17 00:00:00 2001 From: Max Yang Date: Tue, 2 Jun 2026 17:08:07 -0700 Subject: [PATCH 326/986] Add more detailed skip message + documentation for CUDA std::string case --- src/axom/core/tests/core_flatmap.hpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/axom/core/tests/core_flatmap.hpp b/src/axom/core/tests/core_flatmap.hpp index 61d2471665..705b2eb073 100644 --- a/src/axom/core/tests/core_flatmap.hpp +++ b/src/axom/core/tests/core_flatmap.hpp @@ -771,11 +771,16 @@ AXOM_TYPED_TEST(core_flatmap, copy_host_device) using DeviceExec = typename TestFixture::DeviceExec; // CUDA failure - Skip tests if key or value is of type std::string - #if defined(AXOM_USE_CUDA) + #if defined(AXOM_USE_CUDA) && defined(__GLIBCXX__) if constexpr(std::is_same::value || std::is_same::value) { - return; + // Copies of non-trivial types to or from device-only memory on CUDA rely + // on the types being "trivially relocatable," just as in axom::Array. + // + // For libstdc++, std::string is not trivially-relocatable, as it keeps a + // pointer to itself in its implementation of small-string optimization. + GTEST_SKIP() << "std::string is not supported in device-only memory on GCC's libstdc++"; } #endif From 5a82a9f12b5daeb428208390baf0bdd98f695aee Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Tue, 19 May 2026 08:16:39 -0700 Subject: [PATCH 327/986] Add triangle methods --- src/axom/primal/CMakeLists.txt | 1 + src/axom/primal/geometry/BezierPatch.hpp | 119 +-- src/axom/primal/geometry/BezierTriangle.hpp | 935 ++++++++++++++++++ src/axom/primal/tests/CMakeLists.txt | 1 + src/axom/primal/tests/primal_bezier_patch.cpp | 4 +- .../primal/tests/primal_bezier_triangle.cpp | 353 +++++++ 6 files changed, 1325 insertions(+), 88 deletions(-) create mode 100644 src/axom/primal/geometry/BezierTriangle.hpp create mode 100644 src/axom/primal/tests/primal_bezier_triangle.cpp diff --git a/src/axom/primal/CMakeLists.txt b/src/axom/primal/CMakeLists.txt index 5172c7fa2b..d66a6443ae 100644 --- a/src/axom/primal/CMakeLists.txt +++ b/src/axom/primal/CMakeLists.txt @@ -23,6 +23,7 @@ set( primal_headers ## geometry geometry/BezierCurve.hpp geometry/BezierPatch.hpp + geometry/BezierTriangle.hpp geometry/BoundingBox.hpp geometry/CoordinateTransformer.hpp geometry/Cone.hpp diff --git a/src/axom/primal/geometry/BezierPatch.hpp b/src/axom/primal/geometry/BezierPatch.hpp index f63694a540..5fe088e40d 100644 --- a/src/axom/primal/geometry/BezierPatch.hpp +++ b/src/axom/primal/geometry/BezierPatch.hpp @@ -821,19 +821,7 @@ class BezierPatch // and BezierPatch of weights (w) BezierPatch projective(ord_u, ord_v); BezierPatch weights(ord_u, ord_v); - - for(int p = 0; p <= ord_u; ++p) - { - for(int q = 0; q <= ord_v; ++q) - { - weights(p, q)[0] = m_weights(p, q); - - for(int i = 0; i < NDIMS; ++i) - { - projective(p, q)[i] = m_controlPoints(p, q)[i] * m_weights(p, q); - } - } - } + fill_projective_patches(projective, weights); BezierCurve P = projective.isocurve_u(u); BezierCurve W = weights.isocurve_u(u); @@ -942,19 +930,7 @@ class BezierPatch // and BezierPatch of weights (w) BezierPatch projective(ord_u, ord_v); BezierPatch weights(ord_u, ord_v); - - for(int p = 0; p <= ord_u; ++p) - { - for(int q = 0; q <= ord_v; ++q) - { - weights(p, q)[0] = m_weights(p, q); - - for(int i = 0; i < NDIMS; ++i) - { - projective(p, q)[i] = m_controlPoints(p, q)[i] * m_weights(p, q); - } - } - } + fill_projective_patches(projective, weights); BezierCurve P = projective.isocurve_v(v); BezierCurve W = weights.isocurve_v(v); @@ -1131,19 +1107,7 @@ class BezierPatch // and BezierPatch of weights (w) BezierPatch projective(ord_u, ord_v); BezierPatch weights(ord_u, ord_v); - - for(int p = 0; p <= ord_u; ++p) - { - for(int q = 0; q <= ord_v; ++q) - { - weights(p, q)[0] = m_weights(p, q); - - for(int i = 0; i < NDIMS; ++i) - { - projective(p, q)[i] = m_controlPoints(p, q)[i] * m_weights(p, q); - } - } - } + fill_projective_patches(projective, weights); Point P; Vector P_u, P_v, P_uu, P_vv, P_uv; @@ -1175,12 +1139,12 @@ class BezierPatch * * \note We typically evaluate the patch at \a u and \a v between 0 and 1 */ - void evaluate_linear_derivatives(T u, - T v, - Point& eval, - Vector& Du, - Vector& Dv, - Vector& DuDv) const + void evaluateLinearDerivatives(T u, + T v, + Point& eval, + Vector& Du, + Vector& Dv, + Vector& DuDv) const { using axom::utilities::lerp; const int ord_u = getOrder_u(); @@ -1307,19 +1271,7 @@ class BezierPatch // and BezierPatch of weights (w) BezierPatch projective(ord_u, ord_v); BezierPatch weights(ord_u, ord_v); - - for(int p = 0; p <= ord_u; ++p) - { - for(int q = 0; q <= ord_v; ++q) - { - weights(p, q)[0] = m_weights(p, q); - - for(int i = 0; i < NDIMS; ++i) - { - projective(p, q)[i] = m_controlPoints(p, q)[i] * m_weights(p, q); - } - } - } + fill_projective_patches(projective, weights); Point P; Vector P_u, P_v, P_uu, P_vv, P_uv; @@ -1568,19 +1520,7 @@ class BezierPatch // and BezierPatch of weights (w) BezierPatch projective(ord_u, ord_v); BezierPatch weights(ord_u, ord_v); - - for(int p = 0; p <= ord_u; ++p) - { - for(int q = 0; q <= ord_v; ++q) - { - weights(p, q)[0] = m_weights(p, q); - - for(int i = 0; i < NDIMS; ++i) - { - projective(p, q)[i] = m_controlPoints(p, q)[i] * m_weights(p, q); - } - } - } + fill_projective_patches(projective, weights); Point P; Vector P_u, P_v, P_uu, P_vv, P_uv; @@ -1770,19 +1710,7 @@ class BezierPatch // and BezierPatch of weights (w) BezierPatch projective(ord_u, ord_v); BezierPatch weights(ord_u, ord_v); - - for(int p = 0; p <= ord_u; ++p) - { - for(int q = 0; q <= ord_v; ++q) - { - weights(p, q)[0] = m_weights(p, q); - - for(int i = 0; i < NDIMS; ++i) - { - projective(p, q)[i] = m_controlPoints(p, q)[i] * m_weights(p, q); - } - } - } + fill_projective_patches(projective, weights); Point P; Vector P_u, P_v, P_uv; @@ -1790,8 +1718,8 @@ class BezierPatch Point W; Vector W_u, W_v, W_uv; - projective.evaluate_linear_derivatives(u, v, P, P_u, P_v, P_uv); - weights.evaluate_linear_derivatives(u, v, W, W_u, W_v, W_uv); + projective.evaluateLinearDerivatives(u, v, P, P_u, P_v, P_uv); + weights.evaluateLinearDerivatives(u, v, W, W_u, W_v, W_uv); // Store values used in each coordinate computation double weight_prod = 2 * W_u[0] * W_v[0] - W[0] * W_uv[0]; @@ -2121,6 +2049,25 @@ class BezierPatch return true; } + void fill_projective_patches(BezierPatches& projective, BezierPatches& weights) const + { + SLIC_ASSERT(isRational()); + SLIC_ASSERT(is_valid_rational()); + + for(int p = 0; p <= ord_u; ++p) + { + for(int q = 0; q <= ord_v; ++q) + { + weights(p, q)[0] = m_weights(p, q); + + for(int i = 0; i < NDIMS; ++i) + { + projective(p, q)[i] = m_controlPoints(p, q)[i] * m_weights(p, q); + } + } + } + } + private: CoordsMat m_controlPoints; WeightsMat m_weights; diff --git a/src/axom/primal/geometry/BezierTriangle.hpp b/src/axom/primal/geometry/BezierTriangle.hpp new file mode 100644 index 0000000000..6b05f928ee --- /dev/null +++ b/src/axom/primal/geometry/BezierTriangle.hpp @@ -0,0 +1,935 @@ +// 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) + +/*! + * \file BezierTriangle.hpp + * + * \brief A BezierTriangle primitive + */ + +#ifndef AXOM_PRIMAL_BEZIERTRIANGLE_HPP_ +#define AXOM_PRIMAL_BEZIERTRIANGLE_HPP_ + +#include "axom/core.hpp" +#include "axom/slic.hpp" + +#include "axom/primal/geometry/Point.hpp" +#include "axom/primal/geometry/Vector.hpp" +#include "axom/primal/geometry/Segment.hpp" +#include "axom/primal/geometry/BezierCurve.hpp" +#include "axom/primal/geometry/BoundingBox.hpp" +#include "axom/primal/geometry/OrientedBoundingBox.hpp" + +#include "axom/primal/operators/squared_distance.hpp" + +#include + +namespace axom +{ +namespace primal +{ +// Forward declare the templated classes and operator functions +template +class BezierTriangle; + +/*! \brief Overloaded output operator for Bezier Triangles*/ +template +std::ostream& operator<<(std::ostream& os, const BezierTriangle& bTri); + +/*! + * \class BezierTriangle + * + * \brief Represents a Bezier triangle defined by a triangular array of control points + * \tparam T the coordinate type, e.g., double, float, etc. + * + * A Bezier triangle of order \a N has \f$ (N+1)(N+2)/2 \f$ control points. + * It is parametrized over the domain \f$ u \ge 0, v \ge 0, u+v \le 1 \f$. + * + * Control points are indexed using integer coordinates \f$ (i,j) \f$ with + * \f$ 0 \le i \le N \f$ and \f$ 0 \le j \le N-i \f$ and accessed via `operator()(i,j)`. + * Internally, the triangular control net is stored in a 1D array and `triIndex(N,i,j)` + * maps \f$ (i,j) \f$ to that linear storage index. + * + * Rational triangles are represented by an additional set of positive weights. + * Polynomial (nonrational) Bezier triangles are identified by an empty weights array. + * + * \note This triangle uses permuted barycentric coordinates for evaluation such that, when + * `getOrder()==1`, the parameter values correspond to the triangle vertices: + * - `evaluate(0,0) == (*this)(0,0)` + * - `evaluate(0,1) == (*this)(0,1)` + * - `evaluate(1,0) == (*this)(1,0)` + */ +template +class BezierTriangle +{ +public: + using PointType = Point; + using VectorType = Vector; + + using CoordsVec = axom::Array; + using WeightsVec = axom::Array; + + using BoundingBoxType = BoundingBox; + using OrientedBoundingBoxType = OrientedBoundingBox; + using BezierCurveType = primal::BezierCurve; + + AXOM_STATIC_ASSERT_MSG((NDIMS == 1) || (NDIMS == 2) || (NDIMS == 3), + "A Bezier Triangle object may be defined in 1-, 2-, or 3-D"); + + AXOM_STATIC_ASSERT_MSG(std::is_arithmetic::value, + "A Bezier Triangle must be defined using an arithmetic type"); + +public: + ///@{ + /** + * @name Constructors for BezierTriangle + * + * The constructors allow for flexible initialization of BezierTriangle objects from: + * - 1D Axom arrays/views of control points and weights, + * - C-style arrays of control points and weights, + * - a specified polynomial order, + * - rational or polynomial (nonrational) triangles, depending on the presence of weights. + * + * The triangle is parametrized over the domain \f$ u \ge 0, v \ge 0, u+v \le 1 \f$. + * + * Rational triangles are identified by a non-empty weights array, + * and nonrational triangles by an empty weights array. + * All weights must be greater than 0 in a rational triangle. + * + * For 1D control point/weight arrays, the expected layout corresponds to the indexing + * used by `operator()(i,j)`: + \verbatim + pts[ triIndex(N,i,j) ] <-> (*this)(i,j) for 0<=i<=N and 0<=j<=N-i + \endverbatim + */ + + /** + * \brief Constructor from ArrayViews of control points and weights + * + * \param [in] controlPoints ArrayView of control points (size: (ord+1)(ord+2)/2 or 0) + * \param [in] weights ArrayView of weights (size: (ord+1)(ord+2)/2 or 0) + * \param [in] ord The triangle's polynomial order + * + * If \a controlPoints is empty, we still allocate space for the control points. + * \pre ord must be greater than or equal to -1 + */ + BezierTriangle(axom::ArrayView controlPoints, + axom::ArrayView weights, + int ord) + : m_ord(ord) + { + SLIC_ASSERT(ord >= -1); + + const int SZ = (m_ord >= 0) ? triSize(m_ord) : 0; + SLIC_ASSERT(controlPoints.size() >= weights.size()); + + // note: always allocate space for control points + if(controlPoints.empty()) + { + m_controlPoints.resize(SZ); + } + else + { + SLIC_ASSERT(controlPoints.data() != nullptr); + SLIC_ASSERT(controlPoints.size() == SZ); + m_controlPoints = controlPoints; + } + + // note: only allocate space for weights when they are supplied + if(!weights.empty()) + { + SLIC_ASSERT(weights.data() != nullptr); + SLIC_ASSERT(weights.size() == SZ); + m_weights = weights; + SLIC_ASSERT(is_valid_rational()); + } + } + + /// Constructor from ArrayViews of (non-const) control points and weights + BezierTriangle(axom::ArrayView controlPoints, axom::ArrayView weights, int ord) + : BezierTriangle(axom::ArrayView(controlPoints.data(), controlPoints.size()), + axom::ArrayView(weights.data(), weights.size()), + ord) + { } + + /*! + * \brief Constructor for a polynomial (nonrational) Bezier Triangle that reserves space + * + * \param [in] ord The triangle's polynomial order + * \pre ord must be greater than or equal to -1 + */ + explicit BezierTriangle(int ord = -1) + : BezierTriangle(axom::ArrayView(nullptr, 0), + axom::ArrayView(nullptr, 0), + ord) + { } + + /*! + * \brief Constructor for a polynomial Bezier Triangle from an array of coordinates + * + * \param [in] pts A 1D C-style array of (ord+1)(ord+2)/2 control points + * \param [in] ord The triangle's polynomial order + * \pre ord is greater than or equal to zero + */ + BezierTriangle(const PointType* pts, int ord) + : BezierTriangle(axom::ArrayView(pts, triSize(ord)), + axom::ArrayView(nullptr, 0), + ord) + { } + + /*! + * \brief Constructor for a rational Bezier Triangle from arrays of coordinates and weights + * + * \param [in] pts A 1D C-style array of (ord+1)(ord+2)/2 control points + * \param [in] weights A 1D C-style array of (ord+1)(ord+2)/2 positive weights + * \param [in] ord The triangle's polynomial order + * \pre ord is greater than or equal to zero + * + * If \a weights is the null pointer, creates a nonrational triangle. + */ + BezierTriangle(const PointType* pts, const T* weights, int ord) + : BezierTriangle(axom::ArrayView(pts, triSize(ord)), + axom::ArrayView(weights, weights ? triSize(ord) : 0), + ord) + { } + + /*! + * \brief Constructor from an Axom array of control points + * + * \param [in] pts A 1D Axom array of (ord+1)(ord+2)/2 control points + * \param [in] ord The triangle's polynomial order (>= 0) + */ + BezierTriangle(const CoordsVec& pts, int ord) + : BezierTriangle(pts.view(), axom::ArrayView(nullptr, 0), ord) + { } + + /*! + * \brief Constructor from Axom arrays of control points and weights + * + * \param [in] pts A 1D Axom array of (ord+1)(ord+2)/2 control points + * \param [in] weights A 1D Axom array of (ord+1)(ord+2)/2 positive weights + * \param [in] ord The triangle's polynomial order (>= 0) + */ + BezierTriangle(const CoordsVec& pts, const WeightsVec& weights, int ord) + : BezierTriangle(pts.view(), weights.view(), ord) + { } + + ///@} + + /*! + * \brief Returns true when this triangle is rational + * + * A rational triangle has a weight per control point; polynomial (nonrational) triangles + * are identified by an empty weight array. + */ + bool isRational() const { return !m_weights.empty(); } + + /*! + * \brief Returns a reference to the triangle's control points + * + * The control net contains `triSize(getOrder())` points (or 0 when `getOrder()<0`). + */ + CoordsVec& getControlPoints() { return m_controlPoints; } + + /// \overload + const CoordsVec& getControlPoints() const { return m_controlPoints; } + + /*! + * \brief Returns a reference to the triangle's weights + * + * The weight array is empty for polynomial triangles. For rational triangles it contains + * `triSize(getOrder())` positive weights. + */ + WeightsVec& getWeights() { return m_weights; } + + /// \overload + const WeightsVec& getWeights() const { return m_weights; } + + /*! + * \brief Returns an axis-aligned bounding box containing the Bezier triangle + * + * \note The returned box is computed from the control points. + */ + BoundingBoxType boundingBox() const + { + return BoundingBoxType(m_controlPoints.data(), static_cast(m_controlPoints.size())); + } + + /*! + * \brief Returns an oriented bounding box containing the Bezier triangle + * + * \note The returned box is computed from the control points. + */ + OrientedBoundingBoxType orientedBoundingBox() const + { + return OrientedBoundingBoxType(m_controlPoints.data(), static_cast(m_controlPoints.size())); + } + + /*! + * \brief Sets the order of the Bezier triangle and resizes internal storage + * + * \param [in] ord The polynomial order + * + * \pre ord must be greater than or equal to -1 + * + * \note This function only resizes the control point and weight arrays and does not + * initialize their values. If the triangle is rational (`isRational()==true`), the + * weight array is also resized to match the number of control points. + */ + void setOrder(int ord) + { + SLIC_ASSERT(ord >= -1); + + m_ord = ord; + + const int SZ = (m_ord >= 0) ? triSize(m_ord) : 0; + m_controlPoints.resize(SZ); + if(isRational()) + { + m_weights.resize(SZ); + } + } + + /*! + * \brief Returns the polynomial order of the triangle + * + * \note A default constructed triangle has order -1 and no control points. + */ + int getOrder() const { return m_ord; } + + /*! + * \brief Access a control point in the triangular control net + * + * \param [in] i The first index (0 <= i <= getOrder()) + * \param [in] j The second index (0 <= j <= getOrder()-i) + * + * \pre \a i and \a j are in range and \a i+\a j <= getOrder() + */ + PointType& operator()(int i, int j) + { + SLIC_ASSERT(i >= 0); + SLIC_ASSERT(j >= 0); + SLIC_ASSERT(i + j <= m_ord); + return m_controlPoints[triIndex(m_ord, i, j)]; + } + + const PointType& operator()(int i, int j) const + { + SLIC_ASSERT(i >= 0); + SLIC_ASSERT(j >= 0); + SLIC_ASSERT(i + j <= m_ord); + return m_controlPoints[triIndex(m_ord, i, j)]; + } + + /*! + * \brief Evaluates the Bezier triangle at \a (u,v) + * + * \param [in] u Parameter value along the \a u axis + * \param [in] v Parameter value along the \a v axis + * + * \pre getOrder() >= 0 + * \pre u >= 0, v >= 0, and u+v <= 1 + * + * \return Point value S(u,v) + * + * \note In the rational case, evaluation is performed in projective space and divided + * by the evaluated weight. + */ + PointType evaluate(T u, T v) const + { + SLIC_ASSERT(m_ord >= 0); + SLIC_ASSERT(u >= T(0)); + SLIC_ASSERT(v >= T(0)); + SLIC_ASSERT(u + v <= T(1)); + + if(!isRational()) + { + PointType ptval; + + const int npts = m_controlPoints.size(); + axom::Array dCarray(npts); + + // Run de Casteljau algorithm on each dimension + for(int N = 0; N < NDIMS; ++N) + { + for(int n = 0; n < npts; ++n) + { + dCarray[n] = m_controlPoints[n][N]; + } + + for(int p = 1; p <= m_ord; ++p) + { + const int end = m_ord - p + 1; + for(int i = 0; i < end; ++i) + { + for(int j = 0; j < end - i; ++j) + { + const auto& A = dCarray[triIndex(end, i, j)]; + const auto& B = dCarray[triIndex(end, i, j + 1)]; + const auto& C = dCarray[triIndex(end, i + 1, j)]; + dCarray[triIndex(end - 1, i, j)] = A + u * (C - A) + v * (B - A); + } + } + } + + ptval[N] = dCarray[0]; + } + + return ptval; + } + else + { + // Rational case: evaluate in projective space, then divide by weight + BezierTriangle projective(m_ord); + BezierTriangle weights(m_ord); + fill_projective_triangles(projective, weights); + + const Point P = projective.evaluate(u, v); + const Point W = weights.evaluate(u, v); + + PointType eval; + for(int N = 0; N < NDIMS; ++N) + { + eval[N] = P[N] / W[0]; + } + return eval; + } + } + + /*! + * \brief Evaluates first derivatives of the Bezier triangle at \a (u,v) + * + * \param [in] u Parameter value along the \a u axis + * \param [in] v Parameter value along the \a v axis + * \param [out] eval Point value S(u,v) + * \param [out] Du First derivative S_u(u,v) + * \param [out] Dv First derivative S_v(u,v) + * + * \pre getOrder() >= 0 + * \pre u >= 0, v >= 0, and u+v <= 1 + */ + void evaluateFirstDerivatives(T u, + T v, + Point& eval, + Vector& Du, + Vector& Dv) const + { + SLIC_ASSERT(m_ord >= 0); + SLIC_ASSERT(u >= T(0)); + SLIC_ASSERT(v >= T(0)); + SLIC_ASSERT(u + v <= T(1)); + + if(m_ord == 0) + { + eval = m_controlPoints[0]; + for(int N = 0; N < NDIMS; ++N) + { + Du[N] = T(0); + Dv[N] = T(0); + } + return; + } + + if(!isRational()) + { + const int npts = m_controlPoints.size(); + axom::Array dCarray(npts); + + // Run de Casteljau algorithm on each dimension + for(int N = 0; N < NDIMS; ++N) + { + for(int n = 0; n < npts; ++n) + { + dCarray[n] = m_controlPoints[n][N]; + } + + for(int p = 1; p <= m_ord - 1; ++p) + { + const int end = m_ord - p + 1; + for(int i = 0; i < end; ++i) + { + for(int j = 0; j < end - i; ++j) + { + const auto& A = dCarray[triIndex(end, i, j)]; + const auto& B = dCarray[triIndex(end, i, j + 1)]; + const auto& C = dCarray[triIndex(end, i + 1, j)]; + dCarray[triIndex(end - 1, i, j)] = A + u * (C - A) + v * (B - A); + } + } + } + + // The last reduction yields a linear triangle: + // S(u,v) = A + u(C-A) + v(B-A) + Du[N] = (dCarray[2] - dCarray[0]); + Dv[N] = (dCarray[1] - dCarray[0]); + eval[N] = dCarray[0] + u * Du[N] + v * Dv[N]; + + Du[N] *= m_ord; + Dv[N] *= m_ord; + } + } + else + { + BezierTriangle projective(m_ord); + BezierTriangle weights(m_ord); + fill_projective_triangles(projective, weights); + + Point P; + Vector P_u, P_v; + + Point W; + Vector W_u, W_v; + + projective.evaluateFirstDerivatives(u, v, P, P_u, P_v); + weights.evaluateFirstDerivatives(u, v, W, W_u, W_v); + + for(int N = 0; N < NDIMS; ++N) + { + eval[N] = P[N] / W[0]; + Du[N] = (P_u[N] - eval[N] * W_u[0]) / W[0]; + Dv[N] = (P_v[N] - eval[N] * W_v[0]) / W[0]; + } + } + } + + /*! + * \brief Evaluates all linear derivatives of a Bezier triangle at (\a u, \a v) + * + * \param [in] u Parameter value at which to evaluate along the u axis + * \param [in] v Parameter value at which to evaluate along the v axis + * \param [out] eval The point value of the Bezier triangle at (u, v) + * \param [out] Du The vector value of S_u(u, v) + * \param [out] Dv The vector value of S_v(u, v) + * \param [out] DuDv The vector value of S_uv(u, v) == S_vu(u, v) + */ + void evaluateLinearDerivatives(T u, + T v, + Point& eval, + Vector& Du, + Vector& Dv, + Vector& DuDv) const + { + SLIC_ASSERT(m_ord >= 0); + SLIC_ASSERT(u >= T(0)); + SLIC_ASSERT(v >= T(0)); + SLIC_ASSERT(u + v <= T(1)); + + if(!isRational()) + { + if(m_ord < 2) + { + evaluateFirstDerivatives(u, v, eval, Du, Dv); + for(int N = 0; N < NDIMS; ++N) + { + DuDv[N] = T(0); + } + return; + } + + const int npts = m_controlPoints.size(); + axom::Array dCarray(npts); + + const T n_ord = static_cast(m_ord); + const T n_ord_nm1 = static_cast(m_ord) * static_cast(m_ord - 1); + + for(int N = 0; N < NDIMS; ++N) + { + for(int n = 0; n < npts; ++n) + { + dCarray[n] = m_controlPoints[n][N]; + } + + // Reduce to a quadratic triangle (order 2) + for(int p = 1; p <= m_ord - 2; ++p) + { + const int end = m_ord - p + 1; + for(int i = 0; i < end; ++i) + { + for(int j = 0; j < end - i; ++j) + { + const auto& A = dCarray[triIndex(end, i, j)]; + const auto& B = dCarray[triIndex(end, i, j + 1)]; + const auto& C = dCarray[triIndex(end, i + 1, j)]; + dCarray[triIndex(end - 1, i, j)] = A + u * (C - A) + v * (B - A); + } + } + } + + // Extract the reduced quadratic triangle values + const T Q00 = dCarray[triIndex(2, 0, 0)]; + const T Q01 = dCarray[triIndex(2, 0, 1)]; + const T Q02 = dCarray[triIndex(2, 0, 2)]; + const T Q10 = dCarray[triIndex(2, 1, 0)]; + const T Q11 = dCarray[triIndex(2, 1, 1)]; + const T Q20 = dCarray[triIndex(2, 2, 0)]; + + // One reduction yields a linear triangle (order 1) + const T L00 = Q00 + u * (Q10 - Q00) + v * (Q01 - Q00); + const T L01 = Q01 + u * (Q11 - Q01) + v * (Q02 - Q01); + const T L10 = Q10 + u * (Q20 - Q10) + v * (Q11 - Q10); + + eval[N] = L00 + u * (L10 - L00) + v * (L01 - L00); + Du[N] = n_ord * (L10 - L00); + Dv[N] = n_ord * (L01 - L00); + DuDv[N] = n_ord_nm1 * (Q11 - Q10 - Q01 + Q00); + } + } + else + { + BezierTriangle projective(m_ord); + BezierTriangle weights(m_ord); + fill_projective_triangles(projective, weights); + + Point P; + Vector P_u, P_v, P_uv; + + Point W; + Vector W_u, W_v, W_uv; + + projective.evaluateLinearDerivatives(u, v, P, P_u, P_v, P_uv); + weights.evaluateLinearDerivatives(u, v, W, W_u, W_v, W_uv); + + for(int N = 0; N < NDIMS; ++N) + { + eval[N] = P[N] / W[0]; + Du[N] = (P_u[N] - eval[N] * W_u[0]) / W[0]; + Dv[N] = (P_v[N] - eval[N] * W_v[0]) / W[0]; + DuDv[N] = (P_uv[N] - Du[N] * W_v[0] - Dv[N] * W_u[0] - eval[N] * W_uv[0]) / W[0]; + } + } + } + + /*! + * \brief Evaluates all second derivatives of a Bezier triangle at (\a u, \a v) + * + * \param [in] u Parameter value at which to evaluate along the u axis + * \param [in] v Parameter value at which to evaluate along the v axis + * \param [out] eval The point value of the Bezier triangle at (u, v) + * \param [out] Du The vector value of S_u(u, v) + * \param [out] Dv The vector value of S_v(u, v) + * \param [out] DuDu The vector value of S_uu(u, v) + * \param [out] DvDv The vector value of S_vv(u, v) + * \param [out] DuDv The vector value of S_uv(u, v) == S_vu(u, v) + */ + void evaluateSecondDerivatives(T u, + T v, + Point& eval, + Vector& Du, + Vector& Dv, + Vector& DuDu, + Vector& DvDv, + Vector& DuDv) const + { + SLIC_ASSERT(m_ord >= 0); + SLIC_ASSERT(u >= T(0)); + SLIC_ASSERT(v >= T(0)); + SLIC_ASSERT(u + v <= T(1)); + + if(m_ord == 0) + { + eval = m_controlPoints[0]; + for(int N = 0; N < NDIMS; ++N) + { + Du[N] = T(0); + Dv[N] = T(0); + DuDu[N] = T(0); + DvDv[N] = T(0); + DuDv[N] = T(0); + } + return; + } + + if(m_ord == 1) + { + evaluateFirstDerivatives(u, v, eval, Du, Dv); + for(int N = 0; N < NDIMS; ++N) + { + DuDu[N] = T(0); + DvDv[N] = T(0); + DuDv[N] = T(0); + } + return; + } + + if(!isRational()) + { + const int npts = m_controlPoints.size(); + axom::Array dCarray(npts); + + const T n_ord = static_cast(m_ord); + const T n_ord_nm1 = static_cast(m_ord) * static_cast(m_ord - 1); + + for(int N = 0; N < NDIMS; ++N) + { + for(int n = 0; n < npts; ++n) + { + dCarray[n] = m_controlPoints[n][N]; + } + + // Reduce to a quadratic triangle (order 2) + for(int p = 1; p <= m_ord - 2; ++p) + { + const int end = m_ord - p + 1; + for(int i = 0; i < end; ++i) + { + for(int j = 0; j < end - i; ++j) + { + const auto& A = dCarray[triIndex(end, i, j)]; + const auto& B = dCarray[triIndex(end, i, j + 1)]; + const auto& C = dCarray[triIndex(end, i + 1, j)]; + dCarray[triIndex(end - 1, i, j)] = A + u * (C - A) + v * (B - A); + } + } + } + + // Extract the reduced quadratic triangle values + const T Q00 = dCarray[triIndex(2, 0, 0)]; + const T Q01 = dCarray[triIndex(2, 0, 1)]; + const T Q02 = dCarray[triIndex(2, 0, 2)]; + const T Q10 = dCarray[triIndex(2, 1, 0)]; + const T Q11 = dCarray[triIndex(2, 1, 1)]; + const T Q20 = dCarray[triIndex(2, 2, 0)]; + + // One reduction yields a linear triangle (order 1) + const T L00 = Q00 + u * (Q10 - Q00) + v * (Q01 - Q00); + const T L01 = Q01 + u * (Q11 - Q01) + v * (Q02 - Q01); + const T L10 = Q10 + u * (Q20 - Q10) + v * (Q11 - Q10); + + eval[N] = L00 + u * (L10 - L00) + v * (L01 - L00); + Du[N] = n_ord * (L10 - L00); + Dv[N] = n_ord * (L01 - L00); + + // Second derivatives from second differences of the reduced quadratic triangle + DuDu[N] = n_ord_nm1 * (Q20 - T(2) * Q10 + Q00); + DvDv[N] = n_ord_nm1 * (Q02 - T(2) * Q01 + Q00); + DuDv[N] = n_ord_nm1 * (Q11 - Q10 - Q01 + Q00); + } + } + else + { + BezierTriangle projective(m_ord); + BezierTriangle weights(m_ord); + fill_projective_triangles(projective, weights); + + Point P; + Vector P_u, P_v, P_uu, P_vv, P_uv; + + Point W; + Vector W_u, W_v, W_uu, W_vv, W_uv; + + projective.evaluateSecondDerivatives(u, v, P, P_u, P_v, P_uu, P_vv, P_uv); + weights.evaluateSecondDerivatives(u, v, W, W_u, W_v, W_uu, W_vv, W_uv); + + for(int N = 0; N < NDIMS; ++N) + { + eval[N] = P[N] / W[0]; + Du[N] = (P_u[N] - eval[N] * W_u[0]) / W[0]; + Dv[N] = (P_v[N] - eval[N] * W_v[0]) / W[0]; + DuDu[N] = (P_uu[N] - T(2) * W_u[0] * Du[N] - eval[N] * W_uu[0]) / W[0]; + DvDv[N] = (P_vv[N] - T(2) * W_v[0] * Dv[N] - eval[N] * W_vv[0]) / W[0]; + DuDv[N] = (P_uv[N] - Du[N] * W_v[0] - Dv[N] * W_u[0] - eval[N] * W_uv[0]) / W[0]; + } + } + } + + /*! + * \brief Computes a tangent of a Bezier triangle at (\a u, \a v) along the u axis + */ + VectorType du(T u, T v) const + { + PointType eval; + VectorType Du, Dv; + evaluateFirstDerivatives(u, v, eval, Du, Dv); + return Du; + } + + /*! + * \brief Computes a tangent of a Bezier triangle at (\a u, \a v) along the v axis + */ + VectorType dv(T u, T v) const + { + PointType eval; + VectorType Du, Dv; + evaluateFirstDerivatives(u, v, eval, Du, Dv); + return Dv; + } + + /*! + * \brief Computes the second derivative of a Bezier triangle at (\a u, \a v) along the u axis + */ + VectorType dudu(T u, T v) const + { + PointType eval; + VectorType Du, Dv, DuDu, DvDv, DuDv; + evaluateSecondDerivatives(u, v, eval, Du, Dv, DuDu, DvDv, DuDv); + return DuDu; + } + + /*! + * \brief Computes the second derivative of a Bezier triangle at (\a u, \a v) along the v axis + */ + VectorType dvdv(T u, T v) const + { + PointType eval; + VectorType Du, Dv, DuDu, DvDv, DuDv; + evaluateSecondDerivatives(u, v, eval, Du, Dv, DuDu, DvDv, DuDv); + return DvDv; + } + + /*! + * \brief Computes the mixed second derivative of a Bezier triangle at (\a u, \a v) + */ + VectorType dudv(T u, T v) const + { + PointType eval; + VectorType Du, Dv, DuDu, DvDv, DuDv; + evaluateSecondDerivatives(u, v, eval, Du, Dv, DuDu, DvDv, DuDv); + return DuDv; + } + + /// Convenience alias for S_vu(u,v), which equals S_uv(u,v) for polynomial triangles + VectorType dvdu(T u, T v) const { return dudv(u, v); } + + /*! + * \brief Computes the normal vector of a Bezier triangle at (\a u, \a v) + * + * \note Only meaningful for NDIMS==3. + */ + VectorType normal(T u, T v) const + { + Point eval; + Vector Du, Dv; + evaluateFirstDerivatives(u, v, eval, Du, Dv); + return VectorType::cross_product(Du, Dv); + } + + /*! + * \brief Simple formatted print of a Bezier Triangle instance + * + * \param os The output stream to write to + * \return A reference to the modified ostream + */ + std::ostream& print(std::ostream& os) const + { + os << "{ order " << m_ord << " Bezier Triangle "; + + for(int i = 0; i <= m_ord; ++i) + { + for(int j = 0; j <= m_ord - i; ++j) + { + os << (*this)(i, j) << ((i < m_ord || j < m_ord - i) ? "," : ""); + } + } + + if(isRational()) + { + os << ", weights ["; + for(int i = 0; i <= m_ord; ++i) + { + for(int j = 0; j <= m_ord - i; ++j) + { + os << m_weights[triIndex(m_ord, i, j)] << ((i < m_ord || j < m_ord - i) ? "," : ""); + } + } + os << "]"; + } + + os << "}"; + return os; + } + + /*! + * \brief Returns the number of control points for a triangle of order \a ord + * + * \param [in] ord Triangle order + */ + static constexpr size_t triSize(int ord) + { + return (ord >= 0) ? static_cast((ord + 1) * (ord + 2) / 2) : size_t {0}; + } + + /*! + * \brief Maps triangular indices \a (i,j) to the linear storage index + * + * \param [in] ord Triangle order + * \param [in] i First control net index + * \param [in] j Second control net index + * + * \pre ord >= 0, i >= 0, j >= 0, and i+j <= ord + */ + static constexpr size_t triIndex(int ord, int i, int j) + { + return static_cast(i * (2 * ord + 3 - i) / 2 + j); + } + +private: + /// Check that the weights used are positive, and + /// that there is one for each control node + bool is_valid_rational() const + { + if(!isRational()) + { + return true; + } + + if(m_weights.size() != m_controlPoints.size()) + { + return false; + } + + for(int p = 0; p < m_weights.size(); ++p) + { + if(m_weights[p] <= 0) + { + return false; + } + } + + return true; + } + + void fill_projective_triangles(BezierTriangle& projective, + BezierTriangle& weights) const + { + SLIC_ASSERT(isRational()); + SLIC_ASSERT(is_valid_rational()); + + for(int i = 0; i <= m_ord; ++i) + { + for(int j = 0; j <= m_ord - i; ++j) + { + const T w = m_weights[triIndex(m_ord, i, j)]; + weights(i, j)[0] = w; + + for(int N = 0; N < NDIMS; ++N) + { + projective(i, j)[N] = (*this)(i, j)[N] * w; + } + } + } + } + +private: + int m_ord; + + CoordsVec m_controlPoints; + WeightsVec m_weights; +}; + +//------------------------------------------------------------------------------ +/// Free functions related to BezierTriangle +//------------------------------------------------------------------------------ +template +std::ostream& operator<<(std::ostream& os, const BezierTriangle& bTri) +{ + bTri.print(os); + return os; +} + +} // namespace primal +} // namespace axom + +#endif // AXOM_PRIMAL_BEZIERTRIANGLE_HPP_ diff --git a/src/axom/primal/tests/CMakeLists.txt b/src/axom/primal/tests/CMakeLists.txt index ec9fec1d86..99cb2c2a7a 100644 --- a/src/axom/primal/tests/CMakeLists.txt +++ b/src/axom/primal/tests/CMakeLists.txt @@ -11,6 +11,7 @@ set( primal_tests primal_bezier_curve.cpp primal_bezier_intersect.cpp primal_bezier_patch.cpp + primal_bezier_triangle.cpp primal_boundingbox.cpp primal_bounding_box_intersect.cpp primal_clip.cpp diff --git a/src/axom/primal/tests/primal_bezier_patch.cpp b/src/axom/primal/tests/primal_bezier_patch.cpp index c69b0c006e..6279f4718c 100644 --- a/src/axom/primal/tests/primal_bezier_patch.cpp +++ b/src/axom/primal/tests/primal_bezier_patch.cpp @@ -822,7 +822,7 @@ TEST(primal_bezierpatch, rational_batch_derivatives) BezierPatchType patch(controlPoints, weights, order_u, order_v); patch.evaluateFirstDerivatives(u, v, batch1_val, batch1_du, batch1_dv); - patch.evaluate_linear_derivatives(u, v, batch2_val, batch2_du, batch2_dv, batch2_dudv); + patch.evaluateLinearDerivatives(u, v, batch2_val, batch2_du, batch2_dv, batch2_dudv); patch.evaluateSecondDerivatives(u, v, batch3_val, @@ -855,7 +855,7 @@ TEST(primal_bezierpatch, rational_batch_derivatives) patch.swapAxes(); patch.evaluateFirstDerivatives(u, v, batch1_val, batch1_du, batch1_dv); - patch.evaluate_linear_derivatives(u, v, batch2_val, batch2_du, batch2_dv, batch2_dudv); + patch.evaluateLinearDerivatives(u, v, batch2_val, batch2_du, batch2_dv, batch2_dudv); patch.evaluateSecondDerivatives(u, v, batch3_val, diff --git a/src/axom/primal/tests/primal_bezier_triangle.cpp b/src/axom/primal/tests/primal_bezier_triangle.cpp new file mode 100644 index 0000000000..9c755f8d8b --- /dev/null +++ b/src/axom/primal/tests/primal_bezier_triangle.cpp @@ -0,0 +1,353 @@ +// 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) + +/*! + * \file primal_bezier_triangle.cpp + * \brief This file tests primal's Bezier triangle functionality + */ + +#include "gtest/gtest.h" + +#include "axom/slic.hpp" + +#include "axom/primal/geometry/BezierTriangle.hpp" + +#include +#include + +namespace primal = axom::primal; + +//------------------------------------------------------------------------------ +TEST(primal_beziertriangle, sizing_constructors) +{ + constexpr int DIM = 3; + using CoordType = double; + using BezierTriangleType = primal::BezierTriangle; + using CoordsVec = BezierTriangleType::CoordsVec; + + // testing default BezierTriangle constructor + { + BezierTriangleType bTri; + EXPECT_FALSE(bTri.isRational()); + + EXPECT_EQ(-1, bTri.getOrder()); + EXPECT_EQ(0, bTri.getControlPoints().size()); + EXPECT_EQ(CoordsVec(), bTri.getControlPoints()); + } + + // testing BezierTriangle order constructor + for(int ord = -1; ord < 5; ++ord) + { + BezierTriangleType bTri(ord); + EXPECT_FALSE(bTri.isRational()); + + EXPECT_EQ(ord, bTri.getOrder()); + EXPECT_EQ(BezierTriangleType::triSize(ord), bTri.getControlPoints().size()); + EXPECT_EQ(0, bTri.getWeights().size()); + } +} + +//------------------------------------------------------------------------------ +TEST(primal_beziertriangle, array_constructors) +{ + constexpr int DIM = 3; + using CoordType = double; + using PointType = primal::Point; + using BezierTriangleType = primal::BezierTriangle; + + SLIC_INFO("Testing BezierTriangle array constructors"); + + constexpr int ord = 2; + constexpr int npts = (ord + 1) * (ord + 2) / 2; + + PointType controlPoints[npts]; + CoordType weights[npts]; + + for(int i = 0; i <= ord; ++i) + { + for(int j = 0; j <= ord - i; ++j) + { + const int idx = BezierTriangleType::triIndex(ord, i, j); + controlPoints[idx] = PointType {static_cast(i), + static_cast(j), + static_cast(i + 2 * j)}; + weights[idx] = static_cast(0.25 + idx); + } + } + + auto check_triangle = [&](const BezierTriangleType& tri, bool expect_rational) { + EXPECT_EQ(ord, tri.getOrder()); + EXPECT_EQ(npts, tri.getControlPoints().size()); + EXPECT_EQ(expect_rational, tri.isRational()); + EXPECT_EQ(expect_rational ? npts : 0, tri.getWeights().size()); + + for(int i = 0; i <= ord; ++i) + { + for(int j = 0; j <= ord - i; ++j) + { + const int idx = BezierTriangleType::triIndex(ord, i, j); + EXPECT_EQ(tri(i, j), controlPoints[idx]); + if(expect_rational) + { + EXPECT_EQ(tri.getWeights()[idx], weights[idx]); + } + } + } + }; + + // check C-array constructors + { + SCOPED_TRACE("Testing C-array constructor, polynomial"); + BezierTriangleType nTri(controlPoints, ord); + check_triangle(nTri, false); + + SCOPED_TRACE("Testing C-array constructor, polynomial w/ null weight"); + BezierTriangleType nTri2(controlPoints, static_cast(nullptr), ord); + check_triangle(nTri2, false); + + SCOPED_TRACE("Testing C-array constructor, rational"); + BezierTriangleType rTri(controlPoints, weights, ord); + check_triangle(rTri, true); + } + + // check ArrayView constructors (from C-arrays) + { + axom::ArrayView cp(controlPoints, npts); + axom::ArrayView w(weights, npts); + + SCOPED_TRACE("Testing ArrayView constructor, polynomial"); + BezierTriangleType nTri(cp, axom::ArrayView(nullptr, 0), ord); + check_triangle(nTri, false); + + SCOPED_TRACE("Testing ArrayView constructor, rational"); + BezierTriangleType rTri(cp, w, ord); + check_triangle(rTri, true); + } + + // check 1D Array constructors + { + axom::Array cp; + cp.assign(std::begin(controlPoints), std::end(controlPoints)); + + axom::Array w; + w.assign(std::begin(weights), std::end(weights)); + + SCOPED_TRACE("Testing Array constructor, polynomial"); + BezierTriangleType nTri(cp, ord); + check_triangle(nTri, false); + + SCOPED_TRACE("Testing Array constructor, rational"); + BezierTriangleType rTri(cp, w, ord); + check_triangle(rTri, true); + } +} + +//------------------------------------------------------------------------------ +TEST(primal_beziertriangle, set_order) +{ + constexpr int DIM = 3; + using CoordType = double; + using BezierTriangleType = primal::BezierTriangle; + + BezierTriangleType bTri; + EXPECT_EQ(-1, bTri.getOrder()); + EXPECT_EQ(0, bTri.getControlPoints().size()); + EXPECT_FALSE(bTri.isRational()); + + constexpr int ord = 2; + bTri.setOrder(ord); + EXPECT_EQ(ord, bTri.getOrder()); + EXPECT_EQ(BezierTriangleType::triSize(ord), bTri.getControlPoints().size()); + EXPECT_FALSE(bTri.isRational()); + + // Setting the order should resize weights in rational triangles + BezierTriangleType rTri(1); + rTri.getWeights().resize(rTri.getControlPoints().size()); + for(int i = 0; i < rTri.getWeights().size(); ++i) + { + rTri.getWeights()[i] = 1.0; + } + EXPECT_TRUE(rTri.isRational()); + + constexpr int ord2 = 3; + rTri.setOrder(ord2); + EXPECT_EQ(ord2, rTri.getOrder()); + EXPECT_EQ(BezierTriangleType::triSize(ord2), rTri.getControlPoints().size()); + EXPECT_EQ(BezierTriangleType::triSize(ord2), rTri.getWeights().size()); +} + +//------------------------------------------------------------------------------ +TEST(primal_beziertriangle, evaluate_linear) +{ + using CoordType = double; + using BTri = primal::BezierTriangle; + using PointType = BTri::PointType; + using VectorType = BTri::VectorType; + + constexpr int ord = 1; + constexpr CoordType eps = 1e-14; + + BTri tri(ord); + + const PointType p00 {1.0, 2.0, 3.0}; + const PointType p01 {2.0, -1.0, 0.5}; + const PointType p10 {0.25, 1.5, -2.0}; + tri(0, 0) = p00; + tri(0, 1) = p01; + tri(1, 0) = p10; + + EXPECT_EQ(tri.evaluate(0.0, 0.0), p00); + EXPECT_EQ(tri.evaluate(0.0, 1.0), p01); + EXPECT_EQ(tri.evaluate(1.0, 0.0), p10); + + const CoordType u = 0.3; + const CoordType v = 0.2; + const PointType expected = PointType {p00[0] + u * (p10[0] - p00[0]) + v * (p01[0] - p00[0]), + p00[1] + u * (p10[1] - p00[1]) + v * (p01[1] - p00[1]), + p00[2] + u * (p10[2] - p00[2]) + v * (p01[2] - p00[2])}; + + const PointType eval = tri.evaluate(u, v); + for(int d = 0; d < 3; ++d) + { + EXPECT_NEAR(eval[d], expected[d], eps); + } + + PointType e1; + VectorType Du, Dv; + tri.evaluateFirstDerivatives(u, v, e1, Du, Dv); + for(int d = 0; d < 3; ++d) + { + EXPECT_NEAR(e1[d], expected[d], eps); + EXPECT_NEAR(Du[d], (p10[d] - p00[d]), eps); + EXPECT_NEAR(Dv[d], (p01[d] - p00[d]), eps); + } + + VectorType DuDu, DvDv, DuDv; + tri.evaluateSecondDerivatives(u, v, e1, Du, Dv, DuDu, DvDv, DuDv); + for(int d = 0; d < 3; ++d) + { + EXPECT_NEAR(DuDu[d], 0.0, eps); + EXPECT_NEAR(DvDv[d], 0.0, eps); + EXPECT_NEAR(DuDv[d], 0.0, eps); + } +} + +//------------------------------------------------------------------------------ +TEST(primal_beziertriangle, evaluate_quadratic_second_derivatives_constant) +{ + using CoordType = double; + using BTri = primal::BezierTriangle; + using PointType = BTri::PointType; + using VectorType = BTri::VectorType; + + constexpr int ord = 2; + constexpr CoordType eps = 1e-12; + + BTri tri(ord); + + // Populate control net with deterministic values + for(int i = 0; i <= ord; ++i) + { + for(int j = 0; j <= ord - i; ++j) + { + tri(i, j) = PointType {static_cast(i), + static_cast(j), + static_cast(i * i + j)}; + } + } + + // Vertex interpolation + EXPECT_EQ(tri.evaluate(0.0, 0.0), tri(0, 0)); + EXPECT_EQ(tri.evaluate(0.0, 1.0), tri(0, ord)); + EXPECT_EQ(tri.evaluate(1.0, 0.0), tri(ord, 0)); + + auto derivs = [&](CoordType u, CoordType v) { + PointType e; + VectorType Du, Dv, DuDu, DvDv, DuDv; + tri.evaluateSecondDerivatives(u, v, e, Du, Dv, DuDu, DvDv, DuDv); + return std::array {DuDu, DvDv, DuDv}; + }; + + const auto d0 = derivs(0.2, 0.3); + const auto d1 = derivs(0.6, 0.1); + for(int d = 0; d < 3; ++d) + { + EXPECT_NEAR(d0[0][d], d1[0][d], eps); + EXPECT_NEAR(d0[1][d], d1[1][d], eps); + EXPECT_NEAR(d0[2][d], d1[2][d], eps); + } +} + +TEST(primal_beziertriangle, rational_triangles) +{ + using CoordType = double; + using BTri = primal::BezierTriangle; + using PointType = BTri::PointType; + using VectorType = BTri::VectorType; + + constexpr int ord = 2; + constexpr CoordType eps = 1e-12; + + BTri poly(ord); + BTri rat(ord); + rat.getWeights().resize(rat.getControlPoints().size()); + for(int i = 0; i < rat.getWeights().size(); ++i) + { + rat.getWeights()[i] = 1.0; + } + + // Populate a shared control net + poly(0, 0) = PointType {0.0, 0.0, 0.0}; + poly(0, 1) = PointType {1.0, 0.0, 0.0}; + poly(0, 2) = PointType {2.0, 0.0, 0.0}; + poly(1, 0) = PointType {0.0, 1.0, 0.0}; + poly(1, 1) = PointType {1.0, 1.0, 1.0}; + poly(2, 0) = PointType {0.0, 2.0, 0.0}; + + rat(0, 0) = poly(0, 0); + rat(0, 1) = poly(0, 1); + rat(0, 2) = poly(0, 2); + rat(1, 0) = poly(1, 0); + rat(1, 1) = poly(1, 1); + rat(2, 0) = poly(2, 0); + + auto check_at = [&](CoordType u, CoordType v) { + // value + const PointType p0 = poly.evaluate(u, v); + const PointType p1 = rat.evaluate(u, v); + for(int d = 0; d < 3; ++d) + { + EXPECT_NEAR(p0[d], p1[d], eps); + } + + // first derivatives + PointType e0, e1; + VectorType du0, dv0, du1, dv1; + poly.evaluateFirstDerivatives(u, v, e0, du0, dv0); + rat.evaluateFirstDerivatives(u, v, e1, du1, dv1); + for(int d = 0; d < 3; ++d) + { + EXPECT_NEAR(e0[d], e1[d], eps); + EXPECT_NEAR(du0[d], du1[d], eps); + EXPECT_NEAR(dv0[d], dv1[d], eps); + } + + // second derivatives + VectorType duu0, dvv0, duv0, duu1, dvv1, duv1; + poly.evaluateSecondDerivatives(u, v, e0, du0, dv0, duu0, dvv0, duv0); + rat.evaluateSecondDerivatives(u, v, e1, du1, dv1, duu1, dvv1, duv1); + for(int d = 0; d < 3; ++d) + { + EXPECT_NEAR(e0[d], e1[d], eps); + EXPECT_NEAR(duu0[d], duu1[d], eps); + EXPECT_NEAR(dvv0[d], dvv1[d], eps); + EXPECT_NEAR(duv0[d], duv1[d], eps); + } + }; + + check_at(0.2, 0.3); + check_at(0.6, 0.1); +} From e7ab0281e28ab5386e062e2c91a2d68c48382796 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Tue, 19 May 2026 08:25:01 -0700 Subject: [PATCH 328/986] Update release notes --- RELEASE-NOTES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 1053530907..876a20df9f 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -26,6 +26,7 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ - Primal: Adds `NURBSPatch::isTriviallyTrimmed()` to check if the trimming curves for a patch lie on the patch boundaries - Quest: Adds support for reading mfem files with variable order NURBS curves (requires mfem>4.9). - Quest: Adds OMP support for fast GWN methods for STL/Triangulated STEP input and linearized NURBS Curve input. +- Quest: Adds OMP supported, fast and accurate GWN method for NURBS curves and trimmed NURBS surfaces. - Klee: Adds an optional "center" parameter in scale operators that permits scaling relative to a custom center point. - Quest: `SamplingShaper` now supports selecting MFEM quadrature families for custom sample-point generation, including anisotropic per-direction sampling resolution on quadrilateral and hexahedral meshes. Quadrature type is selected via @@ -36,6 +37,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` +- Primal: Adds a `primal::BezierTriangle` class ### Removed From d9a78073edcfebf21168b5859c2dd75b14d5b4a1 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Wed, 20 May 2026 21:51:02 -0700 Subject: [PATCH 329/986] First recursive approach --- src/axom/primal/geometry/BezierTriangle.hpp | 741 +++++++++++++++++++- 1 file changed, 714 insertions(+), 27 deletions(-) diff --git a/src/axom/primal/geometry/BezierTriangle.hpp b/src/axom/primal/geometry/BezierTriangle.hpp index 6b05f928ee..0566c841aa 100644 --- a/src/axom/primal/geometry/BezierTriangle.hpp +++ b/src/axom/primal/geometry/BezierTriangle.hpp @@ -25,7 +25,10 @@ #include "axom/primal/operators/squared_distance.hpp" +#include #include +#include +#include namespace axom { @@ -75,6 +78,7 @@ class BezierTriangle using BoundingBoxType = BoundingBox; using OrientedBoundingBoxType = OrientedBoundingBox; using BezierCurveType = primal::BezierCurve; + using EdgesVec = axom::Array; AXOM_STATIC_ASSERT_MSG((NDIMS == 1) || (NDIMS == 2) || (NDIMS == 3), "A Bezier Triangle object may be defined in 1-, 2-, or 3-D"); @@ -148,7 +152,7 @@ class BezierTriangle } } - /// Constructor from ArrayViews of (non-const) control points and weights + /// \brief Constructor from ArrayViews of (non-const) control points and weights BezierTriangle(axom::ArrayView controlPoints, axom::ArrayView weights, int ord) : BezierTriangle(axom::ArrayView(controlPoints.data(), controlPoints.size()), axom::ArrayView(weights.data(), weights.size()), @@ -234,7 +238,7 @@ class BezierTriangle */ CoordsVec& getControlPoints() { return m_controlPoints; } - /// \overload + /// \brief Returns a reference to the triangle's control points const CoordsVec& getControlPoints() const { return m_controlPoints; } /*! @@ -245,7 +249,7 @@ class BezierTriangle */ WeightsVec& getWeights() { return m_weights; } - /// \overload + /// \brief Returns a reference to the triangle's weights const WeightsVec& getWeights() const { return m_weights; } /*! @@ -300,6 +304,703 @@ class BezierTriangle */ int getOrder() const { return m_ord; } + /*! + * \brief Returns one of the boundary edges of the Bezier triangle + * + * \param [in] edgeIdx Index of the requested edge in \a [0,2] + * + * The edges are returned in counter-clockwise order with respect to the + * parameter domain (u,v), with edge 0 across from corner 0 (i.e. evaluate(0,0)): + * - \a edgeIdx = 0: u+v = 1 from `evaluate(1,0)` to `evaluate(0,1)` + * - \a edgeIdx = 1: u = 0 from `evaluate(0,1)` to `evaluate(0,0)` + * - \a edgeIdx = 2: v = 0 from `evaluate(0,0)` to `evaluate(1,0)` + * + * For rational triangles, the returned curve is rational and uses the subset of + * weights corresponding to the boundary control points. + * + * \return A Bezier curve representing the requested edge. + */ + BezierCurveType getEdge(int edgeIdx) const + { + SLIC_ASSERT(m_ord >= 0); + SLIC_ASSERT(edgeIdx >= 0 && edgeIdx < 3); + + axom::Array pts(m_ord + 1); + axom::Array wts; + if(isRational()) + { + wts.resize(m_ord + 1); + } + + switch(edgeIdx) + { + case 0: + for(int k = 0; k <= m_ord; ++k) + { + const int i = k; + const int j = m_ord - k; + pts[k] = (*this)(i, j); + if(isRational()) + { + wts[k] = m_weights[triIndex(m_ord, i, j)]; + } + } + break; + case 1: + for(int k = 0; k <= m_ord; ++k) + { + const int i = m_ord - k; + pts[k] = (*this)(i, 0); + if(isRational()) + { + wts[k] = m_weights[triIndex(m_ord, i, 0)]; + } + } + break; + case 2: + for(int j = 0; j <= m_ord; ++j) + { + pts[j] = (*this)(0, j); + if(isRational()) + { + wts[j] = m_weights[triIndex(m_ord, 0, j)]; + } + } + break; + default: + break; + } + + return isRational() ? BezierCurveType(pts, wts, m_ord) : BezierCurveType(pts, m_ord); + } + + /*! + * \brief Returns all three boundary edges of the Bezier triangle + * + * \return An array of three Bezier curves, ordered the same as `getEdge(int)`. + */ + EdgesVec getEdges() const + { + SLIC_ASSERT(m_ord >= 0); + EdgesVec edges; + edges.reserve(3); + edges.push_back(getEdge(0)); + edges.push_back(getEdge(1)); + edges.push_back(getEdge(2)); + return edges; + } + + /*! + * \brief Restricts this polynomial Bezier triangle to a subtriangle of the parameter domain + * + * The subtriangle is defined by three barycentric coordinates \a (u,v,w) (with \a w = 1-u-v) + * in the parameter domain of this triangle. + * + * The returned triangle is parametrized over the reference domain and maps its vertices as: + * - local `(0,0)` -> \a Va + * - local `(0,1)` -> \a Vb + * - local `(1,0)` -> \a Vc + * + * \param [in] Va Barycentric coordinates of the first subtriangle vertex `(u,v,w)` + * \param [in] Vb Barycentric coordinates of the second subtriangle vertex `(u,v,w)` + * \param [in] Vc Barycentric coordinates of the third subtriangle vertex `(u,v,w)` + * + * \pre getOrder() >= 0 + * \pre This triangle is polynomial (nonrational) + */ + BezierTriangle restrictToSubtriangle(const Point& Va, + const Point& Vb, + const Point& Vc) const + { + BezierTriangle out; + restrictToSubtriangle(Va, Vb, Vc, out); + return out; + } + + /*! + * \brief Restricts this polynomial Bezier triangle to a subtriangle of the parameter domain + * + * See overload returning a `BezierTriangle` for the vertex mapping convention. + * + * \param [in] Va Barycentric coordinates of the first subtriangle vertex `(u,v,w)` + * \param [in] Vb Barycentric coordinates of the second subtriangle vertex `(u,v,w)` + * \param [in] Vc Barycentric coordinates of the third subtriangle vertex `(u,v,w)` + * \param [out] out Output restricted Bezier triangle + * + * \pre getOrder() >= 0 + * \pre This triangle is polynomial (nonrational) + */ + void restrictToSubtriangle(const Point& Va, + const Point& Vb, + const Point& Vc, + BezierTriangle& out) const + { + using Barycentric = Point; + SLIC_ASSERT(m_ord >= 0); + SLIC_ASSERT(!isRational()); + + const int n = m_ord; + + auto reduce_once = [&](const axom::Array& prev, int deg, const Barycentric& Q) { + const int newDeg = deg - 1; + axom::Array next; + next.resize(triSize(newDeg)); + + for(int ii = 0; ii <= newDeg; ++ii) + { + for(int jj = 0; jj <= newDeg - ii; ++jj) + { + const auto& A0 = prev[triIndex(deg, ii, jj)]; + const auto& B0 = prev[triIndex(deg, ii, jj + 1)]; + const auto& C0 = prev[triIndex(deg, ii + 1, jj)]; + + PointType val; + for(int N = 0; N < NDIMS; ++N) + { + // Barycentric coordinates permuted to match convention in evaluate() + val[N] = Q[2] * A0[N] + Q[1] * B0[N] + Q[0] * C0[N]; + } + next[triIndex(newDeg, ii, jj)] = val; + } + } + + return next; + }; + + // Tetrahedral family of intermediate nets, indexed by the counts of (Vc, Vb, Va) + // fixed in the blossom. For p fixed arguments, there are triSize(p) nets, each of + // degree (n-p). At p==n, each net is degree 0 and corresponds to a restricted + // control point b(Vc^i, Vb^j, Va^(n-i-j)). + std::vector> prevNets(1); + prevNets[0] = m_controlPoints; + + for(int p = 1; p <= n; ++p) + { + const int degPrev = n - (p - 1); + std::vector> currNets(triSize(p)); + + for(int a = 0; a <= p; ++a) + { + for(int b = 0; b <= p - a; ++b) + { + const int idx = triIndex(p, a, b); + const int c = p - a - b; + + // Choose a unique predecessor (and thus a unique evaluation order) to avoid + // redundant reductions; the blossom symmetry ensures the final values are + // order-independent. + const Barycentric* Q = nullptr; + int predIdx = -1; + + if(c > 0) + { + predIdx = triIndex(p - 1, a, b); + Q = &Va; + } + else if(b > 0) + { + predIdx = triIndex(p - 1, a, b - 1); + Q = &Vb; + } + else + { + predIdx = triIndex(p - 1, a - 1, b); + Q = &Vc; + } + + currNets[idx] = reduce_once(prevNets[predIdx], degPrev, *Q); + } + } + + prevNets.swap(currNets); + } + + out.setOrder(n); + out.getWeights().resize(0); + for(int i = 0; i <= n; ++i) + { + for(int j = 0; j <= n - i; ++j) + { + out(i, j) = prevNets[triIndex(n, i, j)][0]; + } + } + } + + /*! + * \brief Splits a polynomial Bezier triangle into three subtriangles by connecting an + * interior parameter point \a (u,v) to the triangle's three vertices + * + * \param [in] u Parameter value along the \a u axis for the split point + * \param [in] v Parameter value along the \a v axis for the split point + * \param [out] t0 Subtriangle over the parameter triangle with vertices + * `(0,1)`, `(1,0)`, and `(u,v)` (preserves edge 0) + * \param [out] t1 Subtriangle over the parameter triangle with vertices + * `(1,0)`, `(0,0)`, and `(u,v)` (preserves edge 1) + * \param [out] t2 Subtriangle over the parameter triangle with vertices + * `(0,0)`, `(0,1)`, and `(u,v)` (preserves edge 2) + * + * \pre \a u > 0, \a v > 0, and \a u + \a v < 1 + * + * A + * /|\ + * / | \ + * /t2|t1\ + * / /Q\ \ + * / / t0 \ \ + * //_________\\ + * B C + * + * \return t0 = Tri(B,C,Q), t1 = Tri(C,A,Q), t2 = Tri(A,B,Q) + */ + void split(T u, T v, BezierTriangle& t0, BezierTriangle& t1, BezierTriangle& t2) const + { + SLIC_ASSERT(m_ord >= 0); + SLIC_ASSERT(!isRational()); + SLIC_ASSERT(u > T(0)); + SLIC_ASSERT(v > T(0)); + SLIC_ASSERT(u + v < T(1)); + + // Q is the split point in barycentric coordinates over the reference parameter triangle: + using Barycentric = Point; + const Barycentric Q {u, v, T(1) - u - v}; + + const int n = m_ord; + std::vector> net(static_cast(n + 1)); + net[0] = m_controlPoints; + + for(int p = 1; p <= n; ++p) + { + const int deg = n - p + 1; + const int newDeg = deg - 1; + + net[p].resize(triSize(newDeg)); + const auto& prev = net[p - 1]; + + for(int ii = 0; ii <= newDeg; ++ii) + { + for(int jj = 0; jj <= newDeg - ii; ++jj) + { + const auto& A0 = prev[triIndex(deg, ii, jj)]; + const auto& B0 = prev[triIndex(deg, ii, jj + 1)]; + const auto& C0 = prev[triIndex(deg, ii + 1, jj)]; + + PointType val; + for(int N = 0; N < NDIMS; ++N) + { + val[N] = Q[2] * A0[N] + Q[1] * B0[N] + Q[0] * C0[N]; + } + net[p][triIndex(newDeg, ii, jj)] = val; + } + } + } + + t0.setOrder(n); + t1.setOrder(n); + t2.setOrder(n); + t0.getWeights().resize(0); + t1.getWeights().resize(0); + t2.getWeights().resize(0); + + // Subtriangle (B,C,Q): (0,1), (1,0), (u,v) + for(int i = 0; i <= n; ++i) + { + const int deg = n - i; + for(int j = 0; j <= n - i; ++j) + { + t0(i, j) = net[i][triIndex(deg, j, deg - j)]; + } + } + + // Subtriangle (C,A,Q): (1,0), (0,0), (u,v) + for(int i = 0; i <= n; ++i) + { + const int deg = n - i; + for(int j = 0; j <= n - i; ++j) + { + t1(i, j) = net[i][triIndex(deg, deg - j, 0)]; + } + } + + // Subtriangle (A,B,Q): (0,0), (0,1), (u,v) + for(int i = 0; i <= n; ++i) + { + const int deg = n - i; + for(int j = 0; j <= n - i; ++j) + { + t2(i, j) = net[i][triIndex(deg, 0, j)]; + } + } + } + + /*! + * \brief Splits a polynomial Bezier triangle into two subtriangles by connecting a + * point on a boundary edge to the opposite vertex + * + * \param [in] edgeIdx Index of the boundary edge to split (same convention as `getEdge(int)`) + * \param [in] s Parameter in \a [0,1] locating the split point along the chosen edge + * \param [out] t0 First output subtriangle + * \param [out] t1 Second output subtriangle + * + * \pre edgeIdx is 0, 1, or 2 + * \pre \a s is in (0,1) + * + * Taking P0 as the vertex opposite edge `edgeIdx`: + * + * P0 + * /|\ + * / | \ + * / | \ + * / | \ + * / t0 | t1 \ + * /_____|_____\ + * P1 Q P2 + * s=0 s s=1 + * + * \return t0 = Tri( P0, P1, Q ), t1 = Tri( P2, P0, Q ) + */ + void split(int edgeIdx, T s, BezierTriangle& t0, BezierTriangle& t1) const + { + SLIC_ASSERT(m_ord >= 0); + SLIC_ASSERT(!isRational()); + SLIC_ASSERT(edgeIdx >= 0 && edgeIdx < 3); + SLIC_ASSERT(s > T(0) && s < T(1)); + + using Barycentric = Point; + + Barycentric Q; + switch(edgeIdx) + { + case 0: // (u=s, v=1-s, w=0) + Q = Barycentric {s, T(1) - s, T(0)}; + break; + case 1: // (u=1-s, v=0, w=s) + Q = Barycentric {T(1) - s, T(0), s}; + break; + case 2: // (u=0, v=s, w=1-s) + Q = Barycentric {T(0), s, T(1) - s}; + break; + default: + break; + } + + const int n = m_ord; + std::vector> net(static_cast(n + 1)); + net[0] = m_controlPoints; + + for(int p = 1; p <= n; ++p) + { + const int deg = n - p + 1; + const int newDeg = deg - 1; + + net[p].resize(triSize(newDeg)); + const auto& prev = net[p - 1]; + + for(int ii = 0; ii <= newDeg; ++ii) + { + for(int jj = 0; jj <= newDeg - ii; ++jj) + { + const auto& A0 = prev[triIndex(deg, ii, jj)]; + const auto& B0 = prev[triIndex(deg, ii, jj + 1)]; + const auto& C0 = prev[triIndex(deg, ii + 1, jj)]; + + PointType val; + for(int N = 0; N < NDIMS; ++N) + { + // Barycentric coordinates permuted to match convention in evaluate + val[N] = Q[2] * A0[N] + Q[1] * B0[N] + Q[0] * C0[N]; + } + net[p][triIndex(newDeg, ii, jj)] = val; + } + } + } + + auto fill_from_net = [&](int keptEdge, BezierTriangle& out) { + out.setOrder(n); + out.getWeights().resize(0); + for(int i = 0; i <= n; ++i) + { + const int deg = n - i; + for(int j = 0; j <= n - i; ++j) + { + switch(keptEdge) + { + case 0: + // Keeps original BC line: (j,deg-j) + out(i, j) = net[i][triIndex(deg, j, deg - j)]; + break; + case 1: + // Keeps original CA line: (deg-j,0) + out(i, j) = net[i][triIndex(deg, deg - j, 0)]; + break; + case 2: + // Keeps original AB line: (0,j) + out(i, j) = net[i][triIndex(deg, 0, j)]; + break; + default: + break; + } + } + } + }; + + switch(edgeIdx) + { + case 0: + // Q on BC -> keep AB and CA + fill_from_net(2, t0); // (A,B,Q) + fill_from_net(1, t1); // (C,A,Q) + break; + case 1: + // Q on CA -> keep BC and AB + fill_from_net(0, t0); // (B,C,Q) + fill_from_net(2, t1); // (A,B,Q) + break; + case 2: + // Q on AB -> keep CA and BC + fill_from_net(1, t0); // (C,A,Q) + fill_from_net(0, t1); // (B,C,Q) + break; + default: + break; + } + } + + /*! + * \brief Splits a polynomial Bezier triangle into four subtriangles by inserting one + * split point on each boundary edge and connecting the split points pairwise + * + * \param [in] s1 Parameter in \a [0,1] locating the split point on edge 0 (same convention as `getEdge(0)`) + * \param [in] s2 Parameter in \a [0,1] locating the split point on edge 1 (same convention as `getEdge(1)`) + * \param [in] s3 Parameter in \a [0,1] locating the split point on edge 2 (same convention as `getEdge(2)`) + * \param [out] t1 Subtriangle near vertex `(0,0)` with vertices `(0,0)`, point on edge 2, point on edge 1 + * \param [out] t2 Subtriangle near vertex `(0,1)` with vertices `(0,1)`, point on edge 0, point on edge 2 + * \param [out] t3 Subtriangle near vertex `(1,0)` with vertices `(1,0)`, point on edge 1, point on edge 0 + * \param [out] t4 Central subtriangle with vertices point on edge 1, point on edge 0, point on edge 2 + * + * \pre \a s1, \a s2, \a s3 are all in (0,1) + * C + * /\ + * /t3\ + * P1 /____\ P0 + * /\ t4 /\ + * /t1\ /t2\ + * /____\/____\ + * A P2 B + * + * \return t1 = Tri( A, P2, P1 ), t2 = Tri( B, P0, P2 ), t3 = Tri( C, P1, P0 ), t4 = Tri( P1, P0, P2 ) + */ + void split(T s1, + T s2, + T s3, + BezierTriangle& t1, + BezierTriangle& t2, + BezierTriangle& t3, + BezierTriangle& t4) const + { + using Barycentric = Point; + SLIC_ASSERT(m_ord >= 0); + SLIC_ASSERT(!isRational()); + SLIC_ASSERT(s1 > T(0) && s1 < T(1)); + SLIC_ASSERT(s2 > T(0) && s2 < T(1)); + SLIC_ASSERT(s3 > T(0) && s3 < T(1)); + + // Barycentric coordinates in (u, v, w) where w = 1-u-v + // and the triangle vertices are: A=(0,0,1), B=(0,1,0), C=(1,0,0) + const Barycentric A {T(0), T(0), T(1)}; + const Barycentric B {T(0), T(1), T(0)}; + const Barycentric C {T(1), T(0), T(0)}; + + // Edge points (u, v, w = 1-u-v) following the same orientation as getEdge(0..2) + // edge0: B->C (w == 0) + // edge1: C->A (v == 0) + // edge2: A->B (u == 0) + const Barycentric P0 {s1, T(1) - s1, T(0)}; + const Barycentric P1 {T(1) - s2, T(0), s2}; + const Barycentric P2 {T(0), s3, T(1) - s3}; + + // Corner triangles (ordered to match the diagram and preserve interior-edge orientation) + restrictToSubtriangle(A, P2, P1, t1); + restrictToSubtriangle(B, P0, P2, t2); + restrictToSubtriangle(C, P1, P0, t3); + restrictToSubtriangle(P1, P0, P2, t4); + } + + /*! + * \brief Uniform 4-way "triforce" split at edge midpoints + * + * This is a convenience wrapper for `split(0.5, 0.5, 0.5, ...)`. A future optimized + * implementation can exploit the symmetry at \a s=0.5 to share intermediate nets. + * + * \param [out] t1 Subtriangle near vertex `(0,0)` + * \param [out] t2 Subtriangle near vertex `(0,1)` + * \param [out] t3 Subtriangle near vertex `(1,0)` + * \param [out] t4 Central subtriangle + * + * \pre getOrder() >= 0 + * \pre This triangle is polynomial (nonrational) + */ + void uniformSplit(BezierTriangle& t1, + BezierTriangle& t2, + BezierTriangle& t3, + BezierTriangle& t4) const + { + SLIC_ASSERT(m_ord >= 0); + SLIC_ASSERT(!isRational()); + + // Optimized uniform subdivision at the edge midpoints (s=0.5) following: + // Kenneth I. Joy, "A Uniform Subdivision Method for Triangular Bezier Patches". + // + // The method constructs a degenerate "Bezier tetrahedron" of intermediate points via + // repeated midpoint averaging in the control net, then extracts the four subpatch + // control nets from its four faces (three corner triangles + one central triangle). + // + // Notation: the paper indexes control points as P_{i,j,k} with i+j+k = n and defines + // intermediate points P_{i,j,k}^{[n1,n2,n3]} via: + // if n1>0: 1/2( P^{[n1-1,n2,n3]}_{i,j,k} + P^{[n1-1,n2,n3]}_{i-1,j+1,k} ) + // if n2>0: 1/2( P^{[n1,n2-1,n3]}_{i,j,k} + P^{[n1,n2-1,n3]}_{i,j-1,k+1} ) + // if n3>0: 1/2( P^{[n1,n2,n3-1]}_{i,j,k} + P^{[n1,n2,n3-1]}_{i+1,j,k-1} ) + // else: P_{i,j,k} + // + // The four output meshes are then: + // Q1 = { P^{[m,0,k]}_{i,0,k} : i+k=n, m=0..i } (near paper vertex P1) + // Q2 = { P^{[i,m,0]}_{i,j,0} : i+j=n, m=0..j } (near paper vertex P2) + // Q3 = { P^{[0,j,m]}_{0,j,k} : j+k=n, m=0..k } (near paper vertex P3) + // Q4 = { P^{[i,j,k]}_{i,j,k} : i+j+k=n } (central) + // + // Under this class' (u,v,w) convention, paper (P1,P2,P3) corresponds to (C,B,A), + // so Q3->t1 (near A), Q2->t2 (near B), Q1->t3 (near C), Q4->t4 (central). + + const int n = m_ord; + + t1.setOrder(n); + t2.setOrder(n); + t3.setOrder(n); + t4.setOrder(n); + t1.getWeights().resize(0); + t2.getWeights().resize(0); + t3.getWeights().resize(0); + t4.getWeights().resize(0); + + // Memoize intermediate values. For a fixed order, all indices are in [0,n], so we can + // use a mixed-radix packing with base (n+1) (safe for typical Bezier orders). + const std::uint64_t base = static_cast(n + 1); + auto make_key = [&](int i, int j, int k, int n1, int n2, int n3) -> std::uint64_t { + std::uint64_t key = static_cast(i); + key = key * base + static_cast(j); + key = key * base + static_cast(k); + key = key * base + static_cast(n1); + key = key * base + static_cast(n2); + key = key * base + static_cast(n3); + return key; + }; + + std::unordered_map memo; + memo.reserve(static_cast((n + 1) * (n + 1) * (n + 1))); + + // Recursive evaluation of P^{[n1,n2,n3]}_{i,j,k}. + std::function eval = + [&](int i, int j, int k, int n1, int n2, int n3) -> PointType { + SLIC_ASSERT(i >= 0 && j >= 0 && k >= 0); + SLIC_ASSERT(i + j + k == n); + SLIC_ASSERT(n1 >= 0 && n2 >= 0 && n3 >= 0); + SLIC_ASSERT(n1 <= n && n2 <= n && n3 <= n); + + const auto key = make_key(i, j, k, n1, n2, n3); + auto it = memo.find(key); + if(it != memo.end()) + { + return it->second; + } + + PointType out; + if(n1 > 0) + { + SLIC_ASSERT(i > 0); + const auto a = eval(i, j, k, n1 - 1, n2, n3); + const auto b = eval(i - 1, j + 1, k, n1 - 1, n2, n3); + for(int d = 0; d < NDIMS; ++d) + { + out[d] = T(0.5) * (a[d] + b[d]); + } + } + else if(n2 > 0) + { + SLIC_ASSERT(j > 0); + const auto a = eval(i, j, k, n1, n2 - 1, n3); + const auto b = eval(i, j - 1, k + 1, n1, n2 - 1, n3); + for(int d = 0; d < NDIMS; ++d) + { + out[d] = T(0.5) * (a[d] + b[d]); + } + } + else if(n3 > 0) + { + SLIC_ASSERT(k > 0); + const auto a = eval(i, j, k, n1, n2, n3 - 1); + const auto b = eval(i + 1, j, k - 1, n1, n2, n3 - 1); + for(int d = 0; d < NDIMS; ++d) + { + out[d] = T(0.5) * (a[d] + b[d]); + } + } + else + { + // Base: original control point P_{i,j,k} (k implied by n-i-j). + SLIC_ASSERT(k == n - i - j); + out = (*this)(i, j); + } + + memo.emplace(key, out); + return out; + }; + + // Q3 -> t1 : t1(i_local=j, j_local=m) = P^{[0,j,m]}_{0,j,k}, with k = n-j. + for(int i_local = 0; i_local <= n; ++i_local) + { + const int j = i_local; + const int k = n - j; + for(int j_local = 0; j_local <= n - i_local; ++j_local) + { + const int m = j_local; + t1(i_local, j_local) = eval(0, j, k, 0, j, m); + } + } + + // Q2 -> t2 : t2(i_local=i, j_local=m) = P^{[i,m,0]}_{i,j,0}, with j = n-i. + for(int i_local = 0; i_local <= n; ++i_local) + { + const int i = i_local; + const int j = n - i; + for(int j_local = 0; j_local <= n - i_local; ++j_local) + { + const int m = j_local; + t2(i_local, j_local) = eval(i, j, 0, i, m, 0); + } + } + + // Q1 -> t3 : t3(i_local=k, j_local=m) = P^{[m,0,k]}_{i,0,k}, with i = n-k. + for(int i_local = 0; i_local <= n; ++i_local) + { + const int k = i_local; + const int i = n - k; + for(int j_local = 0; j_local <= n - i_local; ++j_local) + { + const int m = j_local; + t3(i_local, j_local) = eval(i, 0, k, m, 0, k); + } + } + + // Q4 -> t4 : t4(i,j) = P^{[i,j,k]}_{i,j,k}, k = n-i-j. + for(int i = 0; i <= n; ++i) + { + for(int j = 0; j <= n - i; ++j) + { + const int k = n - i - j; + t4(i, j) = eval(i, j, k, i, j, k); + } + } + } + /*! * \brief Access a control point in the triangular control net * @@ -316,6 +1017,7 @@ class BezierTriangle return m_controlPoints[triIndex(m_ord, i, j)]; } + /// \brief Access a control point in the triangular control net const PointType& operator()(int i, int j) const { SLIC_ASSERT(i >= 0); @@ -330,13 +1032,9 @@ class BezierTriangle * \param [in] u Parameter value along the \a u axis * \param [in] v Parameter value along the \a v axis * - * \pre getOrder() >= 0 * \pre u >= 0, v >= 0, and u+v <= 1 - * + * * \return Point value S(u,v) - * - * \note In the rational case, evaluation is performed in projective space and divided - * by the evaluated weight. */ PointType evaluate(T u, T v) const { @@ -408,7 +1106,6 @@ class BezierTriangle * \param [out] Du First derivative S_u(u,v) * \param [out] Dv First derivative S_v(u,v) * - * \pre getOrder() >= 0 * \pre u >= 0, v >= 0, and u+v <= 1 */ void evaluateFirstDerivatives(T u, @@ -735,9 +1432,7 @@ class BezierTriangle } } - /*! - * \brief Computes a tangent of a Bezier triangle at (\a u, \a v) along the u axis - */ + /// \brief Computes a tangent of a Bezier triangle at (\a u, \a v) along the u axis VectorType du(T u, T v) const { PointType eval; @@ -746,9 +1441,7 @@ class BezierTriangle return Du; } - /*! - * \brief Computes a tangent of a Bezier triangle at (\a u, \a v) along the v axis - */ + /// \brief Computes a tangent of a Bezier triangle at (\a u, \a v) along the v axis VectorType dv(T u, T v) const { PointType eval; @@ -757,9 +1450,7 @@ class BezierTriangle return Dv; } - /*! - * \brief Computes the second derivative of a Bezier triangle at (\a u, \a v) along the u axis - */ + /// \brief Computes the second derivative of a Bezier triangle at (\a u, \a v) along the u axis VectorType dudu(T u, T v) const { PointType eval; @@ -768,9 +1459,7 @@ class BezierTriangle return DuDu; } - /*! - * \brief Computes the second derivative of a Bezier triangle at (\a u, \a v) along the v axis - */ + /// \brief Computes the second derivative of a Bezier triangle at (\a u, \a v) along the v axis VectorType dvdv(T u, T v) const { PointType eval; @@ -779,9 +1468,7 @@ class BezierTriangle return DvDv; } - /*! - * \brief Computes the mixed second derivative of a Bezier triangle at (\a u, \a v) - */ + /// \brief Computes the mixed second derivative of a Bezier triangle at (\a u, \a v) VectorType dudv(T u, T v) const { PointType eval; @@ -790,7 +1477,7 @@ class BezierTriangle return DuDv; } - /// Convenience alias for S_vu(u,v), which equals S_uv(u,v) for polynomial triangles + /// \brief Convenience alias for S_vu(u,v), which equals S_uv(u,v) for polynomial triangles VectorType dvdu(T u, T v) const { return dudv(u, v); } /*! @@ -866,8 +1553,7 @@ class BezierTriangle } private: - /// Check that the weights used are positive, and - /// that there is one for each control node + /// \brief Check that each weight is positive and the size matches control nodes bool is_valid_rational() const { if(!isRational()) @@ -891,6 +1577,7 @@ class BezierTriangle return true; } + /// \brief For a rational triangle, fill triangle objects with weighted control points and weights void fill_projective_triangles(BezierTriangle& projective, BezierTriangle& weights) const { From 0c01bf70b37beaf199f3bd7202e421e3614064a1 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Thu, 21 May 2026 09:43:03 -0700 Subject: [PATCH 330/986] work --- src/axom/primal/geometry/BezierPatch.hpp | 7 +- src/axom/primal/geometry/BezierTriangle.hpp | 219 ++---------- .../primal/tests/primal_bezier_triangle.cpp | 332 ++++++++++++++++++ 3 files changed, 360 insertions(+), 198 deletions(-) diff --git a/src/axom/primal/geometry/BezierPatch.hpp b/src/axom/primal/geometry/BezierPatch.hpp index 5fe088e40d..6c62741e6e 100644 --- a/src/axom/primal/geometry/BezierPatch.hpp +++ b/src/axom/primal/geometry/BezierPatch.hpp @@ -2049,10 +2049,13 @@ class BezierPatch return true; } - void fill_projective_patches(BezierPatches& projective, BezierPatches& weights) const + void fill_projective_patches(BezierPatch& projective, BezierPatch& weights) const { SLIC_ASSERT(isRational()); - SLIC_ASSERT(is_valid_rational()); + SLIC_ASSERT(isValidRational()); + + const int ord_u = getOrder_u(); + const int ord_v = getOrder_v(); for(int p = 0; p <= ord_u; ++p) { diff --git a/src/axom/primal/geometry/BezierTriangle.hpp b/src/axom/primal/geometry/BezierTriangle.hpp index 0566c841aa..6fefe8587b 100644 --- a/src/axom/primal/geometry/BezierTriangle.hpp +++ b/src/axom/primal/geometry/BezierTriangle.hpp @@ -27,8 +27,6 @@ #include #include -#include -#include namespace axom { @@ -479,36 +477,41 @@ class BezierTriangle const int degPrev = n - (p - 1); std::vector> currNets(triSize(p)); - for(int a = 0; a <= p; ++a) + for(int fixedVcCount = 0; fixedVcCount <= p; ++fixedVcCount) { - for(int b = 0; b <= p - a; ++b) + for(int fixedVbCount = 0; fixedVbCount <= p - fixedVcCount; ++fixedVbCount) { - const int idx = triIndex(p, a, b); - const int c = p - a - b; - - // Choose a unique predecessor (and thus a unique evaluation order) to avoid - // redundant reductions; the blossom symmetry ensures the final values are - // order-independent. - const Barycentric* Q = nullptr; + const int fixedVaCount = p - fixedVcCount - fixedVbCount; + const int idx = triIndex(p, fixedVcCount, fixedVbCount); + + // Each net in this "tetrahedral" construction corresponds to a blossom value + // b(Vc^fixedVcCount, Vb^fixedVbCount, Va^fixedVaCount) at degree (n-p). + // + // There are multiple equivalent ways to compute each blossom value (blossom is + // symmetric in its arguments). We pick one deterministic predecessor to keep + // the recurrence simple and compute each net exactly once. + // + // Convention: consume Va arguments first, then Vb, then Vc. int predIdx = -1; + const Barycentric& splitPoint = + (fixedVaCount > 0) ? Va : (fixedVbCount > 0) ? Vb : Vc; - if(c > 0) + if(fixedVaCount > 0) { - predIdx = triIndex(p - 1, a, b); - Q = &Va; + predIdx = triIndex(p - 1, fixedVcCount, fixedVbCount); } - else if(b > 0) + else if(fixedVbCount > 0) { - predIdx = triIndex(p - 1, a, b - 1); - Q = &Vb; + predIdx = triIndex(p - 1, fixedVcCount, fixedVbCount - 1); } else { - predIdx = triIndex(p - 1, a - 1, b); - Q = &Vc; + SLIC_ASSERT(fixedVcCount > 0); + predIdx = triIndex(p - 1, fixedVcCount - 1, fixedVbCount); } - currNets[idx] = reduce_once(prevNets[predIdx], degPrev, *Q); + SLIC_ASSERT(predIdx >= 0); + currNets[idx] = reduce_once(prevNets[predIdx], degPrev, splitPoint); } } @@ -825,182 +828,6 @@ class BezierTriangle restrictToSubtriangle(P1, P0, P2, t4); } - /*! - * \brief Uniform 4-way "triforce" split at edge midpoints - * - * This is a convenience wrapper for `split(0.5, 0.5, 0.5, ...)`. A future optimized - * implementation can exploit the symmetry at \a s=0.5 to share intermediate nets. - * - * \param [out] t1 Subtriangle near vertex `(0,0)` - * \param [out] t2 Subtriangle near vertex `(0,1)` - * \param [out] t3 Subtriangle near vertex `(1,0)` - * \param [out] t4 Central subtriangle - * - * \pre getOrder() >= 0 - * \pre This triangle is polynomial (nonrational) - */ - void uniformSplit(BezierTriangle& t1, - BezierTriangle& t2, - BezierTriangle& t3, - BezierTriangle& t4) const - { - SLIC_ASSERT(m_ord >= 0); - SLIC_ASSERT(!isRational()); - - // Optimized uniform subdivision at the edge midpoints (s=0.5) following: - // Kenneth I. Joy, "A Uniform Subdivision Method for Triangular Bezier Patches". - // - // The method constructs a degenerate "Bezier tetrahedron" of intermediate points via - // repeated midpoint averaging in the control net, then extracts the four subpatch - // control nets from its four faces (three corner triangles + one central triangle). - // - // Notation: the paper indexes control points as P_{i,j,k} with i+j+k = n and defines - // intermediate points P_{i,j,k}^{[n1,n2,n3]} via: - // if n1>0: 1/2( P^{[n1-1,n2,n3]}_{i,j,k} + P^{[n1-1,n2,n3]}_{i-1,j+1,k} ) - // if n2>0: 1/2( P^{[n1,n2-1,n3]}_{i,j,k} + P^{[n1,n2-1,n3]}_{i,j-1,k+1} ) - // if n3>0: 1/2( P^{[n1,n2,n3-1]}_{i,j,k} + P^{[n1,n2,n3-1]}_{i+1,j,k-1} ) - // else: P_{i,j,k} - // - // The four output meshes are then: - // Q1 = { P^{[m,0,k]}_{i,0,k} : i+k=n, m=0..i } (near paper vertex P1) - // Q2 = { P^{[i,m,0]}_{i,j,0} : i+j=n, m=0..j } (near paper vertex P2) - // Q3 = { P^{[0,j,m]}_{0,j,k} : j+k=n, m=0..k } (near paper vertex P3) - // Q4 = { P^{[i,j,k]}_{i,j,k} : i+j+k=n } (central) - // - // Under this class' (u,v,w) convention, paper (P1,P2,P3) corresponds to (C,B,A), - // so Q3->t1 (near A), Q2->t2 (near B), Q1->t3 (near C), Q4->t4 (central). - - const int n = m_ord; - - t1.setOrder(n); - t2.setOrder(n); - t3.setOrder(n); - t4.setOrder(n); - t1.getWeights().resize(0); - t2.getWeights().resize(0); - t3.getWeights().resize(0); - t4.getWeights().resize(0); - - // Memoize intermediate values. For a fixed order, all indices are in [0,n], so we can - // use a mixed-radix packing with base (n+1) (safe for typical Bezier orders). - const std::uint64_t base = static_cast(n + 1); - auto make_key = [&](int i, int j, int k, int n1, int n2, int n3) -> std::uint64_t { - std::uint64_t key = static_cast(i); - key = key * base + static_cast(j); - key = key * base + static_cast(k); - key = key * base + static_cast(n1); - key = key * base + static_cast(n2); - key = key * base + static_cast(n3); - return key; - }; - - std::unordered_map memo; - memo.reserve(static_cast((n + 1) * (n + 1) * (n + 1))); - - // Recursive evaluation of P^{[n1,n2,n3]}_{i,j,k}. - std::function eval = - [&](int i, int j, int k, int n1, int n2, int n3) -> PointType { - SLIC_ASSERT(i >= 0 && j >= 0 && k >= 0); - SLIC_ASSERT(i + j + k == n); - SLIC_ASSERT(n1 >= 0 && n2 >= 0 && n3 >= 0); - SLIC_ASSERT(n1 <= n && n2 <= n && n3 <= n); - - const auto key = make_key(i, j, k, n1, n2, n3); - auto it = memo.find(key); - if(it != memo.end()) - { - return it->second; - } - - PointType out; - if(n1 > 0) - { - SLIC_ASSERT(i > 0); - const auto a = eval(i, j, k, n1 - 1, n2, n3); - const auto b = eval(i - 1, j + 1, k, n1 - 1, n2, n3); - for(int d = 0; d < NDIMS; ++d) - { - out[d] = T(0.5) * (a[d] + b[d]); - } - } - else if(n2 > 0) - { - SLIC_ASSERT(j > 0); - const auto a = eval(i, j, k, n1, n2 - 1, n3); - const auto b = eval(i, j - 1, k + 1, n1, n2 - 1, n3); - for(int d = 0; d < NDIMS; ++d) - { - out[d] = T(0.5) * (a[d] + b[d]); - } - } - else if(n3 > 0) - { - SLIC_ASSERT(k > 0); - const auto a = eval(i, j, k, n1, n2, n3 - 1); - const auto b = eval(i + 1, j, k - 1, n1, n2, n3 - 1); - for(int d = 0; d < NDIMS; ++d) - { - out[d] = T(0.5) * (a[d] + b[d]); - } - } - else - { - // Base: original control point P_{i,j,k} (k implied by n-i-j). - SLIC_ASSERT(k == n - i - j); - out = (*this)(i, j); - } - - memo.emplace(key, out); - return out; - }; - - // Q3 -> t1 : t1(i_local=j, j_local=m) = P^{[0,j,m]}_{0,j,k}, with k = n-j. - for(int i_local = 0; i_local <= n; ++i_local) - { - const int j = i_local; - const int k = n - j; - for(int j_local = 0; j_local <= n - i_local; ++j_local) - { - const int m = j_local; - t1(i_local, j_local) = eval(0, j, k, 0, j, m); - } - } - - // Q2 -> t2 : t2(i_local=i, j_local=m) = P^{[i,m,0]}_{i,j,0}, with j = n-i. - for(int i_local = 0; i_local <= n; ++i_local) - { - const int i = i_local; - const int j = n - i; - for(int j_local = 0; j_local <= n - i_local; ++j_local) - { - const int m = j_local; - t2(i_local, j_local) = eval(i, j, 0, i, m, 0); - } - } - - // Q1 -> t3 : t3(i_local=k, j_local=m) = P^{[m,0,k]}_{i,0,k}, with i = n-k. - for(int i_local = 0; i_local <= n; ++i_local) - { - const int k = i_local; - const int i = n - k; - for(int j_local = 0; j_local <= n - i_local; ++j_local) - { - const int m = j_local; - t3(i_local, j_local) = eval(i, 0, k, m, 0, k); - } - } - - // Q4 -> t4 : t4(i,j) = P^{[i,j,k]}_{i,j,k}, k = n-i-j. - for(int i = 0; i <= n; ++i) - { - for(int j = 0; j <= n - i; ++j) - { - const int k = n - i - j; - t4(i, j) = eval(i, j, k, i, j, k); - } - } - } - /*! * \brief Access a control point in the triangular control net * diff --git a/src/axom/primal/tests/primal_bezier_triangle.cpp b/src/axom/primal/tests/primal_bezier_triangle.cpp index 9c755f8d8b..996531cb5b 100644 --- a/src/axom/primal/tests/primal_bezier_triangle.cpp +++ b/src/axom/primal/tests/primal_bezier_triangle.cpp @@ -351,3 +351,335 @@ TEST(primal_beziertriangle, rational_triangles) check_at(0.2, 0.3); check_at(0.6, 0.1); } + +//------------------------------------------------------------------------------ +TEST(primal_beziertriangle, edges) +{ + constexpr int DIM = 3; + using CoordType = double; + using BTri = primal::BezierTriangle; + using PointType = BTri::PointType; + + constexpr int ord = 3; + BTri poly(ord); + BTri rat(ord); + rat.getWeights().resize(rat.getControlPoints().size()); + + for(int i = 0; i <= ord; ++i) + { + for(int j = 0; j <= ord - i; ++j) + { + const int idx = BTri::triIndex(ord, i, j); + const auto ii = static_cast(i); + const auto jj = static_cast(j); + poly(i, j) = PointType {ii, jj, 100. * ii + jj}; + rat(i, j) = poly(i, j); + rat.getWeights()[idx] = 0.25 + idx; + } + } + + // edge 0: u + v = 1, from (0,1) to (ord,0) + { + const auto e0 = poly.getEdge(0); + EXPECT_EQ(ord, e0.getOrder()); + EXPECT_FALSE(e0.isRational()); + EXPECT_EQ(ord + 1, e0.getNumControlPoints()); + EXPECT_EQ(poly(0, ord), e0.getInitPoint()); + EXPECT_EQ(poly(ord, 0), e0.getEndPoint()); + } + + // edge 1: v = 0, from (ord,0) to (0,0) + { + const auto e1 = poly.getEdge(1); + EXPECT_EQ(ord, e1.getOrder()); + EXPECT_FALSE(e1.isRational()); + EXPECT_EQ(ord + 1, e1.getNumControlPoints()); + EXPECT_EQ(poly(ord, 0), e1.getInitPoint()); + EXPECT_EQ(poly(0, 0), e1.getEndPoint()); + } + + // edge 2: u = 0, from (0,0) to (0,ord) + { + const auto e2 = poly.getEdge(2); + EXPECT_EQ(ord, e2.getOrder()); + EXPECT_FALSE(e2.isRational()); + EXPECT_EQ(ord + 1, e2.getNumControlPoints()); + EXPECT_EQ(poly(0, 0), e2.getInitPoint()); + EXPECT_EQ(poly(0, ord), e2.getEndPoint()); + } + + // weight propagation to rational edge curves + { + const auto e0 = rat.getEdge(0); + EXPECT_TRUE(e0.isRational()); + EXPECT_EQ(ord, e0.getOrder()); + ASSERT_EQ(ord + 1, e0.getWeights().size()); + + const auto e1 = rat.getEdge(1); + EXPECT_TRUE(e1.isRational()); + EXPECT_EQ(ord, e1.getOrder()); + ASSERT_EQ(ord + 1, e1.getWeights().size()); + + const auto e2 = rat.getEdge(2); + EXPECT_TRUE(e2.isRational()); + EXPECT_EQ(ord, e2.getOrder()); + ASSERT_EQ(ord + 1, e2.getWeights().size()); + } +} + +//------------------------------------------------------------------------------ +TEST(primal_beziertriangle, split_interior_polynomial) +{ + constexpr int DIM = 3; + using CoordType = double; + using BTri = primal::BezierTriangle; + using PointType = BTri::PointType; + + constexpr int ord = 3; + BTri tri(ord); + + for(int i = 0; i <= ord; ++i) + { + for(int j = 0; j <= ord - i; ++j) + { + const auto ii = static_cast(i); + const auto jj = static_cast(j); + tri(i, j) = PointType {ii, jj, 10.0 * ii - 3.0 * jj}; + } + } + + const CoordType u = 0.2; + const CoordType v = 0.3; + + BTri t0, t1, t2; + tri.split(u, v, t0, t1, t2); + + EXPECT_EQ(ord, t0.getOrder()); + EXPECT_EQ(ord, t1.getOrder()); + EXPECT_EQ(ord, t2.getOrder()); + EXPECT_FALSE(t0.isRational()); + EXPECT_FALSE(t1.isRational()); + EXPECT_FALSE(t2.isRational()); + + // Vertex mapping checks + const auto pA = tri.evaluate(0.0, 0.0); + const auto pB = tri.evaluate(0.0, 1.0); + const auto pC = tri.evaluate(1.0, 0.0); + const auto pQ = tri.evaluate(u, v); + + for(int i = 0; i < DIM; ++i) + { + // t0 -> (B, C, Q) + EXPECT_NEAR(pB[i], t0.evaluate(0.0, 0.0)[i], 1e-10); + EXPECT_NEAR(pC[i], t0.evaluate(0.0, 1.0)[i], 1e-10); + EXPECT_NEAR(pQ[i], t0.evaluate(1.0, 0.0)[i], 1e-10); + + // t1 -> (C, A, Q) + EXPECT_NEAR(pC[i], t1.evaluate(0.0, 0.0)[i], 1e-10); + EXPECT_NEAR(pA[i], t1.evaluate(0.0, 1.0)[i], 1e-10); + EXPECT_NEAR(pQ[i], t1.evaluate(1.0, 0.0)[i], 1e-10); + + // t2 -> (A, B, Q) + EXPECT_NEAR(pA[i], t2.evaluate(0.0, 0.0)[i], 1e-10); + EXPECT_NEAR(pB[i], t2.evaluate(0.0, 1.0)[i], 1e-10); + EXPECT_NEAR(pQ[i], t2.evaluate(1.0, 0.0)[i], 1e-10); + } + + // Interior point checks via affine parameter mapping + const CoordType s = 0.2; + const CoordType t = 0.1; + + // t0 is over vertices (0,1), (1,0), (u,v) + { + const CoordType u2 = s * u + t; + const CoordType v2 = 1.0 + s * (v - 1.0) - t; + const auto expected = tri.evaluate(u2, v2); + const auto actual = t0.evaluate(s, t); + for(int d = 0; d < DIM; ++d) + { + EXPECT_NEAR(expected[d], actual[d], 1e-12); + } + } + + // t1 is over vertices (1,0), (0,0), (u,v) + { + const CoordType u3 = 1.0 + s * (u - 1.0) - t; + const CoordType v3 = s * v; + const auto expected = tri.evaluate(u3, v3); + const auto actual = t1.evaluate(s, t); + for(int d = 0; d < DIM; ++d) + { + EXPECT_NEAR(expected[d], actual[d], 1e-12); + } + } + + // t2 is over vertices (0,0), (0,1), (u,v) + { + const CoordType u1 = s * u; + const CoordType v1 = t + s * v; + const auto expected = tri.evaluate(u1, v1); + const auto actual = t2.evaluate(s, t); + for(int d = 0; d < DIM; ++d) + { + EXPECT_NEAR(expected[d], actual[d], 1e-12); + } + } +} + +//------------------------------------------------------------------------------ +TEST(primal_beziertriangle, split_edge_polynomial) +{ + constexpr int DIM = 3; + using CoordType = double; + using BTri = primal::BezierTriangle; + using PointType = BTri::PointType; + + constexpr int ord = 3; + BTri tri(ord); + + for(int i = 0; i <= ord; ++i) + { + for(int j = 0; j <= ord - i; ++j) + { + const auto ii = static_cast(i); + const auto jj = static_cast(j); + tri(i, j) = PointType {ii, jj, 10.0 * ii - 3.0 * jj}; + } + } + + const auto pA = tri.evaluate(0.0, 0.0); + const auto pB = tri.evaluate(0.0, 1.0); + const auto pC = tri.evaluate(1.0, 0.0); + + axom::Array vertices {pA, pB, pC}; + + const CoordType s = 0.35; + + BTri t0, t1; + + for(int i = 0; i < 3; ++i) + { + tri.split(i, s, t0, t1); + + EXPECT_EQ(ord, t0.getOrder()); + EXPECT_EQ(ord, t1.getOrder()); + EXPECT_FALSE(t0.isRational()); + EXPECT_FALSE(t1.isRational()); + + // Vertex mapping checks + const auto pQ = tri.getEdge(i).evaluate(s); + + for(int N = 0; N < DIM; ++N) + { + EXPECT_NEAR(vertices[(i + 0) % 3][N], t0.evaluate(0.0, 0.0)[N], 1e-10); + EXPECT_NEAR(vertices[(i + 1) % 3][N], t0.evaluate(0.0, 1.0)[N], 1e-10); + + EXPECT_NEAR(vertices[(i + 2) % 3][N], t1.evaluate(0.0, 0.0)[N], 1e-10); + EXPECT_NEAR(vertices[(i + 0) % 3][N], t1.evaluate(0.0, 1.0)[N], 1e-10); + + // Subtriangles agree at the last vertex + EXPECT_NEAR(pQ[N], t0.evaluate(1.0, 0.0)[N], 1e-10); + EXPECT_NEAR(pQ[N], t1.evaluate(1.0, 0.0)[N], 1e-10); + } + } +} + +//------------------------------------------------------------------------------ +TEST(primal_beziertriangle, split_triforce_polynomial) +{ + constexpr int DIM = 3; + using CoordType = double; + using BTri = primal::BezierTriangle; + using PointType = BTri::PointType; + + constexpr int ord = 3; + BTri tri(ord); + + for(int i = 0; i <= ord; ++i) + { + for(int j = 0; j <= ord - i; ++j) + { + const auto ii = static_cast(i); + const auto jj = static_cast(j); + tri(i, j) = PointType {ii, jj, 10.0 * ii - 3.0 * jj}; + } + } + + const CoordType s0 = 0.25; // edge0: B->C + const CoordType s1 = 0.6; // edge1: C->A + const CoordType s2 = 0.4; // edge2: A->B + + BTri t1, t2, t3, t4; + tri.split(s0, s1, s2, t1, t2, t3, t4); + + EXPECT_EQ(ord, t1.getOrder()); + EXPECT_EQ(ord, t2.getOrder()); + EXPECT_EQ(ord, t3.getOrder()); + EXPECT_EQ(ord, t4.getOrder()); + EXPECT_FALSE(t1.isRational()); + EXPECT_FALSE(t2.isRational()); + EXPECT_FALSE(t3.isRational()); + EXPECT_FALSE(t4.isRational()); + + const auto pA = tri.evaluate(0.0, 0.0); + const auto pB = tri.evaluate(0.0, 1.0); + const auto pC = tri.evaluate(1.0, 0.0); + + const auto pP0 = tri.getEdge(0).evaluate(s0); + const auto pP1 = tri.getEdge(1).evaluate(s1); + const auto pP2 = tri.getEdge(2).evaluate(s2); + + for(int d = 0; d < DIM; ++d) + { + // t1 = Tri(A, P2, P1) + EXPECT_NEAR(pA[d], t1.evaluate(0.0, 0.0)[d], 1e-10); + EXPECT_NEAR(pP2[d], t1.evaluate(0.0, 1.0)[d], 1e-10); + EXPECT_NEAR(pP1[d], t1.evaluate(1.0, 0.0)[d], 1e-10); + + // t2 = Tri(B, P0, P2) + EXPECT_NEAR(pB[d], t2.evaluate(0.0, 0.0)[d], 1e-10); + EXPECT_NEAR(pP0[d], t2.evaluate(0.0, 1.0)[d], 1e-10); + EXPECT_NEAR(pP2[d], t2.evaluate(1.0, 0.0)[d], 1e-10); + + // t3 = Tri(C, P1, P0) + EXPECT_NEAR(pC[d], t3.evaluate(0.0, 0.0)[d], 1e-10); + EXPECT_NEAR(pP1[d], t3.evaluate(0.0, 1.0)[d], 1e-10); + EXPECT_NEAR(pP0[d], t3.evaluate(1.0, 0.0)[d], 1e-10); + + // t4 = Tri(P1, P0, P2) + EXPECT_NEAR(pP1[d], t4.evaluate(0.0, 0.0)[d], 1e-10); + EXPECT_NEAR(pP0[d], t4.evaluate(0.0, 1.0)[d], 1e-10); + EXPECT_NEAR(pP2[d], t4.evaluate(1.0, 0.0)[d], 1e-10); + } + + // Shared interior edges agree (same geometry and orientation for the chosen outputs) + const CoordType s = 0.37; + const auto eP0P2_from_t2 = t2.getEdge(0).evaluate(s); // P0 -> P2 + const auto eP0P2_from_t4 = t4.getEdge(0).evaluate(s); // P0 -> P2 + + const auto eP2P1_from_t1 = t1.getEdge(0).evaluate(s); // P2 -> P1 + const auto eP2P1_from_t4 = t4.getEdge(1).evaluate(s); // P2 -> P1 + + const auto eP1P0_from_t3 = t3.getEdge(0).evaluate(s); // P1 -> P0 + const auto eP1P0_from_t4 = t4.getEdge(2).evaluate(s); // P1 -> P0 + + for(int d = 0; d < DIM; ++d) + { + EXPECT_NEAR(eP0P2_from_t2[d], eP0P2_from_t4[d], 1e-12); + EXPECT_NEAR(eP2P1_from_t1[d], eP2P1_from_t4[d], 1e-12); + EXPECT_NEAR(eP1P0_from_t3[d], eP1P0_from_t4[d], 1e-12); + } +} + +int main(int argc, char* argv[]) +{ + int result = 0; + + ::testing::InitGoogleTest(&argc, argv); + + axom::slic::SimpleLogger logger; + + result = RUN_ALL_TESTS(); + + return result; +} From c67931a146714b58f9a8006ed0603da139ead97a Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Mon, 25 May 2026 21:14:45 -0700 Subject: [PATCH 331/986] add a bunch of subdivision methods --- src/axom/primal/geometry/BezierTriangle.hpp | 816 +++++++++++++----- .../primal/tests/primal_bezier_triangle.cpp | 399 ++++++++- 2 files changed, 975 insertions(+), 240 deletions(-) diff --git a/src/axom/primal/geometry/BezierTriangle.hpp b/src/axom/primal/geometry/BezierTriangle.hpp index 6fefe8587b..bf7cf7d845 100644 --- a/src/axom/primal/geometry/BezierTriangle.hpp +++ b/src/axom/primal/geometry/BezierTriangle.hpp @@ -18,14 +18,9 @@ #include "axom/primal/geometry/Point.hpp" #include "axom/primal/geometry/Vector.hpp" -#include "axom/primal/geometry/Segment.hpp" #include "axom/primal/geometry/BezierCurve.hpp" #include "axom/primal/geometry/BoundingBox.hpp" #include "axom/primal/geometry/OrientedBoundingBox.hpp" - -#include "axom/primal/operators/squared_distance.hpp" - -#include #include namespace axom @@ -47,7 +42,7 @@ std::ostream& operator<<(std::ostream& os, const BezierTriangle& bTri) * \tparam T the coordinate type, e.g., double, float, etc. * * A Bezier triangle of order \a N has \f$ (N+1)(N+2)/2 \f$ control points. - * It is parametrized over the domain \f$ u \ge 0, v \ge 0, u+v \le 1 \f$. + * It is parametrized over the domain \f$ u0 \ge 0, v0 \ge 0, u0+v0 \le 1 \f$. * * Control points are indexed using integer coordinates \f$ (i,j) \f$ with * \f$ 0 \le i \le N \f$ and \f$ 0 \le j \le N-i \f$ and accessed via `operator()(i,j)`. @@ -57,11 +52,12 @@ std::ostream& operator<<(std::ostream& os, const BezierTriangle& bTri) * Rational triangles are represented by an additional set of positive weights. * Polynomial (nonrational) Bezier triangles are identified by an empty weights array. * - * \note This triangle uses permuted barycentric coordinates for evaluation such that, when + * \note This triangle uses permuted barycentric coordinates (u0, v0) for evaluation such that, when * `getOrder()==1`, the parameter values correspond to the triangle vertices: * - `evaluate(0,0) == (*this)(0,0)` * - `evaluate(0,1) == (*this)(0,1)` * - `evaluate(1,0) == (*this)(1,0)` + * These are mapped to standard Barycentric coordinates through (u0, v0) = {1 - u0 - v0, v0, u0} */ template class BezierTriangle @@ -250,6 +246,43 @@ class BezierTriangle /// \brief Returns a reference to the triangle's weights const WeightsVec& getWeights() const { return m_weights; } + /*! + * \brief Get a specific weight from a rational Bezier triangle + * + * \param [in] i First control net index + * \param [in] j Second control net index + * \pre Requires that the triangle be rational + */ + const T& getWeight(int i, int j) const + { + SLIC_ASSERT(isRational()); + SLIC_ASSERT(m_weights.size() == m_controlPoints.size()); + SLIC_ASSERT(i >= 0); + SLIC_ASSERT(j >= 0); + SLIC_ASSERT(i + j <= m_ord); + return m_weights[triIndex(m_ord, i, j)]; + } + + /*! + * \brief Set the weight at a specific index for a rational Bezier triangle + * + * \param [in] i First control net index + * \param [in] j Second control net index + * \param [in] weight The updated value of the weight + * \pre Requires that the triangle be rational + * \pre Requires that the weight be positive + */ + void setWeight(int i, int j, T weight) + { + SLIC_ASSERT(isRational()); + SLIC_ASSERT(m_weights.size() == m_controlPoints.size()); + SLIC_ASSERT(weight > T(0)); + SLIC_ASSERT(i >= 0); + SLIC_ASSERT(j >= 0); + SLIC_ASSERT(i + j <= m_ord); + m_weights[triIndex(m_ord, i, j)] = weight; + } + /*! * \brief Returns an axis-aligned bounding box containing the Bezier triangle * @@ -273,8 +306,8 @@ class BezierTriangle /*! * \brief Sets the order of the Bezier triangle and resizes internal storage * - * \param [in] ord The polynomial order - * + * \param [in] ord The polynomial order + * * \pre ord must be greater than or equal to -1 * * \note This function only resizes the control point and weight arrays and does not @@ -302,6 +335,32 @@ class BezierTriangle */ int getOrder() const { return m_ord; } + /*! + * \brief Return one vertex from the Bezier triangle + * + * \param [in] vertIdx Index of the requested vertex + * + * The vertices are returned in counter-clockwise order with respect to + * the first control point (*this)(0, 0) == evaluate(0, 0) + * + * \return The PointType object at the vertex + */ + PointType getVertex(int vertIdx) const + { + SLIC_ASSERT(m_ord >= 0); + SLIC_ASSERT(vertIdx >= 0 && vertIdx < 3); + + switch(vertIdx) + { + case 0: + return (*this)(0, 0); + case 1: + return (*this)(0, m_ord); + default: + return (*this)(m_ord, 0); + } + } + /*! * \brief Returns one of the boundary edges of the Bezier triangle * @@ -313,9 +372,6 @@ class BezierTriangle * - \a edgeIdx = 1: u = 0 from `evaluate(0,1)` to `evaluate(0,0)` * - \a edgeIdx = 2: v = 0 from `evaluate(0,0)` to `evaluate(1,0)` * - * For rational triangles, the returned curve is rational and uses the subset of - * weights corresponding to the boundary control points. - * * \return A Bezier curve representing the requested edge. */ BezierCurveType getEdge(int edgeIdx) const @@ -389,34 +445,7 @@ class BezierTriangle } /*! - * \brief Restricts this polynomial Bezier triangle to a subtriangle of the parameter domain - * - * The subtriangle is defined by three barycentric coordinates \a (u,v,w) (with \a w = 1-u-v) - * in the parameter domain of this triangle. - * - * The returned triangle is parametrized over the reference domain and maps its vertices as: - * - local `(0,0)` -> \a Va - * - local `(0,1)` -> \a Vb - * - local `(1,0)` -> \a Vc - * - * \param [in] Va Barycentric coordinates of the first subtriangle vertex `(u,v,w)` - * \param [in] Vb Barycentric coordinates of the second subtriangle vertex `(u,v,w)` - * \param [in] Vc Barycentric coordinates of the third subtriangle vertex `(u,v,w)` - * - * \pre getOrder() >= 0 - * \pre This triangle is polynomial (nonrational) - */ - BezierTriangle restrictToSubtriangle(const Point& Va, - const Point& Vb, - const Point& Vc) const - { - BezierTriangle out; - restrictToSubtriangle(Va, Vb, Vc, out); - return out; - } - - /*! - * \brief Restricts this polynomial Bezier triangle to a subtriangle of the parameter domain + * \brief Restricts this Bezier triangle to a subtriangle of the parameter domain * * See overload returning a `BezierTriangle` for the vertex mapping convention. * @@ -426,98 +455,118 @@ class BezierTriangle * \param [out] out Output restricted Bezier triangle * * \pre getOrder() >= 0 - * \pre This triangle is polynomial (nonrational) + * + * \note The barycentric inputs \a Va, \a Vb, \a Vc are \a (u,v,w) triplets in the same + * parameter-coordinate convention used internally by `evaluate(u,v)` (with \a w = 1-u-v). */ void restrictToSubtriangle(const Point& Va, const Point& Vb, const Point& Vc, BezierTriangle& out) const { + using TriangularArray = axom::Array; using Barycentric = Point; + SLIC_ASSERT(m_ord >= 0); - SLIC_ASSERT(!isRational()); + if(isRational()) + { + // Rational case: restrict in projective space, then convert back. + BezierTriangle projective(m_ord); + BezierTriangle weights(m_ord); + fill_projective_triangles(projective, weights); + + BezierTriangle proj_out(m_ord); + BezierTriangle w_out(m_ord); + + projective.restrictToSubtriangle(Va, Vb, Vc, proj_out); + weights.restrictToSubtriangle(Va, Vb, Vc, w_out); + + set_from_projective_triangles(proj_out, w_out, out); + return; + } const int n = m_ord; - auto reduce_once = [&](const axom::Array& prev, int deg, const Barycentric& Q) { - const int newDeg = deg - 1; - axom::Array next; - next.resize(triSize(newDeg)); + // Given a degree `deg` control net in `prev`, perform one reduction step so that + // `next` becomes the degree `deg-1` control net at barycentric point `Q`. + auto reduce_once = + [&](const TriangularArray& prev, TriangularArray& next, int deg, const Barycentric& Q) { + const int newDeg = deg - 1; - for(int ii = 0; ii <= newDeg; ++ii) - { - for(int jj = 0; jj <= newDeg - ii; ++jj) + for(int ii = 0; ii <= newDeg; ++ii) { - const auto& A0 = prev[triIndex(deg, ii, jj)]; - const auto& B0 = prev[triIndex(deg, ii, jj + 1)]; - const auto& C0 = prev[triIndex(deg, ii + 1, jj)]; - - PointType val; - for(int N = 0; N < NDIMS; ++N) + for(int jj = 0; jj <= newDeg - ii; ++jj) { - // Barycentric coordinates permuted to match convention in evaluate() - val[N] = Q[2] * A0[N] + Q[1] * B0[N] + Q[0] * C0[N]; + const auto& A0 = prev[triIndex(deg, ii, jj)]; + const auto& B0 = prev[triIndex(deg, ii, jj + 1)]; + const auto& C0 = prev[triIndex(deg, ii + 1, jj)]; + + PointType val; + for(int N = 0; N < NDIMS; ++N) + { + // Barycentric coordinates permuted to match convention in evaluate() + val[N] = Q[2] * A0[N] + Q[1] * B0[N] + Q[0] * C0[N]; + } + next[triIndex(newDeg, ii, jj)] = val; } - next[triIndex(newDeg, ii, jj)] = val; } - } - - return next; - }; - - // Tetrahedral family of intermediate nets, indexed by the counts of (Vc, Vb, Va) - // fixed in the blossom. For p fixed arguments, there are triSize(p) nets, each of - // degree (n-p). At p==n, each net is degree 0 and corresponds to a restricted - // control point b(Vc^i, Vb^j, Va^(n-i-j)). - std::vector> prevNets(1); + }; + + // The restricted control net over (Va,Vb,Vc) is given by blossom values: + // out(i,j) = b(Vc^i, Vb^j, Va^(n-i-j)) for 0<=i<=n and 0<=j<=n-i + // + // At layer p, we maintain all intermediate nets with exactly p fixed arguments, + // i.e. b(Vc^i0, Vb^j0, Vc^k0) with i0 + j0 + k0 = p, and use them to compute nets + // for blossom values with one more fixed argument until p == n + axom::Array prevNets(1); prevNets[0] = m_controlPoints; for(int p = 1; p <= n; ++p) { const int degPrev = n - (p - 1); - std::vector> currNets(triSize(p)); + const int degCurr = degPrev - 1; + + // Allocate triangular arrays for each layer-p net. + axom::Array currNets(triSize(p)); + for(auto& net : currNets) + { + net.resize(triSize(degCurr)); + } - for(int fixedVcCount = 0; fixedVcCount <= p; ++fixedVcCount) + // Iterate over count triples with i0 + j0 + k0 == p. + for(int i0 = 0; i0 <= p; ++i0) { - for(int fixedVbCount = 0; fixedVbCount <= p - fixedVcCount; ++fixedVbCount) + for(int j0 = 0; j0 <= p - i0; ++j0) { - const int fixedVaCount = p - fixedVcCount - fixedVbCount; - const int idx = triIndex(p, fixedVcCount, fixedVbCount); - - // Each net in this "tetrahedral" construction corresponds to a blossom value - // b(Vc^fixedVcCount, Vb^fixedVbCount, Va^fixedVaCount) at degree (n-p). - // - // There are multiple equivalent ways to compute each blossom value (blossom is - // symmetric in its arguments). We pick one deterministic predecessor to keep - // the recurrence simple and compute each net exactly once. - // - // Convention: consume Va arguments first, then Vb, then Vc. - int predIdx = -1; - const Barycentric& splitPoint = - (fixedVaCount > 0) ? Va : (fixedVbCount > 0) ? Vb : Vc; - - if(fixedVaCount > 0) + const int k0 = p - i0 - j0; + const int idx = triIndex(p, i0, j0); + + // Increase Va, then Vb, then Vc. + // The order is arbitrary, since the blossom is symmetric. + if(k0 > 0) { - predIdx = triIndex(p - 1, fixedVcCount, fixedVbCount); + const int predIdx = triIndex(p - 1, i0, j0); + reduce_once(prevNets[predIdx], currNets[idx], degPrev, Va); } - else if(fixedVbCount > 0) + else if(j0 > 0) { - predIdx = triIndex(p - 1, fixedVcCount, fixedVbCount - 1); + const int predIdx = triIndex(p - 1, i0, j0 - 1); + reduce_once(prevNets[predIdx], currNets[idx], degPrev, Vb); } else { - SLIC_ASSERT(fixedVcCount > 0); - predIdx = triIndex(p - 1, fixedVcCount - 1, fixedVbCount); + SLIC_ASSERT(i0 > 0); + const int predIdx = triIndex(p - 1, i0 - 1, j0); + reduce_once(prevNets[predIdx], currNets[idx], degPrev, Vc); } - - SLIC_ASSERT(predIdx >= 0); - currNets[idx] = reduce_once(prevNets[predIdx], degPrev, splitPoint); } } + // Discard the previous nets, since layer p determines layer p+1 prevNets.swap(currNets); } + // Each net in the last layer (p==n) has degree 0 and contains a single control point. out.setOrder(n); out.getWeights().resize(0); for(int i = 0; i <= n; ++i) @@ -530,11 +579,11 @@ class BezierTriangle } /*! - * \brief Splits a polynomial Bezier triangle into three subtriangles by connecting an + * \brief Splits a Bezier triangle into three subtriangles by connecting an * interior parameter point \a (u,v) to the triangle's three vertices * - * \param [in] u Parameter value along the \a u axis for the split point - * \param [in] v Parameter value along the \a v axis for the split point + * \param [in] u0 Parameter value along the \a u axis for the split point + * \param [in] v0 Parameter value along the \a v axis for the split point * \param [out] t0 Subtriangle over the parameter triangle with vertices * `(0,1)`, `(1,0)`, and `(u,v)` (preserves edge 0) * \param [out] t1 Subtriangle over the parameter triangle with vertices @@ -542,33 +591,50 @@ class BezierTriangle * \param [out] t2 Subtriangle over the parameter triangle with vertices * `(0,0)`, `(0,1)`, and `(u,v)` (preserves edge 2) * - * \pre \a u > 0, \a v > 0, and \a u + \a v < 1 - * + * \pre \a u0 > 0, \a v0 > 0, and \a u0 + \a v0 < 1 + * * A * /|\ - * / | \ - * /t2|t1\ + * / | \ + * /t2|t1\ * / /Q\ \ - * / / t0 \ \ + * / / t0 \ \ * //_________\\ - * B C - * + * B C + * * \return t0 = Tri(B,C,Q), t1 = Tri(C,A,Q), t2 = Tri(A,B,Q) */ - void split(T u, T v, BezierTriangle& t0, BezierTriangle& t1, BezierTriangle& t2) const + void split(T u0, T v0, BezierTriangle& t0, BezierTriangle& t1, BezierTriangle& t2) const { SLIC_ASSERT(m_ord >= 0); - SLIC_ASSERT(!isRational()); - SLIC_ASSERT(u > T(0)); - SLIC_ASSERT(v > T(0)); - SLIC_ASSERT(u + v < T(1)); + SLIC_ASSERT(u0 > T(0)); + SLIC_ASSERT(v0 > T(0)); + SLIC_ASSERT(u0 + v0 < T(1)); + + if(isRational()) + { + // Rational case: split in projective space and for weights, then convert back. + BezierTriangle projective(m_ord); + BezierTriangle weights(m_ord); + fill_projective_triangles(projective, weights); + + BezierTriangle p0, p1, p2; + BezierTriangle w0, w1, w2; + projective.split(u0, v0, p0, p1, p2); + weights.split(u0, v0, w0, w1, w2); + + set_from_projective_triangles(p0, w0, t0); + set_from_projective_triangles(p1, w1, t1); + set_from_projective_triangles(p2, w2, t2); + return; + } // Q is the split point in barycentric coordinates over the reference parameter triangle: using Barycentric = Point; - const Barycentric Q {u, v, T(1) - u - v}; + const Barycentric Q {u0, v0, T(1) - u0 - v0}; const int n = m_ord; - std::vector> net(static_cast(n + 1)); + axom::Array> net(n + 1); net[0] = m_controlPoints; for(int p = 1; p <= n; ++p) @@ -604,7 +670,7 @@ class BezierTriangle t1.getWeights().resize(0); t2.getWeights().resize(0); - // Subtriangle (B,C,Q): (0,1), (1,0), (u,v) + // Subtriangle (B,C,Q): (0,1), (1,0), (u0,v0) for(int i = 0; i <= n; ++i) { const int deg = n - i; @@ -614,7 +680,7 @@ class BezierTriangle } } - // Subtriangle (C,A,Q): (1,0), (0,0), (u,v) + // Subtriangle (C,A,Q): (1,0), (0,0), (u0,v0) for(int i = 0; i <= n; ++i) { const int deg = n - i; @@ -624,7 +690,7 @@ class BezierTriangle } } - // Subtriangle (A,B,Q): (0,0), (0,1), (u,v) + // Subtriangle (A,B,Q): (0,0), (0,1), (u0,v0) for(int i = 0; i <= n; ++i) { const int deg = n - i; @@ -636,11 +702,11 @@ class BezierTriangle } /*! - * \brief Splits a polynomial Bezier triangle into two subtriangles by connecting a + * \brief Splits a Bezier triangle into two subtriangles by connecting a * point on a boundary edge to the opposite vertex * * \param [in] edgeIdx Index of the boundary edge to split (same convention as `getEdge(int)`) - * \param [in] s Parameter in \a [0,1] locating the split point along the chosen edge + * \param [in] s Parameter in \a (0,1) locating the split point along the chosen edge * \param [out] t0 First output subtriangle * \param [out] t1 Second output subtriangle * @@ -648,26 +714,43 @@ class BezierTriangle * \pre \a s is in (0,1) * * Taking P0 as the vertex opposite edge `edgeIdx`: - * + * * P0 * /|\ - * / | \ - * / | \ + * / | \ + * / | \ * / | \ - * / t0 | t1 \ + * / t0 | t1 \ * /_____|_____\ - * P1 Q P2 + * P1 Q P2 * s=0 s s=1 - * + * * \return t0 = Tri( P0, P1, Q ), t1 = Tri( P2, P0, Q ) */ void split(int edgeIdx, T s, BezierTriangle& t0, BezierTriangle& t1) const { SLIC_ASSERT(m_ord >= 0); - SLIC_ASSERT(!isRational()); SLIC_ASSERT(edgeIdx >= 0 && edgeIdx < 3); SLIC_ASSERT(s > T(0) && s < T(1)); + if(isRational()) + { + // Rational case: split in projective space and for weights, then convert back. + BezierTriangle projective(m_ord); + BezierTriangle weights(m_ord); + fill_projective_triangles(projective, weights); + + BezierTriangle p0, p1; + BezierTriangle w0, w1; + projective.split(edgeIdx, s, p0, p1); + weights.split(edgeIdx, s, w0, w1); + + set_from_projective_triangles(p0, w0, t0); + set_from_projective_triangles(p1, w1, t1); + return; + } + + using TriangularArray = axom::Array; using Barycentric = Point; Barycentric Q; @@ -687,7 +770,7 @@ class BezierTriangle } const int n = m_ord; - std::vector> net(static_cast(n + 1)); + axom::Array net(n + 1); net[0] = m_controlPoints; for(int p = 1; p <= n; ++p) @@ -769,12 +852,92 @@ class BezierTriangle } /*! - * \brief Splits a polynomial Bezier triangle into four subtriangles by inserting one + * \brief Uniform 4-way split at edge midpoints + * + * \param [out] t0 Subtriangle near vertex `(0,0)` + * \param [out] t1 Subtriangle near vertex `(0,1)` + * \param [out] t2 Subtriangle near vertex `(1,0)` + * \param [out] t3 Central subtriangle + * + * This is equivalent to `split(0.5, 0.5, 0.5, ...)`, but with optimizations to reduce + * redundant computations by sharing intermediate control nets. + * We also separate the implementation based on triangle rationality to improve performance + * + * C + * /\ + * /t2\ + * P1 /____\ P0 + * /\ t3 /\ + * /t0\ /t1\ + * /____\/____\ + * A P2 B + * + * \return t0 = Tri( A, P2, P1 ), t1 = Tri( B, P0, P2 ), t2 = Tri( C, P1, P0 ), t3 = Tri( P1, P0, P2 ) + */ + void uniformSplit(BezierTriangle& t0, BezierTriangle& t1, BezierTriangle& t2, BezierTriangle& t3) const + { + SLIC_ASSERT(m_ord >= 0); + const int n = m_ord; + const int triN = triSize(n); + + t0.setOrder(n); + t1.setOrder(n); + t2.setOrder(n); + t3.setOrder(n); + + if(!isRational()) + { + t0.getWeights().resize(0); + t1.getWeights().resize(0); + t2.getWeights().resize(0); + t3.getWeights().resize(0); + + // For polynomial triangles, these accessors just use the regular control points + auto get_point = [&](int i, int j) -> PointType { return (*this)(i, j); }; + auto set_point = [&](BezierTriangle& out, int i, int j, const PointType& pt) { + out(i, j) = pt; + }; + uniform_split_impl(get_point, set_point, t0, t1, t2, t3); + } + else + { + using HomogeneousPoint = Point; + t0.getWeights().resize(triN); + t1.getWeights().resize(triN); + t2.getWeights().resize(triN); + t3.getWeights().resize(triN); + + // For rational triangles, these accessors generate homogeneous control points + auto get_hom_point = [&](int i, int j) -> HomogeneousPoint { + HomogeneousPoint hp; + const T w = m_weights[triIndex(n, i, j)]; + hp[NDIMS] = w; + for(int d = 0; d < NDIMS; ++d) + { + hp[d] = (*this)(i, j)[d] * w; + } + return hp; + }; + auto set_hom_point = [&](BezierTriangle& out, int i, int j, const HomogeneousPoint& hp) { + const T w = hp[NDIMS]; + SLIC_ASSERT(w > T(0)); + out.getWeights()[triIndex(n, i, j)] = w; + for(int d = 0; d < NDIMS; ++d) + { + out(i, j)[d] = hp[d] / w; + } + }; + uniform_split_impl(get_hom_point, set_hom_point, t0, t1, t2, t3); + } + } + + /*! + * \brief Splits a Bezier triangle into four subtriangles by inserting one * split point on each boundary edge and connecting the split points pairwise * - * \param [in] s1 Parameter in \a [0,1] locating the split point on edge 0 (same convention as `getEdge(0)`) - * \param [in] s2 Parameter in \a [0,1] locating the split point on edge 1 (same convention as `getEdge(1)`) - * \param [in] s3 Parameter in \a [0,1] locating the split point on edge 2 (same convention as `getEdge(2)`) + * \param [in] s1 Parameter in \a (0,1) locating the split point on edge 0 (same convention as `getEdge(0)`) + * \param [in] s2 Parameter in \a (0,1) locating the split point on edge 1 (same convention as `getEdge(1)`) + * \param [in] s3 Parameter in \a (0,1) locating the split point on edge 2 (same convention as `getEdge(2)`) * \param [out] t1 Subtriangle near vertex `(0,0)` with vertices `(0,0)`, point on edge 2, point on edge 1 * \param [out] t2 Subtriangle near vertex `(0,1)` with vertices `(0,1)`, point on edge 0, point on edge 2 * \param [out] t3 Subtriangle near vertex `(1,0)` with vertices `(1,0)`, point on edge 1, point on edge 0 @@ -782,14 +945,14 @@ class BezierTriangle * * \pre \a s1, \a s2, \a s3 are all in (0,1) * C - * /\ - * /t3\ - * P1 /____\ P0 - * /\ t4 /\ - * /t1\ /t2\ + * /\ + * /t3\ + * P1 /____\ P0 + * /\ t4 /\ + * /t1\ /t2\ * /____\/____\ - * A P2 B - * + * A P2 B + * * \return t1 = Tri( A, P2, P1 ), t2 = Tri( B, P0, P2 ), t3 = Tri( C, P1, P0 ), t4 = Tri( P1, P0, P2 ) */ void split(T s1, @@ -802,7 +965,6 @@ class BezierTriangle { using Barycentric = Point; SLIC_ASSERT(m_ord >= 0); - SLIC_ASSERT(!isRational()); SLIC_ASSERT(s1 > T(0) && s1 < T(1)); SLIC_ASSERT(s2 > T(0) && s2 < T(1)); SLIC_ASSERT(s3 > T(0) && s3 < T(1)); @@ -854,21 +1016,27 @@ class BezierTriangle } /*! - * \brief Evaluates the Bezier triangle at \a (u,v) + * \brief Evaluates the Bezier triangle at \a (u0,v0) + * + * \param [in] u0 Parameter value along the \a u axis + * \param [in] v0 Parameter value along the \a v axis + * + * \pre u0 >= 0, v0 >= 0, and u0+v0 <= 1 * - * \param [in] u Parameter value along the \a u axis - * \param [in] v Parameter value along the \a v axis + * \note Evaluation uses permuted barycentric coordinates such that + * parameter values (u0, v0) correspond to the triangle vertices: + * - `evaluate(0,0) == (*this)(0,0)` + * - `evaluate(0,1) == (*this)(0,order)` + * - `evaluate(1,0) == (*this)(order,0)` * - * \pre u >= 0, v >= 0, and u+v <= 1 - * - * \return Point value S(u,v) + * \return Point value S(u0,v0) */ - PointType evaluate(T u, T v) const + PointType evaluate(T u0, T v0) const { SLIC_ASSERT(m_ord >= 0); - SLIC_ASSERT(u >= T(0)); - SLIC_ASSERT(v >= T(0)); - SLIC_ASSERT(u + v <= T(1)); + SLIC_ASSERT(u0 >= T(0)); + SLIC_ASSERT(v0 >= T(0)); + SLIC_ASSERT(u0 + v0 <= T(1)); if(!isRational()) { @@ -895,7 +1063,7 @@ class BezierTriangle const auto& A = dCarray[triIndex(end, i, j)]; const auto& B = dCarray[triIndex(end, i, j + 1)]; const auto& C = dCarray[triIndex(end, i + 1, j)]; - dCarray[triIndex(end - 1, i, j)] = A + u * (C - A) + v * (B - A); + dCarray[triIndex(end - 1, i, j)] = A + u0 * (C - A) + v0 * (B - A); } } } @@ -912,8 +1080,8 @@ class BezierTriangle BezierTriangle weights(m_ord); fill_projective_triangles(projective, weights); - const Point P = projective.evaluate(u, v); - const Point W = weights.evaluate(u, v); + const Point P = projective.evaluate(u0, v0); + const Point W = weights.evaluate(u0, v0); PointType eval; for(int N = 0; N < NDIMS; ++N) @@ -925,26 +1093,26 @@ class BezierTriangle } /*! - * \brief Evaluates first derivatives of the Bezier triangle at \a (u,v) + * \brief Evaluates first derivatives of the Bezier triangle at \a (u0,v0) * - * \param [in] u Parameter value along the \a u axis - * \param [in] v Parameter value along the \a v axis + * \param [in] u0 Parameter value along the \a u axis + * \param [in] v0 Parameter value along the \a v axis * \param [out] eval Point value S(u,v) * \param [out] Du First derivative S_u(u,v) * \param [out] Dv First derivative S_v(u,v) * - * \pre u >= 0, v >= 0, and u+v <= 1 + * \pre u0 >= 0, v0 >= 0, and u0+v0 <= 1 */ - void evaluateFirstDerivatives(T u, - T v, + void evaluateFirstDerivatives(T u0, + T v0, Point& eval, Vector& Du, Vector& Dv) const { SLIC_ASSERT(m_ord >= 0); - SLIC_ASSERT(u >= T(0)); - SLIC_ASSERT(v >= T(0)); - SLIC_ASSERT(u + v <= T(1)); + SLIC_ASSERT(u0 >= T(0)); + SLIC_ASSERT(v0 >= T(0)); + SLIC_ASSERT(u0 + v0 <= T(1)); if(m_ord == 0) { @@ -980,16 +1148,16 @@ class BezierTriangle const auto& A = dCarray[triIndex(end, i, j)]; const auto& B = dCarray[triIndex(end, i, j + 1)]; const auto& C = dCarray[triIndex(end, i + 1, j)]; - dCarray[triIndex(end - 1, i, j)] = A + u * (C - A) + v * (B - A); + dCarray[triIndex(end - 1, i, j)] = A + u0 * (C - A) + v0 * (B - A); } } } // The last reduction yields a linear triangle: - // S(u,v) = A + u(C-A) + v(B-A) + // S(u,v) = A + u0(C-A) + v0(B-A) Du[N] = (dCarray[2] - dCarray[0]); Dv[N] = (dCarray[1] - dCarray[0]); - eval[N] = dCarray[0] + u * Du[N] + v * Dv[N]; + eval[N] = dCarray[0] + u0 * Du[N] + v0 * Dv[N]; Du[N] *= m_ord; Dv[N] *= m_ord; @@ -1007,8 +1175,8 @@ class BezierTriangle Point W; Vector W_u, W_v; - projective.evaluateFirstDerivatives(u, v, P, P_u, P_v); - weights.evaluateFirstDerivatives(u, v, W, W_u, W_v); + projective.evaluateFirstDerivatives(u0, v0, P, P_u, P_v); + weights.evaluateFirstDerivatives(u0, v0, W, W_u, W_v); for(int N = 0; N < NDIMS; ++N) { @@ -1022,30 +1190,30 @@ class BezierTriangle /*! * \brief Evaluates all linear derivatives of a Bezier triangle at (\a u, \a v) * - * \param [in] u Parameter value at which to evaluate along the u axis - * \param [in] v Parameter value at which to evaluate along the v axis + * \param [in] u0 Parameter value at which to evaluate along the u axis + * \param [in] v0 Parameter value at which to evaluate along the v axis * \param [out] eval The point value of the Bezier triangle at (u, v) * \param [out] Du The vector value of S_u(u, v) * \param [out] Dv The vector value of S_v(u, v) * \param [out] DuDv The vector value of S_uv(u, v) == S_vu(u, v) */ - void evaluateLinearDerivatives(T u, - T v, + void evaluateLinearDerivatives(T u0, + T v0, Point& eval, Vector& Du, Vector& Dv, Vector& DuDv) const { SLIC_ASSERT(m_ord >= 0); - SLIC_ASSERT(u >= T(0)); - SLIC_ASSERT(v >= T(0)); - SLIC_ASSERT(u + v <= T(1)); + SLIC_ASSERT(u0 >= T(0)); + SLIC_ASSERT(v0 >= T(0)); + SLIC_ASSERT(u0 + v0 <= T(1)); if(!isRational()) { if(m_ord < 2) { - evaluateFirstDerivatives(u, v, eval, Du, Dv); + evaluateFirstDerivatives(u0, v0, eval, Du, Dv); for(int N = 0; N < NDIMS; ++N) { DuDv[N] = T(0); @@ -1077,7 +1245,7 @@ class BezierTriangle const auto& A = dCarray[triIndex(end, i, j)]; const auto& B = dCarray[triIndex(end, i, j + 1)]; const auto& C = dCarray[triIndex(end, i + 1, j)]; - dCarray[triIndex(end - 1, i, j)] = A + u * (C - A) + v * (B - A); + dCarray[triIndex(end - 1, i, j)] = A + u0 * (C - A) + v0 * (B - A); } } } @@ -1091,11 +1259,11 @@ class BezierTriangle const T Q20 = dCarray[triIndex(2, 2, 0)]; // One reduction yields a linear triangle (order 1) - const T L00 = Q00 + u * (Q10 - Q00) + v * (Q01 - Q00); - const T L01 = Q01 + u * (Q11 - Q01) + v * (Q02 - Q01); - const T L10 = Q10 + u * (Q20 - Q10) + v * (Q11 - Q10); + const T L00 = Q00 + u0 * (Q10 - Q00) + v0 * (Q01 - Q00); + const T L01 = Q01 + u0 * (Q11 - Q01) + v0 * (Q02 - Q01); + const T L10 = Q10 + u0 * (Q20 - Q10) + v0 * (Q11 - Q10); - eval[N] = L00 + u * (L10 - L00) + v * (L01 - L00); + eval[N] = L00 + u0 * (L10 - L00) + v0 * (L01 - L00); Du[N] = n_ord * (L10 - L00); Dv[N] = n_ord * (L01 - L00); DuDv[N] = n_ord_nm1 * (Q11 - Q10 - Q01 + Q00); @@ -1113,8 +1281,8 @@ class BezierTriangle Point W; Vector W_u, W_v, W_uv; - projective.evaluateLinearDerivatives(u, v, P, P_u, P_v, P_uv); - weights.evaluateLinearDerivatives(u, v, W, W_u, W_v, W_uv); + projective.evaluateLinearDerivatives(u0, v0, P, P_u, P_v, P_uv); + weights.evaluateLinearDerivatives(u0, v0, W, W_u, W_v, W_uv); for(int N = 0; N < NDIMS; ++N) { @@ -1127,10 +1295,10 @@ class BezierTriangle } /*! - * \brief Evaluates all second derivatives of a Bezier triangle at (\a u, \a v) + * \brief Evaluates all second derivatives of a Bezier triangle at (\a u0, \a v0) * - * \param [in] u Parameter value at which to evaluate along the u axis - * \param [in] v Parameter value at which to evaluate along the v axis + * \param [in] u0 Parameter value at which to evaluate along the u axis + * \param [in] v0 Parameter value at which to evaluate along the v axis * \param [out] eval The point value of the Bezier triangle at (u, v) * \param [out] Du The vector value of S_u(u, v) * \param [out] Dv The vector value of S_v(u, v) @@ -1138,8 +1306,8 @@ class BezierTriangle * \param [out] DvDv The vector value of S_vv(u, v) * \param [out] DuDv The vector value of S_uv(u, v) == S_vu(u, v) */ - void evaluateSecondDerivatives(T u, - T v, + void evaluateSecondDerivatives(T u0, + T v0, Point& eval, Vector& Du, Vector& Dv, @@ -1148,9 +1316,9 @@ class BezierTriangle Vector& DuDv) const { SLIC_ASSERT(m_ord >= 0); - SLIC_ASSERT(u >= T(0)); - SLIC_ASSERT(v >= T(0)); - SLIC_ASSERT(u + v <= T(1)); + SLIC_ASSERT(u0 >= T(0)); + SLIC_ASSERT(v0 >= T(0)); + SLIC_ASSERT(u0 + v0 <= T(1)); if(m_ord == 0) { @@ -1168,7 +1336,7 @@ class BezierTriangle if(m_ord == 1) { - evaluateFirstDerivatives(u, v, eval, Du, Dv); + evaluateFirstDerivatives(u0, v0, eval, Du, Dv); for(int N = 0; N < NDIMS; ++N) { DuDu[N] = T(0); @@ -1204,7 +1372,7 @@ class BezierTriangle const auto& A = dCarray[triIndex(end, i, j)]; const auto& B = dCarray[triIndex(end, i, j + 1)]; const auto& C = dCarray[triIndex(end, i + 1, j)]; - dCarray[triIndex(end - 1, i, j)] = A + u * (C - A) + v * (B - A); + dCarray[triIndex(end - 1, i, j)] = A + u0 * (C - A) + v0 * (B - A); } } } @@ -1218,11 +1386,11 @@ class BezierTriangle const T Q20 = dCarray[triIndex(2, 2, 0)]; // One reduction yields a linear triangle (order 1) - const T L00 = Q00 + u * (Q10 - Q00) + v * (Q01 - Q00); - const T L01 = Q01 + u * (Q11 - Q01) + v * (Q02 - Q01); - const T L10 = Q10 + u * (Q20 - Q10) + v * (Q11 - Q10); + const T L00 = Q00 + u0 * (Q10 - Q00) + v0 * (Q01 - Q00); + const T L01 = Q01 + u0 * (Q11 - Q01) + v0 * (Q02 - Q01); + const T L10 = Q10 + u0 * (Q20 - Q10) + v0 * (Q11 - Q10); - eval[N] = L00 + u * (L10 - L00) + v * (L01 - L00); + eval[N] = L00 + u0 * (L10 - L00) + v0 * (L01 - L00); Du[N] = n_ord * (L10 - L00); Dv[N] = n_ord * (L01 - L00); @@ -1244,8 +1412,8 @@ class BezierTriangle Point W; Vector W_u, W_v, W_uu, W_vv, W_uv; - projective.evaluateSecondDerivatives(u, v, P, P_u, P_v, P_uu, P_vv, P_uv); - weights.evaluateSecondDerivatives(u, v, W, W_u, W_v, W_uu, W_vv, W_uv); + projective.evaluateSecondDerivatives(u0, v0, P, P_u, P_v, P_uu, P_vv, P_uv); + weights.evaluateSecondDerivatives(u0, v0, W, W_u, W_v, W_uu, W_vv, W_uv); for(int N = 0; N < NDIMS; ++N) { @@ -1259,64 +1427,64 @@ class BezierTriangle } } - /// \brief Computes a tangent of a Bezier triangle at (\a u, \a v) along the u axis - VectorType du(T u, T v) const + /// \brief Computes a tangent of a Bezier triangle at (\a u0, \a v0) along the u axis + VectorType du(T u0, T v0) const { PointType eval; VectorType Du, Dv; - evaluateFirstDerivatives(u, v, eval, Du, Dv); + evaluateFirstDerivatives(u0, v0, eval, Du, Dv); return Du; } - /// \brief Computes a tangent of a Bezier triangle at (\a u, \a v) along the v axis - VectorType dv(T u, T v) const + /// \brief Computes a tangent of a Bezier triangle at (\a u0, \a v0) along the v axis + VectorType dv(T u0, T v0) const { PointType eval; VectorType Du, Dv; - evaluateFirstDerivatives(u, v, eval, Du, Dv); + evaluateFirstDerivatives(u0, v0, eval, Du, Dv); return Dv; } - /// \brief Computes the second derivative of a Bezier triangle at (\a u, \a v) along the u axis - VectorType dudu(T u, T v) const + /// \brief Computes the second derivative of a Bezier triangle at (\a u0, \a v0) along the u axis + VectorType dudu(T u0, T v0) const { PointType eval; VectorType Du, Dv, DuDu, DvDv, DuDv; - evaluateSecondDerivatives(u, v, eval, Du, Dv, DuDu, DvDv, DuDv); + evaluateSecondDerivatives(u0, v0, eval, Du, Dv, DuDu, DvDv, DuDv); return DuDu; } - /// \brief Computes the second derivative of a Bezier triangle at (\a u, \a v) along the v axis - VectorType dvdv(T u, T v) const + /// \brief Computes the second derivative of a Bezier triangle at (\a u0, \a v0) along the v axis + VectorType dvdv(T u0, T v0) const { PointType eval; VectorType Du, Dv, DuDu, DvDv, DuDv; - evaluateSecondDerivatives(u, v, eval, Du, Dv, DuDu, DvDv, DuDv); + evaluateSecondDerivatives(u0, v0, eval, Du, Dv, DuDu, DvDv, DuDv); return DvDv; } - /// \brief Computes the mixed second derivative of a Bezier triangle at (\a u, \a v) - VectorType dudv(T u, T v) const + /// \brief Computes the mixed second derivative of a Bezier triangle at (\a u0, \a v0) + VectorType dudv(T u0, T v0) const { PointType eval; VectorType Du, Dv, DuDu, DvDv, DuDv; - evaluateSecondDerivatives(u, v, eval, Du, Dv, DuDu, DvDv, DuDv); + evaluateSecondDerivatives(u0, v0, eval, Du, Dv, DuDu, DvDv, DuDv); return DuDv; } - /// \brief Convenience alias for S_vu(u,v), which equals S_uv(u,v) for polynomial triangles - VectorType dvdu(T u, T v) const { return dudv(u, v); } + /// \brief Convenience alias for S_vu(u0,v0), which equals S_uv(u0,v0) for polynomial triangles + VectorType dvdu(T u0, T v0) const { return dudv(u0, v0); } /*! - * \brief Computes the normal vector of a Bezier triangle at (\a u, \a v) + * \brief Computes the normal vector of a Bezier triangle at (\a u0, \a v0) * * \note Only meaningful for NDIMS==3. */ - VectorType normal(T u, T v) const + VectorType normal(T u0, T v0) const { Point eval; Vector Du, Dv; - evaluateFirstDerivatives(u, v, eval, Du, Dv); + evaluateFirstDerivatives(u0, v0, eval, Du, Dv); return VectorType::cross_product(Du, Dv); } @@ -1360,9 +1528,9 @@ class BezierTriangle * * \param [in] ord Triangle order */ - static constexpr size_t triSize(int ord) + static constexpr int triSize(int ord) { - return (ord >= 0) ? static_cast((ord + 1) * (ord + 2) / 2) : size_t {0}; + return (ord >= 0) ? ((ord + 1) * (ord + 2) / 2) : 0; } /*! @@ -1426,6 +1594,220 @@ class BezierTriangle } } + /*! + * \brief Private function to evaluate the uniform split algorithm + * + * \param [in] get_eval_point Lambda that returns an `EvalPointType` for control point `(i,j)` + * \param [in] set_eval_point Lambda that assigns an `EvalPointType` to output triangle control point `(i,j)` + * If the triangle is polynomial, these should access the control point as-is. + * If the triangle is rational, these should access the homogeneous control point. + * + * Implements the algorithm from Kenneth I. Joy, "A Uniform Subdivision Method for Triangular Bezier Patches" + * + * We construct a degenerate "Bezier tetrahedron" of intermediate points via repeated + * midpoint averaging in the control net, then extract the four subtriangle control nets + * from its four faces (three corner triangles + one central triangle) + * + * We separate this templated implementation so that we can more efficiently process rational triangles. + */ + template + void uniform_split_impl(GetEvalPointFn get_eval_point, + SetEvalPointFn set_eval_point, + BezierTriangle& t0, + BezierTriangle& t1, + BezierTriangle& t2, + BezierTriangle& t3) const + { + const int n = m_ord; + const int triN = triSize(n); + constexpr int evalDims = EvalPointType::dimension(); + + axom::Array offsets(n + 2); + offsets[0] = 0; + for(int p = 0; p <= n; ++p) + { + offsets[p + 1] = offsets[p] + triSize(p); + } + const int tetN = offsets[n + 1]; + + // tetSize: Number of (n1,n2,n3) triples with n1+n2+n3 <= m_ord. + SLIC_ASSERT(tetN == (n + 1) * (n + 2) * (n + 3) / 6); + + // Storage for the degenerate tetrahedron: + // - tetIdx indexes a (n1,n2,n3) triple with fixed sum p via: + // tetIdx(p,n1,n2) = offsets[p] + triIndex(p,n1,n2), with n3 = p-n1-n2 + // - within each state, we store a full triangle mesh at degree n indexed by triIndex(n,i,j) + // (even though only a subset of indices are valid for a given (n1,n2,n3)). + axom::Array> tet(tetN); + for(int s = 0; s < tetN; ++s) + { + tet[s].resize(triN); + } + + auto tetIdx = [&](int p, int n1, int n2) -> int { + SLIC_ASSERT(p >= 0 && p <= n); + SLIC_ASSERT(n1 >= 0 && n1 <= p); + SLIC_ASSERT(n2 >= 0 && n2 <= p - n1); + return offsets[p] + triIndex(p, n1, n2); + }; + auto getTetPt = [&](int state, int i, int j) -> const EvalPointType& { + return tet[static_cast(state)][triIndex(n, i, j)]; + }; + auto setTetPt = [&](int state, int i, int j, const EvalPointType& value) { + tet[static_cast(state)][triIndex(n, i, j)] = value; + }; + + const int base = tetIdx(0, 0, 0); + for(int i = 0; i <= n; ++i) + { + for(int j = 0; j <= n - i; ++j) + { + setTetPt(base, i, j, get_eval_point(i, j)); + } + } + + // Build the tetrahedron states in increasing p = n1+n2+n3. + for(int p = 1; p <= n; ++p) + { + for(int n1 = 0; n1 <= p; ++n1) + { + for(int n2 = 0; n2 <= p - n1; ++n2) + { + const int n3 = p - n1 - n2; + const int curr = tetIdx(p, n1, n2); + + // Pick the predecessor state and the second point used in the midpoint average. + int prev = -1, di = 0, dj = 0; + if(n1 > 0) + { + prev = tetIdx(p - 1, n1 - 1, n2); + di = -1; + dj = +1; + } + else if(n2 > 0) + { + prev = tetIdx(p - 1, n1, n2 - 1); + di = 0; + dj = -1; + } + else + { + // n1==0 && n2==0 so n3>0 here. + prev = tetIdx(p - 1, 0, 0); + di = +1; + dj = 0; + } + + // Valid domain: i>=n1, j>=n2, k>=n3. + for(int i = n1; i <= n; ++i) + { + const int jMax = n - n3 - i; + if(jMax < n2) + { + continue; + } + for(int j = n2; j <= jMax; ++j) + { + const auto& a = getTetPt(prev, i, j); + const auto& b = getTetPt(prev, i + di, j + dj); + + EvalPointType out; + for(int d = 0; d < evalDims; ++d) + { + out[d] = T(0.5) * (a[d] + b[d]); + } + setTetPt(curr, i, j, out); + } + } + } + } + } + + // Extract the four faces Q1..Q4, given by + // Q1 = { P^{[m,0,k]}_{i,0,k} : i+k=n, m=0..i } + // Q2 = { P^{[i,m,0]}_{i,j,0} : i+j=n, m=0..j } + // Q3 = { P^{[0,j,m]}_{0,j,k} : j+k=n, m=0..k } + // Q4 = { P^{[i,j,k]}_{i,j,k} : i+j+k=n } + + // Q3 -> t0 : P^{[0,j,m]}_{0,j,k} with j+k=n and m<=k maps to local (m,j) + // so that (0,1) is the midpoint on AB and (1,0) is the midpoint on AC. + for(int j = 0; j <= n; ++j) + { + const int i = 0; + for(int m = 0; m <= n - j; ++m) + { + const int p = j + m; + const int state = tetIdx(p, 0, j); + set_eval_point(t0, m, j, getTetPt(state, i, j)); + } + } + + // Q2 -> t1 : P^{[i,m,0]}_{i,j,0} with i+j=n and m<=j maps to local (m,i) + // so that (0,1) is the midpoint on BC and (1,0) is the midpoint on AB. + for(int i = 0; i <= n; ++i) + { + const int j = n - i; + for(int m = 0; m <= j; ++m) + { + const int p = i + m; + const int state = tetIdx(p, i, m); + set_eval_point(t1, m, i, getTetPt(state, i, j)); + } + } + + // Q1 -> t2 : P^{[m,0,k]}_{i,0,k} with i+k=n and m<=i maps to local (m,k) + // so that (0,1) is the midpoint on AC and (1,0) is the midpoint on BC. + for(int k = 0; k <= n; ++k) + { + const int i = n - k; + const int j = 0; + for(int m = 0; m <= i; ++m) + { + const int p = m + k; + const int state = tetIdx(p, m, 0); + set_eval_point(t2, m, k, getTetPt(state, i, j)); + } + } + + // Q4 -> t3 : P^{[i,j,k]}_{i,j,k} with i+j+k=n maps to local (j,i) + // so that (0,1) is the midpoint on BC and (1,0) is the midpoint on AB. + for(int i = 0; i <= n; ++i) + { + for(int j = 0; j <= n - i; ++j) + { + const int state = offsets[n] + triIndex(n, j, i); + set_eval_point(t3, i, j, getTetPt(state, j, i)); + } + } + } + + /// \brief Fill a (possibly non-rational) triangle from projective control points and weights + static void set_from_projective_triangles(const BezierTriangle& projective, + const BezierTriangle& weights, + BezierTriangle& out) + { + const int ord = projective.getOrder(); + SLIC_ASSERT(ord == weights.getOrder()); + + out.setOrder(ord); + out.getWeights().resize(triSize(ord)); + + for(int i = 0; i <= ord; ++i) + { + for(int j = 0; j <= ord - i; ++j) + { + const T w = weights(i, j)[0]; + SLIC_ASSERT(w > T(0)); + out.getWeights()[triIndex(ord, i, j)] = w; + + for(int N = 0; N < NDIMS; ++N) + { + out(i, j)[N] = projective(i, j)[N] / w; + } + } + } + } + private: int m_ord; diff --git a/src/axom/primal/tests/primal_bezier_triangle.cpp b/src/axom/primal/tests/primal_bezier_triangle.cpp index 996531cb5b..4764485d64 100644 --- a/src/axom/primal/tests/primal_bezier_triangle.cpp +++ b/src/axom/primal/tests/primal_bezier_triangle.cpp @@ -16,10 +16,45 @@ #include "axom/primal/geometry/BezierTriangle.hpp" #include -#include namespace primal = axom::primal; +namespace +{ +template +void fillControlNet(BTri& tri) +{ + // Fills the BezierTriangle with sample data + using CoordType = typename BTri::PointType::CoordType; + using PointType = typename BTri::PointType; + + const int ord = tri.getOrder(); + for(int i = 0; i <= ord; ++i) + { + for(int j = 0; j <= ord - i; ++j) + { + const auto ii = static_cast(i); + const auto jj = static_cast(j); + tri(i, j) = PointType {ii, jj, 10.0 * ii - 3.0 * jj}; + } + } +} + +template +void makeRational(BTri& tri) +{ + using CoordType = typename BTri::PointType::CoordType; + + tri.getWeights().resize(tri.getControlPoints().size()); + for(int k = 0; k < tri.getWeights().size(); ++k) + { + // Positive, non-uniform weights + tri.getWeights()[k] = static_cast(0.5 + 0.25 * k); + } + EXPECT_TRUE(tri.isRational()); +} +} // namespace + //------------------------------------------------------------------------------ TEST(primal_beziertriangle, sizing_constructors) { @@ -438,15 +473,7 @@ TEST(primal_beziertriangle, split_interior_polynomial) constexpr int ord = 3; BTri tri(ord); - for(int i = 0; i <= ord; ++i) - { - for(int j = 0; j <= ord - i; ++j) - { - const auto ii = static_cast(i); - const auto jj = static_cast(j); - tri(i, j) = PointType {ii, jj, 10.0 * ii - 3.0 * jj}; - } - } + fillControlNet(tri); const CoordType u = 0.2; const CoordType v = 0.3; @@ -527,25 +554,110 @@ TEST(primal_beziertriangle, split_interior_polynomial) } //------------------------------------------------------------------------------ -TEST(primal_beziertriangle, split_edge_polynomial) +TEST(primal_beziertriangle, split_interior_rational) { constexpr int DIM = 3; using CoordType = double; using BTri = primal::BezierTriangle; - using PointType = BTri::PointType; constexpr int ord = 3; BTri tri(ord); + fillControlNet(tri); + makeRational(tri); - for(int i = 0; i <= ord; ++i) + const CoordType u = 0.2; + const CoordType v = 0.3; + + BTri t0, t1, t2; + tri.split(u, v, t0, t1, t2); + + EXPECT_EQ(ord, t0.getOrder()); + EXPECT_EQ(ord, t1.getOrder()); + EXPECT_EQ(ord, t2.getOrder()); + EXPECT_TRUE(t0.isRational()); + EXPECT_TRUE(t1.isRational()); + EXPECT_TRUE(t2.isRational()); + EXPECT_EQ(BTri::triSize(ord), t0.getWeights().size()); + EXPECT_EQ(BTri::triSize(ord), t1.getWeights().size()); + EXPECT_EQ(BTri::triSize(ord), t2.getWeights().size()); + + // Vertex mapping checks + const auto pA = tri.evaluate(0.0, 0.0); + const auto pB = tri.evaluate(0.0, 1.0); + const auto pC = tri.evaluate(1.0, 0.0); + const auto pQ = tri.evaluate(u, v); + + for(int i = 0; i < DIM; ++i) { - for(int j = 0; j <= ord - i; ++j) + // t0 -> (B, C, Q) + EXPECT_NEAR(pB[i], t0.evaluate(0.0, 0.0)[i], 1e-10); + EXPECT_NEAR(pC[i], t0.evaluate(0.0, 1.0)[i], 1e-10); + EXPECT_NEAR(pQ[i], t0.evaluate(1.0, 0.0)[i], 1e-10); + + // t1 -> (C, A, Q) + EXPECT_NEAR(pC[i], t1.evaluate(0.0, 0.0)[i], 1e-10); + EXPECT_NEAR(pA[i], t1.evaluate(0.0, 1.0)[i], 1e-10); + EXPECT_NEAR(pQ[i], t1.evaluate(1.0, 0.0)[i], 1e-10); + + // t2 -> (A, B, Q) + EXPECT_NEAR(pA[i], t2.evaluate(0.0, 0.0)[i], 1e-10); + EXPECT_NEAR(pB[i], t2.evaluate(0.0, 1.0)[i], 1e-10); + EXPECT_NEAR(pQ[i], t2.evaluate(1.0, 0.0)[i], 1e-10); + } + + // Interior point checks via affine parameter mapping + const CoordType s = 0.2; + const CoordType t = 0.1; + + // t0 is over vertices (0,1), (1,0), (u,v) + { + const CoordType u2 = s * u + t; + const CoordType v2 = 1.0 + s * (v - 1.0) - t; + const auto expected = tri.evaluate(u2, v2); + const auto actual = t0.evaluate(s, t); + for(int d = 0; d < DIM; ++d) { - const auto ii = static_cast(i); - const auto jj = static_cast(j); - tri(i, j) = PointType {ii, jj, 10.0 * ii - 3.0 * jj}; + EXPECT_NEAR(expected[d], actual[d], 1e-12); + } + } + + // t1 is over vertices (1,0), (0,0), (u,v) + { + const CoordType u3 = 1.0 + s * (u - 1.0) - t; + const CoordType v3 = s * v; + const auto expected = tri.evaluate(u3, v3); + const auto actual = t1.evaluate(s, t); + for(int d = 0; d < DIM; ++d) + { + EXPECT_NEAR(expected[d], actual[d], 1e-12); + } + } + + // t2 is over vertices (0,0), (0,1), (u,v) + { + const CoordType u1 = s * u; + const CoordType v1 = t + s * v; + const auto expected = tri.evaluate(u1, v1); + const auto actual = t2.evaluate(s, t); + for(int d = 0; d < DIM; ++d) + { + EXPECT_NEAR(expected[d], actual[d], 1e-12); } } +} + +//------------------------------------------------------------------------------ +TEST(primal_beziertriangle, split_edge_polynomial) +{ + constexpr int DIM = 3; + using CoordType = double; + using BTri = primal::BezierTriangle; + using PointType = BTri::PointType; + + constexpr int ord = 3; + BTri tri(ord); + + fillControlNet(tri); const auto pA = tri.evaluate(0.0, 0.0); const auto pB = tri.evaluate(0.0, 1.0); @@ -585,7 +697,7 @@ TEST(primal_beziertriangle, split_edge_polynomial) } //------------------------------------------------------------------------------ -TEST(primal_beziertriangle, split_triforce_polynomial) +TEST(primal_beziertriangle, split_edge_rational) { constexpr int DIM = 3; using CoordType = double; @@ -594,16 +706,59 @@ TEST(primal_beziertriangle, split_triforce_polynomial) constexpr int ord = 3; BTri tri(ord); + fillControlNet(tri); + makeRational(tri); - for(int i = 0; i <= ord; ++i) + const auto pA = tri.evaluate(0.0, 0.0); + const auto pB = tri.evaluate(0.0, 1.0); + const auto pC = tri.evaluate(1.0, 0.0); + + axom::Array vertices {pA, pB, pC}; + + const CoordType s = 0.35; + + BTri t0, t1; + + for(int i = 0; i < 3; ++i) { - for(int j = 0; j <= ord - i; ++j) + tri.split(i, s, t0, t1); + + EXPECT_EQ(ord, t0.getOrder()); + EXPECT_EQ(ord, t1.getOrder()); + EXPECT_TRUE(t0.isRational()); + EXPECT_TRUE(t1.isRational()); + EXPECT_EQ(BTri::triSize(ord), t0.getWeights().size()); + EXPECT_EQ(BTri::triSize(ord), t1.getWeights().size()); + + // Vertex mapping checks + const auto pQ = tri.getEdge(i).evaluate(s); + + for(int N = 0; N < DIM; ++N) { - const auto ii = static_cast(i); - const auto jj = static_cast(j); - tri(i, j) = PointType {ii, jj, 10.0 * ii - 3.0 * jj}; + EXPECT_NEAR(vertices[(i + 0) % 3][N], t0.evaluate(0.0, 0.0)[N], 1e-10); + EXPECT_NEAR(vertices[(i + 1) % 3][N], t0.evaluate(0.0, 1.0)[N], 1e-10); + + EXPECT_NEAR(vertices[(i + 2) % 3][N], t1.evaluate(0.0, 0.0)[N], 1e-10); + EXPECT_NEAR(vertices[(i + 0) % 3][N], t1.evaluate(0.0, 1.0)[N], 1e-10); + + // Subtriangles agree at the last vertex + EXPECT_NEAR(pQ[N], t0.evaluate(1.0, 0.0)[N], 1e-10); + EXPECT_NEAR(pQ[N], t1.evaluate(1.0, 0.0)[N], 1e-10); } } +} + +//------------------------------------------------------------------------------ +TEST(primal_beziertriangle, split_fourway_polynomial) +{ + constexpr int DIM = 3; + using CoordType = double; + using BTri = primal::BezierTriangle; + + constexpr int ord = 3; + BTri tri(ord); + + fillControlNet(tri); const CoordType s0 = 0.25; // edge0: B->C const CoordType s1 = 0.6; // edge1: C->A @@ -671,6 +826,204 @@ TEST(primal_beziertriangle, split_triforce_polynomial) } } +//------------------------------------------------------------------------------ +TEST(primal_beziertriangle, split_fourway_rational) +{ + constexpr int DIM = 3; + using CoordType = double; + using BTri = primal::BezierTriangle; + + constexpr int ord = 3; + BTri tri(ord); + fillControlNet(tri); + makeRational(tri); + + const CoordType s0 = 0.25; // edge0: B->C + const CoordType s1 = 0.6; // edge1: C->A + const CoordType s2 = 0.4; // edge2: A->B + + BTri t1, t2, t3, t4; + tri.split(s0, s1, s2, t1, t2, t3, t4); + + EXPECT_EQ(ord, t1.getOrder()); + EXPECT_EQ(ord, t2.getOrder()); + EXPECT_EQ(ord, t3.getOrder()); + EXPECT_EQ(ord, t4.getOrder()); + EXPECT_TRUE(t1.isRational()); + EXPECT_TRUE(t2.isRational()); + EXPECT_TRUE(t3.isRational()); + EXPECT_TRUE(t4.isRational()); + + const auto pA = tri.evaluate(0.0, 0.0); + const auto pB = tri.evaluate(0.0, 1.0); + const auto pC = tri.evaluate(1.0, 0.0); + + const auto pP0 = tri.getEdge(0).evaluate(s0); + const auto pP1 = tri.getEdge(1).evaluate(s1); + const auto pP2 = tri.getEdge(2).evaluate(s2); + + for(int d = 0; d < DIM; ++d) + { + // t1 = Tri(A, P2, P1) + EXPECT_NEAR(pA[d], t1.evaluate(0.0, 0.0)[d], 1e-10); + EXPECT_NEAR(pP2[d], t1.evaluate(0.0, 1.0)[d], 1e-10); + EXPECT_NEAR(pP1[d], t1.evaluate(1.0, 0.0)[d], 1e-10); + + // t2 = Tri(B, P0, P2) + EXPECT_NEAR(pB[d], t2.evaluate(0.0, 0.0)[d], 1e-10); + EXPECT_NEAR(pP0[d], t2.evaluate(0.0, 1.0)[d], 1e-10); + EXPECT_NEAR(pP2[d], t2.evaluate(1.0, 0.0)[d], 1e-10); + + // t3 = Tri(C, P1, P0) + EXPECT_NEAR(pC[d], t3.evaluate(0.0, 0.0)[d], 1e-10); + EXPECT_NEAR(pP1[d], t3.evaluate(0.0, 1.0)[d], 1e-10); + EXPECT_NEAR(pP0[d], t3.evaluate(1.0, 0.0)[d], 1e-10); + + // t4 = Tri(P1, P0, P2) + EXPECT_NEAR(pP1[d], t4.evaluate(0.0, 0.0)[d], 1e-10); + EXPECT_NEAR(pP0[d], t4.evaluate(0.0, 1.0)[d], 1e-10); + EXPECT_NEAR(pP2[d], t4.evaluate(1.0, 0.0)[d], 1e-10); + } + + // Shared interior edges agree + const CoordType s = 0.37; + const auto eP0P2_from_t2 = t2.getEdge(0).evaluate(s); // P0 -> P2 + const auto eP0P2_from_t4 = t4.getEdge(0).evaluate(s); // P0 -> P2 + + const auto eP2P1_from_t1 = t1.getEdge(0).evaluate(s); // P2 -> P1 + const auto eP2P1_from_t4 = t4.getEdge(1).evaluate(s); // P2 -> P1 + + const auto eP1P0_from_t3 = t3.getEdge(0).evaluate(s); // P1 -> P0 + const auto eP1P0_from_t4 = t4.getEdge(2).evaluate(s); // P1 -> P0 + + for(int d = 0; d < DIM; ++d) + { + EXPECT_NEAR(eP0P2_from_t2[d], eP0P2_from_t4[d], 1e-12); + EXPECT_NEAR(eP2P1_from_t1[d], eP2P1_from_t4[d], 1e-12); + EXPECT_NEAR(eP1P0_from_t3[d], eP1P0_from_t4[d], 1e-12); + } +} + +//------------------------------------------------------------------------------ +TEST(primal_beziertriangle, uniformSplit_fourway_polynomial) +{ + constexpr int DIM = 3; + using CoordType = double; + using BTri = primal::BezierTriangle; + using PointType = BTri::PointType; + + constexpr int ord = 3; + BTri tri(ord); + + fillControlNet(tri); + + BTri t1, t2, t3, t4; + tri.uniformSplit(t1, t2, t3, t4); + + // Compare against the general (non-optimized) split at s=0.5. + BTri r1, r2, r3, r4; + tri.split(0.5, 0.5, 0.5, r1, r2, r3, r4); + + const auto pP0 = tri.getEdge(0).evaluate(0.5); + const auto pP1 = tri.getEdge(1).evaluate(0.5); + const auto pP2 = tri.getEdge(2).evaluate(0.5); + + for(int d = 0; d < DIM; ++d) + { + EXPECT_NEAR(pP1[d], t4.evaluate(0.0, 0.0)[d], 1e-10); + EXPECT_NEAR(pP0[d], t4.evaluate(0.0, 1.0)[d], 1e-10); + EXPECT_NEAR(pP2[d], t4.evaluate(1.0, 0.0)[d], 1e-10); + } + + const CoordType s = 0.23; + const CoordType t = 0.17; + const auto p11 = t1.evaluate(s, t); + const auto p12 = r1.evaluate(s, t); + const auto p21 = t2.evaluate(s, t); + const auto p22 = r2.evaluate(s, t); + const auto p31 = t3.evaluate(s, t); + const auto p32 = r3.evaluate(s, t); + const auto p41 = t4.evaluate(s, t); + const auto p42 = r4.evaluate(s, t); + + for(int d = 0; d < DIM; ++d) + { + EXPECT_NEAR(p11[d], p12[d], 1e-12); + EXPECT_NEAR(p21[d], p22[d], 1e-12); + EXPECT_NEAR(p31[d], p32[d], 1e-12); + EXPECT_NEAR(p41[d], p42[d], 1e-12); + } + + // Also confirm vertex ordering matches the general split for s=0.5 + for(int d = 0; d < DIM; ++d) + { + EXPECT_NEAR(t1.evaluate(0.0, 0.0)[d], r1.evaluate(0.0, 0.0)[d], 1e-12); + EXPECT_NEAR(t1.evaluate(0.0, 1.0)[d], r1.evaluate(0.0, 1.0)[d], 1e-12); + EXPECT_NEAR(t1.evaluate(1.0, 0.0)[d], r1.evaluate(1.0, 0.0)[d], 1e-12); + + EXPECT_NEAR(t2.evaluate(0.0, 0.0)[d], r2.evaluate(0.0, 0.0)[d], 1e-12); + EXPECT_NEAR(t2.evaluate(0.0, 1.0)[d], r2.evaluate(0.0, 1.0)[d], 1e-12); + EXPECT_NEAR(t2.evaluate(1.0, 0.0)[d], r2.evaluate(1.0, 0.0)[d], 1e-12); + + EXPECT_NEAR(t3.evaluate(0.0, 0.0)[d], r3.evaluate(0.0, 0.0)[d], 1e-12); + EXPECT_NEAR(t3.evaluate(0.0, 1.0)[d], r3.evaluate(0.0, 1.0)[d], 1e-12); + EXPECT_NEAR(t3.evaluate(1.0, 0.0)[d], r3.evaluate(1.0, 0.0)[d], 1e-12); + + EXPECT_NEAR(t4.evaluate(0.0, 0.0)[d], r4.evaluate(0.0, 0.0)[d], 1e-12); + EXPECT_NEAR(t4.evaluate(0.0, 1.0)[d], r4.evaluate(0.0, 1.0)[d], 1e-12); + EXPECT_NEAR(t4.evaluate(1.0, 0.0)[d], r4.evaluate(1.0, 0.0)[d], 1e-12); + } +} + +//------------------------------------------------------------------------------ +TEST(primal_beziertriangle, uniformSplit_fourway_rational) +{ + constexpr int DIM = 3; + using CoordType = double; + using BTri = primal::BezierTriangle; + + constexpr int ord = 3; + BTri tri(ord); + fillControlNet(tri); + makeRational(tri); + + BTri t1, t2, t3, t4; + tri.uniformSplit(t1, t2, t3, t4); + + EXPECT_TRUE(t1.isRational()); + EXPECT_TRUE(t2.isRational()); + EXPECT_TRUE(t3.isRational()); + EXPECT_TRUE(t4.isRational()); + + // Compare against the general (non-optimized) split at s=0.5. + BTri r1, r2, r3, r4; + tri.split(0.5, 0.5, 0.5, r1, r2, r3, r4); + + EXPECT_TRUE(r1.isRational()); + EXPECT_TRUE(r2.isRational()); + EXPECT_TRUE(r3.isRational()); + EXPECT_TRUE(r4.isRational()); + + const CoordType s = 0.23; + const CoordType t = 0.17; + const auto p11 = t1.evaluate(s, t); + const auto p12 = r1.evaluate(s, t); + const auto p21 = t2.evaluate(s, t); + const auto p22 = r2.evaluate(s, t); + const auto p31 = t3.evaluate(s, t); + const auto p32 = r3.evaluate(s, t); + const auto p41 = t4.evaluate(s, t); + const auto p42 = r4.evaluate(s, t); + + for(int d = 0; d < DIM; ++d) + { + EXPECT_NEAR(p11[d], p12[d], 1e-12); + EXPECT_NEAR(p21[d], p22[d], 1e-12); + EXPECT_NEAR(p31[d], p32[d], 1e-12); + EXPECT_NEAR(p41[d], p42[d], 1e-12); + } +} + int main(int argc, char* argv[]) { int result = 0; From 8cc70f67a9521bdd669e380463e2320079b7930a Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Mon, 25 May 2026 23:10:39 -0700 Subject: [PATCH 332/986] Fix style --- src/axom/primal/geometry/BezierTriangle.hpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/axom/primal/geometry/BezierTriangle.hpp b/src/axom/primal/geometry/BezierTriangle.hpp index bf7cf7d845..303dc8e264 100644 --- a/src/axom/primal/geometry/BezierTriangle.hpp +++ b/src/axom/primal/geometry/BezierTriangle.hpp @@ -1528,10 +1528,7 @@ class BezierTriangle * * \param [in] ord Triangle order */ - static constexpr int triSize(int ord) - { - return (ord >= 0) ? ((ord + 1) * (ord + 2) / 2) : 0; - } + static constexpr int triSize(int ord) { return (ord >= 0) ? ((ord + 1) * (ord + 2) / 2) : 0; } /*! * \brief Maps triangular indices \a (i,j) to the linear storage index From c4568bd8699983849fc007d42844b422e35de8aa Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Fri, 5 Jun 2026 12:48:05 -0700 Subject: [PATCH 333/986] Update from review comments --- src/axom/primal/geometry/BezierTriangle.hpp | 400 ++++++++++-------- .../primal/tests/primal_bezier_triangle.cpp | 152 +++---- 2 files changed, 296 insertions(+), 256 deletions(-) diff --git a/src/axom/primal/geometry/BezierTriangle.hpp b/src/axom/primal/geometry/BezierTriangle.hpp index 303dc8e264..11b5a593c2 100644 --- a/src/axom/primal/geometry/BezierTriangle.hpp +++ b/src/axom/primal/geometry/BezierTriangle.hpp @@ -40,6 +40,8 @@ std::ostream& operator<<(std::ostream& os, const BezierTriangle& bTri) * * \brief Represents a Bezier triangle defined by a triangular array of control points * \tparam T the coordinate type, e.g., double, float, etc. + * \tparam NDIMS The dimension of each control point, e.g. 4 for homogeneous surfaces + * or 1 for rational weights. * * A Bezier triangle of order \a N has \f$ (N+1)(N+2)/2 \f$ control points. * It is parametrized over the domain \f$ u0 \ge 0, v0 \ge 0, u0+v0 \le 1 \f$. @@ -57,13 +59,26 @@ std::ostream& operator<<(std::ostream& os, const BezierTriangle& bTri) * - `evaluate(0,0) == (*this)(0,0)` * - `evaluate(0,1) == (*this)(0,1)` * - `evaluate(1,0) == (*this)(1,0)` - * These are mapped to standard Barycentric coordinates through (u0, v0) = {1 - u0 - v0, v0, u0} + * + * These are mapped to standard Barycentric coordinates {u,v,w} through (u0, v0) = {1 - u0 - v0, v0, u0}: + * + * Parametric (u0, v0): Barycentric {u,v,w}: + * (1, 0) {0,0,1} + * /\ /\ + * / \ / \ + * ^ / \ <---> / \ + * | / \ / \ + * | / \ / \ + * v0 /__________\ /__________\ + * (0, 0) u0 ---> (0, 1) {1,0,0} {0,1,0} + * */ template class BezierTriangle { public: using PointType = Point; + using Barycentric = Point; using VectorType = Vector; using CoordsVec = axom::Array; @@ -77,6 +92,11 @@ class BezierTriangle AXOM_STATIC_ASSERT_MSG((NDIMS == 1) || (NDIMS == 2) || (NDIMS == 3), "A Bezier Triangle object may be defined in 1-, 2-, or 3-D"); + // Allows template types to access private member data of other allowable templates + friend class BezierTriangle; + friend class BezierTriangle; + friend class BezierTriangle; + AXOM_STATIC_ASSERT_MSG(std::is_arithmetic::value, "A Bezier Triangle must be defined using an arithmetic type"); @@ -225,6 +245,19 @@ class BezierTriangle */ bool isRational() const { return !m_weights.empty(); } + /// Make trivially rational. If already rational, do nothing + void makeRational() + { + if(!isRational()) + { + m_weights.resize(triSize(m_ord)); + m_weights.fill(1.0); + } + } + + /// Make nonrational by shrinking array of weights + void makeNonrational() { m_weights.clear(); } + /*! * \brief Returns a reference to the triangle's control points * @@ -246,43 +279,6 @@ class BezierTriangle /// \brief Returns a reference to the triangle's weights const WeightsVec& getWeights() const { return m_weights; } - /*! - * \brief Get a specific weight from a rational Bezier triangle - * - * \param [in] i First control net index - * \param [in] j Second control net index - * \pre Requires that the triangle be rational - */ - const T& getWeight(int i, int j) const - { - SLIC_ASSERT(isRational()); - SLIC_ASSERT(m_weights.size() == m_controlPoints.size()); - SLIC_ASSERT(i >= 0); - SLIC_ASSERT(j >= 0); - SLIC_ASSERT(i + j <= m_ord); - return m_weights[triIndex(m_ord, i, j)]; - } - - /*! - * \brief Set the weight at a specific index for a rational Bezier triangle - * - * \param [in] i First control net index - * \param [in] j Second control net index - * \param [in] weight The updated value of the weight - * \pre Requires that the triangle be rational - * \pre Requires that the weight be positive - */ - void setWeight(int i, int j, T weight) - { - SLIC_ASSERT(isRational()); - SLIC_ASSERT(m_weights.size() == m_controlPoints.size()); - SLIC_ASSERT(weight > T(0)); - SLIC_ASSERT(i >= 0); - SLIC_ASSERT(j >= 0); - SLIC_ASSERT(i + j <= m_ord); - m_weights[triIndex(m_ord, i, j)] = weight; - } - /*! * \brief Returns an axis-aligned bounding box containing the Bezier triangle * @@ -362,7 +358,7 @@ class BezierTriangle } /*! - * \brief Returns one of the boundary edges of the Bezier triangle + * \brief Returns a copy of one of the boundary edges of the Bezier triangle * * \param [in] edgeIdx Index of the requested edge in \a [0,2] * @@ -372,7 +368,7 @@ class BezierTriangle * - \a edgeIdx = 1: u = 0 from `evaluate(0,1)` to `evaluate(0,0)` * - \a edgeIdx = 2: v = 0 from `evaluate(0,0)` to `evaluate(1,0)` * - * \return A Bezier curve representing the requested edge. + * \return A copy of the Bezier curve representing the requested edge. */ BezierCurveType getEdge(int edgeIdx) const { @@ -391,33 +387,33 @@ class BezierTriangle case 0: for(int k = 0; k <= m_ord; ++k) { - const int i = k; - const int j = m_ord - k; - pts[k] = (*this)(i, j); + const int idx = triIndex(m_ord, k, m_ord - k); + pts[k] = m_controlPoints[idx]; if(isRational()) { - wts[k] = m_weights[triIndex(m_ord, i, j)]; + wts[k] = m_weights[idx]; } } break; case 1: for(int k = 0; k <= m_ord; ++k) { - const int i = m_ord - k; - pts[k] = (*this)(i, 0); + const int idx = triIndex(m_ord, m_ord - k, 0); + pts[k] = m_controlPoints[idx]; if(isRational()) { - wts[k] = m_weights[triIndex(m_ord, i, 0)]; + wts[k] = m_weights[idx]; } } break; case 2: - for(int j = 0; j <= m_ord; ++j) + for(int k = 0; k <= m_ord; ++k) { - pts[j] = (*this)(0, j); + const int idx = triIndex(m_ord, 0, k); + pts[k] = m_controlPoints[idx]; if(isRational()) { - wts[j] = m_weights[triIndex(m_ord, 0, j)]; + wts[k] = m_weights[idx]; } } break; @@ -449,23 +445,22 @@ class BezierTriangle * * See overload returning a `BezierTriangle` for the vertex mapping convention. * - * \param [in] Va Barycentric coordinates of the first subtriangle vertex `(u,v,w)` - * \param [in] Vb Barycentric coordinates of the second subtriangle vertex `(u,v,w)` - * \param [in] Vc Barycentric coordinates of the third subtriangle vertex `(u,v,w)` + * \param [in] Qa Barycentric coordinates of the first subtriangle vertex `(u,v,w)` + * \param [in] Qb Barycentric coordinates of the second subtriangle vertex `(u,v,w)` + * \param [in] Qc Barycentric coordinates of the third subtriangle vertex `(u,v,w)` * \param [out] out Output restricted Bezier triangle * * \pre getOrder() >= 0 * - * \note The barycentric inputs \a Va, \a Vb, \a Vc are \a (u,v,w) triplets in the same - * parameter-coordinate convention used internally by `evaluate(u,v)` (with \a w = 1-u-v). + * \note The barycentric inputs \a Qa, \a Qb, \a Qc are standard Barycentric coordiantes + * related to parameter convention through (u0 = Qc, v0 = Qb) */ - void restrictToSubtriangle(const Point& Va, - const Point& Vb, - const Point& Vc, + void restrictToSubtriangle(const Barycentric& Qa, + const Barycentric& Qb, + const Barycentric& Qc, BezierTriangle& out) const { using TriangularArray = axom::Array; - using Barycentric = Point; SLIC_ASSERT(m_ord >= 0); if(isRational()) @@ -478,8 +473,8 @@ class BezierTriangle BezierTriangle proj_out(m_ord); BezierTriangle w_out(m_ord); - projective.restrictToSubtriangle(Va, Vb, Vc, proj_out); - weights.restrictToSubtriangle(Va, Vb, Vc, w_out); + projective.restrictToSubtriangle(Qa, Qb, Qc, proj_out); + weights.restrictToSubtriangle(Qa, Qb, Qc, w_out); set_from_projective_triangles(proj_out, w_out, out); return; @@ -501,22 +496,16 @@ class BezierTriangle const auto& B0 = prev[triIndex(deg, ii, jj + 1)]; const auto& C0 = prev[triIndex(deg, ii + 1, jj)]; - PointType val; - for(int N = 0; N < NDIMS; ++N) - { - // Barycentric coordinates permuted to match convention in evaluate() - val[N] = Q[2] * A0[N] + Q[1] * B0[N] + Q[0] * C0[N]; - } - next[triIndex(newDeg, ii, jj)] = val; + next[triIndex(newDeg, ii, jj)] = triInterpolate(A0, B0, C0, Q); } } }; - // The restricted control net over (Va,Vb,Vc) is given by blossom values: - // out(i,j) = b(Vc^i, Vb^j, Va^(n-i-j)) for 0<=i<=n and 0<=j<=n-i + // The restricted control net over (Qa,Qb,Qc) is given by blossom values: + // out(i,j) = b(Qc^i, Qb^j, Qa^(n-i-j)) for 0<=i<=n and 0<=j<=n-i // // At layer p, we maintain all intermediate nets with exactly p fixed arguments, - // i.e. b(Vc^i0, Vb^j0, Vc^k0) with i0 + j0 + k0 = p, and use them to compute nets + // i.e. b(Qc^i0, Qb^j0, Qc^k0) with i0 + j0 + k0 = p, and use them to compute nets // for blossom values with one more fixed argument until p == n axom::Array prevNets(1); prevNets[0] = m_controlPoints; @@ -546,18 +535,18 @@ class BezierTriangle if(k0 > 0) { const int predIdx = triIndex(p - 1, i0, j0); - reduce_once(prevNets[predIdx], currNets[idx], degPrev, Va); + reduce_once(prevNets[predIdx], currNets[idx], degPrev, Qa); } else if(j0 > 0) { const int predIdx = triIndex(p - 1, i0, j0 - 1); - reduce_once(prevNets[predIdx], currNets[idx], degPrev, Vb); + reduce_once(prevNets[predIdx], currNets[idx], degPrev, Qb); } else { SLIC_ASSERT(i0 > 0); const int predIdx = triIndex(p - 1, i0 - 1, j0); - reduce_once(prevNets[predIdx], currNets[idx], degPrev, Vc); + reduce_once(prevNets[predIdx], currNets[idx], degPrev, Qc); } } } @@ -568,28 +557,29 @@ class BezierTriangle // Each net in the last layer (p==n) has degree 0 and contains a single control point. out.setOrder(n); - out.getWeights().resize(0); + out.makeNonrational(); for(int i = 0; i <= n; ++i) { for(int j = 0; j <= n - i; ++j) { - out(i, j) = prevNets[triIndex(n, i, j)][0]; + const int idx = triIndex(n, i, j); + out.m_controlPoints[idx] = prevNets[idx][0]; } } } /*! * \brief Splits a Bezier triangle into three subtriangles by connecting an - * interior parameter point \a (u,v) to the triangle's three vertices + * interior parameter point \a (u0,v0) to the triangle's three vertices * * \param [in] u0 Parameter value along the \a u axis for the split point * \param [in] v0 Parameter value along the \a v axis for the split point * \param [out] t0 Subtriangle over the parameter triangle with vertices - * `(0,1)`, `(1,0)`, and `(u,v)` (preserves edge 0) + * `(0,1)`, `(1,0)`, and `(u0,v0)` (preserves edge 0) * \param [out] t1 Subtriangle over the parameter triangle with vertices - * `(1,0)`, `(0,0)`, and `(u,v)` (preserves edge 1) + * `(1,0)`, `(0,0)`, and `(u0,v0)` (preserves edge 1) * \param [out] t2 Subtriangle over the parameter triangle with vertices - * `(0,0)`, `(0,1)`, and `(u,v)` (preserves edge 2) + * `(0,0)`, `(0,1)`, and `(u0,v0)` (preserves edge 2) * * \pre \a u0 > 0, \a v0 > 0, and \a u0 + \a v0 < 1 * @@ -607,9 +597,7 @@ class BezierTriangle void split(T u0, T v0, BezierTriangle& t0, BezierTriangle& t1, BezierTriangle& t2) const { SLIC_ASSERT(m_ord >= 0); - SLIC_ASSERT(u0 > T(0)); - SLIC_ASSERT(v0 > T(0)); - SLIC_ASSERT(u0 + v0 < T(1)); + SLIC_ASSERT(is_valid_interior_parameter(u0, v0)); if(isRational()) { @@ -630,8 +618,7 @@ class BezierTriangle } // Q is the split point in barycentric coordinates over the reference parameter triangle: - using Barycentric = Point; - const Barycentric Q {u0, v0, T(1) - u0 - v0}; + const Barycentric Q {T(1) - u0 - v0, v0, u0}; const int n = m_ord; axom::Array> net(n + 1); @@ -653,24 +640,14 @@ class BezierTriangle const auto& B0 = prev[triIndex(deg, ii, jj + 1)]; const auto& C0 = prev[triIndex(deg, ii + 1, jj)]; - PointType val; - for(int N = 0; N < NDIMS; ++N) - { - val[N] = Q[2] * A0[N] + Q[1] * B0[N] + Q[0] * C0[N]; - } - net[p][triIndex(newDeg, ii, jj)] = val; + net[p][triIndex(newDeg, ii, jj)] = triInterpolate(A0, B0, C0, Q); } } } - t0.setOrder(n); - t1.setOrder(n); - t2.setOrder(n); - t0.getWeights().resize(0); - t1.getWeights().resize(0); - t2.getWeights().resize(0); - // Subtriangle (B,C,Q): (0,1), (1,0), (u0,v0) + t0.setOrder(n); + t0.makeNonrational(); for(int i = 0; i <= n; ++i) { const int deg = n - i; @@ -681,6 +658,8 @@ class BezierTriangle } // Subtriangle (C,A,Q): (1,0), (0,0), (u0,v0) + t1.setOrder(n); + t1.makeNonrational(); for(int i = 0; i <= n; ++i) { const int deg = n - i; @@ -691,6 +670,8 @@ class BezierTriangle } // Subtriangle (A,B,Q): (0,0), (0,1), (u0,v0) + t2.setOrder(n); + t2.makeNonrational(); for(int i = 0; i <= n; ++i) { const int deg = n - i; @@ -751,19 +732,18 @@ class BezierTriangle } using TriangularArray = axom::Array; - using Barycentric = Point; Barycentric Q; switch(edgeIdx) { - case 0: // (u=s, v=1-s, w=0) - Q = Barycentric {s, T(1) - s, T(0)}; + case 0: // (u=0, v=1-s, w=s) + Q = Barycentric {T(0), T(1) - s, s}; break; - case 1: // (u=1-s, v=0, w=s) - Q = Barycentric {T(1) - s, T(0), s}; + case 1: // (u=s, v=0, w=1-s) + Q = Barycentric {s, T(0), T(1) - s}; break; - case 2: // (u=0, v=s, w=1-s) - Q = Barycentric {T(0), s, T(1) - s}; + case 2: // (u=1-s, v=s, w=0) + Q = Barycentric {T(1) - s, s, T(0)}; break; default: break; @@ -789,20 +769,14 @@ class BezierTriangle const auto& B0 = prev[triIndex(deg, ii, jj + 1)]; const auto& C0 = prev[triIndex(deg, ii + 1, jj)]; - PointType val; - for(int N = 0; N < NDIMS; ++N) - { - // Barycentric coordinates permuted to match convention in evaluate - val[N] = Q[2] * A0[N] + Q[1] * B0[N] + Q[0] * C0[N]; - } - net[p][triIndex(newDeg, ii, jj)] = val; + net[p][triIndex(newDeg, ii, jj)] = triInterpolate(A0, B0, C0, Q); } } } auto fill_from_net = [&](int keptEdge, BezierTriangle& out) { out.setOrder(n); - out.getWeights().resize(0); + out.makeNonrational(); for(int i = 0; i <= n; ++i) { const int deg = n - i; @@ -887,10 +861,10 @@ class BezierTriangle if(!isRational()) { - t0.getWeights().resize(0); - t1.getWeights().resize(0); - t2.getWeights().resize(0); - t3.getWeights().resize(0); + t0.makeNonrational(); + t1.makeNonrational(); + t2.makeNonrational(); + t3.makeNonrational(); // For polynomial triangles, these accessors just use the regular control points auto get_point = [&](int i, int j) -> PointType { return (*this)(i, j); }; @@ -902,29 +876,33 @@ class BezierTriangle else { using HomogeneousPoint = Point; - t0.getWeights().resize(triN); - t1.getWeights().resize(triN); - t2.getWeights().resize(triN); - t3.getWeights().resize(triN); + t0.makeRational(); + t1.makeRational(); + t2.makeRational(); + t3.makeRational(); // For rational triangles, these accessors generate homogeneous control points auto get_hom_point = [&](int i, int j) -> HomogeneousPoint { + const int idx = triIndex(n, i, j); + HomogeneousPoint hp; - const T w = m_weights[triIndex(n, i, j)]; - hp[NDIMS] = w; + hp[NDIMS] = m_weights[idx]; for(int d = 0; d < NDIMS; ++d) { - hp[d] = (*this)(i, j)[d] * w; + hp[d] = m_controlPoints[idx][d] * m_weights[idx]; } return hp; }; auto set_hom_point = [&](BezierTriangle& out, int i, int j, const HomogeneousPoint& hp) { + const int idx = triIndex(n, i, j); + const T w = hp[NDIMS]; SLIC_ASSERT(w > T(0)); - out.getWeights()[triIndex(n, i, j)] = w; + + out.m_weights[idx] = w; for(int d = 0; d < NDIMS; ++d) { - out(i, j)[d] = hp[d] / w; + out.m_controlPoints[idx][d] = hp[d] / w; } }; uniform_split_impl(get_hom_point, set_hom_point, t0, t1, t2, t3); @@ -963,25 +941,24 @@ class BezierTriangle BezierTriangle& t3, BezierTriangle& t4) const { - using Barycentric = Point; SLIC_ASSERT(m_ord >= 0); SLIC_ASSERT(s1 > T(0) && s1 < T(1)); SLIC_ASSERT(s2 > T(0) && s2 < T(1)); SLIC_ASSERT(s3 > T(0) && s3 < T(1)); - // Barycentric coordinates in (u, v, w) where w = 1-u-v - // and the triangle vertices are: A=(0,0,1), B=(0,1,0), C=(1,0,0) - const Barycentric A {T(0), T(0), T(1)}; + // Standard Barycentric coordinates in {u,v,w} where w = 1-u-v + // and the triangle vertices are: A={1,0,0}, B={0,1,0}, C={0,0,1} + const Barycentric A {T(1), T(0), T(0)}; const Barycentric B {T(0), T(1), T(0)}; - const Barycentric C {T(1), T(0), T(0)}; + const Barycentric C {T(0), T(0), T(1)}; - // Edge points (u, v, w = 1-u-v) following the same orientation as getEdge(0..2) + // Edge points {u,v,w} following the same orientation as getEdge(0..2) // edge0: B->C (w == 0) // edge1: C->A (v == 0) // edge2: A->B (u == 0) - const Barycentric P0 {s1, T(1) - s1, T(0)}; - const Barycentric P1 {T(1) - s2, T(0), s2}; - const Barycentric P2 {T(0), s3, T(1) - s3}; + const Barycentric P0 {T(0), T(1) - s1, s1}; + const Barycentric P1 {s2, T(0), T(1) - s2}; + const Barycentric P2 {T(1) - s3, s3, T(0)}; // Corner triangles (ordered to match the diagram and preserve interior-edge orientation) restrictToSubtriangle(A, P2, P1, t1); @@ -1000,43 +977,68 @@ class BezierTriangle */ PointType& operator()(int i, int j) { - SLIC_ASSERT(i >= 0); - SLIC_ASSERT(j >= 0); - SLIC_ASSERT(i + j <= m_ord); + SLIC_ASSERT(isValidIndex(m_ord, i, j)); return m_controlPoints[triIndex(m_ord, i, j)]; } /// \brief Access a control point in the triangular control net const PointType& operator()(int i, int j) const { - SLIC_ASSERT(i >= 0); - SLIC_ASSERT(j >= 0); - SLIC_ASSERT(i + j <= m_ord); + SLIC_ASSERT(isValidIndex(m_ord, i, j)); return m_controlPoints[triIndex(m_ord, i, j)]; } + /*! + * \brief Get a specific weight from a rational Bezier triangle + * + * \param [in] i First control net index + * \param [in] j Second control net index + * \pre Requires that the triangle be rational + */ + const T& getWeight(int i, int j) const + { + SLIC_ASSERT(isRational()); + SLIC_ASSERT(isValidIndex(m_ord, i, j)); + return m_weights[triIndex(m_ord, i, j)]; + } + + /*! + * \brief Set the weight at a specific index for a rational Bezier triangle + * + * \param [in] i First control net index + * \param [in] j Second control net index + * \param [in] weight The updated value of the weight + * \pre Requires that the triangle be rational + * \pre Requires that the weight be positive + */ + void setWeight(int i, int j, T weight) + { + SLIC_ASSERT(isRational()); + SLIC_ASSERT(weight > T {0}); + SLIC_ASSERT(isValidIndex(m_ord, i, j)); + m_weights[triIndex(m_ord, i, j)] = weight; + } + /*! * \brief Evaluates the Bezier triangle at \a (u0,v0) * * \param [in] u0 Parameter value along the \a u axis * \param [in] v0 Parameter value along the \a v axis * - * \pre u0 >= 0, v0 >= 0, and u0+v0 <= 1 - * * \note Evaluation uses permuted barycentric coordinates such that * parameter values (u0, v0) correspond to the triangle vertices: * - `evaluate(0,0) == (*this)(0,0)` * - `evaluate(0,1) == (*this)(0,order)` * - `evaluate(1,0) == (*this)(order,0)` * + * \warning Will automatically extrapolate if (u0, v0) are outside the triangular + * domain (u0 >= 0, v0 >= 0, and u0+v0 <= 1) + * * \return Point value S(u0,v0) */ PointType evaluate(T u0, T v0) const { SLIC_ASSERT(m_ord >= 0); - SLIC_ASSERT(u0 >= T(0)); - SLIC_ASSERT(v0 >= T(0)); - SLIC_ASSERT(u0 + v0 <= T(1)); if(!isRational()) { @@ -1063,7 +1065,8 @@ class BezierTriangle const auto& A = dCarray[triIndex(end, i, j)]; const auto& B = dCarray[triIndex(end, i, j + 1)]; const auto& C = dCarray[triIndex(end, i + 1, j)]; - dCarray[triIndex(end - 1, i, j)] = A + u0 * (C - A) + v0 * (B - A); + dCarray[triIndex(end - 1, i, j)] = + triInterpolate(A, B, C, Barycentric {1.0 - u0 - v0, v0, u0}); } } } @@ -1101,7 +1104,8 @@ class BezierTriangle * \param [out] Du First derivative S_u(u,v) * \param [out] Dv First derivative S_v(u,v) * - * \pre u0 >= 0, v0 >= 0, and u0+v0 <= 1 + * \warning Will automatically extrapolate if (u0, v0) are outside the triangular + * domain (u0 >= 0, v0 >= 0, and u0+v0 <= 1) */ void evaluateFirstDerivatives(T u0, T v0, @@ -1110,9 +1114,6 @@ class BezierTriangle Vector& Dv) const { SLIC_ASSERT(m_ord >= 0); - SLIC_ASSERT(u0 >= T(0)); - SLIC_ASSERT(v0 >= T(0)); - SLIC_ASSERT(u0 + v0 <= T(1)); if(m_ord == 0) { @@ -1148,7 +1149,8 @@ class BezierTriangle const auto& A = dCarray[triIndex(end, i, j)]; const auto& B = dCarray[triIndex(end, i, j + 1)]; const auto& C = dCarray[triIndex(end, i + 1, j)]; - dCarray[triIndex(end - 1, i, j)] = A + u0 * (C - A) + v0 * (B - A); + dCarray[triIndex(end - 1, i, j)] = + triInterpolate(A, B, C, Barycentric {1.0 - u0 - v0, v0, u0}); } } } @@ -1196,6 +1198,9 @@ class BezierTriangle * \param [out] Du The vector value of S_u(u, v) * \param [out] Dv The vector value of S_v(u, v) * \param [out] DuDv The vector value of S_uv(u, v) == S_vu(u, v) + * + * \warning Will automatically extrapolate if (u0, v0) are outside the triangular + * domain (u0 >= 0, v0 >= 0, and u0+v0 <= 1) */ void evaluateLinearDerivatives(T u0, T v0, @@ -1205,9 +1210,6 @@ class BezierTriangle Vector& DuDv) const { SLIC_ASSERT(m_ord >= 0); - SLIC_ASSERT(u0 >= T(0)); - SLIC_ASSERT(v0 >= T(0)); - SLIC_ASSERT(u0 + v0 <= T(1)); if(!isRational()) { @@ -1245,7 +1247,8 @@ class BezierTriangle const auto& A = dCarray[triIndex(end, i, j)]; const auto& B = dCarray[triIndex(end, i, j + 1)]; const auto& C = dCarray[triIndex(end, i + 1, j)]; - dCarray[triIndex(end - 1, i, j)] = A + u0 * (C - A) + v0 * (B - A); + dCarray[triIndex(end - 1, i, j)] = + triInterpolate(A, B, C, Barycentric {1.0 - u0 - v0, v0, u0}); } } } @@ -1305,6 +1308,9 @@ class BezierTriangle * \param [out] DuDu The vector value of S_uu(u, v) * \param [out] DvDv The vector value of S_vv(u, v) * \param [out] DuDv The vector value of S_uv(u, v) == S_vu(u, v) + * + * \warning Will automatically extrapolate if (u0, v0) are outside the triangular + * domain (u0 >= 0, v0 >= 0, and u0+v0 <= 1) */ void evaluateSecondDerivatives(T u0, T v0, @@ -1316,9 +1322,6 @@ class BezierTriangle Vector& DuDv) const { SLIC_ASSERT(m_ord >= 0); - SLIC_ASSERT(u0 >= T(0)); - SLIC_ASSERT(v0 >= T(0)); - SLIC_ASSERT(u0 + v0 <= T(1)); if(m_ord == 0) { @@ -1372,7 +1375,8 @@ class BezierTriangle const auto& A = dCarray[triIndex(end, i, j)]; const auto& B = dCarray[triIndex(end, i, j + 1)]; const auto& C = dCarray[triIndex(end, i + 1, j)]; - dCarray[triIndex(end - 1, i, j)] = A + u0 * (C - A) + v0 * (B - A); + dCarray[triIndex(end - 1, i, j)] = + triInterpolate(A, B, C, Barycentric {1.0 - u0 - v0, v0, u0}); } } } @@ -1544,6 +1548,27 @@ class BezierTriangle return static_cast(i * (2 * ord + 3 - i) / 2 + j); } + /// \brief Check if a given index is valid in the triangular array + static constexpr bool isValidIndex(int ord, int i, int j) + { + return (i >= 0) && (j >= 0) && (i + j <= ord); + } + + /// \brief Do a triangular interpolation from three points and a barycentric coordinate + static constexpr PointType triInterpolate(const PointType& A, + const PointType& B, + const PointType& C, + const Barycentric& Q) + { + return PointType {Q[0] * A.array() + Q[1] * B.array() + Q[2] * C.array()}; + } + + /// \brief Do a triangular interpolation from three coordinates and a barycentric coordinate + static constexpr T triInterpolate(const T& A, const T& B, const T& C, const Barycentric& Q) + { + return Q[0] * A + Q[1] * B + Q[2] * C; + } + private: /// \brief Check that each weight is positive and the size matches control nodes bool is_valid_rational() const @@ -1569,26 +1594,10 @@ class BezierTriangle return true; } - /// \brief For a rational triangle, fill triangle objects with weighted control points and weights - void fill_projective_triangles(BezierTriangle& projective, - BezierTriangle& weights) const + /// \brief Check if the given coordinates are interior to the triangle + bool is_valid_interior_parameter(T u0, T v0) const { - SLIC_ASSERT(isRational()); - SLIC_ASSERT(is_valid_rational()); - - for(int i = 0; i <= m_ord; ++i) - { - for(int j = 0; j <= m_ord - i; ++j) - { - const T w = m_weights[triIndex(m_ord, i, j)]; - weights(i, j)[0] = w; - - for(int N = 0; N < NDIMS; ++N) - { - projective(i, j)[N] = (*this)(i, j)[N] * w; - } - } - } + return (u0 > T {0}) && (v0 > T {0}) && (u0 + v0 < T {1}); } /*! @@ -1787,19 +1796,44 @@ class BezierTriangle SLIC_ASSERT(ord == weights.getOrder()); out.setOrder(ord); - out.getWeights().resize(triSize(ord)); + out.makeRational(); for(int i = 0; i <= ord; ++i) { for(int j = 0; j <= ord - i; ++j) { - const T w = weights(i, j)[0]; + const int idx = triIndex(ord, i, j); + const T w = weights.m_controlPoints[idx][0]; + SLIC_ASSERT(w > T(0)); - out.getWeights()[triIndex(ord, i, j)] = w; + out.m_weights[idx] = w; + + for(int N = 0; N < NDIMS; ++N) + { + out.m_controlPoints[idx][N] = projective.m_controlPoints[idx][N] / w; + } + } + } + } + + /// \brief For a rational triangle, fill triangle objects with weighted control points and weights + void fill_projective_triangles(BezierTriangle& projective, + BezierTriangle& weights) const + { + SLIC_ASSERT(isRational()); + SLIC_ASSERT(is_valid_rational()); + + for(int i = 0; i <= m_ord; ++i) + { + for(int j = 0; j <= m_ord - i; ++j) + { + const int idx = triIndex(m_ord, i, j); + const T w = m_weights[idx]; + weights.m_controlPoints[idx][0] = w; for(int N = 0; N < NDIMS; ++N) { - out(i, j)[N] = projective(i, j)[N] / w; + projective.m_controlPoints[idx][N] = m_controlPoints[idx][N] * w; } } } diff --git a/src/axom/primal/tests/primal_bezier_triangle.cpp b/src/axom/primal/tests/primal_bezier_triangle.cpp index 4764485d64..9e7dc70865 100644 --- a/src/axom/primal/tests/primal_bezier_triangle.cpp +++ b/src/axom/primal/tests/primal_bezier_triangle.cpp @@ -494,22 +494,23 @@ TEST(primal_beziertriangle, split_interior_polynomial) const auto pC = tri.evaluate(1.0, 0.0); const auto pQ = tri.evaluate(u, v); + constexpr double tol = 1e-10; for(int i = 0; i < DIM; ++i) { // t0 -> (B, C, Q) - EXPECT_NEAR(pB[i], t0.evaluate(0.0, 0.0)[i], 1e-10); - EXPECT_NEAR(pC[i], t0.evaluate(0.0, 1.0)[i], 1e-10); - EXPECT_NEAR(pQ[i], t0.evaluate(1.0, 0.0)[i], 1e-10); + EXPECT_NEAR(pB[i], t0.evaluate(0.0, 0.0)[i], tol); + EXPECT_NEAR(pC[i], t0.evaluate(0.0, 1.0)[i], tol); + EXPECT_NEAR(pQ[i], t0.evaluate(1.0, 0.0)[i], tol); // t1 -> (C, A, Q) - EXPECT_NEAR(pC[i], t1.evaluate(0.0, 0.0)[i], 1e-10); - EXPECT_NEAR(pA[i], t1.evaluate(0.0, 1.0)[i], 1e-10); - EXPECT_NEAR(pQ[i], t1.evaluate(1.0, 0.0)[i], 1e-10); + EXPECT_NEAR(pC[i], t1.evaluate(0.0, 0.0)[i], tol); + EXPECT_NEAR(pA[i], t1.evaluate(0.0, 1.0)[i], tol); + EXPECT_NEAR(pQ[i], t1.evaluate(1.0, 0.0)[i], tol); // t2 -> (A, B, Q) - EXPECT_NEAR(pA[i], t2.evaluate(0.0, 0.0)[i], 1e-10); - EXPECT_NEAR(pB[i], t2.evaluate(0.0, 1.0)[i], 1e-10); - EXPECT_NEAR(pQ[i], t2.evaluate(1.0, 0.0)[i], 1e-10); + EXPECT_NEAR(pA[i], t2.evaluate(0.0, 0.0)[i], tol); + EXPECT_NEAR(pB[i], t2.evaluate(0.0, 1.0)[i], tol); + EXPECT_NEAR(pQ[i], t2.evaluate(1.0, 0.0)[i], tol); } // Interior point checks via affine parameter mapping @@ -524,7 +525,7 @@ TEST(primal_beziertriangle, split_interior_polynomial) const auto actual = t0.evaluate(s, t); for(int d = 0; d < DIM; ++d) { - EXPECT_NEAR(expected[d], actual[d], 1e-12); + EXPECT_NEAR(expected[d], actual[d], tol); } } @@ -536,7 +537,7 @@ TEST(primal_beziertriangle, split_interior_polynomial) const auto actual = t1.evaluate(s, t); for(int d = 0; d < DIM; ++d) { - EXPECT_NEAR(expected[d], actual[d], 1e-12); + EXPECT_NEAR(expected[d], actual[d], tol); } } @@ -548,7 +549,7 @@ TEST(primal_beziertriangle, split_interior_polynomial) const auto actual = t2.evaluate(s, t); for(int d = 0; d < DIM; ++d) { - EXPECT_NEAR(expected[d], actual[d], 1e-12); + EXPECT_NEAR(expected[d], actual[d], tol); } } } @@ -587,22 +588,23 @@ TEST(primal_beziertriangle, split_interior_rational) const auto pC = tri.evaluate(1.0, 0.0); const auto pQ = tri.evaluate(u, v); + constexpr double tol = 1e-10; for(int i = 0; i < DIM; ++i) { // t0 -> (B, C, Q) - EXPECT_NEAR(pB[i], t0.evaluate(0.0, 0.0)[i], 1e-10); - EXPECT_NEAR(pC[i], t0.evaluate(0.0, 1.0)[i], 1e-10); - EXPECT_NEAR(pQ[i], t0.evaluate(1.0, 0.0)[i], 1e-10); + EXPECT_NEAR(pB[i], t0.evaluate(0.0, 0.0)[i], tol); + EXPECT_NEAR(pC[i], t0.evaluate(0.0, 1.0)[i], tol); + EXPECT_NEAR(pQ[i], t0.evaluate(1.0, 0.0)[i], tol); // t1 -> (C, A, Q) - EXPECT_NEAR(pC[i], t1.evaluate(0.0, 0.0)[i], 1e-10); - EXPECT_NEAR(pA[i], t1.evaluate(0.0, 1.0)[i], 1e-10); - EXPECT_NEAR(pQ[i], t1.evaluate(1.0, 0.0)[i], 1e-10); + EXPECT_NEAR(pC[i], t1.evaluate(0.0, 0.0)[i], tol); + EXPECT_NEAR(pA[i], t1.evaluate(0.0, 1.0)[i], tol); + EXPECT_NEAR(pQ[i], t1.evaluate(1.0, 0.0)[i], tol); // t2 -> (A, B, Q) - EXPECT_NEAR(pA[i], t2.evaluate(0.0, 0.0)[i], 1e-10); - EXPECT_NEAR(pB[i], t2.evaluate(0.0, 1.0)[i], 1e-10); - EXPECT_NEAR(pQ[i], t2.evaluate(1.0, 0.0)[i], 1e-10); + EXPECT_NEAR(pA[i], t2.evaluate(0.0, 0.0)[i], tol); + EXPECT_NEAR(pB[i], t2.evaluate(0.0, 1.0)[i], tol); + EXPECT_NEAR(pQ[i], t2.evaluate(1.0, 0.0)[i], tol); } // Interior point checks via affine parameter mapping @@ -617,7 +619,7 @@ TEST(primal_beziertriangle, split_interior_rational) const auto actual = t0.evaluate(s, t); for(int d = 0; d < DIM; ++d) { - EXPECT_NEAR(expected[d], actual[d], 1e-12); + EXPECT_NEAR(expected[d], actual[d], tol); } } @@ -629,7 +631,7 @@ TEST(primal_beziertriangle, split_interior_rational) const auto actual = t1.evaluate(s, t); for(int d = 0; d < DIM; ++d) { - EXPECT_NEAR(expected[d], actual[d], 1e-12); + EXPECT_NEAR(expected[d], actual[d], tol); } } @@ -641,7 +643,7 @@ TEST(primal_beziertriangle, split_interior_rational) const auto actual = t2.evaluate(s, t); for(int d = 0; d < DIM; ++d) { - EXPECT_NEAR(expected[d], actual[d], 1e-12); + EXPECT_NEAR(expected[d], actual[d], tol); } } } @@ -784,27 +786,28 @@ TEST(primal_beziertriangle, split_fourway_polynomial) const auto pP1 = tri.getEdge(1).evaluate(s1); const auto pP2 = tri.getEdge(2).evaluate(s2); + constexpr double tol = 1e-10; for(int d = 0; d < DIM; ++d) { // t1 = Tri(A, P2, P1) - EXPECT_NEAR(pA[d], t1.evaluate(0.0, 0.0)[d], 1e-10); - EXPECT_NEAR(pP2[d], t1.evaluate(0.0, 1.0)[d], 1e-10); - EXPECT_NEAR(pP1[d], t1.evaluate(1.0, 0.0)[d], 1e-10); + EXPECT_NEAR(pA[d], t1.evaluate(0.0, 0.0)[d], tol); + EXPECT_NEAR(pP2[d], t1.evaluate(0.0, 1.0)[d], tol); + EXPECT_NEAR(pP1[d], t1.evaluate(1.0, 0.0)[d], tol); // t2 = Tri(B, P0, P2) - EXPECT_NEAR(pB[d], t2.evaluate(0.0, 0.0)[d], 1e-10); - EXPECT_NEAR(pP0[d], t2.evaluate(0.0, 1.0)[d], 1e-10); - EXPECT_NEAR(pP2[d], t2.evaluate(1.0, 0.0)[d], 1e-10); + EXPECT_NEAR(pB[d], t2.evaluate(0.0, 0.0)[d], tol); + EXPECT_NEAR(pP0[d], t2.evaluate(0.0, 1.0)[d], tol); + EXPECT_NEAR(pP2[d], t2.evaluate(1.0, 0.0)[d], tol); // t3 = Tri(C, P1, P0) - EXPECT_NEAR(pC[d], t3.evaluate(0.0, 0.0)[d], 1e-10); - EXPECT_NEAR(pP1[d], t3.evaluate(0.0, 1.0)[d], 1e-10); - EXPECT_NEAR(pP0[d], t3.evaluate(1.0, 0.0)[d], 1e-10); + EXPECT_NEAR(pC[d], t3.evaluate(0.0, 0.0)[d], tol); + EXPECT_NEAR(pP1[d], t3.evaluate(0.0, 1.0)[d], tol); + EXPECT_NEAR(pP0[d], t3.evaluate(1.0, 0.0)[d], tol); // t4 = Tri(P1, P0, P2) - EXPECT_NEAR(pP1[d], t4.evaluate(0.0, 0.0)[d], 1e-10); - EXPECT_NEAR(pP0[d], t4.evaluate(0.0, 1.0)[d], 1e-10); - EXPECT_NEAR(pP2[d], t4.evaluate(1.0, 0.0)[d], 1e-10); + EXPECT_NEAR(pP1[d], t4.evaluate(0.0, 0.0)[d], tol); + EXPECT_NEAR(pP0[d], t4.evaluate(0.0, 1.0)[d], tol); + EXPECT_NEAR(pP2[d], t4.evaluate(1.0, 0.0)[d], tol); } // Shared interior edges agree (same geometry and orientation for the chosen outputs) @@ -820,9 +823,9 @@ TEST(primal_beziertriangle, split_fourway_polynomial) for(int d = 0; d < DIM; ++d) { - EXPECT_NEAR(eP0P2_from_t2[d], eP0P2_from_t4[d], 1e-12); - EXPECT_NEAR(eP2P1_from_t1[d], eP2P1_from_t4[d], 1e-12); - EXPECT_NEAR(eP1P0_from_t3[d], eP1P0_from_t4[d], 1e-12); + EXPECT_NEAR(eP0P2_from_t2[d], eP0P2_from_t4[d], tol); + EXPECT_NEAR(eP2P1_from_t1[d], eP2P1_from_t4[d], tol); + EXPECT_NEAR(eP1P0_from_t3[d], eP1P0_from_t4[d], tol); } } @@ -862,27 +865,28 @@ TEST(primal_beziertriangle, split_fourway_rational) const auto pP1 = tri.getEdge(1).evaluate(s1); const auto pP2 = tri.getEdge(2).evaluate(s2); + constexpr double tol = 1e-10; for(int d = 0; d < DIM; ++d) { // t1 = Tri(A, P2, P1) - EXPECT_NEAR(pA[d], t1.evaluate(0.0, 0.0)[d], 1e-10); - EXPECT_NEAR(pP2[d], t1.evaluate(0.0, 1.0)[d], 1e-10); - EXPECT_NEAR(pP1[d], t1.evaluate(1.0, 0.0)[d], 1e-10); + EXPECT_NEAR(pA[d], t1.evaluate(0.0, 0.0)[d], tol); + EXPECT_NEAR(pP2[d], t1.evaluate(0.0, 1.0)[d], tol); + EXPECT_NEAR(pP1[d], t1.evaluate(1.0, 0.0)[d], tol); // t2 = Tri(B, P0, P2) - EXPECT_NEAR(pB[d], t2.evaluate(0.0, 0.0)[d], 1e-10); - EXPECT_NEAR(pP0[d], t2.evaluate(0.0, 1.0)[d], 1e-10); - EXPECT_NEAR(pP2[d], t2.evaluate(1.0, 0.0)[d], 1e-10); + EXPECT_NEAR(pB[d], t2.evaluate(0.0, 0.0)[d], tol); + EXPECT_NEAR(pP0[d], t2.evaluate(0.0, 1.0)[d], tol); + EXPECT_NEAR(pP2[d], t2.evaluate(1.0, 0.0)[d], tol); // t3 = Tri(C, P1, P0) - EXPECT_NEAR(pC[d], t3.evaluate(0.0, 0.0)[d], 1e-10); - EXPECT_NEAR(pP1[d], t3.evaluate(0.0, 1.0)[d], 1e-10); - EXPECT_NEAR(pP0[d], t3.evaluate(1.0, 0.0)[d], 1e-10); + EXPECT_NEAR(pC[d], t3.evaluate(0.0, 0.0)[d], tol); + EXPECT_NEAR(pP1[d], t3.evaluate(0.0, 1.0)[d], tol); + EXPECT_NEAR(pP0[d], t3.evaluate(1.0, 0.0)[d], tol); // t4 = Tri(P1, P0, P2) - EXPECT_NEAR(pP1[d], t4.evaluate(0.0, 0.0)[d], 1e-10); - EXPECT_NEAR(pP0[d], t4.evaluate(0.0, 1.0)[d], 1e-10); - EXPECT_NEAR(pP2[d], t4.evaluate(1.0, 0.0)[d], 1e-10); + EXPECT_NEAR(pP1[d], t4.evaluate(0.0, 0.0)[d], tol); + EXPECT_NEAR(pP0[d], t4.evaluate(0.0, 1.0)[d], tol); + EXPECT_NEAR(pP2[d], t4.evaluate(1.0, 0.0)[d], tol); } // Shared interior edges agree @@ -898,9 +902,9 @@ TEST(primal_beziertriangle, split_fourway_rational) for(int d = 0; d < DIM; ++d) { - EXPECT_NEAR(eP0P2_from_t2[d], eP0P2_from_t4[d], 1e-12); - EXPECT_NEAR(eP2P1_from_t1[d], eP2P1_from_t4[d], 1e-12); - EXPECT_NEAR(eP1P0_from_t3[d], eP1P0_from_t4[d], 1e-12); + EXPECT_NEAR(eP0P2_from_t2[d], eP0P2_from_t4[d], tol); + EXPECT_NEAR(eP2P1_from_t1[d], eP2P1_from_t4[d], tol); + EXPECT_NEAR(eP1P0_from_t3[d], eP1P0_from_t4[d], tol); } } @@ -928,11 +932,12 @@ TEST(primal_beziertriangle, uniformSplit_fourway_polynomial) const auto pP1 = tri.getEdge(1).evaluate(0.5); const auto pP2 = tri.getEdge(2).evaluate(0.5); + constexpr double tol = 1e-10; for(int d = 0; d < DIM; ++d) { - EXPECT_NEAR(pP1[d], t4.evaluate(0.0, 0.0)[d], 1e-10); - EXPECT_NEAR(pP0[d], t4.evaluate(0.0, 1.0)[d], 1e-10); - EXPECT_NEAR(pP2[d], t4.evaluate(1.0, 0.0)[d], 1e-10); + EXPECT_NEAR(pP1[d], t4.evaluate(0.0, 0.0)[d], tol); + EXPECT_NEAR(pP0[d], t4.evaluate(0.0, 1.0)[d], tol); + EXPECT_NEAR(pP2[d], t4.evaluate(1.0, 0.0)[d], tol); } const CoordType s = 0.23; @@ -957,21 +962,21 @@ TEST(primal_beziertriangle, uniformSplit_fourway_polynomial) // Also confirm vertex ordering matches the general split for s=0.5 for(int d = 0; d < DIM; ++d) { - EXPECT_NEAR(t1.evaluate(0.0, 0.0)[d], r1.evaluate(0.0, 0.0)[d], 1e-12); - EXPECT_NEAR(t1.evaluate(0.0, 1.0)[d], r1.evaluate(0.0, 1.0)[d], 1e-12); - EXPECT_NEAR(t1.evaluate(1.0, 0.0)[d], r1.evaluate(1.0, 0.0)[d], 1e-12); + EXPECT_NEAR(t1.evaluate(0.0, 0.0)[d], r1.evaluate(0.0, 0.0)[d], tol); + EXPECT_NEAR(t1.evaluate(0.0, 1.0)[d], r1.evaluate(0.0, 1.0)[d], tol); + EXPECT_NEAR(t1.evaluate(1.0, 0.0)[d], r1.evaluate(1.0, 0.0)[d], tol); - EXPECT_NEAR(t2.evaluate(0.0, 0.0)[d], r2.evaluate(0.0, 0.0)[d], 1e-12); - EXPECT_NEAR(t2.evaluate(0.0, 1.0)[d], r2.evaluate(0.0, 1.0)[d], 1e-12); - EXPECT_NEAR(t2.evaluate(1.0, 0.0)[d], r2.evaluate(1.0, 0.0)[d], 1e-12); + EXPECT_NEAR(t2.evaluate(0.0, 0.0)[d], r2.evaluate(0.0, 0.0)[d], tol); + EXPECT_NEAR(t2.evaluate(0.0, 1.0)[d], r2.evaluate(0.0, 1.0)[d], tol); + EXPECT_NEAR(t2.evaluate(1.0, 0.0)[d], r2.evaluate(1.0, 0.0)[d], tol); - EXPECT_NEAR(t3.evaluate(0.0, 0.0)[d], r3.evaluate(0.0, 0.0)[d], 1e-12); - EXPECT_NEAR(t3.evaluate(0.0, 1.0)[d], r3.evaluate(0.0, 1.0)[d], 1e-12); - EXPECT_NEAR(t3.evaluate(1.0, 0.0)[d], r3.evaluate(1.0, 0.0)[d], 1e-12); + EXPECT_NEAR(t3.evaluate(0.0, 0.0)[d], r3.evaluate(0.0, 0.0)[d], tol); + EXPECT_NEAR(t3.evaluate(0.0, 1.0)[d], r3.evaluate(0.0, 1.0)[d], tol); + EXPECT_NEAR(t3.evaluate(1.0, 0.0)[d], r3.evaluate(1.0, 0.0)[d], tol); - EXPECT_NEAR(t4.evaluate(0.0, 0.0)[d], r4.evaluate(0.0, 0.0)[d], 1e-12); - EXPECT_NEAR(t4.evaluate(0.0, 1.0)[d], r4.evaluate(0.0, 1.0)[d], 1e-12); - EXPECT_NEAR(t4.evaluate(1.0, 0.0)[d], r4.evaluate(1.0, 0.0)[d], 1e-12); + EXPECT_NEAR(t4.evaluate(0.0, 0.0)[d], r4.evaluate(0.0, 0.0)[d], tol); + EXPECT_NEAR(t4.evaluate(0.0, 1.0)[d], r4.evaluate(0.0, 1.0)[d], tol); + EXPECT_NEAR(t4.evaluate(1.0, 0.0)[d], r4.evaluate(1.0, 0.0)[d], tol); } } @@ -1015,12 +1020,13 @@ TEST(primal_beziertriangle, uniformSplit_fourway_rational) const auto p41 = t4.evaluate(s, t); const auto p42 = r4.evaluate(s, t); + constexpr double tol = 1e-12; for(int d = 0; d < DIM; ++d) { - EXPECT_NEAR(p11[d], p12[d], 1e-12); - EXPECT_NEAR(p21[d], p22[d], 1e-12); - EXPECT_NEAR(p31[d], p32[d], 1e-12); - EXPECT_NEAR(p41[d], p42[d], 1e-12); + EXPECT_NEAR(p11[d], p12[d], tol); + EXPECT_NEAR(p21[d], p22[d], tol); + EXPECT_NEAR(p31[d], p32[d], tol); + EXPECT_NEAR(p41[d], p42[d], tol); } } From cc6a0c628e62c6a76cbe1629373a3cddd9a53f94 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Fri, 5 Jun 2026 22:29:52 -0700 Subject: [PATCH 334/986] Adjust comments to reflect invalid bezier objects --- src/axom/primal/geometry/BezierCurve.hpp | 9 ++++++--- src/axom/primal/geometry/BezierPatch.hpp | 5 ++++- src/axom/primal/geometry/BezierTriangle.hpp | 12 ++++++------ 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/axom/primal/geometry/BezierCurve.hpp b/src/axom/primal/geometry/BezierCurve.hpp index de70531a58..c74905cf47 100644 --- a/src/axom/primal/geometry/BezierCurve.hpp +++ b/src/axom/primal/geometry/BezierCurve.hpp @@ -52,6 +52,10 @@ std::ostream& operator<<(std::ostream& os, const BezierCurve& bCurve); * * Contains an array of positive weights to represent a rational Bezier curve. * Nonrational Bezier curves are identified by an empty weights array. + * + * A default-constructed curve will have order -1, and is "invalid". + * Arrays of nodes and weights will be empty, and most methods are invalid + * * Algorithms for Rational Bezier curves derived from * Gerald Farin, "Algorithms for rational Bezier curves" * Computer-Aided Design, Volume 15, Number 2, 1983, @@ -103,15 +107,14 @@ class BezierCurve * If \a controlPoints is empty, we still allocate space for \a ord+1 control points * \pre order \a ord is greater than or equal to -1 * \pre controlPoints is either empty or has size \a ord+1 - * \pre weights is either empty or has size \a ord+1 - * \pre controlPoints cannot be empty if weights are supplied + * \pre weights is either empty or has size of controlPoints */ BezierCurve(axom::ArrayView controlPoints, axom::ArrayView weights, int ord) { SLIC_ASSERT(ord >= -1); const int SZ = utilities::max(0, ord + 1); - SLIC_ASSERT(controlPoints.size() >= weights.size()); + SLIC_ASSERT(weights.empty() || controlPoints.size() == weights.size()); // note: always allocates space for the control points if(controlPoints.empty()) diff --git a/src/axom/primal/geometry/BezierPatch.hpp b/src/axom/primal/geometry/BezierPatch.hpp index 6c62741e6e..95ec88eca4 100644 --- a/src/axom/primal/geometry/BezierPatch.hpp +++ b/src/axom/primal/geometry/BezierPatch.hpp @@ -52,6 +52,9 @@ std::ostream& operator<<(std::ostream& os, const BezierPatch& bPatch); * Contains a 2D array of positive weights to represent a rational Bezier patch. * Polynomial (nonrational) Bezier patches are identified by an empty weights array. * + * A default-constructed patch will have order -1 in both directions, and is "invalid". + * Arrays of nodes and weights will be empty, and most methods are invalid. + * * Algorithms for Rational Bezier curves derived from * Gerald Farin, "Algorithms for rational Bezier curves" * Computer-Aided Design, Volume 15, Number 2, 1983, @@ -114,7 +117,7 @@ class BezierPatch * \param [in] ord_v The patch's polynomial order on the second axis * * If \a controlPoints is empty, we still allocate space for (ord_u+1, ord_v+1) control points - * \pre ord_u and ord_v must either both be at least 0 for a valid patch, or both must be -1 for an empty patch + * \pre ord_u and ord_v must either both be at least 0 for a valid patch, or both must be -1 for an invalid patch */ BezierPatch(axom::ArrayView controlPoints, axom::ArrayView weights, diff --git a/src/axom/primal/geometry/BezierTriangle.hpp b/src/axom/primal/geometry/BezierTriangle.hpp index 11b5a593c2..155c4db6ee 100644 --- a/src/axom/primal/geometry/BezierTriangle.hpp +++ b/src/axom/primal/geometry/BezierTriangle.hpp @@ -40,8 +40,7 @@ std::ostream& operator<<(std::ostream& os, const BezierTriangle& bTri) * * \brief Represents a Bezier triangle defined by a triangular array of control points * \tparam T the coordinate type, e.g., double, float, etc. - * \tparam NDIMS The dimension of each control point, e.g. 4 for homogeneous surfaces - * or 1 for rational weights. + * \tparam NDIMS The dimension of each control point, e.g. 1 for rational weights * * A Bezier triangle of order \a N has \f$ (N+1)(N+2)/2 \f$ control points. * It is parametrized over the domain \f$ u0 \ge 0, v0 \ge 0, u0+v0 \le 1 \f$. @@ -54,6 +53,9 @@ std::ostream& operator<<(std::ostream& os, const BezierTriangle& bTri) * Rational triangles are represented by an additional set of positive weights. * Polynomial (nonrational) Bezier triangles are identified by an empty weights array. * + * A default-constructed triangle will have order -1, and is "invalid". + * Arrays of nodes and weights will be empty, and most methods are invalid + * * \note This triangle uses permuted barycentric coordinates (u0, v0) for evaluation such that, when * `getOrder()==1`, the parameter values correspond to the triangle vertices: * - `evaluate(0,0) == (*this)(0,0)` @@ -118,10 +120,8 @@ class BezierTriangle * All weights must be greater than 0 in a rational triangle. * * For 1D control point/weight arrays, the expected layout corresponds to the indexing - * used by `operator()(i,j)`: - \verbatim - pts[ triIndex(N,i,j) ] <-> (*this)(i,j) for 0<=i<=N and 0<=j<=N-i - \endverbatim + * used by `operator()(i,j)`: + * pts[ triIndex(N,i,j) ] <-> (*this)(i,j) for 0<=i<=N and 0<=j<=N-i */ /** From 5ffa0354c9c7e7922291c96f87420ffdf9f12fad Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Fri, 5 Jun 2026 22:54:05 -0700 Subject: [PATCH 335/986] Fix some comments about empty/invalid curves and patches --- src/axom/primal/geometry/BezierCurve.hpp | 9 +++++---- src/axom/primal/geometry/BezierPatch.hpp | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/axom/primal/geometry/BezierCurve.hpp b/src/axom/primal/geometry/BezierCurve.hpp index c74905cf47..4e98cd56ce 100644 --- a/src/axom/primal/geometry/BezierCurve.hpp +++ b/src/axom/primal/geometry/BezierCurve.hpp @@ -160,7 +160,7 @@ class BezierCurve * * \param [in] pts a vector with ord+1 control points * \param [in] ord The Curve's polynomial order - * \pre order is greater than or equal to zero + * \pre order is greater than or equal to -1 */ BezierCurve(const PointType* pts, int ord) : BezierCurve(axom::ArrayView(pts, ord + 1), @@ -174,7 +174,7 @@ class BezierCurve * \param [in] pts a vector with ord+1 control points * \param [in] weights a vector with ord+1 positive weights * \param [in] ord The Curve's polynomial order - * \pre order is greater than or equal to zero + * \pre order is greater than or equal to -1 */ BezierCurve(const PointType* pts, const T* weights, int ord) : BezierCurve(axom::ArrayView(pts, ord + 1), @@ -187,7 +187,7 @@ class BezierCurve * * \param [in] pts an array with ord+1 control points * \param [in] ord The Curve's polynomial order - * \pre order is greater than or equal to zero + * \pre order+1 is equal to pts.size() */ BezierCurve(const axom::Array& pts, int ord) : BezierCurve(pts.view(), axom::ArrayView(nullptr, 0), ord) @@ -199,7 +199,8 @@ class BezierCurve * \param [in] pts an array with ord+1 control points * \param [in] weights an array with ord+1 positive weights * \param [in] ord The Curve's polynomial order - * \pre order is greater than or equal to zero + * \pre pts.size() is equal to order+1 + * \pre weights.size() is equal to order+1 or 0 */ BezierCurve(const axom::Array& pts, const axom::Array& weights, int ord) : BezierCurve(pts.view(), weights.view(), ord) diff --git a/src/axom/primal/geometry/BezierPatch.hpp b/src/axom/primal/geometry/BezierPatch.hpp index 95ec88eca4..f5511224c8 100644 --- a/src/axom/primal/geometry/BezierPatch.hpp +++ b/src/axom/primal/geometry/BezierPatch.hpp @@ -176,7 +176,7 @@ class BezierPatch * space for the given order of the surface * * \param [in] ord_u, ord_v The patch's polynomial orders - * \pre ord_u, ord_v greater than or equal to -1. + * \pre ord_u and ord_v must either both be at least 0 for a valid patch, or both must be -1 for an invalid patch */ BezierPatch(int ord_u = -1, int ord_v = -1) : BezierPatch(axom::ArrayView(nullptr, {0, 0}), From 6b0e4ae3017d71a69e43e78894738d461daca157 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Sun, 7 Jun 2026 21:14:50 -0700 Subject: [PATCH 336/986] Fix some warnings --- src/axom/primal/geometry/BezierTriangle.hpp | 1 - src/axom/primal/tests/primal_bezier_triangle.cpp | 2 -- 2 files changed, 3 deletions(-) diff --git a/src/axom/primal/geometry/BezierTriangle.hpp b/src/axom/primal/geometry/BezierTriangle.hpp index 155c4db6ee..6431eb8bbc 100644 --- a/src/axom/primal/geometry/BezierTriangle.hpp +++ b/src/axom/primal/geometry/BezierTriangle.hpp @@ -852,7 +852,6 @@ class BezierTriangle { SLIC_ASSERT(m_ord >= 0); const int n = m_ord; - const int triN = triSize(n); t0.setOrder(n); t1.setOrder(n); diff --git a/src/axom/primal/tests/primal_bezier_triangle.cpp b/src/axom/primal/tests/primal_bezier_triangle.cpp index 9e7dc70865..838aa1cfea 100644 --- a/src/axom/primal/tests/primal_bezier_triangle.cpp +++ b/src/axom/primal/tests/primal_bezier_triangle.cpp @@ -468,7 +468,6 @@ TEST(primal_beziertriangle, split_interior_polynomial) constexpr int DIM = 3; using CoordType = double; using BTri = primal::BezierTriangle; - using PointType = BTri::PointType; constexpr int ord = 3; BTri tri(ord); @@ -914,7 +913,6 @@ TEST(primal_beziertriangle, uniformSplit_fourway_polynomial) constexpr int DIM = 3; using CoordType = double; using BTri = primal::BezierTriangle; - using PointType = BTri::PointType; constexpr int ord = 3; BTri tri(ord); From 8a3241c0eec66972266bed6fb8b06fd7b6cbf683 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 8 Jun 2026 12:59:35 -0700 Subject: [PATCH 337/986] 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 338/986] 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 339/986] 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 340/986] 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 341/986] 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 342/986] 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 343/986] 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 344/986] 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 1e915e693a5b8a57f2298fab14fa4d9e62995c55 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 9 Jun 2026 10:40:54 -0700 Subject: [PATCH 345/986] Matrix fixes --- src/axom/bump/tests/bump_views.cpp | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/axom/bump/tests/bump_views.cpp b/src/axom/bump/tests/bump_views.cpp index 1ef194f854..04665d6be0 100644 --- a/src/axom/bump/tests/bump_views.cpp +++ b/src/axom/bump/tests/bump_views.cpp @@ -745,6 +745,13 @@ struct test_braid2d_mat } } + template + struct ViewPackage + { + MatsetView matsetView; + MatsetFieldView fieldView; + }; + template static void test_matsetview_iterators(axom::IndexType nzones, MatsetView matsetView, @@ -757,15 +764,18 @@ struct test_braid2d_mat axom::Array resultsArrayDevice(nResults, nResults, allocatorID); auto resultsView = resultsArrayDevice.view(); + // Bundle the views together for device access. + ViewPackage viewPackage{matsetView, fieldView}; + axom::for_all( nzones, AXOM_LAMBDA(axom::IndexType index) { typename MatsetView::IDList ids {}; typename MatsetView::VFList vfs {}; - matsetView.zoneMaterials(index, ids, vfs); + viewPackage.matsetView.zoneMaterials(index, ids, vfs); // Get the end iterator for the zone. - const auto end = matsetView.endZone(index); + const auto end = viewPackage.matsetView.endZone(index); int eq_count = 0; int count = 0; @@ -782,7 +792,7 @@ struct test_braid2d_mat // Make sure the iterator order is the same as for the values we got from zoneMaterials(). int i = 0; - for(auto it = matsetView.beginZone(index); it != end; it++, i++) + for(auto it = viewPackage.matsetView.beginZone(index); it != end; it++, i++) { eq_count += (vfs[i] == it.volume_fraction() && ids[i] == it.material_id()) ? 1 : 0; count++; @@ -794,9 +804,9 @@ struct test_braid2d_mat if constexpr(!std::is_same_v) { int i = 0; - for(auto it = matsetView.beginZone(index); it != end; it++, i++) + for(auto it = viewPackage.matsetView.beginZone(index); it != end; it++, i++) { - const auto value = fieldView.value(it); + const auto value = viewPackage.fieldView.value(it); eq_count += (value == it.volume_fraction()) ? 1 : 0; count++; } @@ -810,7 +820,7 @@ struct test_braid2d_mat FloatType vfStorage[ARRAY_SIZE]; axom::ArrayView idView(idStorage, ARRAY_SIZE); axom::ArrayView vfView(vfStorage, ARRAY_SIZE); - const auto nmats = matsetView.zoneMaterials(index, idView, vfView); + const auto nmats = viewPackage.matsetView.zoneMaterials(index, idView, vfView); eq_count += (nmats == ids.size()) ? 1 : 0; count++; for(axom::IndexType j = 0; j < nmats; j++) From f55dc233e775ed41748ec953dfebd701941dfb7c Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 9 Jun 2026 11:12:16 -0700 Subject: [PATCH 346/986] Renamed a variable. --- src/axom/bump/tests/bump_views.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/axom/bump/tests/bump_views.cpp b/src/axom/bump/tests/bump_views.cpp index 04665d6be0..4ddecb0a95 100644 --- a/src/axom/bump/tests/bump_views.cpp +++ b/src/axom/bump/tests/bump_views.cpp @@ -765,17 +765,17 @@ struct test_braid2d_mat auto resultsView = resultsArrayDevice.view(); // Bundle the views together for device access. - ViewPackage viewPackage{matsetView, fieldView}; + ViewPackage deviceViews{matsetView, fieldView}; axom::for_all( nzones, AXOM_LAMBDA(axom::IndexType index) { typename MatsetView::IDList ids {}; typename MatsetView::VFList vfs {}; - viewPackage.matsetView.zoneMaterials(index, ids, vfs); + deviceViews.matsetView.zoneMaterials(index, ids, vfs); // Get the end iterator for the zone. - const auto end = viewPackage.matsetView.endZone(index); + const auto end = deviceViews.matsetView.endZone(index); int eq_count = 0; int count = 0; @@ -792,7 +792,7 @@ struct test_braid2d_mat // Make sure the iterator order is the same as for the values we got from zoneMaterials(). int i = 0; - for(auto it = viewPackage.matsetView.beginZone(index); it != end; it++, i++) + for(auto it = deviceViews.matsetView.beginZone(index); it != end; it++, i++) { eq_count += (vfs[i] == it.volume_fraction() && ids[i] == it.material_id()) ? 1 : 0; count++; @@ -804,9 +804,9 @@ struct test_braid2d_mat if constexpr(!std::is_same_v) { int i = 0; - for(auto it = viewPackage.matsetView.beginZone(index); it != end; it++, i++) + for(auto it = deviceViews.matsetView.beginZone(index); it != end; it++, i++) { - const auto value = viewPackage.fieldView.value(it); + const auto value = deviceViews.fieldView.value(it); eq_count += (value == it.volume_fraction()) ? 1 : 0; count++; } @@ -820,7 +820,7 @@ struct test_braid2d_mat FloatType vfStorage[ARRAY_SIZE]; axom::ArrayView idView(idStorage, ARRAY_SIZE); axom::ArrayView vfView(vfStorage, ARRAY_SIZE); - const auto nmats = viewPackage.matsetView.zoneMaterials(index, idView, vfView); + const auto nmats = deviceViews.matsetView.zoneMaterials(index, idView, vfView); eq_count += (nmats == ids.size()) ? 1 : 0; count++; for(axom::IndexType j = 0; j < nmats; j++) From f3a738cf0fedd12623843e4a1210f80ce9760f0b Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 9 Jun 2026 11:32:55 -0700 Subject: [PATCH 347/986] make style --- src/axom/bump/tests/bump_views.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/axom/bump/tests/bump_views.cpp b/src/axom/bump/tests/bump_views.cpp index 4ddecb0a95..491691e866 100644 --- a/src/axom/bump/tests/bump_views.cpp +++ b/src/axom/bump/tests/bump_views.cpp @@ -765,7 +765,7 @@ struct test_braid2d_mat auto resultsView = resultsArrayDevice.view(); // Bundle the views together for device access. - ViewPackage deviceViews{matsetView, fieldView}; + ViewPackage deviceViews {matsetView, fieldView}; axom::for_all( nzones, From cde6aed3825830673bc44f5a1a69016a405bd881 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Tue, 9 Jun 2026 11:55:03 -0700 Subject: [PATCH 348/986] Change randomness to Halton sequence --- .../detail/winding_number_3d_impl.hpp | 33 +++++++++++++++---- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/src/axom/primal/operators/detail/winding_number_3d_impl.hpp b/src/axom/primal/operators/detail/winding_number_3d_impl.hpp index aee0ad8235..9b1e9e48f1 100644 --- a/src/axom/primal/operators/detail/winding_number_3d_impl.hpp +++ b/src/axom/primal/operators/detail/winding_number_3d_impl.hpp @@ -577,10 +577,28 @@ double nurbs_winding_number(const Point& query, * of the surface at known locations. */ - // Lambda to generate an entirely random unit vector - auto random_unit = []() -> Vector { - double theta = axom::utilities::random_real(0.0, 2 * M_PI); - double u = axom::utilities::random_real(-1.0, 1.0); + // If a new cast direction is needed, use an (2, 3)-Halton sequence + // projected onto the sphere to pick the next direction + auto halton = [](int i, int base) -> T { + T f = 1.0; + T r = 0.0; + while(i > 0) + { + f /= base; + r += f * (i % base); + i /= base; + } + return r; + }; + + // Shift the entire sequence by the original cast direction + const T cast_direction_u = (1.0 - cast_direction[2]) / 2.0 - halton(1, 2); + const T cast_direction_v = + (0.5 * M_1_PI * std::atan2(cast_direction[1], cast_direction[0]) + 1.0) - halton(1, 3); + + auto recast_direction = [&](int attempt) -> Vector { + T theta = 2.0 * M_PI * std::fmod(halton(attempt + 1, 3) + cast_direction_v + 1.0, 1.0); + T u = 2.0 * std::fmod(halton(attempt + 1, 2) + cast_direction_u + 1.0, 1.0) - 1.0; return Vector {sin(theta) * sqrt(1 - u * u), cos(theta) * sqrt(1 - u * u), u}; }; @@ -594,7 +612,8 @@ double nurbs_winding_number(const Point& query, auto request_recast = [&]() -> bool { if(can_recast) { - cast_direction_local = random_unit(); + cast_direction_local = recast_direction(recast_attempt + 1); + return true; } return false; @@ -848,8 +867,8 @@ double nurbs_winding_number(const Point& query, // with a cast ray that is mostly in the direction of the normal (assuming it's non-zero) Vector new_cast_direction = the_disk.normal(up[i], vp[i]); new_cast_direction = (new_cast_direction.norm() < EPS) - ? random_unit() - : (new_cast_direction.unitVector() + 0.1 * random_unit()).unitVector(); + ? recast_direction(recast_attempt + 1) + : (new_cast_direction.unitVector() + 0.1 * recast_direction(recast_attempt + 1)).unitVector(); the_gwn += nurbs_winding_number(query, the_disk, From 6165404d6b2c3541efb1c83b5ca6dabd40cbfc4f Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Tue, 9 Jun 2026 11:55:13 -0700 Subject: [PATCH 349/986] Change randomness to use deterministic seed --- .../detail/winding_number_3d_memoization.hpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp b/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp index b7eed08dc5..392cac110f 100644 --- a/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp +++ b/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp @@ -268,12 +268,19 @@ class NURBSPatchGWNCache // For symmetric patches (e.g. cylinders), the numerator can be close to 0 due to cancellation. constexpr double k_dir_eps = 1e-3; - // Generate a random direction - double theta = axom::utilities::random_real(0.0, 2 * M_PI); - double u = axom::utilities::random_real(-1.0, 1.0); + // Generate a random direction with simple hashes + unsigned int seed1 = + std::hash {}(m_bBox.getMin()[0] + m_bBox.getMin()[1] + m_bBox.getMin()[2]); + unsigned int seed2 = + std::hash {}(m_bBox.getMax()[0] + m_bBox.getMax()[1] + m_bBox.getMax()[2]); + + double theta = axom::utilities::random_real(0.0, 2 * M_PI, seed1); + double u = axom::utilities::random_real(-1.0, 1.0, seed2); const auto random_unit = Vector {sin(theta) * sqrt(1 - u * u), cos(theta) * sqrt(1 - u * u), u}; + m_castDirection = m_normal.unitVector(); + // If the average normal is too small, use the random direction as-is if((m_surfaceArea <= 0.0) || (m_normal.norm() / m_surfaceArea) < k_dir_eps) { From 5a648968704d30ad18ea03e52a45eab2a2c17028 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Tue, 9 Jun 2026 12:12:26 -0700 Subject: [PATCH 350/986] Remove unnecessary normalization --- .../primal/operators/detail/winding_number_3d_memoization.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp b/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp index 392cac110f..bafbd18b6b 100644 --- a/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp +++ b/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp @@ -279,8 +279,6 @@ class NURBSPatchGWNCache const auto random_unit = Vector {sin(theta) * sqrt(1 - u * u), cos(theta) * sqrt(1 - u * u), u}; - m_castDirection = m_normal.unitVector(); - // If the average normal is too small, use the random direction as-is if((m_surfaceArea <= 0.0) || (m_normal.norm() / m_surfaceArea) < k_dir_eps) { From 3cf3ca1e6b992e95fabd6b57f1dfdb1473ac1027 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 2 Jun 2026 18:46:51 -0700 Subject: [PATCH 351/986] Adds contructors to primal::KnotVector that do not perform validity checks This allows the user to call `isValid()` and handle errors. --- src/axom/primal/geometry/KnotVector.hpp | 72 ++++++++++++++++++++++--- 1 file changed, 65 insertions(+), 7 deletions(-) diff --git a/src/axom/primal/geometry/KnotVector.hpp b/src/axom/primal/geometry/KnotVector.hpp index 035e4cc0cc..8199223d5a 100644 --- a/src/axom/primal/geometry/KnotVector.hpp +++ b/src/axom/primal/geometry/KnotVector.hpp @@ -51,6 +51,15 @@ class KnotVector "A knot vector must be defined using an arithmetic type"); public: + /*! + * \brief Tag type to construct a KnotVector without asserting validity in the constructor + * + * This enables callers to construct a KnotVector from potentially-invalid input, + * then explicitly check \a isValid() and handle errors gracefully. + */ + struct SkipValidityChecks + { }; + ///@{ /** * \name Constructors for KnotVector @@ -75,18 +84,34 @@ class KnotVector * \pre The \a knots can be empty when the degree is -1, otherwise knots.data() is not \a nullptr * \sa isValid() tests conditions for a valid knot span instance */ - KnotVector(axom::ArrayView knots, int degree) : m_deg(degree) + KnotVector(axom::ArrayView knots, int degree) + : KnotVector(knots, degree, SkipValidityChecks {}) { - SLIC_ASSERT(degree >= -1); - SLIC_ASSERT(knots.size() >= (degree + 1)); - SLIC_ASSERT(knots.empty() || knots.data() != nullptr); + SLIC_ASSERT(isValid()); + } - if(!knots.empty()) + /*! + * \brief Constructor from a user-supplied knot vector without asserting \a isValid() + * + * \param [in] knots the knot vector + * \param [in] degree the degree of the curve + * \param [in] SkipValidityChecks tag to indicate validity is not asserted + * + * \post The KnotVector degree is at least -1 + * \post The KnotVector values will be copied when the input \a knots is non-empty and non-null + * \note The resulting KnotVector may be invalid; call \a isValid() to verify. + */ + KnotVector(axom::ArrayView knots, int degree, SkipValidityChecks) + : m_deg(axom::utilities::max(degree, -1)) + { + if(knots.empty() || knots.data() == nullptr) + { + m_deg = -1; + } + else { m_knots = knots; } - - SLIC_ASSERT(isValid()); } /*! @@ -100,6 +125,15 @@ class KnotVector : KnotVector(axom::ArrayView(knots.data(), knots.size()), degree) { } + /*! + * \brief Constructor from a user-supplied knot vector (axom::ArrayView) without asserting \a isValid() + * + * \overload for ArrayView of non-const T + */ + KnotVector(axom::ArrayView knots, int degree, SkipValidityChecks) + : KnotVector(axom::ArrayView(knots.data(), knots.size()), degree, SkipValidityChecks {}) + { } + /// \brief Default constructor for an empty (invalid) knot vector KnotVector() : m_deg(-1) { } @@ -126,6 +160,15 @@ class KnotVector : KnotVector(axom::ArrayView(knots, nkts), degree) { } + /*! + * \brief Constructor from a user-supplied knot vector (C-style array) without asserting \a isValid() + * + * \see KnotVector(axom::ArrayView knots, int degree, SkipValidityChecks) + */ + KnotVector(const T* knots, axom::IndexType nkts, int degree, SkipValidityChecks) + : KnotVector(axom::ArrayView(knots, nkts), degree, SkipValidityChecks {}) + { } + /*! * \brief Constructor from a user-supplied knot vector (axom::Array) * @@ -136,6 +179,15 @@ class KnotVector */ KnotVector(const axom::Array& knots, int degree) : KnotVector(knots.view(), degree) { } + /*! + * \brief Constructor from a user-supplied knot vector (axom::Array) without asserting \a isValid() + * + * \see KnotVector(axom::ArrayView knots, int degree, SkipValidityChecks) + */ + KnotVector(const axom::Array& knots, int degree, SkipValidityChecks) + : KnotVector(knots.view(), degree, SkipValidityChecks {}) + { } + ///@} ///@{ @@ -228,6 +280,12 @@ class KnotVector return false; } + // Check knots vector has the correct size and data + if(m_knots.size() < (m_deg + 1 || m_knots.data() == nullptr)) + { + return false; + } + // Check for monotonicity for(int i = 0; i < m_knots.size() - 1; ++i) { From 95a6fd03523e4ed8fdc58c5746d6eacb40ac1d06 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 2 Jun 2026 18:48:50 -0700 Subject: [PATCH 352/986] Adds error handling and messages to C2CReader::readContour() --- src/axom/quest/io/C2CReader.cpp | 82 +++++++++++++++++++++++++++++++-- 1 file changed, 77 insertions(+), 5 deletions(-) diff --git a/src/axom/quest/io/C2CReader.cpp b/src/axom/quest/io/C2CReader.cpp index a2e2558f52..d55087a56e 100644 --- a/src/axom/quest/io/C2CReader.cpp +++ b/src/axom/quest/io/C2CReader.cpp @@ -57,21 +57,93 @@ int C2CReader::readContour() SLIC_INFO(fmt::format("Loading contour with {} pieces", contour.getPieces().size())); + m_nurbsData.clear(); + m_nurbsData.reserve(contour.getPieces().size()); + + int piece_index = 0; for(auto* piece : contour.getPieces()) { const auto nurbsData = c2c::toNurbs(*piece, m_lengthUnit); + // Load control points axom::Array controlPoints; + controlPoints.reserve(nurbsData.controlPoints.size()); for(const auto& pt : nurbsData.controlPoints) { controlPoints.emplace_back(PointType {pt.getZ().getValue(), pt.getR().getValue()}); } + const auto pts_view = controlPoints.view(); + const int npts = static_cast(controlPoints.size()); + + // Load and check knot vector; check degree first then knots + const auto nkts = static_cast(nurbsData.knots.size()); + const int degree = static_cast(nkts - npts - 1); + if(degree < 0) + { + SLIC_WARNING( + fmt::format("Invalid contour file '{}': computed negative NURBS degree for piece " + "{} (npts={}, nkts={})", + m_fileName, + piece_index, + npts, + nkts)); + return 1; + } + + const axom::ArrayView knots_view(nurbsData.knots.data(), nkts); + primal::KnotVector knotvec(knots_view, + degree, + primal::KnotVector::SkipValidityChecks {}); + + if(!knotvec.isValid()) + { + SLIC_WARNING( + fmt::format("Invalid contour file '{}': piece {} converted to an invalid NURBS knot vector " + "(degree={}). " + "This can happen for linear splines with duplicate points.", + m_fileName, + piece_index, + degree)); + m_nurbsData.clear(); + return 1; + } + + // Load and check weights; count must either be 0 or match control points + const axom::ArrayView wts_view( + nurbsData.weights.data(), + static_cast(nurbsData.weights.size())); + if(!wts_view.empty() && wts_view.size() != pts_view.size()) + { + SLIC_WARNING( + fmt::format("Invalid contour file '{}': piece {} has {} weights for {} control points", + m_fileName, + piece_index, + wts_view.size(), + npts)); + m_nurbsData.clear(); + return 1; + } + + // the weights are non-trivial when present and not all equal to 1 + bool has_non_trivial_weights = false; + for(const double& wt : wts_view) + { + if(wt != 1.0) + { + has_non_trivial_weights = true; + } + } + + if(has_non_trivial_weights) + { + m_nurbsData.emplace_back(pts_view, wts_view, knotvec); + } + else + { + m_nurbsData.emplace_back(pts_view, knotvec); + } - m_nurbsData.emplace_back(controlPoints.data(), - nurbsData.weights.data(), - controlPoints.size(), - nurbsData.knots.data(), - nurbsData.knots.size()); + ++piece_index; } return 0; From 9788808c57a19e3f05729561bb6f9f0524d4d53e Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 2 Jun 2026 18:54:05 -0700 Subject: [PATCH 353/986] Adds a test that attempts to read a degenerate c2c file The reader now returns a non-zero value instead of calling SLIC_ASSERT and failing silently in Debug configs, but continuing w/ bad data in release configs. --- src/axom/quest/tests/quest_c2c_reader.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/axom/quest/tests/quest_c2c_reader.cpp b/src/axom/quest/tests/quest_c2c_reader.cpp index 19fa3fc665..35dd2ddf33 100644 --- a/src/axom/quest/tests/quest_c2c_reader.cpp +++ b/src/axom/quest/tests/quest_c2c_reader.cpp @@ -5,6 +5,7 @@ // SPDX-License-Identifier: (BSD-3-Clause) #include "axom/config.hpp" +#include "axom/core/utilities/FileUtilities.hpp" #ifndef AXOM_USE_C2C #error These tests should only be included when Axom is configured with C2C @@ -228,6 +229,23 @@ TEST(quest_c2c_reader, interpolate_spline) delete mesh; } +TEST(quest_c2c_reader, duplicate_point_linear_fails_gracefully) +{ +#ifdef AXOM_DATA_DIR + // This file contains an invalid contour -- reading the files should return non-zero + const auto fileName = + axom::utilities::filesystem::joinPath(AXOM_DATA_DIR, "contours/duplicate_point_linear.contour"); + + quest::C2CReader reader; + reader.setFileName(fileName); + + EXPECT_NE(0, reader.read()); + EXPECT_EQ(0, reader.getCurvesView().size()); +#else + GTEST_SKIP() << "AXOM_DATA_DIR not defined"; +#endif +} + //------------------------------------------------------------------------------ int main(int argc, char* argv[]) From 760f7393ea128d6615e15fc4cca13741d79c5e21 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 2 Jun 2026 18:57:59 -0700 Subject: [PATCH 354/986] Handles bad c2c input in quest containment example --- .../quest/examples/containment_driver.cpp | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/axom/quest/examples/containment_driver.cpp b/src/axom/quest/examples/containment_driver.cpp index 7d6c5399a6..4febb6acc1 100644 --- a/src/axom/quest/examples/containment_driver.cpp +++ b/src/axom/quest/examples/containment_driver.cpp @@ -69,12 +69,17 @@ class ContainmentDriver } #ifdef AXOM_USE_C2C - void loadContourMesh(const std::string& inputFile, int segmentsPerKnotSpan) + bool loadContourMesh(const std::string& inputFile, int segmentsPerKnotSpan) { AXOM_ANNOTATE_SCOPE("load c2c"); quest::C2CReader reader; reader.setFileName(inputFile); - reader.read(); + const int rc = reader.read(); + if(rc != 0) + { + SLIC_WARNING(axom::fmt::format("Failed to load contour file '{}'", inputFile)); + return false; + } // Create surface mesh m_surfaceMesh.reset(new UMesh(2, mint::SEGMENT)); @@ -82,15 +87,15 @@ class ContainmentDriver lin.getLinearMeshUniform(reader.getCurvesView(), static_cast(m_surfaceMesh.get()), segmentsPerKnotSpan); + return true; } #else - void loadContourMesh(const std::string& inputFile, int segmentsPerKnotSpan) + bool loadContourMesh(const std::string&, int)) { - AXOM_UNUSED_VAR(inputFile); - AXOM_UNUSED_VAR(segmentsPerKnotSpan); - SLIC_ERROR( + SLIC_WARNING( "Configuration error: Loading contour files is only supported when Axom " "is configured with C2C support."); + return false; } #endif // AXOM_USE_C2C @@ -608,7 +613,10 @@ int main(int argc, char** argv) if(is2D) { - driver2D.loadContourMesh(params.inputFile, params.samplesPerKnotSpan); + if(!driver2D.loadContourMesh(params.inputFile, params.samplesPerKnotSpan)) + { + return 1; + } } else { From ea685cb30ec497e7aae7fa64d076c8c6c465fbd0 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 2 Jun 2026 19:37:51 -0700 Subject: [PATCH 355/986] Adds test for reading in bad contour during shaping --- src/axom/quest/DiscreteShape.cpp | 39 ++++++------------- .../quest/interface/internal/QuestHelpers.cpp | 2 +- .../quest/tests/quest_sampling_shaper.cpp | 31 +++++++++++++++ 3 files changed, 44 insertions(+), 28 deletions(-) diff --git a/src/axom/quest/DiscreteShape.cpp b/src/axom/quest/DiscreteShape.cpp index 8c6bebfda4..7f4f9eb0aa 100644 --- a/src/axom/quest/DiscreteShape.cpp +++ b/src/axom/quest/DiscreteShape.cpp @@ -152,18 +152,18 @@ std::shared_ptr DiscreteShape::createMeshRepresentation() SLIC_ERROR_ROOT_IF(file_format != "c2c", axom::fmt::format(" '{}' format requires .contour file type", file_format)); - // Get the transforms that are being applied to the mesh. Get them - // as a single concatenated matrix. + // Get the transforms that are being applied to the mesh as a single concatenated matrix auto transform = getTransforms(); - // Pass in the transform so any transformations can figure into computing the revolved volume. + // Pass in the transform so any transformations can figure into computing the revolved volume axom::mint::Mesh* meshRep = nullptr; const bool uniform = !(m_refinementType == DiscreteShape::RefinementDynamic && m_percentError > MINIMUM_PERCENT_ERROR); - #ifdef AXOM_USE_MPI + int rc = quest::internal::READ_FAILED; try { + #ifdef AXOM_USE_MPI rc = quest::internal::read_c2c_mesh(shapePath, uniform, transform, @@ -173,25 +173,7 @@ std::shared_ptr DiscreteShape::createMeshRepresentation() meshRep, m_revolvedVolume, // output arg m_comm); - } - catch(const std::exception& e) - { - SLIC_ERROR_ROOT( - axom::fmt::format("Failed to read C2C shape '{}' from file '{}'. Exception: {}", - m_shape.getName(), - shapePath, - e.what())); - } - catch(...) - { - SLIC_ERROR_ROOT(axom::fmt::format("Failed to read C2C shape '{}' from file '{}'.", - m_shape.getName(), - shapePath)); - } #else - int rc = quest::internal::READ_FAILED; - try - { rc = quest::internal::read_c2c_mesh(shapePath, uniform, transform, @@ -200,6 +182,7 @@ std::shared_ptr DiscreteShape::createMeshRepresentation() m_percentError, meshRep, m_revolvedVolume); // output arg + #endif } catch(const std::exception& e) { @@ -215,11 +198,13 @@ std::shared_ptr DiscreteShape::createMeshRepresentation() m_shape.getName(), shapePath)); } - #endif - SLIC_ERROR_ROOT_IF(rc != quest::internal::READ_SUCCESS, - axom::fmt::format("Failed to read C2C shape '{}' from file '{}'.", - m_shape.getName(), - shapePath)); + + SLIC_ERROR_ROOT_IF( + rc != quest::internal::READ_SUCCESS, + axom::fmt::format( + "Invalid C2C contour for shape '{}' from file '{}'. See earlier warnings for details.", + m_shape.getName(), + shapePath)); m_meshRep.reset(meshRep); diff --git a/src/axom/quest/interface/internal/QuestHelpers.cpp b/src/axom/quest/interface/internal/QuestHelpers.cpp index ea73192ae2..9e8a7993e6 100644 --- a/src/axom/quest/interface/internal/QuestHelpers.cpp +++ b/src/axom/quest/interface/internal/QuestHelpers.cpp @@ -400,7 +400,7 @@ int read_c2c_mesh(const std::string& file, } else { - SLIC_WARNING("reading C2C file failed, setting mesh to NULL"); + SLIC_WARNING_ROOT(axom::fmt::format("reading C2C file '{}' failed, setting mesh to NULL", file)); m = nullptr; } diff --git a/src/axom/quest/tests/quest_sampling_shaper.cpp b/src/axom/quest/tests/quest_sampling_shaper.cpp index 2f2070e9e8..6eff2b50a2 100644 --- a/src/axom/quest/tests/quest_sampling_shaper.cpp +++ b/src/axom/quest/tests/quest_sampling_shaper.cpp @@ -785,6 +785,37 @@ dimensions: 2 } } +TEST_F(SamplingShaperTest2D, duplicate_point_linear_contour_aborts) +{ +#if defined(AXOM_USE_C2C) && defined(AXOM_DATA_DIR) + const auto& testname = ::testing::UnitTest::GetInstance()->current_test_info()->name(); + + const auto contour_file = fs::joinPath(AXOM_DATA_DIR, "contours/duplicate_point_linear.contour"); + + const std::string shape_template = R"( +dimensions: 2 + +shapes: +- name: dup_linear + material: {} + geometry: + format: c2c + path: {} +)"; + + fs::TempFile shape_file(testname, ".yaml"); + shape_file.write(axom::fmt::format(axom::fmt::runtime(shape_template), "dupMat", contour_file)); + + this->validateShapeFile(shape_file.getPath()); + this->initializeShaping(shape_file.getPath()); + + slic::ScopedAbortToThrow abort_guard; + EXPECT_THROW(this->runShaping(), slic::SlicAbortException); +#else + GTEST_SKIP() << "Test requires AXOM_USE_C2C and AXOM_DATA_DIR"; +#endif +} + TEST_F(SamplingShaperTest2D, basic_circle_projector) { const auto& testname = ::testing::UnitTest::GetInstance()->current_test_info()->name(); From 49513267e00f82ed943d0b28404dadc93629bcc5 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 2 Jun 2026 21:08:04 -0700 Subject: [PATCH 356/986] Adds a `verbose` flag to `primal::KnotVector::isValid()` This let's callers understand the (first) validity failure. --- src/axom/primal/geometry/KnotVector.hpp | 55 +++++++++++++++++++++++-- src/axom/quest/io/C2CReader.cpp | 4 +- 2 files changed, 54 insertions(+), 5 deletions(-) diff --git a/src/axom/primal/geometry/KnotVector.hpp b/src/axom/primal/geometry/KnotVector.hpp index 8199223d5a..2e715887ac 100644 --- a/src/axom/primal/geometry/KnotVector.hpp +++ b/src/axom/primal/geometry/KnotVector.hpp @@ -272,17 +272,37 @@ class KnotVector } /// \brief Return if the knot vector is valid - bool isValid() const + /// + /// \param [in] verbose When true, emits a warning on the first failing check. + bool isValid(bool verbose = false) const { // Check degree if(m_deg < 0) { + SLIC_WARNING_ROOT_IF(verbose, "Invalid KnotVector: degree is negative"); return false; } - // Check knots vector has the correct size and data - if(m_knots.size() < (m_deg + 1 || m_knots.data() == nullptr)) + if(m_knots.empty()) { + SLIC_WARNING_ROOT_IF(verbose, "Invalid KnotVector: knot array is empty"); + return false; + } + + if(m_knots.data() == nullptr) + { + SLIC_WARNING_ROOT_IF(verbose, "Invalid KnotVector: knot array data pointer is null"); + return false; + } + + if(m_knots.size() < (m_deg + 1)) + { + SLIC_WARNING_ROOT_IF( + verbose, + axom::fmt::format( + "Invalid KnotVector: knot array too small for degree (degree={}, num_knots={})", + m_deg, + m_knots.size())); return false; } @@ -291,6 +311,14 @@ class KnotVector { if(m_knots[i] > m_knots[i + 1]) { + SLIC_WARNING_ROOT_IF( + verbose, + axom::fmt::format( + "Invalid KnotVector: knot vector is not monotone (knot[{}]={} > knot[{}]={})", + i, + m_knots[i], + i + 1, + m_knots[i + 1])); return false; } } @@ -303,10 +331,24 @@ class KnotVector { if(m_knots[i] != minKnot) { + SLIC_WARNING_ROOT_IF( + verbose, + axom::fmt::format( + "Invalid KnotVector: knot vector is not clamped at start (knot[{}]={} != minKnot={})", + i, + m_knots[i], + minKnot)); return false; } if(m_knots[nkts - 1 - i] != maxKnot) { + SLIC_WARNING_ROOT_IF( + verbose, + axom::fmt::format( + "Invalid KnotVector: knot vector is not clamped at end (knot[{}]={} != maxKnot={})", + nkts - 1 - i, + m_knots[nkts - 1 - i], + maxKnot)); return false; } } @@ -326,6 +368,13 @@ class KnotVector this_multiplicity++; if(this_multiplicity > m_deg) { + SLIC_WARNING_ROOT_IF( + verbose, + axom::fmt::format("Invalid KnotVector: internal knot multiplicity exceeds degree " + "(knot={}, multiplicity={}, degree={})", + this_knot, + this_multiplicity, + m_deg)); return false; } } diff --git a/src/axom/quest/io/C2CReader.cpp b/src/axom/quest/io/C2CReader.cpp index d55087a56e..d22ef43df1 100644 --- a/src/axom/quest/io/C2CReader.cpp +++ b/src/axom/quest/io/C2CReader.cpp @@ -95,12 +95,12 @@ int C2CReader::readContour() degree, primal::KnotVector::SkipValidityChecks {}); - if(!knotvec.isValid()) + if(!knotvec.isValid(true)) { SLIC_WARNING( fmt::format("Invalid contour file '{}': piece {} converted to an invalid NURBS knot vector " "(degree={}). " - "This can happen for linear splines with duplicate points.", + "See previous warning for the first invalidity reason.", m_fileName, piece_index, degree)); From 35ab68938e57c0d7872591de371fedfed3bdb539 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 2 Jun 2026 21:12:48 -0700 Subject: [PATCH 357/986] Updates data submodule --- data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data b/data index 1a77bc5d81..216dfb6c3b 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 1a77bc5d81ef1c3e007d7d634238b488cbdf60d4 +Subproject commit 216dfb6c3b1717d1782efb5d1070646f237e5f6f From f9e50cdd81ed52cf2fb6b0357483a496d2357900 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 2 Jun 2026 21:15:33 -0700 Subject: [PATCH 358/986] Updates RELEASE-NOTES --- RELEASE-NOTES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 1053530907..4b5d02911a 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -36,6 +36,8 @@ 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` +- Primal: Adds KnotVector constructors that skip validity assertion checks, allowing the user to call `isValid()` + and handle the error appropriately. ### Removed @@ -47,6 +49,7 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ ### Fixed - Primal: Fixes signs of `compute_moments` to match orientation convention in `primal::evaluate_area_integral` +- Quest: Improves error handling/reporting when loading an invalid c2c contour ## [Version 0.14.0] - Release date 2026-03-31 From c2b4175ebfb2ac472f6b248484cdd37da8340a88 Mon Sep 17 00:00:00 2001 From: Kenny Weiss Date: Wed, 3 Jun 2026 09:39:58 -0700 Subject: [PATCH 359/986] Fix typo --- src/axom/quest/examples/containment_driver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/axom/quest/examples/containment_driver.cpp b/src/axom/quest/examples/containment_driver.cpp index 4febb6acc1..b5a5b5e664 100644 --- a/src/axom/quest/examples/containment_driver.cpp +++ b/src/axom/quest/examples/containment_driver.cpp @@ -90,7 +90,7 @@ class ContainmentDriver return true; } #else - bool loadContourMesh(const std::string&, int)) + bool loadContourMesh(const std::string&, int) { SLIC_WARNING( "Configuration error: Loading contour files is only supported when Axom " From 811605f267cfa1edd4e24fca7528d9f93c58ae09 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 3 Jun 2026 10:53:03 -0700 Subject: [PATCH 360/986] Improves consistency of KnotVector error checks and messages --- src/axom/primal/geometry/KnotVector.hpp | 6 +++--- src/axom/quest/examples/containment_driver.cpp | 4 +++- src/axom/quest/io/C2CReader.cpp | 12 ++++-------- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/axom/primal/geometry/KnotVector.hpp b/src/axom/primal/geometry/KnotVector.hpp index 2e715887ac..f7b31ae2a5 100644 --- a/src/axom/primal/geometry/KnotVector.hpp +++ b/src/axom/primal/geometry/KnotVector.hpp @@ -87,7 +87,7 @@ class KnotVector KnotVector(axom::ArrayView knots, int degree) : KnotVector(knots, degree, SkipValidityChecks {}) { - SLIC_ASSERT(isValid()); + SLIC_ASSERT(isValid(true)); } /*! @@ -97,12 +97,12 @@ class KnotVector * \param [in] degree the degree of the curve * \param [in] SkipValidityChecks tag to indicate validity is not asserted * - * \post The KnotVector degree is at least -1 + * \post The KnotVector degree is clamped to be at least -1 * \post The KnotVector values will be copied when the input \a knots is non-empty and non-null * \note The resulting KnotVector may be invalid; call \a isValid() to verify. */ KnotVector(axom::ArrayView knots, int degree, SkipValidityChecks) - : m_deg(axom::utilities::max(degree, -1)) + : m_deg(axom::utilities::clampLower(degree, -1)) { if(knots.empty() || knots.data() == nullptr) { diff --git a/src/axom/quest/examples/containment_driver.cpp b/src/axom/quest/examples/containment_driver.cpp index b5a5b5e664..4f0c073d04 100644 --- a/src/axom/quest/examples/containment_driver.cpp +++ b/src/axom/quest/examples/containment_driver.cpp @@ -77,7 +77,9 @@ class ContainmentDriver const int rc = reader.read(); if(rc != 0) { - SLIC_WARNING(axom::fmt::format("Failed to load contour file '{}'", inputFile)); + SLIC_WARNING( + axom::fmt::format("Failed to load contour file '{}'. See earlier warnings for details.", + inputFile)); return false; } diff --git a/src/axom/quest/io/C2CReader.cpp b/src/axom/quest/io/C2CReader.cpp index d22ef43df1..bb5e5f775a 100644 --- a/src/axom/quest/io/C2CReader.cpp +++ b/src/axom/quest/io/C2CReader.cpp @@ -72,7 +72,6 @@ int C2CReader::readContour() { controlPoints.emplace_back(PointType {pt.getZ().getValue(), pt.getR().getValue()}); } - const auto pts_view = controlPoints.view(); const int npts = static_cast(controlPoints.size()); // Load and check knot vector; check degree first then knots @@ -99,12 +98,10 @@ int C2CReader::readContour() { SLIC_WARNING( fmt::format("Invalid contour file '{}': piece {} converted to an invalid NURBS knot vector " - "(degree={}). " - "See previous warning for the first invalidity reason.", + "(degree={}).", m_fileName, piece_index, degree)); - m_nurbsData.clear(); return 1; } @@ -112,7 +109,7 @@ int C2CReader::readContour() const axom::ArrayView wts_view( nurbsData.weights.data(), static_cast(nurbsData.weights.size())); - if(!wts_view.empty() && wts_view.size() != pts_view.size()) + if(!wts_view.empty() && wts_view.size() != controlPoints.size()) { SLIC_WARNING( fmt::format("Invalid contour file '{}': piece {} has {} weights for {} control points", @@ -120,7 +117,6 @@ int C2CReader::readContour() piece_index, wts_view.size(), npts)); - m_nurbsData.clear(); return 1; } @@ -136,11 +132,11 @@ int C2CReader::readContour() if(has_non_trivial_weights) { - m_nurbsData.emplace_back(pts_view, wts_view, knotvec); + m_nurbsData.emplace_back(controlPoints.view(), wts_view, knotvec); } else { - m_nurbsData.emplace_back(pts_view, knotvec); + m_nurbsData.emplace_back(controlPoints.view(), knotvec); } ++piece_index; From 7d74efc02e2248c81a0b0bea5457f113b2ff82de Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 3 Jun 2026 10:53:38 -0700 Subject: [PATCH 361/986] Adds unit tests for new KnotVector constructors and validity checks --- src/axom/primal/tests/primal_knot_vector.cpp | 146 +++++++++++++++++++ 1 file changed, 146 insertions(+) diff --git a/src/axom/primal/tests/primal_knot_vector.cpp b/src/axom/primal/tests/primal_knot_vector.cpp index f162ce0dfd..1adf57d71a 100644 --- a/src/axom/primal/tests/primal_knot_vector.cpp +++ b/src/axom/primal/tests/primal_knot_vector.cpp @@ -439,6 +439,152 @@ TEST(primal_knotvector, split) } } +//------------------------------------------------------------------------------ +TEST(primal_knotvector, skip_validity_checks_constructors) +{ + using SkipTag = primal::KnotVector::SkipValidityChecks; + + constexpr int degree = 2; + constexpr int nkts = 9; + double valid_knots[] = {0.0, 0.0, 0.0, 0.2, 0.5, 0.8, 1.0, 1.0, 1.0}; + + // Test C-array constructor with SkipValidityChecks + { + primal::KnotVector kvector(valid_knots, nkts, degree, SkipTag {}); + EXPECT_TRUE(kvector.isValid()); + EXPECT_EQ(degree, kvector.getDegree()); + EXPECT_EQ(nkts, kvector.getNumKnots()); + } + + // Test ArrayView constructor with SkipValidityChecks + { + axom::ArrayView knots_v(valid_knots, nkts); + primal::KnotVector kvector(knots_v, degree, SkipTag {}); + EXPECT_TRUE(kvector.isValid()); + EXPECT_EQ(degree, kvector.getDegree()); + } + + // Test ArrayView constructor with SkipValidityChecks + { + axom::ArrayView knots_v(valid_knots, nkts); + primal::KnotVector kvector(knots_v, degree, SkipTag {}); + EXPECT_TRUE(kvector.isValid()); + EXPECT_EQ(degree, kvector.getDegree()); + } + + // Test Array constructor with SkipValidityChecks + { + axom::Array knot_arr; + knot_arr.assign(std::begin(valid_knots), std::end(valid_knots)); + primal::KnotVector kvector(knot_arr, degree, SkipTag {}); + EXPECT_TRUE(kvector.isValid()); + EXPECT_EQ(degree, kvector.getDegree()); + } + + // Test that invalid input doesn't assert with SkipValidityChecks + { + double invalid_knots[] = {0.0, 0.0}; // Too few knots + primal::KnotVector kvector(invalid_knots, 2, degree, SkipTag {}); + EXPECT_FALSE(kvector.isValid()); + } +} + +//------------------------------------------------------------------------------ +TEST(primal_knotvector, validity_checks) +{ + using SkipTag = primal::KnotVector::SkipValidityChecks; + + // negative degree is invalid + { + const int degree = -5; + double knots[] = {0.0, 0.0, 1.0, 1.0}; + primal::KnotVector kvector(knots, 4, degree, SkipTag {}); + + EXPECT_FALSE(kvector.isValid()); + // Degree should be clamped to -1 + EXPECT_EQ(-1, kvector.getDegree()); + } + + // empty knot vector is invalid + { + axom::ArrayView empty_knots(nullptr, 0); + primal::KnotVector kvector(empty_knots, 2, SkipTag {}); + + EXPECT_FALSE(kvector.isValid()); + EXPECT_EQ(0, kvector.getNumKnots()); + } + + // null knot vector is invalid + { + axom::ArrayView null_knots(nullptr, 5); + primal::KnotVector kvector(null_knots, 2, SkipTag {}); + + EXPECT_FALSE(kvector.isValid()); + } + + // knot vector needs to be sufficiently large for the given degree + { + constexpr int degree = 3; + double knots[] = {0.0, 0.0, 1.0}; // Need at least degree+1 = 4 knots + + primal::KnotVector kvector(knots, 3, degree, SkipTag {}); + + EXPECT_FALSE(kvector.isValid()); + } + + // knot vector needs to be monotonic + { + constexpr int degree = 2; + double knots[] = {0.0, 0.0, 0.0, 0.5, 0.3, 1.0, 1.0, 1.0}; // 0.5 > 0.3 + + primal::KnotVector kvector(knots, 8, degree, SkipTag {}); + + EXPECT_FALSE(kvector.isValid()); + } + + // knot vector needs to be clamped at start + { + constexpr int degree = 2; + double knots[] = {0.0, 0.0, 0.1, 0.5, 0.8, 1.0, 1.0, 1.0}; // Start not clamped + + primal::KnotVector kvector(knots, 8, degree, SkipTag {}); + + EXPECT_FALSE(kvector.isValid()); + } + + // .. and at end + { + constexpr int degree = 2; + double knots[] = {0.0, 0.0, 0.0, 0.5, 0.8, 0.9, 1.0, 1.0}; // End not clamped + + primal::KnotVector kvector(knots, 8, degree, SkipTag {}); + + EXPECT_FALSE(kvector.isValid()); + } + + // knot vector multiplicities cannot be larger than degree + { + constexpr int degree = 2; + // Internal knot 0.5 has multiplicity 3, which exceeds degree 2 + double knots[] = {0.0, 0.0, 0.0, 0.5, 0.5, 0.5, 1.0, 1.0, 1.0}; + + primal::KnotVector kvector(knots, 9, degree, SkipTag {}); + + EXPECT_FALSE(kvector.isValid()); + } + + // .. but can equal degree + { + constexpr int degree = 3; + // Internal knot 0.5 has multiplicity 3, which equals degree - this should be valid + double knots[] = {0.0, 0.0, 0.0, 0.0, 0.5, 0.5, 0.5, 1.0, 1.0, 1.0, 1.0}; + + primal::KnotVector kvector(knots, 11, degree, SkipTag {}); + + EXPECT_TRUE(kvector.isValid()); + } +} + int main(int argc, char* argv[]) { int result = 0; From 624c4f080d7478dfd5bf54cf55b358d78d36bd8b Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 3 Jun 2026 11:35:37 -0700 Subject: [PATCH 362/986] Fixes use-after-free issue Misc: Updates docs, and early return optimization --- src/axom/primal/geometry/KnotVector.hpp | 16 +++++-- src/axom/quest/io/C2CReader.cpp | 55 +++++++++++++++---------- 2 files changed, 47 insertions(+), 24 deletions(-) diff --git a/src/axom/primal/geometry/KnotVector.hpp b/src/axom/primal/geometry/KnotVector.hpp index f7b31ae2a5..784d05e4f4 100644 --- a/src/axom/primal/geometry/KnotVector.hpp +++ b/src/axom/primal/geometry/KnotVector.hpp @@ -271,9 +271,19 @@ class KnotVector m_knots.clear(); } - /// \brief Return if the knot vector is valid - /// - /// \param [in] verbose When true, emits a warning on the first failing check. + /*! + * \brief Return if the knot vector is valid + * + * Checks that the knot vector satisfies all requirements for a valid B-spline/NURBS knot vector: + * degree >= 0, sufficient knots, monotonic sequence, clamped ends, and valid internal multiplicities. + * + * \param [in] verbose When true, emits a warning message describing the first failing check. + * + * \note Only the *first* validation error is reported when \a verbose is true. + * Fix that error and call \a isValid(true) again to discover subsequent errors. + * + * \return true if the knot vector satisfies all validity conditions, false otherwise + */ bool isValid(bool verbose = false) const { // Check degree diff --git a/src/axom/quest/io/C2CReader.cpp b/src/axom/quest/io/C2CReader.cpp index bb5e5f775a..baeaf914f0 100644 --- a/src/axom/quest/io/C2CReader.cpp +++ b/src/axom/quest/io/C2CReader.cpp @@ -106,37 +106,50 @@ int C2CReader::readContour() } // Load and check weights; count must either be 0 or match control points - const axom::ArrayView wts_view( - nurbsData.weights.data(), - static_cast(nurbsData.weights.size())); - if(!wts_view.empty() && wts_view.size() != controlPoints.size()) + axom::Array weights; + if(!nurbsData.weights.empty()) { - SLIC_WARNING( - fmt::format("Invalid contour file '{}': piece {} has {} weights for {} control points", - m_fileName, - piece_index, - wts_view.size(), - npts)); - return 1; - } + if(static_cast(nurbsData.weights.size()) != controlPoints.size()) + { + SLIC_WARNING( + fmt::format("Invalid contour file '{}': piece {} has {} weights for {} control points", + m_fileName, + piece_index, + nurbsData.weights.size(), + npts)); + return 1; + } - // the weights are non-trivial when present and not all equal to 1 - bool has_non_trivial_weights = false; - for(const double& wt : wts_view) - { - if(wt != 1.0) + // Check if weights are non-trivial (present and not all equal to 1) + bool has_non_trivial_weights = false; + for(const double& wt : nurbsData.weights) + { + if(wt != 1.0) + { + has_non_trivial_weights = true; + break; + } + } + + // Only copy weights if they are non-trivial + if(has_non_trivial_weights) { - has_non_trivial_weights = true; + weights.reserve(nurbsData.weights.size()); + for(const double& wt : nurbsData.weights) + { + weights.push_back(wt); + } } } - if(has_non_trivial_weights) + // Construct NURBSCurve using Array constructors to avoid use-after-free + if(weights.empty()) { - m_nurbsData.emplace_back(controlPoints.view(), wts_view, knotvec); + m_nurbsData.emplace_back(controlPoints, knotvec); } else { - m_nurbsData.emplace_back(controlPoints.view(), knotvec); + m_nurbsData.emplace_back(controlPoints, weights, knotvec); } ++piece_index; From 6c95ca5f96c3fc5ea37fbfbea5389b40f86220b3 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 3 Jun 2026 11:36:02 -0700 Subject: [PATCH 363/986] Fixes misc warning from axom@develop --- src/axom/quest/examples/quest_candidates_example.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/axom/quest/examples/quest_candidates_example.cpp b/src/axom/quest/examples/quest_candidates_example.cpp index 3d33399a11..239fd188ee 100644 --- a/src/axom/quest/examples/quest_candidates_example.cpp +++ b/src/axom/quest/examples/quest_candidates_example.cpp @@ -941,7 +941,7 @@ int main(int argc, char** argv) // Print total number of pairs across all ranks int totalNumCandidates = 0; - MPI_Reduce(&numCandidates, &totalNumCandidates, 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD); + MPI_Reduce(const_cast(&numCandidates), &totalNumCandidates, 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD); if(myRank == 0) { SLIC_INFO(axom::fmt::format(axom::utilities::locale(), From 5ee0d6c385cf28a32e0a95655e27745fc72862b7 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 3 Jun 2026 11:56:25 -0700 Subject: [PATCH 364/986] Improves validity check in primal::KnotVector Since we require clamped knots, the number of knots needs to be at least 2*(degree+1). --- src/axom/primal/geometry/KnotVector.hpp | 16 +++++++++------- src/axom/primal/tests/primal_knot_vector.cpp | 9 +++++++++ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/axom/primal/geometry/KnotVector.hpp b/src/axom/primal/geometry/KnotVector.hpp index 784d05e4f4..399b50f1c7 100644 --- a/src/axom/primal/geometry/KnotVector.hpp +++ b/src/axom/primal/geometry/KnotVector.hpp @@ -305,14 +305,16 @@ class KnotVector return false; } - if(m_knots.size() < (m_deg + 1)) + // For clamped knot vectors, we require at least (p+1) repeated knots at each end + const axom::IndexType min_num_knots = static_cast(2 * (m_deg + 1)); + if(m_knots.size() < min_num_knots) { - SLIC_WARNING_ROOT_IF( - verbose, - axom::fmt::format( - "Invalid KnotVector: knot array too small for degree (degree={}, num_knots={})", - m_deg, - m_knots.size())); + SLIC_WARNING_ROOT_IF(verbose, + axom::fmt::format("Invalid KnotVector: knot array too small for degree " + "(degree={}, num_knots={}, min_num_knots={})", + m_deg, + m_knots.size(), + min_num_knots)); return false; } diff --git a/src/axom/primal/tests/primal_knot_vector.cpp b/src/axom/primal/tests/primal_knot_vector.cpp index 1adf57d71a..5d3efe57f7 100644 --- a/src/axom/primal/tests/primal_knot_vector.cpp +++ b/src/axom/primal/tests/primal_knot_vector.cpp @@ -532,6 +532,15 @@ TEST(primal_knotvector, validity_checks) EXPECT_FALSE(kvector.isValid()); } + // knot vector needs enough knots for a clamped vector + { + constexpr int degree = 2; + double knots[] = {0.0, 0.0, 0.0}; + + primal::KnotVector kvector(knots, 3, degree, SkipTag {}); + EXPECT_FALSE(kvector.isValid()); + } + // knot vector needs to be monotonic { constexpr int degree = 2; From ec95b16a9de3f8997b1a0d4d2587140468e9108d Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 3 Jun 2026 11:59:30 -0700 Subject: [PATCH 365/986] Fixes consistency in c2c reader when given bad input Use local curve array and swap after all curves are successfully read. --- src/axom/quest/io/C2CReader.cpp | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/src/axom/quest/io/C2CReader.cpp b/src/axom/quest/io/C2CReader.cpp index baeaf914f0..d9f05c83f0 100644 --- a/src/axom/quest/io/C2CReader.cpp +++ b/src/axom/quest/io/C2CReader.cpp @@ -17,6 +17,7 @@ #include #include +#include namespace axom { @@ -31,6 +32,9 @@ int C2CReader::read() using axom::utilities::string::endsWith; + // Always clear prior results so callers never observe stale curves after a failed read + this->clear(); + int ret = 1; if(endsWith(m_fileName, ".contour")) @@ -57,8 +61,9 @@ int C2CReader::readContour() SLIC_INFO(fmt::format("Loading contour with {} pieces", contour.getPieces().size())); - m_nurbsData.clear(); - m_nurbsData.reserve(contour.getPieces().size()); + // Build results transactionally so we don't retain partial curves on error + CurveArray nurbs_data; + nurbs_data.reserve(contour.getPieces().size()); int piece_index = 0; for(auto* piece : contour.getPieces()) @@ -73,6 +78,10 @@ int C2CReader::readContour() controlPoints.emplace_back(PointType {pt.getZ().getValue(), pt.getR().getValue()}); } const int npts = static_cast(controlPoints.size()); + if(npts <= 0) + { + continue; + } // Load and check knot vector; check degree first then knots const auto nkts = static_cast(nurbsData.knots.size()); @@ -89,6 +98,19 @@ int C2CReader::readContour() return 1; } + if(npts <= degree) + { + SLIC_WARNING( + fmt::format("Invalid contour file '{}': piece {} has too few control points for degree " + "(degree={}, npts={}, nkts={})", + m_fileName, + piece_index, + degree, + npts, + nkts)); + return 1; + } + const axom::ArrayView knots_view(nurbsData.knots.data(), nkts); primal::KnotVector knotvec(knots_view, degree, @@ -145,16 +167,17 @@ int C2CReader::readContour() // Construct NURBSCurve using Array constructors to avoid use-after-free if(weights.empty()) { - m_nurbsData.emplace_back(controlPoints, knotvec); + nurbs_data.emplace_back(controlPoints, knotvec); } else { - m_nurbsData.emplace_back(controlPoints, weights, knotvec); + nurbs_data.emplace_back(controlPoints, weights, knotvec); } ++piece_index; } + m_nurbsData = std::move(nurbs_data); return 0; } From 4958d46ad03fbc7691a93058651ba15c7e9c1b12 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 9 Jun 2026 12:48:36 -0700 Subject: [PATCH 366/986] Updates data submodule to main after adding new dataset --- data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data b/data index 216dfb6c3b..55e6d239e8 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 216dfb6c3b1717d1782efb5d1070646f237e5f6f +Subproject commit 55e6d239e80593acca6b96ce38b2c9b0a6e6ae3e From 5dd570cc63a48786c8bc85d4026c12136483c9af Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Thu, 11 Jun 2026 08:56:27 -0700 Subject: [PATCH 367/986] Update release notes --- RELEASE-NOTES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 4b5d02911a..e505bc123a 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -50,6 +50,7 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ ### Fixed - Primal: Fixes signs of `compute_moments` to match orientation convention in `primal::evaluate_area_integral` - Quest: Improves error handling/reporting when loading an invalid c2c contour +- Primal: Improves reproducibility of 3D GWN methods by removing some sources of randomness ## [Version 0.14.0] - Release date 2026-03-31 From 319bf60ea88004b5ed896f0ac7f4d7246c26faf8 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Thu, 11 Jun 2026 11:42:16 -0700 Subject: [PATCH 368/986] Verify determinism --- src/axom/primal/tests/primal_solid_angle.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/axom/primal/tests/primal_solid_angle.cpp b/src/axom/primal/tests/primal_solid_angle.cpp index da59455f3a..f9e1606331 100644 --- a/src/axom/primal/tests/primal_solid_angle.cpp +++ b/src/axom/primal/tests/primal_solid_angle.cpp @@ -674,6 +674,13 @@ TEST(primal_solid_angle, teardrop_regression_test) const bool calc_containment = (std::round(gwn_array[n]) != 0); EXPECT_EQ(calc_containment, true_containment_arr[n]); } + + // Verify determinism of GWN calculation + double gwn_1 = axom::primal::winding_number(query_arr[tot_npts / 2], teardrop_shape[0]); + double gwn_2 = axom::primal::winding_number(query_arr[tot_npts / 2], teardrop_shape[0]); + + // Should be equal in double precision + EXPECT_EQ(gwn_1, gwn_2); } int main(int argc, char** argv) From 36d4c568f172a6afa33b8a606f9898fe2eee7cd1 Mon Sep 17 00:00:00 2001 From: Rich Hornung Date: Thu, 11 Jun 2026 13:17:52 -0700 Subject: [PATCH 369/986] simplify description of dependencies --- src/index.rst | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/src/index.rst b/src/index.rst index 60995d9126..367db72141 100644 --- a/src/index.rst +++ b/src/index.rst @@ -23,7 +23,7 @@ emphasize the following principles in software design and implementation: * Start design and implementation based on concrete application use cases and maintain flexibility to meet the needs of a diverse set of applications * Develop high-quality, robust, high performance software that has well-designed APIs, good documentation, and solid testing - * Apply consistent software engineering practices across all Axom components so developers can easily work on them + * Apply consistent software engineering practices across all Axom components so developers can easily work on any of them * Ensure that components integrate well together and are easy for applications to adopt The main drivers of Axom capabilities originate in the needs of multiphysics @@ -42,21 +42,22 @@ Axom software components are maintained and developed on the .. note:: While Axom is developed in C++, its components have native interfaces in C and Fortran for straightforward usage in applications developed in those languages. Python interfaces - are in development. + for select components are in development. Our current collection of components is listed here. The number of components and their capabilities will expand over time as new needs are identified. + * Core: Basic utilities and data structures used throughout Axom * Bump: Blueprint Utilities for Mesh Processing * Inlet: Input file parsing and information storage/retrieval - * Klee: Shaping specification and implementation + * Klee: Material shaping specification and implementation * Lumberjack: Scalable parallel message logging and filtering * Mint: Mesh data model - * Mir: (Material interface reconstruction) + * Mir: Material interface reconstruction * Multimat: Managing multimaterial field data * Primal: Computational geometry primitives - * Quest: Querying on surface tool + * Quest: Querying on surface tools * Sidre: Simulation data repository * Sina: Simulation insight and analysis * Slam: Set-theoretic lightweight API for meshes @@ -128,16 +129,23 @@ Component Level Dependencies Axom has the following inter-component dependencies: -- Core has no dependencies and the other components depend on Core -- Bump depends on Sidre, Slic, Spin, and Primal. +- Core has no dependencies +- Most other components depend on Core, either directly or transitively + +Additionally, + +- Bump depends on Slic, Sidre, Spin, and Primal +- Inlet depends on Slic, Sidre and Primal +- Klee depends on Slic, Sidre, Inlet, and Primal +- Mint depends on Slic, Slam, and optionally Sidre +- Mir depends on Slic, Bump, Slam, Sidre, and Primal. +- Multimat depends on Slic and Slam +- Primal depends on Slic +- Quest depends on Slic, Mint, Primal, Slam, Spin, and optionally, Klee and Sidre +- Sidre depends on Slic +- Slam depends on Slic - Slic optionally depends on Lumberjack -- Slam, Spin, Primal, Mint, Quest, and Sidre depend on Slic -- Mint depends on Slam, and optionally Sidre -- Mir depends on Bump, Slic, Slam, and Primal. -- Multimat depends on Slic, and Slam -- Inlet depends on Sidre, Slic, and Primal -- Klee depends on Sidre, Slic, Inlet and Primal -- Quest depends on Slam, Spin, Primal, Mint, and, optionally, Klee, Bump and Sidre +- Spin depends on Slic, Primal, and Slam The figure below summarizes these dependencies. Solid links indicate hard dependencies; dashed links indicate optional dependencies. From 94980a9c411af4ee345ca28330ee2a201a4d408f Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 11 Jun 2026 17:34:09 -0700 Subject: [PATCH 370/986] 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 371/986] 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 661df3bba28efa5fb944769dde305ca571808bc9 Mon Sep 17 00:00:00 2001 From: Chris White Date: Fri, 12 Jun 2026 12:20:21 -0700 Subject: [PATCH 372/986] add ability to have collections with variant values --- src/axom/inlet/CMakeLists.txt | 1 + src/axom/inlet/ConduitReader.cpp | 66 ++++++++++ src/axom/inlet/ConduitReader.hpp | 6 + src/axom/inlet/Container.cpp | 64 ++++++++++ src/axom/inlet/Container.hpp | 79 +++++++++++- src/axom/inlet/Inlet.hpp | 31 +++++ src/axom/inlet/LuaReader.cpp | 76 +++++++++++ src/axom/inlet/LuaReader.hpp | 9 ++ src/axom/inlet/Reader.hpp | 19 +++ src/axom/inlet/VariantKey.hpp | 9 ++ src/axom/inlet/VariantValue.hpp | 32 +++++ src/axom/inlet/docs/sphinx/simple_types.rst | 110 +++++++++++++++- src/axom/inlet/examples/CMakeLists.txt | 8 ++ .../examples/homogeneous_collections.cpp | 106 ++++++++++++++++ .../inlet/examples/variant_collections.cpp | 118 ++++++++++++++++++ src/axom/inlet/tests/inlet_Reader.cpp | 19 +++ src/axom/inlet/tests/inlet_object.cpp | 44 +++++++ 17 files changed, 789 insertions(+), 8 deletions(-) create mode 100644 src/axom/inlet/VariantValue.hpp create mode 100644 src/axom/inlet/examples/homogeneous_collections.cpp create mode 100644 src/axom/inlet/examples/variant_collections.cpp diff --git a/src/axom/inlet/CMakeLists.txt b/src/axom/inlet/CMakeLists.txt index 02622710db..544fea9c53 100644 --- a/src/axom/inlet/CMakeLists.txt +++ b/src/axom/inlet/CMakeLists.txt @@ -25,6 +25,7 @@ set(inlet_headers Container.hpp Proxy.hpp VariantKey.hpp + VariantValue.hpp Verifiable.hpp VerifiableScalar.hpp Writer.hpp diff --git a/src/axom/inlet/ConduitReader.cpp b/src/axom/inlet/ConduitReader.cpp index fa685b25fb..faca9fced0 100644 --- a/src/axom/inlet/ConduitReader.cpp +++ b/src/axom/inlet/ConduitReader.cpp @@ -155,6 +155,27 @@ void arrayToMap(const conduit::DataArray& array, } } +template +void arrayToMap(const conduit::DataArray& array, + std::unordered_map& map) +{ + map.clear(); + for(conduit::index_t i = 0; i < array.number_of_elements(); i++) + { + if(std::is_floating_point::value) + { + const double double_value = array[i]; + const int int_value = static_cast(double_value); + map[i] = (static_cast(int_value) == double_value) ? VariantValue {int_value} + : VariantValue {double_value}; + } + else + { + map[i] = VariantValue {static_cast(array[i])}; + } + } +} + /*! ******************************************************************************* * \brief Recursive name retrieval function - adds the names of all descendents @@ -259,6 +280,39 @@ ReaderResult ConduitReader::getValue(const conduit::Node* node, bool& value) return node->dtype().is_empty() ? ReaderResult::NotFound : ReaderResult::WrongType; } +ReaderResult ConduitReader::getValue(const conduit::Node* node, VariantValue& value) +{ + if(!node) + { + return ReaderResult::NotFound; + } + + bool bool_value = false; + if(getValue(node, bool_value) == ReaderResult::Success) + { + value = bool_value; + return ReaderResult::Success; + } + + if(node->dtype().is_number() && !node->dtype().is_uint8()) + { + const double double_value = node->to_double(); + const int int_value = node->to_int(); + value = (static_cast(int_value) == double_value) ? VariantValue {int_value} + : VariantValue {double_value}; + return ReaderResult::Success; + } + + std::string string_value; + if(getValue(node, string_value) == ReaderResult::Success) + { + value = string_value; + return ReaderResult::Success; + } + + return node->dtype().is_empty() ? ReaderResult::NotFound : ReaderResult::WrongType; +} + ReaderResult ConduitReader::getBool(const std::string& id, bool& value) { return getValue(detail::traverseNode(m_root, id), value); @@ -325,6 +379,18 @@ ReaderResult ConduitReader::getStringMap(const std::string& id, return getDictionary(id, values); } +ReaderResult ConduitReader::getVariantMap(const std::string& id, + std::unordered_map& values) +{ + return getArray(id, values); +} + +ReaderResult ConduitReader::getVariantMap(const std::string& id, + std::unordered_map& values) +{ + return getDictionary(id, values); +} + ReaderResult ConduitReader::getIndices(const std::string& id, std::vector& indices) { indices.clear(); diff --git a/src/axom/inlet/ConduitReader.hpp b/src/axom/inlet/ConduitReader.hpp index f694578b8c..a1238d21c8 100644 --- a/src/axom/inlet/ConduitReader.hpp +++ b/src/axom/inlet/ConduitReader.hpp @@ -76,6 +76,11 @@ class ConduitReader : public Reader ReaderResult getStringMap(const std::string& id, std::unordered_map& values) override; + ReaderResult getVariantMap(const std::string& id, + std::unordered_map& values) override; + ReaderResult getVariantMap(const std::string& id, + std::unordered_map& values) override; + ReaderResult getIndices(const std::string& id, std::vector& indices) override; ReaderResult getIndices(const std::string& id, std::vector& indices) override; @@ -110,6 +115,7 @@ class ConduitReader : public Reader ReaderResult getValue(const conduit::Node* node, std::string& value); ReaderResult getValue(const conduit::Node* node, double& value); ReaderResult getValue(const conduit::Node* node, bool& value); + ReaderResult getValue(const conduit::Node* node, VariantValue& value); template ReaderResult getDictionary(const std::string& id, std::unordered_map& values); diff --git a/src/axom/inlet/Container.cpp b/src/axom/inlet/Container.cpp index 3382cfbb30..2c09040f71 100644 --- a/src/axom/inlet/Container.cpp +++ b/src/axom/inlet/Container.cpp @@ -188,6 +188,12 @@ Verifiable& Container::addStringArray(const std::string& name, return addPrimitiveArray(name, description); } +Verifiable& Container::addVariantArray(const std::string& name, + const std::string& description) +{ + return addPrimitiveArray(name, description); +} + template Container& Container::addStructCollection(const std::string& name, const std::string& description) { @@ -251,6 +257,12 @@ Verifiable& Container::addStringDictionary(const std::string& name, return addPrimitiveArray(name, description, true); } +Verifiable& Container::addVariantDictionary(const std::string& name, + const std::string& description) +{ + return addPrimitiveArray(name, description, true); +} + Container& Container::addStructDictionary(const std::string& name, const std::string& description) { return addStructCollection(name, description); @@ -490,6 +502,46 @@ std::vector registerCollection(Container& container, return result; } +void addVariantValue(Container& container, const std::string& key, const VariantValue& value) +{ + std::visit([&container, &key](const auto& concrete_value) { + container.addPrimitive(key, "", true, concrete_value); + }, value); +} + +std::vector registerCollection( + Container& container, + const std::unordered_map& collection) +{ + std::vector result; + for(const auto& entry : collection) + { + result.push_back(entry.first); + addVariantValue(container, std::to_string(entry.first), entry.second); + } + return result; +} + +std::vector registerCollection( + Container& container, + const std::unordered_map& collection) +{ + std::vector result; + for(const auto& entry : collection) + { + result.push_back(entry.first); + auto string_key = indexToString(entry.first); + const auto illegal_char_loc = string_key.find_first_of("/[]"); + SLIC_ERROR_IF(illegal_char_loc != std::string::npos, + fmt::format("[Inlet] Dictionary key '{0}' contains illegal character '{1}'", + string_key, + string_key[illegal_char_loc])); + SLIC_ERROR_IF(string_key.empty(), "[Inlet] Dictionary key cannot be the empty string"); + addVariantValue(container, string_key, entry.second); + } + return result; +} + /*! ***************************************************************************** * \brief Implementation helper for adding primitive arrays @@ -560,6 +612,18 @@ struct PrimitiveArrayHelper } }; +template +struct PrimitiveArrayHelper +{ + static std::vector add(Container& container, Reader& reader, const std::string& lookupPath) + { + std::unordered_map map; + const auto result = reader.getVariantMap(lookupPath, map); + markRetrievalStatus(*container.sidreGroup(), result); + return registerCollection(container, map); + } +}; + void addIndexViewToGroup(sidre::Group& group, const int& index) { group.createViewScalar("", index); diff --git a/src/axom/inlet/Container.hpp b/src/axom/inlet/Container.hpp index 0bb5b1d1ba..765e489507 100644 --- a/src/axom/inlet/Container.hpp +++ b/src/axom/inlet/Container.hpp @@ -29,6 +29,7 @@ #include "axom/inlet/Reader.hpp" #include "axom/inlet/inlet_utils.hpp" #include "axom/inlet/VariantKey.hpp" +#include "axom/inlet/VariantValue.hpp" #include "axom/inlet/Verifiable.hpp" #include "axom/sidre.hpp" @@ -82,6 +83,13 @@ struct is_inlet_primitive std::is_same::value; }; +template +struct is_variant_value +{ + using BaseType = typename std::decay::type; + static constexpr bool value = std::is_same::value; +}; + /*! ******************************************************************************* * \class is_inlet_primitive @@ -446,6 +454,19 @@ class Container : public Verifiable Verifiable& addStringArray(const std::string& name, const std::string& description = ""); + /*! + ***************************************************************************** + * \brief Add an array of mixed primitive Fields to the input file schema. + * + * \param [in] name Name of the array + * \param [in] description Description of the Field + * + * \return Reference to the created array + ***************************************************************************** + */ + Verifiable& addVariantArray(const std::string& name, + const std::string& description = ""); + /*! ***************************************************************************** * \brief Add an array of Fields to the input file schema. @@ -508,6 +529,19 @@ class Container : public Verifiable Verifiable& addStringDictionary(const std::string& name, const std::string& description = ""); + /*! + ***************************************************************************** + * \brief Add a dictionary of mixed primitive Fields to the input file schema. + * + * \param [in] name Name of the dict + * \param [in] description Description of the dictionary + * + * \return Reference to the created dictionary + ***************************************************************************** + */ + Verifiable& addVariantDictionary(const std::string& name, + const std::string& description = ""); + /*! ***************************************************************************** * \brief Add a dictionary of user-defined types to the input file schema. @@ -633,6 +667,45 @@ class Container : public Verifiable return getField(name).get(); } + /*! + ******************************************************************************* + * \brief Returns a stored mixed primitive value. + * + * \param [in] name Name of the Field value to be gotten + * \return The retrieved value + * + * \tparam T The variant value type + ******************************************************************************* + */ + template + typename std::enable_if::value, T>::type get(const std::string& name) const + { + if(!hasField(name)) + { + const std::string msg = fmt::format( + "[Inlet] Field with specified path " + "does not exist: {0}", + name); + SLIC_ERROR(msg); + } + + const Field& field = getField(name); + switch(field.type()) + { + case InletType::Bool: + return field.get(); + case InletType::Integer: + return field.get(); + case InletType::Double: + return field.get(); + case InletType::String: + return field.get(); + default: + SLIC_ERROR(fmt::format("[Inlet] Field with specified path is not a variant value: {0}", name)); + return {}; + } + } + /*! ******************************************************************************* * \brief Returns a stored value of user-defined type. @@ -649,7 +722,8 @@ class Container : public Verifiable */ template typename std::enable_if::value && !detail::is_inlet_array::value && - !detail::is_inlet_dict::value && !detail::is_std_vector::value, + !detail::is_inlet_dict::value && !detail::is_std_vector::value && + !detail::is_variant_value::value, T>::type get(const std::string& name = "") const { @@ -928,7 +1002,8 @@ class Container : public Verifiable ***************************************************************************** */ template ::value>::type> + typename SFINAE = typename std::enable_if::value || + detail::is_variant_value::value>::type> Verifiable& addPrimitiveArray(const std::string& name, const std::string& description = "", const bool isDict = false, diff --git a/src/axom/inlet/Inlet.hpp b/src/axom/inlet/Inlet.hpp index 1e3492a143..e2de42a88d 100644 --- a/src/axom/inlet/Inlet.hpp +++ b/src/axom/inlet/Inlet.hpp @@ -362,6 +362,21 @@ class Inlet return m_globalContainer.addStringArray(name, description); } + /*! + ***************************************************************************** + * \brief Add an array of mixed primitive Fields to the input file schema. + * + * \param [in] name Name of the array + * \param [in] description Description of the array + * + * \return Reference to the created array + ***************************************************************************** + */ + Verifiable& addVariantArray(const std::string& name, const std::string& description = "") + { + return m_globalContainer.addVariantArray(name, description); + } + /*! ***************************************************************************** * \brief Add an array of user-defined type to the input file schema. @@ -460,6 +475,22 @@ class Inlet return m_globalContainer.addStringDictionary(name, description); } + /*! + ***************************************************************************** + * \brief Add a dictionary of mixed primitive Fields to the input file schema. + * + * \param [in] name Name of the dict + * \param [in] description Description of the dictionary + * + * \return Reference to the created dictionary + ***************************************************************************** + */ + Verifiable& addVariantDictionary(const std::string& name, + const std::string& description = "") + { + return m_globalContainer.addVariantDictionary(name, description); + } + /*! ***************************************************************************** * \brief Add an dictionary of user-defined type to the input file schema. diff --git a/src/axom/inlet/LuaReader.cpp b/src/axom/inlet/LuaReader.cpp index cc38b39978..9c6fd9e2e8 100644 --- a/src/axom/inlet/LuaReader.cpp +++ b/src/axom/inlet/LuaReader.cpp @@ -66,6 +66,29 @@ VariantKey extractAs(const axom::sol::object& obj) } } +bool extractVariantValue(const axom::sol::object& obj, VariantValue& value) +{ + switch(obj.get_type()) + { + case axom::sol::type::boolean: + value = obj.as(); + return true; + case axom::sol::type::number: + { + const double double_value = obj.as(); + const int int_value = obj.as(); + value = (static_cast(int_value) == double_value) ? VariantValue {int_value} + : VariantValue {double_value}; + return true; + } + case axom::sol::type::string: + value = obj.as(); + return true; + default: + return false; + } +} + /*! ******************************************************************************* * \brief Recursive name retrieval function - adds the names of all descendents @@ -277,6 +300,18 @@ ReaderResult LuaReader::getStringMap(const std::string& id, return getMap(id, values, axom::sol::type::string); } +ReaderResult LuaReader::getVariantMap(const std::string& id, + std::unordered_map& values) +{ + return getVariantMapInternal(id, values); +} + +ReaderResult LuaReader::getVariantMap(const std::string& id, + std::unordered_map& values) +{ + return getVariantMapInternal(id, values); +} + template bool LuaReader::traverseToTable(Iter begin, Iter end, axom::sol::table& table) { @@ -578,6 +613,47 @@ ReaderResult LuaReader::getMap(const std::string& id, return collectionRetrievalResult(contains_other_type, !values.empty()); } +template +ReaderResult LuaReader::getVariantMapInternal(const std::string& id, + std::unordered_map& values) +{ + values.clear(); + std::vector tokens = axom::utilities::string::split(id, SCOPE_DELIMITER); + + axom::sol::table t; + if(tokens.empty() || !traverseToTable(tokens.begin(), tokens.end(), t)) + { + return ReaderResult::NotFound; + } + + const auto is_correct_key_type = [](const axom::sol::type type) { + const bool is_number = type == axom::sol::type::number; + if(std::is_same::value) + { + return is_number; + } + else + { + return is_number || (type == axom::sol::type::string); + } + }; + + bool contains_other_type = false; + for(const auto& entry : t) + { + VariantValue value; + if(is_correct_key_type(entry.first.get_type()) && detail::extractVariantValue(entry.second, value)) + { + values[detail::extractAs(entry.first)] = value; + } + else + { + contains_other_type = true; + } + } + return collectionRetrievalResult(contains_other_type, !values.empty()); +} + template ReaderResult LuaReader::getIndicesInternal(const std::string& id, std::vector& indices) { diff --git a/src/axom/inlet/LuaReader.hpp b/src/axom/inlet/LuaReader.hpp index 5c8661377a..b835d7f819 100644 --- a/src/axom/inlet/LuaReader.hpp +++ b/src/axom/inlet/LuaReader.hpp @@ -72,6 +72,11 @@ class LuaReader : public Reader ReaderResult getStringMap(const std::string& id, std::unordered_map& values) override; + ReaderResult getVariantMap(const std::string& id, + std::unordered_map& values) override; + ReaderResult getVariantMap(const std::string& id, + std::unordered_map& values) override; + ReaderResult getIndices(const std::string& id, std::vector& indices) override; ReaderResult getIndices(const std::string& id, std::vector& indices) override; @@ -117,6 +122,10 @@ class LuaReader : public Reader std::unordered_map& values, axom::sol::type type); + template + ReaderResult getVariantMapInternal(const std::string& id, + std::unordered_map& values); + template ReaderResult getIndicesInternal(const std::string& id, std::vector& indices); diff --git a/src/axom/inlet/Reader.hpp b/src/axom/inlet/Reader.hpp index 5b44482b2b..6e1a6aacc0 100644 --- a/src/axom/inlet/Reader.hpp +++ b/src/axom/inlet/Reader.hpp @@ -22,6 +22,7 @@ #include "axom/inlet/Function.hpp" #include "axom/inlet/VariantKey.hpp" +#include "axom/inlet/VariantValue.hpp" namespace axom { @@ -214,6 +215,24 @@ class Reader virtual ReaderResult getStringMap(const std::string& id, std::unordered_map& values) = 0; + /*! + ***************************************************************************** + * \brief Get an index-variant mapping for the given array + * + * This retrieves arrays/dictionaries with mixed primitive values. + * + * \param [in] id The identifier to the collection that will be retrieved + * \param [out] map The mixed primitive values that were retrieved + * + * \return The status of the retrieval, \see ReaderResult + ***************************************************************************** + */ + virtual ReaderResult getVariantMap(const std::string& id, + std::unordered_map& values) = 0; + /// \overload + virtual ReaderResult getVariantMap(const std::string& id, + std::unordered_map& values) = 0; + /*! ***************************************************************************** * \brief Get the list of indices for a collection diff --git a/src/axom/inlet/VariantKey.hpp b/src/axom/inlet/VariantKey.hpp index e4c32decab..8a31b35e5d 100644 --- a/src/axom/inlet/VariantKey.hpp +++ b/src/axom/inlet/VariantKey.hpp @@ -16,6 +16,10 @@ #ifndef INLET_KEY_HPP #define INLET_KEY_HPP +#include +#include + +#include "axom/fmt.hpp" #include "axom/slic/interface/slic.hpp" namespace axom @@ -167,4 +171,9 @@ struct hash }; } // end namespace std +/// Overload to format an inlet::VariantKey using fmt +template <> +struct axom::fmt::formatter : ostream_formatter +{ }; + #endif // INLET_KEY_HPP diff --git a/src/axom/inlet/VariantValue.hpp b/src/axom/inlet/VariantValue.hpp new file mode 100644 index 0000000000..b4e03006b0 --- /dev/null +++ b/src/axom/inlet/VariantValue.hpp @@ -0,0 +1,32 @@ +// 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) + +/*! + ******************************************************************************* + * \file VariantValue.hpp + * + * \brief This file contains Inlet's variant value type for mixed primitive + * collections. + ******************************************************************************* + */ + +#ifndef INLET_VARIANT_VALUE_HPP +#define INLET_VARIANT_VALUE_HPP + +#include +#include + +namespace axom +{ +namespace inlet +{ + +using VariantValue = std::variant; + +} // namespace inlet +} // namespace axom + +#endif diff --git a/src/axom/inlet/docs/sphinx/simple_types.rst b/src/axom/inlet/docs/sphinx/simple_types.rst index bc950e2bca..b385916abd 100644 --- a/src/axom/inlet/docs/sphinx/simple_types.rst +++ b/src/axom/inlet/docs/sphinx/simple_types.rst @@ -127,30 +127,128 @@ can get the Container instance first, then access it with the relative name. Arrays ****** -Coming soon! +Primitive arrays store a collection of values under integer keys. The ``addBoolArray``, +``addIntArray``, ``addDoubleArray``, and ``addStringArray`` schema methods expect +all values in the array to have the requested primitive type. + +In this example, both arrays contain only integer values. The first input array +is contiguous and the second uses explicit integer keys: + +.. literalinclude:: ../../examples/homogeneous_collections.cpp + :start-after: _inlet_simple_types_homogeneous_arrays_input_start + :end-before: _inlet_simple_types_homogeneous_arrays_input_end + :language: lua + +For arrays whose values can be any supported primitive type, use ``addVariantArray``. +Variant arrays store values as ``inlet::VariantValue``, which is a +``std::variant``. + +In this example, the arrays contain mixed primitive value types: + +.. literalinclude:: ../../examples/variant_collections.cpp + :start-after: _inlet_simple_types_variant_arrays_input_start + :end-before: _inlet_simple_types_variant_arrays_input_end + :language: lua Defining And Storing -------------------- -Coming soon! +Homogeneous primitive arrays are added with the type-specific ``add*Array`` method: + +.. literalinclude:: ../../examples/homogeneous_collections.cpp + :start-after: _inlet_simple_types_homogeneous_collections_add_start + :end-before: _inlet_simple_types_homogeneous_collections_add_end + :language: C++ + +Variant arrays are added with ``addVariantArray``: + +.. literalinclude:: ../../examples/variant_collections.cpp + :start-after: _inlet_simple_types_variant_collections_add_start + :end-before: _inlet_simple_types_variant_collections_add_end + :language: C++ Accessing --------- -Coming soon! +Contiguous homogeneous arrays can be retrieved as ``std::vector``. Integer-keyed +homogeneous arrays can be retrieved as ``std::unordered_map`` when the +original indices are needed. + +.. literalinclude:: ../../examples/homogeneous_collections.cpp + :start-after: _inlet_simple_types_homogeneous_arrays_access_start + :end-before: _inlet_simple_types_homogeneous_arrays_access_end + :language: C++ + +Contiguous variant arrays can be retrieved as ``std::vector``. +Integer-keyed variant arrays can be retrieved as +``std::unordered_map`` when the original indices are +needed. + +.. literalinclude:: ../../examples/variant_collections.cpp + :start-after: _inlet_simple_types_variant_arrays_access_start + :end-before: _inlet_simple_types_variant_arrays_access_end + :language: C++ ************ Dictionaries ************ -Coming soon! +Dictionaries store a collection of values under arbitrary string keys or a mix of +string and integer keys. The ``addBoolDictionary``, ``addIntDictionary``, +``addDoubleDictionary``, and ``addStringDictionary`` schema methods expect all +values in the dictionary to have the requested primitive type. + +In this example, all dictionary values are integers: + +.. literalinclude:: ../../examples/homogeneous_collections.cpp + :start-after: _inlet_simple_types_homogeneous_dictionary_input_start + :end-before: _inlet_simple_types_homogeneous_dictionary_input_end + :language: lua + +For dictionaries whose values can be any supported primitive type, use +``addVariantDictionary``. Mixed string and integer keys are represented with +``inlet::VariantKey``. + +In this example, the dictionary has mixed primitive values and mixed key types: + +.. literalinclude:: ../../examples/variant_collections.cpp + :start-after: _inlet_simple_types_variant_dictionary_input_start + :end-before: _inlet_simple_types_variant_dictionary_input_end + :language: lua Defining And Storing -------------------- -Coming soon! +Homogeneous primitive dictionaries are added with the type-specific +``add*Dictionary`` method: + +.. literalinclude:: ../../examples/homogeneous_collections.cpp + :start-after: _inlet_simple_types_homogeneous_collections_add_start + :end-before: _inlet_simple_types_homogeneous_collections_add_end + :language: C++ + +Variant dictionaries are added with ``addVariantDictionary``: + +.. literalinclude:: ../../examples/variant_collections.cpp + :start-after: _inlet_simple_types_variant_collections_add_start + :end-before: _inlet_simple_types_variant_collections_add_end + :language: C++ Accessing --------- -Coming soon! +Mixed-key homogeneous dictionaries can be retrieved as +``std::unordered_map``. + +.. literalinclude:: ../../examples/homogeneous_collections.cpp + :start-after: _inlet_simple_types_homogeneous_dictionary_access_start + :end-before: _inlet_simple_types_homogeneous_dictionary_access_end + :language: C++ + +Mixed-key variant dictionaries can be retrieved as +``std::unordered_map``. + +.. literalinclude:: ../../examples/variant_collections.cpp + :start-after: _inlet_simple_types_variant_dictionary_access_start + :end-before: _inlet_simple_types_variant_dictionary_access_end + :language: C++ diff --git a/src/axom/inlet/examples/CMakeLists.txt b/src/axom/inlet/examples/CMakeLists.txt index 707f22d5a0..179cea838d 100644 --- a/src/axom/inlet/examples/CMakeLists.txt +++ b/src/axom/inlet/examples/CMakeLists.txt @@ -21,7 +21,9 @@ blt_list_append( arrays.cpp documentation_generation.cpp fields.cpp + homogeneous_collections.cpp lua_library.cpp + variant_collections.cpp containers.cpp user_defined_type.cpp verification.cpp @@ -68,9 +70,15 @@ if (SOL_FOUND) axom_add_test( NAME inlet_fields_ex COMMAND inlet_fields_ex) + axom_add_test( NAME inlet_homogeneous_collections_ex + COMMAND inlet_homogeneous_collections_ex ) + axom_add_test( NAME inlet_lua_library_ex COMMAND inlet_lua_library_ex ) + axom_add_test( NAME inlet_variant_collections_ex + COMMAND inlet_variant_collections_ex ) + axom_add_test( NAME inlet_containers_ex COMMAND inlet_containers_ex) diff --git a/src/axom/inlet/examples/homogeneous_collections.cpp b/src/axom/inlet/examples/homogeneous_collections.cpp new file mode 100644 index 0000000000..7142a07622 --- /dev/null +++ b/src/axom/inlet/examples/homogeneous_collections.cpp @@ -0,0 +1,106 @@ +// 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/inlet.hpp" +#include "axom/slic/core/SimpleLogger.hpp" +#include "axom/fmt.hpp" + +#include +#include +#include + +namespace inlet = axom::inlet; + +/* Input file snippets used for documentation +// _inlet_simple_types_homogeneous_arrays_input_start + +contiguous = { 10, 20, 30 } + +indexed = { + [10] = 100, + [20] = 200, + [30] = 300 +} + +// _inlet_simple_types_homogeneous_arrays_input_end + +// _inlet_simple_types_homogeneous_dictionary_input_start + +keyed = { + [1] = 100, + low = 10, + medium = 20, + high = 30 +} + +// _inlet_simple_types_homogeneous_dictionary_input_end +*/ + +const std::string input = R"( + contiguous = { 10, 20, 30 } + + indexed = { + [10] = 100, + [20] = 200, + [30] = 300 + } + + keyed = { + [1] = 100, + low = 10, + medium = 20, + high = 30 + } +)"; + +int main() +{ + axom::slic::SimpleLogger logger; + + auto lr = std::make_unique(); + lr->parseString(input); + inlet::Inlet inlet(std::move(lr)); + + // _inlet_simple_types_homogeneous_collections_add_start + inlet.addIntArray("contiguous", "Contiguous integer values"); + inlet.addIntArray("indexed", "Integer-keyed integer values"); + inlet.addIntDictionary("keyed", "Mixed-key integer values"); + // _inlet_simple_types_homogeneous_collections_add_end + + if(!inlet.verify()) + { + SLIC_ERROR("Inlet failed to verify against provided schema"); + } + + // _inlet_simple_types_homogeneous_arrays_access_start + SLIC_INFO("Contiguous int array as std::vector:"); + const std::vector contiguous_values = inlet["contiguous"].get>(); + for(const int value : contiguous_values) + { + SLIC_INFO(axom::fmt::format("{}", value)); + } + + SLIC_INFO("Integer-keyed int array as std::unordered_map:"); + const std::unordered_map indexed_values = + inlet["indexed"].get>(); + for(const auto& entry : indexed_values) + { + SLIC_INFO(axom::fmt::format("{} = {}", entry.first, entry.second)); + } + // _inlet_simple_types_homogeneous_arrays_access_end + + // _inlet_simple_types_homogeneous_dictionary_access_start + SLIC_INFO("Mixed-key int dictionary as std::unordered_map:"); + const std::unordered_map keyed_values = + inlet["keyed"].get>(); + for(const auto& entry : keyed_values) + { + SLIC_INFO(axom::fmt::format("{} = {}", entry.first, entry.second)); + } + // _inlet_simple_types_homogeneous_dictionary_access_end + + return 0; +} diff --git a/src/axom/inlet/examples/variant_collections.cpp b/src/axom/inlet/examples/variant_collections.cpp new file mode 100644 index 0000000000..b3b9853bca --- /dev/null +++ b/src/axom/inlet/examples/variant_collections.cpp @@ -0,0 +1,118 @@ +// 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/inlet.hpp" +#include "axom/slic/core/SimpleLogger.hpp" +#include "axom/fmt.hpp" + +#include +#include +#include +#include + +namespace inlet = axom::inlet; + +/* Input file snippets used for documentation +// _inlet_simple_types_variant_arrays_input_start + +contiguous = { 42, "hello", true, 3.14 } + +indexed = { + [10] = 42, + [20] = "hello", + [30] = true, + [40] = 3.14 +} + +// _inlet_simple_types_variant_arrays_input_end + +// _inlet_simple_types_variant_dictionary_input_start + +keyed = { + [1] = 42, + message = "hello", + enabled = true, + pi = 3.14 +} + +// _inlet_simple_types_variant_dictionary_input_end +*/ + +const std::string input = R"( + contiguous = { 42, "hello", true, 3.14 } + + indexed = { + [10] = 42, + [20] = "hello", + [30] = true, + [40] = 3.14 + } + + keyed = { + [1] = 42, + message = "hello", + enabled = true, + pi = 3.14 + } +)"; + +int main() +{ + axom::slic::SimpleLogger logger; + + auto lr = std::make_unique(); + lr->parseString(input); + inlet::Inlet inlet(std::move(lr)); + + // _inlet_simple_types_variant_collections_add_start + inlet.addVariantArray("contiguous", "Contiguous mixed POD values"); + inlet.addVariantArray("indexed", "Integer-keyed mixed POD values"); + inlet.addVariantDictionary("keyed", "Mixed-key mixed POD values"); + // _inlet_simple_types_variant_collections_add_end + + if(!inlet.verify()) + { + SLIC_ERROR("Inlet failed to verify against provided schema"); + } + + // _inlet_simple_types_variant_arrays_access_start + SLIC_INFO("Contiguous VariantArray as std::vector:"); + const std::vector contiguous_values = + inlet["contiguous"].get>(); + for(const inlet::VariantValue& value : contiguous_values) + { + std::visit([](const auto& concrete_value) { + SLIC_INFO(axom::fmt::format("{}", concrete_value)); + }, value); + } + + SLIC_INFO("Integer-keyed VariantArray as std::unordered_map:"); + const std::unordered_map indexed_values = + inlet["indexed"].get>(); + std::map sorted_indexed_values(indexed_values.begin(), + indexed_values.end()); + for(const auto& entry : sorted_indexed_values) + { + std::visit([&entry](const auto& concrete_value) { + SLIC_INFO(axom::fmt::format("{} = {}", entry.first, concrete_value)); + }, entry.second); + } + // _inlet_simple_types_variant_arrays_access_end + + // _inlet_simple_types_variant_dictionary_access_start + SLIC_INFO("Mixed-key VariantDictionary as std::unordered_map:"); + const std::unordered_map keyed_values = + inlet["keyed"].get>(); + for(const auto& entry : keyed_values) + { + std::visit([&entry](const auto& concrete_value) { + SLIC_INFO(axom::fmt::format("{} = {}", entry.first, concrete_value)); + }, entry.second); + } + // _inlet_simple_types_variant_dictionary_access_end + + return 0; +} diff --git a/src/axom/inlet/tests/inlet_Reader.cpp b/src/axom/inlet/tests/inlet_Reader.cpp index 88f61da9c6..5e6c077181 100644 --- a/src/axom/inlet/tests/inlet_Reader.cpp +++ b/src/axom/inlet/tests/inlet_Reader.cpp @@ -14,6 +14,7 @@ #include #include #include +#include template class inlet_Reader : public testing::Test @@ -180,6 +181,24 @@ TYPED_TEST(inlet_Reader, getMap) // EXPECT_EQ(expectedStrs, strs); } +TYPED_TEST(inlet_Reader, getVariantMap) +{ + std::string testString = "luaArray = { [0] = 42, [1] = 'hello', [2] = true, [3] = 3.14 }"; + TypeParam reader; + reader.parseString(fromLuaTo(testString)); + + std::unordered_map values; + ReaderResult retValue = reader.getVariantMap("luaArray", values); + EXPECT_EQ(retValue, ReaderResult::Success); + + std::unordered_map expected { + {0, axom::inlet::VariantValue {42}}, + {1, axom::inlet::VariantValue {std::string {"hello"}}}, + {2, axom::inlet::VariantValue {true}}, + {3, axom::inlet::VariantValue {3.14}}}; + EXPECT_EQ(expected, values); +} + TYPED_TEST(inlet_Reader, emptyCollections) { TypeParam reader; diff --git a/src/axom/inlet/tests/inlet_object.cpp b/src/axom/inlet/tests/inlet_object.cpp index b652ed790e..6a36eb6b47 100644 --- a/src/axom/inlet/tests/inlet_object.cpp +++ b/src/axom/inlet/tests/inlet_object.cpp @@ -19,11 +19,13 @@ #include #include #include +#include using axom::Path; using axom::inlet::Inlet; using axom::inlet::InletType; using axom::inlet::VariantKey; +using axom::inlet::VariantValue; using axom::inlet::VerificationError; using ::testing::Contains; @@ -829,6 +831,33 @@ TYPED_TEST(inlet_object, primitive_arrays_as_std_vector) EXPECT_EQ(arr_w_indices, expected_arr_w_indices); } +TYPED_TEST(inlet_object, variant_arrays_as_std_vector) +{ + std::string testString = " arr = { [0] = 42, [1] = 'hello', [2] = true, [3] = 3.14 }"; + Inlet inlet = createBasicInlet(testString); + + inlet.addVariantArray("arr"); + + EXPECT_TRUE(inlet.verify()); + + std::vector expected_arr { + VariantValue {42}, + VariantValue {std::string {"hello"}}, + VariantValue {true}, + VariantValue {3.14}}; + std::vector arr = inlet["arr"].get>(); + EXPECT_EQ(arr, expected_arr); + + std::unordered_map expected_arr_w_indices { + {0, VariantValue {42}}, + {1, VariantValue {std::string {"hello"}}}, + {2, VariantValue {true}}, + {3, VariantValue {3.14}}}; + std::unordered_map arr_w_indices = + inlet["arr"].get>(); + EXPECT_EQ(arr_w_indices, expected_arr_w_indices); +} + TYPED_TEST(inlet_object, primitive_arrays_as_std_vector_wrong_type) { std::string testString = " arr = { [0] = 'a', [1] = 'b', [2] = 'c'}"; @@ -1365,6 +1394,21 @@ TEST(inlet_object_lua_dict, mixed_keys_primitive) EXPECT_EQ(dict, correct_dict); } +TEST(inlet_object_lua_dict, mixed_keys_variant) +{ + std::string testString = "foo = { ['key1'] = 'hello', [1] = 42, ['flag'] = true }"; + Inlet inlet = createBasicInlet(testString); + + inlet.addVariantDictionary("foo", "foo's description"); + std::unordered_map dict = + inlet["foo"].get>(); + std::unordered_map correct_dict = { + {"key1", VariantValue {std::string {"hello"}}}, + {1, VariantValue {42}}, + {"flag", VariantValue {true}}}; + EXPECT_EQ(dict, correct_dict); +} + TEST(inlet_object_lua_dict, mixed_keys_primitive_ignore_string_only) { std::string testString = "foo = { ['key1'] = 4, [1] = 6 }"; From 23af4946c9af8633c8e166d4ace809524af14def Mon Sep 17 00:00:00 2001 From: Chris White Date: Fri, 12 Jun 2026 12:48:36 -0700 Subject: [PATCH 373/986] style --- src/axom/inlet/Container.cpp | 16 +++++++--------- src/axom/inlet/Container.hpp | 6 +++--- src/axom/inlet/Inlet.hpp | 3 ++- src/axom/inlet/tests/inlet_object.cpp | 9 ++++----- 4 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/axom/inlet/Container.cpp b/src/axom/inlet/Container.cpp index 2c09040f71..a28de9d9fe 100644 --- a/src/axom/inlet/Container.cpp +++ b/src/axom/inlet/Container.cpp @@ -504,14 +504,13 @@ std::vector registerCollection(Container& container, void addVariantValue(Container& container, const std::string& key, const VariantValue& value) { - std::visit([&container, &key](const auto& concrete_value) { - container.addPrimitive(key, "", true, concrete_value); - }, value); + std::visit([&container, &key]( + const auto& concrete_value) { container.addPrimitive(key, "", true, concrete_value); }, + value); } -std::vector registerCollection( - Container& container, - const std::unordered_map& collection) +std::vector registerCollection(Container& container, + const std::unordered_map& collection) { std::vector result; for(const auto& entry : collection) @@ -522,9 +521,8 @@ std::vector registerCollection( return result; } -std::vector registerCollection( - Container& container, - const std::unordered_map& collection) +std::vector registerCollection(Container& container, + const std::unordered_map& collection) { std::vector result; for(const auto& entry : collection) diff --git a/src/axom/inlet/Container.hpp b/src/axom/inlet/Container.hpp index 765e489507..cc9d9743ff 100644 --- a/src/axom/inlet/Container.hpp +++ b/src/axom/inlet/Container.hpp @@ -721,9 +721,9 @@ class Container : public Verifiable ******************************************************************************* */ template - typename std::enable_if::value && !detail::is_inlet_array::value && - !detail::is_inlet_dict::value && !detail::is_std_vector::value && - !detail::is_variant_value::value, + typename std::enable_if::value && + !detail::is_inlet_array::value && !detail::is_inlet_dict::value && + !detail::is_std_vector::value && !detail::is_variant_value::value, T>::type get(const std::string& name = "") const { diff --git a/src/axom/inlet/Inlet.hpp b/src/axom/inlet/Inlet.hpp index e2de42a88d..1a8974068e 100644 --- a/src/axom/inlet/Inlet.hpp +++ b/src/axom/inlet/Inlet.hpp @@ -372,7 +372,8 @@ class Inlet * \return Reference to the created array ***************************************************************************** */ - Verifiable& addVariantArray(const std::string& name, const std::string& description = "") + Verifiable& addVariantArray(const std::string& name, + const std::string& description = "") { return m_globalContainer.addVariantArray(name, description); } diff --git a/src/axom/inlet/tests/inlet_object.cpp b/src/axom/inlet/tests/inlet_object.cpp index 6a36eb6b47..521f87bc15 100644 --- a/src/axom/inlet/tests/inlet_object.cpp +++ b/src/axom/inlet/tests/inlet_object.cpp @@ -840,11 +840,10 @@ TYPED_TEST(inlet_object, variant_arrays_as_std_vector) EXPECT_TRUE(inlet.verify()); - std::vector expected_arr { - VariantValue {42}, - VariantValue {std::string {"hello"}}, - VariantValue {true}, - VariantValue {3.14}}; + std::vector expected_arr {VariantValue {42}, + VariantValue {std::string {"hello"}}, + VariantValue {true}, + VariantValue {3.14}}; std::vector arr = inlet["arr"].get>(); EXPECT_EQ(arr, expected_arr); From f4e40bd9989db29c3024d5fa2b379778cb113226 Mon Sep 17 00:00:00 2001 From: Chris White Date: Fri, 12 Jun 2026 13:39:40 -0700 Subject: [PATCH 374/986] tighten the conduit path for bool and string variant arrays to not fall to the base error case --- src/axom/inlet/ConduitReader.cpp | 91 ++++++++++++++++++++++++++- src/axom/inlet/ConduitReader.hpp | 2 + src/axom/inlet/tests/inlet_Reader.cpp | 34 ++++++++++ 3 files changed, 126 insertions(+), 1 deletion(-) diff --git a/src/axom/inlet/ConduitReader.cpp b/src/axom/inlet/ConduitReader.cpp index faca9fced0..b01be547bd 100644 --- a/src/axom/inlet/ConduitReader.cpp +++ b/src/axom/inlet/ConduitReader.cpp @@ -176,6 +176,16 @@ void arrayToMap(const conduit::DataArray& array, } } +void boolArrayToMap(const conduit::DataArray& array, + std::unordered_map& map) +{ + map.clear(); + for(conduit::index_t i = 0; i < array.number_of_elements(); i++) + { + map[i] = VariantValue {static_cast(array[i])}; + } +} + /*! ******************************************************************************* * \brief Recursive name retrieval function - adds the names of all descendents @@ -382,7 +392,7 @@ ReaderResult ConduitReader::getStringMap(const std::string& id, ReaderResult ConduitReader::getVariantMap(const std::string& id, std::unordered_map& values) { - return getArray(id, values); + return getVariantArray(id, values); } ReaderResult ConduitReader::getVariantMap(const std::string& id, @@ -576,5 +586,84 @@ ReaderResult ConduitReader::getArray(const std::string& id, std::unordered_map& values) +{ + values.clear(); + const auto node_ptr = detail::traverseNode(m_root, id); + if(!node_ptr) + { + return ReaderResult::NotFound; + } + const auto& node = *node_ptr; + // If it's empty, then the array must have been empty, which counts as successful + if(node.dtype().is_empty()) + { + return ReaderResult::Success; + } + // Dense primitive arrays can be copied directly. JSON booleans are represented + // by Conduit as uint8 arrays, so treat that case as bool instead of int. + else if(node.dtype().number_of_elements() > 1 && !node.dtype().is_list() && + !node.dtype().is_object()) + { + if(node.dtype().is_floating_point()) + { + detail::arrayToMap(node.as_double_array(), values); + } + else if(node.dtype().is_int32()) + { + detail::arrayToMap(node.as_int32_array(), values); + } + else if(node.dtype().is_int64()) + { + detail::arrayToMap(node.as_int64_array(), values); + } + else if((m_protocol == "json") && node.dtype().is_uint8()) + { + detail::boolArrayToMap(node.as_uint8_array(), values); + } + else + { + return ReaderResult::WrongType; + } + } + else if(!node.dtype().is_list() && !node.dtype().is_object()) + { + // Single-element arrays will be just the element itself + // If it's a single element, we know the index is zero + VariantValue value; + const auto result = getValue(&node, value); + if(result == ReaderResult::Success) + { + values[0] = value; + } + else + { + return result; + } + } + else + { + conduit::index_t index = 0; + bool contains_other_type = false; + for(const auto& child : node.children()) + { + VariantValue value; + const auto result = getValue(&child, value); + if(result == ReaderResult::Success) + { + values[index] = value; + } + else + { + contains_other_type = true; + } + index++; + } + return collectionRetrievalResult(contains_other_type, !values.empty()); + } + return ReaderResult::Success; +} + } // end namespace inlet } // end namespace axom diff --git a/src/axom/inlet/ConduitReader.hpp b/src/axom/inlet/ConduitReader.hpp index a1238d21c8..1a8a339282 100644 --- a/src/axom/inlet/ConduitReader.hpp +++ b/src/axom/inlet/ConduitReader.hpp @@ -122,6 +122,8 @@ class ConduitReader : public Reader template ReaderResult getArray(const std::string& id, std::unordered_map& values); + ReaderResult getVariantArray(const std::string& id, + std::unordered_map& values); conduit::Node m_root; const std::string m_protocol; }; diff --git a/src/axom/inlet/tests/inlet_Reader.cpp b/src/axom/inlet/tests/inlet_Reader.cpp index 5e6c077181..6dbc99e4da 100644 --- a/src/axom/inlet/tests/inlet_Reader.cpp +++ b/src/axom/inlet/tests/inlet_Reader.cpp @@ -199,6 +199,40 @@ TYPED_TEST(inlet_Reader, getVariantMap) EXPECT_EQ(expected, values); } +TEST(inlet_Reader_JSON, getVariantMapBoolArray) +{ + axom::inlet::JSONReader reader; + bool result = reader.parseString("{\"bools\": [true, false, true]}"); + EXPECT_TRUE(result); + + std::unordered_map values; + ReaderResult retValue = reader.getVariantMap("bools", values); + EXPECT_EQ(retValue, ReaderResult::Success); + + std::unordered_map expected { + {0, axom::inlet::VariantValue {true}}, + {1, axom::inlet::VariantValue {false}}, + {2, axom::inlet::VariantValue {true}}}; + EXPECT_EQ(expected, values); +} + +TEST(inlet_Reader_JSON, getVariantMapStringArray) +{ + axom::inlet::JSONReader reader; + bool result = reader.parseString("{\"strings\": [\"red\", \"green\", \"blue\"]}"); + EXPECT_TRUE(result); + + std::unordered_map values; + ReaderResult retValue = reader.getVariantMap("strings", values); + EXPECT_EQ(retValue, ReaderResult::Success); + + std::unordered_map expected { + {0, axom::inlet::VariantValue {std::string {"red"}}}, + {1, axom::inlet::VariantValue {std::string {"green"}}}, + {2, axom::inlet::VariantValue {std::string {"blue"}}}}; + EXPECT_EQ(expected, values); +} + TYPED_TEST(inlet_Reader, emptyCollections) { TypeParam reader; From ea5d50e28077e216c9cda4dd7908c74094ee2761 Mon Sep 17 00:00:00 2001 From: Chris White Date: Fri, 12 Jun 2026 16:23:42 -0700 Subject: [PATCH 375/986] split accessing example --- src/axom/inlet/docs/sphinx/simple_types.rst | 16 +++++++++++----- .../inlet/examples/homogeneous_collections.cpp | 6 ++++-- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/axom/inlet/docs/sphinx/simple_types.rst b/src/axom/inlet/docs/sphinx/simple_types.rst index b385916abd..b52366714b 100644 --- a/src/axom/inlet/docs/sphinx/simple_types.rst +++ b/src/axom/inlet/docs/sphinx/simple_types.rst @@ -170,13 +170,19 @@ Variant arrays are added with ``addVariantArray``: Accessing --------- -Contiguous homogeneous arrays can be retrieved as ``std::vector``. Integer-keyed -homogeneous arrays can be retrieved as ``std::unordered_map`` when the -original indices are needed. +Contiguous homogeneous arrays can be retrieved as ``std::vector``: .. literalinclude:: ../../examples/homogeneous_collections.cpp - :start-after: _inlet_simple_types_homogeneous_arrays_access_start - :end-before: _inlet_simple_types_homogeneous_arrays_access_end + :start-after: _inlet_simple_types_homogeneous_arrays_access_vector_start + :end-before: _inlet_simple_types_homogeneous_arrays_access_vector_end + :language: C++ + +Integer-keyed homogeneous arrays can be retrieved as +``std::unordered_map`` when the original indices are needed: + +.. literalinclude:: ../../examples/homogeneous_collections.cpp + :start-after: _inlet_simple_types_homogeneous_arrays_access_map_start + :end-before: _inlet_simple_types_homogeneous_arrays_access_map_end :language: C++ Contiguous variant arrays can be retrieved as ``std::vector``. diff --git a/src/axom/inlet/examples/homogeneous_collections.cpp b/src/axom/inlet/examples/homogeneous_collections.cpp index 7142a07622..4d618a4bf2 100644 --- a/src/axom/inlet/examples/homogeneous_collections.cpp +++ b/src/axom/inlet/examples/homogeneous_collections.cpp @@ -75,14 +75,16 @@ int main() SLIC_ERROR("Inlet failed to verify against provided schema"); } - // _inlet_simple_types_homogeneous_arrays_access_start + // _inlet_simple_types_homogeneous_arrays_access_vector_start SLIC_INFO("Contiguous int array as std::vector:"); const std::vector contiguous_values = inlet["contiguous"].get>(); for(const int value : contiguous_values) { SLIC_INFO(axom::fmt::format("{}", value)); } + // _inlet_simple_types_homogeneous_arrays_access_vector_end + // _inlet_simple_types_homogeneous_arrays_access_map_start SLIC_INFO("Integer-keyed int array as std::unordered_map:"); const std::unordered_map indexed_values = inlet["indexed"].get>(); @@ -90,7 +92,7 @@ int main() { SLIC_INFO(axom::fmt::format("{} = {}", entry.first, entry.second)); } - // _inlet_simple_types_homogeneous_arrays_access_end + // _inlet_simple_types_homogeneous_arrays_access_map_end // _inlet_simple_types_homogeneous_dictionary_access_start SLIC_INFO("Mixed-key int dictionary as std::unordered_map:"); From 52b6e4dc0dd3447590d3dacae73ba32712886f8e Mon Sep 17 00:00:00 2001 From: Chris White Date: Fri, 12 Jun 2026 16:29:54 -0700 Subject: [PATCH 376/986] split accessing variant array example --- src/axom/inlet/docs/sphinx/simple_types.rst | 15 +++++++++++---- src/axom/inlet/examples/variant_collections.cpp | 6 ++++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/axom/inlet/docs/sphinx/simple_types.rst b/src/axom/inlet/docs/sphinx/simple_types.rst index b52366714b..4b0fa0c7b5 100644 --- a/src/axom/inlet/docs/sphinx/simple_types.rst +++ b/src/axom/inlet/docs/sphinx/simple_types.rst @@ -185,14 +185,21 @@ Integer-keyed homogeneous arrays can be retrieved as :end-before: _inlet_simple_types_homogeneous_arrays_access_map_end :language: C++ -Contiguous variant arrays can be retrieved as ``std::vector``. +Contiguous variant arrays can be retrieved as +``std::vector``: + +.. literalinclude:: ../../examples/variant_collections.cpp + :start-after: _inlet_simple_types_variant_arrays_access_vector_start + :end-before: _inlet_simple_types_variant_arrays_access_vector_end + :language: C++ + Integer-keyed variant arrays can be retrieved as ``std::unordered_map`` when the original indices are -needed. +needed: .. literalinclude:: ../../examples/variant_collections.cpp - :start-after: _inlet_simple_types_variant_arrays_access_start - :end-before: _inlet_simple_types_variant_arrays_access_end + :start-after: _inlet_simple_types_variant_arrays_access_map_start + :end-before: _inlet_simple_types_variant_arrays_access_map_end :language: C++ ************ diff --git a/src/axom/inlet/examples/variant_collections.cpp b/src/axom/inlet/examples/variant_collections.cpp index b3b9853bca..e46d6264b6 100644 --- a/src/axom/inlet/examples/variant_collections.cpp +++ b/src/axom/inlet/examples/variant_collections.cpp @@ -78,7 +78,7 @@ int main() SLIC_ERROR("Inlet failed to verify against provided schema"); } - // _inlet_simple_types_variant_arrays_access_start + // _inlet_simple_types_variant_arrays_access_vector_start SLIC_INFO("Contiguous VariantArray as std::vector:"); const std::vector contiguous_values = inlet["contiguous"].get>(); @@ -88,7 +88,9 @@ int main() SLIC_INFO(axom::fmt::format("{}", concrete_value)); }, value); } + // _inlet_simple_types_variant_arrays_access_vector_end + // _inlet_simple_types_variant_arrays_access_map_start SLIC_INFO("Integer-keyed VariantArray as std::unordered_map:"); const std::unordered_map indexed_values = inlet["indexed"].get>(); @@ -100,7 +102,7 @@ int main() SLIC_INFO(axom::fmt::format("{} = {}", entry.first, concrete_value)); }, entry.second); } - // _inlet_simple_types_variant_arrays_access_end + // _inlet_simple_types_variant_arrays_access_map_end // _inlet_simple_types_variant_dictionary_access_start SLIC_INFO("Mixed-key VariantDictionary as std::unordered_map:"); From 76dd45f8bceaeb8fc71042171648be6e7cf7958e Mon Sep 17 00:00:00 2001 From: Chris White Date: Fri, 12 Jun 2026 16:35:38 -0700 Subject: [PATCH 377/986] add release notes --- RELEASE-NOTES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 4b5d02911a..9489695352 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -38,6 +38,7 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ - Core: Adds `axom::Array::pop_back` for API compatibility with `std::vector` - 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. ### Removed From 2bb5e5a145803d4ad0e77a934170e52216c6dfe2 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Fri, 12 Jun 2026 17:30:54 -0700 Subject: [PATCH 378/986] 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 379/986] 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 380/986] 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 381/986] 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 4532de74eb567ddf8f7c6900e6b0d7e9ea65fa9e Mon Sep 17 00:00:00 2001 From: Chris White Date: Fri, 12 Jun 2026 21:31:52 -0700 Subject: [PATCH 382/986] style --- src/axom/inlet/ConduitReader.hpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/axom/inlet/ConduitReader.hpp b/src/axom/inlet/ConduitReader.hpp index 1a8a339282..b9593e83d9 100644 --- a/src/axom/inlet/ConduitReader.hpp +++ b/src/axom/inlet/ConduitReader.hpp @@ -122,8 +122,7 @@ class ConduitReader : public Reader template ReaderResult getArray(const std::string& id, std::unordered_map& values); - ReaderResult getVariantArray(const std::string& id, - std::unordered_map& values); + ReaderResult getVariantArray(const std::string& id, std::unordered_map& values); conduit::Node m_root; const std::string m_protocol; }; From 89d11ce5fb7be22cf2debc5a9cc024a356b1c950 Mon Sep 17 00:00:00 2001 From: Chris White Date: Fri, 12 Jun 2026 21:34:02 -0700 Subject: [PATCH 383/986] style --- .../inlet/examples/variant_collections.cpp | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/axom/inlet/examples/variant_collections.cpp b/src/axom/inlet/examples/variant_collections.cpp index e46d6264b6..06bd716c4a 100644 --- a/src/axom/inlet/examples/variant_collections.cpp +++ b/src/axom/inlet/examples/variant_collections.cpp @@ -84,9 +84,8 @@ int main() inlet["contiguous"].get>(); for(const inlet::VariantValue& value : contiguous_values) { - std::visit([](const auto& concrete_value) { - SLIC_INFO(axom::fmt::format("{}", concrete_value)); - }, value); + std::visit([](const auto& concrete_value) { SLIC_INFO(axom::fmt::format("{}", concrete_value)); }, + value); } // _inlet_simple_types_variant_arrays_access_vector_end @@ -98,9 +97,11 @@ int main() indexed_values.end()); for(const auto& entry : sorted_indexed_values) { - std::visit([&entry](const auto& concrete_value) { - SLIC_INFO(axom::fmt::format("{} = {}", entry.first, concrete_value)); - }, entry.second); + std::visit( + [&entry](const auto& concrete_value) { + SLIC_INFO(axom::fmt::format("{} = {}", entry.first, concrete_value)); + }, + entry.second); } // _inlet_simple_types_variant_arrays_access_map_end @@ -110,9 +111,11 @@ int main() inlet["keyed"].get>(); for(const auto& entry : keyed_values) { - std::visit([&entry](const auto& concrete_value) { - SLIC_INFO(axom::fmt::format("{} = {}", entry.first, concrete_value)); - }, entry.second); + std::visit( + [&entry](const auto& concrete_value) { + SLIC_INFO(axom::fmt::format("{} = {}", entry.first, concrete_value)); + }, + entry.second); } // _inlet_simple_types_variant_dictionary_access_end From f5cfd69ce1b76935af4546c0cda4fd65ddc457ff Mon Sep 17 00:00:00 2001 From: Chris White Date: Fri, 12 Jun 2026 16:15:16 -0700 Subject: [PATCH 384/986] add ability to have user defined structs in variant arrays --- src/axom/inlet/Container.cpp | 8 +- src/axom/inlet/Container.hpp | 275 +++++++++++++++++- src/axom/inlet/Inlet.hpp | 39 +++ src/axom/inlet/docs/sphinx/advanced_types.rst | 26 ++ src/axom/inlet/inlet_utils.hpp | 1 + src/axom/inlet/tests/inlet_object.cpp | 113 +++++++ 6 files changed, 458 insertions(+), 4 deletions(-) diff --git a/src/axom/inlet/Container.cpp b/src/axom/inlet/Container.cpp index a28de9d9fe..7fc8f8d623 100644 --- a/src/axom/inlet/Container.cpp +++ b/src/axom/inlet/Container.cpp @@ -1051,10 +1051,16 @@ Container& Container::registerVerifier(Verifier lambda) bool Container::verify(std::vector* errors) const { + const bool collection_group_provided = isCollectionGroup(m_name) && + m_sidreGroup->hasView(detail::VARIANT_STRUCT_COLLECTION_FLAG) && + m_sidreGroup->hasView("retrieval_status") && + static_cast(static_cast(m_sidreGroup->getView("retrieval_status")->getData())) == + ReaderResult::Success; + // Whether the calling container has anything in it // If the name is empty then we're the global (root) container, which we always // consider to be defined - const bool this_container_defined = isUserProvided() || m_name.empty(); + const bool this_container_defined = isUserProvided() || m_name.empty() || collection_group_provided; // If this container was required, make sure something was defined in it bool verified = verifyRequired(*m_sidreGroup, this_container_defined, "Container", errors); diff --git a/src/axom/inlet/Container.hpp b/src/axom/inlet/Container.hpp index cc9d9743ff..4f2936687a 100644 --- a/src/axom/inlet/Container.hpp +++ b/src/axom/inlet/Container.hpp @@ -18,9 +18,12 @@ #include #include #include +#include +#include #include #include #include +#include #include "axom/fmt.hpp" @@ -61,8 +64,28 @@ namespace inlet class Container; +template +class VariantStructCollection; + namespace detail { +struct VariantStructFactoryBase +{ + virtual ~VariantStructFactoryBase() = default; +}; + +template +struct VariantStructFactory : VariantStructFactoryBase +{ + explicit VariantStructFactory(const std::string& discriminatorName) + : discriminator(discriminatorName) + { } + + std::string discriminator; + std::set labels; + std::unordered_map> constructors; +}; + /*! ******************************************************************************* * \class is_inlet_primitive @@ -174,6 +197,14 @@ template struct is_std_vector> : std::true_type { }; +template +struct is_std_variant : std::false_type +{ }; + +template +struct is_std_variant> : std::true_type +{ }; + template struct is_primitive_std_vector : std::false_type { }; @@ -321,6 +352,45 @@ void updateUnexpectedNames(const std::string& accessedName, } // namespace detail class Proxy; +/*! + ******************************************************************************* + * \class VariantStructCollection + * + * \brief Helper for defining collections whose entries are selected from a + * finite set of user-defined struct types. + ******************************************************************************* + */ +template +class VariantStructCollection +{ +public: + /*! + ***************************************************************************** + * \brief Add a struct alternative to this variant collection. + * + * \param [in] label Discriminator value selecting this alternative + * \param [in] defineSchema Callable accepting a Container& and defining the + * schema for the selected alternative + * \tparam Alternative One of the types held by the Variant + ***************************************************************************** + */ + template + VariantStructCollection& addAlternative(const std::string& label, SchemaDefiner&& defineSchema); + + Container& collection() { return *m_collection; } + +private: + VariantStructCollection(Container& collection, detail::VariantStructFactory& factory) + : m_collection(&collection) + , m_factory(&factory) + { } + + Container* m_collection; + detail::VariantStructFactory* m_factory; + + friend class Container; +}; + /*! ******************************************************************************* * \class Container @@ -467,6 +537,25 @@ class Container : public Verifiable Verifiable& addVariantArray(const std::string& name, const std::string& description = ""); + /*! + ***************************************************************************** + * \brief Add an array of variant user-defined types to the input file schema. + * + * Each element must contain a string discriminator field whose value selects + * one of the registered struct alternatives. + * + * \param [in] name Name of the array + * \param [in] discriminator Name of the field selecting the alternative + * \param [in] description Description of the array + * + * \return Helper used to register struct alternatives + ***************************************************************************** + */ + template + VariantStructCollection addVariantStructArray(const std::string& name, + const std::string& discriminator = "type", + const std::string& description = ""); + /*! ***************************************************************************** * \brief Add an array of Fields to the input file schema. @@ -542,6 +631,23 @@ class Container : public Verifiable Verifiable& addVariantDictionary(const std::string& name, const std::string& description = ""); + /*! + ***************************************************************************** + * \brief Add a dictionary of variant user-defined types to the input file schema. + * + * \param [in] name Name of the dictionary + * \param [in] discriminator Name of the field selecting the alternative + * \param [in] description Description of the dictionary + * + * \return Helper used to register struct alternatives + ***************************************************************************** + */ + template + VariantStructCollection addVariantStructDictionary( + const std::string& name, + const std::string& discriminator = "type", + const std::string& description = ""); + /*! ***************************************************************************** * \brief Add a dictionary of user-defined types to the input file schema. @@ -721,9 +827,9 @@ class Container : public Verifiable ******************************************************************************* */ template - typename std::enable_if::value && - !detail::is_inlet_array::value && !detail::is_inlet_dict::value && - !detail::is_std_vector::value && !detail::is_variant_value::value, + typename std::enable_if::value && !detail::is_inlet_array::value && + !detail::is_inlet_dict::value && !detail::is_std_vector::value && + !detail::is_variant_value::value && !detail::is_std_variant::value, T>::type get(const std::string& name = "") const { @@ -1208,6 +1314,14 @@ class Container : public Verifiable */ template std::unordered_map getCollection() const + { + return getCollectionImpl( + std::integral_constant < bool, + detail::is_std_variant::value && !detail::is_variant_value::value > {}); + } + + template + std::unordered_map getCollectionImpl(std::false_type) const { std::unordered_map map; for(const auto& indexLabel : detail::collectionIndices(*this)) @@ -1220,6 +1334,26 @@ class Container : public Verifiable return map; } + template + std::unordered_map getCollectionImpl(std::true_type) const + { + const auto& factory = variantStructFactory(); + std::unordered_map map; + for(const auto& indexLabel : detail::collectionIndices(*this)) + { + if(detail::matchesKeyType(indexLabel)) + { + const auto& element = getContainer(detail::indexToString(indexLabel)); + const auto label = element.get(factory.discriminator); + const auto constructor = factory.constructors.find(label); + SLIC_ERROR_IF(constructor == factory.constructors.end(), + fmt::format("[Inlet] Unknown variant struct discriminator '{0}'", label)); + map.emplace(detail::toIndex(indexLabel), constructor->second(element)); + } + } + return map; + } + /*! ******************************************************************************* * \brief Adds a group containing the indices of a collection to the calling @@ -1249,6 +1383,17 @@ class Container : public Verifiable template Container& addStructCollection(const std::string& name, const std::string& description = ""); + template + VariantStructCollection addVariantStructCollection(const std::string& name, + const std::string& discriminator, + const std::string& description = ""); + + template + detail::VariantStructFactory& variantStructFactory(const std::string& discriminator); + + template + const detail::VariantStructFactory& variantStructFactory() const; + /*! ***************************************************************************** * \brief Returns true if the calling object is part of a struct collection, @@ -1321,12 +1466,136 @@ class Container : public Verifiable std::vector m_aggregate_fields; std::vector> m_aggregate_funcs; + std::unordered_map> + m_variant_struct_factories; + // Used when the calling Container is a struct collection within a struct collection // Need to delegate schema-defining calls (add*) to the elements of the nested // collection std::vector> m_nested_aggregates; + + template + friend class VariantStructCollection; }; +template +template +VariantStructCollection& VariantStructCollection::addAlternative( + const std::string& label, + SchemaDefiner&& defineSchema) +{ + static_assert(std::is_constructible::value, + "Alternative must be one of the types held by the variant"); + static_assert(detail::has_FromInlet_specialization::value, + "To read a variant struct alternative, specialize FromInlet"); + + m_factory->labels.insert(label); + m_factory->constructors[label] = [](const Container& container) { + FromInlet from_inlet; + return Variant {from_inlet(container)}; + }; + + for(const auto& index : detail::collectionIndices(*m_collection)) + { + Container& element = m_collection->getContainer(detail::indexToString(index)); + if(element.isUserProvided(m_factory->discriminator) && + element.template get(m_factory->discriminator) == label) + { + defineSchema(element); + } + } + + return *this; +} + +template +VariantStructCollection Container::addVariantStructArray(const std::string& name, + const std::string& discriminator, + const std::string& description) +{ + return addVariantStructCollection(name, discriminator, description); +} + +template +VariantStructCollection Container::addVariantStructDictionary(const std::string& name, + const std::string& discriminator, + const std::string& description) +{ + return addVariantStructCollection(name, discriminator, description); +} + +template +VariantStructCollection Container::addVariantStructCollection(const std::string& name, + const std::string& discriminator, + const std::string& description) +{ + static_assert(detail::is_std_variant::value, + "Variant struct collections must be retrieved as std::variant alternatives"); + + auto& collection = addStructCollection(name, description); + if(!collection.m_sidreGroup->hasView(detail::VARIANT_STRUCT_COLLECTION_FLAG)) + { + collection.m_sidreGroup->createViewScalar(detail::VARIANT_STRUCT_COLLECTION_FLAG, true); + } + auto& factory = collection.template variantStructFactory(discriminator); + auto* factory_ptr = &factory; + collection.addString(discriminator, "Variant struct discriminator") + .required() + .registerVerifier([factory_ptr](const Field& field) { + const auto label = field.get(); + return factory_ptr->labels.find(label) != factory_ptr->labels.end(); + }); + collection.m_verifier = [factory_ptr](const Container& container, std::vector*) { + for(const auto& index : detail::collectionIndices(container)) + { + const auto child_name = + utilities::string::appendPrefix(container.name(), detail::indexToString(index)); + const auto& element = *container.getChildContainers().at(child_name); + if(!element.isUserProvided(factory_ptr->discriminator)) + { + return false; + } + + const auto label = element.template get(factory_ptr->discriminator); + if(factory_ptr->labels.find(label) == factory_ptr->labels.end()) + { + return false; + } + } + return true; + }; + return VariantStructCollection(collection, factory); +} + +template +detail::VariantStructFactory& Container::variantStructFactory(const std::string& discriminator) +{ + auto& base_factory = m_variant_struct_factories[std::type_index(typeid(Variant))]; + if(!base_factory) + { + base_factory = std::make_unique>(discriminator); + } + + auto* factory = dynamic_cast*>(base_factory.get()); + SLIC_ERROR_IF(factory == nullptr, "[Inlet] Variant struct factory type mismatch"); + SLIC_ERROR_IF(factory->discriminator != discriminator, + fmt::format("[Inlet] Variant struct collection already uses discriminator '{0}'", + factory->discriminator)); + return *factory; +} + +template +const detail::VariantStructFactory& Container::variantStructFactory() const +{ + const auto factory_iter = m_variant_struct_factories.find(std::type_index(typeid(Variant))); + SLIC_ERROR_IF(factory_iter == m_variant_struct_factories.end(), + "[Inlet] Variant struct collection schema has not been registered"); + + auto* factory = dynamic_cast*>(factory_iter->second.get()); + SLIC_ERROR_IF(factory == nullptr, "[Inlet] Variant struct factory type mismatch"); + return *factory; +} + } // namespace inlet } // namespace axom diff --git a/src/axom/inlet/Inlet.hpp b/src/axom/inlet/Inlet.hpp index 1a8974068e..a0c15c206e 100644 --- a/src/axom/inlet/Inlet.hpp +++ b/src/axom/inlet/Inlet.hpp @@ -393,6 +393,25 @@ class Inlet return m_globalContainer.addStructArray(name, description); } + /*! + ***************************************************************************** + * \brief Add an array of variant user-defined types to the input file schema. + * + * \param [in] name Name of the array + * \param [in] discriminator Name of the field selecting the alternative + * \param [in] description Description of the array + * + * \return Helper used to register struct alternatives + ***************************************************************************** + */ + template + VariantStructCollection addVariantStructArray(const std::string& name, + const std::string& discriminator = "type", + const std::string& description = "") + { + return m_globalContainer.addVariantStructArray(name, discriminator, description); + } + /*! ***************************************************************************** * \brief Get a function from the input deck @@ -507,6 +526,26 @@ class Inlet return m_globalContainer.addStructDictionary(name, description); } + /*! + ***************************************************************************** + * \brief Add a dictionary of variant user-defined types to the input file schema. + * + * \param [in] name Name of the dictionary + * \param [in] discriminator Name of the field selecting the alternative + * \param [in] description Description of the dictionary + * + * \return Helper used to register struct alternatives + ***************************************************************************** + */ + template + VariantStructCollection addVariantStructDictionary( + const std::string& name, + const std::string& discriminator = "type", + const std::string& description = "") + { + return m_globalContainer.addVariantStructDictionary(name, discriminator, description); + } + /*! ***************************************************************************** * \brief Returns the global list of unexpected names, i.e., entries diff --git a/src/axom/inlet/docs/sphinx/advanced_types.rst b/src/axom/inlet/docs/sphinx/advanced_types.rst index 953657cf13..540b2e9cc4 100644 --- a/src/axom/inlet/docs/sphinx/advanced_types.rst +++ b/src/axom/inlet/docs/sphinx/advanced_types.rst @@ -194,3 +194,29 @@ as ``std::unordered_map``: String-keyed dictionaries are implemented as ``std::unordered_map`` and can be retrieved in the same way as the array above. For dictionaries with a mix of string and integer keys, the ``inlet::VariantKey`` type can be used, namely, by retrieving a ``std::unordered_map``. + +Variant Struct Collections +-------------------------- + +Collections whose entries can be selected from a finite set of user-defined struct types can be +defined with ``addVariantStructArray`` or ``addVariantStructDictionary``. Each entry must contain +a string discriminator field, and each alternative still uses its normal ``FromInlet`` specialization +for retrieval: + +.. code-block:: C++ + + using Shape = std::variant; + + auto shapes = inlet.addVariantStructArray("shapes", "kind"); + shapes.addAlternative("circle", [](inlet::Container& circle) { + circle.addDouble("radius").required(); + }); + shapes.addAlternative("box", [](inlet::Container& box) { + box.addDouble("width").required(); + box.addDouble("height").required(); + }); + + auto values = inlet["shapes"].get>(); + +Verification fails if an entry omits the discriminator or uses a discriminator value that was not +registered as an alternative. diff --git a/src/axom/inlet/inlet_utils.hpp b/src/axom/inlet/inlet_utils.hpp index 0f23e456bf..114197a58c 100644 --- a/src/axom/inlet/inlet_utils.hpp +++ b/src/axom/inlet/inlet_utils.hpp @@ -136,6 +136,7 @@ namespace detail const std::string COLLECTION_GROUP_NAME = "_inlet_collection"; const std::string COLLECTION_INDICES_NAME = "_inlet_collection_indices"; const std::string STRUCT_COLLECTION_FLAG = "_inlet_struct_collection"; +const std::string VARIANT_STRUCT_COLLECTION_FLAG = "_inlet_variant_struct_collection"; const std::string REQUIRED_FLAG = "required"; const std::string STRICT_FLAG = "strict"; } // namespace detail diff --git a/src/axom/inlet/tests/inlet_object.cpp b/src/axom/inlet/tests/inlet_object.cpp index 521f87bc15..2cab056a4f 100644 --- a/src/axom/inlet/tests/inlet_object.cpp +++ b/src/axom/inlet/tests/inlet_object.cpp @@ -57,6 +57,46 @@ struct FromInlet } }; +struct Circle +{ + double radius; + + bool operator==(const Circle& other) const { return radius == other.radius; } +}; + +struct Box +{ + double width; + double height; + + bool operator==(const Box& other) const { return width == other.width && height == other.height; } +}; + +using Shape = std::variant; + +template <> +struct FromInlet +{ + Circle operator()(const axom::inlet::Container& base) { return {base["radius"]}; } +}; + +template <> +struct FromInlet +{ + Box operator()(const axom::inlet::Container& base) { return {base["width"], base["height"]}; } +}; + +void defineShapeSchema(axom::inlet::VariantStructCollection& shapes) +{ + shapes.addAlternative("circle", [](axom::inlet::Container& circle) { + circle.addDouble("radius").required(); + }); + shapes.addAlternative("box", [](axom::inlet::Container& box) { + box.addDouble("width").required(); + box.addDouble("height").required(); + }); +} + template class inlet_object : public ::testing::Test { }; @@ -125,6 +165,79 @@ TYPED_TEST(inlet_object, simple_array_of_struct_by_value) EXPECT_EQ(foos, expected_foos); } +TYPED_TEST(inlet_object, variant_array_of_struct_by_value) +{ + std::string testString = + "shapes = { [0] = { kind = \"circle\"; radius = 2.5 }, " + " [1] = { kind = \"box\"; width = 3.0; height = 4.0 } }"; + Inlet inlet = createBasicInlet(testString); + + auto shapes = inlet.addVariantStructArray("shapes", "kind"); + defineShapeSchema(shapes); + + EXPECT_TRUE(inlet.verify()); + + std::unordered_map expected_shapes = {{0, Circle {2.5}}, {1, Box {3.0, 4.0}}}; + auto actual_shapes = inlet["shapes"].get>(); + EXPECT_EQ(actual_shapes, expected_shapes); +} + +TYPED_TEST(inlet_object, variant_array_of_struct_as_vector) +{ + std::string testString = + "shapes = { [0] = { kind = \"circle\"; radius = 2.5 }, " + " [1] = { kind = \"box\"; width = 3.0; height = 4.0 } }"; + Inlet inlet = createBasicInlet(testString); + + auto shapes = inlet.addVariantStructArray("shapes", "kind"); + defineShapeSchema(shapes); + + auto actual_shapes = inlet["shapes"].get>(); + ASSERT_EQ(actual_shapes.size(), 2u); + EXPECT_EQ(actual_shapes[0], Shape(Circle {2.5})); + EXPECT_EQ(actual_shapes[1], Shape(Box {3.0, 4.0})); +} + +TYPED_TEST(inlet_object, variant_dictionary_of_struct_by_value) +{ + std::string testString = + "shapes = { ball = { kind = \"circle\"; radius = 2.5 }, " + " block = { kind = \"box\"; width = 3.0; height = 4.0 } }"; + Inlet inlet = createBasicInlet(testString); + + auto shapes = inlet.addVariantStructDictionary("shapes", "kind"); + defineShapeSchema(shapes); + + EXPECT_TRUE(inlet.verify()); + + std::unordered_map expected_shapes = {{VariantKey {"ball"}, Circle {2.5}}, + {VariantKey {"block"}, Box {3.0, 4.0}}}; + auto actual_shapes = inlet["shapes"].get>(); + EXPECT_EQ(actual_shapes, expected_shapes); +} + +TYPED_TEST(inlet_object, variant_array_of_struct_missing_discriminator_fails) +{ + std::string testString = "shapes = { [0] = { radius = 2.5 } }"; + Inlet inlet = createBasicInlet(testString); + + auto shapes = inlet.addVariantStructArray("shapes", "kind"); + defineShapeSchema(shapes); + + EXPECT_FALSE(inlet.verify()); +} + +TYPED_TEST(inlet_object, variant_array_of_struct_unknown_discriminator_fails) +{ + std::string testString = "shapes = { [0] = { kind = \"triangle\"; side = 2.5 } }"; + Inlet inlet = createBasicInlet(testString); + + auto shapes = inlet.addVariantStructArray("shapes", "kind"); + defineShapeSchema(shapes); + + EXPECT_FALSE(inlet.verify()); +} + TYPED_TEST(inlet_object, simple_array_of_struct_implicit_idx) { std::string testString = From b0ef69c35b5edb63b1445a2d58a2e06034ed71e2 Mon Sep 17 00:00:00 2001 From: Chris White Date: Sat, 13 Jun 2026 13:11:11 -0700 Subject: [PATCH 385/986] add release notes --- RELEASE-NOTES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 9489695352..e983129266 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,6 +39,7 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ - 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. +- Inlet: Added the ability to have collections (array and dictionary) with variant user defined structures. ### Removed From 856d641bdf01fe022227ef66dd0af8ecfad0e90f Mon Sep 17 00:00:00 2001 From: Chris White Date: Sat, 13 Jun 2026 13:33:27 -0700 Subject: [PATCH 386/986] improve example --- src/axom/inlet/docs/sphinx/advanced_types.rst | 21 ++-- src/axom/inlet/examples/CMakeLists.txt | 4 + .../examples/variant_struct_collections.cpp | 109 ++++++++++++++++++ 3 files changed, 121 insertions(+), 13 deletions(-) create mode 100644 src/axom/inlet/examples/variant_struct_collections.cpp diff --git a/src/axom/inlet/docs/sphinx/advanced_types.rst b/src/axom/inlet/docs/sphinx/advanced_types.rst index 540b2e9cc4..549477ce96 100644 --- a/src/axom/inlet/docs/sphinx/advanced_types.rst +++ b/src/axom/inlet/docs/sphinx/advanced_types.rst @@ -203,20 +203,15 @@ defined with ``addVariantStructArray`` or ``addVariantStructDictionary``. Each a string discriminator field, and each alternative still uses its normal ``FromInlet`` specialization for retrieval: -.. code-block:: C++ - - using Shape = std::variant; - - auto shapes = inlet.addVariantStructArray("shapes", "kind"); - shapes.addAlternative("circle", [](inlet::Container& circle) { - circle.addDouble("radius").required(); - }); - shapes.addAlternative("box", [](inlet::Container& box) { - box.addDouble("width").required(); - box.addDouble("height").required(); - }); +.. literalinclude:: ../../examples/variant_struct_collections.cpp + :start-after: _inlet_variant_struct_collections_start + :end-before: _inlet_variant_struct_collections_end + :language: C++ - auto values = inlet["shapes"].get>(); +.. literalinclude:: ../../examples/variant_struct_collections.cpp + :start-after: _inlet_variant_struct_collections_usage_start + :end-before: _inlet_variant_struct_collections_usage_end + :language: C++ Verification fails if an entry omits the discriminator or uses a discriminator value that was not registered as an alternative. diff --git a/src/axom/inlet/examples/CMakeLists.txt b/src/axom/inlet/examples/CMakeLists.txt index 179cea838d..38c7d9026e 100644 --- a/src/axom/inlet/examples/CMakeLists.txt +++ b/src/axom/inlet/examples/CMakeLists.txt @@ -23,6 +23,7 @@ blt_list_append( fields.cpp homogeneous_collections.cpp lua_library.cpp + variant_struct_collections.cpp variant_collections.cpp containers.cpp user_defined_type.cpp @@ -79,6 +80,9 @@ if (SOL_FOUND) axom_add_test( NAME inlet_variant_collections_ex COMMAND inlet_variant_collections_ex ) + axom_add_test( NAME inlet_variant_struct_collections_ex + COMMAND inlet_variant_struct_collections_ex ) + axom_add_test( NAME inlet_containers_ex COMMAND inlet_containers_ex) diff --git a/src/axom/inlet/examples/variant_struct_collections.cpp b/src/axom/inlet/examples/variant_struct_collections.cpp new file mode 100644 index 0000000000..cf43c37a5a --- /dev/null +++ b/src/axom/inlet/examples/variant_struct_collections.cpp @@ -0,0 +1,109 @@ +// 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/inlet.hpp" +#include "axom/slic/core/SimpleLogger.hpp" +#include "axom/fmt.hpp" + +#include +#include +#include +#include + +namespace inlet = axom::inlet; + +// _inlet_variant_struct_collections_start +struct Circle +{ + double radius; +}; + +struct Box +{ + double width; + double height; +}; + +using Shape = std::variant; + +template <> +struct FromInlet +{ + Circle operator()(const inlet::Container& input_data) + { + return {input_data["radius"]}; + } +}; + +template <> +struct FromInlet +{ + Box operator()(const inlet::Container& input_data) + { + return {input_data["width"], input_data["height"]}; + } +}; + +void defineShapeSchema(inlet::VariantStructCollection& shapes) +{ + shapes.addAlternative("circle", [](inlet::Container& circle) { + circle.addDouble("radius").required(); + }); + shapes.addAlternative("box", [](inlet::Container& box) { + box.addDouble("width").required(); + box.addDouble("height").required(); + }); +} +// _inlet_variant_struct_collections_end + +const std::string input = R"( + shapes = { + { kind = "circle", radius = 2.5 }, + { kind = "box", width = 3.0, height = 4.0 } + } +)"; + +int main() +{ + axom::slic::SimpleLogger logger; + + auto lr = std::make_unique(); + lr->parseString(input); + inlet::Inlet inlet(std::move(lr)); + + // _inlet_variant_struct_collections_usage_start + auto shapes_schema = inlet.addVariantStructArray("shapes", "kind"); + defineShapeSchema(shapes_schema); + + if(!inlet.verify()) + { + SLIC_ERROR("Inlet failed to verify against provided schema"); + } + + const std::vector shapes = inlet["shapes"].get>(); + // _inlet_variant_struct_collections_usage_end + + for(const Shape& shape : shapes) + { + std::visit( + [](const auto& concrete_shape) { + using ShapeType = std::decay_t; + if constexpr(std::is_same_v) + { + SLIC_INFO(axom::fmt::format("circle radius = {}", concrete_shape.radius)); + } + else + { + SLIC_INFO(axom::fmt::format("box width = {}, height = {}", + concrete_shape.width, + concrete_shape.height)); + } + }, + shape); + } + + return 0; +} From 8abcab851019a9089721c5238a29925b9662549e Mon Sep 17 00:00:00 2001 From: Chris White Date: Sat, 13 Jun 2026 13:54:16 -0700 Subject: [PATCH 387/986] improve example --- src/axom/inlet/docs/sphinx/advanced_types.rst | 67 +++++++++++++++++-- .../examples/variant_struct_collections.cpp | 56 ++++++++++------ 2 files changed, 98 insertions(+), 25 deletions(-) diff --git a/src/axom/inlet/docs/sphinx/advanced_types.rst b/src/axom/inlet/docs/sphinx/advanced_types.rst index 549477ce96..ce473feb58 100644 --- a/src/axom/inlet/docs/sphinx/advanced_types.rst +++ b/src/axom/inlet/docs/sphinx/advanced_types.rst @@ -198,20 +198,75 @@ in the same way as the array above. For dictionaries with a mix of string and i Variant Struct Collections -------------------------- -Collections whose entries can be selected from a finite set of user-defined struct types can be -defined with ``addVariantStructArray`` or ``addVariantStructDictionary``. Each entry must contain -a string discriminator field, and each alternative still uses its normal ``FromInlet`` specialization -for retrieval: +Variant struct collections store a collection whose entries can be selected from a +finite set of user-defined struct types. They are useful when every entry in a +collection shares the same role, but different entries require different fields. +For example, a ``shapes`` collection might contain both circles and boxes. + +Each entry must contain a string discriminator field. The discriminator value +selects which struct schema and ``FromInlet`` specialization Inlet should use for +that entry. + +Defining And Storing +~~~~~~~~~~~~~~~~~~~~ + +Represent the possible entry types with a ``std::variant``. Each alternative in +the variant is a normal user-defined type, so it still provides its own +``FromInlet`` specialization: .. literalinclude:: ../../examples/variant_struct_collections.cpp :start-after: _inlet_variant_struct_collections_start :end-before: _inlet_variant_struct_collections_end :language: C++ +Use ``addVariantStructArray`` for array-like input. The function takes the +collection name and the discriminator field name. After creating the collection +schema, register each allowed discriminator value with ``addAlternative`` and +define the schema for that alternative: + .. literalinclude:: ../../examples/variant_struct_collections.cpp - :start-after: _inlet_variant_struct_collections_usage_start - :end-before: _inlet_variant_struct_collections_usage_end + :start-after: _inlet_variant_struct_collections_schema_usage_start + :end-before: _inlet_variant_struct_collections_schema_usage_end :language: C++ Verification fails if an entry omits the discriminator or uses a discriminator value that was not registered as an alternative. + +Accessing +~~~~~~~~~ + +Variant struct collections are retrieved as collections of the same +``std::variant`` type used when defining the schema. Call ``verify`` before +retrieval to check that every entry has a known discriminator: + +.. literalinclude:: ../../examples/variant_struct_collections.cpp + :start-after: _inlet_variant_struct_collections_verify_start + :end-before: _inlet_variant_struct_collections_verify_end + :language: C++ + +Contiguous arrays can be retrieved as ``std::vector``: + +.. literalinclude:: ../../examples/variant_struct_collections.cpp + :start-after: _inlet_variant_struct_collections_access_vector_start + :end-before: _inlet_variant_struct_collections_access_vector_end + :language: C++ + +The same array-like collection can also be retrieved as an integer-keyed +dictionary when the original indices are needed: + +.. literalinclude:: ../../examples/variant_struct_collections.cpp + :start-after: _inlet_variant_struct_collections_access_dictionary_start + :end-before: _inlet_variant_struct_collections_access_dictionary_end + :language: C++ + +For associative input, use ``addVariantStructDictionary`` and retrieve the +collection as ``std::unordered_map``. + +Once retrieved, use normal ``std::variant`` access patterns, such as +``std::visit``, ``std::get_if``, or ``std::holds_alternative``, to work with the +concrete struct stored in each entry: + +.. literalinclude:: ../../examples/variant_struct_collections.cpp + :start-after: _inlet_variant_struct_collections_visit_start + :end-before: _inlet_variant_struct_collections_visit_end + :language: C++ diff --git a/src/axom/inlet/examples/variant_struct_collections.cpp b/src/axom/inlet/examples/variant_struct_collections.cpp index cf43c37a5a..6452b189a3 100644 --- a/src/axom/inlet/examples/variant_struct_collections.cpp +++ b/src/axom/inlet/examples/variant_struct_collections.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -66,26 +67,9 @@ const std::string input = R"( } )"; -int main() +// _inlet_variant_struct_collections_visit_start +void printShapes(const std::vector& shapes) { - axom::slic::SimpleLogger logger; - - auto lr = std::make_unique(); - lr->parseString(input); - inlet::Inlet inlet(std::move(lr)); - - // _inlet_variant_struct_collections_usage_start - auto shapes_schema = inlet.addVariantStructArray("shapes", "kind"); - defineShapeSchema(shapes_schema); - - if(!inlet.verify()) - { - SLIC_ERROR("Inlet failed to verify against provided schema"); - } - - const std::vector shapes = inlet["shapes"].get>(); - // _inlet_variant_struct_collections_usage_end - for(const Shape& shape : shapes) { std::visit( @@ -104,6 +88,40 @@ int main() }, shape); } +} +// _inlet_variant_struct_collections_visit_end + +int main() +{ + axom::slic::SimpleLogger logger; + + auto lr = std::make_unique(); + lr->parseString(input); + inlet::Inlet inlet(std::move(lr)); + + // _inlet_variant_struct_collections_schema_usage_start + auto shapes_schema = inlet.addVariantStructArray("shapes", "kind"); + defineShapeSchema(shapes_schema); + // _inlet_variant_struct_collections_schema_usage_end + + // _inlet_variant_struct_collections_verify_start + if(!inlet.verify()) + { + SLIC_ERROR("Inlet failed to verify against provided schema"); + } + // _inlet_variant_struct_collections_verify_end + + // _inlet_variant_struct_collections_access_vector_start + const std::vector shapes = inlet["shapes"].get>(); + // _inlet_variant_struct_collections_access_vector_end + + // _inlet_variant_struct_collections_access_dictionary_start + const std::unordered_map shapes_by_index = + inlet["shapes"].get>(); + // _inlet_variant_struct_collections_access_dictionary_end + + printShapes(shapes); + SLIC_INFO(axom::fmt::format("Read {} shapes by index", shapes_by_index.size())); return 0; } From efec150a1cb08b0437b1353135e15003442f3c57 Mon Sep 17 00:00:00 2001 From: Chris White Date: Sat, 13 Jun 2026 13:59:51 -0700 Subject: [PATCH 388/986] give example of named structs in dictionary --- src/axom/inlet/docs/sphinx/advanced_types.rst | 37 +++++--- .../examples/variant_struct_collections.cpp | 92 +++++++++++++------ 2 files changed, 91 insertions(+), 38 deletions(-) diff --git a/src/axom/inlet/docs/sphinx/advanced_types.rst b/src/axom/inlet/docs/sphinx/advanced_types.rst index ce473feb58..14927bf9b6 100644 --- a/src/axom/inlet/docs/sphinx/advanced_types.rst +++ b/src/axom/inlet/docs/sphinx/advanced_types.rst @@ -219,14 +219,30 @@ the variant is a normal user-defined type, so it still provides its own :end-before: _inlet_variant_struct_collections_end :language: C++ -Use ``addVariantStructArray`` for array-like input. The function takes the -collection name and the discriminator field name. After creating the collection -schema, register each allowed discriminator value with ``addAlternative`` and -define the schema for that alternative: +For array-like input, use ``addVariantStructArray``. The function takes the +collection name and the discriminator field name: .. literalinclude:: ../../examples/variant_struct_collections.cpp - :start-after: _inlet_variant_struct_collections_schema_usage_start - :end-before: _inlet_variant_struct_collections_schema_usage_end + :start-after: _inlet_variant_struct_collections_array_input_start + :end-before: _inlet_variant_struct_collections_array_input_end + :language: lua + +.. literalinclude:: ../../examples/variant_struct_collections.cpp + :start-after: _inlet_variant_struct_collections_array_schema_usage_start + :end-before: _inlet_variant_struct_collections_array_schema_usage_end + :language: C++ + +For named dictionary input, use ``addVariantStructDictionary`` with the same +variant type and discriminator field: + +.. literalinclude:: ../../examples/variant_struct_collections.cpp + :start-after: _inlet_variant_struct_collections_dictionary_input_start + :end-before: _inlet_variant_struct_collections_dictionary_input_end + :language: lua + +.. literalinclude:: ../../examples/variant_struct_collections.cpp + :start-after: _inlet_variant_struct_collections_dictionary_schema_usage_start + :end-before: _inlet_variant_struct_collections_dictionary_schema_usage_end :language: C++ Verification fails if an entry omits the discriminator or uses a discriminator value that was not @@ -244,24 +260,21 @@ retrieval to check that every entry has a known discriminator: :end-before: _inlet_variant_struct_collections_verify_end :language: C++ -Contiguous arrays can be retrieved as ``std::vector``: +The array input can be retrieved as ``std::vector``: .. literalinclude:: ../../examples/variant_struct_collections.cpp :start-after: _inlet_variant_struct_collections_access_vector_start :end-before: _inlet_variant_struct_collections_access_vector_end :language: C++ -The same array-like collection can also be retrieved as an integer-keyed -dictionary when the original indices are needed: +The named dictionary input can be retrieved as +``std::unordered_map``: .. literalinclude:: ../../examples/variant_struct_collections.cpp :start-after: _inlet_variant_struct_collections_access_dictionary_start :end-before: _inlet_variant_struct_collections_access_dictionary_end :language: C++ -For associative input, use ``addVariantStructDictionary`` and retrieve the -collection as ``std::unordered_map``. - Once retrieved, use normal ``std::variant`` access patterns, such as ``std::visit``, ``std::get_if``, or ``std::holds_alternative``, to work with the concrete struct stored in each entry: diff --git a/src/axom/inlet/examples/variant_struct_collections.cpp b/src/axom/inlet/examples/variant_struct_collections.cpp index 6452b189a3..bbcd050a91 100644 --- a/src/axom/inlet/examples/variant_struct_collections.cpp +++ b/src/axom/inlet/examples/variant_struct_collections.cpp @@ -60,49 +60,63 @@ void defineShapeSchema(inlet::VariantStructCollection& shapes) } // _inlet_variant_struct_collections_end -const std::string input = R"( +const std::string array_input = R"( + -- _inlet_variant_struct_collections_array_input_start shapes = { { kind = "circle", radius = 2.5 }, { kind = "box", width = 3.0, height = 4.0 } } + -- _inlet_variant_struct_collections_array_input_end )"; +const std::string dictionary_input = R"( + -- _inlet_variant_struct_collections_dictionary_input_start + shapes = { + ["ball"] = { kind = "circle", radius = 2.5 }, + ["block"] = { kind = "box", width = 3.0, height = 4.0 } + } + -- _inlet_variant_struct_collections_dictionary_input_end +)"; + +void printShape(const Shape& shape) +{ + std::visit( + [](const auto& concrete_shape) { + using ShapeType = std::decay_t; + if constexpr(std::is_same_v) + { + SLIC_INFO(axom::fmt::format("circle radius = {}", concrete_shape.radius)); + } + else + { + SLIC_INFO(axom::fmt::format("box width = {}, height = {}", + concrete_shape.width, + concrete_shape.height)); + } + }, + shape); +} + // _inlet_variant_struct_collections_visit_start void printShapes(const std::vector& shapes) { for(const Shape& shape : shapes) { - std::visit( - [](const auto& concrete_shape) { - using ShapeType = std::decay_t; - if constexpr(std::is_same_v) - { - SLIC_INFO(axom::fmt::format("circle radius = {}", concrete_shape.radius)); - } - else - { - SLIC_INFO(axom::fmt::format("box width = {}, height = {}", - concrete_shape.width, - concrete_shape.height)); - } - }, - shape); + printShape(shape); } } // _inlet_variant_struct_collections_visit_end -int main() +void runArrayExample() { - axom::slic::SimpleLogger logger; - auto lr = std::make_unique(); - lr->parseString(input); + lr->parseString(array_input); inlet::Inlet inlet(std::move(lr)); - // _inlet_variant_struct_collections_schema_usage_start + // _inlet_variant_struct_collections_array_schema_usage_start auto shapes_schema = inlet.addVariantStructArray("shapes", "kind"); defineShapeSchema(shapes_schema); - // _inlet_variant_struct_collections_schema_usage_end + // _inlet_variant_struct_collections_array_schema_usage_end // _inlet_variant_struct_collections_verify_start if(!inlet.verify()) @@ -115,13 +129,39 @@ int main() const std::vector shapes = inlet["shapes"].get>(); // _inlet_variant_struct_collections_access_vector_end + printShapes(shapes); +} + +void runDictionaryExample() +{ + auto lr = std::make_unique(); + lr->parseString(dictionary_input); + inlet::Inlet inlet(std::move(lr)); + + // _inlet_variant_struct_collections_dictionary_schema_usage_start + auto shapes_schema = inlet.addVariantStructDictionary("shapes", "kind"); + defineShapeSchema(shapes_schema); + // _inlet_variant_struct_collections_dictionary_schema_usage_end + + if(!inlet.verify()) + { + SLIC_ERROR("Inlet failed to verify against provided schema"); + } + // _inlet_variant_struct_collections_access_dictionary_start - const std::unordered_map shapes_by_index = - inlet["shapes"].get>(); + const std::unordered_map shapes_by_name = + inlet["shapes"].get>(); // _inlet_variant_struct_collections_access_dictionary_end - printShapes(shapes); - SLIC_INFO(axom::fmt::format("Read {} shapes by index", shapes_by_index.size())); + SLIC_INFO(axom::fmt::format("Read {} named shapes", shapes_by_name.size())); +} + +int main() +{ + axom::slic::SimpleLogger logger; + + runArrayExample(); + runDictionaryExample(); return 0; } From ad11e66316fb608cd5f146dff71f0aa6a3211cb1 Mon Sep 17 00:00:00 2001 From: Chris White Date: Sat, 13 Jun 2026 18:24:48 -0700 Subject: [PATCH 389/986] style --- src/axom/inlet/examples/variant_struct_collections.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/axom/inlet/examples/variant_struct_collections.cpp b/src/axom/inlet/examples/variant_struct_collections.cpp index bbcd050a91..187a5dcc4e 100644 --- a/src/axom/inlet/examples/variant_struct_collections.cpp +++ b/src/axom/inlet/examples/variant_struct_collections.cpp @@ -33,10 +33,7 @@ using Shape = std::variant; template <> struct FromInlet { - Circle operator()(const inlet::Container& input_data) - { - return {input_data["radius"]}; - } + Circle operator()(const inlet::Container& input_data) { return {input_data["radius"]}; } }; template <> From 5071b2f3e5eba43504a916c32bf7231c253667ae Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 15 Jun 2026 11:14:17 -0700 Subject: [PATCH 390/986] 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 391/986] 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 392/986] 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 393/986] 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 e1e4d381151734cf3ca7756340c413e3c75ebfa3 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Fri, 12 Jun 2026 15:50:41 -0700 Subject: [PATCH 394/986] Bugfix: ArrayBase has a stride that must be copied/assigned --- src/axom/core/ArrayBase.hpp | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/axom/core/ArrayBase.hpp b/src/axom/core/ArrayBase.hpp index ffb44ad5b8..3b5fb2b8f3 100644 --- a/src/axom/core/ArrayBase.hpp +++ b/src/axom/core/ArrayBase.hpp @@ -584,15 +584,25 @@ class ArrayBase : m_stride(mapping.strides()[0]) { } - // Empty implementation because no member data + /*! + * \brief Copy the stride from another 1D array-like object. + * + * When this array is a view, the source's spacing must be preserved so the + * view continues to address the same elements. When this array is owning, + * the stride is the compile-time unit stride regardless of the source + * (a deep copy from a strided view compacts into contiguous storage). + */ template - AXOM_HOST_DEVICE ArrayBase(const ArrayBase::type, 1, OtherArrayType>&) + AXOM_HOST_DEVICE ArrayBase( + const ArrayBase::type, 1, OtherArrayType>& other) + : m_stride(static_cast(other.minStride())) { } - // Empty implementation because no member data + /// \overload template AXOM_HOST_DEVICE ArrayBase( - const ArrayBase::type, 1, OtherArrayType>&) + const ArrayBase::type, 1, OtherArrayType>& other) + : m_stride(static_cast(other.minStride())) { } /// \brief Returns the dimensions of the Array @@ -655,8 +665,8 @@ class ArrayBase /// @} /// \brief Swaps two ArrayBases - /// No member data, so this is a no-op - void swap(ArrayBase&) { } + /// Swaps the stride; this is a no-op for owning arrays (unit stride). + void swap(ArrayBase& other) { std::swap(m_stride, other.m_stride); } /// \brief Set the shape /// No member data, so this is a no-op From be8f69cdcf69a507c7d5cd46a30da9e3691194ce Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Fri, 12 Jun 2026 15:55:35 -0700 Subject: [PATCH 395/986] Adds tests to confirm that ArrayView strides are copied/assigned --- src/axom/core/tests/core_array.hpp | 44 ++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/axom/core/tests/core_array.hpp b/src/axom/core/tests/core_array.hpp index 36a9715945..227d2eb2da 100644 --- a/src/axom/core/tests/core_array.hpp +++ b/src/axom/core/tests/core_array.hpp @@ -2434,6 +2434,50 @@ void test_resize_with_stackarray(DataType value) } } +TEST(core_array, check_1D_view_spacing_preserved) +{ + int buf[12]; + for(int i = 0; i < 12; i++) + { + buf[i] = i; + } + + // A strided 1D view referencing elements {0, 3, 6, 9} + axom::ArrayView v(buf, {{4}}, axom::StackArray {{3}}); + EXPECT_EQ(v.minStride(), 3); + EXPECT_EQ(v[1], 3); + + // A strided 1D view created through the min_stride constructor + { + axom::ArrayView vs(buf, {{4}}, 3); + EXPECT_EQ(vs.minStride(), 3); + EXPECT_EQ(vs[2], 6); + } + + // Conversion to a view-of-const must preserve the spacing + { + axom::ArrayView cv = v; + EXPECT_EQ(cv.minStride(), 3); + EXPECT_EQ(cv[1], 3); + EXPECT_EQ(cv[3], 9); + EXPECT_EQ(cv.mapping().strides()[0], 3); + } + + // Copy construction must preserve the spacing + { + axom::ArrayView v2(v); + EXPECT_EQ(v2.minStride(), 3); + EXPECT_EQ(v2[2], 6); + } + + // Owning 1D arrays are always contiguous (unit stride) + { + axom::Array a(4); + EXPECT_EQ(a.minStride(), 1); + EXPECT_EQ(a.mapping().strides()[0], 1); + } +} + TEST(core_array, resize_stackarray) { test_resize_with_stackarray(false); From 47e22800e5a833d7249f718aef84ecfd4374951f Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Fri, 12 Jun 2026 16:04:45 -0700 Subject: [PATCH 396/986] Optimization: axom::Array never has non-unit stride Setting it to compile time 1 (in ArrayBase) avoids the cost in ArrayBase::operator[]. Note that ArrayView can still have non-unit strides. --- src/axom/core/ArrayBase.hpp | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/src/axom/core/ArrayBase.hpp b/src/axom/core/ArrayBase.hpp index 3b5fb2b8f3..dda7313508 100644 --- a/src/axom/core/ArrayBase.hpp +++ b/src/axom/core/ArrayBase.hpp @@ -562,6 +562,21 @@ class ArrayBase private: constexpr static bool is_array_view = detail::ArrayTraits::is_view; + /*! + * \brief Empty stand-in for the stride of an owning 1D Array. + * + * Owning 1D arrays are always contiguous, so their stride is the compile-time constant 1. + * Encoding that in the type (rather than storing a runtime int that is invariantly 1) + * lets operator[] and flatIndex() compile down to data()[idx], with no runtime multiply on the + * address-generation path. ArrayViews support runtime spacing and continue to store their stride as an int. + */ + struct UnitStrideTag + { + AXOM_HOST_DEVICE constexpr UnitStrideTag(int = 1) { } + AXOM_HOST_DEVICE constexpr operator int() const { return 1; } + }; + using StrideStorage = typename std::conditional::type; + public: /* If ArrayType is an ArrayView, we use shallow-const semantics, akin to * std::span; a const ArrayView will still allow for mutating the underlying @@ -574,15 +589,22 @@ class ArrayBase AXOM_HOST_DEVICE ArrayBase(IndexType = 0) { } - AXOM_HOST_DEVICE ArrayBase(const StackArray&, int stride = 1) : m_stride(stride) { } + AXOM_HOST_DEVICE ArrayBase(const StackArray&, int stride = 1) : m_stride(stride) + { + assert(is_array_view || stride == 1); + } AXOM_HOST_DEVICE ArrayBase(const StackArray&, const StackArray& stride) : m_stride(static_cast(stride[0])) - { } + { + assert(is_array_view || stride[0] == 1); + } AXOM_HOST_DEVICE ArrayBase(const StackArray&, const MDMapping<1>& mapping) - : m_stride(mapping.strides()[0]) - { } + : m_stride(static_cast(mapping.strides()[0])) + { + assert(is_array_view || mapping.strides()[0] == 1); + } /*! * \brief Copy the stride from another 1D array-like object. @@ -710,7 +732,7 @@ class ArrayBase } /// @} - int m_stride {1}; + StrideStorage m_stride {1}; }; //------------------------------------------------------------------------------ From 4699ece4acf696e77383acddf22fcece0a027047 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Fri, 12 Jun 2026 16:10:17 -0700 Subject: [PATCH 397/986] Updates RELEASE-NOTES --- RELEASE-NOTES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index e59f2e6265..0aa736a8f9 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -48,11 +48,13 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ ### Changed - Updates CMake code check targets to only use checked in files (via `git ls-files`, when available) +- Core: Optimization for axom::Array indirection -- since the stride is always 1, we can remove the runtime multiplication ### Fixed - Primal: Fixes signs of `compute_moments` to match orientation convention in `primal::evaluate_area_integral` - Quest: Improves error handling/reporting when loading an invalid c2c contour - Primal: Improves reproducibility of 3D GWN methods by removing some sources of randomness +- Core: ArrayView assigments/copies now copy the stride ## [Version 0.14.0] - Release date 2026-03-31 From 3495d8d0718dd206873c303ac2f56ed31eae4192 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Fri, 12 Jun 2026 17:49:55 -0700 Subject: [PATCH 398/986] Adds benchmark tests for array access operators --- src/axom/core/tests/core_benchmark_array.cpp | 123 ++++++++++++++++++- 1 file changed, 121 insertions(+), 2 deletions(-) diff --git a/src/axom/core/tests/core_benchmark_array.cpp b/src/axom/core/tests/core_benchmark_array.cpp index 634490468b..07b5b682e8 100644 --- a/src/axom/core/tests/core_benchmark_array.cpp +++ b/src/axom/core/tests/core_benchmark_array.cpp @@ -26,8 +26,9 @@ enum class ArrayFeatureBenchmarks Constructors = 1 << 0, Insertion = 1 << 1, Iterators = 1 << 2, + Access = 1 << 3, - All = Constructors | Insertion | Iterators + All = Constructors | Insertion | Iterators | Access }; inline ArrayFeatureBenchmarks operator|(ArrayFeatureBenchmarks lhs, ArrayFeatureBenchmarks rhs) @@ -68,7 +69,8 @@ struct axom::fmt::formatter static const std::map feature_map = { {ArrayFeatureBenchmarks::Constructors, "Constructors"}, {ArrayFeatureBenchmarks::Insertion, "Insertion"}, - {ArrayFeatureBenchmarks::Iterators, "Iterators"}}; + {ArrayFeatureBenchmarks::Iterators, "Iterators"}, + {ArrayFeatureBenchmarks::Access, "Access"}}; if(feature == ArrayFeatureBenchmarks::None) { @@ -369,6 +371,97 @@ void iterate_direct(benchmark::State& state) } } +//----------------------------------------------------------------------------- +// Benchmarks for contiguous 1D element access +//----------------------------------------------------------------------------- +template +void access_bracket(benchmark::State& state) +{ + using T = typename Container::value_type; + const int size = state.range(0); + + Container data(size); + for(int i = 0; i < size; ++i) + { + data[i] = static_cast(i); + } + + for(auto _ : state) + { + T sum {}; + for(int i = 0; i < size; ++i) + { + sum += data[i]; + } + benchmark::DoNotOptimize(sum); + } + + state.SetItemsProcessed(static_cast(state.iterations()) * size); +} + +template +void address_bracket(benchmark::State& state) +{ + using T = typename Container::value_type; + const int size = state.range(0); + + Container data(size); + + for(auto _ : state) + { + for(int i = 0; i < size; ++i) + { + T* ptr = &data[i]; + benchmark::DoNotOptimize(ptr); + } + } + + state.SetItemsProcessed(static_cast(state.iterations()) * size); +} + +template +void access_flatIndex(benchmark::State& state) +{ + const int size = state.range(0); + + axom::Array data(size); + for(int i = 0; i < size; ++i) + { + data[i] = static_cast(i); + } + + for(auto _ : state) + { + T sum {}; + for(int i = 0; i < size; ++i) + { + sum += data.flatIndex(i); + } + benchmark::DoNotOptimize(sum); + } + + state.SetItemsProcessed(static_cast(state.iterations()) * size); +} + +template +void address_flatIndex(benchmark::State& state) +{ + const int size = state.range(0); + + axom::Array data(size); + + for(auto _ : state) + { + for(int i = 0; i < size; ++i) + { + T* ptr = &data.flatIndex(i); + benchmark::DoNotOptimize(ptr); + } + } + + state.SetItemsProcessed(static_cast(state.iterations()) * size); +} + //----------------------------------------------------------------------------- // Register all the tests //----------------------------------------------------------------------------- @@ -410,6 +503,30 @@ void RegisterBenchmark() // clang-format on } +void RegisterAccessBenchmarks() +{ + if((args_benchmark_features & ArrayFeatureBenchmarks::Access) == ArrayFeatureBenchmarks::None) + { + return; + } + + benchmark::RegisterBenchmark("Array::access_bracket", + &access_bracket>) + ->Apply(CustomArgs); + benchmark::RegisterBenchmark("Array::access_flatIndex", &access_flatIndex) + ->Apply(CustomArgs); + benchmark::RegisterBenchmark("Array::address_bracket", + &address_bracket>) + ->Apply(CustomArgs); + benchmark::RegisterBenchmark("Array::address_flatIndex", &address_flatIndex) + ->Apply(CustomArgs); + benchmark::RegisterBenchmark("vector::access_bracket", &access_bracket>) + ->Apply(CustomArgs); + benchmark::RegisterBenchmark("vector::address_bracket", + &address_bracket>) + ->Apply(CustomArgs); +} + //----------------------------------------------------------------------------- // Main and helper functions to consistently register templated test types // @@ -470,6 +587,7 @@ int main(int argc, char* argv[]) {"constructors", ArrayFeatureBenchmarks::Constructors}, {"insertion", ArrayFeatureBenchmarks::Insertion}, {"iterators", ArrayFeatureBenchmarks::Iterators}, + {"access", ArrayFeatureBenchmarks::Access}, {"all", ArrayFeatureBenchmarks::All}}; std::string lower_feature = feature; @@ -507,6 +625,7 @@ int main(int argc, char* argv[]) } RegisterBenchmarks(); + RegisterAccessBenchmarks(); ::benchmark::RunSpecifiedBenchmarks(); return 0; } From 0ac48fc97ceb1273f0a42a89b97463941f764514 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Fri, 12 Jun 2026 19:54:27 -0700 Subject: [PATCH 399/986] Bugfix: When copying a strided ArrayView to Array, we need to respect the stride --- RELEASE-NOTES.md | 1 + src/axom/core/Array.hpp | 29 +++++++---- src/axom/core/ArrayBase.hpp | 77 ++++++++++++++++++++++++++---- src/axom/core/tests/core_array.hpp | 45 +++++++++++++++++ 4 files changed, 135 insertions(+), 17 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 0aa736a8f9..831f46a272 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -55,6 +55,7 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ - Quest: Improves error handling/reporting when loading an invalid c2c contour - Primal: Improves reproducibility of 3D GWN methods by removing some sources of randomness - Core: ArrayView assigments/copies now copy the stride +- Core: Array construction from strided ArrayView now correctly copies the strided elements ## [Version 0.14.0] - Release date 2026-03-31 diff --git a/src/axom/core/Array.hpp b/src/axom/core/Array.hpp index 98d2143731..b103ffbc72 100644 --- a/src/axom/core/Array.hpp +++ b/src/axom/core/Array.hpp @@ -983,17 +983,17 @@ class Array : public ArrayBase>, pro void initialize(IndexType num_elements, IndexType capacity, bool should_default_construct = true); /*! - * \brief Helper function for initializing an Array instance with an existing - * range of elements. + * \brief Helper function for initializing an Array instance with an existing range of elements. * * \param [in] data pointer to the existing array of elements * \param [in] num_elements the number of elements in the existing array + * \param [in] src_stride the inter-element stride between elements of the existing array * \param [in] data_space the memory space in which data has been allocated - * \param [in] user_provided_allocator true if the Array's allocator ID was - * provided by the user + * \param [in] user_provided_allocator true if the Array's allocator ID was provided by the user */ void initialize_from_other(const T* data, IndexType num_elements, + IndexType src_stride, MemorySpace data_space, bool user_provided_allocator); @@ -1202,7 +1202,7 @@ Array::Array(std::initializer_list elems, int a : m_allocator_id(allocator_id) , m_arrayOps(m_allocator_id, m_executeOnGPU) { - initialize_from_other(elems.begin(), elems.size(), MemorySpace::Dynamic, true); + initialize_from_other(elems.begin(), elems.size(), 1 /* stride */, MemorySpace::Dynamic, true); } //------------------------------------------------------------------------------ @@ -1275,6 +1275,7 @@ Array::Array(const ArrayBase(other).data(), static_cast(other).size(), + other.minStride(), axom::detail::getAllocatorSpace(m_allocator_id), false); } @@ -1289,6 +1290,7 @@ Array::Array(const ArrayBase(other).data(), static_cast(other).size(), + other.minStride(), axom::detail::getAllocatorSpace(m_allocator_id), false); } @@ -1306,6 +1308,7 @@ Array::Array(const ArrayBase(other).data(), static_cast(other).size(), + other.minStride(), axom::detail::getAllocatorSpace(src_allocator), true); } @@ -1323,6 +1326,7 @@ Array::Array(const ArrayBase(other).data(), static_cast(other).size(), + other.minStride(), axom::detail::getAllocatorSpace(src_allocator), true); } @@ -1388,7 +1392,7 @@ inline void Array::assign(InputIt first, InputIt l { tmp.push_back(*it); } - initialize_from_other(tmp.data(), tmp.size(), MemorySpace::Dynamic, true); + initialize_from_other(tmp.data(), tmp.size(), 1 /* stride */, MemorySpace::Dynamic, true); } //------------------------------------------------------------------------------ @@ -1715,6 +1719,7 @@ template inline void Array::initialize_from_other( const T* other_data, IndexType num_elements, + IndexType src_stride, MemorySpace other_data_space, bool AXOM_DEBUG_PARAM(user_provided_allocator)) { @@ -1734,9 +1739,15 @@ inline void Array::initialize_from_other( m_executeOnGPU = axom::isDeviceAllocator(m_allocator_id); m_arrayOps = OpHelper {m_allocator_id, m_executeOnGPU}; this->setCapacity(num_elements); - // Use fill_range to ensure that copy constructors are invoked for each - // element. - m_arrayOps.fill_range(m_data, 0, num_elements, other_data, other_data_space); + // Use strided copy when necessary, otherwise use efficient contiguous copy + if(src_stride == 1) + { + m_arrayOps.fill_range(m_data, 0, num_elements, other_data, other_data_space); + } + else + { + m_arrayOps.fill_range_strided(m_data, 0, num_elements, other_data, src_stride, other_data_space); + } this->updateNumElements(num_elements); } diff --git a/src/axom/core/ArrayBase.hpp b/src/axom/core/ArrayBase.hpp index dda7313508..197c88f354 100644 --- a/src/axom/core/ArrayBase.hpp +++ b/src/axom/core/ArrayBase.hpp @@ -7,15 +7,15 @@ #ifndef AXOM_ARRAYBASE_HPP_ #define AXOM_ARRAYBASE_HPP_ -#include "axom/config.hpp" // for compile-time defines -#include "axom/core/Macros.hpp" // for axom macros -#include "axom/core/MDMapping.hpp" // for index conversion -#include "axom/core/memory_management.hpp" // for memory allocation functions -#include "axom/core/utilities/Utilities.hpp" // for processAbort() -#include "axom/core/Types.hpp" // for IndexType definition +#include "axom/config.hpp" +#include "axom/core/Macros.hpp" +#include "axom/core/MDMapping.hpp" +#include "axom/core/memory_management.hpp" +#include "axom/core/utilities/Utilities.hpp" +#include "axom/core/Types.hpp" #include "axom/core/StackArray.hpp" -#include "axom/core/numerics/matvecops.hpp" // for dot_product -#include "axom/core/execution/for_all.hpp" // for for_all, *_EXEC +#include "axom/core/numerics/matvecops.hpp" +#include "axom/core/execution/for_all.hpp" // C/C++ includes #include // for std::cerr and std::ostream @@ -1105,6 +1105,67 @@ struct ArrayOps } } + /*! + * \brief Fills an uninitialized array with a strided range of objects of type T. + * + * Similar to fill_range, but handles source data with non-unit stride. When src_stride == 1, + * this behaves identically to fill_range (and uses the same fast path). When src_stride > 1, + * elements are copied from positions 0, src_stride, 2*src_stride, etc. + * + * \param [inout] array the array to fill + * \param [in] begin the index at which to begin placing elements + * \param [in] nelems the number of elements to copy + * \param [in] values pointer to the first element of the source data + * \param [in] src_stride spacing between consecutive source elements + * \param [in] valueSpace the memory space in which values resides + */ + void fill_range_strided(T* array, + IndexType begin, + IndexType nelems, + const T* values, + IndexType src_stride, + MemorySpace valueSpace) + { + if constexpr(std::is_trivially_copyable_v) + { + if(src_stride == 1) + { + // Contiguous case - use efficient bulk copy + axom::copy(array + begin, values, sizeof(T) * nelems); + } + else + { + // Strided case - element-by-element copy + StagingBuffer dst_buf(space, array, begin, nelems); + DeviceStagingBuffer src_buf(valueSpace, const_cast(values), 0, nelems * src_stride, true); + + T* dst = dst_buf.getStagingBuffer(); + const T* src = src_buf.getStagingBuffer(); + + for(IndexType i = 0; i < nelems; ++i) + { + dst[i] = src[i * src_stride]; + } + // Staging buffers clean up automatically via destructors + } + } + else + { + // Non-trivially copyable - use placement new with stride + StagingBuffer dst_buf(space, array, begin, nelems); + DeviceStagingBuffer src_buf(valueSpace, const_cast(values), 0, nelems * src_stride, true); + + T* dst = dst_buf.getStagingBuffer(); + const T* src = src_buf.getStagingBuffer(); + + for(IndexType i = 0; i < nelems; ++i) + { + new(&dst[i]) T(src[i * src_stride]); + } + // Staging buffers clean up automatically via destructors + } + } + /*! * \brief Constructs a new element in uninitialized memory. * diff --git a/src/axom/core/tests/core_array.hpp b/src/axom/core/tests/core_array.hpp index 227d2eb2da..f798d829b4 100644 --- a/src/axom/core/tests/core_array.hpp +++ b/src/axom/core/tests/core_array.hpp @@ -2476,6 +2476,51 @@ TEST(core_array, check_1D_view_spacing_preserved) EXPECT_EQ(a.minStride(), 1); EXPECT_EQ(a.mapping().strides()[0], 1); } + + // Deep copy from strided view to owning array -- must copy the values, not contiguous indices + { + int source[10]; + for(int i = 0; i < 10; ++i) + { + source[i] = i * 10; // {0, 10, 20, 30, 40, 50, 60, 70, 80, 90} + } + + // Strided view with stride=2 references elements {0, 20, 40, 60, 80} + axom::ArrayView strided_view(source, {{5}}, 2); + EXPECT_EQ(strided_view.minStride(), 2); + EXPECT_EQ(strided_view[0], 0); + EXPECT_EQ(strided_view[1], 20); + EXPECT_EQ(strided_view[2], 40); + EXPECT_EQ(strided_view[3], 60); + EXPECT_EQ(strided_view[4], 80); + + // Deep copy to owning array - should preserve the VALUES + axom::Array arr(strided_view); + EXPECT_EQ(arr.size(), 5); + EXPECT_EQ(arr.minStride(), 1); // Owning array is contiguous + EXPECT_EQ(arr[0], 0); // Should copy source[0*2], not source[0] + EXPECT_EQ(arr[1], 20); // Should copy source[1*2], not source[1] + EXPECT_EQ(arr[2], 40); // Should copy source[2*2], not source[2] + EXPECT_EQ(arr[3], 60); // Should copy source[3*2], not source[3] + EXPECT_EQ(arr[4], 80); // Should copy source[4*2], not source[4] + } + + // Test with stride=3 + { + int source[12]; + for(int i = 0; i < 12; ++i) + { + source[i] = i * 100; + } + + axom::ArrayView view3(source, {{4}}, 3); // {0, 300, 600, 900} + axom::Array arr3(view3); + EXPECT_EQ(arr3.size(), 4); + EXPECT_EQ(arr3[0], 0); + EXPECT_EQ(arr3[1], 300); + EXPECT_EQ(arr3[2], 600); + EXPECT_EQ(arr3[3], 900); + } } TEST(core_array, resize_stackarray) From 9646485dafc20c6e7dc05da35bbe8fc4f5dc1f0a Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Fri, 12 Jun 2026 19:55:11 -0700 Subject: [PATCH 400/986] Fixes warning -- assert is related to NDEBUG, not AXOM_DEBUG --- src/axom/bump/views/Shapes.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/axom/bump/views/Shapes.hpp b/src/axom/bump/views/Shapes.hpp index aa41c74b69..a75298196d 100644 --- a/src/axom/bump/views/Shapes.hpp +++ b/src/axom/bump/views/Shapes.hpp @@ -87,7 +87,7 @@ struct PointTraits AXOM_HOST_DEVICE constexpr static axom::StackArray getFace(IndexType faceIndex) { -#if !defined(AXOM_DEBUG) +#ifdef NDEBUG AXOM_UNUSED_VAR(faceIndex); #endif assert(faceIndex == 0); @@ -136,7 +136,7 @@ struct LineTraits AXOM_HOST_DEVICE constexpr static axom::StackArray getFace(IndexType faceIndex) { -#if !defined(AXOM_DEBUG) +#ifdef NDEBUG AXOM_UNUSED_VAR(faceIndex); #endif assert(faceIndex == 0); @@ -190,7 +190,7 @@ struct TriTraits AXOM_HOST_DEVICE constexpr static axom::StackArray getFace(IndexType faceIndex) { -#if !defined(AXOM_DEBUG) +#ifdef NDEBUG AXOM_UNUSED_VAR(faceIndex); #endif assert(faceIndex == 0); @@ -245,7 +245,7 @@ struct QuadTraits AXOM_HOST_DEVICE constexpr static axom::StackArray getFace(IndexType faceIndex) { -#if !defined(AXOM_DEBUG) +#ifdef NDEBUG AXOM_UNUSED_VAR(faceIndex); #endif assert(faceIndex == 0); From 0de4c781469c8d694b2817d7505be6dfc7323116 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Fri, 12 Jun 2026 20:28:41 -0700 Subject: [PATCH 401/986] Marks some stride-related ArrayBase functions as constexpr --- src/axom/core/ArrayBase.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/axom/core/ArrayBase.hpp b/src/axom/core/ArrayBase.hpp index 197c88f354..387a76d65d 100644 --- a/src/axom/core/ArrayBase.hpp +++ b/src/axom/core/ArrayBase.hpp @@ -638,7 +638,7 @@ class ArrayBase /*! * \brief Returns the stride between adjacent items. */ - AXOM_HOST_DEVICE IndexType minStride() const { return m_stride; } + AXOM_HOST_DEVICE constexpr IndexType minStride() const { return m_stride; } /*! * \brief Accessor, returns a reference to the given value. @@ -698,7 +698,7 @@ class ArrayBase /*! * \brief Returns the minimum "chunk size" that should be allocated */ - IndexType blockSize() const { return m_stride; } + constexpr IndexType blockSize() const { return m_stride; } /*! * \brief Updates the internal dimensions and striding based on the insertion From f0665597209644d84331363c7e05d2a58ef75ad4 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Mon, 15 Jun 2026 12:27:55 -0700 Subject: [PATCH 402/986] Address PR suggestion and minor formatting --- src/axom/bump/views/Shapes.hpp | 8 ++++---- src/axom/core/tests/core_array.hpp | 18 +++++++----------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/axom/bump/views/Shapes.hpp b/src/axom/bump/views/Shapes.hpp index a75298196d..1efd6b5e8e 100644 --- a/src/axom/bump/views/Shapes.hpp +++ b/src/axom/bump/views/Shapes.hpp @@ -87,7 +87,7 @@ struct PointTraits AXOM_HOST_DEVICE constexpr static axom::StackArray getFace(IndexType faceIndex) { -#ifdef NDEBUG +#if defined(NDEBUG) AXOM_UNUSED_VAR(faceIndex); #endif assert(faceIndex == 0); @@ -136,7 +136,7 @@ struct LineTraits AXOM_HOST_DEVICE constexpr static axom::StackArray getFace(IndexType faceIndex) { -#ifdef NDEBUG +#if defined(NDEBUG) AXOM_UNUSED_VAR(faceIndex); #endif assert(faceIndex == 0); @@ -190,7 +190,7 @@ struct TriTraits AXOM_HOST_DEVICE constexpr static axom::StackArray getFace(IndexType faceIndex) { -#ifdef NDEBUG +#if defined(NDEBUG) AXOM_UNUSED_VAR(faceIndex); #endif assert(faceIndex == 0); @@ -245,7 +245,7 @@ struct QuadTraits AXOM_HOST_DEVICE constexpr static axom::StackArray getFace(IndexType faceIndex) { -#ifdef NDEBUG +#if defined(NDEBUG) AXOM_UNUSED_VAR(faceIndex); #endif assert(faceIndex == 0); diff --git a/src/axom/core/tests/core_array.hpp b/src/axom/core/tests/core_array.hpp index f798d829b4..60712a01b6 100644 --- a/src/axom/core/tests/core_array.hpp +++ b/src/axom/core/tests/core_array.hpp @@ -2479,11 +2479,7 @@ TEST(core_array, check_1D_view_spacing_preserved) // Deep copy from strided view to owning array -- must copy the values, not contiguous indices { - int source[10]; - for(int i = 0; i < 10; ++i) - { - source[i] = i * 10; // {0, 10, 20, 30, 40, 50, 60, 70, 80, 90} - } + int source[10] = {0, 10, 20, 30, 40, 50, 60, 70, 80, 90}; // Strided view with stride=2 references elements {0, 20, 40, 60, 80} axom::ArrayView strided_view(source, {{5}}, 2); @@ -2494,15 +2490,15 @@ TEST(core_array, check_1D_view_spacing_preserved) EXPECT_EQ(strided_view[3], 60); EXPECT_EQ(strided_view[4], 80); - // Deep copy to owning array - should preserve the VALUES + // Deep copy to owning array - should preserve the stride axom::Array arr(strided_view); EXPECT_EQ(arr.size(), 5); EXPECT_EQ(arr.minStride(), 1); // Owning array is contiguous - EXPECT_EQ(arr[0], 0); // Should copy source[0*2], not source[0] - EXPECT_EQ(arr[1], 20); // Should copy source[1*2], not source[1] - EXPECT_EQ(arr[2], 40); // Should copy source[2*2], not source[2] - EXPECT_EQ(arr[3], 60); // Should copy source[3*2], not source[3] - EXPECT_EQ(arr[4], 80); // Should copy source[4*2], not source[4] + EXPECT_EQ(arr[0], 0); // Each should copy source[i*2], not source[i] + EXPECT_EQ(arr[1], 20); + EXPECT_EQ(arr[2], 40); + EXPECT_EQ(arr[3], 60); + EXPECT_EQ(arr[4], 80); } // Test with stride=3 From 17ac2e52723e7340431ddf8762eb83286e126dd7 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Mon, 15 Jun 2026 12:39:22 -0700 Subject: [PATCH 403/986] Fixes dangling ref warnings in sina w/ gcc --- src/axom/sina/core/Curve.cpp | 7 +++++-- src/axom/sina/tests/sina_ConduitUtil.cpp | 9 +++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/axom/sina/core/Curve.cpp b/src/axom/sina/core/Curve.cpp index a31baca613..5e756eef02 100644 --- a/src/axom/sina/core/Curve.cpp +++ b/src/axom/sina/core/Curve.cpp @@ -18,6 +18,7 @@ #include "axom/sina/core/ConduitUtil.hpp" #include +#include namespace axom { @@ -52,8 +53,10 @@ Curve::Curve(std::string name_, conduit::Node const &curveAsNode) , units {} , tags {} { - auto &valuesAsNode = getRequiredField(VALUES_KEY, curveAsNode, CURVE_TYPE_NAME); - values = toDoubleVector(valuesAsNode, VALUES_KEY); + std::string const curve_type_name {CURVE_TYPE_NAME}; + std::string const values_key {VALUES_KEY}; + conduit::Node const &valuesAsNode = getRequiredField(values_key, curveAsNode, curve_type_name); + values = toDoubleVector(valuesAsNode, values_key); units = getOptionalString(UNITS_KEY, curveAsNode, CURVE_TYPE_NAME); diff --git a/src/axom/sina/tests/sina_ConduitUtil.cpp b/src/axom/sina/tests/sina_ConduitUtil.cpp index 7635ebce74..4901dd58ac 100644 --- a/src/axom/sina/tests/sina_ConduitUtil.cpp +++ b/src/axom/sina/tests/sina_ConduitUtil.cpp @@ -5,6 +5,7 @@ // SPDX-License-Identifier: (BSD-3-Clause) #include +#include #include "gtest/gtest.h" #include "gmock/gmock.h" @@ -31,7 +32,9 @@ TEST(ConduitUtil, getRequiredField_present) { conduit::Node parent; parent["fieldName"] = "field value"; - auto &field = getRequiredField("fieldName", parent, "parent name"); + std::string const field_name {"fieldName"}; + std::string const parent_name {"parent name"}; + conduit::Node const &field = getRequiredField(field_name, parent, parent_name); EXPECT_TRUE(field.dtype().is_string()); EXPECT_EQ("field value", field.as_string()); } @@ -41,7 +44,9 @@ TEST(ConduitUtil, getRequiredField_missing) conduit::Node parent; try { - auto &field = getRequiredField("fieldName", parent, "parent name"); + std::string const field_name {"fieldName"}; + std::string const parent_name {"parent name"}; + conduit::Node const &field = getRequiredField(field_name, parent, parent_name); FAIL() << "Should not have found field, but got " << field.name(); } catch(std::invalid_argument const &expected) From 054d597be569ddca3ad9b44e39c60e6973247d08 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Mon, 15 Jun 2026 14:25:37 -0700 Subject: [PATCH 404/986] Fixes an uninitialized warning in our strict-aliasing warning ... and ensures that we're still triggering the strict-aliasing warning w/ gcc --- src/thirdparty/tests/CMakeLists.txt | 3 ++- src/thirdparty/tests/compiler_flag_strict_aliasing.cpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/thirdparty/tests/CMakeLists.txt b/src/thirdparty/tests/CMakeLists.txt index 4dee51c1c0..fdf664de6c 100644 --- a/src/thirdparty/tests/CMakeLists.txt +++ b/src/thirdparty/tests/CMakeLists.txt @@ -318,9 +318,10 @@ set_target_properties(compiler_flag_unused_local_typedef_test PROPERTIES COMPILE_FLAGS "${ADDL_SMOKE_FLAGS} ${AXOM_DISABLE_UNUSED_LOCAL_TYPEDEF}") # the aliasing test requires optimization, set this up as custom compiler flag -# in case we need more flexibility for this flag (e.g. for MSVC) +# GNU requires a higher warning level to trigger strict-aliasing, before disabling it blt_append_custom_compiler_flag(FLAGS_VAR TMP_OPT_FLAG DEFAULT "-O2" + GNU "-O2 -Wstrict-aliasing=2" MSVC " " ) diff --git a/src/thirdparty/tests/compiler_flag_strict_aliasing.cpp b/src/thirdparty/tests/compiler_flag_strict_aliasing.cpp index 7c91961d43..8cfe47e8b5 100644 --- a/src/thirdparty/tests/compiler_flag_strict_aliasing.cpp +++ b/src/thirdparty/tests/compiler_flag_strict_aliasing.cpp @@ -31,7 +31,8 @@ struct Bar int main() { Foo foo = {1, nullptr}; - ((Bar*)(&foo))->i++; // violates strict aliasing + auto* bar = reinterpret_cast(&foo); + bar->i = foo.i + 1; // violates strict aliasing std::cout << " foo.i: " << foo.i << std::endl; return 0; From b5d32746183232155337911a4dadb89d6ea06c9a Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 15 Jun 2026 17:52:54 -0700 Subject: [PATCH 405/986] 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 406/986] 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 407/986] 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 408/986] 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 409/986] 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 410/986] 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 411/986] 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 bbcf352107f77f6c511fc7521859455cd06e0d14 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Sun, 14 Jun 2026 14:42:44 -0700 Subject: [PATCH 412/986] Python: Distinguish between build-, run-, and test- Python dependencies We have the following dependencies: * Runtime: Conduit, numpy and mpi4py * Build: nanobind (statically linked into the module) * Test: pytest, pluggy, iniconfig Users only need the runtime dependencies. Test dependencies are only needed when AXOM_ENABLE_PYTHON_TESTS is enabled. This commit updates our run_axom_with_python.sh wrapper to only add the runtime deps, while the testing deps are added to the ENVIRONMENT via a CTest property. --- src/cmake/AxomMacros.cmake | 32 ++++++++++- .../thirdparty/SetupAxomThirdParty.cmake | 57 +++++++++++++------ src/tools/CMakeLists.txt | 20 ++++++- src/tools/run_python_with_axom.sh.in | 14 ++++- 4 files changed, 98 insertions(+), 25 deletions(-) diff --git a/src/cmake/AxomMacros.cmake b/src/cmake/AxomMacros.cmake index 0dcd45095f..5a092650e3 100644 --- a/src/cmake/AxomMacros.cmake +++ b/src/cmake/AxomMacros.cmake @@ -603,6 +603,28 @@ macro(axom_configure_file _source _target) execute_process(COMMAND ${CMAKE_COMMAND} -E remove ${_tmp_target}) endmacro(axom_configure_file) +##------------------------------------------------------------------------------ +## axom_python_test_environment() +## +## Composes the ENVIRONMENT entry ("PYTHONPATH=::...") that provides +## the pytest paths (pytest and its dependencies) from their respective CMake cache variables. +## +## Note: runtime dependencies (e.g. axom, conduit, numpy) are expected to be preprended +## via the run_python_with_axom.sh script. +##------------------------------------------------------------------------------ +function(axom_python_test_environment output_var) + set(_paths "") + foreach(_var PY_PYTEST_DIR PY_PLUGGY_DIR PY_INICONFIG_DIR) + blt_list_append(TO _paths ELEMENTS "${${_var}}" IF ${_var}) + endforeach() + if(_paths) + list(JOIN _paths ":" _joined) + set(${output_var} "PYTHONPATH=${_joined}" PARENT_SCOPE) + else() + set(${output_var} "" PARENT_SCOPE) + endif() +endfunction() + ##------------------------------------------------------------------------------ ## axom_add_python_test(NAME [name] ## SOURCE [source] @@ -626,13 +648,19 @@ macro(axom_add_python_test) "${arg_OUTPUT_DIR}/${arg_SOURCE}" COPYONLY) # Run unit test with pytest ("python3 -m pytest"). - # Use convenience script that has - # pytest, pysidre, and conduit added to PYTHONPATH. + # The run_python_with_axom.sh wrapper provides the runtime environment + # and the testing dependencies are injected via the test's ENVIRONMENT property when provided. # "-p no:cacheprovider" disables caching. add_test (NAME ${arg_NAME} COMMAND ${PROJECT_BINARY_DIR}/bin/run_python_with_axom.sh -m pytest -s -p no:cacheprovider ${arg_OUTPUT_DIR}/${arg_SOURCE} ) + axom_python_test_environment(_py_test_env) + if(_py_test_env) + set_tests_properties(${arg_NAME} PROPERTIES ENVIRONMENT "${_py_test_env}") + endif() + unset(_py_test_env) + endmacro(axom_add_python_test) ##------------------------------------------------------------------------------ diff --git a/src/cmake/thirdparty/SetupAxomThirdParty.cmake b/src/cmake/thirdparty/SetupAxomThirdParty.cmake index 213a64c470..5f2e1a40a9 100644 --- a/src/cmake/thirdparty/SetupAxomThirdParty.cmake +++ b/src/cmake/thirdparty/SetupAxomThirdParty.cmake @@ -331,12 +331,22 @@ if(EXISTS ${Python_EXECUTABLE}) ) endif() - # Check if python environment potentially contains all - # required dependencies + # Check if the python environment contains the runtime dependencies for Axom's python + # conduit (Node interop) and numpy (ndarray returns). + # nanobind is statically linked at build time and is located separately above. execute_process( COMMAND "${CMAKE_COMMAND}" -E env - "${Python_EXECUTABLE}" -c "import nanobind, conduit, numpy, pytest" - RESULT_VARIABLE PY_ENV_IMPORT_CODE + "${Python_EXECUTABLE}" -c "import conduit, numpy" + RESULT_VARIABLE PY_RUNTIME_IMPORT_CODE + OUTPUT_QUIET + ERROR_QUIET + ) + + # Check if the python environment contains the pytest test harness, + execute_process( + COMMAND "${CMAKE_COMMAND}" -E env + "${Python_EXECUTABLE}" -c "import pytest" + RESULT_VARIABLE PY_PYTEST_IMPORT_CODE OUTPUT_QUIET ERROR_QUIET ) @@ -351,24 +361,35 @@ if(EXISTS ${Python_EXECUTABLE}) ) endif() -# If python environment does not contain required modules, check if -# library installation paths were provided instead. -if((NOT PY_ENV_IMPORT_CODE EQUAL 0) +# If the python environment does not contain the required runtime modules, +# check if library installation paths were provided instead. +if((NOT PY_RUNTIME_IMPORT_CODE EQUAL 0) + AND + nanobind_ROOT + AND + (NOT CONDUIT_PYTHON_MODULE_DIR OR NOT PY_NUMPY_DIR)) + message(FATAL_ERROR + "Axom's python extensions require conduit and numpy at runtime." + "\nThe python library installation paths can be specified with CMake variables: " + "CONDUIT_PYTHON_MODULE_DIR, PY_NUMPY_DIR") +endif() + +# The pytest harness (pytest plus its dependencies pluggy and iniconfig) is a +# test-only requirement; it is injected per-test via the ENVIRONMENT property +# (see axom_python_test_environment in AxomMacros.cmake) and is only required +# when Axom's python tests are enabled. +if(AXOM_ENABLE_PYTHON_TESTS + AND + (NOT PY_PYTEST_IMPORT_CODE EQUAL 0) AND nanobind_ROOT AND - (NOT PY_NANOBIND_DIR - OR NOT CONDUIT_PYTHON_MODULE_DIR - OR NOT PY_NUMPY_DIR - OR NOT PY_PYTEST_DIR - OR NOT PY_PLUGGY_DIR - OR NOT PY_INICONFIG_DIR)) + (NOT PY_PYTEST_DIR OR NOT PY_PLUGGY_DIR OR NOT PY_INICONFIG_DIR)) message(FATAL_ERROR - "Axom's python extensions require nanobind, numpy, pytest, and conduit." - "\nThe python library installation paths " - "(and pytest's dependencies pluggy and iniconfig) " - "can be specified with CMake variables: " - "PY_NANOBIND_DIR, CONDUIT_PYTHON_MODULE_DIR, PY_NUMPY_DIR, PY_PYTEST_DIR, PY_PLUGGY_DIR, PY_INICONFIG_DIR") + "Running Axom's python tests requires pytest (and its dependencies pluggy and iniconfig)." + "\nThe library installation paths can be specified with CMake variables: " + "PY_PYTEST_DIR, PY_PLUGGY_DIR, PY_INICONFIG_DIR." + "\nAlternatively, configure with AXOM_ENABLE_PYTHON_TESTS=OFF.") endif() # When Axom is configured with MPI, diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index 498ead9e39..e7698639a1 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -209,11 +209,27 @@ if(NANOBIND_FOUND) unset(_PYEXT_DIR) - # Add smoke test to check script + # Smoke tests for the script. + # The wrapper provides the runtime environment only: + # Axom extensions, conduit (Node interop), numpy (ndarray returns), plus mpi4py in MPI configurations. + # nanobind is a build-time dependency (statically linked into the extensions). + # pytest/pluggy/iniconfig are test-harness dependencies. if(AXOM_ENABLE_TESTS AND AXOM_ENABLE_SIDRE) axom_add_test( NAME run_python_with_axom_build - COMMAND ${PROJECT_BINARY_DIR}/bin/run_python_with_axom.sh -c "import pysidre, nanobind, conduit, numpy, pytest") + COMMAND ${PROJECT_BINARY_DIR}/bin/run_python_with_axom.sh -c "import pysidre, conduit, numpy") + + # The pytest harness is provided per-test via the ENVIRONMENT property + # verify that the wrapper + injected ENVIRONMENT combination resolves. + axom_add_test( + NAME run_python_with_axom_pytest_harness + COMMAND ${PROJECT_BINARY_DIR}/bin/run_python_with_axom.sh -c "import pytest") + axom_python_test_environment(_py_test_env) + if(_py_test_env) + set_tests_properties(run_python_with_axom_pytest_harness + PROPERTIES ENVIRONMENT "${_py_test_env}") + endif() + unset(_py_test_env) endif() #-------------------------------------------------------------------------- diff --git a/src/tools/run_python_with_axom.sh.in b/src/tools/run_python_with_axom.sh.in index f12ffa08a5..31670449f1 100755 --- a/src/tools/run_python_with_axom.sh.in +++ b/src/tools/run_python_with_axom.sh.in @@ -7,7 +7,15 @@ # SPDX-License-Identifier: (BSD-3-Clause) ##----------------------------------------------------------------------------- -## Convenience script that runs python interpreter with Axom extension(s) in -## the PYTHONPATH. +## Convenience script that runs the python interpreter with Axom's extension(s) +## and their runtime dependencies in the PYTHONPATH: +## - Axom's extension modules (e.g. pysidre) +## - conduit's python module (conduit::Node interop) +## - numpy (ndarray returns) +## - mpi4py (only populated in MPI-enabled configurations) +## +## Build-time (nanobind) and testing dependencies (pytest/pluggy/iniconfig) +## are intentionally NOT added here. They are expected to be added to the PYTHONPATH +## via the test's ENVIRONMENT property ##----------------------------------------------------------------------------- -env PYTHONPATH=@_PYEXT_DIR@:@CONDUIT_PYTHON_MODULE_DIR@:@PY_NANOBIND_DIR@:@PY_NUMPY_DIR@:@PY_PYTEST_DIR@:@PY_PLUGGY_DIR@:@PY_INICONFIG_DIR@:@PY_MPI4PY_DIR@:$PYTHONPATH @Python_EXECUTABLE@ "$@" +env PYTHONPATH=@_PYEXT_DIR@:@CONDUIT_PYTHON_MODULE_DIR@:@PY_NUMPY_DIR@:@PY_MPI4PY_DIR@:$PYTHONPATH @Python_EXECUTABLE@ "$@" From 56f8fe85c91de44fa24080c52a7db94a76d7706a Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Sun, 14 Jun 2026 14:45:38 -0700 Subject: [PATCH 413/986] Spack: Allow Axom to build against newer nanobind versions --- scripts/spack/packages/axom/package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/spack/packages/axom/package.py b/scripts/spack/packages/axom/package.py index f191f31cfe..229536925b 100644 --- a/scripts/spack/packages/axom/package.py +++ b/scripts/spack/packages/axom/package.py @@ -279,7 +279,7 @@ class Axom(CachedCMakePackage, CudaPackage, ROCmPackage): # Python with when("+python"): - depends_on("py-nanobind@2.7.0") + depends_on("py-nanobind@2.7.0:") depends_on("py-pytest") depends_on("py-numpy") depends_on("py-mpi4py", when="+mpi") From 39b5a7803d9245374c1978fc79199962a77f44c3 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Sun, 14 Jun 2026 14:50:16 -0700 Subject: [PATCH 414/986] Conduit's python is a private dependency for pysidre sidre's public interface no longer depends on whether nanobind was present. --- src/axom/sidre/CMakeLists.txt | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/axom/sidre/CMakeLists.txt b/src/axom/sidre/CMakeLists.txt index 22fbdb8f63..ebf689d416 100644 --- a/src/axom/sidre/CMakeLists.txt +++ b/src/axom/sidre/CMakeLists.txt @@ -119,10 +119,6 @@ if(AXOM_ENABLE_MPI) blt_list_append(TO sidre_depends ELEMENTS scr IF SCR_FOUND) endif() -if(NANOBIND_FOUND) - list(APPEND sidre_depends conduit::conduit_python) -endif() - axom_add_library(NAME sidre SOURCES ${sidre_sources} HEADERS ${sidre_headers} @@ -143,7 +139,9 @@ endif() if(NANOBIND_FOUND) nanobind_add_module(pysidre nanobind_sidre.cpp) - target_link_libraries(pysidre PRIVATE sidre) + # conduit::conduit_python provides conduit_python.hpp + # and is needed only by the binding translation unit, not by libsidre + target_link_libraries(pysidre PRIVATE sidre conduit::conduit_python) # Use HIP executable linker flags for the python module # (CMake treats modules separately from executables, From d5a07e5116511894a614ac1d0dfb6d99b0846eb9 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Sun, 14 Jun 2026 14:53:10 -0700 Subject: [PATCH 415/986] Adds Axom version to pysidre module --- src/axom/sidre/nanobind_sidre.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/axom/sidre/nanobind_sidre.cpp b/src/axom/sidre/nanobind_sidre.cpp index 581609b1fd..943c8984f3 100644 --- a/src/axom/sidre/nanobind_sidre.cpp +++ b/src/axom/sidre/nanobind_sidre.cpp @@ -10,6 +10,7 @@ #include #include +#include "axom/config.hpp" #include "axom/core/Types.hpp" #include "core/SidreTypes.hpp" #include "core/Buffer.hpp" @@ -244,6 +245,9 @@ NB_MODULE(pysidre, m_sidre) { m_sidre.doc() = "A python extension for Axom's Sidre component"; + // Module version mirrors the Axom release + m_sidre.attr("__version__") = AXOM_VERSION_FULL; + m_sidre.attr("InvalidIndex") = axom::InvalidIndex; m_sidre.attr("InvalidName") = axom::utilities::string::InvalidName; From 109c70bb837dee0c97630220b6835167466a0236 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Sun, 14 Jun 2026 15:10:22 -0700 Subject: [PATCH 416/986] Updates RELEASE-NOTES and improves CMake guard on python test --- RELEASE-NOTES.md | 1 + .../thirdparty/SetupAxomThirdParty.cmake | 7 +++--- src/tools/CMakeLists.txt | 22 ++++++++++--------- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 831f46a272..c7e1460c6d 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -49,6 +49,7 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ ### Changed - Updates CMake code check targets to only use checked in files (via `git ls-files`, when available) - Core: Optimization for axom::Array indirection -- since the stride is always 1, we can remove the runtime multiplication +- Python: Removes build and test dependencies from `run_python_with_axom.sh` wrapper script ### Fixed - Primal: Fixes signs of `compute_moments` to match orientation convention in `primal::evaluate_area_integral` diff --git a/src/cmake/thirdparty/SetupAxomThirdParty.cmake b/src/cmake/thirdparty/SetupAxomThirdParty.cmake index 5f2e1a40a9..0a53f7cd16 100644 --- a/src/cmake/thirdparty/SetupAxomThirdParty.cmake +++ b/src/cmake/thirdparty/SetupAxomThirdParty.cmake @@ -370,13 +370,12 @@ if((NOT PY_RUNTIME_IMPORT_CODE EQUAL 0) (NOT CONDUIT_PYTHON_MODULE_DIR OR NOT PY_NUMPY_DIR)) message(FATAL_ERROR "Axom's python extensions require conduit and numpy at runtime." - "\nThe python library installation paths can be specified with CMake variables: " + "\nThe python library installation paths can be specified with CMake variables: " "CONDUIT_PYTHON_MODULE_DIR, PY_NUMPY_DIR") endif() -# The pytest harness (pytest plus its dependencies pluggy and iniconfig) is a -# test-only requirement; it is injected per-test via the ENVIRONMENT property -# (see axom_python_test_environment in AxomMacros.cmake) and is only required +# The pytest harness (pytest and its dependencies) is a test-only requirement. +# It is injected per-test via the ENVIRONMENT property and is only required # when Axom's python tests are enabled. if(AXOM_ENABLE_PYTHON_TESTS AND diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index e7698639a1..a7be4a3af1 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -219,17 +219,19 @@ if(NANOBIND_FOUND) NAME run_python_with_axom_build COMMAND ${PROJECT_BINARY_DIR}/bin/run_python_with_axom.sh -c "import pysidre, conduit, numpy") - # The pytest harness is provided per-test via the ENVIRONMENT property - # verify that the wrapper + injected ENVIRONMENT combination resolves. - axom_add_test( - NAME run_python_with_axom_pytest_harness - COMMAND ${PROJECT_BINARY_DIR}/bin/run_python_with_axom.sh -c "import pytest") - axom_python_test_environment(_py_test_env) - if(_py_test_env) - set_tests_properties(run_python_with_axom_pytest_harness - PROPERTIES ENVIRONMENT "${_py_test_env}") + if(AXOM_ENABLE_PYTHON_TESTS) + # The pytest harness is provided per-test via the ENVIRONMENT property; + # verify that the wrapper + injected ENVIRONMENT combination resolves. + axom_add_test( + NAME run_python_with_axom_pytest_harness + COMMAND ${PROJECT_BINARY_DIR}/bin/run_python_with_axom.sh -c "import pytest") + axom_python_test_environment(_py_test_env) + if(_py_test_env) + set_tests_properties(run_python_with_axom_pytest_harness + PROPERTIES ENVIRONMENT "${_py_test_env}") + endif() + unset(_py_test_env) endif() - unset(_py_test_env) endif() #-------------------------------------------------------------------------- From 0911e5b88e7c5fefc472a9805f914dab6320e969 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Mon, 15 Jun 2026 12:13:00 -0700 Subject: [PATCH 417/986] Manual formatting, per PR suggestion --- .../thirdparty/SetupAxomThirdParty.cmake | 31 ++++++------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/src/cmake/thirdparty/SetupAxomThirdParty.cmake b/src/cmake/thirdparty/SetupAxomThirdParty.cmake index 0a53f7cd16..13f5f091ad 100644 --- a/src/cmake/thirdparty/SetupAxomThirdParty.cmake +++ b/src/cmake/thirdparty/SetupAxomThirdParty.cmake @@ -364,10 +364,8 @@ endif() # If the python environment does not contain the required runtime modules, # check if library installation paths were provided instead. if((NOT PY_RUNTIME_IMPORT_CODE EQUAL 0) - AND - nanobind_ROOT - AND - (NOT CONDUIT_PYTHON_MODULE_DIR OR NOT PY_NUMPY_DIR)) + AND nanobind_ROOT + AND (NOT CONDUIT_PYTHON_MODULE_DIR OR NOT PY_NUMPY_DIR)) message(FATAL_ERROR "Axom's python extensions require conduit and numpy at runtime." "\nThe python library installation paths can be specified with CMake variables: " @@ -378,12 +376,9 @@ endif() # It is injected per-test via the ENVIRONMENT property and is only required # when Axom's python tests are enabled. if(AXOM_ENABLE_PYTHON_TESTS - AND - (NOT PY_PYTEST_IMPORT_CODE EQUAL 0) - AND - nanobind_ROOT - AND - (NOT PY_PYTEST_DIR OR NOT PY_PLUGGY_DIR OR NOT PY_INICONFIG_DIR)) + AND (NOT PY_PYTEST_IMPORT_CODE EQUAL 0) + AND nanobind_ROOT + AND (NOT PY_PYTEST_DIR OR NOT PY_PLUGGY_DIR OR NOT PY_INICONFIG_DIR)) message(FATAL_ERROR "Running Axom's python tests requires pytest (and its dependencies pluggy and iniconfig)." "\nThe library installation paths can be specified with CMake variables: " @@ -395,12 +390,9 @@ endif() # if python environment does not contain required mpi4py module, # check if mpi4py library installation path was provided instead. if(AXOM_ENABLE_MPI - AND - (NOT MPI4PY_ENV_IMPORT_CODE EQUAL 0) - AND - nanobind_ROOT - AND - (NOT PY_MPI4PY_DIR)) + AND (NOT MPI4PY_ENV_IMPORT_CODE EQUAL 0) + AND nanobind_ROOT + AND (NOT PY_MPI4PY_DIR)) message(FATAL_ERROR "Axom's python extension requires mpi4py when Axom library is configured with MPI." "\nThe mpi4py library installation paths " @@ -415,11 +407,8 @@ if(nanobind_ROOT AND NOT AXOM_ENABLE_CUDA AND NOT AXOM_ENABLE_ASAN AND NOT AXOM_ENABLE_UBSAN - AND - ((NOT "$ENV{SYS_TYPE}" STREQUAL "blueos_3_ppc64le_ib_p9") - OR - ("$ENV{SYS_TYPE}" STREQUAL "blueos_3_ppc64le_ib_p9" - AND NOT "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang"))) + AND ((NOT "$ENV{SYS_TYPE}" STREQUAL "blueos_3_ppc64le_ib_p9") + OR ("$ENV{SYS_TYPE}" STREQUAL "blueos_3_ppc64le_ib_p9" AND NOT "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang"))) axom_assert_is_directory(DIR_VARIABLE nanobind_ROOT) find_package(nanobind CONFIG REQUIRED) From 058acbebb637486c35959837d86f326dd2eb365a Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 16 Jun 2026 09:47:09 -0700 Subject: [PATCH 418/986] 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 419/986] 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 420/986] 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 421/986] 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 422/986] 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 423/986] 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 424/986] 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 425/986] 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 d8d8816486d67967182894373cd4e28a70adf9eb Mon Sep 17 00:00:00 2001 From: Chris White Date: Tue, 16 Jun 2026 14:14:16 -0700 Subject: [PATCH 426/986] add example on how to do a user defined struct variant --- src/axom/inlet/docs/sphinx/advanced_types.rst | 38 +++++ src/axom/inlet/examples/CMakeLists.txt | 4 + .../inlet/examples/user_defined_variant.cpp | 144 ++++++++++++++++++ 3 files changed, 186 insertions(+) create mode 100644 src/axom/inlet/examples/user_defined_variant.cpp diff --git a/src/axom/inlet/docs/sphinx/advanced_types.rst b/src/axom/inlet/docs/sphinx/advanced_types.rst index 14927bf9b6..7e8adfbae2 100644 --- a/src/axom/inlet/docs/sphinx/advanced_types.rst +++ b/src/axom/inlet/docs/sphinx/advanced_types.rst @@ -88,6 +88,44 @@ If a ``Car`` object as defined above is located at the path "car" within the inp Car car = inlet["car"].get(); +******************************** +Individual Variant Structs +******************************** + +When a single input table may describe one of several user-defined struct types, +use a normal ``addStruct`` schema with a discriminator field and provide a +``FromInlet`` specialization that constructs the selected alternative. This is +useful for inputs that are variant-valued but are not arrays or dictionaries. + +For example, a single ``shape`` table can use a ``kind`` field to select the +concrete shape: + +.. literalinclude:: ../../examples/user_defined_variant.cpp + :start-after: _inlet_user_defined_variant_input_start + :end-before: _inlet_user_defined_variant_input_end + :language: lua + +The C++ type can store the concrete result in a ``std::variant`` while keeping +the Inlet-facing type as a user-defined struct: + +.. literalinclude:: ../../examples/user_defined_variant.cpp + :start-after: _inlet_user_defined_variant_start + :end-before: _inlet_user_defined_variant_end + :language: C++ + +Define the schema with ``addStruct`` and retrieve the result as the wrapper +user-defined type: + +.. literalinclude:: ../../examples/user_defined_variant.cpp + :start-after: _inlet_user_defined_variant_schema_usage_start + :end-before: _inlet_user_defined_variant_schema_usage_end + :language: C++ + +.. literalinclude:: ../../examples/user_defined_variant.cpp + :start-after: _inlet_user_defined_variant_access_start + :end-before: _inlet_user_defined_variant_access_end + :language: C++ + ********************************** Arrays and Dictionaries of Structs ********************************** diff --git a/src/axom/inlet/examples/CMakeLists.txt b/src/axom/inlet/examples/CMakeLists.txt index 38c7d9026e..593ce27bf1 100644 --- a/src/axom/inlet/examples/CMakeLists.txt +++ b/src/axom/inlet/examples/CMakeLists.txt @@ -23,6 +23,7 @@ blt_list_append( fields.cpp homogeneous_collections.cpp lua_library.cpp + user_defined_variant.cpp variant_struct_collections.cpp variant_collections.cpp containers.cpp @@ -89,6 +90,9 @@ if (SOL_FOUND) axom_add_test( NAME inlet_user_defined_type_ex COMMAND inlet_user_defined_type_ex --file ${CMAKE_CURRENT_LIST_DIR}/example1.lua) + axom_add_test( NAME inlet_user_defined_variant_ex + COMMAND inlet_user_defined_variant_ex ) + axom_add_test( NAME inlet_verification_ex COMMAND inlet_verification_ex ) diff --git a/src/axom/inlet/examples/user_defined_variant.cpp b/src/axom/inlet/examples/user_defined_variant.cpp new file mode 100644 index 0000000000..9864e64c1c --- /dev/null +++ b/src/axom/inlet/examples/user_defined_variant.cpp @@ -0,0 +1,144 @@ +// 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/inlet.hpp" +#include "axom/slic/core/SimpleLogger.hpp" +#include "axom/fmt.hpp" + +#include +#include +#include + +namespace inlet = axom::inlet; + +// _inlet_user_defined_variant_start +struct Circle +{ + double radius; +}; + +struct Box +{ + double width; + double height; +}; + +struct Shape +{ + std::variant value; +}; + +template <> +struct FromInlet +{ + Circle operator()(const inlet::Container& input_data) { return {input_data["radius"]}; } +}; + +template <> +struct FromInlet +{ + Box operator()(const inlet::Container& input_data) + { + return {input_data["width"], input_data["height"]}; + } +}; + +template <> +struct FromInlet +{ + Shape operator()(const inlet::Container& input_data) + { + const std::string kind = input_data["kind"]; + if(kind == "circle") + { + return {FromInlet {}(input_data)}; + } + + return {FromInlet {}(input_data)}; + } +}; + +void defineShapeSchema(inlet::Container& shape) +{ + shape.addString("kind", "Shape variant discriminator").required().validValues({"circle", "box"}); + shape.addDouble("radius", "Circle radius").required(false); + shape.addDouble("width", "Box width").required(false); + shape.addDouble("height", "Box height").required(false); + + shape.registerVerifier([](const inlet::Container& input_data) { + if(!input_data.isUserProvided("kind")) + { + return false; + } + + const std::string kind = input_data["kind"]; + if(kind == "circle") + { + return input_data.isUserProvided("radius") && !input_data.isUserProvided("width") && + !input_data.isUserProvided("height"); + } + + return input_data.isUserProvided("width") && input_data.isUserProvided("height") && + !input_data.isUserProvided("radius"); + }); +} +// _inlet_user_defined_variant_end + +const std::string input = R"( + -- _inlet_user_defined_variant_input_start + shape = { + kind = "box", + width = 3.0, + height = 4.0 + } + -- _inlet_user_defined_variant_input_end +)"; + +void printShape(const Shape& shape) +{ + std::visit( + [](const auto& concrete_shape) { + using ShapeType = std::decay_t; + if constexpr(std::is_same_v) + { + SLIC_INFO(axom::fmt::format("circle radius = {}", concrete_shape.radius)); + } + else + { + SLIC_INFO(axom::fmt::format("box width = {}, height = {}", + concrete_shape.width, + concrete_shape.height)); + } + }, + shape.value); +} + +int main() +{ + axom::slic::SimpleLogger logger; + + auto lr = std::make_unique(); + lr->parseString(input); + inlet::Inlet inlet(std::move(lr)); + + // _inlet_user_defined_variant_schema_usage_start + auto& shape_schema = inlet.addStruct("shape", "A single user-defined variant"); + defineShapeSchema(shape_schema); + // _inlet_user_defined_variant_schema_usage_end + + if(!inlet.verify()) + { + SLIC_ERROR("Inlet failed to verify against provided schema"); + } + + // _inlet_user_defined_variant_access_start + const Shape shape = inlet["shape"].get(); + // _inlet_user_defined_variant_access_end + + printShape(shape); + + return 0; +} From 15b2881e7243c7e4070177ab5c3af62885dfa609 Mon Sep 17 00:00:00 2001 From: Chris White Date: Tue, 16 Jun 2026 15:02:31 -0700 Subject: [PATCH 427/986] use more generalized term --- src/axom/inlet/docs/sphinx/advanced_types.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/axom/inlet/docs/sphinx/advanced_types.rst b/src/axom/inlet/docs/sphinx/advanced_types.rst index 7e8adfbae2..5059917511 100644 --- a/src/axom/inlet/docs/sphinx/advanced_types.rst +++ b/src/axom/inlet/docs/sphinx/advanced_types.rst @@ -92,7 +92,7 @@ If a ``Car`` object as defined above is located at the path "car" within the inp Individual Variant Structs ******************************** -When a single input table may describe one of several user-defined struct types, +When a single input entry may describe one of several user-defined struct types, use a normal ``addStruct`` schema with a discriminator field and provide a ``FromInlet`` specialization that constructs the selected alternative. This is useful for inputs that are variant-valued but are not arrays or dictionaries. From 6ed7430bebf8f05277ff174e99e4c7adf60f61ba Mon Sep 17 00:00:00 2001 From: Chris White Date: Tue, 16 Jun 2026 15:46:26 -0700 Subject: [PATCH 428/986] simplify and explain example more --- .../inlet/examples/user_defined_variant.cpp | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/axom/inlet/examples/user_defined_variant.cpp b/src/axom/inlet/examples/user_defined_variant.cpp index 9864e64c1c..8fb61f2541 100644 --- a/src/axom/inlet/examples/user_defined_variant.cpp +++ b/src/axom/inlet/examples/user_defined_variant.cpp @@ -26,10 +26,7 @@ struct Box double height; }; -struct Shape -{ - std::variant value; -}; +using Shape = std::variant; template <> struct FromInlet @@ -49,15 +46,17 @@ struct FromInlet template <> struct FromInlet { - Shape operator()(const inlet::Container& input_data) + // Shape is a std::variant, so it cannot be read through Container::get(). + // Use the public Proxy returned by inlet["shape"] to access its fields instead. + Shape operator()(const inlet::Proxy& input_data) { const std::string kind = input_data["kind"]; if(kind == "circle") { - return {FromInlet {}(input_data)}; + return Circle {input_data["radius"]}; } - return {FromInlet {}(input_data)}; + return Box {input_data["width"], input_data["height"]}; } }; @@ -113,29 +112,34 @@ void printShape(const Shape& shape) concrete_shape.height)); } }, - shape.value); + shape); } int main() { + // Initialize Axom's logger axom::slic::SimpleLogger logger; + // Create Inlet object with the Lua Reader and parse the input file snippet auto lr = std::make_unique(); lr->parseString(input); inlet::Inlet inlet(std::move(lr)); + // Define the input file schema // _inlet_user_defined_variant_schema_usage_start auto& shape_schema = inlet.addStruct("shape", "A single user-defined variant"); defineShapeSchema(shape_schema); // _inlet_user_defined_variant_schema_usage_end + // Verify input file validates against the schema if(!inlet.verify()) { SLIC_ERROR("Inlet failed to verify against provided schema"); } + // Create a shape object from inlet container // _inlet_user_defined_variant_access_start - const Shape shape = inlet["shape"].get(); + const Shape shape = FromInlet {}(inlet["shape"]); // _inlet_user_defined_variant_access_end printShape(shape); From 93caf5e3efa8fb8bc01dfcbacfcc706075b7e257 Mon Sep 17 00:00:00 2001 From: Chris White Date: Tue, 16 Jun 2026 16:26:50 -0700 Subject: [PATCH 429/986] add errors to check even though the validator probably caught unknown discrimitators --- src/axom/inlet/examples/user_defined_variant.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/axom/inlet/examples/user_defined_variant.cpp b/src/axom/inlet/examples/user_defined_variant.cpp index 8fb61f2541..b213967856 100644 --- a/src/axom/inlet/examples/user_defined_variant.cpp +++ b/src/axom/inlet/examples/user_defined_variant.cpp @@ -55,8 +55,13 @@ struct FromInlet { return Circle {input_data["radius"]}; } + else if(kind == "box") + { + return Box {input_data["width"], input_data["height"]}; + } - return Box {input_data["width"], input_data["height"]}; + SLIC_ERROR(axom::fmt::format("Unknown shape discriminator '{}'", kind)); + return Box {0.0, 0.0}; } }; @@ -79,9 +84,13 @@ void defineShapeSchema(inlet::Container& shape) return input_data.isUserProvided("radius") && !input_data.isUserProvided("width") && !input_data.isUserProvided("height"); } + else if(kind == "box") + { + return input_data.isUserProvided("width") && input_data.isUserProvided("height") && + !input_data.isUserProvided("radius"); + } - return input_data.isUserProvided("width") && input_data.isUserProvided("height") && - !input_data.isUserProvided("radius"); + return false; }); } // _inlet_user_defined_variant_end From 9ac5607a1baf590292cacb9085d984e08c7963a9 Mon Sep 17 00:00:00 2001 From: Chris White Date: Tue, 16 Jun 2026 16:37:04 -0700 Subject: [PATCH 430/986] remove using --- src/axom/inlet/examples/user_defined_variant.cpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/axom/inlet/examples/user_defined_variant.cpp b/src/axom/inlet/examples/user_defined_variant.cpp index b213967856..5fcc46fa6d 100644 --- a/src/axom/inlet/examples/user_defined_variant.cpp +++ b/src/axom/inlet/examples/user_defined_variant.cpp @@ -12,8 +12,6 @@ #include #include -namespace inlet = axom::inlet; - // _inlet_user_defined_variant_start struct Circle { @@ -31,13 +29,13 @@ using Shape = std::variant; template <> struct FromInlet { - Circle operator()(const inlet::Container& input_data) { return {input_data["radius"]}; } + Circle operator()(const axom::inlet::Container& input_data) { return {input_data["radius"]}; } }; template <> struct FromInlet { - Box operator()(const inlet::Container& input_data) + Box operator()(const axom::inlet::Container& input_data) { return {input_data["width"], input_data["height"]}; } @@ -48,7 +46,7 @@ struct FromInlet { // Shape is a std::variant, so it cannot be read through Container::get(). // Use the public Proxy returned by inlet["shape"] to access its fields instead. - Shape operator()(const inlet::Proxy& input_data) + Shape operator()(const axom::inlet::Proxy& input_data) { const std::string kind = input_data["kind"]; if(kind == "circle") @@ -65,14 +63,14 @@ struct FromInlet } }; -void defineShapeSchema(inlet::Container& shape) +void defineShapeSchema(axom::inlet::Container& shape) { shape.addString("kind", "Shape variant discriminator").required().validValues({"circle", "box"}); shape.addDouble("radius", "Circle radius").required(false); shape.addDouble("width", "Box width").required(false); shape.addDouble("height", "Box height").required(false); - shape.registerVerifier([](const inlet::Container& input_data) { + shape.registerVerifier([](const axom::inlet::Container& input_data) { if(!input_data.isUserProvided("kind")) { return false; @@ -130,9 +128,9 @@ int main() axom::slic::SimpleLogger logger; // Create Inlet object with the Lua Reader and parse the input file snippet - auto lr = std::make_unique(); + auto lr = std::make_unique(); lr->parseString(input); - inlet::Inlet inlet(std::move(lr)); + axom::inlet::Inlet inlet(std::move(lr)); // Define the input file schema // _inlet_user_defined_variant_schema_usage_start From 8e5ab48c5d349ae87ab4f0d6b0688a2344324487 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 16 Jun 2026 16:43:18 -0700 Subject: [PATCH 431/986] Fixed typo --- src/axom/bump/MergeMeshes.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/axom/bump/MergeMeshes.hpp b/src/axom/bump/MergeMeshes.hpp index 98b3c2f7f4..c24dec2aff 100644 --- a/src/axom/bump/MergeMeshes.hpp +++ b/src/axom/bump/MergeMeshes.hpp @@ -1684,7 +1684,7 @@ class MergeMeshesAndMatsets : public MergeMeshes /*! * \brief Get the material information we'll need to merge materials. * - * \param inputes The inputs to be merged. + * \param inputs The inputs to be merged. * \param[out] mi The material information. */ void getMaterialInfo(const std::vector &inputs, MaterialInfo &mi) const From 61255d6df1580b84929d755376ab005e8c0aaa7f Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 10 Jun 2026 21:51:12 +0000 Subject: [PATCH 432/986] Fix FlatMap copy assignment -- need to compare addresses, not values Adds typed tests covering assignment over a non-empty target, source preservation, and self-assignment. --- src/axom/core/FlatMap.hpp | 2 +- src/axom/core/tests/core_flatmap.hpp | 39 ++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/axom/core/FlatMap.hpp b/src/axom/core/FlatMap.hpp index 9e6d8d5e98..13f83dfdda 100644 --- a/src/axom/core/FlatMap.hpp +++ b/src/axom/core/FlatMap.hpp @@ -179,7 +179,7 @@ class FlatMap : detail::flat_map::SequentialLookupPolicy::value, "Cannot copy an axom::FlatMap when value type is not " "copy-constructible."); - if(*this != other) + if(this != &other) { FlatMap new_map(other); swap(new_map); diff --git a/src/axom/core/tests/core_flatmap.hpp b/src/axom/core/tests/core_flatmap.hpp index 705b2eb073..b032c9887d 100644 --- a/src/axom/core/tests/core_flatmap.hpp +++ b/src/axom/core/tests/core_flatmap.hpp @@ -481,6 +481,45 @@ AXOM_TYPED_TEST(core_flatmap, init_and_copy) } } +AXOM_TYPED_TEST(core_flatmap, copy_assign) +{ + using MapType = typename TestFixture::MapType; + MapType test_map; + const int NUM_ELEMS = 40; + + for(int i = 0; i < NUM_ELEMS; i++) + { + test_map[this->getKey(i)] = this->getValue(i + 10.0); + } + + // Copy-assign over a non-empty map with different contents should replace prior contents + MapType copied_map; + copied_map[this->getKey(NUM_ELEMS + 5)] = this->getValue(0.0); + copied_map = test_map; + + EXPECT_EQ(copied_map.size(), NUM_ELEMS); + EXPECT_EQ(copied_map.find(this->getKey(NUM_ELEMS + 5)), copied_map.end()); + for(int i = 0; i < NUM_ELEMS; i++) + { + auto it = copied_map.find(this->getKey(i)); + ASSERT_NE(it, copied_map.end()); + EXPECT_EQ(it->second, this->getValue(i + 10.0)); + } + + // The source should be unchanged + EXPECT_EQ(test_map.size(), NUM_ELEMS); + + // Self-assignment is a no-op + copied_map = static_cast(copied_map); + EXPECT_EQ(copied_map.size(), NUM_ELEMS); + for(int i = 0; i < NUM_ELEMS; i++) + { + auto it = copied_map.find(this->getKey(i)); + ASSERT_NE(it, copied_map.end()); + EXPECT_EQ(it->second, this->getValue(i + 10.0)); + } +} + AXOM_TYPED_TEST(core_flatmap, insert_until_rehash) { using MapType = typename TestFixture::MapType; From 2baf6a5b6dd5c0d997594428edacbc5852592b0a Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 10 Jun 2026 21:52:27 +0000 Subject: [PATCH 433/986] Remove FlatMap's const operator[], which inserts for missing keys Removing it cannot break callers since this would not have compiled. Const callers should use find()/at()/count()/contains(). at() throws std::out_of_range on a missing key. --- src/axom/core/FlatMap.hpp | 9 ------- src/axom/core/tests/core_flatmap.hpp | 35 ++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/src/axom/core/FlatMap.hpp b/src/axom/core/FlatMap.hpp index 13f83dfdda..0a7b1209c4 100644 --- a/src/axom/core/FlatMap.hpp +++ b/src/axom/core/FlatMap.hpp @@ -332,7 +332,6 @@ class FlatMap : detail::flat_map::SequentialLookupPolicy::value, @@ -340,14 +339,6 @@ class FlatMap : detail::flat_map::SequentialLookupPolicytry_emplace(key).first->second; } - const ValueType& operator[](const KeyType& key) const - { - static_assert(std::is_default_constructible::value, - "Cannot use axom::FlatMap::operator[] when value type is not " - "default-constructible."); - return this->try_emplace(key).first->second; - } - /// @} /*! * \brief Return the number of entries matching a given key. diff --git a/src/axom/core/tests/core_flatmap.hpp b/src/axom/core/tests/core_flatmap.hpp index b032c9887d..89c6616fab 100644 --- a/src/axom/core/tests/core_flatmap.hpp +++ b/src/axom/core/tests/core_flatmap.hpp @@ -520,6 +520,41 @@ AXOM_TYPED_TEST(core_flatmap, copy_assign) } } +AXOM_TYPED_TEST(core_flatmap, const_lookup) +{ + using MapType = typename TestFixture::MapType; + MapType test_map; + const int NUM_ELEMS = 20; + + for(int i = 0; i < NUM_ELEMS; i++) + { + test_map[this->getKey(i)] = this->getValue(i + 10.0); + } + + // Read-only lookups must be through const reference (matching std::unordered_map) + // operator[] is intentionally non-const since it inserts on a missing key + const MapType& const_map = test_map; + EXPECT_EQ(const_map.size(), NUM_ELEMS); + for(int i = 0; i < NUM_ELEMS; i++) + { + auto key = this->getKey(i); + auto value = this->getValue(i + 10.0); + + auto it = const_map.find(key); + ASSERT_NE(it, const_map.end()); + EXPECT_EQ(it->second, value); + EXPECT_EQ(const_map.at(key), value); + EXPECT_EQ(const_map.count(key), 1); + EXPECT_TRUE(const_map.contains(key)); + } + + auto missing = this->getKey(NUM_ELEMS + 5); + EXPECT_EQ(const_map.find(missing), const_map.end()); + EXPECT_EQ(const_map.count(missing), 0); + EXPECT_FALSE(const_map.contains(missing)); + EXPECT_THROW(const_map.at(missing), std::out_of_range); +} + AXOM_TYPED_TEST(core_flatmap, insert_until_rehash) { using MapType = typename TestFixture::MapType; From d1cb266754c3afdfd2ba7d748b37139330b75d09 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 10 Jun 2026 21:54:17 +0000 Subject: [PATCH 434/986] DeviceHash: hash in 64 bits regardless of IndexType width DeviceHashHelper returned axom::IndexType and integer keys were converted before the 64-bit mixer ran. With AXOM_USE_64BIT_INDEXTYPE=OFF every key wider than 32 bits is truncated first, so keys equal mod 2^32 produce identical final hashes. This was happening in the Morton codes in spin's SparseOctreeLevel and in numerics/quadrature. --- src/axom/core/DeviceHash.hpp | 36 +++++++++++++++--------- src/axom/core/tests/core_device_hash.hpp | 31 ++++++++++++++++++++ 2 files changed, 53 insertions(+), 14 deletions(-) diff --git a/src/axom/core/DeviceHash.hpp b/src/axom/core/DeviceHash.hpp index 0c934fbadc..cbd6d5e3e6 100644 --- a/src/axom/core/DeviceHash.hpp +++ b/src/axom/core/DeviceHash.hpp @@ -11,6 +11,7 @@ #include "axom/core/Macros.hpp" #include "axom/core/Types.hpp" +#include #include namespace axom @@ -25,8 +26,11 @@ template struct DeviceHashHelper::value>> { using argument_type = T; - using result_type = axom::IndexType; - AXOM_HOST_DEVICE axom::IndexType operator()(T value) const { return value; } + using result_type = std::uint64_t; + AXOM_HOST_DEVICE std::uint64_t operator()(T value) const + { + return static_cast(value); + } }; /// \brief Specialization for floating-point types @@ -34,15 +38,15 @@ template struct DeviceHashHelper::value>> { using argument_type = T; - using result_type = axom::IndexType; - AXOM_HOST_DEVICE axom::IndexType operator()(T value) const + using result_type = std::uint64_t; + AXOM_HOST_DEVICE std::uint64_t operator()(T value) const { // Special case: -0.0 and 0.0 compare equal but have different byte representations. if(value == T {0.}) { return 0; } - return value; + return static_cast(static_cast(value)); } }; @@ -51,10 +55,10 @@ template struct DeviceHashHelper::value>> { using argument_type = T; - using result_type = axom::IndexType; - AXOM_HOST_DEVICE axom::IndexType operator()(T value) const + using result_type = std::uint64_t; + AXOM_HOST_DEVICE std::uint64_t operator()(T value) const { - return static_cast(value); + return static_cast(value); } }; @@ -63,10 +67,10 @@ template struct DeviceHashHelper { using argument_type = T*; - using result_type = axom::IndexType; - AXOM_HOST_DEVICE axom::IndexType operator()(T* ptr) const + using result_type = std::uint64_t; + AXOM_HOST_DEVICE std::uint64_t operator()(T* ptr) const { - return static_cast(reinterpret_cast(ptr)); + return static_cast(reinterpret_cast(ptr)); } }; @@ -75,10 +79,10 @@ template struct DeviceHashHelper { using argument_type = T; - using result_type = axom::IndexType; - axom::IndexType operator()(const T& object) const + using result_type = std::uint64_t; + std::uint64_t operator()(const T& object) const { - return static_cast(std::hash {}(object)); + return static_cast(std::hash {}(object)); } }; @@ -89,6 +93,10 @@ struct DeviceHashHelper * * \brief Implements a host/device-callable hash function for supported types, * and passes through to std::hash otherwise. + * + * The result type is always std::uint64_t, independent of the configured axom::IndexType width. + * Hashes feed bit mixers and bucket selection, where truncating wide keys (e.g. 64-bit Morton codes) + * to a 32-bit IndexType before mixing would make keys equal mod 2^32 collide. */ template struct DeviceHash : public detail::DeviceHashHelper diff --git a/src/axom/core/tests/core_device_hash.hpp b/src/axom/core/tests/core_device_hash.hpp index 8ccee915d9..d19ac69386 100644 --- a/src/axom/core/tests/core_device_hash.hpp +++ b/src/axom/core/tests/core_device_hash.hpp @@ -259,3 +259,34 @@ AXOM_TYPED_TEST(core_device_hash, hash_user_defined) } } } + +TEST(core_device_hash, hash_width_decoupled_from_indextype) +{ + // The hash result must be 64 bits wide regardless of the configured + // axom::IndexType. When the result type was IndexType, builds with + // AXOM_USE_64BIT_INDEXTYPE=OFF truncated integer keys to 32 bits before + // the FlatMap bit mixer ran, so keys equal mod 2^32 (e.g. deep Morton codes) + // produced identical hashes. The type assertions catch the coupling in every + // build configuration; the value checks fail in the truncating configuration itself. + static_assert(std::is_same::result_type, std::uint64_t>::value, + "integral hash result must be std::uint64_t"); + static_assert(std::is_same::result_type, std::uint64_t>::value, + "integral hash result must be std::uint64_t"); + static_assert(std::is_same::result_type, std::uint64_t>::value, + "floating-point hash result must be std::uint64_t"); + static_assert(std::is_same::result_type, std::uint64_t>::value, + "pointer hash result must be std::uint64_t"); + static_assert(std::is_same::result_type, std::uint64_t>::value, + "catch-all (std::hash) result must be std::uint64_t"); + static_assert( + std::is_same {}(std::uint64_t {})), std::uint64_t>::value, + "integral hash operator() must return std::uint64_t"); + + axom::DeviceHash device_hasher; + const std::uint64_t base = 1; + const std::uint64_t plus_2_32 = base + (std::uint64_t {1} << 32); + const std::uint64_t plus_2_33 = base + (std::uint64_t {1} << 33); + EXPECT_NE(device_hasher(base), device_hasher(plus_2_32)); + EXPECT_NE(device_hasher(base), device_hasher(plus_2_33)); + EXPECT_NE(device_hasher(plus_2_32), device_hasher(plus_2_33)); +} From 51f9a863c2a063d54db4d77918b94769ed5b344c Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 10 Jun 2026 22:00:30 +0000 Subject: [PATCH 435/986] DeviceHash: hash floating-point keys by bit pattern, not value The floating-point specialization returned the key converted to an integer. Every key sharing an integer part therefore collided -- e.g. all numbers between -1 and 1 converted to the integer 0, so a FlatMap keyed on fractional floats degenerated into one probe chain with O(size) inserts and finds --- src/axom/core/DeviceHash.hpp | 27 +++++++++++++++-- src/axom/core/tests/core_device_hash.hpp | 38 +++++++++++++++++++++++- src/axom/core/tests/core_flatmap.hpp | 27 +++++++++++++++++ 3 files changed, 88 insertions(+), 4 deletions(-) diff --git a/src/axom/core/DeviceHash.hpp b/src/axom/core/DeviceHash.hpp index cbd6d5e3e6..d1ac31ced2 100644 --- a/src/axom/core/DeviceHash.hpp +++ b/src/axom/core/DeviceHash.hpp @@ -12,6 +12,7 @@ #include "axom/core/Types.hpp" #include +#include #include namespace axom @@ -41,12 +42,32 @@ struct DeviceHashHelper::value>> using result_type = std::uint64_t; AXOM_HOST_DEVICE std::uint64_t operator()(T value) const { - // Special case: -0.0 and 0.0 compare equal but have different byte representations. + // -0.0 and 0.0 compare equal but have different bit patterns; normalize so both hash identically if(value == T {0.}) { - return 0; + value = T {0.}; } - return static_cast(static_cast(value)); + + // Hash the bit pattern, not the converted value. + // A float-to-integer value conversion collapses every key sharing an integer part, + // e.g. all numbers between -1 and 1 converts to integer 0 + + // NUM_WORDS is 1 for float or double, possibly 2 for long double + constexpr std::size_t NUM_WORDS = (sizeof(T) + sizeof(std::uint64_t) - 1) / sizeof(std::uint64_t); + // zero out words since we might only copy 4 bytes in for floats + std::uint64_t words[NUM_WORDS] = {0}; + memcpy(words, &value, sizeof(T)); + + std::uint64_t result = words[0]; + // Extra processing fortypes wider than 64 bits (long double). + // Use an odd multiplier (2^64/golden-ratio-phi), + // so the halves cannot cancel under a later XOR-style mixer + for(std::size_t i = 1; i < NUM_WORDS; i++) + { + result = result * std::uint64_t {0x9e3779b97f4a7c15} + words[i]; + } + + return result; } }; diff --git a/src/axom/core/tests/core_device_hash.hpp b/src/axom/core/tests/core_device_hash.hpp index d19ac69386..58173725e1 100644 --- a/src/axom/core/tests/core_device_hash.hpp +++ b/src/axom/core/tests/core_device_hash.hpp @@ -11,6 +11,9 @@ // gtest includes #include "gtest/gtest.h" +// C++ includes +#include + template class core_device_hash : public ::testing::Test { @@ -274,7 +277,7 @@ TEST(core_device_hash, hash_width_decoupled_from_indextype) "integral hash result must be std::uint64_t"); static_assert(std::is_same::result_type, std::uint64_t>::value, "floating-point hash result must be std::uint64_t"); - static_assert(std::is_same::result_type, std::uint64_t>::value, + static_assert(std::is_same::result_type, std::uint64_t>::value, "pointer hash result must be std::uint64_t"); static_assert(std::is_same::result_type, std::uint64_t>::value, "catch-all (std::hash) result must be std::uint64_t"); @@ -290,3 +293,36 @@ TEST(core_device_hash, hash_width_decoupled_from_indextype) EXPECT_NE(device_hasher(base), device_hasher(plus_2_33)); EXPECT_NE(device_hasher(plus_2_32), device_hasher(plus_2_33)); } + +TEST(core_device_hash, hash_float_bit_pattern) +{ + // Floating-point keys must be hashed by bit pattern, not by integer value conversion. + // This is a regression test for a previous implementation where the conversion collapsed + // every key with the same integer value, e.g. all numbers between -1 and 1 converted to integer 0 + // so a FlatMap keyed on fractional floats degenerated into a single probe chain. + axom::DeviceHash float_hasher; + axom::DeviceHash double_hasher; + + EXPECT_NE(float_hasher(0.25f), float_hasher(0.75f)); + EXPECT_NE(float_hasher(0.25f), std::uint64_t {0}); + EXPECT_NE(double_hasher(0.25), double_hasher(0.75)); + + // A spread of fractional keys must be collision-free at this scale + std::set float_hashes, double_hashes; + const int NUM_KEYS = 1000; + for(int i = 1; i <= NUM_KEYS; i++) + { + float_hashes.insert(float_hasher(i / static_cast(NUM_KEYS + 1))); + double_hashes.insert(double_hasher(i / static_cast(NUM_KEYS + 1))); + } + EXPECT_EQ(float_hashes.size(), NUM_KEYS); + EXPECT_EQ(double_hashes.size(), NUM_KEYS); + + // Signed zeros compare equal and must hash equal + EXPECT_EQ(float_hasher(0.0f), float_hasher(-0.0f)); + EXPECT_EQ(double_hasher(0.0), double_hasher(-0.0)); + + // Magnitudes beyond any integer type's range are now well-defined and distinct + EXPECT_NE(double_hasher(1e300), double_hasher(2e300)); + EXPECT_NE(float_hasher(-0.5f), float_hasher(0.5f)); +} diff --git a/src/axom/core/tests/core_flatmap.hpp b/src/axom/core/tests/core_flatmap.hpp index 89c6616fab..186fbbf70a 100644 --- a/src/axom/core/tests/core_flatmap.hpp +++ b/src/axom/core/tests/core_flatmap.hpp @@ -129,6 +129,33 @@ using MyTypes = ::testing::Types, TYPED_TEST_SUITE(core_flatmap, MyTypes); +TEST(core_flatmap_unit, float_keys_in_unit_interval) +{ + // Regression test for the floating-point DeviceHash specialization, which + // converted keys to integers by value: every key in (0, 1) hashed to 0, so + // this map was a single probe chain and each insert/find was O(size). + // With bit-pattern hashing the keys spread normally. + axom::FlatMap test_map; + const int NUM_ELEMS = 512; + + for(int i = 1; i <= NUM_ELEMS; i++) + { + float key = i / static_cast(NUM_ELEMS + 2); + test_map[key] = i; + } + + EXPECT_EQ(test_map.size(), NUM_ELEMS); + for(int i = 1; i <= NUM_ELEMS; i++) + { + float key = i / static_cast(NUM_ELEMS + 2); + auto it = test_map.find(key); + ASSERT_NE(it, test_map.end()); + EXPECT_EQ(it->second, i); + } + EXPECT_EQ(test_map.find(1.5f), test_map.end()); + EXPECT_EQ(test_map.count(0.5f), 1); +} + AXOM_TYPED_TEST(core_flatmap, default_init) { using MapType = typename TestFixture::MapType; From 2c0060213e1337f7b93ee01630ae30cb9532ad7a Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 10 Jun 2026 22:03:17 +0000 Subject: [PATCH 436/986] FlatTable: wrap probe advance with a mask, not a signed division The quadratic probe advance in probeIndex and probeEmptyIndex wrapped using a mod (%) operator. Since the group count is always a power of two, we can use a bitmask instead. Adds a cross-group probe stress test: a degenerate hash drives 600 keys through one initial group so inserts, lookups, misses, erases, and reinserts all walk and wrap the group sequence. --- src/axom/core/detail/FlatTable.hpp | 27 ++++++----- src/axom/core/tests/core_flatmap.hpp | 72 ++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 11 deletions(-) diff --git a/src/axom/core/detail/FlatTable.hpp b/src/axom/core/detail/FlatTable.hpp index e99853bdcf..66ac4019b7 100644 --- a/src/axom/core/detail/FlatTable.hpp +++ b/src/axom/core/detail/FlatTable.hpp @@ -319,10 +319,11 @@ struct SequentialLookupPolicy : ProbePolicy IndexType probeEmptyIndex(int ngroups_pow_2, ArrayView metadata, HashType hash) const { // We use the k MSBs of the hash as the initial group probe point, - // where ngroups = 2^k. - int bitshift_right = ((CHAR_BIT * sizeof(HashType)) - ngroups_pow_2); - HashType curr_group = hash >> bitshift_right; - curr_group &= ((1 << ngroups_pow_2) - 1); + // where ngroups = 2^k. Since the group count is always a power of two, + // wrapping a group index is a bitwise AND with this mask. + const int bitshift_right = ((CHAR_BIT * sizeof(HashType)) - ngroups_pow_2); + const HashType group_mask = (HashType {1} << ngroups_pow_2) - 1; + HashType curr_group = (hash >> bitshift_right) & group_mask; int empty_group = NO_MATCH; int empty_bucket = NO_MATCH; @@ -347,7 +348,10 @@ struct SequentialLookupPolicy : ProbePolicy // Set the overflow bit and continue probing. metadata[curr_group].setOverflow(hash_8); } - curr_group = (curr_group + this->getNext(iteration)) % metadata.size(); + // Mask instead of "% metadata.size()": the group count is a power of + // two, and the modulo compiled to a 64-bit signed division on the + // critical path of every probe continuation. + curr_group = (curr_group + this->getNext(iteration)) & group_mask; } if(empty_group != NO_MATCH) { @@ -373,10 +377,11 @@ struct SequentialLookupPolicy : ProbePolicy FoundIndex&& on_hash_found) const { // We use the k MSBs of the hash as the initial group probe point, - // where ngroups = 2^k. - int bitshift_right = ((CHAR_BIT * sizeof(HashType)) - ngroups_pow_2); - HashType curr_group = hash >> bitshift_right; - curr_group &= ((1 << ngroups_pow_2) - 1); + // where ngroups = 2^k. Since the group count is always a power of two, + // wrapping a group index is a bitwise AND with this mask. + const int bitshift_right = ((CHAR_BIT * sizeof(HashType)) - ngroups_pow_2); + const HashType group_mask = (HashType {1} << ngroups_pow_2) - 1; + HashType curr_group = (hash >> bitshift_right) & group_mask; std::uint8_t hash_8 = static_cast(hash); bool keep_going = true; @@ -397,8 +402,8 @@ struct SequentialLookupPolicy : ProbePolicy { break; } - // Probe the next bucket. - curr_group = (curr_group + this->getNext(iteration)) % metadata.size(); + // Probe the next bucket. Note that the group count is a power of 2 so we can use a bit mask + curr_group = (curr_group + this->getNext(iteration)) & group_mask; } } diff --git a/src/axom/core/tests/core_flatmap.hpp b/src/axom/core/tests/core_flatmap.hpp index 186fbbf70a..4e3a2f4506 100644 --- a/src/axom/core/tests/core_flatmap.hpp +++ b/src/axom/core/tests/core_flatmap.hpp @@ -156,6 +156,78 @@ TEST(core_flatmap_unit, float_keys_in_unit_interval) EXPECT_EQ(test_map.count(0.5f), 1); } +// Hash functor whose group-selector bits (the top bits) are always zero, +// so every key lands in the same initial group and probing must walk across groups. +// Stress tests the cross-group probe sequence. +struct DegenerateGroupHash +{ + using argument_type = int; + using result_type = std::uint64_t; + std::uint64_t operator()(int key) const + { + return static_cast(static_cast(key) & 0xFF); + } +}; + +TEST(core_flatmap_unit, cross_group_probe_chains) +{ + // Forces hundreds of keys through a single initial group: + // inserts walk probeEmptyIndex's group sequence, lookups walk probeIndex's, + // both wrap around the group array, and erases punch holes mid-sequence. + // Guards the probe-advance arithmetic (group wrapping) against regressions. + axom::FlatMap test_map; + const int NUM_ELEMS = 600; + + for(int i = 0; i < NUM_ELEMS; i++) + { + test_map[i] = i * 3; + } + EXPECT_EQ(test_map.size(), NUM_ELEMS); + for(int i = 0; i < NUM_ELEMS; i++) + { + auto it = test_map.find(i); + ASSERT_NE(it, test_map.end()); + EXPECT_EQ(it->second, i * 3); + } + for(int i = NUM_ELEMS; i < NUM_ELEMS + 64; i++) + { + EXPECT_EQ(test_map.find(i), test_map.end()); + } + + // Erase every third key (some mid-probe-sequence) and re-verify + for(int i = 0; i < NUM_ELEMS; i += 3) + { + EXPECT_EQ(test_map.erase(i), 1); + } + EXPECT_EQ(test_map.size(), NUM_ELEMS - (NUM_ELEMS + 2) / 3); + for(int i = 0; i < NUM_ELEMS; i++) + { + if(i % 3 == 0) + { + EXPECT_EQ(test_map.find(i), test_map.end()); + } + else + { + auto it = test_map.find(i); + ASSERT_NE(it, test_map.end()); + EXPECT_EQ(it->second, i * 3); + } + } + + // Reinsert over the holes and verify + for(int i = 0; i < NUM_ELEMS; i += 3) + { + test_map[i] = i * 7; + } + EXPECT_EQ(test_map.size(), NUM_ELEMS); + for(int i = 0; i < NUM_ELEMS; i++) + { + auto it = test_map.find(i); + ASSERT_NE(it, test_map.end()); + EXPECT_EQ(it->second, (i % 3 == 0) ? i * 7 : i * 3); + } +} + AXOM_TYPED_TEST(core_flatmap, default_init) { using MapType = typename TestFixture::MapType; From c6555a89ab3d7c04e8696319c7f3df7a4cbe327e Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 15 Jan 2026 19:55:17 -0800 Subject: [PATCH 437/986] Adds initial benchmark for flatmap vs map vs unordered_map vs sparsehash --- src/axom/core/tests/CMakeLists.txt | 3 +- .../core/tests/core_benchmark_flatmap.cpp | 463 ++++++++++++++++++ 2 files changed, 465 insertions(+), 1 deletion(-) create mode 100644 src/axom/core/tests/core_benchmark_flatmap.cpp diff --git a/src/axom/core/tests/CMakeLists.txt b/src/axom/core/tests/CMakeLists.txt index eecb1bc949..b30ebb25fc 100644 --- a/src/axom/core/tests/CMakeLists.txt +++ b/src/axom/core/tests/CMakeLists.txt @@ -212,7 +212,8 @@ endforeach() if (ENABLE_BENCHMARKS) set(core_benchmarks - core_benchmark_array.cpp ) + core_benchmark_array.cpp + core_benchmark_flatmap.cpp ) foreach(test ${core_benchmarks}) get_filename_component(test_name ${test} NAME_WE) diff --git a/src/axom/core/tests/core_benchmark_flatmap.cpp b/src/axom/core/tests/core_benchmark_flatmap.cpp new file mode 100644 index 0000000000..978c47f6d1 --- /dev/null +++ b/src/axom/core/tests/core_benchmark_flatmap.cpp @@ -0,0 +1,463 @@ +// 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 "benchmark/benchmark.h" + +#include "axom/config.hpp" +#include "axom/core.hpp" +#include "axom/slic.hpp" + +#include "axom/CLI11.hpp" +#include "axom/fmt.hpp" + +#include "axom/core/FlatMap.hpp" +#include "axom/core/FlatMapUtil.hpp" + +#if defined(AXOM_USE_SPARSEHASH) + #include "axom/sparsehash/sparse_hash_map" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + +using KeyType = std::int64_t; +using ValueType = std::int64_t; + +enum class FlatMapFeatureBenchmarks +{ + None = 0, + Insertion = 1 << 0, + Lookup = 1 << 1, + BatchedInsertion = 1 << 2, + + All = Insertion | Lookup | BatchedInsertion +}; + +inline FlatMapFeatureBenchmarks operator|(FlatMapFeatureBenchmarks lhs, FlatMapFeatureBenchmarks rhs) +{ + using T = std::underlying_type_t; + return static_cast(static_cast(lhs) | static_cast(rhs)); +} + +inline FlatMapFeatureBenchmarks& operator|=(FlatMapFeatureBenchmarks& lhs, + FlatMapFeatureBenchmarks rhs) +{ + lhs = lhs | rhs; + return lhs; +} + +inline FlatMapFeatureBenchmarks operator&(FlatMapFeatureBenchmarks lhs, FlatMapFeatureBenchmarks rhs) +{ + using T = std::underlying_type_t; + return static_cast(static_cast(lhs) & static_cast(rhs)); +} + +std::vector args_benchmark_sizes; +FlatMapFeatureBenchmarks args_benchmark_features {FlatMapFeatureBenchmarks::None}; +int args_batch_size = 1 << 10; +} // namespace + +template <> +struct axom::fmt::formatter +{ + template + constexpr auto parse(ParseContext& ctx) + { + return ctx.begin(); + } + + template + auto format(FlatMapFeatureBenchmarks feature, FormatContext& ctx) const + { + static const std::map feature_map = { + {FlatMapFeatureBenchmarks::Insertion, "Insertion"}, + {FlatMapFeatureBenchmarks::Lookup, "Lookup"}, + {FlatMapFeatureBenchmarks::BatchedInsertion, "BatchedInsertion"}}; + + if(feature == FlatMapFeatureBenchmarks::None) + { + return axom::fmt::format_to(ctx.out(), "None"); + } + else if(feature == FlatMapFeatureBenchmarks::All) + { + return axom::fmt::format_to(ctx.out(), "All"); + } + + std::string name; + for(const auto& kv : feature_map) + { + if((feature & kv.first) != FlatMapFeatureBenchmarks::None) + { + name += name.empty() ? kv.second : "|" + kv.second; + } + } + return axom::fmt::format_to(ctx.out(), "{}", name); + } +}; + +namespace +{ + +void CustomArgs(benchmark::internal::Benchmark* b) +{ + for(int sz : ::args_benchmark_sizes) + { + b->Arg(sz); + } +} + +std::vector make_shuffled_keys(int n, std::uint64_t seed) +{ + std::vector keys; + keys.reserve(static_cast(n)); + for(int i = 0; i < n; ++i) + { + keys.push_back(static_cast(i)); + } + + std::mt19937_64 rng(seed); + std::shuffle(keys.begin(), keys.end(), rng); + return keys; +} + +std::vector> make_pairs(const std::vector& keys) +{ + std::vector> pairs; + pairs.reserve(keys.size()); + for(std::size_t i = 0; i < keys.size(); ++i) + { + pairs.emplace_back(keys[i], static_cast(i)); + } + return pairs; +} + +std::vector make_miss_keys(const std::vector& keys, KeyType offset) +{ + std::vector misses; + misses.reserve(keys.size()); + for(KeyType k : keys) + { + misses.push_back(k + offset); + } + return misses; +} + +template +struct MapFactory +{ + static MapType make_empty(std::size_t) { return MapType {}; } + static void reserve(MapType&, std::size_t) { } +}; + +template +struct MapFactory> +{ + using MapType = std::unordered_map; + static MapType make_empty(std::size_t) { return MapType {}; } + static void reserve(MapType& map, std::size_t n) { map.reserve(n); } +}; + +template +struct MapFactory> +{ + using MapType = axom::FlatMap; + static MapType make_empty(std::size_t) { return MapType {}; } + static void reserve(MapType& map, std::size_t n) { map.reserve(static_cast(n)); } +}; + +#if defined(AXOM_USE_SPARSEHASH) +template +void reserve_sparsehash(axom::google::sparse_hash_map& map, std::size_t n) +{ + map.max_load_factor(0.8f); + const auto buckets_needed = + static_cast(static_cast(n) / map.max_load_factor()) + 1; + map.resize(buckets_needed); +} +#endif + +#if defined(AXOM_USE_SPARSEHASH) +template +struct MapFactory> +{ + using MapType = axom::google::sparse_hash_map; + static MapType make_empty(std::size_t) { return MapType {}; } + static void reserve(MapType& map, std::size_t n) { reserve_sparsehash(map, n); } +}; +#endif + +template +MapType make_reserved_map(std::size_t n) +{ + MapType map = MapFactory::make_empty(n); + MapFactory::reserve(map, n); + return map; +} + +template +MapType make_filled_map(const std::vector>& pairs) +{ + MapType map = make_reserved_map(pairs.size()); + map.insert(pairs.begin(), pairs.end()); + return map; +} + +template +void BM_Insert_StartEmpty(benchmark::State& state) +{ + const int n = state.range(0); + const auto keys = make_shuffled_keys(n, 0xA2D5B7C4ULL); + const auto pairs = make_pairs(keys); + + for(auto _ : state) + { + MapType map = MapFactory::make_empty(pairs.size()); + map.insert(pairs.begin(), pairs.end()); + benchmark::DoNotOptimize(map); + } +} + +template +void BM_Insert_Reserved(benchmark::State& state) +{ + const int n = state.range(0); + const auto keys = make_shuffled_keys(n, 0xA2D5B7C4ULL); + const auto pairs = make_pairs(keys); + + for(auto _ : state) + { + MapType map = make_reserved_map(pairs.size()); + map.insert(pairs.begin(), pairs.end()); + benchmark::DoNotOptimize(map); + } +} + +template +void BM_Find_Hit(benchmark::State& state) +{ + const int n = state.range(0); + const auto keys = make_shuffled_keys(n, 0xC0FFEEULL); + const auto pairs = make_pairs(keys); + const MapType map = make_filled_map(pairs); + + for(auto _ : state) + { + ValueType sum = 0; + for(KeyType k : keys) + { + auto it = map.find(k); + if(it != map.end()) + { + sum += it->second; + } + } + benchmark::DoNotOptimize(sum); + } +} + +template +void BM_Find_Miss(benchmark::State& state) +{ + const int n = state.range(0); + const auto keys = make_shuffled_keys(n, 0xC0FFEEULL); + const auto pairs = make_pairs(keys); + const MapType map = make_filled_map(pairs); + const auto miss_keys = make_miss_keys(keys, static_cast(n) + 11); + + for(auto _ : state) + { + std::int64_t misses = 0; + for(KeyType k : miss_keys) + { + misses += (map.find(k) == map.end()) ? 1 : 0; + } + benchmark::DoNotOptimize(misses); + } +} + +template +void insert_pairs_in_batches(MapType& map, + const std::vector>& pairs, + int batch_size) +{ + const std::size_t n = pairs.size(); + const std::size_t bs = static_cast(std::max(1, batch_size)); + for(std::size_t offset = 0; offset < n; offset += bs) + { + const std::size_t count = std::min(bs, n - offset); + map.insert(pairs.begin() + static_cast(offset), + pairs.begin() + static_cast(offset + count)); + } +} + +template +void insert_pairs_in_batches(axom::FlatMap& map, + const std::vector>& pairs, + int batch_size) +{ + const std::size_t n = pairs.size(); + const std::size_t bs = static_cast(std::max(1, batch_size)); + for(std::size_t offset = 0; offset < n; offset += bs) + { + const std::size_t count = std::min(bs, n - offset); + map.template insert(pairs.begin() + static_cast(offset), + pairs.begin() + static_cast(offset + count)); + } +} + +template +void BM_BatchedInsert_Reserved(benchmark::State& state) +{ + const int n = state.range(0); + const auto keys = make_shuffled_keys(n, 0x1CEB00DAULL); + const auto pairs = make_pairs(keys); + + for(auto _ : state) + { + MapType map = make_reserved_map(pairs.size()); + insert_pairs_in_batches(map, pairs, ::args_batch_size); + benchmark::DoNotOptimize(map); + } +} + +} // namespace + +//----------------------------------------------------------------------------- +// Register benchmarks +//----------------------------------------------------------------------------- + +template +void RegisterBenchmarksFor(const std::string& map_name) +{ + auto name = [&map_name](const std::string& op) { + return axom::fmt::format("{}::{}", map_name, op); + }; + + // clang-format off + if((::args_benchmark_features & FlatMapFeatureBenchmarks::Insertion) != FlatMapFeatureBenchmarks::None) + { + benchmark::RegisterBenchmark(name("insert_startEmpty"), &BM_Insert_StartEmpty)->Apply(CustomArgs); + benchmark::RegisterBenchmark(name("insert_reserved"), &BM_Insert_Reserved)->Apply(CustomArgs); + } + + if((::args_benchmark_features & FlatMapFeatureBenchmarks::Lookup) != FlatMapFeatureBenchmarks::None) + { + benchmark::RegisterBenchmark(name("find_hit"), &BM_Find_Hit)->Apply(CustomArgs); + benchmark::RegisterBenchmark(name("find_miss"), &BM_Find_Miss)->Apply(CustomArgs); + } + + if((::args_benchmark_features & FlatMapFeatureBenchmarks::BatchedInsertion) != FlatMapFeatureBenchmarks::None) + { + benchmark::RegisterBenchmark(name("insert_batched_reserved"), &BM_BatchedInsert_Reserved)->Apply(CustomArgs); + } + // clang-format on +} + +int main(int argc, char* argv[]) +{ + std::vector local_test_sizes; + FlatMapFeatureBenchmarks local_benchmark_features {FlatMapFeatureBenchmarks::None}; + int local_batch_size = ::args_batch_size; + + axom::CLI::App app {"Axom FlatMap benchmarks"}; + app.add_option("-s,--custom_sizes", local_test_sizes) + ->description("Adds custom map sizes to benchmark (positive numbers only)") + ->expected(-1) + ->default_val(std::vector {1 << 16}) + ->each([](const std::string& num_str) { + int num = std::stoi(num_str); + if(num < 0) + { + throw axom::CLI::ValidationError("Negative numbers are not allowed"); + } + }); + + app + .add_flag_callback("--use_cache_related_sizes", + [&local_test_sizes]() { + local_test_sizes.push_back(1 << 3); // small + local_test_sizes.push_back(1 << 16); // larger than 32K L1 cache + local_test_sizes.push_back(1 << 19); // larger than 256K L2 cache + //local_test_sizes.push_back(1 << 25); // larger than 25M L3 cache + }) + ->description("Test map sizes related to typical cache sizes"); + + app.add_option("--batch_size", local_batch_size) + ->description("Batch size for batched insertion benchmarks") + ->default_val(local_batch_size) + ->check(axom::CLI::PositiveNumber); + + std::vector feature_strings; + auto feature_opt = + app.add_option("-f,--features", feature_strings) + ->description( + "Features to benchmark (Insertion, Lookup, BatchedInsertion, All); default is 'All'") + ->expected(-1) + ->each([&local_benchmark_features](const std::string& feature) { + static const std::map feature_map = { + {"insertion", FlatMapFeatureBenchmarks::Insertion}, + {"lookup", FlatMapFeatureBenchmarks::Lookup}, + {"batchedinsertion", FlatMapFeatureBenchmarks::BatchedInsertion}, + {"all", FlatMapFeatureBenchmarks::All}}; + + std::string lower_feature = feature; + std::transform(lower_feature.begin(), lower_feature.end(), lower_feature.begin(), ::tolower); + auto it = feature_map.find(lower_feature); + if(it == feature_map.end()) + { + throw axom::CLI::ValidationError("Invalid feature: " + feature); + } + + local_benchmark_features |= it->second; + }); + + app.allow_extras(); // pass additional args to gbenchmark + CLI11_PARSE(app, argc, argv); + + ::benchmark::Initialize(&argc, argv); + axom::slic::SimpleLogger logger; + + // process input into global variables + { + ::args_benchmark_features = + feature_opt->count() > 0 ? local_benchmark_features : FlatMapFeatureBenchmarks::All; + + std::sort(local_test_sizes.begin(), local_test_sizes.end()); + auto last = std::unique(local_test_sizes.begin(), local_test_sizes.end()); + local_test_sizes.erase(last, local_test_sizes.end()); + std::swap(::args_benchmark_sizes, local_test_sizes); + + ::args_batch_size = local_batch_size; + + SLIC_INFO("Parsed and processed command line arguments:"); + SLIC_INFO(axom::fmt::format("- Map sizes: {}", axom::fmt::join(::args_benchmark_sizes, ","))); + SLIC_INFO(axom::fmt::format("- Batch size: {}", ::args_batch_size)); + SLIC_INFO(axom::fmt::format("- Map features to test: {}", ::args_benchmark_features)); + } + + RegisterBenchmarksFor>("axom::FlatMap"); + RegisterBenchmarksFor>("std::unordered_map"); + RegisterBenchmarksFor>("std::map"); + +#if defined(AXOM_USE_SPARSEHASH) + RegisterBenchmarksFor>( + "axom::google::sparse_hash_map"); +#endif + + ::benchmark::RunSpecifiedBenchmarks(); + return 0; +} From 09dc808d31866b2c19561aa61c2501319d0f9b77 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 15 Jan 2026 20:20:13 -0800 Subject: [PATCH 438/986] Improves performance of FlatMap batched insertion for SEQ policy --- src/axom/core/FlatMapUtil.hpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/axom/core/FlatMapUtil.hpp b/src/axom/core/FlatMapUtil.hpp index 5515607046..d1adb18f90 100644 --- a/src/axom/core/FlatMapUtil.hpp +++ b/src/axom/core/FlatMapUtil.hpp @@ -263,6 +263,27 @@ void FlatMap::insert(InputIt kv_begin, InputIt kv_end) typename std::iterator_traits::iterator_category>::value, "InputIt must be a random-access iterator for batched construction"); + // Fast path for sequential execution: + // The batched insertion algorithm below is designed for parallel execution and + // uses per-group locks and auxiliary arrays for deduplication. In SEQ, those + // structures add significant overhead; a simple sequential loop provides + // better performance while preserving the documented semantics that later + // duplicates overwrite earlier ones. + if constexpr(std::is_same_v) + { + const IndexType num_elems = std::distance(kv_begin, kv_end); + + // Ensure we have enough capacity up-front to avoid repeated rehashing. + this->reserve(this->size() + num_elems); + + for(IndexType idx = 0; idx < num_elems; ++idx) + { + auto kv = *(kv_begin + idx); + this->insert_or_assign(kv.first, kv.second); + } + return; + } + using HashResult = typename Hash::result_type; using GroupBucket = detail::flat_map::GroupBucket; From acdf68343bdefa4ffa71410a32d00fd4671fdbfb Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 15 Jan 2026 20:50:35 -0800 Subject: [PATCH 439/986] Adds FlatMap benchmarks for hits and misses of precached entities --- src/axom/core/FlatMap.hpp | 18 +++++ src/axom/core/detail/FlatTable.hpp | 4 +- .../core/tests/core_benchmark_flatmap.cpp | 75 +++++++++++++++++++ 3 files changed, 94 insertions(+), 3 deletions(-) diff --git a/src/axom/core/FlatMap.hpp b/src/axom/core/FlatMap.hpp index 0a7b1209c4..5f3aca38ae 100644 --- a/src/axom/core/FlatMap.hpp +++ b/src/axom/core/FlatMap.hpp @@ -74,6 +74,8 @@ class FlatMap : detail::flat_map::SequentialLookupPolicy; using const_iterator = IteratorImpl; @@ -289,6 +291,8 @@ class FlatMap : detail::flat_map::SequentialLookupPolicy auto FlatMap::find(const KeyType& key) -> iterator { auto hash = Hash {}(key); + return find_with_hash(key, hash); +} + +template +auto FlatMap::find_with_hash(const KeyType& key, hash_result_type hash) + -> iterator +{ iterator found_iter = end(); this->probeIndex(m_numGroups2, m_metadata, hash, [&](IndexType bucket_index) -> bool { if(this->m_buckets[bucket_index].get().first == key) @@ -849,6 +860,13 @@ template auto FlatMap::find(const KeyType& key) const -> const_iterator { auto hash = Hash {}(key); + return find_with_hash(key, hash); +} + +template +auto FlatMap::find_with_hash(const KeyType& key, hash_result_type hash) const + -> const_iterator +{ const_iterator found_iter = end(); this->probeIndex(m_numGroups2, m_metadata, hash, [&](IndexType bucket_index) -> bool { if(this->m_buckets[bucket_index].get().first == key) diff --git a/src/axom/core/detail/FlatTable.hpp b/src/axom/core/detail/FlatTable.hpp index 66ac4019b7..6dec9f0970 100644 --- a/src/axom/core/detail/FlatTable.hpp +++ b/src/axom/core/detail/FlatTable.hpp @@ -348,9 +348,7 @@ struct SequentialLookupPolicy : ProbePolicy // Set the overflow bit and continue probing. metadata[curr_group].setOverflow(hash_8); } - // Mask instead of "% metadata.size()": the group count is a power of - // two, and the modulo compiled to a 64-bit signed division on the - // critical path of every probe continuation. + // The group count is a power of two, so we can use a bitmask (instead of a modulo) curr_group = (curr_group + this->getNext(iteration)) & group_mask; } if(empty_group != NO_MATCH) diff --git a/src/axom/core/tests/core_benchmark_flatmap.cpp b/src/axom/core/tests/core_benchmark_flatmap.cpp index 978c47f6d1..21ea611db6 100644 --- a/src/axom/core/tests/core_benchmark_flatmap.cpp +++ b/src/axom/core/tests/core_benchmark_flatmap.cpp @@ -289,6 +289,67 @@ void BM_Find_Miss(benchmark::State& state) } } +void BM_FlatMap_Find_Hit_Prehashed(benchmark::State& state) +{ + using MapType = axom::FlatMap; + using HashResult = typename MapType::hash_result_type; + + const int n = state.range(0); + const auto keys = make_shuffled_keys(n, 0xC0FFEEULL); + const auto pairs = make_pairs(keys); + const MapType map = make_filled_map(pairs); + + std::vector hashes; + hashes.reserve(keys.size()); + for(KeyType k : keys) + { + hashes.push_back(typename MapType::hasher {}(k)); + } + + for(auto _ : state) + { + ValueType sum = 0; + for(std::size_t i = 0; i < keys.size(); ++i) + { + auto it = map.find_with_hash(keys[i], hashes[i]); + if(it != map.end()) + { + sum += it->second; + } + } + benchmark::DoNotOptimize(sum); + } +} + +void BM_FlatMap_Find_Miss_Prehashed(benchmark::State& state) +{ + using MapType = axom::FlatMap; + using HashResult = typename MapType::hash_result_type; + + const int n = state.range(0); + const auto keys = make_shuffled_keys(n, 0xC0FFEEULL); + const auto pairs = make_pairs(keys); + const MapType map = make_filled_map(pairs); + const auto miss_keys = make_miss_keys(keys, static_cast(n) + 11); + + std::vector hashes; + hashes.reserve(miss_keys.size()); + for(KeyType k : miss_keys) + { + hashes.push_back(typename MapType::hasher {}(k)); + } + + for(auto _ : state) + { + std::int64_t misses = 0; + for(std::size_t i = 0; i < miss_keys.size(); ++i) + { + misses += (map.find_with_hash(miss_keys[i], hashes[i]) == map.end()) ? 1 : 0; + } + benchmark::DoNotOptimize(misses); + } +} + template void insert_pairs_in_batches(MapType& map, const std::vector>& pairs, @@ -367,6 +428,19 @@ void RegisterBenchmarksFor(const std::string& map_name) // clang-format on } +void RegisterFlatMapPrehashedBenchmarks() +{ + auto name = [](const std::string& op) { return axom::fmt::format("axom::FlatMap::{}", op); }; + + if((::args_benchmark_features & FlatMapFeatureBenchmarks::Lookup) != FlatMapFeatureBenchmarks::None) + { + benchmark::RegisterBenchmark(name("find_hit_prehashed"), &BM_FlatMap_Find_Hit_Prehashed) + ->Apply(CustomArgs); + benchmark::RegisterBenchmark(name("find_miss_prehashed"), &BM_FlatMap_Find_Miss_Prehashed) + ->Apply(CustomArgs); + } +} + int main(int argc, char* argv[]) { std::vector local_test_sizes; @@ -450,6 +524,7 @@ int main(int argc, char* argv[]) } RegisterBenchmarksFor>("axom::FlatMap"); + RegisterFlatMapPrehashedBenchmarks(); RegisterBenchmarksFor>("std::unordered_map"); RegisterBenchmarksFor>("std::map"); From 862195a78741a5ab162d196283fd694744416883 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 15 Jan 2026 21:11:11 -0800 Subject: [PATCH 440/986] Exploring faster hash functions --- src/axom/core/FlatMap.hpp | 6 ++---- src/axom/core/detail/FlatTable.hpp | 21 +++++++++++++++++++ .../core/tests/core_benchmark_flatmap.cpp | 3 +++ 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/axom/core/FlatMap.hpp b/src/axom/core/FlatMap.hpp index 5f3aca38ae..5fbaceed7d 100644 --- a/src/axom/core/FlatMap.hpp +++ b/src/axom/core/FlatMap.hpp @@ -835,8 +835,7 @@ FlatMap::FlatMap(IndexType num_elems, template auto FlatMap::find(const KeyType& key) -> iterator { - auto hash = Hash {}(key); - return find_with_hash(key, hash); + return find_with_hash(key, Hash {}(key)); } template @@ -859,8 +858,7 @@ auto FlatMap::find_with_hash(const KeyType& key, hash_ template auto FlatMap::find(const KeyType& key) const -> const_iterator { - auto hash = Hash {}(key); - return find_with_hash(key, hash); + return find_with_hash(key, Hash {}(key)); } template diff --git a/src/axom/core/detail/FlatTable.hpp b/src/axom/core/detail/FlatTable.hpp index 6dec9f0970..5da5d9be3c 100644 --- a/src/axom/core/detail/FlatTable.hpp +++ b/src/axom/core/detail/FlatTable.hpp @@ -72,6 +72,27 @@ struct HashMixer64 } }; +/*! + * \brief A faster (but lower-cost) hash mixer for 64-bit hashing. + * + * Intended for performance experiments when the cost of hashing dominates + * lookup. Uses a single 64-bit multiply followed by an xor-fold. + */ +template class HashFunc> +struct FastHashMixer64 +{ + using argument_type = typename HashFunc::argument_type; + using result_type = typename HashFunc::result_type; + + AXOM_HOST_DEVICE uint64_t operator()(const KeyType& key) const + { + uint64_t hash = static_cast(HashFunc {}(key)); + hash *= 0x9e3779b97f4a7c15ULL; + hash ^= hash >> 32; + return hash; + } +}; + // We follow the design of boost::unordered_flat_map, which uses a 128-bit chunk // of metadata for each group of 15 buckets. // This is split up into an "overflow bit", and 15 bytes representing the diff --git a/src/axom/core/tests/core_benchmark_flatmap.cpp b/src/axom/core/tests/core_benchmark_flatmap.cpp index 21ea611db6..8164f234c5 100644 --- a/src/axom/core/tests/core_benchmark_flatmap.cpp +++ b/src/axom/core/tests/core_benchmark_flatmap.cpp @@ -15,6 +15,7 @@ #include "axom/core/FlatMap.hpp" #include "axom/core/FlatMapUtil.hpp" +#include "axom/core/detail/FlatTable.hpp" #if defined(AXOM_USE_SPARSEHASH) #include "axom/sparsehash/sparse_hash_map" @@ -525,6 +526,8 @@ int main(int argc, char* argv[]) RegisterBenchmarksFor>("axom::FlatMap"); RegisterFlatMapPrehashedBenchmarks(); + using FastHash = axom::detail::flat_map::FastHashMixer64; + RegisterBenchmarksFor>("axom::FlatMapFastHash"); RegisterBenchmarksFor>("std::unordered_map"); RegisterBenchmarksFor>("std::map"); From 296934c5cf6049580ff1b693de4a48083204740d Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 10 Mar 2026 15:08:25 -0700 Subject: [PATCH 441/986] Adds benchmark for flatmap load factor --- .../core/tests/core_benchmark_flatmap.cpp | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/src/axom/core/tests/core_benchmark_flatmap.cpp b/src/axom/core/tests/core_benchmark_flatmap.cpp index 8164f234c5..3fe000ec62 100644 --- a/src/axom/core/tests/core_benchmark_flatmap.cpp +++ b/src/axom/core/tests/core_benchmark_flatmap.cpp @@ -22,6 +22,7 @@ #endif #include +#include #include #include #include @@ -217,6 +218,27 @@ MapType make_filled_map(const std::vector>& pairs) return map; } +template +axom::FlatMap make_filled_flatmap_with_target_load_factor( + const std::vector>& pairs, + double target_load_factor) +{ + using MapType = axom::FlatMap; + MapType map; + + const double max_lf = map.max_load_factor(); + const double lf = std::max(1e-3, std::min(target_load_factor, max_lf)); + const double n = static_cast(pairs.size()); + + // FlatMap's ctor/rehash argument is scaled internally by max_load_factor. + // To target load factor `lf` for `n` elements, scale the count accordingly. + const axom::IndexType rehash_count = static_cast(std::ceil((n * max_lf) / lf)); + + map.rehash(rehash_count); + map.insert(pairs.begin(), pairs.end()); + return map; +} + template void BM_Insert_StartEmpty(benchmark::State& state) { @@ -290,6 +312,32 @@ void BM_Find_Miss(benchmark::State& state) } } +template +void BM_FlatMap_Find_Hit_TargetLoad(benchmark::State& state, double target_load_factor) +{ + using MapType = axom::FlatMap; + + const int n = state.range(0); + const auto keys = make_shuffled_keys(n, 0xC0FFEEULL); + const auto pairs = make_pairs(keys); + const MapType map = + make_filled_flatmap_with_target_load_factor(pairs, target_load_factor); + + for(auto _ : state) + { + ValueType sum = 0; + for(KeyType k : keys) + { + auto it = map.find(k); + if(it != map.end()) + { + sum += it->second; + } + } + benchmark::DoNotOptimize(sum); + } +} + void BM_FlatMap_Find_Hit_Prehashed(benchmark::State& state) { using MapType = axom::FlatMap; @@ -528,6 +576,23 @@ int main(int argc, char* argv[]) RegisterFlatMapPrehashedBenchmarks(); using FastHash = axom::detail::flat_map::FastHashMixer64; RegisterBenchmarksFor>("axom::FlatMapFastHash"); + + // Explore the impact of lower load factors on successful lookups. + // This trades memory for potentially fewer probes and fewer cache misses. + using DefaultHash = axom::FlatMap::hasher; + benchmark::RegisterBenchmark("axom::FlatMap::find_hit_lf0p50", [](benchmark::State& st) { + BM_FlatMap_Find_Hit_TargetLoad(st, 0.50); + })->Apply(CustomArgs); + benchmark::RegisterBenchmark("axom::FlatMap::find_hit_lf0p70", [](benchmark::State& st) { + BM_FlatMap_Find_Hit_TargetLoad(st, 0.70); + })->Apply(CustomArgs); + benchmark::RegisterBenchmark("axom::FlatMapFastHash::find_hit_lf0p50", [](benchmark::State& st) { + BM_FlatMap_Find_Hit_TargetLoad(st, 0.50); + })->Apply(CustomArgs); + benchmark::RegisterBenchmark("axom::FlatMapFastHash::find_hit_lf0p70", [](benchmark::State& st) { + BM_FlatMap_Find_Hit_TargetLoad(st, 0.70); + })->Apply(CustomArgs); + RegisterBenchmarksFor>("std::unordered_map"); RegisterBenchmarksFor>("std::map"); From 8b6cb5851a8bf8e44fa6532f0103901ccd29557d Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 11 Jun 2026 05:09:42 +0000 Subject: [PATCH 442/986] Benchmark: decouple lookup order from insertion order in FlatMap suite BM_Find_Hit looks keys up in the order they were inserted. Since node-based maps walk the heap nearly sequentially, the hardware prefetcher hides their pointer-chasing latency. This commit adds find_hit_shuffled (same keys, independently shuffled lookup order) and find_hit_randkeys (distinct pseudorandom 64-bit keys, shuffled lookup order) to better exhibit expected lookup behavior. --- .../core/tests/core_benchmark_flatmap.cpp | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/src/axom/core/tests/core_benchmark_flatmap.cpp b/src/axom/core/tests/core_benchmark_flatmap.cpp index 3fe000ec62..c87eb86e7c 100644 --- a/src/axom/core/tests/core_benchmark_flatmap.cpp +++ b/src/axom/core/tests/core_benchmark_flatmap.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -158,6 +159,46 @@ std::vector make_miss_keys(const std::vector& keys, KeyType of return misses; } +/*! + * \brief Returns a copy of \a keys reshuffled with an independent seed. + * + * Looking keys up in the exact order they were inserted is rarely representative, + * and it systematically favors node-based containers: with libstdc++'s identity hash + * for integers and densely numbered keys, the i-th lookup touches the i-th allocated node, + * so the lookup loop streams through the heap nearly sequentially and the hardware prefetcher hides + * most of the pointer-chasing latency. An independently shuffled lookup order removes that correlation. + */ +std::vector make_lookup_order(const std::vector& keys, std::uint64_t seed) +{ + std::vector lookup = keys; + std::mt19937_64 rng(seed); + std::shuffle(lookup.begin(), lookup.end(), rng); + return lookup; +} + +/*! + * \brief Generates \a n distinct pseudorandom 64-bit keys. + * + * Dense keys in [0, n) are friendly to identity-style integer hashes and bucket layouts. + * Random keys exercise hashing and probing the way sparse or pointer-derived IDs do. + */ +std::vector make_random_unique_keys(int n, std::uint64_t seed) +{ + std::mt19937_64 rng(seed); + std::unordered_set seen; + std::vector keys; + keys.reserve(static_cast(n)); + while(keys.size() < static_cast(n)) + { + const KeyType k = static_cast(rng()); + if(seen.insert(k).second) + { + keys.push_back(k); + } + } + return keys; +} + template struct MapFactory { @@ -269,6 +310,9 @@ void BM_Insert_Reserved(benchmark::State& state) } } +// NOTE: BM_Find_Hit looks keys up in insertion order, which favors +// node-based maps as described on make_lookup_order() above. +// Prefer BM_Find_Hit_Shuffled and BM_Find_Hit_RandomKeys when comparing containers. template void BM_Find_Hit(benchmark::State& state) { @@ -292,6 +336,54 @@ void BM_Find_Hit(benchmark::State& state) } } +template +void BM_Find_Hit_Shuffled(benchmark::State& state) +{ + const int n = state.range(0); + const auto keys = make_shuffled_keys(n, 0xC0FFEEULL); + const auto pairs = make_pairs(keys); + const MapType map = make_filled_map(pairs); + const auto lookup_keys = make_lookup_order(keys, 0xBADC0DE5ULL); + + for(auto _ : state) + { + ValueType sum = 0; + for(KeyType k : lookup_keys) + { + auto it = map.find(k); + if(it != map.end()) + { + sum += it->second; + } + } + benchmark::DoNotOptimize(sum); + } +} + +template +void BM_Find_Hit_RandomKeys(benchmark::State& state) +{ + const int n = state.range(0); + const auto keys = make_random_unique_keys(n, 0xFEEDFACEULL); + const auto pairs = make_pairs(keys); + const MapType map = make_filled_map(pairs); + const auto lookup_keys = make_lookup_order(keys, 0xBADC0DE5ULL); + + for(auto _ : state) + { + ValueType sum = 0; + for(KeyType k : lookup_keys) + { + auto it = map.find(k); + if(it != map.end()) + { + sum += it->second; + } + } + benchmark::DoNotOptimize(sum); + } +} + template void BM_Find_Miss(benchmark::State& state) { @@ -467,6 +559,8 @@ void RegisterBenchmarksFor(const std::string& map_name) if((::args_benchmark_features & FlatMapFeatureBenchmarks::Lookup) != FlatMapFeatureBenchmarks::None) { benchmark::RegisterBenchmark(name("find_hit"), &BM_Find_Hit)->Apply(CustomArgs); + benchmark::RegisterBenchmark(name("find_hit_shuffled"), &BM_Find_Hit_Shuffled)->Apply(CustomArgs); + benchmark::RegisterBenchmark(name("find_hit_randkeys"), &BM_Find_Hit_RandomKeys)->Apply(CustomArgs); benchmark::RegisterBenchmark(name("find_miss"), &BM_Find_Miss)->Apply(CustomArgs); } From 324c90a03e064b5ec8a7f10744eb8befa356c5a3 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 11 Jun 2026 05:11:25 +0000 Subject: [PATCH 443/986] FlatMap: force-inline the lookup hot path When find_with_hash() in not inlined, every lookup is more expensive (extra registers, and a stack spill for the key) and requires loop-invariant setup that cannot be hoisted out of the caller's lookup loop. Forcing the probe path inline removed 20-40% of find_hit time and 15-35% of find_miss time for FlatMap at n = 2^16 and 2^20. --- src/axom/core/FlatMap.hpp | 9 +++++---- src/axom/core/detail/FlatTable.hpp | 23 +++++++++++++++++------ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/axom/core/FlatMap.hpp b/src/axom/core/FlatMap.hpp index 5fbaceed7d..875db27c1a 100644 --- a/src/axom/core/FlatMap.hpp +++ b/src/axom/core/FlatMap.hpp @@ -289,10 +289,11 @@ class FlatMap : detail::flat_map::SequentialLookupPolicy #endif +// Force-inline annotation for the FlatMap/FlatTable lookup hot path. +#if defined(__CUDACC__) || defined(__HIPCC__) + #define AXOM_FLATMAP_FORCE_INLINE __forceinline__ +#elif defined(__GNUC__) || defined(__clang__) + #define AXOM_FLATMAP_FORCE_INLINE inline __attribute__((always_inline)) +#elif defined(_MSC_VER) + #define AXOM_FLATMAP_FORCE_INLINE __forceinline +#else + #define AXOM_FLATMAP_FORCE_INLINE inline +#endif + namespace axom { namespace detail @@ -162,7 +173,7 @@ struct GroupBucket } template - AXOM_HOST_DEVICE int visitHashBucket(std::uint8_t hash, Func&& visitor) const + AXOM_FLATMAP_FORCE_INLINE AXOM_HOST_DEVICE int visitHashBucket(std::uint8_t hash, Func&& visitor) const { std::uint8_t reducedHash = reduceHash(hash); #if !defined(AXOM_DEVICE_CODE) && defined(_AXOM_CORE_HAVE_SSE2) @@ -273,7 +284,7 @@ struct GroupBucket } template - AXOM_HOST_DEVICE bool getMaybeOverflowed(std::uint8_t hash) const + AXOM_FLATMAP_FORCE_INLINE AXOM_HOST_DEVICE bool getMaybeOverflowed(std::uint8_t hash) const { std::uint8_t hashOfwBit = 1 << (hash % 8); std::uint8_t curr_ofw; @@ -390,10 +401,10 @@ struct SequentialLookupPolicy : ProbePolicy * matching hash */ template - AXOM_HOST_DEVICE void probeIndex(int ngroups_pow_2, - ArrayView metadata, - HashType hash, - FoundIndex&& on_hash_found) const + AXOM_FLATMAP_FORCE_INLINE AXOM_HOST_DEVICE void probeIndex(int ngroups_pow_2, + ArrayView metadata, + HashType hash, + FoundIndex&& on_hash_found) const { // We use the k MSBs of the hash as the initial group probe point, // where ngroups = 2^k. Since the group count is always a power of two, From 03ac4d99017df403f67ae8268368ecc041a0955c Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 11 Jun 2026 05:13:25 +0000 Subject: [PATCH 444/986] FlatMap: hash once and avoid FP division in getEmplacePos `getEmplacePos()` computed `Hash{}(key)`, then called `find(key)`, which hashed the same key a second time. It then performed a floating-point division against MAX_LOAD_FACTOR on every insertion to decide whether to grow. Note: This reduced instruction count but the performance improvements within run-to-run noise in our measurements. --- src/axom/core/FlatMap.hpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/axom/core/FlatMap.hpp b/src/axom/core/FlatMap.hpp index 875db27c1a..087eb75810 100644 --- a/src/axom/core/FlatMap.hpp +++ b/src/axom/core/FlatMap.hpp @@ -7,6 +7,7 @@ #ifndef Axom_Core_FlatMap_HPP #define Axom_Core_FlatMap_HPP +#include #include #include #include @@ -897,14 +898,21 @@ auto FlatMap::getEmplacePos(const KeyType& key) auto hash = Hash {}(key); // If the key already exists, return the existing iterator. - iterator existing_elem = this->find(key); + // Reuse the hash computed above rather than re-hashing inside find(). + iterator existing_elem = this->find_with_hash(key, hash); if(existing_elem != this->end()) { return {existing_elem, false}; } // Resize to double the number of bucket groups if insertion would put us // above the maximum load factor. - if(((m_loadCount + 1) / (double)bucket_count()) >= MAX_LOAD_FACTOR) + // MAX_LOAD_FACTOR is exactly 7/8, so (count + 1) / buckets >= 7/8 is + // equivalent to 8 * (count + 1) >= 7 * buckets in exact integer arithmetic. + // This avoids a floating-point division on every insertion. + static_assert(MAX_LOAD_FACTOR == 0.875, + "Integer load-factor check below assumes MAX_LOAD_FACTOR == 7/8."); + if(8 * (static_cast(m_loadCount) + 1) >= + 7 * static_cast(bucket_count())) { IndexType newNumGroups = m_metadata.size() * 2; rehash(newNumGroups * BucketsPerGroup - 1); From f658482439a76424278a87de7a4dca1f16af4f29 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 11 Jun 2026 05:14:16 +0000 Subject: [PATCH 445/986] Benchmark: report realized load factor in target-load-factor scenarios FlatMap rounds its group count up to a power of two, so for a fixed element count the achievable load factors form a geometric ladder and a nominal target is quantized to the next rung at or below it. At n = 2^16 the 0.70 target and the default reserve(n) geometry coincide (actual load factor 0.533, which is why find_hit_lf0p70 reproduced find_hit to within noise), and the 0.50 target lands at 0.267 -- a table twice as large. That scenario was really measuring a larger working set, not a shorter probe sequence. --- src/axom/core/tests/core_benchmark_flatmap.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/axom/core/tests/core_benchmark_flatmap.cpp b/src/axom/core/tests/core_benchmark_flatmap.cpp index c87eb86e7c..a06d9be21a 100644 --- a/src/axom/core/tests/core_benchmark_flatmap.cpp +++ b/src/axom/core/tests/core_benchmark_flatmap.cpp @@ -273,6 +273,15 @@ axom::FlatMap make_filled_flatmap_with_target_load_factor( // FlatMap's ctor/rehash argument is scaled internally by max_load_factor. // To target load factor `lf` for `n` elements, scale the count accordingly. + // + // NOTE: FlatMap rounds its group count up to a power of two, so for a + // fixed n the achievable load factors form a geometric ladder + // (n / (15 * 2^k - 1) for integer k) and the request is quantized to the + // next rung at or below the target. At n = 2^16 this means a 0.70 target + // and the default reserve(n) geometry coincide at an actual load factor + // of 0.533, and a 0.50 target lands at 0.267 (a table twice as large). + // The benchmarks below export the realized load factor and bucket count + // as counters; compare those, not the nominal targets. const axom::IndexType rehash_count = static_cast(std::ceil((n * max_lf) / lf)); map.rehash(rehash_count); @@ -415,6 +424,11 @@ void BM_FlatMap_Find_Hit_TargetLoad(benchmark::State& state, double target_load_ const MapType map = make_filled_flatmap_with_target_load_factor(pairs, target_load_factor); + // Export the geometry actually realized after power-of-two rounding so + // that runs with different nominal targets can be compared meaningfully. + state.counters["load_factor"] = map.load_factor(); + state.counters["buckets"] = static_cast(map.bucket_count()); + for(auto _ : state) { ValueType sum = 0; From 1935fe63ebaa6b9c756f80088e146058377ad34b Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 11 Jun 2026 05:15:00 +0000 Subject: [PATCH 446/986] FlatTable: honor visitor early-exit in scalar visitHashBucket The SSE2 path of GroupBucket::visitHashBucket() stops visiting as soon as the visitor returns false, but the scalar fallback (including GPU path) ignored the return value and kept scanning all 15 slots. In-tree visitors and the duplicate check in the batched insert path return false to mean 'stop', and extra visits load and compare a key which could incur a cache miss per probe group. --- src/axom/core/detail/FlatTable.hpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/axom/core/detail/FlatTable.hpp b/src/axom/core/detail/FlatTable.hpp index 846960597f..45276cdf8f 100644 --- a/src/axom/core/detail/FlatTable.hpp +++ b/src/axom/core/detail/FlatTable.hpp @@ -201,7 +201,11 @@ struct GroupBucket { if(metadata.buckets[i] == reducedHash) { - visitor(i); + if(!visitor(i)) + { + // Found a match - stop visiting, mirroring the SSE2 path above. + break; + } } } #endif From cb5793a21c4bd10331a76d6a9572476f95ff95cf Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 11 Jun 2026 11:16:57 -0700 Subject: [PATCH 447/986] Fixes hip build via missing AXOM_HOST_DEVICE --- src/axom/core/tests/core_flatmap.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/axom/core/tests/core_flatmap.hpp b/src/axom/core/tests/core_flatmap.hpp index 4e3a2f4506..69241c2c2a 100644 --- a/src/axom/core/tests/core_flatmap.hpp +++ b/src/axom/core/tests/core_flatmap.hpp @@ -163,7 +163,7 @@ struct DegenerateGroupHash { using argument_type = int; using result_type = std::uint64_t; - std::uint64_t operator()(int key) const + AXOM_HOST_DEVICE std::uint64_t operator()(int key) const { return static_cast(static_cast(key) & 0xFF); } From 6691dbbebad57650c76089acbdf287f702f5f9a8 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 11 Jun 2026 12:06:08 -0700 Subject: [PATCH 448/986] FlatMap: Fuse the find and empty-slot probes in getEmplacePos() Emplacing a new key walked the probe sequence twice -- first to check for a key and then to find an empty slot within the key. We now do both within a single call. --- src/axom/core/FlatMap.hpp | 20 +++-- src/axom/core/detail/FlatTable.hpp | 83 ++++++++++++++++++++ src/axom/core/tests/core_flatmap.hpp | 108 +++++++++++++++++++++++++++ 3 files changed, 205 insertions(+), 6 deletions(-) diff --git a/src/axom/core/FlatMap.hpp b/src/axom/core/FlatMap.hpp index 087eb75810..c51d246414 100644 --- a/src/axom/core/FlatMap.hpp +++ b/src/axom/core/FlatMap.hpp @@ -897,9 +897,18 @@ auto FlatMap::getEmplacePos(const KeyType& key) { auto hash = Hash {}(key); - // If the key already exists, return the existing iterator. - // Reuse the hash computed above rather than re-hashing inside find(). - iterator existing_elem = this->find_with_hash(key, hash); + // Single fused probe: visit key matches and locate the insertion slot in a single pass + iterator existing_elem = this->end(); + IndexType newBucket = + this->probeEmplaceIndex(m_numGroups2, m_metadata, hash, [&](IndexType bucket_index) -> bool { + if(this->m_buckets[bucket_index].get().first == key) + { + existing_elem = iterator(this, bucket_index); + return false; + } + return true; + }); + if(existing_elem != this->end()) { return {existing_elem, false}; @@ -916,11 +925,10 @@ auto FlatMap::getEmplacePos(const KeyType& key) { IndexType newNumGroups = m_metadata.size() * 2; rehash(newNumGroups * BucketsPerGroup - 1); + // The table was rebuilt, so the slot is stale. If we got here, the key is missing + newBucket = this->probeEmptyIndex(m_numGroups2, m_metadata, hash); } - // Get an empty index to place the element into. - IndexType newBucket = this->probeEmptyIndex(m_numGroups2, m_metadata, hash); - // Add a hash to the corresponding bucket slot. this->setBucketHash(m_metadata, newBucket, hash); m_size++; diff --git a/src/axom/core/detail/FlatTable.hpp b/src/axom/core/detail/FlatTable.hpp index 45276cdf8f..2a9307bd73 100644 --- a/src/axom/core/detail/FlatTable.hpp +++ b/src/axom/core/detail/FlatTable.hpp @@ -394,6 +394,89 @@ struct SequentialLookupPolicy : ProbePolicy return NO_MATCH; } + /*! + * \brief Fused find-or-locate-empty probe for single-key emplacement. + * + * Walks the probe sequence once, simultaneously visiting key matches and + * tracking the first empty slot. Overflow bits are maintained in the + * same way as probeEmptyIndex(): a full group that hasn't yielded an insertion + * slot is marked overflowed for this hash before moving on. + * + * \param [in] ngroups_pow_2 the number of groups, expressed as a power of 2 + * \param [in] metadata the array of metadata for the groups in the hash map + * \param [in] hash the hash to search for and, if absent, insert + * \param [in] on_hash_found functor called for each matching bucket slot; + * returns false to stop the probe (existing key found) + * + * \return the bucket index to insert into, or NO_MATCH if the visitor stopped the probe + */ + template + IndexType probeEmplaceIndex(int ngroups_pow_2, + ArrayView metadata, + HashType hash, + FoundIndex&& on_hash_found) const + { + const int bitshift_right = ((CHAR_BIT * sizeof(HashType)) - ngroups_pow_2); + const HashType group_mask = (HashType {1} << ngroups_pow_2) - 1; + HashType curr_group = (hash >> bitshift_right) & group_mask; + int empty_group = NO_MATCH; + int empty_bucket = NO_MATCH; + + std::uint8_t hash_8 = static_cast(hash); + bool may_exist = true; + for(int iteration = 0; iteration < metadata.size(); ++iteration) + { + if(may_exist) + { + // The key may be in the current group, so scan for matches just as probeIndex() does + bool keep_going = true; + metadata[curr_group].visitHashBucket(hash_8, [&](IndexType bucket_index) -> bool { + keep_going = on_hash_found(curr_group * GroupBucket::Size + bucket_index); + return keep_going; + }); + if(!keep_going) + { + // Visitor stopped the probe: the key already exists + return NO_MATCH; + } + } + + if(empty_group == NO_MATCH) + { + int tentative_empty_bucket = metadata[curr_group].getEmptyBucket(); + if(tentative_empty_bucket != GroupBucket::InvalidSlot) + { + empty_group = curr_group; + empty_bucket = tentative_empty_bucket; + } + } + + if(!metadata[curr_group].getMaybeOverflowed(hash_8)) + { + // The key cannot exist past this group + may_exist = false; + if(empty_group != NO_MATCH) + { + break; + } + // Full group at the end of the trail, mark as overflowed and keep looking for an empty slot + metadata[curr_group].setOverflow(hash_8); + } + else if(empty_group == NO_MATCH) + { + // Full group inside the trail + metadata[curr_group].setOverflow(hash_8); + } + // The group count is a power of two, so we can use a bitmask (instead of a modulo) + curr_group = (curr_group + this->getNext(iteration)) & group_mask; + } + if(empty_group != NO_MATCH) + { + return empty_group * GroupBucket::Size + empty_bucket; + } + return NO_MATCH; + } + /*! * \brief Finds the next potential bucket index for a given hash in a group * array for an open-addressing hash map. diff --git a/src/axom/core/tests/core_flatmap.hpp b/src/axom/core/tests/core_flatmap.hpp index 69241c2c2a..1d134550c4 100644 --- a/src/axom/core/tests/core_flatmap.hpp +++ b/src/axom/core/tests/core_flatmap.hpp @@ -228,6 +228,114 @@ TEST(core_flatmap_unit, cross_group_probe_chains) } } +// Hash functor that maps every key to the same hash value +// This forces long probe chains and stresses fused "find + empty-slot" probing +struct ConstantHash64 +{ + using argument_type = int; + using result_type = std::uint64_t; + + AXOM_HOST_DEVICE std::uint64_t operator()(int) const { return std::uint64_t {0}; } +}; + +TEST(core_flatmap_unit, fused_emplace_probe_no_duplicate_across_tombstone) +{ + using MapType = axom::FlatMap; + MapType test_map; + + const int NUM_ELEMS = 40; + for(int i = 0; i < NUM_ELEMS; i++) + { + test_map.insert_or_assign(i, i * 10); + } + ASSERT_EQ(test_map.size(), NUM_ELEMS); + + // Create a tombstone early in the probe trail + EXPECT_EQ(test_map.erase(1), 1); + ASSERT_EQ(test_map.size(), NUM_ELEMS - 1); + + // Update a key that should live beyond the first group. The fused emplace probe + // must keep probing past the earlier empty slot and find the existing key. + const int existing_key = NUM_ELEMS - 1; + auto result = test_map.insert_or_assign(existing_key, 12345); + EXPECT_FALSE(result.second); + EXPECT_EQ(test_map.size(), NUM_ELEMS - 1); + EXPECT_EQ(test_map.at(existing_key), 12345); + + // Verify we did not accidentally insert a duplicate key + // (which would be possible if the probe stopped at the tombstone) + int occurrences = 0; + for(const auto& kv : test_map) + { + occurrences += (kv.first == existing_key) ? 1 : 0; + } + EXPECT_EQ(occurrences, 1); +} + +TEST(core_flatmap_unit, fused_emplace_probe_try_emplace_respects_existing_after_tombstone) +{ + using MapType = axom::FlatMap; + MapType test_map; + + const int NUM_ELEMS = 40; + for(int i = 0; i < NUM_ELEMS; i++) + { + test_map.insert_or_assign(i, i * 10); + } + ASSERT_EQ(test_map.size(), NUM_ELEMS); + + // Create a tombstone early in the probe trail + EXPECT_EQ(test_map.erase(2), 1); + ASSERT_EQ(test_map.size(), NUM_ELEMS - 1); + + const int existing_key = NUM_ELEMS - 2; + test_map.insert_or_assign(existing_key, 777); + ASSERT_EQ(test_map.at(existing_key), 777); + + // try_emplace must not insert or overwrite when the key exists + auto emplace_res = test_map.try_emplace(existing_key, 999); + EXPECT_FALSE(emplace_res.second); + EXPECT_EQ(emplace_res.first->second, 777); + + int occurrences = 0; + for(const auto& kv : test_map) + { + occurrences += (kv.first == existing_key) ? 1 : 0; + } + EXPECT_EQ(occurrences, 1); +} + +TEST(core_flatmap_unit, fused_emplace_probe_recomputes_slot_after_rehash) +{ + using MapType = axom::FlatMap; + MapType test_map; + + const int init_buckets = test_map.bucket_count(); + const int size_no_rehash = static_cast(test_map.max_load_factor() * init_buckets); + + // Fill right up to the no-rehash threshold. + for(int i = 0; i < size_no_rehash; i++) + { + test_map.insert_or_assign(i, i); + } + ASSERT_EQ(test_map.bucket_count(), init_buckets); + ASSERT_EQ(test_map.size(), size_no_rehash); + + // Create a mid-sequence tombstone. With ConstantHash64 and a full trail, this should + // preserve loadCount, so the next insertion triggers a rehash even though an empty slot exists. + EXPECT_EQ(test_map.erase(0), 1); + ASSERT_EQ(test_map.bucket_count(), init_buckets); + ASSERT_EQ(test_map.size(), size_no_rehash - 1); + + const int buckets_before = test_map.bucket_count(); + const int new_key = 100000; + test_map.insert_or_assign(new_key, 42); + + EXPECT_GT(test_map.bucket_count(), buckets_before); + EXPECT_EQ(test_map.at(new_key), 42); + EXPECT_EQ(test_map.count(0), 0); +} + AXOM_TYPED_TEST(core_flatmap, default_init) { using MapType = typename TestFixture::MapType; From 6d8a86f394d461704eebd4d488c50687e2d058e3 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 11 Jun 2026 13:32:25 -0700 Subject: [PATCH 449/986] FlatMap: Keep move semantics during batch insertion --- src/axom/core/FlatMapUtil.hpp | 8 +++-- src/axom/core/tests/core_flatmap.hpp | 48 ++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/src/axom/core/FlatMapUtil.hpp b/src/axom/core/FlatMapUtil.hpp index d1adb18f90..4eb1c41a05 100644 --- a/src/axom/core/FlatMapUtil.hpp +++ b/src/axom/core/FlatMapUtil.hpp @@ -278,8 +278,12 @@ void FlatMap::insert(InputIt kv_begin, InputIt kv_end) for(IndexType idx = 0; idx < num_elems; ++idx) { - auto kv = *(kv_begin + idx); - this->insert_or_assign(kv.first, kv.second); + // Preserve the value category of the input pair. In particular, when + // kv_begin/kv_end are move iterators, we must forward the mapped value + // so move-only types remain supported. + decltype(auto) kv = *(kv_begin + idx); + this->insert_or_assign(std::forward(kv).first, + std::forward(kv).second); } return; } diff --git a/src/axom/core/tests/core_flatmap.hpp b/src/axom/core/tests/core_flatmap.hpp index 1d134550c4..021bcd741d 100644 --- a/src/axom/core/tests/core_flatmap.hpp +++ b/src/axom/core/tests/core_flatmap.hpp @@ -13,6 +13,9 @@ // gtest includes #include "gtest/gtest.h" +// C++ includes +#include + // Unit test for QuadraticProbing TEST(core_flatmap_unit, quadratic_probing) { @@ -656,6 +659,51 @@ TEST(core_flatmap_moveonly, init_and_move_moveonly) } } +TEST(core_flatmap_moveonly, insert_batched_seq_move_iterators) +{ + using MapType = axom::FlatMap>; + MapType test_map; + + using PairType = std::pair>; + std::vector pairs; + + const int NUM_ELEMS = 64; + pairs.reserve(NUM_ELEMS + 1); + for(int i = 0; i < NUM_ELEMS; i++) + { + pairs.emplace_back(i, std::make_unique(i + 1.0)); + } + + // Include a duplicate so "later duplicates overwrite earlier ones" is exercised, + // while also ensuring the value is moved in all cases. + pairs.emplace_back(NUM_ELEMS / 2, std::make_unique(123.0)); + + test_map.template insert(std::make_move_iterator(pairs.begin()), + std::make_move_iterator(pairs.end())); + + EXPECT_EQ(test_map.size(), NUM_ELEMS); + for(int i = 0; i < NUM_ELEMS; i++) + { + ASSERT_EQ(test_map.count(i), 1); + auto& ptr = test_map.at(i); + ASSERT_NE(ptr.get(), nullptr); + if(i == NUM_ELEMS / 2) + { + EXPECT_EQ(*ptr, 123.0); + } + else + { + EXPECT_EQ(*ptr, i + 1.0); + } + } + + // All source values should have been moved-from. + for(const auto& kv : pairs) + { + EXPECT_EQ(kv.second.get(), nullptr); + } +} + AXOM_TYPED_TEST(core_flatmap, init_and_copy) { using MapType = typename TestFixture::MapType; From f083e50a927eb417235f0f5cee90ef3d16f19e39 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 11 Jun 2026 13:47:37 -0700 Subject: [PATCH 450/986] Improves FlatMap benchmark * Disables sequential find_hit search by default since it is not representative. * Guards several tests by the feature they are testing --- .../core/tests/core_benchmark_flatmap.cpp | 51 ++++++++++++------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/src/axom/core/tests/core_benchmark_flatmap.cpp b/src/axom/core/tests/core_benchmark_flatmap.cpp index a06d9be21a..2f013e35df 100644 --- a/src/axom/core/tests/core_benchmark_flatmap.cpp +++ b/src/axom/core/tests/core_benchmark_flatmap.cpp @@ -72,6 +72,7 @@ inline FlatMapFeatureBenchmarks operator&(FlatMapFeatureBenchmarks lhs, FlatMapF std::vector args_benchmark_sizes; FlatMapFeatureBenchmarks args_benchmark_features {FlatMapFeatureBenchmarks::None}; int args_batch_size = 1 << 10; +bool args_include_insertion_order_lookup = false; } // namespace template <> @@ -162,9 +163,9 @@ std::vector make_miss_keys(const std::vector& keys, KeyType of /*! * \brief Returns a copy of \a keys reshuffled with an independent seed. * - * Looking keys up in the exact order they were inserted is rarely representative, - * and it systematically favors node-based containers: with libstdc++'s identity hash - * for integers and densely numbered keys, the i-th lookup touches the i-th allocated node, + * Looking keys up in the exact order they were inserted is rarely representative, + * and it systematically favors node-based containers: with libstdc++'s identity hash + * for integers and densely numbered keys, the i-th lookup touches the i-th allocated node, * so the lookup loop streams through the heap nearly sequentially and the hardware prefetcher hides * most of the pointer-chasing latency. An independently shuffled lookup order removes that correlation. */ @@ -423,6 +424,7 @@ void BM_FlatMap_Find_Hit_TargetLoad(benchmark::State& state, double target_load_ const auto pairs = make_pairs(keys); const MapType map = make_filled_flatmap_with_target_load_factor(pairs, target_load_factor); + const auto lookup_keys = make_lookup_order(keys, 0xF00DBA11ULL); // Export the geometry actually realized after power-of-two rounding so // that runs with different nominal targets can be compared meaningfully. @@ -432,7 +434,7 @@ void BM_FlatMap_Find_Hit_TargetLoad(benchmark::State& state, double target_load_ for(auto _ : state) { ValueType sum = 0; - for(KeyType k : keys) + for(KeyType k : lookup_keys) { auto it = map.find(k); if(it != map.end()) @@ -572,7 +574,10 @@ void RegisterBenchmarksFor(const std::string& map_name) if((::args_benchmark_features & FlatMapFeatureBenchmarks::Lookup) != FlatMapFeatureBenchmarks::None) { - benchmark::RegisterBenchmark(name("find_hit"), &BM_Find_Hit)->Apply(CustomArgs); + if(::args_include_insertion_order_lookup) + { + benchmark::RegisterBenchmark(name("find_hit"), &BM_Find_Hit)->Apply(CustomArgs); + } benchmark::RegisterBenchmark(name("find_hit_shuffled"), &BM_Find_Hit_Shuffled)->Apply(CustomArgs); benchmark::RegisterBenchmark(name("find_hit_randkeys"), &BM_Find_Hit_RandomKeys)->Apply(CustomArgs); benchmark::RegisterBenchmark(name("find_miss"), &BM_Find_Miss)->Apply(CustomArgs); @@ -603,6 +608,7 @@ int main(int argc, char* argv[]) std::vector local_test_sizes; FlatMapFeatureBenchmarks local_benchmark_features {FlatMapFeatureBenchmarks::None}; int local_batch_size = ::args_batch_size; + bool local_include_insertion_order_lookup = ::args_include_insertion_order_lookup; axom::CLI::App app {"Axom FlatMap benchmarks"}; app.add_option("-s,--custom_sizes", local_test_sizes) @@ -632,6 +638,9 @@ int main(int argc, char* argv[]) ->default_val(local_batch_size) ->check(axom::CLI::PositiveNumber); + app.add_flag("--include_insertion_order_lookup", local_include_insertion_order_lookup) + ->description("Includes insertion-order lookup benchmark (biased; for diagnosis)"); + std::vector feature_strings; auto feature_opt = app.add_option("-f,--features", feature_strings) @@ -673,11 +682,14 @@ int main(int argc, char* argv[]) std::swap(::args_benchmark_sizes, local_test_sizes); ::args_batch_size = local_batch_size; + ::args_include_insertion_order_lookup = local_include_insertion_order_lookup; SLIC_INFO("Parsed and processed command line arguments:"); SLIC_INFO(axom::fmt::format("- Map sizes: {}", axom::fmt::join(::args_benchmark_sizes, ","))); SLIC_INFO(axom::fmt::format("- Batch size: {}", ::args_batch_size)); SLIC_INFO(axom::fmt::format("- Map features to test: {}", ::args_benchmark_features)); + SLIC_INFO(axom::fmt::format("- Include insertion-order lookup: {}", + ::args_include_insertion_order_lookup ? "true" : "false")); } RegisterBenchmarksFor>("axom::FlatMap"); @@ -687,19 +699,22 @@ int main(int argc, char* argv[]) // Explore the impact of lower load factors on successful lookups. // This trades memory for potentially fewer probes and fewer cache misses. - using DefaultHash = axom::FlatMap::hasher; - benchmark::RegisterBenchmark("axom::FlatMap::find_hit_lf0p50", [](benchmark::State& st) { - BM_FlatMap_Find_Hit_TargetLoad(st, 0.50); - })->Apply(CustomArgs); - benchmark::RegisterBenchmark("axom::FlatMap::find_hit_lf0p70", [](benchmark::State& st) { - BM_FlatMap_Find_Hit_TargetLoad(st, 0.70); - })->Apply(CustomArgs); - benchmark::RegisterBenchmark("axom::FlatMapFastHash::find_hit_lf0p50", [](benchmark::State& st) { - BM_FlatMap_Find_Hit_TargetLoad(st, 0.50); - })->Apply(CustomArgs); - benchmark::RegisterBenchmark("axom::FlatMapFastHash::find_hit_lf0p70", [](benchmark::State& st) { - BM_FlatMap_Find_Hit_TargetLoad(st, 0.70); - })->Apply(CustomArgs); + if((::args_benchmark_features & FlatMapFeatureBenchmarks::Lookup) != FlatMapFeatureBenchmarks::None) + { + using DefaultHash = axom::FlatMap::hasher; + benchmark::RegisterBenchmark("axom::FlatMap::find_hit_lf0p50", [](benchmark::State& st) { + BM_FlatMap_Find_Hit_TargetLoad(st, 0.50); + })->Apply(CustomArgs); + benchmark::RegisterBenchmark("axom::FlatMap::find_hit_lf0p70", [](benchmark::State& st) { + BM_FlatMap_Find_Hit_TargetLoad(st, 0.70); + })->Apply(CustomArgs); + benchmark::RegisterBenchmark("axom::FlatMapFastHash::find_hit_lf0p50", [](benchmark::State& st) { + BM_FlatMap_Find_Hit_TargetLoad(st, 0.50); + })->Apply(CustomArgs); + benchmark::RegisterBenchmark("axom::FlatMapFastHash::find_hit_lf0p70", [](benchmark::State& st) { + BM_FlatMap_Find_Hit_TargetLoad(st, 0.70); + })->Apply(CustomArgs); + } RegisterBenchmarksFor>("std::unordered_map"); RegisterBenchmarksFor>("std::map"); From e10cd0260e78bb3196d5a525c8465ad3f1da5dd0 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 11 Jun 2026 14:00:47 -0700 Subject: [PATCH 451/986] FlatMap: Device hash type must be 64 bits Also adds more device hashing tests --- src/axom/core/tests/core_device_hash.hpp | 131 ++++++++++++++++++++--- 1 file changed, 116 insertions(+), 15 deletions(-) diff --git a/src/axom/core/tests/core_device_hash.hpp b/src/axom/core/tests/core_device_hash.hpp index 58173725e1..6cab7708b2 100644 --- a/src/axom/core/tests/core_device_hash.hpp +++ b/src/axom/core/tests/core_device_hash.hpp @@ -12,6 +12,7 @@ #include "gtest/gtest.h" // C++ includes +#include #include template @@ -37,6 +38,7 @@ AXOM_TYPED_TEST(core_device_hash, hash_int) using ExecSpace = typename TestFixture::ExecSpace; axom::DeviceHash device_hasher; + using HashResult = typename decltype(device_hasher)::result_type; constexpr int NUM_HASHES = 4; @@ -44,7 +46,7 @@ AXOM_TYPED_TEST(core_device_hash, hash_int) // Allocate space for hash results. int allocatorID = axom::execution_space::allocatorID(); - axom::IndexType *computed_hashes = axom::allocate(NUM_HASHES, allocatorID); + HashResult* computed_hashes = axom::allocate(NUM_HASHES, allocatorID); // Compute hashes. axom::for_all( @@ -52,8 +54,8 @@ AXOM_TYPED_TEST(core_device_hash, hash_int) AXOM_LAMBDA(int i) { computed_hashes[i] = device_hasher(things_to_hash[i]); }); // Copy back to host. - axom::IndexType computed_hashes_host[NUM_HASHES]; - axom::copy(computed_hashes_host, computed_hashes, sizeof(axom::IndexType) * NUM_HASHES); + HashResult computed_hashes_host[NUM_HASHES]; + axom::copy(computed_hashes_host, computed_hashes, sizeof(HashResult) * NUM_HASHES); axom::deallocate(computed_hashes); for(int i = 0; i < NUM_HASHES; i++) @@ -74,6 +76,7 @@ AXOM_TYPED_TEST(core_device_hash, hash_float) using ExecSpace = typename TestFixture::ExecSpace; axom::DeviceHash device_hasher; + using HashResult = typename decltype(device_hasher)::result_type; constexpr int NUM_HASHES = 4; @@ -81,7 +84,7 @@ AXOM_TYPED_TEST(core_device_hash, hash_float) // Allocate space for hash results. int allocatorID = axom::execution_space::allocatorID(); - axom::IndexType *computed_hashes = axom::allocate(NUM_HASHES, allocatorID); + HashResult* computed_hashes = axom::allocate(NUM_HASHES, allocatorID); // Compute hashes. axom::for_all( @@ -89,8 +92,8 @@ AXOM_TYPED_TEST(core_device_hash, hash_float) AXOM_LAMBDA(int i) { computed_hashes[i] = device_hasher(things_to_hash[i]); }); // Copy back to host. - axom::IndexType computed_hashes_host[NUM_HASHES]; - axom::copy(computed_hashes_host, computed_hashes, sizeof(axom::IndexType) * NUM_HASHES); + HashResult computed_hashes_host[NUM_HASHES]; + axom::copy(computed_hashes_host, computed_hashes, sizeof(HashResult) * NUM_HASHES); axom::deallocate(computed_hashes); for(int i = 0; i < NUM_HASHES; i++) @@ -112,12 +115,13 @@ AXOM_TYPED_TEST(core_device_hash, hash_float) TEST(core_device_hash, hash_string) { axom::DeviceHash device_hasher; + using HashResult = typename decltype(device_hasher)::result_type; constexpr int NUM_HASHES = 4; std::string things_to_hash[NUM_HASHES] {"0", "1", "37", "1100"}; - axom::IndexType computed_hashes[NUM_HASHES]; + HashResult computed_hashes[NUM_HASHES]; // Compute hashes. for(int i = 0; i < NUM_HASHES; i++) @@ -151,6 +155,7 @@ AXOM_TYPED_TEST(core_device_hash, hash_enum) using ExecSpace = typename TestFixture::ExecSpace; axom::DeviceHash device_hasher; + using HashResult = typename decltype(device_hasher)::result_type; constexpr int NUM_HASHES = 4; @@ -161,7 +166,7 @@ AXOM_TYPED_TEST(core_device_hash, hash_enum) // Allocate space for hash results. int allocatorID = axom::execution_space::allocatorID(); - axom::IndexType *computed_hashes = axom::allocate(NUM_HASHES, allocatorID); + HashResult* computed_hashes = axom::allocate(NUM_HASHES, allocatorID); // Compute hashes. axom::for_all( @@ -169,8 +174,8 @@ AXOM_TYPED_TEST(core_device_hash, hash_enum) AXOM_LAMBDA(int i) { computed_hashes[i] = device_hasher(things_to_hash[i]); }); // Copy back to host. - axom::IndexType computed_hashes_host[NUM_HASHES]; - axom::copy(computed_hashes_host, computed_hashes, sizeof(axom::IndexType) * NUM_HASHES); + HashResult computed_hashes_host[NUM_HASHES]; + axom::copy(computed_hashes_host, computed_hashes, sizeof(HashResult) * NUM_HASHES); axom::deallocate(computed_hashes); for(int i = 0; i < NUM_HASHES; i++) @@ -210,7 +215,7 @@ struct DeviceHash> constexpr int NWORDS = sizeof(axom_testing::UserVector) / sizeof(int); alignas(axom_testing::UserVector) int bytes[NWORDS]; // NOTE: Separating these statements fixes a warning about strict-aliasing. - auto ptr = reinterpret_cast *>(bytes); + auto ptr = reinterpret_cast*>(bytes); *ptr = value; axom::IndexType hash_result {}; @@ -228,6 +233,7 @@ AXOM_TYPED_TEST(core_device_hash, hash_user_defined) using ExecSpace = typename TestFixture::ExecSpace; axom::DeviceHash> device_hasher; + using HashResult = typename decltype(device_hasher)::result_type; constexpr int NUM_HASHES = 4; @@ -238,7 +244,7 @@ AXOM_TYPED_TEST(core_device_hash, hash_user_defined) // Allocate space for hash results. int allocatorID = axom::execution_space::allocatorID(); - axom::IndexType *computed_hashes = axom::allocate(NUM_HASHES, allocatorID); + HashResult* computed_hashes = axom::allocate(NUM_HASHES, allocatorID); // Compute hashes. axom::for_all( @@ -246,8 +252,8 @@ AXOM_TYPED_TEST(core_device_hash, hash_user_defined) AXOM_LAMBDA(int i) { computed_hashes[i] = device_hasher(things_to_hash[i]); }); // Copy back to host. - axom::IndexType computed_hashes_host[NUM_HASHES]; - axom::copy(computed_hashes_host, computed_hashes, sizeof(axom::IndexType) * NUM_HASHES); + HashResult computed_hashes_host[NUM_HASHES]; + axom::copy(computed_hashes_host, computed_hashes, sizeof(HashResult) * NUM_HASHES); axom::deallocate(computed_hashes); for(int i = 0; i < NUM_HASHES; i++) @@ -263,6 +269,101 @@ AXOM_TYPED_TEST(core_device_hash, hash_user_defined) } } +AXOM_TYPED_TEST(core_device_hash, hash_uint64_distinguishes_high_bits) +{ + using ExecSpace = typename TestFixture::ExecSpace; + + axom::DeviceHash device_hasher; + using HashResult = typename decltype(device_hasher)::result_type; + + constexpr int NUM_HASHES = 3; + std::uint64_t things_to_hash[NUM_HASHES] = {std::uint64_t {1}, + std::uint64_t {1} + (std::uint64_t {1} << 32), + std::uint64_t {1} + (std::uint64_t {1} << 33)}; + + int allocatorID = axom::execution_space::allocatorID(); + HashResult* computed_hashes = axom::allocate(NUM_HASHES, allocatorID); + + axom::for_all( + NUM_HASHES, + AXOM_LAMBDA(int i) { computed_hashes[i] = device_hasher(things_to_hash[i]); }); + + HashResult computed_hashes_host[NUM_HASHES]; + axom::copy(computed_hashes_host, computed_hashes, sizeof(HashResult) * NUM_HASHES); + axom::deallocate(computed_hashes); + + EXPECT_NE(computed_hashes_host[0], computed_hashes_host[1]); + EXPECT_NE(computed_hashes_host[0], computed_hashes_host[2]); + EXPECT_NE(computed_hashes_host[1], computed_hashes_host[2]); +} + +AXOM_TYPED_TEST(core_device_hash, hash_fractional_float_device) +{ + using ExecSpace = typename TestFixture::ExecSpace; + + axom::DeviceHash device_hasher; + using HashResult = typename decltype(device_hasher)::result_type; + + constexpr int NUM_HASHES = 8; + float things_to_hash[NUM_HASHES] = {0.25f, 0.75f, -0.5f, 0.5f, 0.125f, 0.625f, 0.875f, 1.25f}; + + int allocatorID = axom::execution_space::allocatorID(); + HashResult* computed_hashes = axom::allocate(NUM_HASHES, allocatorID); + + axom::for_all( + NUM_HASHES, + AXOM_LAMBDA(int i) { computed_hashes[i] = device_hasher(things_to_hash[i]); }); + + HashResult computed_hashes_host[NUM_HASHES]; + axom::copy(computed_hashes_host, computed_hashes, sizeof(HashResult) * NUM_HASHES); + axom::deallocate(computed_hashes); + + // Idempotence and pairwise distinctness for these chosen values. + for(int i = 0; i < NUM_HASHES; i++) + { + EXPECT_EQ(computed_hashes_host[i], device_hasher(things_to_hash[i])); + for(int j = i + 1; j < NUM_HASHES; j++) + { + EXPECT_NE(computed_hashes_host[i], computed_hashes_host[j]); + } + } + + EXPECT_EQ(device_hasher(0.0f), device_hasher(-0.0f)); +} + +AXOM_TYPED_TEST(core_device_hash, hash_fractional_double_device) +{ + using ExecSpace = typename TestFixture::ExecSpace; + + axom::DeviceHash device_hasher; + using HashResult = typename decltype(device_hasher)::result_type; + + constexpr int NUM_HASHES = 8; + double things_to_hash[NUM_HASHES] = {0.25, 0.75, -0.5, 0.5, 0.125, 0.625, 0.875, 1.25}; + + int allocatorID = axom::execution_space::allocatorID(); + HashResult* computed_hashes = axom::allocate(NUM_HASHES, allocatorID); + + axom::for_all( + NUM_HASHES, + AXOM_LAMBDA(int i) { computed_hashes[i] = device_hasher(things_to_hash[i]); }); + + HashResult computed_hashes_host[NUM_HASHES]; + axom::copy(computed_hashes_host, computed_hashes, sizeof(HashResult) * NUM_HASHES); + axom::deallocate(computed_hashes); + + for(int i = 0; i < NUM_HASHES; i++) + { + EXPECT_EQ(computed_hashes_host[i], device_hasher(things_to_hash[i])); + for(int j = i + 1; j < NUM_HASHES; j++) + { + EXPECT_NE(computed_hashes_host[i], computed_hashes_host[j]); + } + } + + EXPECT_EQ(device_hasher(0.0), device_hasher(-0.0)); +} + TEST(core_device_hash, hash_width_decoupled_from_indextype) { // The hash result must be 64 bits wide regardless of the configured @@ -277,7 +378,7 @@ TEST(core_device_hash, hash_width_decoupled_from_indextype) "integral hash result must be std::uint64_t"); static_assert(std::is_same::result_type, std::uint64_t>::value, "floating-point hash result must be std::uint64_t"); - static_assert(std::is_same::result_type, std::uint64_t>::value, + static_assert(std::is_same::result_type, std::uint64_t>::value, "pointer hash result must be std::uint64_t"); static_assert(std::is_same::result_type, std::uint64_t>::value, "catch-all (std::hash) result must be std::uint64_t"); From e95e7df5271ebe6cb8d45e8f1288b5a6a67811fe Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 11 Jun 2026 14:23:00 -0700 Subject: [PATCH 452/986] Moves AXOM_FORCE_INLINE to core's Macros.hpp --- src/axom/core/FlatMap.hpp | 9 ++++----- src/axom/core/Macros.hpp | 18 ++++++++++++++++++ src/axom/core/detail/FlatTable.hpp | 23 ++++++----------------- 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/src/axom/core/FlatMap.hpp b/src/axom/core/FlatMap.hpp index c51d246414..8f92292ab4 100644 --- a/src/axom/core/FlatMap.hpp +++ b/src/axom/core/FlatMap.hpp @@ -290,11 +290,10 @@ class FlatMap : detail::flat_map::SequentialLookupPolicy #endif -// Force-inline annotation for the FlatMap/FlatTable lookup hot path. -#if defined(__CUDACC__) || defined(__HIPCC__) - #define AXOM_FLATMAP_FORCE_INLINE __forceinline__ -#elif defined(__GNUC__) || defined(__clang__) - #define AXOM_FLATMAP_FORCE_INLINE inline __attribute__((always_inline)) -#elif defined(_MSC_VER) - #define AXOM_FLATMAP_FORCE_INLINE __forceinline -#else - #define AXOM_FLATMAP_FORCE_INLINE inline -#endif - namespace axom { namespace detail @@ -173,7 +162,7 @@ struct GroupBucket } template - AXOM_FLATMAP_FORCE_INLINE AXOM_HOST_DEVICE int visitHashBucket(std::uint8_t hash, Func&& visitor) const + AXOM_FORCE_INLINE AXOM_HOST_DEVICE int visitHashBucket(std::uint8_t hash, Func&& visitor) const { std::uint8_t reducedHash = reduceHash(hash); #if !defined(AXOM_DEVICE_CODE) && defined(_AXOM_CORE_HAVE_SSE2) @@ -288,7 +277,7 @@ struct GroupBucket } template - AXOM_FLATMAP_FORCE_INLINE AXOM_HOST_DEVICE bool getMaybeOverflowed(std::uint8_t hash) const + AXOM_FORCE_INLINE AXOM_HOST_DEVICE bool getMaybeOverflowed(std::uint8_t hash) const { std::uint8_t hashOfwBit = 1 << (hash % 8); std::uint8_t curr_ofw; @@ -488,10 +477,10 @@ struct SequentialLookupPolicy : ProbePolicy * matching hash */ template - AXOM_FLATMAP_FORCE_INLINE AXOM_HOST_DEVICE void probeIndex(int ngroups_pow_2, - ArrayView metadata, - HashType hash, - FoundIndex&& on_hash_found) const + AXOM_FORCE_INLINE AXOM_HOST_DEVICE void probeIndex(int ngroups_pow_2, + ArrayView metadata, + HashType hash, + FoundIndex&& on_hash_found) const { // We use the k MSBs of the hash as the initial group probe point, // where ngroups = 2^k. Since the group count is always a power of two, From 4f34f61d46bf1c3356bb24201d7d3a1d7cd8f432 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 11 Jun 2026 14:32:35 -0700 Subject: [PATCH 453/986] Adds utility function for initializing initial probe group via bitshift and masking --- src/axom/core/FlatMapUtil.hpp | 12 ++++----- src/axom/core/detail/FlatTable.hpp | 41 +++++++++++++++++++----------- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/src/axom/core/FlatMapUtil.hpp b/src/axom/core/FlatMapUtil.hpp index 4eb1c41a05..da0b363631 100644 --- a/src/axom/core/FlatMapUtil.hpp +++ b/src/axom/core/FlatMapUtil.hpp @@ -359,11 +359,11 @@ void FlatMap::insert(InputIt kv_begin, InputIt kv_end) // Hash keys. auto hash = Hash {}(key); - // We use the k MSBs of the hash as the initial group probe point, - // where ngroups = 2^k. - int bitshift_right = ((CHAR_BIT * sizeof(HashResult)) - ngroups_pow_2); - HashResult curr_group = hash >> bitshift_right; - curr_group &= ((1 << ngroups_pow_2) - 1); + // We use the k MSBs of the hash as the initial group probe point, where ngroups = 2^k. + const auto init = + detail::flat_map::SequentialLookupPolicy::initGroupProbe(hash, ngroups_pow_2); + const HashResult group_mask = init.group_mask; + HashResult curr_group = init.curr_group; std::uint8_t hash_8 = static_cast(hash); @@ -469,7 +469,7 @@ void FlatMap::insert(InputIt kv_begin, InputIt kv_end) else { // Move to next group. - curr_group = (curr_group + LookupPolicy {}.getNext(iteration)) % meta_group.size(); + curr_group = (curr_group + LookupPolicy {}.getNext(iteration)) & group_mask; iteration++; } } diff --git a/src/axom/core/detail/FlatTable.hpp b/src/axom/core/detail/FlatTable.hpp index dafef72b9f..c033ef7795 100644 --- a/src/axom/core/detail/FlatTable.hpp +++ b/src/axom/core/detail/FlatTable.hpp @@ -333,6 +333,21 @@ struct SequentialLookupPolicy : ProbePolicy { constexpr static int NO_MATCH = -1; + struct GroupProbeInit + { + HashType group_mask; + HashType curr_group; + }; + + AXOM_FORCE_INLINE AXOM_HOST_DEVICE static GroupProbeInit initGroupProbe(HashType hash, + int ngroups_pow_2) + { + const int bitshift_right = (CHAR_BIT * sizeof(HashType)) - ngroups_pow_2; + const HashType group_mask = (HashType {1} << ngroups_pow_2) - 1; + const HashType curr_group = (hash >> bitshift_right) & group_mask; + return {group_mask, curr_group}; + } + /*! * \brief Inserts a hash into the first empty bucket in an array of groups * for an open-addressing hash map. @@ -343,12 +358,10 @@ struct SequentialLookupPolicy : ProbePolicy */ IndexType probeEmptyIndex(int ngroups_pow_2, ArrayView metadata, HashType hash) const { - // We use the k MSBs of the hash as the initial group probe point, - // where ngroups = 2^k. Since the group count is always a power of two, - // wrapping a group index is a bitwise AND with this mask. - const int bitshift_right = ((CHAR_BIT * sizeof(HashType)) - ngroups_pow_2); - const HashType group_mask = (HashType {1} << ngroups_pow_2) - 1; - HashType curr_group = (hash >> bitshift_right) & group_mask; + // We use the k MSBs of the hash as the initial group probe point, where ngroups = 2^k. + const auto init = initGroupProbe(hash, ngroups_pow_2); + const HashType group_mask = init.group_mask; + HashType curr_group = init.curr_group; int empty_group = NO_MATCH; int empty_bucket = NO_MATCH; @@ -405,9 +418,9 @@ struct SequentialLookupPolicy : ProbePolicy HashType hash, FoundIndex&& on_hash_found) const { - const int bitshift_right = ((CHAR_BIT * sizeof(HashType)) - ngroups_pow_2); - const HashType group_mask = (HashType {1} << ngroups_pow_2) - 1; - HashType curr_group = (hash >> bitshift_right) & group_mask; + const auto init = initGroupProbe(hash, ngroups_pow_2); + const HashType group_mask = init.group_mask; + HashType curr_group = init.curr_group; int empty_group = NO_MATCH; int empty_bucket = NO_MATCH; @@ -482,12 +495,10 @@ struct SequentialLookupPolicy : ProbePolicy HashType hash, FoundIndex&& on_hash_found) const { - // We use the k MSBs of the hash as the initial group probe point, - // where ngroups = 2^k. Since the group count is always a power of two, - // wrapping a group index is a bitwise AND with this mask. - const int bitshift_right = ((CHAR_BIT * sizeof(HashType)) - ngroups_pow_2); - const HashType group_mask = (HashType {1} << ngroups_pow_2) - 1; - HashType curr_group = (hash >> bitshift_right) & group_mask; + // We use the k MSBs of the hash as the initial group probe point, where ngroups = 2^k. + const auto init = initGroupProbe(hash, ngroups_pow_2); + const HashType group_mask = init.group_mask; + HashType curr_group = init.curr_group; std::uint8_t hash_8 = static_cast(hash); bool keep_going = true; From d409eced2bade2816998bfa34be91eeee813ef87 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 11 Jun 2026 14:58:49 -0700 Subject: [PATCH 454/986] FlatMap: Improves documentation and testing of find_with_hash Also improves device hashing of floating point types (float and long double). --- src/axom/core/DeviceHash.hpp | 22 ++++++------- src/axom/core/FlatMap.hpp | 15 +++++++++ src/axom/core/detail/FlatTable.hpp | 4 +-- .../core/tests/core_benchmark_flatmap.cpp | 9 +++--- src/axom/core/tests/core_device_hash.hpp | 13 ++++++++ src/axom/core/tests/core_flatmap.hpp | 32 +++++++++++++++++++ 6 files changed, 77 insertions(+), 18 deletions(-) diff --git a/src/axom/core/DeviceHash.hpp b/src/axom/core/DeviceHash.hpp index d1ac31ced2..0dc1bb4caa 100644 --- a/src/axom/core/DeviceHash.hpp +++ b/src/axom/core/DeviceHash.hpp @@ -52,21 +52,19 @@ struct DeviceHashHelper::value>> // A float-to-integer value conversion collapses every key sharing an integer part, // e.g. all numbers between -1 and 1 converts to integer 0 - // NUM_WORDS is 1 for float or double, possibly 2 for long double - constexpr std::size_t NUM_WORDS = (sizeof(T) + sizeof(std::uint64_t) - 1) / sizeof(std::uint64_t); - // zero out words since we might only copy 4 bytes in for floats - std::uint64_t words[NUM_WORDS] = {0}; - memcpy(words, &value, sizeof(T)); - - std::uint64_t result = words[0]; - // Extra processing fortypes wider than 64 bits (long double). - // Use an odd multiplier (2^64/golden-ratio-phi), - // so the halves cannot cancel under a later XOR-style mixer - for(std::size_t i = 1; i < NUM_WORDS; i++) + if constexpr(sizeof(T) <= sizeof(std::uint64_t)) { - result = result * std::uint64_t {0x9e3779b97f4a7c15} + words[i]; + // Zero-initialize first since we only copy 4 bytes for floats. + std::uint64_t result = 0; + memcpy(&result, &value, sizeof(T)); + return result; } + // Avoid hashing padding bytes for wider floating types such as x86 long double. + // Collisions are acceptable for a hash; equal values must hash identically. + double narrowed_value = static_cast(value); + std::uint64_t result = 0; + memcpy(&result, &narrowed_value, sizeof(narrowed_value)); return result; } }; diff --git a/src/axom/core/FlatMap.hpp b/src/axom/core/FlatMap.hpp index 8f92292ab4..be1958a9bf 100644 --- a/src/axom/core/FlatMap.hpp +++ b/src/axom/core/FlatMap.hpp @@ -292,6 +292,21 @@ class FlatMap : detail::flat_map::SequentialLookupPolicy(pairs); + const auto lookup_keys = make_lookup_order(keys, 0xBADC0DE5ULL); std::vector hashes; - hashes.reserve(keys.size()); - for(KeyType k : keys) + hashes.reserve(lookup_keys.size()); + for(KeyType k : lookup_keys) { hashes.push_back(typename MapType::hasher {}(k)); } @@ -466,9 +467,9 @@ void BM_FlatMap_Find_Hit_Prehashed(benchmark::State& state) for(auto _ : state) { ValueType sum = 0; - for(std::size_t i = 0; i < keys.size(); ++i) + for(std::size_t i = 0; i < lookup_keys.size(); ++i) { - auto it = map.find_with_hash(keys[i], hashes[i]); + auto it = map.find_with_hash(lookup_keys[i], hashes[i]); if(it != map.end()) { sum += it->second; diff --git a/src/axom/core/tests/core_device_hash.hpp b/src/axom/core/tests/core_device_hash.hpp index 6cab7708b2..dcdb278c37 100644 --- a/src/axom/core/tests/core_device_hash.hpp +++ b/src/axom/core/tests/core_device_hash.hpp @@ -14,6 +14,7 @@ // C++ includes #include #include +#include template class core_device_hash : public ::testing::Test @@ -427,3 +428,15 @@ TEST(core_device_hash, hash_float_bit_pattern) EXPECT_NE(double_hasher(1e300), double_hasher(2e300)); EXPECT_NE(float_hasher(-0.5f), float_hasher(0.5f)); } + +TEST(core_device_hash, hash_long_double_has_stable_equal_value_hash) +{ + axom::DeviceHash long_double_hasher; + + static_assert(std::is_same::result_type, std::uint64_t>::value, + "long double hash result must be std::uint64_t"); + + EXPECT_EQ(long_double_hasher(0.0L), long_double_hasher(-0.0L)); + EXPECT_EQ(long_double_hasher(0.25L), long_double_hasher(static_cast(0.25))); + EXPECT_NE(long_double_hasher(0.25L), long_double_hasher(0.75L)); +} diff --git a/src/axom/core/tests/core_flatmap.hpp b/src/axom/core/tests/core_flatmap.hpp index 021bcd741d..c0665ce21b 100644 --- a/src/axom/core/tests/core_flatmap.hpp +++ b/src/axom/core/tests/core_flatmap.hpp @@ -339,6 +339,38 @@ TEST(core_flatmap_unit, fused_emplace_probe_recomputes_slot_after_rehash) EXPECT_EQ(test_map.count(0), 0); } +TEST(core_flatmap_unit, find_with_hash_uses_precomputed_hash) +{ + using MapType = axom::FlatMap; + MapType test_map; + + const int NUM_ELEMS = 64; + for(int i = 0; i < NUM_ELEMS; i++) + { + test_map.insert_or_assign(i, i * 10); + } + + const int key = 37; + const auto hash = MapType::hasher {}(key); + + auto it = test_map.find_with_hash(key, hash); + ASSERT_NE(it, test_map.end()); + EXPECT_EQ(it->first, key); + EXPECT_EQ(it->second, key * 10); + + const MapType& const_map = test_map; + auto const_it = const_map.find_with_hash(key, hash); + ASSERT_NE(const_it, const_map.end()); + EXPECT_EQ(const_it->first, key); + EXPECT_EQ(const_it->second, key * 10); + + // A precomputed hash is part of the lookup key. Supplying a mismatched hash + // may miss an existing key; this guards the documented precondition. + const auto mismatched_hash = hash ^ MapType::hash_result_type {0x80}; + EXPECT_EQ(test_map.find_with_hash(key, mismatched_hash), test_map.end()); + EXPECT_EQ(const_map.find_with_hash(key, mismatched_hash), const_map.end()); +} + AXOM_TYPED_TEST(core_flatmap, default_init) { using MapType = typename TestFixture::MapType; From 9b14075602dfade3bd9ed8b8569aa55a09247b88 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 11 Jun 2026 16:07:54 -0700 Subject: [PATCH 455/986] Adds benchmarks for device contruction and lookup --- .../core/tests/core_benchmark_flatmap.cpp | 291 +++++++++++++++++- 1 file changed, 287 insertions(+), 4 deletions(-) diff --git a/src/axom/core/tests/core_benchmark_flatmap.cpp b/src/axom/core/tests/core_benchmark_flatmap.cpp index ccd79a9408..edb8a12cdb 100644 --- a/src/axom/core/tests/core_benchmark_flatmap.cpp +++ b/src/axom/core/tests/core_benchmark_flatmap.cpp @@ -13,10 +13,6 @@ #include "axom/CLI11.hpp" #include "axom/fmt.hpp" -#include "axom/core/FlatMap.hpp" -#include "axom/core/FlatMapUtil.hpp" -#include "axom/core/detail/FlatTable.hpp" - #if defined(AXOM_USE_SPARSEHASH) #include "axom/sparsehash/sparse_hash_map" #endif @@ -553,6 +549,269 @@ void BM_BatchedInsert_Reserved(benchmark::State& state) } } +#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) + + // Device execution policy for benchmarking + #if defined(AXOM_USE_HIP) +using DeviceExec = axom::HIP_EXEC<256>; + #elif defined(AXOM_USE_CUDA) +using DeviceExec = axom::CUDA_EXEC<256>; + #endif + + #if defined(AXOM_USE_CUDA) || defined(AXOM_USE_HIP) + +/*! + * \brief Device/parallel benchmarks for FlatMap + * + * These benchmarks measure GPU kernel execution + data transfer overhead. + * Google Benchmark measures wall-clock time, which includes: + * - Host-to-device memory transfers + * - Kernel launch overhead + * - GPU execution time + * - Device-to-host synchronization + * + * For pure kernel performance, profile with nsys/rocprof separately. + * These benchmarks characterize end-to-end device operation cost. + */ + +/*! + * \brief Simple device sanity check + * + * Minimal test to verify device operations work before trying FlatMap. + */ +void BM_Device_Sanity_Check(benchmark::State& state) +{ + const int n = state.range(0); + + const int device_allocator_id = axom::execution_space::allocatorID(); + if(device_allocator_id == axom::INVALID_ALLOCATOR_ID) + { + state.SkipWithError("Device allocator not available"); + return; + } + + // Allocate simple arrays on device + int* device_input = axom::allocate(n, device_allocator_id); + int* device_output = axom::allocate(n, device_allocator_id); + + // Initialize on host + std::vector host_data(n, 42); + axom::copy(device_input, host_data.data(), sizeof(int) * n); + + for(auto _ : state) + { + // Simple kernel: copy input to output + axom::for_all(n, [=] AXOM_HOST_DEVICE(int i) { + device_output[i] = device_input[i] + 1; + }); + + axom::synchronize(); + benchmark::DoNotOptimize(device_output); + } + + axom::deallocate(device_input); + axom::deallocate(device_output); +} + +/*! + * \brief Benchmark parallel batched insertion on device + */ +void BM_FlatMap_Insert_Device_Reserved(benchmark::State& state) +{ + using MapType = axom::FlatMap; + + const int n = state.range(0); + const auto keys = make_shuffled_keys(n, 0x1CEB00DAULL); + const auto pairs = make_pairs(keys); + + // Check if device allocator is available + const int device_allocator_id = axom::execution_space::allocatorID(); + if(device_allocator_id == axom::INVALID_ALLOCATOR_ID) + { + state.SkipWithError("Device allocator not available"); + return; + } + + // Use axom::Array for host data + using PairType = std::pair; + axom::Array host_pairs(pairs.size(), pairs.size()); + std::copy(pairs.begin(), pairs.end(), host_pairs.data()); + + // Copy to device using axom::Array with device allocator + axom::Array device_pairs(pairs.size(), pairs.size(), device_allocator_id); + axom::copy(device_pairs.data(), host_pairs.data(), sizeof(PairType) * pairs.size()); + + const std::size_t bs = static_cast(std::max(1, ::args_batch_size)); + + // Get device-safe ArrayView and extract raw pointer for template instantiation + auto pairs_view = device_pairs.view(); + PairType* device_pairs_ptr = pairs_view.data(); + const std::size_t total_size = pairs.size(); + + for(auto _ : state) + { + // Create map with device allocator and reserve capacity + MapType map(axom::Allocator {device_allocator_id}); + map.reserve(static_cast(pairs.size())); + + // Benchmark parallel batched insertion using raw pointers from ArrayView + for(std::size_t offset = 0; offset < total_size; offset += bs) + { + const std::size_t count = std::min(bs, total_size - offset); + map.template insert(device_pairs_ptr + offset, device_pairs_ptr + offset + count); + } + + // Synchronize to ensure device operations complete + axom::synchronize(); + + benchmark::DoNotOptimize(map); + } +} + +/*! + * \brief Benchmark parallel lookup on device + */ +void BM_FlatMap_Find_Hit_Device(benchmark::State& state) +{ + using MapType = axom::FlatMap; + + const int n = state.range(0); + const auto keys = make_shuffled_keys(n, 0xC0FFEEULL); + const auto pairs = make_pairs(keys); + const auto lookup_keys = make_lookup_order(keys, 0xBADC0DE5ULL); + + // Check if device allocator is available + const int device_allocator_id = axom::execution_space::allocatorID(); + if(device_allocator_id == axom::INVALID_ALLOCATOR_ID) + { + state.SkipWithError("Device allocator not available"); + return; + } + + // Use axom::Array for host data + using PairType = std::pair; + axom::Array host_pairs(pairs.size(), pairs.size()); + std::copy(pairs.begin(), pairs.end(), host_pairs.data()); + + // Copy to device using axom::Array with device allocator + axom::Array device_pairs(pairs.size(), pairs.size(), device_allocator_id); + axom::copy(device_pairs.data(), host_pairs.data(), sizeof(PairType) * pairs.size()); + + // Create and populate map on device + MapType map(axom::Allocator {device_allocator_id}); + map.reserve(static_cast(pairs.size())); + + // Use raw pointer from ArrayView for template instantiation + auto pairs_view = device_pairs.view(); + map.template insert(pairs_view.data(), pairs_view.data() + pairs_view.size()); + + // Copy lookup keys to device using axom::Array + axom::Array host_lookup_keys(lookup_keys.size(), lookup_keys.size()); + std::copy(lookup_keys.begin(), lookup_keys.end(), host_lookup_keys.data()); + + axom::Array device_lookup_keys(lookup_keys.size(), lookup_keys.size(), device_allocator_id); + axom::copy(device_lookup_keys.data(), host_lookup_keys.data(), sizeof(KeyType) * lookup_keys.size()); + + // Allocate result array on device using axom::Array + axom::Array device_results(lookup_keys.size(), lookup_keys.size(), device_allocator_id); + + // Get device-safe views for kernel capture + // Note: Using explicit [=] AXOM_HOST_DEVICE instead of AXOM_LAMBDA to avoid + // RAJA privatizer issues on HIP with non-trivial types in capture + auto map_view = map.view(); + auto lookup_keys_view = device_lookup_keys.view(); + auto results_view = device_results.view(); + + for(auto _ : state) + { + // Perform lookups in parallel using ArrayViews + axom::for_all(static_cast(lookup_keys.size()), + [=] AXOM_HOST_DEVICE(axom::IndexType i) { + auto it = map_view.find(lookup_keys_view[i]); + results_view[i] = + (it != map_view.end()) ? it->second : ValueType {-1}; + }); + + // Synchronize to ensure device operations complete + axom::synchronize(); + + benchmark::DoNotOptimize(device_results.data()); + } +} + +/*! + * \brief Benchmark parallel lookup misses on device + */ +void BM_FlatMap_Find_Miss_Device(benchmark::State& state) +{ + using MapType = axom::FlatMap; + + const int n = state.range(0); + const auto keys = make_shuffled_keys(n, 0xC0FFEEULL); + const auto pairs = make_pairs(keys); + const auto miss_keys = make_miss_keys(keys, static_cast(n) + 11); + + // Check if device allocator is available + const int device_allocator_id = axom::execution_space::allocatorID(); + if(device_allocator_id == axom::INVALID_ALLOCATOR_ID) + { + state.SkipWithError("Device allocator not available"); + return; + } + + // Use axom::Array for host data + using PairType = std::pair; + axom::Array host_pairs(pairs.size(), pairs.size()); + std::copy(pairs.begin(), pairs.end(), host_pairs.data()); + + // Copy to device using axom::Array with device allocator + axom::Array device_pairs(pairs.size(), pairs.size(), device_allocator_id); + axom::copy(device_pairs.data(), host_pairs.data(), sizeof(PairType) * pairs.size()); + + // Create and populate map on device + MapType map(axom::Allocator {device_allocator_id}); + map.reserve(static_cast(pairs.size())); + + // Use raw pointer from ArrayView for template instantiation + auto pairs_view = device_pairs.view(); + map.template insert(pairs_view.data(), pairs_view.data() + pairs_view.size()); + + // Copy miss keys to device using axom::Array + axom::Array host_miss_keys(miss_keys.size(), miss_keys.size()); + std::copy(miss_keys.begin(), miss_keys.end(), host_miss_keys.data()); + + axom::Array device_miss_keys(miss_keys.size(), miss_keys.size(), device_allocator_id); + axom::copy(device_miss_keys.data(), host_miss_keys.data(), sizeof(KeyType) * miss_keys.size()); + + // Allocate result array on device using axom::Array + axom::Array device_misses(miss_keys.size(), miss_keys.size(), device_allocator_id); + + // Get device-safe views for kernel capture + // Note: Using explicit [=] AXOM_HOST_DEVICE instead of AXOM_LAMBDA to avoid + // RAJA privatizer issues on HIP with non-trivial types in capture + auto map_view = map.view(); + auto miss_keys_view = device_miss_keys.view(); + auto misses_view = device_misses.view(); + + for(auto _ : state) + { + // Perform lookups in parallel using ArrayViews + axom::for_all(static_cast(miss_keys.size()), + [=] AXOM_HOST_DEVICE(axom::IndexType i) { + misses_view[i] = + (map_view.find(miss_keys_view[i]) == map_view.end()) ? 1 : 0; + }); + + // Synchronize to ensure device operations complete + axom::synchronize(); + + benchmark::DoNotOptimize(device_misses.data()); + } +} + + #endif // AXOM_USE_CUDA || AXOM_USE_HIP +#endif // AXOM_USE_RAJA && AXOM_USE_UMPIRE + } // namespace //----------------------------------------------------------------------------- @@ -725,6 +984,30 @@ int main(int argc, char* argv[]) "axom::google::sparse_hash_map"); #endif + // Device/parallel benchmarks for debugging +#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) && \ + (defined(AXOM_USE_CUDA) || defined(AXOM_USE_HIP)) + + // Device benchmarks enabled with raw pointers (iterators cause host stack address faults) + benchmark::RegisterBenchmark("Device::sanity_check", &BM_Device_Sanity_Check)->Apply(CustomArgs); + + if((::args_benchmark_features & FlatMapFeatureBenchmarks::BatchedInsertion) != + FlatMapFeatureBenchmarks::None) + { + benchmark::RegisterBenchmark("axom::FlatMap::insert_device_reserved", + &BM_FlatMap_Insert_Device_Reserved) + ->Apply(CustomArgs); + } + + if((::args_benchmark_features & FlatMapFeatureBenchmarks::Lookup) != FlatMapFeatureBenchmarks::None) + { + benchmark::RegisterBenchmark("axom::FlatMap::find_hit_device", &BM_FlatMap_Find_Hit_Device) + ->Apply(CustomArgs); + benchmark::RegisterBenchmark("axom::FlatMap::find_miss_device", &BM_FlatMap_Find_Miss_Device) + ->Apply(CustomArgs); + } +#endif // AXOM_USE_RAJA && AXOM_USE_UMPIRE && (AXOM_USE_CUDA || AXOM_USE_HIP) + ::benchmark::RunSpecifiedBenchmarks(); return 0; } From 489d9ff20694823c614918bc78f8c17324809b7d Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 11 Jun 2026 16:46:07 -0700 Subject: [PATCH 456/986] FlatMap: Generalizes the device benchmarks to other execution spaces, including omp --- src/axom/core/tests/CMakeLists.txt | 8 +- .../core/tests/core_benchmark_flatmap.cpp | 307 ++++++++---------- 2 files changed, 139 insertions(+), 176 deletions(-) diff --git a/src/axom/core/tests/CMakeLists.txt b/src/axom/core/tests/CMakeLists.txt index b30ebb25fc..7322f589f6 100644 --- a/src/axom/core/tests/CMakeLists.txt +++ b/src/axom/core/tests/CMakeLists.txt @@ -218,6 +218,11 @@ if (ENABLE_BENCHMARKS) foreach(test ${core_benchmarks}) get_filename_component(test_name ${test} NAME_WE) + set(_num_threads) + if(test STREQUAL "core_benchmark_flatmap.cpp" AND AXOM_ENABLE_OPENMP) + set(_num_threads ${AXOM_TEST_NUM_OMP_THREADS}) + endif() + axom_add_executable(NAME ${test_name} SOURCES ${test} OUTPUT_DIR ${TEST_OUTPUT_DIRECTORY} @@ -225,6 +230,7 @@ if (ENABLE_BENCHMARKS) FOLDER axom/core/benchmarks) blt_add_benchmark(NAME ${test_name} - COMMAND ${test_name} --benchmark_min_time=0.0001s) + COMMAND ${test_name} --benchmark_min_time=0.0001s + NUM_OMP_THREADS ${_num_threads}) endforeach() endif() diff --git a/src/axom/core/tests/core_benchmark_flatmap.cpp b/src/axom/core/tests/core_benchmark_flatmap.cpp index edb8a12cdb..c99dc61e20 100644 --- a/src/axom/core/tests/core_benchmark_flatmap.cpp +++ b/src/axom/core/tests/core_benchmark_flatmap.cpp @@ -549,269 +549,211 @@ void BM_BatchedInsert_Reserved(benchmark::State& state) } } -#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) - - // Device execution policy for benchmarking - #if defined(AXOM_USE_HIP) -using DeviceExec = axom::HIP_EXEC<256>; - #elif defined(AXOM_USE_CUDA) -using DeviceExec = axom::CUDA_EXEC<256>; - #endif - - #if defined(AXOM_USE_CUDA) || defined(AXOM_USE_HIP) - /*! - * \brief Device/parallel benchmarks for FlatMap + * \brief Execution-space benchmarks for FlatMap. * - * These benchmarks measure GPU kernel execution + data transfer overhead. - * Google Benchmark measures wall-clock time, which includes: - * - Host-to-device memory transfers - * - Kernel launch overhead - * - GPU execution time - * - Device-to-host synchronization - * - * For pure kernel performance, profile with nsys/rocprof separately. - * These benchmarks characterize end-to-end device operation cost. + * These benchmarks measure execution-space operation cost. Host-to-exec-space + * data setup is outside the timed loop; kernel launch/execution and + * synchronization are included. */ -/*! - * \brief Simple device sanity check - * - * Minimal test to verify device operations work before trying FlatMap. - */ -void BM_Device_Sanity_Check(benchmark::State& state) +template +bool get_allocator_or_skip(benchmark::State& state, int& allocator_id) +{ + allocator_id = axom::execution_space::allocatorID(); + if(allocator_id == axom::INVALID_ALLOCATOR_ID) + { + state.SkipWithError("Execution-space allocator not available"); + return false; + } + + return true; +} + +template +axom::Array copy_to_allocator(const std::vector& values, int allocator_id) +{ + axom::Array copied_values(values.size(), values.size(), allocator_id); + if(!values.empty()) + { + axom::copy(copied_values.data(), values.data(), sizeof(T) * values.size()); + } + return copied_values; +} + +template +void BM_ExecSpace_Sanity_Check(benchmark::State& state) { const int n = state.range(0); - const int device_allocator_id = axom::execution_space::allocatorID(); - if(device_allocator_id == axom::INVALID_ALLOCATOR_ID) + int allocator_id = axom::INVALID_ALLOCATOR_ID; + if(!get_allocator_or_skip(state, allocator_id)) { - state.SkipWithError("Device allocator not available"); return; } - // Allocate simple arrays on device - int* device_input = axom::allocate(n, device_allocator_id); - int* device_output = axom::allocate(n, device_allocator_id); + int* input = axom::allocate(n, allocator_id); + int* output = axom::allocate(n, allocator_id); - // Initialize on host std::vector host_data(n, 42); - axom::copy(device_input, host_data.data(), sizeof(int) * n); + axom::copy(input, host_data.data(), sizeof(int) * n); for(auto _ : state) { - // Simple kernel: copy input to output - axom::for_all(n, [=] AXOM_HOST_DEVICE(int i) { - device_output[i] = device_input[i] + 1; - }); + axom::for_all(n, [=] AXOM_HOST_DEVICE(int i) { output[i] = input[i] + 1; }); - axom::synchronize(); - benchmark::DoNotOptimize(device_output); + axom::synchronize(); + benchmark::DoNotOptimize(output); } - axom::deallocate(device_input); - axom::deallocate(device_output); + axom::deallocate(input); + axom::deallocate(output); } /*! - * \brief Benchmark parallel batched insertion on device + * \brief Benchmark parallel batched insertion using an execution space. */ -void BM_FlatMap_Insert_Device_Reserved(benchmark::State& state) +template +void BM_FlatMap_Insert_ExecSpace_Reserved(benchmark::State& state) { using MapType = axom::FlatMap; + using PairType = std::pair; const int n = state.range(0); const auto keys = make_shuffled_keys(n, 0x1CEB00DAULL); const auto pairs = make_pairs(keys); - // Check if device allocator is available - const int device_allocator_id = axom::execution_space::allocatorID(); - if(device_allocator_id == axom::INVALID_ALLOCATOR_ID) + int allocator_id = axom::INVALID_ALLOCATOR_ID; + if(!get_allocator_or_skip(state, allocator_id)) { - state.SkipWithError("Device allocator not available"); return; } - // Use axom::Array for host data - using PairType = std::pair; - axom::Array host_pairs(pairs.size(), pairs.size()); - std::copy(pairs.begin(), pairs.end(), host_pairs.data()); - - // Copy to device using axom::Array with device allocator - axom::Array device_pairs(pairs.size(), pairs.size(), device_allocator_id); - axom::copy(device_pairs.data(), host_pairs.data(), sizeof(PairType) * pairs.size()); - const std::size_t bs = static_cast(std::max(1, ::args_batch_size)); - - // Get device-safe ArrayView and extract raw pointer for template instantiation - auto pairs_view = device_pairs.view(); - PairType* device_pairs_ptr = pairs_view.data(); + axom::Array exec_pairs = copy_to_allocator(pairs, allocator_id); + auto pairs_view = exec_pairs.view(); + PairType* pairs_ptr = pairs_view.data(); const std::size_t total_size = pairs.size(); for(auto _ : state) { - // Create map with device allocator and reserve capacity - MapType map(axom::Allocator {device_allocator_id}); + MapType map(axom::Allocator {allocator_id}); map.reserve(static_cast(pairs.size())); - // Benchmark parallel batched insertion using raw pointers from ArrayView for(std::size_t offset = 0; offset < total_size; offset += bs) { const std::size_t count = std::min(bs, total_size - offset); - map.template insert(device_pairs_ptr + offset, device_pairs_ptr + offset + count); + map.template insert(pairs_ptr + offset, pairs_ptr + offset + count); } - // Synchronize to ensure device operations complete - axom::synchronize(); + axom::synchronize(); benchmark::DoNotOptimize(map); } } /*! - * \brief Benchmark parallel lookup on device + * \brief Benchmark parallel successful lookup using an execution space. */ -void BM_FlatMap_Find_Hit_Device(benchmark::State& state) +template +void BM_FlatMap_Find_Hit_ExecSpace(benchmark::State& state) { using MapType = axom::FlatMap; + using PairType = std::pair; const int n = state.range(0); const auto keys = make_shuffled_keys(n, 0xC0FFEEULL); const auto pairs = make_pairs(keys); const auto lookup_keys = make_lookup_order(keys, 0xBADC0DE5ULL); - // Check if device allocator is available - const int device_allocator_id = axom::execution_space::allocatorID(); - if(device_allocator_id == axom::INVALID_ALLOCATOR_ID) + int allocator_id = axom::INVALID_ALLOCATOR_ID; + if(!get_allocator_or_skip(state, allocator_id)) { - state.SkipWithError("Device allocator not available"); return; } - // Use axom::Array for host data - using PairType = std::pair; - axom::Array host_pairs(pairs.size(), pairs.size()); - std::copy(pairs.begin(), pairs.end(), host_pairs.data()); - - // Copy to device using axom::Array with device allocator - axom::Array device_pairs(pairs.size(), pairs.size(), device_allocator_id); - axom::copy(device_pairs.data(), host_pairs.data(), sizeof(PairType) * pairs.size()); - - // Create and populate map on device - MapType map(axom::Allocator {device_allocator_id}); + axom::Array exec_pairs = copy_to_allocator(pairs, allocator_id); + MapType map(axom::Allocator {allocator_id}); map.reserve(static_cast(pairs.size())); - // Use raw pointer from ArrayView for template instantiation - auto pairs_view = device_pairs.view(); - map.template insert(pairs_view.data(), pairs_view.data() + pairs_view.size()); - - // Copy lookup keys to device using axom::Array - axom::Array host_lookup_keys(lookup_keys.size(), lookup_keys.size()); - std::copy(lookup_keys.begin(), lookup_keys.end(), host_lookup_keys.data()); + auto pairs_view = exec_pairs.view(); + map.template insert(pairs_view.data(), pairs_view.data() + pairs_view.size()); + axom::synchronize(); - axom::Array device_lookup_keys(lookup_keys.size(), lookup_keys.size(), device_allocator_id); - axom::copy(device_lookup_keys.data(), host_lookup_keys.data(), sizeof(KeyType) * lookup_keys.size()); + axom::Array exec_lookup_keys = copy_to_allocator(lookup_keys, allocator_id); + axom::Array exec_results(lookup_keys.size(), lookup_keys.size(), allocator_id); - // Allocate result array on device using axom::Array - axom::Array device_results(lookup_keys.size(), lookup_keys.size(), device_allocator_id); - - // Get device-safe views for kernel capture // Note: Using explicit [=] AXOM_HOST_DEVICE instead of AXOM_LAMBDA to avoid // RAJA privatizer issues on HIP with non-trivial types in capture auto map_view = map.view(); - auto lookup_keys_view = device_lookup_keys.view(); - auto results_view = device_results.view(); + auto lookup_keys_view = exec_lookup_keys.view(); + auto results_view = exec_results.view(); for(auto _ : state) { - // Perform lookups in parallel using ArrayViews - axom::for_all(static_cast(lookup_keys.size()), - [=] AXOM_HOST_DEVICE(axom::IndexType i) { - auto it = map_view.find(lookup_keys_view[i]); - results_view[i] = - (it != map_view.end()) ? it->second : ValueType {-1}; - }); + axom::for_all(static_cast(lookup_keys.size()), + [=] AXOM_HOST_DEVICE(axom::IndexType i) { + auto it = map_view.find(lookup_keys_view[i]); + results_view[i] = (it != map_view.end()) ? it->second : ValueType {-1}; + }); - // Synchronize to ensure device operations complete - axom::synchronize(); + axom::synchronize(); - benchmark::DoNotOptimize(device_results.data()); + benchmark::DoNotOptimize(exec_results.data()); } } /*! - * \brief Benchmark parallel lookup misses on device + * \brief Benchmark parallel missed lookup using an execution space. */ -void BM_FlatMap_Find_Miss_Device(benchmark::State& state) +template +void BM_FlatMap_Find_Miss_ExecSpace(benchmark::State& state) { using MapType = axom::FlatMap; + using PairType = std::pair; const int n = state.range(0); const auto keys = make_shuffled_keys(n, 0xC0FFEEULL); const auto pairs = make_pairs(keys); const auto miss_keys = make_miss_keys(keys, static_cast(n) + 11); - // Check if device allocator is available - const int device_allocator_id = axom::execution_space::allocatorID(); - if(device_allocator_id == axom::INVALID_ALLOCATOR_ID) + int allocator_id = axom::INVALID_ALLOCATOR_ID; + if(!get_allocator_or_skip(state, allocator_id)) { - state.SkipWithError("Device allocator not available"); return; } - // Use axom::Array for host data - using PairType = std::pair; - axom::Array host_pairs(pairs.size(), pairs.size()); - std::copy(pairs.begin(), pairs.end(), host_pairs.data()); - - // Copy to device using axom::Array with device allocator - axom::Array device_pairs(pairs.size(), pairs.size(), device_allocator_id); - axom::copy(device_pairs.data(), host_pairs.data(), sizeof(PairType) * pairs.size()); - - // Create and populate map on device - MapType map(axom::Allocator {device_allocator_id}); + axom::Array exec_pairs = copy_to_allocator(pairs, allocator_id); + MapType map(axom::Allocator {allocator_id}); map.reserve(static_cast(pairs.size())); - // Use raw pointer from ArrayView for template instantiation - auto pairs_view = device_pairs.view(); - map.template insert(pairs_view.data(), pairs_view.data() + pairs_view.size()); - - // Copy miss keys to device using axom::Array - axom::Array host_miss_keys(miss_keys.size(), miss_keys.size()); - std::copy(miss_keys.begin(), miss_keys.end(), host_miss_keys.data()); + auto pairs_view = exec_pairs.view(); + map.template insert(pairs_view.data(), pairs_view.data() + pairs_view.size()); + axom::synchronize(); - axom::Array device_miss_keys(miss_keys.size(), miss_keys.size(), device_allocator_id); - axom::copy(device_miss_keys.data(), host_miss_keys.data(), sizeof(KeyType) * miss_keys.size()); + axom::Array exec_miss_keys = copy_to_allocator(miss_keys, allocator_id); + axom::Array exec_misses(miss_keys.size(), miss_keys.size(), allocator_id); - // Allocate result array on device using axom::Array - axom::Array device_misses(miss_keys.size(), miss_keys.size(), device_allocator_id); - - // Get device-safe views for kernel capture // Note: Using explicit [=] AXOM_HOST_DEVICE instead of AXOM_LAMBDA to avoid // RAJA privatizer issues on HIP with non-trivial types in capture auto map_view = map.view(); - auto miss_keys_view = device_miss_keys.view(); - auto misses_view = device_misses.view(); + auto miss_keys_view = exec_miss_keys.view(); + auto misses_view = exec_misses.view(); for(auto _ : state) { - // Perform lookups in parallel using ArrayViews - axom::for_all(static_cast(miss_keys.size()), - [=] AXOM_HOST_DEVICE(axom::IndexType i) { - misses_view[i] = - (map_view.find(miss_keys_view[i]) == map_view.end()) ? 1 : 0; - }); + axom::for_all(static_cast(miss_keys.size()), + [=] AXOM_HOST_DEVICE(axom::IndexType i) { + misses_view[i] = + (map_view.find(miss_keys_view[i]) == map_view.end()) ? 1 : 0; + }); - // Synchronize to ensure device operations complete - axom::synchronize(); + axom::synchronize(); - benchmark::DoNotOptimize(device_misses.data()); + benchmark::DoNotOptimize(exec_misses.data()); } } - #endif // AXOM_USE_CUDA || AXOM_USE_HIP -#endif // AXOM_USE_RAJA && AXOM_USE_UMPIRE - } // namespace //----------------------------------------------------------------------------- @@ -863,6 +805,33 @@ void RegisterFlatMapPrehashedBenchmarks() } } +template +void RegisterFlatMapExecSpaceBenchmarks(const std::string& exec_suffix, + const std::string& sanity_prefix) +{ + benchmark::RegisterBenchmark(axom::fmt::format("{}::sanity_check", sanity_prefix), + &BM_ExecSpace_Sanity_Check) + ->Apply(CustomArgs); + + if((::args_benchmark_features & FlatMapFeatureBenchmarks::BatchedInsertion) != + FlatMapFeatureBenchmarks::None) + { + benchmark::RegisterBenchmark(axom::fmt::format("axom::FlatMap::insert_{}_reserved", exec_suffix), + &BM_FlatMap_Insert_ExecSpace_Reserved) + ->Apply(CustomArgs); + } + + if((::args_benchmark_features & FlatMapFeatureBenchmarks::Lookup) != FlatMapFeatureBenchmarks::None) + { + benchmark::RegisterBenchmark(axom::fmt::format("axom::FlatMap::find_hit_{}", exec_suffix), + &BM_FlatMap_Find_Hit_ExecSpace) + ->Apply(CustomArgs); + benchmark::RegisterBenchmark(axom::fmt::format("axom::FlatMap::find_miss_{}", exec_suffix), + &BM_FlatMap_Find_Miss_ExecSpace) + ->Apply(CustomArgs); + } +} + int main(int argc, char* argv[]) { std::vector local_test_sizes; @@ -984,29 +953,17 @@ int main(int argc, char* argv[]) "axom::google::sparse_hash_map"); #endif - // Device/parallel benchmarks for debugging -#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) && \ - (defined(AXOM_USE_CUDA) || defined(AXOM_USE_HIP)) - - // Device benchmarks enabled with raw pointers (iterators cause host stack address faults) - benchmark::RegisterBenchmark("Device::sanity_check", &BM_Device_Sanity_Check)->Apply(CustomArgs); + RegisterFlatMapExecSpaceBenchmarks("seq", "SEQ"); - if((::args_benchmark_features & FlatMapFeatureBenchmarks::BatchedInsertion) != - FlatMapFeatureBenchmarks::None) - { - benchmark::RegisterBenchmark("axom::FlatMap::insert_device_reserved", - &BM_FlatMap_Insert_Device_Reserved) - ->Apply(CustomArgs); - } +#if defined(AXOM_USE_OPENMP) && defined(AXOM_USE_RAJA) + RegisterFlatMapExecSpaceBenchmarks("omp", "OMP"); +#endif - if((::args_benchmark_features & FlatMapFeatureBenchmarks::Lookup) != FlatMapFeatureBenchmarks::None) - { - benchmark::RegisterBenchmark("axom::FlatMap::find_hit_device", &BM_FlatMap_Find_Hit_Device) - ->Apply(CustomArgs); - benchmark::RegisterBenchmark("axom::FlatMap::find_miss_device", &BM_FlatMap_Find_Miss_Device) - ->Apply(CustomArgs); - } -#endif // AXOM_USE_RAJA && AXOM_USE_UMPIRE && (AXOM_USE_CUDA || AXOM_USE_HIP) +#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) && defined(AXOM_USE_HIP) + RegisterFlatMapExecSpaceBenchmarks>("device", "Device"); +#elif defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) && defined(AXOM_USE_CUDA) + RegisterFlatMapExecSpaceBenchmarks>("device", "Device"); +#endif ::benchmark::RunSpecifiedBenchmarks(); return 0; From fd31c5e5d63f7d0bad89ed2b6506abb1582ede7f Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 11 Jun 2026 17:01:01 -0700 Subject: [PATCH 457/986] Add number of threads to omp benchmarks --- .../core/tests/core_benchmark_flatmap.cpp | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/axom/core/tests/core_benchmark_flatmap.cpp b/src/axom/core/tests/core_benchmark_flatmap.cpp index c99dc61e20..35119b1a7a 100644 --- a/src/axom/core/tests/core_benchmark_flatmap.cpp +++ b/src/axom/core/tests/core_benchmark_flatmap.cpp @@ -13,6 +13,10 @@ #include "axom/CLI11.hpp" #include "axom/fmt.hpp" +#if defined(AXOM_USE_OPENMP) + #include +#endif + #if defined(AXOM_USE_SPARSEHASH) #include "axom/sparsehash/sparse_hash_map" #endif @@ -805,6 +809,18 @@ void RegisterFlatMapPrehashedBenchmarks() } } +#if defined(AXOM_USE_OPENMP) && defined(AXOM_USE_RAJA) +std::string make_openmp_exec_suffix() +{ + return axom::fmt::format("omp_{}t", omp_get_max_threads()); +} + +std::string make_openmp_sanity_prefix() +{ + return axom::fmt::format("OMP_{}t", omp_get_max_threads()); +} +#endif + template void RegisterFlatMapExecSpaceBenchmarks(const std::string& exec_suffix, const std::string& sanity_prefix) @@ -956,7 +972,8 @@ int main(int argc, char* argv[]) RegisterFlatMapExecSpaceBenchmarks("seq", "SEQ"); #if defined(AXOM_USE_OPENMP) && defined(AXOM_USE_RAJA) - RegisterFlatMapExecSpaceBenchmarks("omp", "OMP"); + RegisterFlatMapExecSpaceBenchmarks(make_openmp_exec_suffix(), + make_openmp_sanity_prefix()); #endif #if defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) && defined(AXOM_USE_HIP) From a85db8211547ab4724d1f8c916ddd89f0e223457 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 11 Jun 2026 17:05:21 -0700 Subject: [PATCH 458/986] Updates RELEASE-NOTES --- RELEASE-NOTES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index a4bd67cc64..059766fcb2 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -59,6 +59,8 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ - Primal: Improves reproducibility of 3D GWN methods by removing some sources of randomness - Core: ArrayView assigments/copies now copy the stride - Core: Array construction from strided ArrayView now correctly copies the strided elements +- Core: Improved `axom::FlatMap` insertion performance by fusing duplicate-key lookup with empty-slot probing. +- Core: Updated DeviceHash to use 64-bit hash results and improved coverage for integer and floating-point hashing. ## [Version 0.14.0] - Release date 2026-03-31 From d8bb8e9d3ae99fddfa806581d6257aae8e31b5c8 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Fri, 12 Jun 2026 18:55:32 -0700 Subject: [PATCH 459/986] Bugfix for rzvector -- `if constexpr` needs an `else` --- src/CMakeLists.txt | 2 +- src/axom/core/FlatMapUtil.hpp | 393 +++++++++++++++++----------------- 2 files changed, 198 insertions(+), 197 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 362e28914d..08eabf44f7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -42,7 +42,7 @@ else() endif() endif() -if (“${PROJECT_SOURCE_DIR}” STREQUAL “${CMAKE_SOURCE_DIR}”) +if ("${PROJECT_SOURCE_DIR}" STREQUAL "${CMAKE_SOURCE_DIR}") # Set some default BLT options before loading BLT only if not included in # another project if (NOT BLT_CXX_STD) diff --git a/src/axom/core/FlatMapUtil.hpp b/src/axom/core/FlatMapUtil.hpp index da0b363631..24cb9e38d6 100644 --- a/src/axom/core/FlatMapUtil.hpp +++ b/src/axom/core/FlatMapUtil.hpp @@ -285,233 +285,234 @@ void FlatMap::insert(InputIt kv_begin, InputIt kv_end) this->insert_or_assign(std::forward(kv).first, std::forward(kv).second); } - return; } - - using HashResult = typename Hash::result_type; - using GroupBucket = detail::flat_map::GroupBucket; - - IndexType num_elems = std::distance(kv_begin, kv_end); - - // Batched insertion assumes probing sequences are gap-free - // (i.e., there are no tombstones from prior erase() operations). - // When tombstones exist, the parallel insertion logic can mishandle duplicates - // under contention (e.g. OpenMP) and produce incorrect size/value results. - // - // If tombstones exist, rehash to compact the table and restore the invariants required by this algorithm. - if(this->m_loadCount != static_cast(this->m_size)) + else { - this->rehash(this->m_size + num_elems); - } + using HashResult = typename Hash::result_type; + using GroupBucket = detail::flat_map::GroupBucket; + + IndexType num_elems = std::distance(kv_begin, kv_end); + + // Batched insertion assumes probing sequences are gap-free + // (i.e., there are no tombstones from prior erase() operations). + // When tombstones exist, the parallel insertion logic can mishandle duplicates + // under contention (e.g. OpenMP) and produce incorrect size/value results. + // + // If tombstones exist, rehash to compact the table and restore the invariants required by this algorithm. + if(this->m_loadCount != static_cast(this->m_size)) + { + this->rehash(this->m_size + num_elems); + } - const bool is_gap_free = (this->m_loadCount == static_cast(this->m_size)); + const bool is_gap_free = (this->m_loadCount == static_cast(this->m_size)); - // Assume that all elements will be inserted into an empty slot. - this->reserve(this->size() + num_elems); + // Assume that all elements will be inserted into an empty slot. + this->reserve(this->size() + num_elems); - FlatMap temp; - bool allocate_temp_map = false; + FlatMap temp; + bool allocate_temp_map = false; #if defined(AXOM_USE_CUDA) && defined(AXOM_USE_UMPIRE) - if(this->m_allocator.getSpace() == MemorySpace::Pinned) - { - // Pinned memory is allocated on the CPU, and is not always coherent with respect to the GPU. - // Instead of using system-scope atomics, we just construct a temporary map in device memory - // and copy it back to the pinned space. - axom::Allocator device_allocator {axom::detail::getAllocatorID()}; - temp = FlatMap(*this, device_allocator); - allocate_temp_map = true; - } + if(this->m_allocator.getSpace() == MemorySpace::Pinned) + { + // Pinned memory is allocated on the CPU, and is not always coherent with respect to the GPU. + // Instead of using system-scope atomics, we just construct a temporary map in device memory + // and copy it back to the pinned space. + axom::Allocator device_allocator {axom::detail::getAllocatorID()}; + temp = FlatMap(*this, device_allocator); + allocate_temp_map = true; + } #endif - FlatMap& map = allocate_temp_map ? temp : *this; - - // Grab some needed internal fields from the flat map. - // We're going to be constructing metadata and the K-V pairs directly - // in-place. - const int ngroups_pow_2 = map.m_numGroups2; - const auto meta_group = map.m_metadata.view(); - const auto buckets = map.m_buckets.view(); - - // Construct an array of locks per-group. This guards metadata updates for - // each insertion. - const IndexType num_groups = 1 << ngroups_pow_2; - Array lock_vec(num_groups, num_groups, map.m_allocator.getID()); - const auto group_locks = lock_vec.view(); - - // Map bucket slots to k-v pair indices. This is used to deduplicate pairs - // with the same key value. - Array key_index_dedup_vec(0, 0, map.m_allocator.getID()); - key_index_dedup_vec.resize(num_groups * GroupBucket::Size, -1); - const auto key_index_dedup = key_index_dedup_vec.view(); - - // Map k-v pair indices to bucket slots. This is essentially the inverse of - // the above mapping. - Array key_index_to_bucket_vec(num_elems, num_elems, map.m_allocator.getID()); - const auto key_index_to_bucket = key_index_to_bucket_vec.view(); - - axom::ReduceSum total_overwrites(0); - - for_all( - num_elems, - AXOM_LAMBDA(IndexType idx) { - // Construct key. - KeyType key = (*(kv_begin + idx)).first; - - // Hash keys. - auto hash = Hash {}(key); - - // We use the k MSBs of the hash as the initial group probe point, where ngroups = 2^k. - const auto init = - detail::flat_map::SequentialLookupPolicy::initGroupProbe(hash, ngroups_pow_2); - const HashResult group_mask = init.group_mask; - HashResult curr_group = init.curr_group; - - std::uint8_t hash_8 = static_cast(hash); - - IndexType duplicate_bucket_index = -1; - IndexType empty_bucket_index = -1; - int iteration = 0; - while(iteration < meta_group.size()) - { - // Try to lock the group. We do this in a non-blocking manner to avoid - // intra-warp progress hazards. - bool group_locked = group_locks[curr_group].tryLock(); - - if(group_locked) + FlatMap& map = allocate_temp_map ? temp : *this; + + // Grab some needed internal fields from the flat map. + // We're going to be constructing metadata and the K-V pairs directly + // in-place. + const int ngroups_pow_2 = map.m_numGroups2; + const auto meta_group = map.m_metadata.view(); + const auto buckets = map.m_buckets.view(); + + // Construct an array of locks per-group. This guards metadata updates for + // each insertion. + const IndexType num_groups = 1 << ngroups_pow_2; + Array lock_vec(num_groups, num_groups, map.m_allocator.getID()); + const auto group_locks = lock_vec.view(); + + // Map bucket slots to k-v pair indices. This is used to deduplicate pairs + // with the same key value. + Array key_index_dedup_vec(0, 0, map.m_allocator.getID()); + key_index_dedup_vec.resize(num_groups * GroupBucket::Size, -1); + const auto key_index_dedup = key_index_dedup_vec.view(); + + // Map k-v pair indices to bucket slots. This is essentially the inverse of + // the above mapping. + Array key_index_to_bucket_vec(num_elems, num_elems, map.m_allocator.getID()); + const auto key_index_to_bucket = key_index_to_bucket_vec.view(); + + axom::ReduceSum total_overwrites(0); + + for_all( + num_elems, + AXOM_LAMBDA(IndexType idx) { + // Construct key. + KeyType key = (*(kv_begin + idx)).first; + + // Hash keys. + auto hash = Hash {}(key); + + // We use the k MSBs of the hash as the initial group probe point, where ngroups = 2^k. + const auto init = + detail::flat_map::SequentialLookupPolicy::initGroupProbe(hash, ngroups_pow_2); + const HashResult group_mask = init.group_mask; + HashResult curr_group = init.curr_group; + + std::uint8_t hash_8 = static_cast(hash); + + IndexType duplicate_bucket_index = -1; + IndexType empty_bucket_index = -1; + int iteration = 0; + while(iteration < meta_group.size()) { - // Every bucket visit - check prior filled buckets for duplicate - // keys. - meta_group[curr_group].visitHashBucket(hash_8, [&](int matching_slot) -> bool { - IndexType bucket_index = curr_group * GroupBucket::Size + matching_slot; + // Try to lock the group. We do this in a non-blocking manner to avoid + // intra-warp progress hazards. + bool group_locked = group_locks[curr_group].tryLock(); - if(buckets[bucket_index].get().first == key) - { - duplicate_bucket_index = bucket_index; - return false; // Don't need to search other buckets. - } - return true; - }); - int empty_slot_index = meta_group[curr_group].getEmptyBucket(); - - if(duplicate_bucket_index == -1 && empty_bucket_index == -1) + if(group_locked) { - // Default probing behavior: no duplicate found yet, and no empty - // bucket found prior. - if(empty_slot_index == GroupBucket::InvalidSlot) - { - // Group is full. Set overflow bit for the group. - meta_group[curr_group].template setOverflow(hash_8); - } - else + // Every bucket visit - check prior filled buckets for duplicate + // keys. + meta_group[curr_group].visitHashBucket(hash_8, [&](int matching_slot) -> bool { + IndexType bucket_index = curr_group * GroupBucket::Size + matching_slot; + + if(buckets[bucket_index].get().first == key) + { + duplicate_bucket_index = bucket_index; + return false; // Don't need to search other buckets. + } + return true; + }); + int empty_slot_index = meta_group[curr_group].getEmptyBucket(); + + if(duplicate_bucket_index == -1 && empty_bucket_index == -1) { - // Update empty bucket index with first empty slot we encounter. - empty_bucket_index = curr_group * GroupBucket::Size + empty_slot_index; - key_index_dedup[empty_bucket_index] = idx; - key_index_to_bucket[idx] = empty_bucket_index; - - // Insert initial element, this will be updated with the value of - // the "winning" key-value pair. - meta_group[curr_group].template setBucket(empty_slot_index, hash_8); + // Default probing behavior: no duplicate found yet, and no empty + // bucket found prior. + if(empty_slot_index == GroupBucket::InvalidSlot) + { + // Group is full. Set overflow bit for the group. + meta_group[curr_group].template setOverflow(hash_8); + } + else + { + // Update empty bucket index with first empty slot we encounter. + empty_bucket_index = curr_group * GroupBucket::Size + empty_slot_index; + key_index_dedup[empty_bucket_index] = idx; + key_index_to_bucket[idx] = empty_bucket_index; + + // Insert initial element, this will be updated with the value of + // the "winning" key-value pair. + meta_group[curr_group].template setBucket(empty_slot_index, hash_8); #if defined(__CUDA_ARCH__) - detail::constructPairInPlace(buckets[empty_bucket_index].get(), - key, - (*(kv_begin + idx)).second); + detail::constructPairInPlace(buckets[empty_bucket_index].get(), + key, + (*(kv_begin + idx)).second); #else - new(&buckets[empty_bucket_index]) KeyValuePair(*(kv_begin + idx)); + new(&buckets[empty_bucket_index]) KeyValuePair(*(kv_begin + idx)); #endif + } } - } - else if(duplicate_bucket_index != -1) - { - // Found a duplicate bucket. - if(!is_gap_free && empty_bucket_index != -1) - { - // We've already encountered an empty bucket earlier to place a - // k-v pair. This may occur if a probing sequence contains gaps - // (insertions followed by erasures). - // - // Just erase this element. - total_overwrites += 1; - - int slot_index = duplicate_bucket_index - curr_group * GroupBucket::Size; - buckets[duplicate_bucket_index].get().~KeyValuePair(); - meta_group[curr_group].clearBucket(slot_index); - } - else + else if(duplicate_bucket_index != -1) { - if(key_index_dedup[duplicate_bucket_index] == -1) + // Found a duplicate bucket. + if(!is_gap_free && empty_bucket_index != -1) { - // The k-v pair matches an already-existing pair in the map. - // Keep track of the number of overwrites so that we don't - // double-count them when incrementing the size. + // We've already encountered an empty bucket earlier to place a + // k-v pair. This may occur if a probing sequence contains gaps + // (insertions followed by erasures). + // + // Just erase this element. total_overwrites += 1; + + int slot_index = duplicate_bucket_index - curr_group * GroupBucket::Size; + buckets[duplicate_bucket_index].get().~KeyValuePair(); + meta_group[curr_group].clearBucket(slot_index); + } + else + { + if(key_index_dedup[duplicate_bucket_index] == -1) + { + // The k-v pair matches an already-existing pair in the map. + // Keep track of the number of overwrites so that we don't + // double-count them when incrementing the size. + total_overwrites += 1; + } + // Highest-indexed kv pair wins. + axom::atomicMax(&key_index_dedup[duplicate_bucket_index], idx); + key_index_to_bucket[idx] = duplicate_bucket_index; } - // Highest-indexed kv pair wins. - axom::atomicMax(&key_index_dedup[duplicate_bucket_index], idx); - key_index_to_bucket[idx] = duplicate_bucket_index; } - } - // Unlock group once we're done. - group_locks[curr_group].unlock(); + // Unlock group once we're done. + group_locks[curr_group].unlock(); - if(duplicate_bucket_index != -1) - { - // We've found a duplicate key to overwrite. - break; - } - else if(empty_bucket_index != -1 && - (is_gap_free || !meta_group[curr_group].getMaybeOverflowed(hash_8))) - { - // If we're inserting into a gap-free map, empty bucket signals the - // end of the probing sequence. - // Otherwise, we need to check the overflow mask to continue probing. - break; - } - else - { - // Move to next group. - curr_group = (curr_group + LookupPolicy {}.getNext(iteration)) & group_mask; - iteration++; + if(duplicate_bucket_index != -1) + { + // We've found a duplicate key to overwrite. + break; + } + else if(empty_bucket_index != -1 && + (is_gap_free || !meta_group[curr_group].getMaybeOverflowed(hash_8))) + { + // If we're inserting into a gap-free map, empty bucket signals the + // end of the probing sequence. + // Otherwise, we need to check the overflow mask to continue probing. + break; + } + else + { + // Move to next group. + curr_group = (curr_group + LookupPolicy {}.getNext(iteration)) & group_mask; + iteration++; + } } } - } - }); - - // Add a counter for duplicated inserts. - axom::ReduceSum total_inserts(0); - - // Using key-deduplication map, assign unique k-v pairs to buckets. - for_all( - num_elems, - AXOM_LAMBDA(IndexType kv_idx) { - IndexType bucket_idx = key_index_to_bucket[kv_idx]; - IndexType winning_idx = key_index_dedup[bucket_idx]; - // Place k-v pair at bucket_idx. - if(kv_idx == winning_idx) - { + }); + + // Add a counter for duplicated inserts. + axom::ReduceSum total_inserts(0); + + // Using key-deduplication map, assign unique k-v pairs to buckets. + for_all( + num_elems, + AXOM_LAMBDA(IndexType kv_idx) { + IndexType bucket_idx = key_index_to_bucket[kv_idx]; + IndexType winning_idx = key_index_dedup[bucket_idx]; + // Place k-v pair at bucket_idx. + if(kv_idx == winning_idx) + { #if defined(__CUDA_ARCH__) - detail::constructPairInPlace(buckets[bucket_idx].get(), - (*(kv_begin + kv_idx)).first, - (*(kv_begin + kv_idx)).second); + detail::constructPairInPlace(buckets[bucket_idx].get(), + (*(kv_begin + kv_idx)).first, + (*(kv_begin + kv_idx)).second); #else - new(&buckets[bucket_idx]) KeyValuePair(*(kv_begin + kv_idx)); + new(&buckets[bucket_idx]) KeyValuePair(*(kv_begin + kv_idx)); #endif - total_inserts += 1; - } - }); + total_inserts += 1; + } + }); - map.m_size += total_inserts.get() - total_overwrites.get(); - map.m_loadCount += total_inserts.get() - total_overwrites.get(); + map.m_size += total_inserts.get() - total_overwrites.get(); + map.m_loadCount += total_inserts.get() - total_overwrites.get(); #if defined(AXOM_USE_CUDA) && defined(AXOM_USE_UMPIRE) - if(allocate_temp_map) - { - // Original pinned map is in temp. - axom::Allocator pinned_allocator = temp.getAllocator(); + if(allocate_temp_map) + { + // Original pinned map is in temp. + axom::Allocator pinned_allocator = temp.getAllocator(); - // Move new FlatMap to pinned memory. - *this = FlatMap(map, pinned_allocator); - } + // Move new FlatMap to pinned memory. + *this = FlatMap(map, pinned_allocator); + } #endif + } } } // namespace axom From 79584adbb670714fd01f4cf4cae98c4cef0d3682 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 17 Jun 2026 17:43:04 -0700 Subject: [PATCH 460/986] 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 2b1f5b49e48a784ff4da843f4c9f20396e6d1d7b Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 16 Jun 2026 20:45:08 -0700 Subject: [PATCH 461/986] pysidre: Fixes lifetime soundness issues in sidre's Python bindings The bindings previously returned sidre-owned objects and zero-copy arrays without tying them back to the object that owned their storage, which could leave dangling pointers/proxies that segfault. We now use `rv_policy::reference_internal` for views, groups and attributes, nd::keep_alive<0,1> for iterators, and nb::handle_t for arrays. This is tested in a new set of `lifetime` Python unit tests. --- src/axom/sidre/nanobind_sidre.cpp | 139 +++++----- src/axom/sidre/tests/CMakeLists.txt | 1 + src/axom/sidre/tests/sidre_lifetime_Py.py | 306 ++++++++++++++++++++++ 3 files changed, 384 insertions(+), 62 deletions(-) create mode 100644 src/axom/sidre/tests/sidre_lifetime_Py.py diff --git a/src/axom/sidre/nanobind_sidre.cpp b/src/axom/sidre/nanobind_sidre.cpp index 943c8984f3..be3bd1adec 100644 --- a/src/axom/sidre/nanobind_sidre.cpp +++ b/src/axom/sidre/nanobind_sidre.cpp @@ -85,9 +85,19 @@ nb::dlpack::dtype typeIDToDtype(DataTypeId id) * * \note Max dimensions (DMAX) is currently set to 10. * \pre data description must have been applied. + * + * \warning The returned array is a zero-copy view into the View's storage; it + * does not own the data. The array keeps the Python \a View object (and, via + * the View's owner-chain, its Group and DataStore) alive for as long as the + * array is referenced. One sharp edge remains: \c View::reallocate (and + * \c Buffer::reallocate) can move the underlying storage while a live array + * still points at the old allocation, the same hazard as resizing a container + * under a numpy view. Re-acquire the array after any reallocation. */ -nb::ndarray viewToNumpyArray(View& self) +nb::ndarray viewToNumpyArray(nb::handle_t h) { + View& self = nb::cast(h); + // Manually applying offset void* data = self.getVoidPtr(); char* data_with_offset = static_cast(data) + (self.getOffset() * self.getBytesPerElement()); @@ -103,13 +113,6 @@ nb::ndarray viewToNumpyArray(View& self) shape[i] = static_cast(shapeOutput[i]); } - // TODO This is tricky and difficult to understand - // Delete 'data' when the 'owner' capsule expires - // nb::capsule owner(data, [](void* p) noexcept { delete[] static_cast(p); }); - - // For external memory (numpy owns it), no deletion takes place - nb::capsule owner(data, [](void*) noexcept { }); - // When stride is not default of 1, guaranteed that shape is 1D. int64_t* strides = nullptr; int64_t stride_array[1]; @@ -121,11 +124,15 @@ nb::ndarray viewToNumpyArray(View& self) DataTypeId id = self.getTypeID(); + // Pass the Python 'self' object as the array owner: nanobind ties the + // array's lifetime to it, so the View (and its DataStore) cannot be freed + // while the array is alive. This resolves the prior no-op owner capsule that + // left the array dangling once the View was collected. return nb::ndarray( /* data = */ data, /* ndim = */ ndims, /* shape = */ shape, - /* owner = */ owner, + /* owner = */ h, /* strides = */ strides, /* dtype = */ typeIDToDtype(id)); } @@ -134,27 +141,29 @@ nb::ndarray viewToNumpyArray(View& self) * \brief Returns a Buffer as a numpy array. * * \pre data description must have been applied. + * + * \warning The returned array is a zero-copy view into the Buffer's storage; it + * does not own the data. The array keeps the Python \a Buffer object (and its + * owning DataStore) alive for as long as the array is referenced. As with + * \c viewToNumpyArray, \c Buffer::reallocate can move storage under a live + * array; re-acquire the array after any reallocation. */ -nb::ndarray bufferToNumpyArray(Buffer& self) +nb::ndarray bufferToNumpyArray(nb::handle_t h) { + Buffer& self = nb::cast(h); + void* data = self.getVoidPtr(); size_t shape[1] = {static_cast(self.getNumElements())}; - // TODO This is tricky and difficult to understand - // Delete 'data' when the 'owner' capsule expires - // nb::capsule owner(data, [](void* p) noexcept { delete[] static_cast(p); }); - - // For external memory (numpy owns it), no deletion takes place - nb::capsule owner(data, [](void*) noexcept { }); - DataTypeId id = self.getTypeID(); + // Pass the Python 'self' object as the array owner (see viewToNumpyArray). return nb::ndarray( /* data = */ data, /* ndim = */ 1, /* shape = */ shape, - /* owner = */ owner, + /* owner = */ h, /* strides = */ nullptr, /* dtype = */ typeIDToDtype(id)); } @@ -182,7 +191,13 @@ void bindIterator(nb::module_& m, const char* iterator_name) nb::type(), iterator_name, self.begin(), - self.end()); + self.end(), + // Pin each yielded element to the iterator (applies to __next__). + // The iterator is in turn pinned to the collection by the + // keep_alive on __iter__ below, and the collection accessor + // (e.g. Group::views()) is reference_internal, so a harvested + // element transitively keeps its DataStore alive. + nb::keep_alive<0, 1>()); }, nb::keep_alive<0, 1>()); } @@ -304,7 +319,7 @@ NB_MODULE(pysidre, m_sidre) .def(nb::init<>()) .def("getRoot", nb::overload_cast<>(&DataStore::getRoot), - nb::rv_policy::reference, + nb::rv_policy::reference_internal, "Return pointer to the root Group") .def("getNumBuffers", &DataStore::getNumBuffers, "Return number of Buffers in the DataStore") .def("hasBuffer", @@ -312,16 +327,16 @@ NB_MODULE(pysidre, m_sidre) "Return true if DataStore owns a Buffer with given index; else false") .def("getBuffer", &DataStore::getBuffer, - nb::rv_policy::reference, + nb::rv_policy::reference_internal, "Return pointer to Buffer object with the given index") .def("createBuffer", nb::overload_cast<>(&DataStore::createBuffer), - nb::rv_policy::reference, + nb::rv_policy::reference_internal, "Create an undescribed Buffer object") .def("createBuffer", nb::overload_cast(&DataStore::createBuffer), - nb::rv_policy::reference, + nb::rv_policy::reference_internal, "Create a Buffer object with specified type and number of elements") .def("destroyBuffer", nb::overload_cast(&DataStore::destroyBuffer), @@ -345,7 +360,7 @@ NB_MODULE(pysidre, m_sidre) "Generate a Conduit Blueprint index based on a mesh in stored in this DataStore.") .def("buffers", nb::overload_cast<>(&DataStore::buffers), - nb::rv_policy::reference, + nb::keep_alive<0, 1>(), "Return an iterator over Buffers") .def("getNumAttributes", @@ -353,19 +368,19 @@ NB_MODULE(pysidre, m_sidre) "Return number of Attributes in the DataStore") .def("createAttributeScalar", &DataStore::createAttributeScalar, - nb::rv_policy::reference, + nb::rv_policy::reference_internal, "Create an Attribute object with a default int scalar value", nb::arg("name"), nb::arg("default_value").noconvert()) .def("createAttributeScalar", &DataStore::createAttributeScalar, - nb::rv_policy::reference, + nb::rv_policy::reference_internal, "Create an Attribute object with a default float (C++ double) scalar value", nb::arg("name"), nb::arg("default_value").noconvert()) .def("createAttributeString", &DataStore::createAttributeString, - nb::rv_policy::reference, + nb::rv_policy::reference_internal, "Create an Attribute object with a default string value") .def("hasAttribute", nb::overload_cast(&DataStore::hasAttribute, nb::const_), @@ -387,11 +402,11 @@ NB_MODULE(pysidre, m_sidre) "Remove all Attributes from the DataStore and destroy them and their data") .def("getAttribute", nb::overload_cast(&DataStore::getAttribute), - nb::rv_policy::reference, + nb::rv_policy::reference_internal, "Return pointer to non-const Attribute with given index") .def("getAttribute", nb::overload_cast(&DataStore::getAttribute), - nb::rv_policy::reference, + nb::rv_policy::reference_internal, "Return pointer to non-const Attribute with given name") // Requires conduit::Node information @@ -412,7 +427,7 @@ NB_MODULE(pysidre, m_sidre) "(i.e., smallest index over all Attribute indices larger than given one)") .def("attributes", nb::overload_cast<>(&DataStore::attributes), - nb::rv_policy::reference, + nb::keep_alive<0, 1>(), "Return an iterator over Attributes") // Nanobind fails compilation on blueos @@ -479,12 +494,12 @@ NB_MODULE(pysidre, m_sidre) "Return the full path of the View object, including its name.") .def("getOwningGroup", nb::overload_cast<>(&View::getOwningGroup), - nb::rv_policy::reference, + nb::rv_policy::reference_internal, "Return the owning Group of the View.") .def("hasBuffer", &View::hasBuffer, "Check if the View has an associated Buffer object.") .def("getBuffer", nb::overload_cast<>(&View::getBuffer), - nb::rv_policy::reference, + nb::rv_policy::reference_internal, "Return the associated Buffer object (non-const).") .def("isExternal", &View::isExternal, "Check if the View holds external data.") .def("isAllocated", &View::isAllocated, "Check if the View's data is allocated.") @@ -666,11 +681,11 @@ NB_MODULE(pysidre, m_sidre) // Attribute accessors .def("getAttribute", nb::overload_cast(&View::getAttribute), - nb::rv_policy::reference, + nb::rv_policy::reference_internal, "Get Attribute by index") .def("getAttribute", nb::overload_cast(&View::getAttribute), - nb::rv_policy::reference, + nb::rv_policy::reference_internal, "Get Attribute by name") .def("hasAttributeValue", @@ -836,13 +851,13 @@ NB_MODULE(pysidre, m_sidre) .def("getPathName", &Group::getPathName, "Return full path of Group object, including its name.") .def("getParent", nb::overload_cast<>(&Group::getParent, nb::const_), - nb::rv_policy::reference, + nb::rv_policy::reference_internal, "Return pointer to non-const parent Group of a Group.") .def("getNumGroups", &Group::getNumGroups, "Return number of child Groups in a Group object.") .def("getNumViews", &Group::getNumViews, "Return number of Views owned by a Group object.") .def("getDataStore", nb::overload_cast<>(&Group::getDataStore, nb::const_), - nb::rv_policy::reference, + nb::rv_policy::reference_internal, "Return pointer to non-const DataStore object that owns this object.") .def("hasView", @@ -863,11 +878,11 @@ NB_MODULE(pysidre, m_sidre) .def("getView", nb::overload_cast(&Group::getView, nb::const_), - nb::rv_policy::reference, + nb::rv_policy::reference_internal, "Return pointer to const View with given name or path.") .def("getView", nb::overload_cast(&Group::getView, nb::const_), - nb::rv_policy::reference, + nb::rv_policy::reference_internal, "Return pointer to non-const View with given index.") .def("getFirstValidViewIndex", &Group::getFirstValidViewIndex, @@ -878,11 +893,11 @@ NB_MODULE(pysidre, m_sidre) .def("createView", nb::overload_cast(&Group::createView), - nb::rv_policy::reference, + nb::rv_policy::reference_internal, "Create an undescribed (i.e., empty) View object with given name or path in this Group.") .def("createView", nb::overload_cast(&Group::createView), - nb::rv_policy::reference, + nb::rv_policy::reference_internal, "Create View object with given name or path in this Group that has a data description " "with data type and number of elements.") .def( @@ -890,17 +905,17 @@ NB_MODULE(pysidre, m_sidre) [](Group& self, const std::string& path, TypeID type, int ndims, const nb::ndarray& shape) { return self.createViewWithShape(path, type, ndims, shape.data()); }, - nb::rv_policy::reference, + nb::rv_policy::reference_internal, "Create View object with given name or path in this Group that has a data description " "with data type and shape.") .def("createView", nb::overload_cast(&Group::createView), - nb::rv_policy::reference, + nb::rv_policy::reference_internal, "Create an undescribed View object with given name or path in this Group and attach given " "Buffer to it.") .def("createView", nb::overload_cast(&Group::createView), - nb::rv_policy::reference, + nb::rv_policy::reference_internal, "Create View object with given name or path in this Group that has a data description " "with data type and number of elements and attach given Buffer to it.") .def( @@ -913,7 +928,7 @@ NB_MODULE(pysidre, m_sidre) Buffer* buffer) { return self.createViewWithShape(path, type, ndims, shape.data(), buffer); }, - nb::rv_policy::reference, + nb::rv_policy::reference_internal, "Create View object with given name or path in this Group that has a data description " "with data type and shape and attach given Buffer to it.") @@ -922,14 +937,14 @@ NB_MODULE(pysidre, m_sidre) [](Group& self, const std::string& path, const nb::ndarray<>& a) { return self.createView(path, a.data()); }, - nb::rv_policy::reference) + nb::rv_policy::reference_internal) .def( "createView", [](Group& self, const std::string& path, TypeID id, IndexType num_elems, const nb::ndarray<>& a) { return self.createView(path, id, num_elems, a.data()); }, - nb::rv_policy::reference, + nb::rv_policy::reference_internal, "Create View object with given name or path in this Group that has a data description " "with data type and number of elements and attach externally-owned data to it.") @@ -943,12 +958,12 @@ NB_MODULE(pysidre, m_sidre) const nb::ndarray<>& external_ptr) { return self.createViewWithShape(path, type, ndims, shape.data(), external_ptr.data()); }, - nb::rv_policy::reference, + nb::rv_policy::reference_internal, "Create View object with given name or path in this Group that has a data description " "with data type and shape and attach externally-owned data (numpy array) to it.") .def("createViewAndAllocate", nb::overload_cast(&Group::createViewAndAllocate), - nb::rv_policy::reference, + nb::rv_policy::reference_internal, "Create View object with given name or path in this Group that has a data description " "with data type and number of elements and allocate data for it.", nb::arg("path"), @@ -960,13 +975,13 @@ NB_MODULE(pysidre, m_sidre) [](Group& self, const std::string& path, TypeID type, int ndims, const std::vector& shape) { return self.createViewWithShapeAndAllocate(path, type, ndims, shape.data()); }, - nb::rv_policy::reference, + nb::rv_policy::reference_internal, "Create View object with given name or path in this Group that has a data description " "with data type and shape and allocate data for it.") .def("createViewScalar", &Group::createViewScalar, - nb::rv_policy::reference, + nb::rv_policy::reference_internal, "Create View object with given name or path in this Group set its data to given scalar " "value (int).", nb::arg("path"), @@ -974,7 +989,7 @@ NB_MODULE(pysidre, m_sidre) nb::arg("allocID") = INVALID_ALLOCATOR_ID) .def("createViewScalar", &Group::createViewScalar, - nb::rv_policy::reference, + nb::rv_policy::reference_internal, "Create View object with given name or path in this Group set its data to given scalar " "value (C++ double, python float).", nb::arg("path"), @@ -982,7 +997,7 @@ NB_MODULE(pysidre, m_sidre) nb::arg("allocID") = INVALID_ALLOCATOR_ID) .def("createViewString", &Group::createViewString, - nb::rv_policy::reference, + nb::rv_policy::reference_internal, "Create View object with given name or path in this Group set its data to given string.", nb::arg("path"), nb::arg("value").noconvert(), @@ -1005,11 +1020,11 @@ NB_MODULE(pysidre, m_sidre) .def("moveView", &Group::moveView, - nb::rv_policy::reference, + nb::rv_policy::reference_internal, "Remove given View object from its owning Group and move it to this Group.") .def("copyView", &Group::copyView, - nb::rv_policy::reference, + nb::rv_policy::reference_internal, "Create a (shallow) copy of given View object and add it to this Group.") .def("hasGroup", @@ -1029,19 +1044,19 @@ NB_MODULE(pysidre, m_sidre) "Return the name of immediate child Group with given index.") .def("getGroup", nb::overload_cast(&Group::getGroup), - nb::rv_policy::reference, + nb::rv_policy::reference_internal, "Return pointer to non-const child Group with given name or path.") .def("getGroup", nb::overload_cast(&Group::getGroup), - nb::rv_policy::reference, + nb::rv_policy::reference_internal, "Return pointer to non-const immediate child Group with given index.") .def("views", nb::overload_cast<>(&Group::views), - nb::rv_policy::reference, + nb::keep_alive<0, 1>(), "Return an iterator over Views") .def("groups", nb::overload_cast<>(&Group::groups), - nb::rv_policy::reference, + nb::keep_alive<0, 1>(), "Return an iterator over Groups") .def("getFirstValidGroupIndex", &Group::getFirstValidGroupIndex, @@ -1051,14 +1066,14 @@ NB_MODULE(pysidre, m_sidre) "Return next valid child Group index after given index.") .def("createGroup", &Group::createGroup, - nb::rv_policy::reference, + nb::rv_policy::reference_internal, "Create a child Group within this Group with given name or path.", nb::arg("path"), nb::arg("is_list") = false, nb::arg("accept_existing") = false) .def("createUnnamedGroup", &Group::createUnnamedGroup, - nb::rv_policy::reference, + nb::rv_policy::reference_internal, "Create a child Group within this Group with no name.", nb::arg("is_list") = false) .def("destroyGroup", @@ -1089,12 +1104,12 @@ NB_MODULE(pysidre, m_sidre) "Remove given Group object from its parent Group and make it a child of this Group.") .def("copyGroup", &Group::copyGroup, - nb::rv_policy::reference, + nb::rv_policy::reference_internal, "Create a (shallow) copy of Group hierarchy rooted at given " "Group and make the copy a child of this Group.") .def("deepCopyGroup", &Group::deepCopyGroup, - nb::rv_policy::reference, + nb::rv_policy::reference_internal, "Create a deep copy of Group hierarchy rooted at given Group and " "make the copy a child of this Group.", nb::arg("srcGroup"), diff --git a/src/axom/sidre/tests/CMakeLists.txt b/src/axom/sidre/tests/CMakeLists.txt index 53c153e580..fbaeebf93e 100644 --- a/src/axom/sidre/tests/CMakeLists.txt +++ b/src/axom/sidre/tests/CMakeLists.txt @@ -56,6 +56,7 @@ set(python_sidre_tests sidre_view_Py.py sidre_external_Py.py sidre_attribute_Py.py + sidre_lifetime_Py.py ) set(sidre_gtests_depends_on sidre fmt gtest) diff --git a/src/axom/sidre/tests/sidre_lifetime_Py.py b/src/axom/sidre/tests/sidre_lifetime_Py.py new file mode 100644 index 0000000000..2368c2c6c8 --- /dev/null +++ b/src/axom/sidre/tests/sidre_lifetime_Py.py @@ -0,0 +1,306 @@ +# 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) +"""Lifetime-soundness regression tests for the sidre python bindings. + +Each test obtains a sidre-owned object (child proxy, ancestor proxy, harvested +iterator element, or zero-copy numpy array), drops every Python owner, forces a +garbage collection, and then *uses* the object. Before the lifetime audit these +patterns dereferenced freed memory and segfaulted; with reference_internal on +owner-chain accessors, keep_alive on iterator elements, and self-as-owner on +returned arrays, the keep_alive graph keeps the backing DataStore alive and the +accesses are safe. + +These tests therefore only "pass" against the audited bindings; against the +prior bindings they crash the interpreter (the failure mode the audit fixes). +""" + +import gc + +import numpy as np +import pytest + +import pysidre + + +def _force_gc(): + # A couple of passes to be robust to reference cycles in the proxies. + for _ in range(3): + gc.collect() + + +# --------------------------------------------------------------------------- +# Child proxies must pin their owner chain (parent -> owned child) +# --------------------------------------------------------------------------- +def test_root_outlives_datastore(): + ds = pysidre.DataStore() + root = ds.getRoot() + del ds + _force_gc() + # root must keep its DataStore alive + assert root.getNumViews() == 0 + root.createView("v") + assert root.hasView("v") + + +def test_child_group_outlives_datastore(): + ds = pysidre.DataStore() + grp = ds.getRoot().createGroup("a/b/c") + del ds + _force_gc() + # grp pins parent group pins root pins DataStore + assert grp.getName() == "c" + assert grp.getPathName() == "a/b/c" + + +def test_view_outlives_datastore(): + ds = pysidre.DataStore() + view = ds.getRoot().createViewAndAllocate("field", pysidre.TypeID.INT_ID, 4) + del ds + _force_gc() + assert view.getNumElements() == 4 + assert view.getName() == "field" + + +def test_buffer_outlives_datastore(): + ds = pysidre.DataStore() + buff = ds.createBuffer(pysidre.TypeID.INT_ID, 8) + buff.allocate() + del ds + _force_gc() + assert buff.getNumElements() == 8 + + +# --------------------------------------------------------------------------- +# Ancestor proxies (child -> ancestor) must pin the object they were minted from +# --------------------------------------------------------------------------- +def test_owning_group_outlives_datastore(): + ds = pysidre.DataStore() + view = ds.getRoot().createView("v") + owner = view.getOwningGroup() + del ds + del view + _force_gc() + assert owner.hasView("v") + + +def test_get_datastore_back_reference(): + ds = pysidre.DataStore() + grp = ds.getRoot().createGroup("child") + back = grp.getDataStore() + del ds + del grp + _force_gc() + # back-reference keeps the store alive; reach a fresh proxy through it + assert back.getRoot().hasGroup("child") + + +def test_view_buffer_back_reference(): + ds = pysidre.DataStore() + view = ds.getRoot().createViewAndAllocate("field", pysidre.TypeID.INT_ID, 4) + buff = view.getBuffer() + del ds + del view + _force_gc() + assert buff.getNumElements() == 4 + + +# --------------------------------------------------------------------------- +# Iterator elements harvested into a list must outlive the collection + store +# --------------------------------------------------------------------------- +def test_harvested_views_outlive_datastore(): + ds = pysidre.DataStore() + root = ds.getRoot() + for i in range(4): + root.createView(f"v{i}") + harvested = list(root.views()) + del ds + del root + _force_gc() + names = sorted(v.getName() for v in harvested) + assert names == ["v0", "v1", "v2", "v3"] + + +def test_harvested_groups_outlive_datastore(): + ds = pysidre.DataStore() + root = ds.getRoot() + for i in range(3): + root.createGroup(f"g{i}") + harvested = list(root.groups()) + del ds + del root + _force_gc() + names = sorted(g.getName() for g in harvested) + assert names == ["g0", "g1", "g2"] + + +def test_harvested_buffers_outlive_datastore(): + ds = pysidre.DataStore() + for _ in range(3): + ds.createBuffer(pysidre.TypeID.INT_ID, 2).allocate() + harvested = list(ds.buffers()) + del ds + _force_gc() + assert sorted(b.getNumElements() for b in harvested) == [2, 2, 2] + + +def test_harvested_attributes_outlive_datastore(): + ds = pysidre.DataStore() + ds.createAttributeString("a0", "x") + ds.createAttributeScalar("a1", 42) + harvested = list(ds.attributes()) + del ds + _force_gc() + assert sorted(a.getName() for a in harvested) == ["a0", "a1"] + + +# --------------------------------------------------------------------------- +# Iterator adaptors must outlive the owning Group/DataStore +# --------------------------------------------------------------------------- +def test_views_adaptor_outlives_group_and_datastore(): + ds = pysidre.DataStore() + root = ds.getRoot() + for i in range(3): + root.createView(f"v{i}") + adaptor = root.views() + del root + del ds + _force_gc() + assert sorted(v.getName() for v in adaptor) == ["v0", "v1", "v2"] + + +def test_groups_adaptor_outlives_group_and_datastore(): + ds = pysidre.DataStore() + root = ds.getRoot() + for i in range(3): + root.createGroup(f"g{i}") + adaptor = root.groups() + del root + del ds + _force_gc() + assert sorted(g.getName() for g in adaptor) == ["g0", "g1", "g2"] + + +def test_buffers_adaptor_outlives_datastore(): + ds = pysidre.DataStore() + for _ in range(3): + ds.createBuffer(pysidre.TypeID.INT_ID, 2).allocate() + adaptor = ds.buffers() + del ds + _force_gc() + assert sorted(b.getNumElements() for b in adaptor) == [2, 2, 2] + + +def test_attributes_adaptor_outlives_datastore(): + ds = pysidre.DataStore() + ds.createAttributeString("a0", "x") + ds.createAttributeScalar("a1", 42) + adaptor = ds.attributes() + del ds + _force_gc() + assert sorted(a.getName() for a in adaptor) == ["a0", "a1"] + + +# --------------------------------------------------------------------------- +# Lookup accessors should return proxies that pin their owner chain +# --------------------------------------------------------------------------- +def test_get_view_outlives_group_and_datastore(): + ds = pysidre.DataStore() + root = ds.getRoot() + root.createView("v") + view = root.getView("v") + del root + del ds + _force_gc() + assert view.getName() == "v" + + +def test_get_group_outlives_group_and_datastore(): + ds = pysidre.DataStore() + root = ds.getRoot() + root.createGroup("g") + grp = root.getGroup("g") + del root + del ds + _force_gc() + assert grp.getName() == "g" + + +def test_get_buffer_outlives_datastore(): + ds = pysidre.DataStore() + ds.createBuffer(pysidre.TypeID.INT_ID, 7).allocate() + buff = ds.getBuffer(0) + del ds + _force_gc() + assert buff.getNumElements() == 7 + + +def test_get_attribute_outlives_datastore(): + ds = pysidre.DataStore() + ds.createAttributeString("a0", "x") + attr = ds.getAttribute("a0") + del ds + _force_gc() + assert attr.getName() == "a0" + + +def test_parent_group_outlives_datastore(): + ds = pysidre.DataStore() + child = ds.getRoot().createGroup("a/b") + parent = child.getParent() + del ds + del child + _force_gc() + assert parent.getName() == "a" + assert parent.hasGroup("b") + + +# --------------------------------------------------------------------------- +# Zero-copy numpy arrays must pin their backing View / Buffer (and DataStore) +# --------------------------------------------------------------------------- +def test_view_array_outlives_datastore(): + ds = pysidre.DataStore() + view = ds.getRoot().createViewAndAllocate("field", pysidre.TypeID.INT_ID, 4) + arr = view.getDataArray() + arr[:] = [10, 20, 30, 40] + del ds + del view + _force_gc() + # array still references valid memory: read and write + assert list(arr) == [10, 20, 30, 40] + arr[0] = 99 + assert arr[0] == 99 + + +def test_buffer_array_outlives_datastore(): + ds = pysidre.DataStore() + buff = ds.createBuffer(pysidre.TypeID.INT_ID, 4) + buff.allocate() + arr = buff.getDataArray() + arr[:] = [1, 2, 3, 4] + del ds + del buff + _force_gc() + assert list(arr) == [1, 2, 3, 4] + + +def test_view_array_survives_owner_chain_collection(): + # Keep only the array; let the entire DataStore/Group/View chain be dropped. + def make_array(): + ds = pysidre.DataStore() + view = ds.getRoot().createViewAndAllocate("field", pysidre.TypeID.FLOAT64_ID, 5) + a = view.getDataArray() + a[:] = np.arange(5, dtype=np.float64) + return a + + arr = make_array() + _force_gc() + np.testing.assert_array_equal(arr, np.arange(5, dtype=np.float64)) + + +if __name__ == "__main__": + import sys + + sys.exit(pytest.main([__file__, "-v"])) From 79111798111c41943d36efbb5450e71a930c4b6e Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 17 Jun 2026 11:41:05 -0700 Subject: [PATCH 462/986] pysidre: Allows constructing a spii IOManager w/ a custom MPI comm Also extends our CMake Python test macro to support NUM_MPI_TASKS. --- src/axom/sidre/nanobind_sidre.cpp | 22 ++++++ src/axom/sidre/tests/CMakeLists.txt | 13 ++++ src/axom/sidre/tests/sidre_spio_Py.py | 106 ++++++++++++++++++++++++++ src/cmake/AxomMacros.cmake | 22 ++++-- 4 files changed, 156 insertions(+), 7 deletions(-) create mode 100644 src/axom/sidre/tests/sidre_spio_Py.py diff --git a/src/axom/sidre/nanobind_sidre.cpp b/src/axom/sidre/nanobind_sidre.cpp index be3bd1adec..b53606df69 100644 --- a/src/axom/sidre/nanobind_sidre.cpp +++ b/src/axom/sidre/nanobind_sidre.cpp @@ -1154,6 +1154,9 @@ NB_MODULE(pysidre, m_sidre) .def("loadExternalData", nb::overload_cast(&Group::loadExternalData), "Load data into the Group's external views from a file.") + .def_static("getDefaultIOProtocol", + &Group::getDefaultIOProtocol, + "Return the default I/O protocol for this Axom build.") .def("rename", &Group::rename, "Change the name of this Group."); // Bindings for the Attribute class @@ -1188,6 +1191,25 @@ NB_MODULE(pysidre, m_sidre) .def( "__init__", [](IOManager* self, bool use_scr) { new(self) IOManager(MPI_COMM_WORLD, use_scr); }, + "Create an IOManager on MPI_COMM_WORLD.", + nb::arg("use_scr") = false) + .def( + "__init__", + [](IOManager* self, nb::object comm, bool use_scr) { + // Accept an mpi4py communicator without depending on mpi4py's C headers. + // mpi4py Comm objects expose py2f(), which returns the Fortran integer handle. + // MPI_Comm_f2c converts that handle back to an MPI_Comm. + // Passing None uses MPI_COMM_WORLD, while no-argument calls are handled by the bool/use_scr overload above. + MPI_Comm c = MPI_COMM_WORLD; + if(!comm.is_none()) + { + MPI_Fint handle = nb::cast(comm.attr("py2f")()); + c = MPI_Comm_f2c(handle); + } + new(self) IOManager(c, use_scr); + }, + "Create an IOManager on an mpi4py communicator, or MPI_COMM_WORLD when comm is None.", + nb::arg("comm"), nb::arg("use_scr") = false) .def("write", &IOManager::write, diff --git a/src/axom/sidre/tests/CMakeLists.txt b/src/axom/sidre/tests/CMakeLists.txt index fbaeebf93e..ebd5af7ce2 100644 --- a/src/axom/sidre/tests/CMakeLists.txt +++ b/src/axom/sidre/tests/CMakeLists.txt @@ -127,6 +127,19 @@ if(NANOBIND_FOUND AND AXOM_ENABLE_PYTHON_TESTS) OUTPUT_DIR ${TEST_OUTPUT_DIRECTORY} ) endforeach() + + if(AXOM_ENABLE_MPI) + set(_sidre_spio_py_num_ranks 4) + else() + set(_sidre_spio_py_num_ranks 1) + endif() + + axom_add_python_test( NAME sidre_spio_Py + SOURCE sidre_spio_Py.py + OUTPUT_DIR ${TEST_OUTPUT_DIRECTORY} + NUM_MPI_TASKS ${_sidre_spio_py_num_ranks} + ) + unset(_sidre_spio_py_num_ranks) endif() #------------------------------------------------------------------------------ diff --git a/src/axom/sidre/tests/sidre_spio_Py.py b/src/axom/sidre/tests/sidre_spio_Py.py new file mode 100644 index 0000000000..cb81374037 --- /dev/null +++ b/src/axom/sidre/tests/sidre_spio_Py.py @@ -0,0 +1,106 @@ +# 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) +"""IOManager communicator-construction tests. + +These exercise the IOManager constructor's optional mpi4py communicator argument: +the default (MPI_COMM_WORLD) path and an explicit split-communicator path. +They are skipped unless the bindings were built with MPI and mpi4py is importable. + +Run multi-rank, e.g.: + mpirun -n 2 python -m pytest sidre_spio_Py.py +""" + +import os + +import pytest + +import pysidre + +if not pysidre.AXOM_ENABLE_MPI: + pytest.skip("pysidre built without MPI", allow_module_level=True) + +mpi4py = pytest.importorskip("mpi4py") +from mpi4py import MPI # noqa: E402 + + +def _shared_base(tmp_path, name): + world = MPI.COMM_WORLD + base_dir = world.bcast(str(tmp_path) if world.Get_rank() == 0 else None, root=0) + return os.path.join(base_dir, name) + + +def _fill_datastore(): + ds = pysidre.DataStore() + root = ds.getRoot() + view = root.createViewAndAllocate("field", pysidre.TypeID.INT_ID, 4) + rank = MPI.COMM_WORLD.Get_rank() + view.getDataArray()[:] = [rank, rank + 1, rank + 2, rank + 3] + return ds + + +def test_iomanager_legacy_use_scr_constructor(): + # Preserve the previous IOManager(use_scr=False) positional API. + pysidre.IOManager(False) + + +def test_iomanager_rejects_non_communicator(): + with pytest.raises(AttributeError): + pysidre.IOManager(object()) + + +def test_iomanager_default_communicator(tmp_path): + # No communicator argument -> MPI_COMM_WORLD (preserves the prior behavior). + world = MPI.COMM_WORLD + ds = _fill_datastore() + iom = pysidre.IOManager() + base = _shared_base(tmp_path, "default_comm") + iom.write(ds.getRoot(), 1, base, pysidre.Group.getDefaultIOProtocol()) + world.Barrier() + assert iom.getNumFilesFromRoot(base + ".root") == 1 + assert iom.getNumGroupsFromRoot(base + ".root") == world.Get_size() + + +def test_iomanager_explicit_world_communicator(tmp_path): + # Passing COMM_WORLD explicitly must match the default path. + world = MPI.COMM_WORLD + ds = _fill_datastore() + iom = pysidre.IOManager(MPI.COMM_WORLD) + base = _shared_base(tmp_path, "explicit_world") + iom.write(ds.getRoot(), 1, base, pysidre.Group.getDefaultIOProtocol()) + world.Barrier() + + ds_in = pysidre.DataStore() + iom.read(ds_in.getRoot(), base + ".root") + arr = ds_in.getRoot().getView("field").getDataArray() + rank = world.Get_rank() + assert list(arr) == [rank, rank + 1, rank + 2, rank + 3] + + +def test_iomanager_split_communicator(tmp_path): + # Construct from a split communicator; each writes its own dataset. + world = MPI.COMM_WORLD + if world.Get_size() < 2: + pytest.skip("split-communicator test needs >= 2 ranks") + + color = world.Get_rank() % 2 + sub = world.Split(color=color, key=world.Get_rank()) + try: + ds = _fill_datastore() + iom = pysidre.IOManager(sub) + # tmp_path differs per rank; rendezvous on a shared, rank-0-broadcast dir + base = _shared_base(tmp_path, f"split_color{color}") + iom.write(ds.getRoot(), 1, base, pysidre.Group.getDefaultIOProtocol()) + sub.Barrier() + assert iom.getNumFilesFromRoot(base + ".root") == 1 + assert iom.getNumGroupsFromRoot(base + ".root") == sub.Get_size() + finally: + sub.Free() + + +if __name__ == "__main__": + import sys + + sys.exit(pytest.main([__file__, "-v"])) diff --git a/src/cmake/AxomMacros.cmake b/src/cmake/AxomMacros.cmake index 5a092650e3..a6de50ebff 100644 --- a/src/cmake/AxomMacros.cmake +++ b/src/cmake/AxomMacros.cmake @@ -626,9 +626,10 @@ function(axom_python_test_environment output_var) endfunction() ##------------------------------------------------------------------------------ -## axom_add_python_test(NAME [name] -## SOURCE [source] -## OUTPUT_DIR [dir]) +## axom_add_python_test(NAME [name] +## SOURCE [source] +## OUTPUT_DIR [dir] +## NUM_MPI_TASKS [n]) ## ## Wrapper around add_test() that handles functionality ## that Axom applies to all python tests. @@ -636,7 +637,7 @@ endfunction() macro(axom_add_python_test) set(options) - set(singleValueArgs NAME SOURCE OUTPUT_DIR) + set(singleValueArgs NAME SOURCE OUTPUT_DIR NUM_MPI_TASKS) set(multiValueArgs) # Parse the arguments to the macro @@ -651,15 +652,22 @@ macro(axom_add_python_test) # The run_python_with_axom.sh wrapper provides the runtime environment # and the testing dependencies are injected via the test's ENVIRONMENT property when provided. # "-p no:cacheprovider" disables caching. - add_test (NAME ${arg_NAME} - COMMAND ${PROJECT_BINARY_DIR}/bin/run_python_with_axom.sh -m pytest -s -p no:cacheprovider ${arg_OUTPUT_DIR}/${arg_SOURCE} - ) + set(_test_command ${PROJECT_BINARY_DIR}/bin/run_python_with_axom.sh + -m pytest -s -p no:cacheprovider ${arg_OUTPUT_DIR}/${arg_SOURCE}) + blt_add_test(NAME ${arg_NAME} + COMMAND ${_test_command} + NUM_MPI_TASKS ${arg_NUM_MPI_TASKS}) + + set_property(TEST ${arg_NAME} + APPEND + PROPERTY ENVIRONMENT "OMPI_MCA_rmaps_base_oversubscribe=1") axom_python_test_environment(_py_test_env) if(_py_test_env) set_tests_properties(${arg_NAME} PROPERTIES ENVIRONMENT "${_py_test_env}") endif() unset(_py_test_env) + unset(_test_command) endmacro(axom_add_python_test) From dc6a5428700b2057ac9189bf7c4ce9eaeffda410 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 17 Jun 2026 12:14:05 -0700 Subject: [PATCH 463/986] pysidre: Adds bindings for generateBlueprintIndex in nanobind>=2.10 Also removes some CMake logic for blueos. --- src/axom/sidre/nanobind_sidre.cpp | 40 +++++++++++++++---- src/axom/sidre/tests/sidre_spio_Py.py | 26 ++++++++++++ .../thirdparty/SetupAxomThirdParty.cmake | 7 ++-- 3 files changed, 62 insertions(+), 11 deletions(-) diff --git a/src/axom/sidre/nanobind_sidre.cpp b/src/axom/sidre/nanobind_sidre.cpp index b53606df69..60a9f96fbc 100644 --- a/src/axom/sidre/nanobind_sidre.cpp +++ b/src/axom/sidre/nanobind_sidre.cpp @@ -281,6 +281,13 @@ NB_MODULE(pysidre, m_sidre) m_sidre.attr("AXOM_ENABLE_MPI") = false; #endif +#if defined(AXOM_USE_MPI) && \ + ((NB_VERSION_MAJOR > 2) || (NB_VERSION_MAJOR == 2 && NB_VERSION_MINOR >= 10)) + m_sidre.attr("AXOM_HAS_DISTRIBUTED_BLUEPRINT_INDEX_BINDING") = true; +#else + m_sidre.attr("AXOM_HAS_DISTRIBUTED_BLUEPRINT_INDEX_BINDING") = false; +#endif + // Bind the DataTypeId enum (TypeID alias) nb::enum_(m_sidre, "TypeID") .value("NO_TYPE_ID", NO_TYPE_ID) @@ -430,13 +437,32 @@ NB_MODULE(pysidre, m_sidre) nb::keep_alive<0, 1>(), "Return an iterator over Attributes") - // Nanobind fails compilation on blueos - // #ifdef AXOM_USE_MPI - // .def("generateBlueprintIndex", - // nb::overload_cast( - // &DataStore::generateBlueprintIndex), - // "Generate a Conduit Blueprint index from a distributed mesh stored in this Datastore") - // #endif +#if defined(AXOM_USE_MPI) && \ + ((NB_VERSION_MAJOR > 2) || (NB_VERSION_MAJOR == 2 && NB_VERSION_MINOR >= 10)) + // Distributed Blueprint index generation builds cleanly under nanobind >= 2.10 + .def( + "generateBlueprintIndex", + [](DataStore& self, + nb::object comm, + const std::string& domain_path, + const std::string& mesh_name, + const std::string& index_path) { + MPI_Comm c = MPI_COMM_WORLD; + if(!comm.is_none()) + { + MPI_Fint handle = nb::cast(comm.attr("py2f")()); + c = MPI_Comm_f2c(handle); + } + return self.generateBlueprintIndex(c, domain_path, mesh_name, index_path); + }, + "Generate a Conduit Blueprint index from a distributed mesh stored in this " + "DataStore. Pass None to use MPI_COMM_WORLD.", + nb::arg("comm").none(), + nb::arg("domain_path"), + nb::arg("mesh_name"), + nb::arg("index_path")) +#endif + .def("print", nb::overload_cast<>(&DataStore::print, nb::const_), "Print JSON description of the DataStore"); diff --git a/src/axom/sidre/tests/sidre_spio_Py.py b/src/axom/sidre/tests/sidre_spio_Py.py index cb81374037..a8922ccf43 100644 --- a/src/axom/sidre/tests/sidre_spio_Py.py +++ b/src/axom/sidre/tests/sidre_spio_Py.py @@ -100,6 +100,32 @@ def test_iomanager_split_communicator(tmp_path): sub.Free() +def test_distributed_generate_blueprint_index(tmp_path): + # The distributed generateBlueprintIndex overload is built only under + # nanobind >= 2.10; skip cleanly if this build omitted it. + if not pysidre.AXOM_HAS_DISTRIBUTED_BLUEPRINT_INDEX_BINDING: + pytest.skip("generateBlueprintIndex not bound") + + ds = pysidre.DataStore() + root = ds.getRoot() + mesh = root.createGroup("mesh") + coords = mesh.createGroup("coordsets/coords") + coords.createViewString("type", "explicit") + topo = mesh.createGroup("topologies/topo") + topo.createViewString("type", "unstructured") + topo.createViewString("coordset", "coords") + + base_dir = MPI.COMM_WORLD.bcast(str(tmp_path) if MPI.COMM_WORLD.Get_rank() == 0 else None, + root=0) + out = os.path.join(base_dir, "bp_index") + + # Distributed signature: (comm, domain_path, mesh_name, index_path). + # We only assert the call is reachable and returns a bool; full Blueprint + # validity is covered by the C++ spio tests. + result = ds.generateBlueprintIndex(MPI.COMM_WORLD, "mesh", "mesh", out) + assert isinstance(result, bool) + + if __name__ == "__main__": import sys diff --git a/src/cmake/thirdparty/SetupAxomThirdParty.cmake b/src/cmake/thirdparty/SetupAxomThirdParty.cmake index 13f5f091ad..c46f1c6966 100644 --- a/src/cmake/thirdparty/SetupAxomThirdParty.cmake +++ b/src/cmake/thirdparty/SetupAxomThirdParty.cmake @@ -400,15 +400,14 @@ if(AXOM_ENABLE_MPI "PY_MPI4PY_DIR") endif() -# "cannot allocate memory in static TLS block" on blueos with cuda and/or clang. +# nanobind extensions have hit "cannot allocate memory in static TLS block" +# when the module is loaded into an interpreter alongside CUDA runtime initialization. # Also disable when sanitizers are enabled, requires environment variable manipulation: # https://stackoverflow.com/questions/55692357/address-sanitizer-on-a-python-extension if(nanobind_ROOT AND NOT AXOM_ENABLE_CUDA AND NOT AXOM_ENABLE_ASAN - AND NOT AXOM_ENABLE_UBSAN - AND ((NOT "$ENV{SYS_TYPE}" STREQUAL "blueos_3_ppc64le_ib_p9") - OR ("$ENV{SYS_TYPE}" STREQUAL "blueos_3_ppc64le_ib_p9" AND NOT "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang"))) + AND NOT AXOM_ENABLE_UBSAN) axom_assert_is_directory(DIR_VARIABLE nanobind_ROOT) find_package(nanobind CONFIG REQUIRED) From e5c2650ae081dfd1426fbfbe8b848bfd375e3c38 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 17 Jun 2026 12:31:00 -0700 Subject: [PATCH 464/986] pysidre: Generates and installs type stubs And uses types in a pysidre test (createdatastore). This allows pysidre to work better with IDEs and tools like mypy and pyright. --- src/axom/sidre/CMakeLists.txt | 26 +++++++++++++++++++ .../examples/sidre_createdatastore_Py.py | 7 ++--- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/axom/sidre/CMakeLists.txt b/src/axom/sidre/CMakeLists.txt index ebf689d416..750473d630 100644 --- a/src/axom/sidre/CMakeLists.txt +++ b/src/axom/sidre/CMakeLists.txt @@ -155,6 +155,32 @@ if(NANOBIND_FOUND) endif() install(TARGETS pysidre LIBRARY DESTINATION lib) + + # Type stubs (PEP 561). nanobind_add_stub imports the module to introspect it, + # so its runtime dependencies (conduit for Node interop, numpy for ndarray returns) + # must be importable during the build. We seed PYTHON_PATH with the module's + # output directory plus the conduit/numpy install dirs from their cache variables when set; + # on an interpreter that already has conduit and numpy on its path these extra entries are harmless. + set(_pysidre_stub_pythonpath $) + if(CONDUIT_PYTHON_MODULE_DIR) + list(APPEND _pysidre_stub_pythonpath ${CONDUIT_PYTHON_MODULE_DIR}) + endif() + if(PY_NUMPY_DIR) + list(APPEND _pysidre_stub_pythonpath ${PY_NUMPY_DIR}) + endif() + + nanobind_add_stub( + pysidre_stub + MODULE pysidre + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/pysidre.pyi" + MARKER_FILE "${CMAKE_CURRENT_BINARY_DIR}/py.typed" + PYTHON_PATH ${_pysidre_stub_pythonpath} + DEPENDS pysidre) + + # Install the stub and py.typed marker next to the extension module so type checkers (mypy, pyright) can find them + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/pysidre.pyi" + "${CMAKE_CURRENT_BINARY_DIR}/py.typed" + DESTINATION lib) endif() diff --git a/src/axom/sidre/examples/sidre_createdatastore_Py.py b/src/axom/sidre/examples/sidre_createdatastore_Py.py index 292fd22774..0e8961cc43 100644 --- a/src/axom/sidre/examples/sidre_createdatastore_Py.py +++ b/src/axom/sidre/examples/sidre_createdatastore_Py.py @@ -6,13 +6,14 @@ import pysidre import numpy as np +import numpy.typing as npt # This example file is based on sidre_createdatastore.cpp. # The python interface is a work-in-progress, and does not yet support # all the features in the C++ source. -def create_datastore(region): +def create_datastore(region: npt.NDArray[np.int_]) -> pysidre.DataStore: ds = pysidre.DataStore() root = ds.getRoot() @@ -73,7 +74,7 @@ def create_datastore(region): return ds -def access_datastore(ds): +def access_datastore(ds: pysidre.DataStore) -> pysidre.DataStore: # Retrieve Group pointers root = ds.getRoot() state = root.getGroup("state") @@ -107,7 +108,7 @@ def access_datastore(ds): return ds -def iterate_datastore(ds): +def iterate_datastore(ds: pysidre.DataStore) -> None: fill_line = "=" * 80 print(fill_line) From 83f60339f39f767ebfda8d53922b9baa3e087e7f Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 17 Jun 2026 13:25:07 -0700 Subject: [PATCH 465/986] pysidre: Ensures our IOManager wrapper holds onto its MPI comm until it's destroyed --- src/axom/sidre/nanobind_sidre.cpp | 204 +++++++++++++++++++------- src/axom/sidre/tests/sidre_spio_Py.py | 25 +++- 2 files changed, 170 insertions(+), 59 deletions(-) diff --git a/src/axom/sidre/nanobind_sidre.cpp b/src/axom/sidre/nanobind_sidre.cpp index 60a9f96fbc..73e88a6954 100644 --- a/src/axom/sidre/nanobind_sidre.cpp +++ b/src/axom/sidre/nanobind_sidre.cpp @@ -10,6 +10,8 @@ #include #include +#include + #include "axom/config.hpp" #include "axom/core/Types.hpp" #include "core/SidreTypes.hpp" @@ -256,6 +258,73 @@ conduit::Node& nbObjectToNode(nb::object& o) return *cpp_node; } +#if defined(AXOM_USE_MPI) +MPI_Comm mpiCommFromObject(nb::object comm) +{ + MPI_Comm c = MPI_COMM_WORLD; + if(!comm.is_none()) + { + MPI_Fint handle = nb::cast(comm.attr("py2f")()); + c = MPI_Comm_f2c(handle); + } + return c; +} + +/*! + * \brief Python-facing IOManager holder that owns the communicator lifetime. + * + * sidre::IOManager stores the MPI_Comm passed to its constructor but does not + * duplicate or free it. That borrowed-communicator contract works for C++ + * callers, but it is unsafe for mpi4py objects: py2f() exposes the object's + * current communicator handle, and Python code may later drop or explicitly + * Free() that object while pysidre.IOManager is still alive. + * + * PyIOManager keeps the public Python class name as pysidre.IOManager while + * giving the binding its own lifetime boundary. It duplicates the input + * communicator, constructs sidre::IOManager with that duplicate, destroys the + * IOManager first, and then frees the duplicate when MPI is still active. + */ +class PyIOManager +{ +public: + PyIOManager(MPI_Comm comm, bool use_scr) + { + int err = MPI_Comm_dup(comm, &m_comm); + SLIC_ERROR_IF(err != MPI_SUCCESS, "Failed to duplicate MPI communicator for pysidre.IOManager"); + m_manager = std::make_unique(m_comm, use_scr); + } + + ~PyIOManager() + { + // IOManager may still use m_comm during destruction, so release it before + // freeing the duplicated communicator. + m_manager.reset(); + + int initialized = 0; + MPI_Initialized(&initialized); + if(initialized != 0 && m_comm != MPI_COMM_NULL) + { + int finalized = 0; + MPI_Finalized(&finalized); + if(finalized == 0) + { + MPI_Comm_free(&m_comm); + } + } + } + + PyIOManager(const PyIOManager&) = delete; + PyIOManager& operator=(const PyIOManager&) = delete; + + IOManager& manager() { return *m_manager; } + const IOManager& manager() const { return *m_manager; } + +private: + MPI_Comm m_comm {MPI_COMM_NULL}; + std::unique_ptr m_manager; +}; +#endif + NB_MODULE(pysidre, m_sidre) { m_sidre.doc() = "A python extension for Axom's Sidre component"; @@ -448,11 +517,7 @@ NB_MODULE(pysidre, m_sidre) const std::string& mesh_name, const std::string& index_path) { MPI_Comm c = MPI_COMM_WORLD; - if(!comm.is_none()) - { - MPI_Fint handle = nb::cast(comm.attr("py2f")()); - c = MPI_Comm_f2c(handle); - } + c = mpiCommFromObject(comm); return self.generateBlueprintIndex(c, domain_path, mesh_name, index_path); }, "Generate a Conduit Blueprint index from a distributed mesh stored in this " @@ -1213,70 +1278,97 @@ NB_MODULE(pysidre, m_sidre) .def("getTypeID", &Attribute::getTypeID, "Return type of Attribute."); #if defined(AXOM_USE_MPI) - nb::class_(m_sidre, "IOManager") + nb::class_(m_sidre, "IOManager") .def( "__init__", - [](IOManager* self, bool use_scr) { new(self) IOManager(MPI_COMM_WORLD, use_scr); }, + [](PyIOManager* self, bool use_scr) { new(self) PyIOManager(MPI_COMM_WORLD, use_scr); }, "Create an IOManager on MPI_COMM_WORLD.", nb::arg("use_scr") = false) .def( "__init__", - [](IOManager* self, nb::object comm, bool use_scr) { + [](PyIOManager* self, nb::object comm, bool use_scr) { // Accept an mpi4py communicator without depending on mpi4py's C headers. // mpi4py Comm objects expose py2f(), which returns the Fortran integer handle. // MPI_Comm_f2c converts that handle back to an MPI_Comm. - // Passing None uses MPI_COMM_WORLD, while no-argument calls are handled by the bool/use_scr overload above. - MPI_Comm c = MPI_COMM_WORLD; - if(!comm.is_none()) - { - MPI_Fint handle = nb::cast(comm.attr("py2f")()); - c = MPI_Comm_f2c(handle); - } - new(self) IOManager(c, use_scr); + // PyIOManager duplicates the communicator, so callers may drop or free + // the mpi4py communicator after construction. + // Passing None uses MPI_COMM_WORLD, while no-argument calls are + // handled by the bool/use_scr overload above. + new(self) PyIOManager(mpiCommFromObject(comm), use_scr); }, "Create an IOManager on an mpi4py communicator, or MPI_COMM_WORLD when comm is None.", nb::arg("comm"), nb::arg("use_scr") = false) - .def("write", - &IOManager::write, - "Write a Group to output files.", - nb::arg("group"), - nb::arg("num_files"), - nb::arg("file_base"), - nb::arg("protocol"), - nb::arg("tree_pattern") = "datagroup") - .def("read", - nb::overload_cast(&IOManager::read), - "Read from input file.", - nb::arg("group"), - nb::arg("root_file"), - nb::arg("protocol"), - nb::arg("preserve_contents") = false) - .def("read", - nb::overload_cast(&IOManager::read), - "Read from a root file.", - nb::arg("group"), - nb::arg("root_file"), - nb::arg("preserve_contents") = false) - .def("loadExternalData", - nb::overload_cast(&IOManager::loadExternalData), - "Load external data into a group.", - nb::arg("group"), - nb::arg("root_file")) - .def("loadExternalData", - nb::overload_cast(&IOManager::loadExternalData), - "Piecewise load of external data into a group.", - nb::arg("parent_group"), - nb::arg("load_group"), - nb::arg("root_file")) - .def("getNumFilesFromRoot", - &IOManager::getNumFilesFromRoot, - "Gets the number of files in the dataset from the specified root file.", - nb::arg("root_file")) - .def("getNumGroupsFromRoot", - &IOManager::getNumGroupsFromRoot, - "Gets the number of groups in the dataset from the specified root file.", - nb::arg("root_file")) + .def( + "write", + [](PyIOManager& self, + Group* group, + int num_files, + const std::string& file_base, + const std::string& protocol, + const std::string& tree_pattern) { + self.manager().write(group, num_files, file_base, protocol, tree_pattern); + }, + "Write a Group to output files.", + nb::arg("group"), + nb::arg("num_files"), + nb::arg("file_base"), + nb::arg("protocol"), + nb::arg("tree_pattern") = "datagroup") + .def( + "read", + [](PyIOManager& self, + Group* group, + const std::string& root_file, + const std::string& protocol, + bool preserve_contents) { + self.manager().read(group, root_file, protocol, preserve_contents); + }, + "Read from input file.", + nb::arg("group"), + nb::arg("root_file"), + nb::arg("protocol"), + nb::arg("preserve_contents") = false) + .def( + "read", + [](PyIOManager& self, Group* group, const std::string& root_file, bool preserve_contents) { + self.manager().read(group, root_file, preserve_contents); + }, + "Read from a root file.", + nb::arg("group"), + nb::arg("root_file"), + nb::arg("preserve_contents") = false) + .def( + "loadExternalData", + [](PyIOManager& self, Group* group, const std::string& root_file) { + self.manager().loadExternalData(group, root_file); + }, + "Load external data into a group.", + nb::arg("group"), + nb::arg("root_file")) + .def( + "loadExternalData", + [](PyIOManager& self, Group* parent_group, Group* load_group, const std::string& root_file) { + self.manager().loadExternalData(parent_group, load_group, root_file); + }, + "Piecewise load of external data into a group.", + nb::arg("parent_group"), + nb::arg("load_group"), + nb::arg("root_file")) + .def( + "getNumFilesFromRoot", + [](PyIOManager& self, const std::string& root_file) { + return self.manager().getNumFilesFromRoot(root_file); + }, + "Gets the number of files in the dataset from the specified root file.", + nb::arg("root_file")) + .def( + "getNumGroupsFromRoot", + [](PyIOManager& self, const std::string& root_file) { + return self.manager().getNumGroupsFromRoot(root_file); + }, + "Gets the number of groups in the dataset from the specified root file.", + nb::arg("root_file")) .def_static("correspondingRelayProtocol", &IOManager::correspondingRelayProtocol, "Finds conduit relay protocol corresponding to a sidre protocol."); diff --git a/src/axom/sidre/tests/sidre_spio_Py.py b/src/axom/sidre/tests/sidre_spio_Py.py index a8922ccf43..ff42e277cb 100644 --- a/src/axom/sidre/tests/sidre_spio_Py.py +++ b/src/axom/sidre/tests/sidre_spio_Py.py @@ -79,6 +79,21 @@ def test_iomanager_explicit_world_communicator(tmp_path): assert list(arr) == [rank, rank + 1, rank + 2, rank + 3] +def test_iomanager_owned_duplicate_survives_comm_free(tmp_path): + # IOManager duplicates the input communicator, so callers may free their + # mpi4py communicator after construction. + comm = MPI.COMM_SELF.Dup() + iom = pysidre.IOManager(comm) + comm.Free() + + ds = _fill_datastore() + base = _shared_base(tmp_path, f"freed_comm_rank{MPI.COMM_WORLD.Get_rank()}") + iom.write(ds.getRoot(), 1, base, pysidre.Group.getDefaultIOProtocol()) + assert iom.getNumFilesFromRoot(base + ".root") == 1 + assert iom.getNumGroupsFromRoot(base + ".root") == 1 + MPI.COMM_WORLD.Barrier() + + def test_iomanager_split_communicator(tmp_path): # Construct from a split communicator; each writes its own dataset. world = MPI.COMM_WORLD @@ -87,17 +102,21 @@ def test_iomanager_split_communicator(tmp_path): color = world.Get_rank() % 2 sub = world.Split(color=color, key=world.Get_rank()) + sub_freed = False try: + sub_size = sub.Get_size() ds = _fill_datastore() iom = pysidre.IOManager(sub) + sub.Free() + sub_freed = True # tmp_path differs per rank; rendezvous on a shared, rank-0-broadcast dir base = _shared_base(tmp_path, f"split_color{color}") iom.write(ds.getRoot(), 1, base, pysidre.Group.getDefaultIOProtocol()) - sub.Barrier() assert iom.getNumFilesFromRoot(base + ".root") == 1 - assert iom.getNumGroupsFromRoot(base + ".root") == sub.Get_size() + assert iom.getNumGroupsFromRoot(base + ".root") == sub_size finally: - sub.Free() + if not sub_freed: + sub.Free() def test_distributed_generate_blueprint_index(tmp_path): From a74d8845f30a6687dba41dbd26f34ad5c61fb7ef Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 17 Jun 2026 14:02:42 -0700 Subject: [PATCH 466/986] pysidre: Handles lifetime-related issues for external data --- src/axom/sidre/nanobind_sidre.cpp | 263 ++++++++++++++++++---- src/axom/sidre/tests/sidre_lifetime_Py.py | 87 +++++++ 2 files changed, 304 insertions(+), 46 deletions(-) diff --git a/src/axom/sidre/nanobind_sidre.cpp b/src/axom/sidre/nanobind_sidre.cpp index 73e88a6954..1cd3005dd1 100644 --- a/src/axom/sidre/nanobind_sidre.cpp +++ b/src/axom/sidre/nanobind_sidre.cpp @@ -11,6 +11,7 @@ #include #include +#include #include "axom/config.hpp" #include "axom/core/Types.hpp" @@ -258,6 +259,109 @@ conduit::Node& nbObjectToNode(nb::object& o) return *cpp_node; } +/*! + * \brief Binding-side owner pinning for external NumPy-backed Sidre views. + * + * Sidre external views (View::setExternalDataPtr / Group::createView(..., void*)) + * borrow a raw pointer and do not own the storage. In Python, it is common to + * pass a temporary NumPy array (e.g. createView("x", np.arange(...))) and then + * drop it immediately, which can leave Sidre holding a dangling pointer once + * the ndarray is garbage collected. + * + * To keep Sidre's C++ semantics unchanged while making the Python API safe, + * we maintain a binding-only registry that maps a C++ View* to a copied + * nanobind::ndarray wrapper. Copying nb::ndarray increments the underlying + * ndarray owner's refcount via nanobind's internal handle, so the NumPy storage + * remains alive as long as the View exists. + * + * Pins are released when the external pointer is cleared (e.g. View.clear(), + * setExternalData(None)) and when views/groups are destroyed via the bound Group::destroy* APIs. + */ +std::unordered_map>& externalDataOwnerRegistry() +{ + // Intentionally heap-allocated so Python-owned references are not destroyed + // after interpreter finalization during static shutdown. + static auto* registry = new std::unordered_map>(); + return *registry; +} + +template +void pinExternalDataOwner(View* view, const nb::ndarray& owner) +{ + if(view != nullptr) + { + externalDataOwnerRegistry()[view] = nb::ndarray<>(owner); + } +} + +void releaseExternalDataOwner(View* view) +{ + if(view != nullptr) + { + externalDataOwnerRegistry().erase(view); + } +} + +void releaseExternalDataOwners(Group* group) +{ + if(group == nullptr) + { + return; + } + + for(auto& v : group->views()) + { + releaseExternalDataOwner(&v); + } + + for(auto& g : group->groups()) + { + releaseExternalDataOwners(&g); + } +} + +void releaseExternalDataOwnersOfViews(Group& group) +{ + for(auto& v : group.views()) + { + releaseExternalDataOwner(&v); + } +} + +View* setExternalDataAndPinOwner(View& view, const nb::ndarray<>& owner) +{ + View* result = view.setExternalDataPtr(owner.data()); + if(result != nullptr && result->isExternal()) + { + pinExternalDataOwner(result, owner); + } + return result; +} + +View* setExternalDataAndPinOwner(View& view, TypeID type, IndexType num_elems, const nb::ndarray<>& owner) +{ + View* result = view.setExternalDataPtr(type, num_elems, owner.data()); + if(result != nullptr && result->isExternal()) + { + pinExternalDataOwner(result, owner); + } + return result; +} + +View* setExternalDataAndPinOwner(View& view, + TypeID type, + int ndims, + const nb::ndarray& shape, + const nb::ndarray<>& owner) +{ + View* result = view.setExternalDataPtr(type, ndims, shape.data(), owner.data()); + if(result != nullptr && result->isExternal()) + { + pinExternalDataOwner(result, owner); + } + return result; +} + #if defined(AXOM_USE_MPI) MPI_Comm mpiCommFromObject(nb::object comm) { @@ -671,7 +775,13 @@ NB_MODULE(pysidre, m_sidre) nb::arg("shape"), nb::arg("buffer").none()) - .def("clear", &View::clear, "Clear data and metadata from the View.") + .def( + "clear", + [](View& self) { + self.clear(); + releaseExternalDataOwner(&self); + }, + "Clear data and metadata from the View.") .def("apply", nb::overload_cast<>(&View::apply), "Apply the View's description to its data.") .def("apply", nb::overload_cast(&View::apply), @@ -717,9 +827,12 @@ NB_MODULE(pysidre, m_sidre) [](View& self, nb::object external_ptr) { if(external_ptr.is_none()) { - return self.setExternalDataPtr(nullptr); + View* result = self.setExternalDataPtr(nullptr); + releaseExternalDataOwner(&self); + return result; } - return self.setExternalDataPtr(nb::cast>(external_ptr).data()); + nb::ndarray<> owner = nb::cast>(external_ptr); + return setExternalDataAndPinOwner(self, owner); }, nb::rv_policy::reference, "Set the View to hold undescribed external data (numpy array).", @@ -727,14 +840,14 @@ NB_MODULE(pysidre, m_sidre) .def( "setExternalData", [](View& self, const nb::ndarray<>& external_ptr) { - return self.setExternalDataPtr(external_ptr.data()); + return setExternalDataAndPinOwner(self, external_ptr); }, nb::rv_policy::reference, "Set the View to hold undescribed external data (numpy array).") .def( "setExternalData", [](View& self, TypeID type, IndexType num_elems, const nb::ndarray<>& external_ptr) { - return self.setExternalDataPtr(type, num_elems, external_ptr.data()); + return setExternalDataAndPinOwner(self, type, num_elems, external_ptr); }, nb::rv_policy::reference, "Set the View to hold described external data (numpy array).") @@ -745,7 +858,7 @@ NB_MODULE(pysidre, m_sidre) int ndims, const nb::ndarray& shape, const nb::ndarray<>& external_ptr) { - return self.setExternalDataPtr(type, ndims, shape.data(), external_ptr.data()); + return setExternalDataAndPinOwner(self, type, ndims, shape, external_ptr); }, nb::rv_policy::reference, "Set the View to hold described external data (numpy array).") @@ -1026,14 +1139,18 @@ NB_MODULE(pysidre, m_sidre) .def( "createView", [](Group& self, const std::string& path, const nb::ndarray<>& a) { - return self.createView(path, a.data()); + View* view = self.createView(path, a.data()); + pinExternalDataOwner(view, a); + return view; }, nb::rv_policy::reference_internal) .def( "createView", [](Group& self, const std::string& path, TypeID id, IndexType num_elems, const nb::ndarray<>& a) { - return self.createView(path, id, num_elems, a.data()); + View* view = self.createView(path, id, num_elems, a.data()); + pinExternalDataOwner(view, a); + return view; }, nb::rv_policy::reference_internal, "Create View object with given name or path in this Group that has a data description " @@ -1047,7 +1164,9 @@ NB_MODULE(pysidre, m_sidre) int ndims, const nb::ndarray& shape, const nb::ndarray<>& external_ptr) { - return self.createViewWithShape(path, type, ndims, shape.data(), external_ptr.data()); + View* view = self.createViewWithShape(path, type, ndims, shape.data(), external_ptr.data()); + pinExternalDataOwner(view, external_ptr); + return view; }, nb::rv_policy::reference_internal, "Create View object with given name or path in this Group that has a data description " @@ -1094,20 +1213,36 @@ NB_MODULE(pysidre, m_sidre) nb::arg("value").noconvert(), nb::arg("allocID") = INVALID_ALLOCATOR_ID) - .def("destroyView", - nb::overload_cast(&Group::destroyView), - "Destroy View with given name or path owned by this Group, but leave its data intact.") - .def("destroyViewAndData", - nb::overload_cast(&Group::destroyViewAndData), - "Destroy View with given name or path owned by this Group and deallocate") - .def("destroyViewAndData", - nb::overload_cast(&Group::destroyViewAndData), - "Destroy View with given index owned by this Group and deallocate its data if it's the " - "only View associated with that data.") - .def("destroyViewsAndData", - &Group::destroyViewsAndData, - "Destroy all Views owned by this Group and deallocate " - "data for each View when it's the only View associated with that data.") + .def( + "destroyView", + [](Group& self, const std::string& path) { + releaseExternalDataOwner(self.getView(path)); + self.destroyView(path); + }, + "Destroy View with given name or path owned by this Group, but leave its data intact.") + .def( + "destroyViewAndData", + [](Group& self, const std::string& path) { + releaseExternalDataOwner(self.getView(path)); + self.destroyViewAndData(path); + }, + "Destroy View with given name or path owned by this Group and deallocate") + .def( + "destroyViewAndData", + [](Group& self, IndexType idx) { + releaseExternalDataOwner(self.getView(idx)); + self.destroyViewAndData(idx); + }, + "Destroy View with given index owned by this Group and deallocate its data if it's the " + "only View associated with that data.") + .def( + "destroyViewsAndData", + [](Group& self) { + releaseExternalDataOwnersOfViews(self); + self.destroyViewsAndData(); + }, + "Destroy all Views owned by this Group and deallocate " + "data for each View when it's the only View associated with that data.") .def("moveView", &Group::moveView, @@ -1167,29 +1302,65 @@ NB_MODULE(pysidre, m_sidre) nb::rv_policy::reference_internal, "Create a child Group within this Group with no name.", nb::arg("is_list") = false) - .def("destroyGroup", - nb::overload_cast(&Group::destroyGroup), - "Destroy child Group in this Group with given name or path.") - .def("destroyGroup", - nb::overload_cast(&Group::destroyGroup), - "Destroy child Group within this Group with given index.") - .def("destroyGroupAndData", - nb::overload_cast(&Group::destroyGroupAndData), - "Destroy child Group at the given path, and destroy data that is " - "not shared elsewhere.") - .def("destroyGroupAndData", - nb::overload_cast(&Group::destroyGroupAndData), - "Destroy child Group with the given index, and destroy data that " - "is not shared elsewhere.") - .def("destroyGroupsAndData", - &Group::destroyGroupsAndData, - "Destroy all child Groups held by this Group, and destroy data that " - "is not shared elsewhere.") - .def("destroyGroupSubtreeAndData", - &Group::destroyGroupSubtreeAndData, - "Destroy the entire subtree of Groups and Views held by this Group, " - "and destroy data that is not shared elsewhere.") - .def("destroyGroups", &Group::destroyGroups, "Destroy all child Groups in this Group.") + .def( + "destroyGroup", + [](Group& self, const std::string& path) { + releaseExternalDataOwners(self.getGroup(path)); + self.destroyGroup(path); + }, + "Destroy child Group in this Group with given name or path.") + .def( + "destroyGroup", + [](Group& self, IndexType idx) { + releaseExternalDataOwners(self.getGroup(idx)); + self.destroyGroup(idx); + }, + "Destroy child Group within this Group with given index.") + .def( + "destroyGroupAndData", + [](Group& self, const std::string& path) { + releaseExternalDataOwners(self.getGroup(path)); + self.destroyGroupAndData(path); + }, + "Destroy child Group at the given path, and destroy data that is " + "not shared elsewhere.") + .def( + "destroyGroupAndData", + [](Group& self, IndexType idx) { + releaseExternalDataOwners(self.getGroup(idx)); + self.destroyGroupAndData(idx); + }, + "Destroy child Group with the given index, and destroy data that " + "is not shared elsewhere.") + .def( + "destroyGroupsAndData", + [](Group& self) { + for(auto& g : self.groups()) + { + releaseExternalDataOwners(&g); + } + self.destroyGroupsAndData(); + }, + "Destroy all child Groups held by this Group, and destroy data that " + "is not shared elsewhere.") + .def( + "destroyGroupSubtreeAndData", + [](Group& self) { + releaseExternalDataOwners(&self); + self.destroyGroupSubtreeAndData(); + }, + "Destroy the entire subtree of Groups and Views held by this Group, " + "and destroy data that is not shared elsewhere.") + .def( + "destroyGroups", + [](Group& self) { + for(auto& g : self.groups()) + { + releaseExternalDataOwners(&g); + } + self.destroyGroups(); + }, + "Destroy all child Groups in this Group.") .def("moveGroup", &Group::moveGroup, "Remove given Group object from its parent Group and make it a child of this Group.") diff --git a/src/axom/sidre/tests/sidre_lifetime_Py.py b/src/axom/sidre/tests/sidre_lifetime_Py.py index 2368c2c6c8..d35f4a02c4 100644 --- a/src/axom/sidre/tests/sidre_lifetime_Py.py +++ b/src/axom/sidre/tests/sidre_lifetime_Py.py @@ -18,6 +18,7 @@ """ import gc +import weakref import numpy as np import pytest @@ -300,6 +301,92 @@ def make_array(): np.testing.assert_array_equal(arr, np.arange(5, dtype=np.float64)) +# --------------------------------------------------------------------------- +# External numpy storage borrowed by Sidre must stay alive with the C++ View +# --------------------------------------------------------------------------- +def test_create_view_external_array_owner_survives_discarded_proxy(): + ds = pysidre.DataStore() + root = ds.getRoot() + + def create_external_view(): + external = np.arange(6, dtype=np.int64) + ref = weakref.ref(external) + root.createView("external", external).apply(pysidre.TypeID.INT64_ID, 6) + return ref + + ref = create_external_view() + _force_gc() + assert ref() is not None + np.testing.assert_array_equal(root.getView("external").getDataArray(), np.arange(6)) + + +def test_create_view_with_shape_external_array_owner_survives_discarded_proxy(): + ds = pysidre.DataStore() + root = ds.getRoot() + shape = np.array([2, 3]) + + def create_external_view(): + external = np.arange(6, dtype=np.int64) + ref = weakref.ref(external) + root.createViewWithShape("shaped", pysidre.TypeID.INT64_ID, 2, shape, external) + return ref + + ref = create_external_view() + _force_gc() + assert ref() is not None + np.testing.assert_array_equal(root.getView("shaped").getDataArray(), np.arange(6).reshape(2, 3)) + + +def test_set_external_data_array_owner_survives_discarded_proxy(): + ds = pysidre.DataStore() + root = ds.getRoot() + + def set_external_data(): + view = root.createView("external") + external = np.arange(6, dtype=np.int64) + ref = weakref.ref(external) + view.setExternalData(pysidre.TypeID.INT64_ID, 6, external) + return ref + + ref = set_external_data() + _force_gc() + assert ref() is not None + np.testing.assert_array_equal(root.getView("external").getDataArray(), np.arange(6)) + + +def test_set_external_data_with_shape_array_owner_survives_discarded_proxy(): + ds = pysidre.DataStore() + root = ds.getRoot() + shape = np.array([2, 3]) + + def set_external_data(): + view = root.createView("shaped") + external = np.arange(6, dtype=np.int64) + ref = weakref.ref(external) + view.setExternalData(pysidre.TypeID.INT64_ID, 2, shape, external) + return ref + + ref = set_external_data() + _force_gc() + assert ref() is not None + np.testing.assert_array_equal(root.getView("shaped").getDataArray(), np.arange(6).reshape(2, 3)) + + +def test_clear_releases_external_array_owner(): + ds = pysidre.DataStore() + view = ds.getRoot().createView("external") + external = np.arange(6, dtype=np.int64) + ref = weakref.ref(external) + view.setExternalData(pysidre.TypeID.INT64_ID, 6, external) + del external + _force_gc() + assert ref() is not None + + view.clear() + _force_gc() + assert ref() is None + + if __name__ == "__main__": import sys From 0d84f9b0fd06b46db1a88bf8b61a4e09b45317ac Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 17 Jun 2026 14:27:28 -0700 Subject: [PATCH 467/986] pysidre: Fixes lifetime issues for Group::moveGroup() --- src/axom/sidre/nanobind_sidre.cpp | 1 + src/axom/sidre/tests/sidre_lifetime_Py.py | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/axom/sidre/nanobind_sidre.cpp b/src/axom/sidre/nanobind_sidre.cpp index 1cd3005dd1..8aff82dd70 100644 --- a/src/axom/sidre/nanobind_sidre.cpp +++ b/src/axom/sidre/nanobind_sidre.cpp @@ -1363,6 +1363,7 @@ NB_MODULE(pysidre, m_sidre) "Destroy all child Groups in this Group.") .def("moveGroup", &Group::moveGroup, + nb::rv_policy::reference_internal, "Remove given Group object from its parent Group and make it a child of this Group.") .def("copyGroup", &Group::copyGroup, diff --git a/src/axom/sidre/tests/sidre_lifetime_Py.py b/src/axom/sidre/tests/sidre_lifetime_Py.py index d35f4a02c4..99dab3f3e6 100644 --- a/src/axom/sidre/tests/sidre_lifetime_Py.py +++ b/src/axom/sidre/tests/sidre_lifetime_Py.py @@ -258,6 +258,27 @@ def test_parent_group_outlives_datastore(): assert parent.hasGroup("b") +def test_moved_group_outlives_owner_chain(): + ds = pysidre.DataStore() + root = ds.getRoot() + src = root.createGroup("src") + dst = root.createGroup("dst") + child = src.createGroup("child") + + moved = dst.moveGroup(child) + assert moved.getPathName() == "dst/child" + + del ds + del root + del src + del dst + del child + _force_gc() + + assert moved.getName() == "child" + assert moved.getPathName() == "dst/child" + + # --------------------------------------------------------------------------- # Zero-copy numpy arrays must pin their backing View / Buffer (and DataStore) # --------------------------------------------------------------------------- From 76315376f9f1ef81400048b6b4f1baa4e2b49013 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 17 Jun 2026 14:37:03 -0700 Subject: [PATCH 468/986] Append to ENVIRONMENT property instead of overwriting --- src/cmake/AxomMacros.cmake | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/cmake/AxomMacros.cmake b/src/cmake/AxomMacros.cmake index a6de50ebff..02a0cd6d0e 100644 --- a/src/cmake/AxomMacros.cmake +++ b/src/cmake/AxomMacros.cmake @@ -664,7 +664,9 @@ macro(axom_add_python_test) axom_python_test_environment(_py_test_env) if(_py_test_env) - set_tests_properties(${arg_NAME} PROPERTIES ENVIRONMENT "${_py_test_env}") + set_property(TEST ${arg_NAME} + APPEND + PROPERTY ENVIRONMENT "${_py_test_env}") endif() unset(_py_test_env) unset(_test_command) From a64d4c4639a5ef16a3d839f8e3518c7aed14a819 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 17 Jun 2026 16:02:22 -0700 Subject: [PATCH 469/986] pysidre: Handles lifetime issues for bound copyGroup, copyView, and destroyView functions ... and adds unit tets. Also adds a comment about thread safety of pysidre. --- src/axom/sidre/nanobind_sidre.cpp | 141 ++++++++++++++++++-- src/axom/sidre/tests/sidre_lifetime_Py.py | 154 ++++++++++++++++++++++ 2 files changed, 285 insertions(+), 10 deletions(-) diff --git a/src/axom/sidre/nanobind_sidre.cpp b/src/axom/sidre/nanobind_sidre.cpp index 8aff82dd70..fa804930d0 100644 --- a/src/axom/sidre/nanobind_sidre.cpp +++ b/src/axom/sidre/nanobind_sidre.cpp @@ -290,6 +290,8 @@ void pinExternalDataOwner(View* view, const nb::ndarray& owner) { if(view != nullptr) { + // Note: Map assignment automatically releases the previous ndarray wrapper if present. + // When nb::ndarray<> is destroyed, nanobind decrements the underlying Python object's refcount externalDataOwnerRegistry()[view] = nb::ndarray<>(owner); } } @@ -328,6 +330,72 @@ void releaseExternalDataOwnersOfViews(Group& group) } } +/*! + * \brief Copy external data pin from source View to destination View. + * + * When copyView() creates a shallow copy that shares external data, the new View + * needs its own pin to prevent the numpy array from being garbage collected. + * This function looks up the source View's pin and copies it to the destination. + * + * \param src_view Source View (must have an external data pin) + * \param dst_view Destination View (will receive a copy of the pin) + */ +void copyExternalDataOwner(const View* src_view, View* dst_view) +{ + if(src_view == nullptr || dst_view == nullptr) + { + return; + } + + auto& registry = externalDataOwnerRegistry(); + auto it = registry.find(const_cast(src_view)); + if(it != registry.end()) + { + // Copy the ndarray handle to the new View, incrementing its refcount + registry[dst_view] = it->second; + } +} + +/*! + * \brief Recursively copy external data pins from source Group hierarchy to destination. + * + * When copyGroup() creates a shallow copy of a Group hierarchy, all Views with + * external data in the source hierarchy need their pins copied to the destination. + * + * \param src_group Source Group (root of hierarchy to copy from) + * \param dst_group Destination Group (root of copied hierarchy) + */ +void copyExternalDataOwners(const Group* src_group, Group* dst_group) +{ + if(src_group == nullptr || dst_group == nullptr) + { + return; + } + + // Copy pins for all Views in this Group + for(auto& src_view : src_group->views()) + { + if(src_view.isExternal()) + { + View* dst_view = dst_group->getView(src_view.getName()); + if(dst_view != nullptr) + { + copyExternalDataOwner(&src_view, dst_view); + } + } + } + + // Recursively copy pins for all child Groups + for(auto& src_child : src_group->groups()) + { + Group* dst_child = dst_group->getGroup(src_child.getName()); + if(dst_child != nullptr) + { + copyExternalDataOwners(&src_child, dst_child); + } + } +} + View* setExternalDataAndPinOwner(View& view, const nb::ndarray<>& owner) { View* result = view.setExternalDataPtr(owner.data()); @@ -431,7 +499,34 @@ class PyIOManager NB_MODULE(pysidre, m_sidre) { - m_sidre.doc() = "A python extension for Axom's Sidre component"; + m_sidre.doc() = R"pbdoc( + A python extension for Axom's Sidre component. + + **Thread Safety:** + This module relies on Python's Global Interpreter Lock (GIL) for thread safety. + + - **Python threads:** Safe. The GIL serializes all operations. + - **C++ threads:** Unsafe. Calling Sidre methods from C++ threads without acquiring + the GIL will cause data races and undefined behavior. + - **GIL release:** The bindings do not release the GIL during operations, so Python + threads are always serialized. If future changes add `nb::call_guard`, + explicit synchronization (e.g., mutexes) must be added to the external data registry. + + **External Data Lifetime:** + Views can reference external numpy arrays via setExternalData() or createView(). + The binding automatically pins these arrays to prevent garbage collection while + the View exists. Pins are released when: + - View.clear() is called + - The View is destroyed via destroyView() or destroyViewAndData() + - The owning Group hierarchy is destroyed via destroyGroup*() methods + + **Reallocation Hazards:** + Arrays obtained via getDataArray() are zero-copy views into Sidre storage. + View::reallocate() or Buffer::reallocate() can move the underlying storage, + leaving existing numpy arrays pointing to freed memory. Always re-acquire + arrays after any reallocation. The binding cannot prevent this hazard without + breaking zero-copy semantics. + )pbdoc"; // Module version mirrors the Axom release m_sidre.attr("__version__") = AXOM_VERSION_FULL; @@ -1220,6 +1315,13 @@ NB_MODULE(pysidre, m_sidre) self.destroyView(path); }, "Destroy View with given name or path owned by this Group, but leave its data intact.") + .def( + "destroyView", + [](Group& self, IndexType idx) { + releaseExternalDataOwner(self.getView(idx)); + self.destroyView(idx); + }, + "Destroy View with given index owned by this Group, but leave its data intact.") .def( "destroyViewAndData", [](Group& self, const std::string& path) { @@ -1248,10 +1350,20 @@ NB_MODULE(pysidre, m_sidre) &Group::moveView, nb::rv_policy::reference_internal, "Remove given View object from its owning Group and move it to this Group.") - .def("copyView", - &Group::copyView, - nb::rv_policy::reference_internal, - "Create a (shallow) copy of given View object and add it to this Group.") + .def( + "copyView", + [](Group& self, View* view) { + View* copy = self.copyView(view); + if(copy != nullptr && view != nullptr && view->isExternal()) + { + // Shallow copy shares external data pointer - copy the pin to prevent + // the numpy array from being garbage collected + copyExternalDataOwner(view, copy); + } + return copy; + }, + nb::rv_policy::reference_internal, + "Create a (shallow) copy of given View object and add it to this Group.") .def("hasGroup", nb::overload_cast(&Group::hasGroup, nb::const_), @@ -1365,11 +1477,20 @@ NB_MODULE(pysidre, m_sidre) &Group::moveGroup, nb::rv_policy::reference_internal, "Remove given Group object from its parent Group and make it a child of this Group.") - .def("copyGroup", - &Group::copyGroup, - nb::rv_policy::reference_internal, - "Create a (shallow) copy of Group hierarchy rooted at given " - "Group and make the copy a child of this Group.") + .def( + "copyGroup", + [](Group& self, Group* group) { + Group* copy = self.copyGroup(group); + if(copy != nullptr && group != nullptr) + { + // Shallow copy shares external data pointers - recursively copy all pins + copyExternalDataOwners(group, copy); + } + return copy; + }, + nb::rv_policy::reference_internal, + "Create a (shallow) copy of Group hierarchy rooted at given " + "Group and make the copy a child of this Group.") .def("deepCopyGroup", &Group::deepCopyGroup, nb::rv_policy::reference_internal, diff --git a/src/axom/sidre/tests/sidre_lifetime_Py.py b/src/axom/sidre/tests/sidre_lifetime_Py.py index 99dab3f3e6..b6d603d404 100644 --- a/src/axom/sidre/tests/sidre_lifetime_Py.py +++ b/src/axom/sidre/tests/sidre_lifetime_Py.py @@ -408,6 +408,160 @@ def test_clear_releases_external_array_owner(): assert ref() is None +def test_copy_view_with_external_data_preserves_pin(): + """copyView on an external View should copy the pin to prevent premature collection.""" + ds = pysidre.DataStore() + root = ds.getRoot() + src_group = root.createGroup("src") + dst_group = root.createGroup("dst") + + # Create view with external data + external = np.arange(10, dtype=np.int64) + ref = weakref.ref(external) + src_view = src_group.createView("original", external) + src_view.apply(pysidre.TypeID.INT64_ID, 10) + del external + _force_gc() + assert ref() is not None # Pin keeps it alive + + # Copy the view - should copy the pin + copied_view = dst_group.copyView(src_view) + assert copied_view.isExternal() + + # Delete source view and DS - copied view should keep pin alive + del src_view + del src_group + del ds + del root + del dst_group + _force_gc() + + # Pin should still be valid + assert ref() is not None + np.testing.assert_array_equal(copied_view.getDataArray(), np.arange(10)) + + +def test_copy_group_with_external_data_preserves_pins(): + """copyGroup should recursively copy pins for all external Views in hierarchy.""" + ds = pysidre.DataStore() + root = ds.getRoot() + src = root.createGroup("source") + + # Create nested groups with external data + external1 = np.arange(5, dtype=np.int32) + external2 = np.arange(8, dtype=np.int64) + ref1 = weakref.ref(external1) + ref2 = weakref.ref(external2) + + src.createView("view1", external1).apply(pysidre.TypeID.INT32_ID, 5) + child = src.createGroup("child") + child.createView("view2", external2).apply(pysidre.TypeID.INT64_ID, 8) + + del external1 + del external2 + _force_gc() + assert ref1() is not None + assert ref2() is not None + + # Copy the entire group hierarchy - copyGroup creates a group with the source name + dst_parent = root.createGroup("copies") + dst = dst_parent.copyGroup(src) + assert dst is not None + assert dst.getName() == "source" + assert dst.hasView("view1") + assert dst.hasGroup("child") + assert dst.getGroup("child").hasView("view2") + + # Delete source hierarchy - copied pins should keep arrays alive + del src + del child + del ds + del root + del dst_parent + _force_gc() + + # Both pins should still be valid + assert ref1() is not None + assert ref2() is not None + np.testing.assert_array_equal(dst.getView("view1").getDataArray(), np.arange(5, dtype=np.int32)) + np.testing.assert_array_equal( + dst.getGroup("child").getView("view2").getDataArray(), np.arange(8, dtype=np.int64)) + + +def test_move_view_with_external_data_preserves_pin(): + """moveView should preserve the pin since the View* pointer doesn't change.""" + ds = pysidre.DataStore() + root = ds.getRoot() + src = root.createGroup("src") + dst = root.createGroup("dst") + + # Create view with external data + external = np.arange(12, dtype=np.int64) + ref = weakref.ref(external) + view = src.createView("moveable", external) + view.apply(pysidre.TypeID.INT64_ID, 12) + del external + _force_gc() + assert ref() is not None # Pin keeps it alive + + # Move the view to dst - View* stays the same, pin should remain valid + moved = dst.moveView(view) + assert moved.getPathName() == "dst/moveable" + assert moved.isExternal() + assert not src.hasView("moveable") + assert dst.hasView("moveable") + + # Delete original references - moved view should keep pin alive + del view + del src + del ds + del root + del dst + _force_gc() + + # Pin should still be valid + assert ref() is not None + np.testing.assert_array_equal(moved.getDataArray(), np.arange(12)) + + +def test_destroy_view_by_index_releases_external_pin(): + """destroyView(IndexType) should release the external data pin.""" + ds = pysidre.DataStore() + root = ds.getRoot() + + external = np.arange(7, dtype=np.int32) + ref = weakref.ref(external) + view = root.createView("indexed", external) + view.apply(pysidre.TypeID.INT32_ID, 7) + view_idx = view.getIndex() + del external + del view + _force_gc() + assert ref() is not None # Pin keeps it alive + + # Destroy by index - should release the pin + root.destroyView(view_idx) + _force_gc() + assert ref() is None # Pin released, array collected + + +def test_pin_overwrite_warning(): + """Setting external data twice on the same View correctly replaces the pin.""" + ds = pysidre.DataStore() + view = ds.getRoot().createView("test") + + # First external data + external1 = np.arange(5, dtype=np.int32) + view.setExternalData(pysidre.TypeID.INT32_ID, 5, external1) + + # Second external data on same view - old pin released, new pin created + external2 = np.arange(10, dtype=np.int64) + view.setExternalData(pysidre.TypeID.INT64_ID, 10, external2) + + # The second pin should be active; first pin was automatically released + np.testing.assert_array_equal(view.getDataArray(), external2) + + if __name__ == "__main__": import sys From 6b2c105ae624d662e3addf927759587b0a0fee67 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 17 Jun 2026 17:47:23 -0700 Subject: [PATCH 470/986] pysidre: Adds a test to ensure views/groups are invalidated upon DataStore desctruction --- src/axom/sidre/nanobind_sidre.cpp | 22 ++++++++++++++++++- src/axom/sidre/tests/sidre_lifetime_Py.py | 26 +++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/axom/sidre/nanobind_sidre.cpp b/src/axom/sidre/nanobind_sidre.cpp index fa804930d0..c7838966a5 100644 --- a/src/axom/sidre/nanobind_sidre.cpp +++ b/src/axom/sidre/nanobind_sidre.cpp @@ -110,6 +110,12 @@ nb::ndarray viewToNumpyArray(nb::handle_t h) IndexType shapeOutput[DMAX]; size_t ndims = self.getShape(DMAX, shapeOutput); + + // Guard against buffer overflow if View has more dimensions than our buffer can hold + SLIC_ERROR_IF(ndims > DMAX, + "View has " << ndims << " dimensions, exceeds maximum of " << DMAX + << ". Cannot convert to numpy array."); + size_t shape[DMAX]; for(size_t i = 0; i < ndims; i++) { @@ -268,7 +274,7 @@ conduit::Node& nbObjectToNode(nb::object& o) * drop it immediately, which can leave Sidre holding a dangling pointer once * the ndarray is garbage collected. * - * To keep Sidre's C++ semantics unchanged while making the Python API safe, + * To keep Sidre's C++ semantics unchanged while making the Python API safe, * we maintain a binding-only registry that maps a C++ View* to a copied * nanobind::ndarray wrapper. Copying nb::ndarray increments the underlying * ndarray owner's refcount via nanobind's internal handle, so the NumPy storage @@ -276,6 +282,14 @@ conduit::Node& nbObjectToNode(nb::object& o) * * Pins are released when the external pointer is cleared (e.g. View.clear(), * setExternalData(None)) and when views/groups are destroyed via the bound Group::destroy* APIs. + * + * **Registry Lifetime:** The registry persists for the process lifetime and may accumulate + * entries for destroyed Views if those Views are destroyed by the C++ DataStore destructor + * rather than through the Python-wrapped destroy methods. This is acceptable because: + * (1) Dangling View* keys are never dereferenced (we only erase, never lookup by pointer) + * (2) The memory overhead is small (one map entry per external View ever created) + * (3) In typical Python usage, Views with external data are explicitly destroyed via + * destroyView()/destroyGroup(), which properly releases pins. */ std::unordered_map>& externalDataOwnerRegistry() { @@ -1311,6 +1325,7 @@ NB_MODULE(pysidre, m_sidre) .def( "destroyView", [](Group& self, const std::string& path) { + // releaseExternalDataOwner is null-safe; destroyView handles invalid paths gracefully releaseExternalDataOwner(self.getView(path)); self.destroyView(path); }, @@ -1318,6 +1333,7 @@ NB_MODULE(pysidre, m_sidre) .def( "destroyView", [](Group& self, IndexType idx) { + // releaseExternalDataOwner is null-safe; destroyView handles invalid indices gracefully releaseExternalDataOwner(self.getView(idx)); self.destroyView(idx); }, @@ -1325,6 +1341,7 @@ NB_MODULE(pysidre, m_sidre) .def( "destroyViewAndData", [](Group& self, const std::string& path) { + // releaseExternalDataOwner is null-safe; destroyViewAndData handles invalid paths gracefully releaseExternalDataOwner(self.getView(path)); self.destroyViewAndData(path); }, @@ -1417,6 +1434,7 @@ NB_MODULE(pysidre, m_sidre) .def( "destroyGroup", [](Group& self, const std::string& path) { + // releaseExternalDataOwners is null-safe; destroyGroup handles invalid paths gracefully releaseExternalDataOwners(self.getGroup(path)); self.destroyGroup(path); }, @@ -1424,6 +1442,7 @@ NB_MODULE(pysidre, m_sidre) .def( "destroyGroup", [](Group& self, IndexType idx) { + // releaseExternalDataOwners is null-safe; destroyGroup handles invalid indices gracefully releaseExternalDataOwners(self.getGroup(idx)); self.destroyGroup(idx); }, @@ -1431,6 +1450,7 @@ NB_MODULE(pysidre, m_sidre) .def( "destroyGroupAndData", [](Group& self, const std::string& path) { + // releaseExternalDataOwners is null-safe; destroyGroupAndData handles invalid paths gracefully releaseExternalDataOwners(self.getGroup(path)); self.destroyGroupAndData(path); }, diff --git a/src/axom/sidre/tests/sidre_lifetime_Py.py b/src/axom/sidre/tests/sidre_lifetime_Py.py index b6d603d404..08563dac8a 100644 --- a/src/axom/sidre/tests/sidre_lifetime_Py.py +++ b/src/axom/sidre/tests/sidre_lifetime_Py.py @@ -562,6 +562,32 @@ def test_pin_overwrite_warning(): np.testing.assert_array_equal(view.getDataArray(), external2) +def test_registry_cleanup_on_explicit_destroy(): + """Pins are released when Views are explicitly destroyed, preventing registry bloat.""" + ds = pysidre.DataStore() + root = ds.getRoot() + + weak_refs = [] + for i in range(5): + external = np.arange(10, dtype=np.int32) + weak_refs.append(weakref.ref(external)) + view = root.createView(f"view_{i}") + view.setExternalData(pysidre.TypeID.INT32_ID, 10, external) + del external # Pin keeps it alive + del view # Don't hold view reference + + # Explicitly destroy all views - this releases pins + for i in range(5): + root.destroyView(f"view_{i}") + + _force_gc() + + # Verify all arrays were collected after explicit destruction + collected_count = sum(1 for ref in weak_refs if ref() is None) + assert collected_count == len(weak_refs), \ + f"Only {collected_count}/{len(weak_refs)} arrays collected after explicit destroy" + + if __name__ == "__main__": import sys From 0f8ffd2cc73fbf4451588a1d086d2b785be22fc6 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 17 Jun 2026 21:24:05 -0700 Subject: [PATCH 471/986] pysidre: Adds lifetime tests for multiple concurrent datastores --- src/axom/sidre/tests/sidre_lifetime_Py.py | 162 ++++++++++++++++++++++ 1 file changed, 162 insertions(+) diff --git a/src/axom/sidre/tests/sidre_lifetime_Py.py b/src/axom/sidre/tests/sidre_lifetime_Py.py index 08563dac8a..b4f535fb43 100644 --- a/src/axom/sidre/tests/sidre_lifetime_Py.py +++ b/src/axom/sidre/tests/sidre_lifetime_Py.py @@ -588,6 +588,168 @@ def test_registry_cleanup_on_explicit_destroy(): f"Only {collected_count}/{len(weak_refs)} arrays collected after explicit destroy" +def test_multiple_concurrent_datastores(): + """Multiple active DataStores with external data should not interfere with each other.""" + # Create multiple DataStores simultaneously + datastores = [] + views = [] + arrays = [] + + for ds_idx in range(3): + ds = pysidre.DataStore() + root = ds.getRoot() + datastores.append(ds) + + # Each DataStore has multiple Views with external data + ds_views = [] + ds_arrays = [] + for view_idx in range(3): + external = np.arange(view_idx * 10, (view_idx + 1) * 10, dtype=np.int32) + ds_arrays.append(external) + view = root.createView(f"view_{view_idx}") + view.setExternalData(pysidre.TypeID.INT32_ID, 10, external) + ds_views.append(view) + + views.append(ds_views) + arrays.append(ds_arrays) + + # Verify all views can access their data correctly + for ds_idx in range(3): + for view_idx in range(3): + view = views[ds_idx][view_idx] + expected = arrays[ds_idx][view_idx] + retrieved = view.getDataArray() + np.testing.assert_array_equal(retrieved, + expected, + err_msg=f"DS{ds_idx} view{view_idx} data mismatch") + + # Destroy one DataStore while others remain active + root0 = datastores[0].getRoot() + for view_idx in range(3): + root0.destroyView(f"view_{view_idx}") + + # Keep references to DS1 and DS2 before deleting DS0 + ds1_views = views[1] + ds2_views = views[2] + ds1_arrays = arrays[1] + ds2_arrays = arrays[2] + + del datastores[0], views[0], arrays[0] + _force_gc() + + # Verify remaining DataStores still work correctly + for local_idx, (ds_views, ds_arrays, ds_label) in enumerate([(ds1_views, ds1_arrays, "DS1"), + (ds2_views, ds2_arrays, "DS2")]): + for view_idx in range(3): + view = ds_views[view_idx] + expected = ds_arrays[view_idx] + retrieved = view.getDataArray() + np.testing.assert_array_equal( + retrieved, + expected, + err_msg=f"After DS0 destroy: {ds_label} view{view_idx} data mismatch") + + # Create a new DataStore and verify it doesn't conflict + ds_new = pysidre.DataStore() + root_new = ds_new.getRoot() + external_new = np.arange(100, 110, dtype=np.int32) + view_new = root_new.createView("new_view") + view_new.setExternalData(pysidre.TypeID.INT32_ID, 10, external_new) + + # Verify new DataStore works + np.testing.assert_array_equal(view_new.getDataArray(), external_new) + + # Verify old DataStores still work + for ds_views, ds_arrays, ds_label in [(ds1_views, ds1_arrays, "DS1"), + (ds2_views, ds2_arrays, "DS2")]: + for view_idx in range(3): + view = ds_views[view_idx] + expected = ds_arrays[view_idx] + retrieved = view.getDataArray() + np.testing.assert_array_equal( + retrieved, + expected, + err_msg=f"After new DS: {ds_label} view{view_idx} data mismatch") + + +def test_concurrent_datastores_with_copy_move(): + """Copy/move operations should work correctly with multiple concurrent DataStores.""" + ds1 = pysidre.DataStore() + ds2 = pysidre.DataStore() + + root1 = ds1.getRoot() + root2 = ds2.getRoot() + + # Create view with external data in DS1 + external1 = np.arange(10, dtype=np.int32) + view1 = root1.createView("src") + view1.setExternalData(pysidre.TypeID.INT32_ID, 10, external1) + + # Copy view to a group in DS1 + grp1 = root1.createGroup("grp1") + copied = grp1.copyView(view1) + np.testing.assert_array_equal(copied.getDataArray(), external1) + + # Create view with different external data in DS2 + external2 = np.arange(20, 30, dtype=np.int64) + view2 = root2.createView("other") + view2.setExternalData(pysidre.TypeID.INT64_ID, 10, external2) + + # Verify both DataStores maintain correct data + np.testing.assert_array_equal(view1.getDataArray(), external1) + np.testing.assert_array_equal(copied.getDataArray(), external1) + np.testing.assert_array_equal(view2.getDataArray(), external2) + + # Move view within DS1 + grp2 = root1.createGroup("grp2") + moved = grp2.moveView(copied) + np.testing.assert_array_equal(moved.getDataArray(), external1) + + # Verify DS2 unaffected + np.testing.assert_array_equal(view2.getDataArray(), external2) + + +def test_concurrent_datastores_registry_isolation(): + """Registry should correctly isolate pins between different DataStores.""" + ds1 = pysidre.DataStore() + ds2 = pysidre.DataStore() + + external1 = np.arange(5, dtype=np.int32) + external2 = np.arange(5, dtype=np.int64) + + ref1 = weakref.ref(external1) + ref2 = weakref.ref(external2) + + # Both DataStores use external data + view1 = ds1.getRoot().createView("v1") + view1.setExternalData(pysidre.TypeID.INT32_ID, 5, external1) + + view2 = ds2.getRoot().createView("v2") + view2.setExternalData(pysidre.TypeID.INT64_ID, 5, external2) + + del external1, external2 # Only pins keep them alive + _force_gc() + + # Both should still be alive + assert ref1() is not None, "DS1 external data collected prematurely" + assert ref2() is not None, "DS2 external data collected prematurely" + + # Destroy view in DS1 + ds1.getRoot().destroyView("v1") + _force_gc() + + # DS1's external data should be collected, DS2's should not + assert ref1() is None, "DS1 external data not collected after destroyView" + assert ref2() is not None, "DS2 external data incorrectly collected" + + # Destroy view in DS2 + ds2.getRoot().destroyView("v2") + _force_gc() + + # Now DS2's external data should be collected too + assert ref2() is None, "DS2 external data not collected after destroyView" + + if __name__ == "__main__": import sys From f8fc0a499e58e5320ee3f318467a14080d9d9b91 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 17 Jun 2026 21:32:09 -0700 Subject: [PATCH 472/986] Updates RELEASE-NOTES --- RELEASE-NOTES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 059766fcb2..8b41964149 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -61,6 +61,7 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ - Core: Array construction from strided ArrayView now correctly copies the strided elements - Core: Improved `axom::FlatMap` insertion performance by fusing duplicate-key lookup with empty-slot probing. - Core: Updated DeviceHash to use 64-bit hash results and improved coverage for integer and floating-point hashing. +- Python: Improves lifetime handling for python wrapped sidre entities, including support for external views into numpy arrays. ## [Version 0.14.0] - Release date 2026-03-31 From cc44e6fd1ea940f1c7d04bd279862819fa796aea Mon Sep 17 00:00:00 2001 From: Jason Burmark Date: Thu, 18 Jun 2026 11:34:05 -0700 Subject: [PATCH 473/986] Free conduit nodes earlier In DistributedClosestPoint::computeClosestPoints cleanup conduit nodes as soon as they are no longer needed instead of waiting until the end of the routine. Also refactor storage to use unique_ptr instead of shared_ptr. --- .../detail/DistributedClosestPointImpl.hpp | 69 +++++++++---------- 1 file changed, 34 insertions(+), 35 deletions(-) diff --git a/src/axom/quest/detail/DistributedClosestPointImpl.hpp b/src/axom/quest/detail/DistributedClosestPointImpl.hpp index c3d70854ab..bd520d1d60 100644 --- a/src/axom/quest/detail/DistributedClosestPointImpl.hpp +++ b/src/axom/quest/detail/DistributedClosestPointImpl.hpp @@ -478,12 +478,15 @@ class DistributedClosestPointImpl } /// Wait for some non-blocking sends (if any) to finish. - void check_send_requests(std::list& isendRequests, bool atLeastOne) const + void check_send_requests(std::list>>& isendRequests, + bool atLeastOne) const { std::vector reqs; - for(auto& isr : isendRequests) + reqs.reserve(isendRequests.size()); + for(auto const& isr : isendRequests) { - reqs.push_back(isr.m_request); + reqs.push_back(isr.first.m_request); } int inCount = static_cast(reqs.size()); @@ -498,17 +501,20 @@ class DistributedClosestPointImpl MPI_Testsome(inCount, reqs.data(), &outCount, indices.data(), MPI_STATUSES_IGNORE); } indices.resize(outCount); + // MPI does not guarantee indices are in order + std::sort(indices.begin(), indices.end()); auto reqIter = isendRequests.begin(); - int prevIdx = 0; + int reqIdx = 0; for(const int idx : indices) { - for(; prevIdx < idx; ++prevIdx) + while (reqIdx < idx) { ++reqIter; + ++reqIdx; } reqIter = isendRequests.erase(reqIter); - ++prevIdx; + ++reqIdx; } } @@ -739,26 +745,19 @@ class DistributedClosestPointExec : public DistributedClosestPointImpl { SLIC_ASSERT_MSG(m_bvh, "BVH tree must be initialized before calling 'computeClosestPoints"); - std::map> xferNodes; + std::unique_ptr xferNodePtr = std::make_unique(); // create conduit Node containing data that has to xfer between ranks. // The node will be mostly empty if there are no domains on this rank - { - xferNodes[m_rank] = std::make_shared(); - conduit::Node& xferNode = *xferNodes[m_rank]; - node_copy_query_to_xfer(queryMesh, xferNode, topologyName); - xferNode["homeRank"] = m_rank; - } + node_copy_query_to_xfer(queryMesh, *xferNodePtr, topologyName); + (*xferNodePtr)["homeRank"] = m_rank; - BoxType myQueryBb = computeMeshBoundingBox(*xferNodes[m_rank]); - put_bounding_box_to_conduit_node(myQueryBb, xferNodes[m_rank]->fetch("aabb")); + BoxType myQueryBb = computeMeshBoundingBox(*xferNodePtr); + put_bounding_box_to_conduit_node(myQueryBb, xferNodePtr->fetch("aabb")); BoxArray allQueryBbs; gatherBoundingBoxes(myQueryBb, allQueryBbs); - { - conduit::Node& xferNode = *xferNodes[m_rank]; - computeLocalClosestPoints(xferNode); - } + computeLocalClosestPoints(*xferNodePtr); const auto& myObjectBb = m_objectPartitionBbs[m_rank]; int remainingRecvs = 0; @@ -775,10 +774,11 @@ class DistributedClosestPointExec : public DistributedClosestPointImpl } } - // arbitrary tags for send/recv xferNode. + // arbitrary tags for send/recv xferNodes. const int tag = 987342; - std::list isendRequests; + std::list>> isendRequests; { /* @@ -786,7 +786,7 @@ class DistributedClosestPointExec : public DistributedClosestPointImpl partition, if any. Increase remainingRecvs, because this data will come back. */ - int firstRecipForMyQuery = next_recipient(*xferNodes[m_rank]); + int firstRecipForMyQuery = next_recipient(*xferNodePtr); if(m_nranks == 1) { SLIC_ASSERT(firstRecipForMyQuery == -1); @@ -795,14 +795,15 @@ class DistributedClosestPointExec : public DistributedClosestPointImpl if(firstRecipForMyQuery == -1) { // No need to send anywhere. Put computed data back into queryMesh. - node_copy_xfer_to_query(*xferNodes[m_rank], queryMesh, topologyName); - xferNodes.erase(m_rank); + node_copy_xfer_to_query(*xferNodePtr, queryMesh, topologyName); + // Free xferNode memory + xferNodePtr.reset(); } else { - isendRequests.emplace_back(conduit::relay::mpi::Request()); + isendRequests.emplace_back(conduit::relay::mpi::Request(), std::move(xferNodePtr)); auto& req = isendRequests.back(); - relay::mpi::isend_using_schema(*xferNodes[m_rank], firstRecipForMyQuery, tag, m_mpiComm, &req); + relay::mpi::isend_using_schema(*req.second, firstRecipForMyQuery, tag, m_mpiComm, &req.first); ++remainingRecvs; } } @@ -813,27 +814,25 @@ class DistributedClosestPointExec : public DistributedClosestPointImpl fmt::format("======= {} receives remaining =======", remainingRecvs)); // Receive the next xferNode - std::shared_ptr recvXferNodePtr = std::make_shared(); + std::unique_ptr recvXferNodePtr = std::make_unique(); conduit::relay::mpi::recv_using_schema(*recvXferNodePtr, MPI_ANY_SOURCE, tag, m_mpiComm); const int homeRank = recvXferNodePtr->fetch_existing("homeRank").as_int(); --remainingRecvs; - xferNodes[homeRank] = recvXferNodePtr; - conduit::Node& xferNode = *xferNodes[homeRank]; if(homeRank == m_rank) { - node_copy_xfer_to_query(xferNode, queryMesh, topologyName); + node_copy_xfer_to_query(*recvXferNodePtr, queryMesh, topologyName); } else { - computeLocalClosestPoints(xferNode); + computeLocalClosestPoints(*recvXferNodePtr); - isendRequests.emplace_back(conduit::relay::mpi::Request()); - auto& isendRequest = isendRequests.back(); - int nextRecipient = next_recipient(xferNode); + int nextRecipient = next_recipient(*recvXferNodePtr); SLIC_ASSERT(nextRecipient != -1); - relay::mpi::isend_using_schema(xferNode, nextRecipient, tag, m_mpiComm, &isendRequest); + isendRequests.emplace_back(conduit::relay::mpi::Request(), std::move(recvXferNodePtr)); + auto& isendRequest = isendRequests.back(); + relay::mpi::isend_using_schema(*isendRequest.second, nextRecipient, tag, m_mpiComm, &isendRequest.first); // Check non-blocking sends to free memory. check_send_requests(isendRequests, false); From d5960d8030ba5fd7edf91bdbe6b87447566c3217 Mon Sep 17 00:00:00 2001 From: Rich Hornung Date: Thu, 18 Jun 2026 12:47:56 -0700 Subject: [PATCH 474/986] Clang format --- .../detail/DistributedClosestPointImpl.hpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/axom/quest/detail/DistributedClosestPointImpl.hpp b/src/axom/quest/detail/DistributedClosestPointImpl.hpp index bd520d1d60..8f96de920f 100644 --- a/src/axom/quest/detail/DistributedClosestPointImpl.hpp +++ b/src/axom/quest/detail/DistributedClosestPointImpl.hpp @@ -478,9 +478,9 @@ class DistributedClosestPointImpl } /// Wait for some non-blocking sends (if any) to finish. - void check_send_requests(std::list>>& isendRequests, - bool atLeastOne) const + void check_send_requests( + std::list>>& isendRequests, + bool atLeastOne) const { std::vector reqs; reqs.reserve(isendRequests.size()); @@ -508,7 +508,7 @@ class DistributedClosestPointImpl int reqIdx = 0; for(const int idx : indices) { - while (reqIdx < idx) + while(reqIdx < idx) { ++reqIter; ++reqIdx; @@ -777,8 +777,7 @@ class DistributedClosestPointExec : public DistributedClosestPointImpl // arbitrary tags for send/recv xferNodes. const int tag = 987342; - std::list>> isendRequests; + std::list>> isendRequests; { /* @@ -832,7 +831,11 @@ class DistributedClosestPointExec : public DistributedClosestPointImpl SLIC_ASSERT(nextRecipient != -1); isendRequests.emplace_back(conduit::relay::mpi::Request(), std::move(recvXferNodePtr)); auto& isendRequest = isendRequests.back(); - relay::mpi::isend_using_schema(*isendRequest.second, nextRecipient, tag, m_mpiComm, &isendRequest.first); + relay::mpi::isend_using_schema(*isendRequest.second, + nextRecipient, + tag, + m_mpiComm, + &isendRequest.first); // Check non-blocking sends to free memory. check_send_requests(isendRequests, false); From bbf71255c3a9d721189887f800f3b0bb9c3caaa6 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 23 Jun 2026 14:48:16 -0700 Subject: [PATCH 475/986] Reconcile spack-packages axom package.py with axom repo package.py. --- scripts/spack/packages/axom/package.py | 56 ++++++++++++++------------ 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/scripts/spack/packages/axom/package.py b/scripts/spack/packages/axom/package.py index 229536925b..6d6593f6f0 100644 --- a/scripts/spack/packages/axom/package.py +++ b/scripts/spack/packages/axom/package.py @@ -16,6 +16,8 @@ from spack_repo.builtin.build_systems.cuda import CudaPackage from spack_repo.builtin.build_systems.rocm import ROCmPackage +from spack.package import * + # Axom components we expose to Spack. Core is always built and is not listed here. _AXOM_COMPONENTS = ( "bump", @@ -35,9 +37,6 @@ ) -from spack.package import * - - def get_spec_path(spec, package_name, path_replacements={}, use_bin=False, use_lib=False): """Extracts the prefix path for the given spack package path_replacements is a dictionary with string replacements for the path. @@ -145,20 +144,21 @@ class Axom(CachedCMakePackage, CudaPackage, ROCmPackage): values=any_combination_of("all", *_AXOM_COMPONENTS).with_default("all"), ) + variant("int64", default=True, description="Use 64bit integers for IndexType") + # variants for package dependencies variant("adiak", default=False, when="@0.13:", description="Build with adiak") - variant("caliper", default=False, when="@0.13:", description="Build with caliper") variant("c2c", default=False, description="Build with c2c") + variant("caliper", default=False, when="@0.13:", description="Build with caliper") variant("conduit", default=True, description="Build with conduit") variant("hdf5", default=True, description="Build with hdf5") variant("lua", default=True, description="Build with Lua") variant("mfem", default=False, description="Build with mfem") variant("opencascade", default=False, description="Build with opencascade") - variant("raja", default=True, description="Build with raja") variant("scr", default=False, description="Build with SCR") variant("umpire", default=True, description="Build with umpire") - variant("int64", default=True, description="Use 64bit integers for IndexType") + variant("raja", default=True, description="Build with raja") varmsg = "Build development tools (such as Sphinx, Doxygen, etc...)" variant("devtools", default=False, description=varmsg) @@ -176,10 +176,10 @@ class Axom(CachedCMakePackage, CudaPackage, ROCmPackage): depends_on("cmake@3.21:", type="build", when="+rocm") depends_on("blt", type="build") - depends_on("blt@0.5.1:0.5.3", type="build", when="@0.6.1:0.8") - depends_on("blt@0.6.2:", type="build", when="@0.9:") - depends_on("blt@0.7", type="build", when="@0.11:") depends_on("blt@0.7.1:", type="build", when="@0.12:") + depends_on("blt@0.7", type="build", when="@0.11:") + depends_on("blt@0.6.2", type="build", when="@0.9:0.10") + depends_on("blt@0.5.1:0.5.3", type="build", when="@0.6.1:0.8") depends_on("mpi", when="+mpi") @@ -201,8 +201,10 @@ class Axom(CachedCMakePackage, CudaPackage, ROCmPackage): with when("+umpire"): depends_on("umpire") depends_on("umpire@2025.12:", when="@0.13:") - depends_on("umpire@2025.09.0:", when="@0.10:") - depends_on("umpire@2024.02.0:", when="@0.9:") + depends_on("umpire@2025.09:", when="@0.12:") + depends_on("umpire@2025.03", when="@0.11") + depends_on("umpire@2024.07", when="@0.10") + depends_on("umpire@2024.02", when="@0.9") depends_on("umpire@2022.03.0:2023.06", when="@0.7.0:0.8") depends_on("umpire@6.0.0", when="@0.6.0") depends_on("umpire@5:5.0.1", when="@:0.5.0") @@ -212,8 +214,10 @@ class Axom(CachedCMakePackage, CudaPackage, ROCmPackage): with when("+raja"): depends_on("raja") depends_on("raja@2025.12.1:", when="@0.13:") - depends_on("raja@2025.09.0:", when="@0.10:") - depends_on("raja@2024.02.0:", when="@0.9:") + depends_on("raja@2025.09:", when="@0.12:") + depends_on("raja@2025.03", when="@0.11") + depends_on("raja@2024.07", when="@0.10") + depends_on("raja@2024.02", when="@0.9") depends_on("raja@2022.03.0:2023.06", when="@0.7.0:0.8") depends_on("raja@0.14.0", when="@0.6.0") depends_on("raja@:0.13.0", when="@:0.5.0") @@ -226,7 +230,7 @@ class Axom(CachedCMakePackage, CudaPackage, ROCmPackage): depends_on("caliper", when="+caliper") with when("+profiling"): depends_on("adiak") - depends_on("caliper") + depends_on("caliper+adiak~papi") depends_on("caliper+cuda", when="+cuda") depends_on("caliper~cuda", when="~cuda") @@ -254,7 +258,6 @@ class Axom(CachedCMakePackage, CudaPackage, ROCmPackage): depends_on(f"umpire {ext_cuda_dep}", when=f"+umpire {ext_cuda_dep}") depends_on(f"caliper {ext_cuda_dep}", when=f"+caliper {ext_cuda_dep}") depends_on(f"caliper {ext_cuda_dep}", when=f"+profiling {ext_cuda_dep}") - depends_on(f"mfem {ext_cuda_dep}", when=f"+mfem {ext_cuda_dep}") for val in ROCmPackage.amdgpu_targets: @@ -415,11 +418,14 @@ def initconfig_compiler_entries(self): if spec.satisfies("%cce"): entries.append(cmake_cache_string("CMAKE_CXX_FLAGS_DEBUG", "-O1 -g")) + # Remove unusable -Mfreeform flag injected by spack + entries = [entry.replace("-Mfreeform", "") for entry in entries] + + # Disable intrusive warning: + # icpx: remark: note that use of '-g' without any optimization-level + # option will turn off most compiler optimizations similar to use of + # '-O0'; use '-Rno-debug-disables-optimization' to disable this remark if spec.satisfies("%oneapi"): - # Disable intrusive warning: - # icpx: remark: note that use of '-g' without any optimization-level - # option will turn off most compiler optimizations similar to use of - # '-O0'; use '-Rno-debug-disables-optimization' to disable this remark entries.append( cmake_cache_string("CMAKE_CXX_FLAGS_DEBUG", "-g -Rno-debug-disables-optimization") ) @@ -496,9 +502,8 @@ def initconfig_hardware_entries(self): # Additional library path for cray compiler if spec.satisfies("%cce"): - hip_link_flags += "-L/opt/cray/pe/cce/{0}/cce/x86_64/lib -Wl,-rpath,/opt/cray/pe/cce/{0}/cce/x86_64/lib ".format( - spec.compiler.version - ) + lib_path = "/opt/cray/pe/cce/{0}/cce/x86_64/lib".format(spec.compiler.version) + hip_link_flags += "-L{0} -Wl,-rpath,{0}".format(lib_path) if spec.satisfies("+fortran"): link_remove_list = [] @@ -698,7 +703,7 @@ def initconfig_package_entries(self): else: entries.append(f"# {dep.upper()} not built\n") - if spec.satisfies("+umpire") and spec.satisfies("^camp"): + if (spec.satisfies("+raja") or spec.satisfies("+umpire")) and spec.satisfies("^camp"): dep_dir = get_spec_path(spec, "camp", path_replacements) entries.append(cmake_cache_path("CAMP_DIR", dep_dir)) @@ -750,7 +755,6 @@ def initconfig_package_entries(self): entries.append(cmake_cache_option("ENABLE_CLANGFORMAT", False)) if spec.satisfies("+python") or spec.satisfies("+devtools"): - # Get path to python executable python_bin_dir = get_spec_path(spec, "python", path_replacements, use_bin=True) entries.append(cmake_cache_path("Python_EXECUTABLE", pjoin(python_bin_dir, "python3"))) @@ -837,7 +841,7 @@ def build_test(self): print("Running Axom Unit Tests...") make("test") - @run_after("install") + @run_after("install", when="+examples") @on_package_attributes(run_tests=True) def test_install_using_cmake(self): """build example with cmake and run""" @@ -853,7 +857,7 @@ def test_install_using_cmake(self): example() make("clean") - @run_after("install") + @run_after("install", when="+examples") @on_package_attributes(run_tests=True) def test_install_using_make(self): """build example with make and run""" From fd453fc8f47657282325a5dea12cb4e1884a9201 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 17 Mar 2026 18:27:50 -0700 Subject: [PATCH 476/986] Fixes Delaunay triangulation for co-spherical data ... and adds regression tests for this. --- src/axom/quest/Delaunay.hpp | 75 +++++++++++--- src/axom/quest/tests/CMakeLists.txt | 1 + src/axom/quest/tests/quest_delaunay.cpp | 130 ++++++++++++++++++++++++ 3 files changed, 192 insertions(+), 14 deletions(-) create mode 100644 src/axom/quest/tests/quest_delaunay.cpp diff --git a/src/axom/quest/Delaunay.hpp b/src/axom/quest/Delaunay.hpp index 20506d8216..8d66683948 100644 --- a/src/axom/quest/Delaunay.hpp +++ b/src/axom/quest/Delaunay.hpp @@ -596,7 +596,8 @@ class Delaunay * \brief Find the Delaunay cavity: the elements whose circumspheres contain the query point * * \details This function starts from an element \a element_i and searches through - * neighboring elements for a list of element indices whose circumspheres contains the query point. + * neighboring elements for a list of element indices whose circumspheres + * contain or touch the query point. * It also finds the faces on the boundaries of the cavity to help with filling the cavity * in the \a delaunayBall function. * @@ -610,8 +611,10 @@ class Delaunay IndexArray stack; stack.reserve(reserveSize); - // add first element (if valid and point is in its circumsphere) - if(m_mesh.isValidElement(element_i) && isPointInSphere(query_pt, element_i)) + // The containing element always belongs to the cavity. Seed it + // unconditionally so co-circular/co-spherical insertions cannot leave + // the cavity empty when the point lies on a circumsphere boundary. + if(m_mesh.isValidElement(element_i)) { m_checked_element_set.insert(element_i); cavity_elems.insert(element_i); @@ -636,7 +639,7 @@ class Delaunay // neighbor is valid; check circumsphere (if necesary), and add to cavity as appropriate if(m_checked_element_set.insert(nbr).second) { - if(isPointInSphere(query_pt, nbr)) + if(isPointInCircumsphere(query_pt, nbr)) { cavity_elems.insert(nbr); stack.push_back(nbr); @@ -725,8 +728,10 @@ class Delaunay /// \brief Returns the number of elements removed during this insertion int numRemovedElements() const { return cavity_elems.size(); } - /// \brief Helper function returns true if the query point is in the sphere formed by the element vertices - bool isPointInSphere(const PointType& query_pt, IndexType element_idx) const; + /// \brief Returns true when the query point is inside or on the element circumsphere + /// \note Uses an inclusive determinant-based in-sphere test to avoid + /// recomputing explicit circumspheres during cavity traversal. + bool isPointInCircumsphere(const PointType& query_pt, IndexType element_idx) const; public: // we create a surface mesh @@ -849,29 +854,71 @@ inline Delaunay<3>::BaryCoordType Delaunay<3>::getBaryCoords(IndexType element_i return tet.physToBarycentric(query_pt); } -// 2D specialization for isPointInSphere(...) +// 2D specialization for isPointInCircumsphere(...) template <> -inline bool Delaunay<2>::InsertionHelper::isPointInSphere(const PointType& query_pt, - IndexType element_idx) const +inline bool Delaunay<2>::InsertionHelper::isPointInCircumsphere(const PointType& query_pt, + IndexType element_idx) const { const auto verts = m_mesh.boundaryVertices(element_idx); const PointType& p0 = m_mesh.getVertexPosition(verts[0]); const PointType& p1 = m_mesh.getVertexPosition(verts[1]); const PointType& p2 = m_mesh.getVertexPosition(verts[2]); - return primal::in_sphere(query_pt, p0, p1, p2, 0.); + + const auto ba = p1 - p0; + const auto ca = p2 - p0; + const auto qa = query_pt - p0; + + // Mirror primal::in_sphere(), but include the circumsphere boundary in the + // cavity so co-circular insertions do not get stranded by a strict test. + const double det = axom::numerics::determinant(ba[0], + ba[1], + ba.squared_norm(), + ca[0], + ca[1], + ca.squared_norm(), + qa[0], + qa[1], + qa.squared_norm()); + + return det < 0. || axom::utilities::isNearlyEqual(det, 0., primal::PRIMAL_TINY); } -// 3D specialization for isPointInSphere(...) +// 3D specialization for isPointInCircumsphere(...) template <> -inline bool Delaunay<3>::InsertionHelper::isPointInSphere(const PointType& query_pt, - IndexType element_idx) const +inline bool Delaunay<3>::InsertionHelper::isPointInCircumsphere(const PointType& query_pt, + IndexType element_idx) const { const auto verts = m_mesh.boundaryVertices(element_idx); const PointType& p0 = m_mesh.getVertexPosition(verts[0]); const PointType& p1 = m_mesh.getVertexPosition(verts[1]); const PointType& p2 = m_mesh.getVertexPosition(verts[2]); const PointType& p3 = m_mesh.getVertexPosition(verts[3]); - return primal::in_sphere(query_pt, p0, p1, p2, p3, 0.); + + const auto ba = p1 - p0; + const auto ca = p2 - p0; + const auto da = p3 - p0; + const auto qa = query_pt - p0; + + // Mirror primal::in_sphere(), but include the circumsphere boundary in the + // cavity so co-spherical insertions do not get stranded by a strict test. + const double det = axom::numerics::determinant(ba[0], + ba[1], + ba[2], + ba.squared_norm(), + ca[0], + ca[1], + ca[2], + ca.squared_norm(), + da[0], + da[1], + da[2], + da.squared_norm(), + qa[0], + qa[1], + qa[2], + qa.squared_norm()); + + return det < 0. || axom::utilities::isNearlyEqual(det, 0., primal::PRIMAL_TINY); } } // end namespace quest diff --git a/src/axom/quest/tests/CMakeLists.txt b/src/axom/quest/tests/CMakeLists.txt index 66b3d792be..98eaace8ac 100644 --- a/src/axom/quest/tests/CMakeLists.txt +++ b/src/axom/quest/tests/CMakeLists.txt @@ -9,6 +9,7 @@ set(quest_tests quest_all_nearest_neighbors.cpp + quest_delaunay.cpp quest_inout_octree.cpp quest_inout_quadtree.cpp quest_linearize_curves.cpp diff --git a/src/axom/quest/tests/quest_delaunay.cpp b/src/axom/quest/tests/quest_delaunay.cpp new file mode 100644 index 0000000000..fc5c320a30 --- /dev/null +++ b/src/axom/quest/tests/quest_delaunay.cpp @@ -0,0 +1,130 @@ +// 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/quest/Delaunay.hpp" +#include "axom/slic.hpp" + +#include +#include + +namespace +{ + +template +using DelaunayType = axom::quest::Delaunay; + +template +void insertPoints(DelaunayType& dt, + const std::vector::PointType>& points) +{ + for(const auto& point : points) + { + dt.insertPoint(point); + } +} + +template +void expectValidDelaunay(DelaunayType& dt, + const std::vector::PointType>& inserted_points, + int expected_num_elements = -1) +{ + dt.removeBoundary(); + + EXPECT_TRUE(dt.getMeshData()->isValid(true)); + EXPECT_TRUE(dt.isValid(true)); + EXPECT_EQ(inserted_points.size(), static_cast(dt.getMeshData()->vertices().size())); + + if(expected_num_elements >= 0) + { + EXPECT_EQ(expected_num_elements, static_cast(dt.getMeshData()->elements().size())); + } + else + { + EXPECT_GT(static_cast(dt.getMeshData()->elements().size()), 0); + } +} + +} // namespace + +TEST(quest_delaunay, cocircular_square_2d) +{ + using PointType = typename DelaunayType<2>::PointType; + using BoundingBox = typename DelaunayType<2>::BoundingBox; + + DelaunayType<2> dt; + dt.initializeBoundary(BoundingBox(PointType {-0.5, -0.5}, PointType {1.5, 1.5})); + + const std::vector points {PointType {0.0, 0.0}, + PointType {1.0, 0.0}, + PointType {1.0, 1.0}, + PointType {0.0, 1.0}}; + + insertPoints(dt, points); + expectValidDelaunay(dt, points, 2); +} + +TEST(quest_delaunay, regular_grid_2d) +{ + using PointType = typename DelaunayType<2>::PointType; + using BoundingBox = typename DelaunayType<2>::BoundingBox; + + DelaunayType<2> dt; + dt.initializeBoundary(BoundingBox(PointType {-1.0, -1.0}, PointType {3.0, 3.0})); + + std::vector points; + points.reserve(9); + + for(int y = 0; y < 3; ++y) + { + for(int x = 0; x < 3; ++x) + { + points.push_back(PointType {static_cast(x), static_cast(y)}); + } + } + + insertPoints(dt, points); + expectValidDelaunay(dt, points, 8); +} + +TEST(quest_delaunay, cospherical_cube_3d) +{ + using PointType = typename DelaunayType<3>::PointType; + using BoundingBox = typename DelaunayType<3>::BoundingBox; + + DelaunayType<3> dt; + dt.initializeBoundary(BoundingBox(PointType {-0.5, -0.5, -0.5}, PointType {1.5, 1.5, 1.5})); + + std::vector points; + points.reserve(8); + + for(int z = 0; z < 2; ++z) + { + for(int y = 0; y < 2; ++y) + { + for(int x = 0; x < 2; ++x) + { + points.push_back( + PointType {static_cast(x), static_cast(y), static_cast(z)}); + } + } + } + + insertPoints(dt, points); + expectValidDelaunay(dt, points); +} + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +int main(int argc, char* argv[]) +{ + ::testing::InitGoogleTest(&argc, argv); + axom::slic::SimpleLogger logger; + + return RUN_ALL_TESTS(); +} From 58aacbeb18a6c19cd0667504ccfeb9db4d038802 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 17 Mar 2026 19:34:51 -0700 Subject: [PATCH 477/986] Update `primal::in_sphere()` to have an `includeBoundary` parameter By default, the behavior will stay the same (and exclude boundaries). When set, in_sphere will return true if the point is inside the sphere or on the sphere (up to the provided tolerance). We use this updated function in the Delaunay triangulation's in/on_sphere tests. --- src/axom/primal/operators/in_sphere.hpp | 32 +++++++++++---- src/axom/primal/tests/primal_in_sphere.cpp | 8 ++++ src/axom/quest/Delaunay.hpp | 48 +--------------------- 3 files changed, 34 insertions(+), 54 deletions(-) diff --git a/src/axom/primal/operators/in_sphere.hpp b/src/axom/primal/operators/in_sphere.hpp index e29d1ea511..5b3913cb51 100644 --- a/src/axom/primal/operators/in_sphere.hpp +++ b/src/axom/primal/operators/in_sphere.hpp @@ -37,6 +37,8 @@ namespace primal * \param [in] p1 the second vertex of the triangle * \param [in] p2 the third vertex of the triangle * \param [in] EPS tolerance for determining if \a q is on the boundary. Default: 1e-8. + * \param [in] includeBoundary if true, points on the circumcircle are treated + * as inside. Default: false. * \return true if the point is inside the circumcircle, false if it is on * the circle's boundary or outside the circle */ @@ -45,7 +47,8 @@ inline bool in_sphere(const Point& q, const Point& p0, const Point& p1, const Point& p2, - double EPS = 1e-8) + double EPS = 1e-8, + bool includeBoundary = false) { const auto ba = p1 - p0; const auto ca = p2 - p0; @@ -58,7 +61,7 @@ inline bool in_sphere(const Point& q, qa[0], qa[1], qa.squared_norm()); // clang-format on - return axom::utilities::isNearlyEqual(det, 0., EPS) ? false : (det < 0); + return includeBoundary ? (det < 0 || axom::utilities::isNearlyEqual(det, 0., EPS)) : (det < 0); } /*! @@ -67,12 +70,17 @@ inline bool in_sphere(const Point& q, * \param [in] q the query point * \param [in] tri the triangle * \param [in] EPS tolerance for determining if \a q is on the boundary. Default: 1e-8. + * \param [in] includeBoundary if true, points on the circumcircle are treated + * as inside. Default: false. * \see in_sphere */ template -inline bool in_sphere(const Point& q, const Triangle& tri, double EPS = 1e-8) +inline bool in_sphere(const Point& q, + const Triangle& tri, + double EPS = 1e-8, + bool includeBoundary = false) { - return in_sphere(q, tri[0], tri[1], tri[2], EPS); + return in_sphere(q, tri[0], tri[1], tri[2], EPS, includeBoundary); } /*! @@ -88,6 +96,8 @@ inline bool in_sphere(const Point& q, const Triangle& tri, double EP * \param [in] p2 the third vertex of the tetrahedron * \param [in] p3 the fourth vertex of the tetrahedron * \param [in] EPS tolerance for determining if \a q is on the boundary. Default: 1e-8. + * \param [in] includeBoundary if true, points on the circumsphere are treated + * as inside. Default: false. * \return true if the point is inside the circumsphere, false if it is on * the sphere's boundary or outside the sphere */ @@ -97,7 +107,8 @@ inline bool in_sphere(const Point& q, const Point& p1, const Point& p2, const Point& p3, - double EPS = 1e-8) + double EPS = 1e-8, + bool includeBoundary = false) { const auto ba = p1 - p0; const auto ca = p2 - p0; @@ -112,7 +123,7 @@ inline bool in_sphere(const Point& q, qa[0], qa[1], qa[2], qa.squared_norm()); // clang-format on - return axom::utilities::isNearlyEqual(det, 0., EPS) ? false : (det < 0); + return includeBoundary ? (det < 0 || axom::utilities::isNearlyEqual(det, 0., EPS)) : (det < 0); } /*! @@ -121,12 +132,17 @@ inline bool in_sphere(const Point& q, * \param [in] q the query point * \param [in] tet the tetrahedron * \param [in] EPS tolerance for determining if \a q is on the boundary. Default: 1e-8. + * \param [in] includeBoundary if true, points on the circumsphere are treated + * as inside. Default: false. * \see in_sphere */ template -inline bool in_sphere(const Point& q, const Tetrahedron& tet, double EPS = 1e-8) +inline bool in_sphere(const Point& q, + const Tetrahedron& tet, + double EPS = 1e-8, + bool includeBoundary = false) { - return in_sphere(q, tet[0], tet[1], tet[2], tet[3], EPS); + return in_sphere(q, tet[0], tet[1], tet[2], tet[3], EPS, includeBoundary); } /*! diff --git a/src/axom/primal/tests/primal_in_sphere.cpp b/src/axom/primal/tests/primal_in_sphere.cpp index baaa418eef..0e46e881c8 100644 --- a/src/axom/primal/tests/primal_in_sphere.cpp +++ b/src/axom/primal/tests/primal_in_sphere.cpp @@ -51,10 +51,14 @@ TEST(primal_in_sphere, test_in_sphere_2d) PointType q1 {1, 1}; EXPECT_FALSE(in_sphere(q1, p0, p1, p2)); EXPECT_FALSE(in_sphere(q1, tri)); + EXPECT_TRUE(in_sphere(q1, p0, p1, p2, 1e-8, true)); + EXPECT_TRUE(in_sphere(q1, tri, 1e-8, true)); PointType q2 {0, 1}; EXPECT_FALSE(in_sphere(q2, p0, p1, p2)); EXPECT_FALSE(in_sphere(q2, tri)); + EXPECT_TRUE(in_sphere(q2, p0, p1, p2, 1e-8, true)); + EXPECT_TRUE(in_sphere(q2, tri, 1e-8, true)); } // Test some points that are outside the circumcircle @@ -98,10 +102,14 @@ TEST(primal_in_sphere, test_in_sphere_3d) PointType q1 {1, 1, 1}; EXPECT_FALSE(in_sphere(q1, p0, p1, p2, p3)); EXPECT_FALSE(in_sphere(q1, tet)); + EXPECT_TRUE(in_sphere(q1, p0, p1, p2, p3, 1e-8, true)); + EXPECT_TRUE(in_sphere(q1, tet, 1e-8, true)); PointType q2 {-1, 1, 1}; EXPECT_FALSE(in_sphere(q2, p0, p1, p2, p3)); EXPECT_FALSE(in_sphere(q2, tet)); + EXPECT_TRUE(in_sphere(q2, p0, p1, p2, p3, 1e-8, true)); + EXPECT_TRUE(in_sphere(q2, tet, 1e-8, true)); } // Test some points that are outside the circumsphere diff --git a/src/axom/quest/Delaunay.hpp b/src/axom/quest/Delaunay.hpp index 8d66683948..a222ca6056 100644 --- a/src/axom/quest/Delaunay.hpp +++ b/src/axom/quest/Delaunay.hpp @@ -729,8 +729,6 @@ class Delaunay int numRemovedElements() const { return cavity_elems.size(); } /// \brief Returns true when the query point is inside or on the element circumsphere - /// \note Uses an inclusive determinant-based in-sphere test to avoid - /// recomputing explicit circumspheres during cavity traversal. bool isPointInCircumsphere(const PointType& query_pt, IndexType element_idx) const; public: @@ -863,24 +861,7 @@ inline bool Delaunay<2>::InsertionHelper::isPointInCircumsphere(const PointType& const PointType& p0 = m_mesh.getVertexPosition(verts[0]); const PointType& p1 = m_mesh.getVertexPosition(verts[1]); const PointType& p2 = m_mesh.getVertexPosition(verts[2]); - - const auto ba = p1 - p0; - const auto ca = p2 - p0; - const auto qa = query_pt - p0; - - // Mirror primal::in_sphere(), but include the circumsphere boundary in the - // cavity so co-circular insertions do not get stranded by a strict test. - const double det = axom::numerics::determinant(ba[0], - ba[1], - ba.squared_norm(), - ca[0], - ca[1], - ca.squared_norm(), - qa[0], - qa[1], - qa.squared_norm()); - - return det < 0. || axom::utilities::isNearlyEqual(det, 0., primal::PRIMAL_TINY); + return primal::in_sphere(query_pt, p0, p1, p2, primal::PRIMAL_TINY, true); } // 3D specialization for isPointInCircumsphere(...) @@ -893,32 +874,7 @@ inline bool Delaunay<3>::InsertionHelper::isPointInCircumsphere(const PointType& const PointType& p1 = m_mesh.getVertexPosition(verts[1]); const PointType& p2 = m_mesh.getVertexPosition(verts[2]); const PointType& p3 = m_mesh.getVertexPosition(verts[3]); - - const auto ba = p1 - p0; - const auto ca = p2 - p0; - const auto da = p3 - p0; - const auto qa = query_pt - p0; - - // Mirror primal::in_sphere(), but include the circumsphere boundary in the - // cavity so co-spherical insertions do not get stranded by a strict test. - const double det = axom::numerics::determinant(ba[0], - ba[1], - ba[2], - ba.squared_norm(), - ca[0], - ca[1], - ca[2], - ca.squared_norm(), - da[0], - da[1], - da[2], - da.squared_norm(), - qa[0], - qa[1], - qa[2], - qa.squared_norm()); - - return det < 0. || axom::utilities::isNearlyEqual(det, 0., primal::PRIMAL_TINY); + return primal::in_sphere(query_pt, p0, p1, p2, p3, primal::PRIMAL_TINY, true); } } // end namespace quest From 48afcaecbf99dc60382ca4690eb114817056c51c Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 17 Mar 2026 19:48:41 -0700 Subject: [PATCH 478/986] Modernization: Use `if constexpr` to specialize Deluanay implementations --- src/axom/quest/Delaunay.hpp | 76 ++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 40 deletions(-) diff --git a/src/axom/quest/Delaunay.hpp b/src/axom/quest/Delaunay.hpp index a222ca6056..ac335d413f 100644 --- a/src/axom/quest/Delaunay.hpp +++ b/src/axom/quest/Delaunay.hpp @@ -825,56 +825,52 @@ inline void Delaunay<3>::generateInitialMesh(std::vector& points, elem.swap(el); } -// 2D specialization for getBaryCoords(...) -template <> -inline Delaunay<2>::BaryCoordType Delaunay<2>::getBaryCoords(IndexType element_idx, - const PointType& query_pt) const +template +inline typename Delaunay::BaryCoordType Delaunay::getBaryCoords(IndexType element_idx, + const PointType& query_pt) const { const auto verts = m_mesh.boundaryVertices(element_idx); - const ElementType tri(m_mesh.getVertexPosition(verts[0]), - m_mesh.getVertexPosition(verts[1]), - m_mesh.getVertexPosition(verts[2])); - return tri.physToBarycentric(query_pt); -} + if constexpr(DIM == 2) + { + const ElementType tri(m_mesh.getVertexPosition(verts[0]), + m_mesh.getVertexPosition(verts[1]), + m_mesh.getVertexPosition(verts[2])); -// 3D specialization for getBaryCoords(...) -template <> -inline Delaunay<3>::BaryCoordType Delaunay<3>::getBaryCoords(IndexType element_idx, - const PointType& query_pt) const -{ - const auto verts = m_mesh.boundaryVertices(element_idx); - const ElementType tet(m_mesh.getVertexPosition(verts[0]), - m_mesh.getVertexPosition(verts[1]), - m_mesh.getVertexPosition(verts[2]), - m_mesh.getVertexPosition(verts[3])); + return tri.physToBarycentric(query_pt); + } + else + { + const ElementType tet(m_mesh.getVertexPosition(verts[0]), + m_mesh.getVertexPosition(verts[1]), + m_mesh.getVertexPosition(verts[2]), + m_mesh.getVertexPosition(verts[3])); - return tet.physToBarycentric(query_pt); + return tet.physToBarycentric(query_pt); + } } -// 2D specialization for isPointInCircumsphere(...) -template <> -inline bool Delaunay<2>::InsertionHelper::isPointInCircumsphere(const PointType& query_pt, - IndexType element_idx) const +template +inline bool Delaunay::InsertionHelper::isPointInCircumsphere(const PointType& query_pt, + IndexType element_idx) const { const auto verts = m_mesh.boundaryVertices(element_idx); - const PointType& p0 = m_mesh.getVertexPosition(verts[0]); - const PointType& p1 = m_mesh.getVertexPosition(verts[1]); - const PointType& p2 = m_mesh.getVertexPosition(verts[2]); - return primal::in_sphere(query_pt, p0, p1, p2, primal::PRIMAL_TINY, true); -} -// 3D specialization for isPointInCircumsphere(...) -template <> -inline bool Delaunay<3>::InsertionHelper::isPointInCircumsphere(const PointType& query_pt, - IndexType element_idx) const -{ - const auto verts = m_mesh.boundaryVertices(element_idx); - const PointType& p0 = m_mesh.getVertexPosition(verts[0]); - const PointType& p1 = m_mesh.getVertexPosition(verts[1]); - const PointType& p2 = m_mesh.getVertexPosition(verts[2]); - const PointType& p3 = m_mesh.getVertexPosition(verts[3]); - return primal::in_sphere(query_pt, p0, p1, p2, p3, primal::PRIMAL_TINY, true); + if constexpr(DIM == 2) + { + const PointType& p0 = m_mesh.getVertexPosition(verts[0]); + const PointType& p1 = m_mesh.getVertexPosition(verts[1]); + const PointType& p2 = m_mesh.getVertexPosition(verts[2]); + return primal::in_sphere(query_pt, p0, p1, p2, primal::PRIMAL_TINY, true); + } + else + { + const PointType& p0 = m_mesh.getVertexPosition(verts[0]); + const PointType& p1 = m_mesh.getVertexPosition(verts[1]); + const PointType& p2 = m_mesh.getVertexPosition(verts[2]); + const PointType& p3 = m_mesh.getVertexPosition(verts[3]); + return primal::in_sphere(query_pt, p0, p1, p2, p3, primal::PRIMAL_TINY, true); + } } } // end namespace quest From 3c1bcad77e1c5fddcaca1ed935c054c1c1668c7c Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 18 Mar 2026 15:07:32 -0700 Subject: [PATCH 479/986] Adds option to set up scattered_interpolation example using regular grid --- src/axom/quest/examples/CMakeLists.txt | 10 ++ .../examples/scattered_interpolation.cpp | 128 +++++++++++++++--- 2 files changed, 120 insertions(+), 18 deletions(-) diff --git a/src/axom/quest/examples/CMakeLists.txt b/src/axom/quest/examples/CMakeLists.txt index ec343820ba..27195b30ac 100644 --- a/src/axom/quest/examples/CMakeLists.txt +++ b/src/axom/quest/examples/CMakeLists.txt @@ -556,6 +556,16 @@ if(AXOM_ENABLE_SIDRE) COMMAND quest_scattered_interpolation_ex -d ${d} -n 10000 -q 20000 ) endforeach() + + axom_add_test( + NAME quest_scattered_interpolation_grid_2d_test + COMMAND quest_scattered_interpolation_ex -d 2 --grid-res 5 5 -q 200 + ) + + axom_add_test( + NAME quest_scattered_interpolation_grid_3d_test + COMMAND quest_scattered_interpolation_ex -d 3 --grid-res 3 3 3 -q 200 + ) endif() endif() diff --git a/src/axom/quest/examples/scattered_interpolation.cpp b/src/axom/quest/examples/scattered_interpolation.cpp index b94739906d..06aea1d2ba 100644 --- a/src/axom/quest/examples/scattered_interpolation.cpp +++ b/src/axom/quest/examples/scattered_interpolation.cpp @@ -417,12 +417,14 @@ struct PointMesh struct Input { std::string outputFile {"scattered_interpolation"}; + std::string delaunayFile; std::string inputFile; bool verboseOutput {false}; int numRandPoints {20}; int numQueryPoints {20}; int dimension {2}; + std::vector inputGridResolution; std::vector boundsMin; std::vector boundsMax; std::string outputProtocol = sidre::Group::getDefaultIOProtocol(); @@ -437,6 +439,12 @@ struct Input public: bool hasInputMesh() const { return !inputFile.empty(); } + bool useRegularGrid() const { return !inputGridResolution.empty(); } + bool shouldExportDelaunay() const { return verboseOutput || !delaunayFile.empty(); } + std::string getDelaunayOutputFile() const + { + return delaunayFile.empty() ? outputFile + "_delaunay.vtk" : delaunayFile; + } void parse(int argc, char** argv, axom::CLI::App& app) { @@ -445,25 +453,31 @@ struct Input ->capture_default_str(); // Options for input data - // Either provide `-n` and `-d`; or `-i` (input mesh) auto input_grp = app.add_option_group("Input", - "Parameters associated with input data.\n" - "If an input mesh is provided, it will override the " - "`-n` and `-d` options."); - - input_grp->add_option("-n,--nrandpt", numRandPoints) - ->description("The number of points to generate for the input mesh") - ->capture_default_str(); + "Choose at most one input source: " + "random points (`--nrandpt`), a regular grid " + "(`--grid-res`), or an input mesh (`--infile`). " + "If none is provided, the default random-point " + "count is used."); input_grp->add_option("-d,--dim", dimension) - ->description( - "The dimension of the mesh. 2 for triangle mesh in 2D; " - "3 for tetrahedral mesh in 3D") + ->description("The dimension of the input") ->capture_default_str(); - input_grp->add_option("-i,--infile", inputFile) - ->description("Input point mesh with associated scalar fields") - ->check(axom::CLI::ExistingFile); + auto* randpt_opt = input_grp->add_option("-n,--nrandpt", numRandPoints); + randpt_opt->description("Generate the input using this many random points")->capture_default_str(); + + auto* grid_opt = input_grp->add_option("--grid-res", inputGridResolution); + grid_opt->description("Generate input along a regular grid with resolution (nx,ny[,nz])") + ->expected(2, 3) + ->check(axom::CLI::PositiveNumber); + + auto* infile_opt = input_grp->add_option("-i,--infile", inputFile); + infile_opt->description("Read the input from a file")->check(axom::CLI::ExistingFile); + + randpt_opt->excludes(grid_opt); + randpt_opt->excludes(infile_opt); + grid_opt->excludes(infile_opt); // Options for defining the query data auto query_grp = @@ -492,6 +506,9 @@ struct Input ->description("The output file") ->capture_default_str(); + output_grp->add_option("--delaunay-file", delaunayFile) + ->description("Optional name for exported Delaunay triangulation VTK file"); + output_grp->add_option("-p,--protocol", outputProtocol) ->description("Set the output protocol for sidre point meshes") ->capture_default_str() @@ -504,6 +521,23 @@ struct Input // could throw an exception app.parse(argc, argv); + if(!hasInputMesh() && useRegularGrid()) + { + if(dimension != static_cast(inputGridResolution.size())) + { + SLIC_WARNING(axom::fmt::format("Overriding requested dimension {} with grid dimension {}", + dimension, + inputGridResolution.size())); + dimension = inputGridResolution.size(); + } + + for(int res : inputGridResolution) + { + SLIC_ERROR_IF(res < 2, + "Regular-grid input requires at least two points along each dimension."); + } + } + // If user doesn't provide bounds, default to unit cube if(boundsMin.empty()) { @@ -520,20 +554,26 @@ struct Input {{ dimension: {} nrandpt: {} + grid resolution: [{}] inputFile: '{}' nquerypt: {} bounding box min: {{{}}} bounding box max: {{{}}} outfile = '{}' + export delaunay = {} + delaunay file = '{}' output protocol = '{}' }})", dimension, numRandPoints, + axom::fmt::join(inputGridResolution, ", "), inputFile, numQueryPoints, axom::fmt::join(boundsMin, ", "), axom::fmt::join(boundsMax, ", "), outputFile, + shouldExportDelaunay(), + shouldExportDelaunay() ? getDelaunayOutputFile() : "", outputProtocol)); axom::slic::setLoggingMsgLevel(verboseOutput ? axom::slic::message::Debug @@ -568,6 +608,46 @@ axom::Array> generatePts(int numPts, return pts; } +template +axom::Array> generateGridPts(const std::vector& grid_res, + const std::vector& bb_min, + const std::vector& bb_max) +{ + using PointType = typename primal::Point; + using BoundingBox = typename primal::BoundingBox; + + SLIC_ASSERT(static_cast(grid_res.size()) == DIM); + + int numPts = 1; + for(int d = 0; d < DIM; ++d) + { + numPts *= grid_res[d]; + } + + axom::Array pts(numPts, numPts); + BoundingBox bbox {PointType(bb_min.data()), PointType(bb_max.data())}; + + for(int idx = 0; idx < numPts; ++idx) + { + int remaining = idx; + PointType pt; + + for(int d = 0; d < DIM; ++d) + { + const int res = grid_res[d]; + const int gridIdx = remaining % res; + remaining /= res; + + const double t = static_cast(gridIdx) / static_cast(res - 1); + pt[d] = bbox.getMin()[d] + t * (bbox.getMax()[d] - bbox.getMin()[d]); + } + + pts[idx] = pt; + } + + return pts; +} + template void initializeInputMesh(Input& params, internal::blueprint::PointMesh& inputMesh) { @@ -608,7 +688,15 @@ void initializeInputMesh(Input& params, internal::blueprint::PointMesh& inputMes } else { - inputMesh.setPoints(generatePts(params.numRandPoints, params.boundsMin, params.boundsMax)); + if(params.useRegularGrid()) + { + inputMesh.setPoints( + generateGridPts(params.inputGridResolution, params.boundsMin, params.boundsMax)); + } + else + { + inputMesh.setPoints(generatePts(params.numRandPoints, params.boundsMin, params.boundsMax)); + } // Extract coordinate positions as scalar fields const int nPts = inputMesh.numPoints(); @@ -637,6 +725,8 @@ void initializeInputMesh(Input& params, internal::blueprint::PointMesh& inputMes pos_z[i] = pt[DIM - 1]; } } + + params.numRandPoints = nPts; } } @@ -891,17 +981,19 @@ int main(int argc, char** argv) numVerts / timer.elapsedTimeInSec())); // Dump the Delaunay complex to disk as a vtk file - if(params.verboseOutput) + if(params.shouldExportDelaunay()) { + const std::string delaunayFile = params.getDelaunayOutputFile(); switch(params.dimension) { case 2: - scattered_2d->exportDelaunayComplex(bp_input, "delaunay_2d.vtk"); + scattered_2d->exportDelaunayComplex(bp_input, std::string(delaunayFile)); break; case 3: - scattered_3d->exportDelaunayComplex(bp_input, "delaunay_3d.vtk"); + scattered_3d->exportDelaunayComplex(bp_input, std::string(delaunayFile)); break; } + SLIC_INFO(axom::fmt::format("Exported Delaunay triangulation to '{}'.", delaunayFile)); } // Find the simplices containing each of the query points From 10c9796ea4fe1b24f837a7906eae9788da5e60ad Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 18 Mar 2026 16:15:13 -0700 Subject: [PATCH 480/986] Improves robustness of Deluanay insertion algorithm for grid points * Seed the cavity with neighboring elements when insertion point lies on a shared face * Increase tolerance for Barycentric coordinates when traversing triangulation. The smaller tolerance caused points to get stuck when they were on a face. --- src/axom/quest/Delaunay.hpp | 44 +++++++++++++++++++------- src/axom/quest/examples/CMakeLists.txt | 2 +- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/axom/quest/Delaunay.hpp b/src/axom/quest/Delaunay.hpp index ac335d413f..f2c6edb5c1 100644 --- a/src/axom/quest/Delaunay.hpp +++ b/src/axom/quest/Delaunay.hpp @@ -37,6 +37,7 @@ class Delaunay { public: AXOM_STATIC_ASSERT_MSG(DIM == 2 || DIM == 3, "The template parameter DIM can only be 2 or 3. "); + static constexpr double BARY_EPS = 1e-12; using DataType = double; @@ -126,8 +127,24 @@ class Delaunay // Run the insertion operation by finding invalidated elements around the point (the "cavity") // and replacing them with new valid elements (the Delaunay "ball") + IndexArray seed_elements; + seed_elements.push_back(element_i); + + const BaryCoordType bary_coord = getBaryCoords(element_i, new_pt); + for(int i = 0; i < VERT_PER_ELEMENT; ++i) + { + if(axom::utilities::abs(bary_coord[i]) <= BARY_EPS) + { + const IndexType nbr = m_mesh.adjacentElements(element_i)[ModularFaceIndex(i) + 1]; + if(m_mesh.isValidElement(nbr)) + { + seed_elements.push_back(nbr); + } + } + } + InsertionHelper insertionHelper(m_mesh); - insertionHelper.findCavityElements(new_pt, element_i); + insertionHelper.findCavityElements(new_pt, seed_elements); insertionHelper.createCavity(); IndexType new_pt_i = m_mesh.addVertex(new_pt); insertionHelper.delaunayBall(new_pt_i); @@ -426,7 +443,7 @@ class Delaunay //Use modular index since it could wrap around to 0 ModularFaceIndex modular_idx(bary_coord.array().argMin()); - if(bary_coord[modular_idx] >= 0) // inside if smallest bary coord positive + if(bary_coord[modular_idx] >= -BARY_EPS) { return element_i; } @@ -604,21 +621,24 @@ class Delaunay * \param query_pt the query point * \param element_i the element to start the search at */ - void findCavityElements(const PointType& query_pt, IndexType element_i) + void findCavityElements(const PointType& query_pt, const IndexArray& seed_elements) { constexpr int reserveSize = (DIM == 2) ? 16 : 64; IndexArray stack; stack.reserve(reserveSize); - // The containing element always belongs to the cavity. Seed it - // unconditionally so co-circular/co-spherical insertions cannot leave - // the cavity empty when the point lies on a circumsphere boundary. - if(m_mesh.isValidElement(element_i)) + // Seed the cavity with the containing element, and with any face-adjacent + // neighbors when the insertion point lies on the containing simplex + // boundary. This avoids repeatedly retriangulating structured inputs that + // insert directly onto existing edges/faces. + for(const IndexType element_i : seed_elements) { - m_checked_element_set.insert(element_i); - cavity_elems.insert(element_i); - stack.push_back(element_i); + if(m_mesh.isValidElement(element_i) && m_checked_element_set.insert(element_i).second) + { + cavity_elems.insert(element_i); + stack.push_back(element_i); + } } while(!stack.empty()) @@ -861,7 +881,7 @@ inline bool Delaunay::InsertionHelper::isPointInCircumsphere(const PointTyp const PointType& p0 = m_mesh.getVertexPosition(verts[0]); const PointType& p1 = m_mesh.getVertexPosition(verts[1]); const PointType& p2 = m_mesh.getVertexPosition(verts[2]); - return primal::in_sphere(query_pt, p0, p1, p2, primal::PRIMAL_TINY, true); + return primal::in_sphere(query_pt, p0, p1, p2, primal::PRIMAL_TINY, false); } else { @@ -869,7 +889,7 @@ inline bool Delaunay::InsertionHelper::isPointInCircumsphere(const PointTyp const PointType& p1 = m_mesh.getVertexPosition(verts[1]); const PointType& p2 = m_mesh.getVertexPosition(verts[2]); const PointType& p3 = m_mesh.getVertexPosition(verts[3]); - return primal::in_sphere(query_pt, p0, p1, p2, p3, primal::PRIMAL_TINY, true); + return primal::in_sphere(query_pt, p0, p1, p2, p3, primal::PRIMAL_TINY, false); } } diff --git a/src/axom/quest/examples/CMakeLists.txt b/src/axom/quest/examples/CMakeLists.txt index 27195b30ac..c76d278a23 100644 --- a/src/axom/quest/examples/CMakeLists.txt +++ b/src/axom/quest/examples/CMakeLists.txt @@ -559,7 +559,7 @@ if(AXOM_ENABLE_SIDRE) axom_add_test( NAME quest_scattered_interpolation_grid_2d_test - COMMAND quest_scattered_interpolation_ex -d 2 --grid-res 5 5 -q 200 + COMMAND quest_scattered_interpolation_ex -d 2 --grid-res 20 20 -q 200 ) axom_add_test( From 89c12e03dd00674d5d7a4507324e864a72236e00 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 18 Mar 2026 16:28:27 -0700 Subject: [PATCH 481/986] Improves testing of Delaunay triangulation on regular grid in 2D --- src/axom/quest/tests/quest_delaunay.cpp | 27 +++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/axom/quest/tests/quest_delaunay.cpp b/src/axom/quest/tests/quest_delaunay.cpp index fc5c320a30..36c284a03e 100644 --- a/src/axom/quest/tests/quest_delaunay.cpp +++ b/src/axom/quest/tests/quest_delaunay.cpp @@ -91,6 +91,33 @@ TEST(quest_delaunay, regular_grid_2d) expectValidDelaunay(dt, points, 8); } +TEST(quest_delaunay, boundary_location_regular_grid_2d) +{ + using PointType = typename DelaunayType<2>::PointType; + using BoundingBox = typename DelaunayType<2>::BoundingBox; + + constexpr int NX = 20; + constexpr int NY = 20; + + DelaunayType<2> dt; + dt.initializeBoundary(BoundingBox(PointType {-1.0, -1.0}, PointType {2.0, 2.0})); + + std::vector points; + points.reserve(NX * NY); + + for(int y = 0; y < NY; ++y) + { + for(int x = 0; x < NX; ++x) + { + points.push_back( + PointType {static_cast(x) / (NX - 1), static_cast(y) / (NY - 1)}); + } + } + + insertPoints(dt, points); + expectValidDelaunay(dt, points, 2 * (NX - 1) * (NY - 1)); +} + TEST(quest_delaunay, cospherical_cube_3d) { using PointType = typename DelaunayType<3>::PointType; From fe56e421fda78b4996945e074c88f03a82f621ff Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 18 Mar 2026 20:10:21 -0700 Subject: [PATCH 482/986] WIP: Improvements to 3D initialization and queries for regular grids --- src/axom/quest/Delaunay.hpp | 587 ++++++++++++++++++++++-- src/axom/quest/tests/quest_delaunay.cpp | 99 ++++ 2 files changed, 637 insertions(+), 49 deletions(-) diff --git a/src/axom/quest/Delaunay.hpp b/src/axom/quest/Delaunay.hpp index f2c6edb5c1..7a6bc62d68 100644 --- a/src/axom/quest/Delaunay.hpp +++ b/src/axom/quest/Delaunay.hpp @@ -19,8 +19,10 @@ #include #include #include +#include #include #include +#include namespace axom { @@ -55,6 +57,7 @@ class Delaunay static constexpr int VERT_PER_ELEMENT = DIM + 1; static constexpr IndexType INVALID_INDEX = -1; + static constexpr double PREDICATE_PERTURB_EPS = 1e-10; private: using ModularFaceIndex = @@ -127,21 +130,8 @@ class Delaunay // Run the insertion operation by finding invalidated elements around the point (the "cavity") // and replacing them with new valid elements (the Delaunay "ball") - IndexArray seed_elements; - seed_elements.push_back(element_i); - - const BaryCoordType bary_coord = getBaryCoords(element_i, new_pt); - for(int i = 0; i < VERT_PER_ELEMENT; ++i) - { - if(axom::utilities::abs(bary_coord[i]) <= BARY_EPS) - { - const IndexType nbr = m_mesh.adjacentElements(element_i)[ModularFaceIndex(i) + 1]; - if(m_mesh.isValidElement(nbr)) - { - seed_elements.push_back(nbr); - } - } - } + const BaryCoordType bary_coord = getRawBaryCoords(element_i, new_pt); + const IndexArray seed_elements = getSeedElements(element_i, bary_coord); InsertionHelper insertionHelper(m_mesh); insertionHelper.findCavityElements(new_pt, seed_elements); @@ -400,6 +390,43 @@ class Delaunay return valid; } + /// \brief Returns true when an element and all of its vertices are valid for point-location predicates + bool isSearchableElement(IndexType element_idx) const + { + if(!m_mesh.isValidElement(element_idx)) + { + return false; + } + + const auto verts = m_mesh.boundaryVertices(element_idx); + for(auto idx : verts.positions()) + { + if(!m_mesh.isValidVertex(verts[idx])) + { + return false; + } + } + + return true; + } + + enum class PointLocationStatus + { + Found, + Outside, + Failed + }; + + struct PointLocationResult + { + IndexType element_idx {INVALID_INDEX}; + PointLocationStatus status {PointLocationStatus::Failed}; + }; + + static constexpr int QUERY_SEARCH_RADIUS = 6; + static constexpr int QUERY_CANDIDATE_LIMIT = 128; + static constexpr int WALK_NEIGHBORHOOD_LAYERS = 2; + /// \brief Find the index of the element that contains the query point, or the element closest to the point. IndexType findContainingElement(const PointType& query_pt, bool warnOnInvalid = true) const { @@ -417,57 +444,401 @@ class Delaunay return INVALID_INDEX; } - // Find a starting element using ElementFinder helper class - IndexType element_i = INVALID_INDEX; + const bool use_query_fallbacks = !warnOnInvalid && DIM == 3; + std::vector candidate_elements = getInitialCandidateElements(query_pt); + std::vector walked_elements; + PointLocationResult walk_result = + walkCandidateElements(query_pt, + candidate_elements, + 0, + use_query_fallbacks ? &walked_elements : nullptr); + + if(walk_result.status == PointLocationStatus::Found) + { + return walk_result.element_idx; + } + + if(walk_result.status == PointLocationStatus::Outside) + { + return INVALID_INDEX; + } + + if(use_query_fallbacks) { - const auto vertex_i = m_element_finder.getNearbyVertex(query_pt); - if(m_mesh.isValidVertex(vertex_i)) + walk_result = + findContainingElementWithQueryFallbacks(query_pt, candidate_elements, walked_elements); + if(walk_result.status == PointLocationStatus::Found) { - element_i = m_mesh.coboundaryElement(vertex_i); + return walk_result.element_idx; } + if(walk_result.status == PointLocationStatus::Outside) + { + return INVALID_INDEX; + } + } + + return findContainingElementLinear(query_pt, warnOnInvalid); + } + + /** + * \brief helper function to retrieve the barycentric coordinate of the query point in the element + */ + BaryCoordType getBaryCoords(IndexType element_idx, const PointType& q_pt) const; + + /** + * \brief helper function to retrieve the barycentric coordinate of the query point + * in the element without predicate perturbation + */ + BaryCoordType getRawBaryCoords(IndexType element_idx, const PointType& q_pt) const; - // Fallback -- start from last valid element that was inserted - if(!m_mesh.isValidElement(element_i)) + /// \brief Returns cavity seed elements based on the simplex feature containing the query point + IndexArray getSeedElements(IndexType element_idx, const BaryCoordType& bary_coord) const + { + IndexArray seed_elements; + seed_elements.push_back(element_idx); + + for(int i = 0; i < VERT_PER_ELEMENT; ++i) + { + if(axom::utilities::abs(bary_coord[i]) <= BARY_EPS) { - element_i = m_mesh.getValidElementIndex(); + const IndexType nbr = m_mesh.adjacentElements(element_idx)[ModularFaceIndex(i) + 1]; + if(m_mesh.isValidElement(nbr)) + { + seed_elements.push_back(nbr); + } } + } + + return seed_elements; + } + + static PointType perturbPointForPredicates(const PointType& pt) + { + if constexpr(DIM == 2) + { + return pt; + } + else + { + const double max_abs_coord = axom::utilities::max( + axom::utilities::abs(pt[0]), + axom::utilities::max(axom::utilities::abs(pt[1]), axom::utilities::abs(pt[2]))); + const double scale = PREDICATE_PERTURB_EPS * (1. + max_abs_coord); + + PointType perturbed = pt; + const double x = pt[0]; + const double y = pt[1]; + const double z = pt[2]; + + perturbed[0] += scale * (0.125 + y + z * z); + perturbed[1] += scale * (0.25 + z + x * x); + perturbed[2] += scale * (0.5 + x + y * y); + return perturbed; + } + } - SLIC_ASSERT(m_mesh.isValidElement(element_i)); + /// \brief Walk from a starting element until the containing element is found or the walk cycles + PointLocationResult walkToContainingElement(const PointType& query_pt, + IndexType start_element, + std::vector* visited_elements_out = nullptr) const + { + if(!isSearchableElement(start_element)) + { + return {}; } + static constexpr int MAX_WALK_STEPS = 256; + std::vector local_visited_elements; + std::vector& visited_elements = + visited_elements_out != nullptr ? *visited_elements_out : local_visited_elements; + visited_elements.clear(); + visited_elements.reserve(MAX_WALK_STEPS); + IndexType element_i = start_element; + while(1) { - const BaryCoordType bary_coord = getBaryCoords(element_i, query_pt); + if(std::find(visited_elements.begin(), visited_elements.end(), element_i) != + visited_elements.end()) + { + return {}; + } + visited_elements.push_back(element_i); - //Find the index of the most negative barycentric coord - //Use modular index since it could wrap around to 0 + const BaryCoordType bary_coord = getBaryCoords(element_i, query_pt); ModularFaceIndex modular_idx(bary_coord.array().argMin()); if(bary_coord[modular_idx] >= -BARY_EPS) { - return element_i; + return {element_i, PointLocationStatus::Found}; + } + + if(static_cast(visited_elements.size()) >= MAX_WALK_STEPS) + { + return {}; } - // else, move to that neighbor - element_i = m_mesh.adjacentElements(element_i)[modular_idx + 1]; + const IndexType next_element = m_mesh.adjacentElements(element_i)[modular_idx + 1]; + if(!m_mesh.isValidElement(next_element)) + { + return {INVALID_INDEX, PointLocationStatus::Outside}; + } - // Either there is a hole in the m_mesh, or the point is outside of the m_mesh. - // Logically, this should never happen. - if(!m_mesh.isValidElement(element_i)) + element_i = next_element; + if(!isSearchableElement(element_i)) { - SLIC_WARNING_IF(warnOnInvalid, - fmt::format("Entered invalid element in " - "Delaunay::findContainingElement(). Underlying mesh {} valid", - m_mesh.isValid() ? "is" : "is not")); - return INVALID_INDEX; + return {}; } } } - /** - * \brief helper function to retrieve the barycentric coordinate of the query point in the element - */ - BaryCoordType getBaryCoords(IndexType element_idx, const PointType& q_pt) const; + void appendCandidateElement(std::vector& candidate_elements, IndexType vertex_i) const + { + const IndexType element_i = m_mesh.coboundaryElement(vertex_i); + if(isSearchableElement(element_i) && + std::find(candidate_elements.begin(), candidate_elements.end(), element_i) == + candidate_elements.end()) + { + candidate_elements.push_back(element_i); + } + } + + void appendCandidateElementsFromVertices(std::vector& candidate_elements, + const std::vector& candidate_vertices) const + { + for(const auto vertex_i : candidate_vertices) + { + appendCandidateElement(candidate_elements, vertex_i); + } + } + + std::vector getInitialCandidateElements(const PointType& query_pt) const + { + std::vector candidate_elements; + candidate_elements.reserve(1); + + const IndexType initial_vertex = m_element_finder.getNearbyVertex(query_pt); + if(m_mesh.isValidVertex(initial_vertex)) + { + appendCandidateElement(candidate_elements, initial_vertex); + } + + if(candidate_elements.empty()) + { + for(auto elem : m_mesh.elements().positions()) + { + if(isSearchableElement(elem)) + { + candidate_elements.push_back(elem); + break; + } + } + } + + return candidate_elements; + } + + PointLocationResult walkCandidateElements(const PointType& query_pt, + const std::vector& candidate_elements, + std::size_t start_idx = 0, + std::vector* walked_elements = nullptr) const + { + for(std::size_t idx = start_idx; idx < candidate_elements.size(); ++idx) + { + std::vector* visited_elements = + (walked_elements != nullptr && idx == start_idx) ? walked_elements : nullptr; + PointLocationResult walk_result = + walkToContainingElement(query_pt, candidate_elements[idx], visited_elements); + if(walk_result.status != PointLocationStatus::Failed) + { + return walk_result; + } + } + + return {}; + } + + PointLocationResult findContainingElementWithQueryFallbacks( + const PointType& query_pt, + std::vector& candidate_elements, + const std::vector& walked_elements) const + { + const IndexType walk_region_elem = findContainingElementFromNeighbors(query_pt, walked_elements); + if(walk_region_elem != INVALID_INDEX) + { + return {walk_region_elem, PointLocationStatus::Found}; + } + + const auto fallback_vertices = + m_element_finder.getNearbyVertices(m_mesh, query_pt, QUERY_SEARCH_RADIUS, QUERY_CANDIDATE_LIMIT); + const std::size_t initial_candidate_count = candidate_elements.size(); + appendCandidateElementsFromVertices(candidate_elements, fallback_vertices); + + PointLocationResult walk_result = + walkCandidateElements(query_pt, candidate_elements, initial_candidate_count); + if(walk_result.status != PointLocationStatus::Failed) + { + return walk_result; + } + + const IndexType nearby_elem = findContainingElementNearby(query_pt, fallback_vertices); + if(nearby_elem != INVALID_INDEX) + { + return {nearby_elem, PointLocationStatus::Found}; + } + + return {}; + } + + /// \brief Scan a small adjacency region around a failed directed walk before falling back to a full scan + IndexType findContainingElementFromNeighbors(const PointType& query_pt, + const std::vector& seed_elements) const + { + if(seed_elements.empty()) + { + return INVALID_INDEX; + } + + std::vector nearby_elements; + nearby_elements.reserve(seed_elements.size() * (1 + WALK_NEIGHBORHOOD_LAYERS * VERT_PER_ELEMENT)); + + auto appendUniqueElement = [&](IndexType element_idx, std::vector& frontier) { + if(isSearchableElement(element_idx) && + std::find(nearby_elements.begin(), nearby_elements.end(), element_idx) == + nearby_elements.end()) + { + nearby_elements.push_back(element_idx); + frontier.push_back(element_idx); + } + }; + + std::vector frontier; + frontier.reserve(seed_elements.size()); + for(const IndexType element_idx : seed_elements) + { + appendUniqueElement(element_idx, frontier); + } + + for(int layer = 0; layer < WALK_NEIGHBORHOOD_LAYERS && !frontier.empty(); ++layer) + { + std::vector next_frontier; + next_frontier.reserve(frontier.size() * VERT_PER_ELEMENT); + + for(const IndexType element_idx : frontier) + { + const auto neighbors = m_mesh.adjacentElements(element_idx); + for(int i = 0; i < VERT_PER_ELEMENT; ++i) + { + appendUniqueElement(neighbors[ModularFaceIndex(i) + 1], next_frontier); + } + } + + frontier.swap(next_frontier); + } + + IndexType best_element = INVALID_INDEX; + DataType best_min_bary = -std::numeric_limits::max(); + for(const IndexType element_idx : nearby_elements) + { + const BaryCoordType bary_coord = getBaryCoords(element_idx, query_pt); + const DataType min_bary = bary_coord[bary_coord.array().argMin()]; + if(min_bary > best_min_bary) + { + best_min_bary = min_bary; + best_element = element_idx; + } + + if(min_bary >= -BARY_EPS) + { + return element_idx; + } + } + + return INVALID_INDEX; + } + + /// \brief Falls back to a linear scan when directed point location cycles on a boundary feature + IndexType findContainingElementLinear(const PointType& query_pt, bool warnOnInvalid) const + { + IndexType best_element = INVALID_INDEX; + DataType best_min_bary = -std::numeric_limits::max(); + + for(auto element_idx : m_mesh.elements().positions()) + { + if(!isSearchableElement(element_idx)) + { + continue; + } + + const BaryCoordType bary_coord = getBaryCoords(element_idx, query_pt); + const DataType min_bary = bary_coord[bary_coord.array().argMin()]; + if(min_bary > best_min_bary) + { + best_min_bary = min_bary; + best_element = element_idx; + } + + if(min_bary >= -BARY_EPS) + { + return element_idx; + } + } + + SLIC_WARNING_IF(warnOnInvalid, + fmt::format("Unable to locate containing element for point {} after exhaustive " + "neighbor search; returning closest candidate with min barycentric " + "coordinate {:.17g}", + query_pt, + best_min_bary)); + + return best_element; + } + + /// \brief Scan the stars of nearby seed vertices as a cheaper local fallback for query point location + IndexType findContainingElementNearby(const PointType& query_pt, + const std::vector& nearby_vertices) const + { + std::vector nearby_elements; + for(const IndexType vertex_idx : nearby_vertices) + { + if(!m_mesh.isValidVertex(vertex_idx)) + { + continue; + } + + const auto star = m_mesh.vertexStar(vertex_idx); + for(const IndexType elem : star) + { + if(isSearchableElement(elem)) + { + nearby_elements.push_back(elem); + } + } + } + + std::sort(nearby_elements.begin(), nearby_elements.end()); + nearby_elements.erase(std::unique(nearby_elements.begin(), nearby_elements.end()), + nearby_elements.end()); + + IndexType best_element = INVALID_INDEX; + DataType best_min_bary = -std::numeric_limits::max(); + for(const IndexType element_idx : nearby_elements) + { + const BaryCoordType bary_coord = getBaryCoords(element_idx, query_pt); + const DataType min_bary = bary_coord[bary_coord.array().argMin()]; + if(min_bary > best_min_bary) + { + best_min_bary = min_bary; + best_element = element_idx; + } + + if(min_bary >= -BARY_EPS) + { + return element_idx; + } + } + + return INVALID_INDEX; + } private: /// \brief Predicate for when to compact internal mesh data structures after removing elements @@ -550,6 +921,97 @@ class Delaunay * \note Some bins might not point to a vertex, so users should check * that the returned index is a valid vertex, e.g. using \a mesh.isValidVertex(vertex_id) */ + inline std::vector getNearbyVertices(const IAMeshType& mesh, + const PointType& pt, + int search_radius = 1, + int max_candidates = 1) const + { + const auto cell = m_lattice.gridCell(pt); + std::vector> candidates; + + auto tryCandidate = [&](const typename LatticeType::GridCell& candidate_cell) { + const IndexType vertex_idx = flatIndex(candidate_cell); + if(mesh.isValidVertex(vertex_idx)) + { + const double sq_dist = primal::squared_distance(mesh.getVertexPosition(vertex_idx), pt); + candidates.emplace_back(sq_dist, vertex_idx); + } + }; + + if constexpr(DIM == 2) + { + for(int dj = -search_radius; dj <= search_radius; ++dj) + { + const IndexType j = cell[1] + dj; + if(j < 0 || j >= m_bins.shape()[1]) + { + continue; + } + + for(int di = -search_radius; di <= search_radius; ++di) + { + const IndexType i = cell[0] + di; + if(i < 0 || i >= m_bins.shape()[0]) + { + continue; + } + + tryCandidate(typename LatticeType::GridCell {{i, j}}); + } + } + } + else + { + for(int dk = -search_radius; dk <= search_radius; ++dk) + { + const IndexType k = cell[2] + dk; + if(k < 0 || k >= m_bins.shape()[2]) + { + continue; + } + + for(int dj = -search_radius; dj <= search_radius; ++dj) + { + const IndexType j = cell[1] + dj; + if(j < 0 || j >= m_bins.shape()[1]) + { + continue; + } + + for(int di = -search_radius; di <= search_radius; ++di) + { + const IndexType i = cell[0] + di; + if(i < 0 || i >= m_bins.shape()[0]) + { + continue; + } + + tryCandidate(typename LatticeType::GridCell {{i, j, k}}); + } + } + } + } + + std::sort(candidates.begin(), candidates.end(), [](const auto& lhs, const auto& rhs) { + return lhs.first < rhs.first; + }); + + std::vector nearby_vertices; + nearby_vertices.reserve( + axom::utilities::min(max_candidates, static_cast(candidates.size()))); + for(const auto& candidate : candidates) + { + nearby_vertices.push_back(candidate.second); + if(static_cast(nearby_vertices.size()) == max_candidates) + { + break; + } + } + + return nearby_vertices; + } + + /// \brief Returns the index of the vertex in the bin containing point \a pt inline IndexType getNearbyVertex(const PointType& pt) const { const auto cell = m_lattice.gridCell(pt); @@ -643,7 +1105,7 @@ class Delaunay while(!stack.empty()) { - IndexType element_idx = stack.back(); + const IndexType element_idx = stack.back(); stack.pop_back(); // Invariant: this element is valid, was checked and is in the cavity @@ -850,6 +1312,32 @@ inline typename Delaunay::BaryCoordType Delaunay::getBaryCoords(IndexT const PointType& query_pt) const { const auto verts = m_mesh.boundaryVertices(element_idx); + const PointType perturbed_query = perturbPointForPredicates(query_pt); + + if constexpr(DIM == 2) + { + const ElementType tri(m_mesh.getVertexPosition(verts[0]), + m_mesh.getVertexPosition(verts[1]), + m_mesh.getVertexPosition(verts[2])); + + return tri.physToBarycentric(perturbed_query); + } + else + { + const ElementType tet(perturbPointForPredicates(m_mesh.getVertexPosition(verts[0])), + perturbPointForPredicates(m_mesh.getVertexPosition(verts[1])), + perturbPointForPredicates(m_mesh.getVertexPosition(verts[2])), + perturbPointForPredicates(m_mesh.getVertexPosition(verts[3]))); + + return tet.physToBarycentric(perturbed_query); + } +} + +template +inline typename Delaunay::BaryCoordType Delaunay::getRawBaryCoords(IndexType element_idx, + const PointType& query_pt) const +{ + const auto verts = m_mesh.boundaryVertices(element_idx); if constexpr(DIM == 2) { @@ -875,21 +1363,22 @@ inline bool Delaunay::InsertionHelper::isPointInCircumsphere(const PointTyp IndexType element_idx) const { const auto verts = m_mesh.boundaryVertices(element_idx); + const PointType perturbed_query = Delaunay::perturbPointForPredicates(query_pt); if constexpr(DIM == 2) { const PointType& p0 = m_mesh.getVertexPosition(verts[0]); const PointType& p1 = m_mesh.getVertexPosition(verts[1]); const PointType& p2 = m_mesh.getVertexPosition(verts[2]); - return primal::in_sphere(query_pt, p0, p1, p2, primal::PRIMAL_TINY, false); + return primal::in_sphere(perturbed_query, p0, p1, p2, primal::PRIMAL_TINY, false); } else { - const PointType& p0 = m_mesh.getVertexPosition(verts[0]); - const PointType& p1 = m_mesh.getVertexPosition(verts[1]); - const PointType& p2 = m_mesh.getVertexPosition(verts[2]); - const PointType& p3 = m_mesh.getVertexPosition(verts[3]); - return primal::in_sphere(query_pt, p0, p1, p2, p3, primal::PRIMAL_TINY, false); + const PointType p0 = Delaunay::perturbPointForPredicates(m_mesh.getVertexPosition(verts[0])); + const PointType p1 = Delaunay::perturbPointForPredicates(m_mesh.getVertexPosition(verts[1])); + const PointType p2 = Delaunay::perturbPointForPredicates(m_mesh.getVertexPosition(verts[2])); + const PointType p3 = Delaunay::perturbPointForPredicates(m_mesh.getVertexPosition(verts[3])); + return primal::in_sphere(perturbed_query, p0, p1, p2, p3, primal::PRIMAL_TINY, true); } } diff --git a/src/axom/quest/tests/quest_delaunay.cpp b/src/axom/quest/tests/quest_delaunay.cpp index 36c284a03e..728eaa88fc 100644 --- a/src/axom/quest/tests/quest_delaunay.cpp +++ b/src/axom/quest/tests/quest_delaunay.cpp @@ -145,6 +145,105 @@ TEST(quest_delaunay, cospherical_cube_3d) expectValidDelaunay(dt, points); } +TEST(quest_delaunay, boundary_location_regular_grid_3d) +{ + using PointType = typename DelaunayType<3>::PointType; + using BoundingBox = typename DelaunayType<3>::BoundingBox; + + const double one_third = 1. / 3.; + const double two_thirds = 2. / 3.; + + DelaunayType<3> dt; + dt.initializeBoundary(BoundingBox(PointType {-0.5, -0.5, -0.5}, PointType {1.5, 1.5, 1.5})); + + std::vector inserted_points {PointType {0., two_thirds, 1.}, + PointType {0., two_thirds, one_third}, + PointType {two_thirds, two_thirds, one_third}, + PointType {two_thirds, two_thirds, two_thirds}, + PointType {one_third, one_third, one_third}, + PointType {0., 1., 1.}, + PointType {one_third, 1., one_third}, + PointType {two_thirds, 1., one_third}, + PointType {0., 0., two_thirds}, + PointType {one_third, two_thirds, two_thirds}, + PointType {1., 1., 0.}, + PointType {0., 0., 1.}, + PointType {one_third, 0., 1.}, + PointType {0., one_third, one_third}, + PointType {two_thirds, 0., one_third}, + PointType {one_third, two_thirds, one_third}}; + + insertPoints(dt, inserted_points); + + const PointType query_pt {one_third, 0., two_thirds}; + EXPECT_NE(DelaunayType<3>::INVALID_INDEX, dt.findContainingElement(query_pt)); + + inserted_points.push_back(query_pt); + dt.insertPoint(query_pt); + + expectValidDelaunay(dt, inserted_points); +} + +TEST(quest_delaunay, query_location_regular_grid_3d) +{ + using PointType = typename DelaunayType<3>::PointType; + using BoundingBox = typename DelaunayType<3>::BoundingBox; + + DelaunayType<3> dt; + dt.initializeBoundary(BoundingBox(PointType {-0.5, -0.5, -0.5}, PointType {1.5, 1.5, 1.5})); + + std::vector points; + points.reserve(4 * 4 * 4); + for(int z = 0; z < 4; ++z) + { + for(int y = 0; y < 4; ++y) + { + for(int x = 0; x < 4; ++x) + { + points.push_back(PointType {x / 3., y / 3., z / 3.}); + } + } + } + + insertPoints(dt, points); + dt.removeBoundary(); + + const std::vector queries {PointType {0.445693, 0.321066, 0.0526028}, + PointType {0.381996, 0.321531, 0.0811576}, + PointType {0.513459, 0.311519, 0.501756}, + PointType {0.499934, 0.32269, 0.0414115}, + PointType {0.541159, 0.307432, 0.546373}, + PointType {0.553746, 0.368503, 0.151107}, + PointType {0.514516, 0.372636, 0.575886}, + PointType {0.649638, 0.366708, 0.596617}}; + + for(const auto& query : queries) + { + EXPECT_NE(DelaunayType<3>::INVALID_INDEX, dt.findContainingElement(query, false)); + } +} + +TEST(quest_delaunay, query_outside_convex_hull_returns_invalid_3d) +{ + using PointType = typename DelaunayType<3>::PointType; + using BoundingBox = typename DelaunayType<3>::BoundingBox; + + DelaunayType<3> dt; + dt.initializeBoundary(BoundingBox(PointType {0., 0., 0.}, PointType {1., 1., 1.})); + + const std::vector points {PointType {0.2, 0.2, 0.2}, + PointType {0.8, 0.2, 0.2}, + PointType {0.2, 0.8, 0.2}, + PointType {0.2, 0.2, 0.8}}; + + insertPoints(dt, points); + dt.removeBoundary(); + + const PointType inside_bbox_outside_hull {0.8, 0.8, 0.8}; + EXPECT_EQ(DelaunayType<3>::INVALID_INDEX, + dt.findContainingElement(inside_bbox_outside_hull, false)); +} + //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ From 9da35b498ab1f9895305c7f38d626b259677823b Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 18 Mar 2026 22:25:51 -0700 Subject: [PATCH 483/986] Revert "WIP: Improvements to 3D initialization and queries for regular grids" This reverts commit fe6b4945ff1a8730942bbe8a39bdf14111aa8c02. --- src/axom/quest/Delaunay.hpp | 587 ++---------------------- src/axom/quest/tests/quest_delaunay.cpp | 99 ---- 2 files changed, 49 insertions(+), 637 deletions(-) diff --git a/src/axom/quest/Delaunay.hpp b/src/axom/quest/Delaunay.hpp index 7a6bc62d68..f2c6edb5c1 100644 --- a/src/axom/quest/Delaunay.hpp +++ b/src/axom/quest/Delaunay.hpp @@ -19,10 +19,8 @@ #include #include #include -#include #include #include -#include namespace axom { @@ -57,7 +55,6 @@ class Delaunay static constexpr int VERT_PER_ELEMENT = DIM + 1; static constexpr IndexType INVALID_INDEX = -1; - static constexpr double PREDICATE_PERTURB_EPS = 1e-10; private: using ModularFaceIndex = @@ -130,8 +127,21 @@ class Delaunay // Run the insertion operation by finding invalidated elements around the point (the "cavity") // and replacing them with new valid elements (the Delaunay "ball") - const BaryCoordType bary_coord = getRawBaryCoords(element_i, new_pt); - const IndexArray seed_elements = getSeedElements(element_i, bary_coord); + IndexArray seed_elements; + seed_elements.push_back(element_i); + + const BaryCoordType bary_coord = getBaryCoords(element_i, new_pt); + for(int i = 0; i < VERT_PER_ELEMENT; ++i) + { + if(axom::utilities::abs(bary_coord[i]) <= BARY_EPS) + { + const IndexType nbr = m_mesh.adjacentElements(element_i)[ModularFaceIndex(i) + 1]; + if(m_mesh.isValidElement(nbr)) + { + seed_elements.push_back(nbr); + } + } + } InsertionHelper insertionHelper(m_mesh); insertionHelper.findCavityElements(new_pt, seed_elements); @@ -390,43 +400,6 @@ class Delaunay return valid; } - /// \brief Returns true when an element and all of its vertices are valid for point-location predicates - bool isSearchableElement(IndexType element_idx) const - { - if(!m_mesh.isValidElement(element_idx)) - { - return false; - } - - const auto verts = m_mesh.boundaryVertices(element_idx); - for(auto idx : verts.positions()) - { - if(!m_mesh.isValidVertex(verts[idx])) - { - return false; - } - } - - return true; - } - - enum class PointLocationStatus - { - Found, - Outside, - Failed - }; - - struct PointLocationResult - { - IndexType element_idx {INVALID_INDEX}; - PointLocationStatus status {PointLocationStatus::Failed}; - }; - - static constexpr int QUERY_SEARCH_RADIUS = 6; - static constexpr int QUERY_CANDIDATE_LIMIT = 128; - static constexpr int WALK_NEIGHBORHOOD_LAYERS = 2; - /// \brief Find the index of the element that contains the query point, or the element closest to the point. IndexType findContainingElement(const PointType& query_pt, bool warnOnInvalid = true) const { @@ -444,401 +417,57 @@ class Delaunay return INVALID_INDEX; } - const bool use_query_fallbacks = !warnOnInvalid && DIM == 3; - std::vector candidate_elements = getInitialCandidateElements(query_pt); - std::vector walked_elements; - PointLocationResult walk_result = - walkCandidateElements(query_pt, - candidate_elements, - 0, - use_query_fallbacks ? &walked_elements : nullptr); - - if(walk_result.status == PointLocationStatus::Found) - { - return walk_result.element_idx; - } - - if(walk_result.status == PointLocationStatus::Outside) - { - return INVALID_INDEX; - } - - if(use_query_fallbacks) + // Find a starting element using ElementFinder helper class + IndexType element_i = INVALID_INDEX; { - walk_result = - findContainingElementWithQueryFallbacks(query_pt, candidate_elements, walked_elements); - if(walk_result.status == PointLocationStatus::Found) + const auto vertex_i = m_element_finder.getNearbyVertex(query_pt); + if(m_mesh.isValidVertex(vertex_i)) { - return walk_result.element_idx; + element_i = m_mesh.coboundaryElement(vertex_i); } - if(walk_result.status == PointLocationStatus::Outside) - { - return INVALID_INDEX; - } - } - - return findContainingElementLinear(query_pt, warnOnInvalid); - } - - /** - * \brief helper function to retrieve the barycentric coordinate of the query point in the element - */ - BaryCoordType getBaryCoords(IndexType element_idx, const PointType& q_pt) const; - - /** - * \brief helper function to retrieve the barycentric coordinate of the query point - * in the element without predicate perturbation - */ - BaryCoordType getRawBaryCoords(IndexType element_idx, const PointType& q_pt) const; - /// \brief Returns cavity seed elements based on the simplex feature containing the query point - IndexArray getSeedElements(IndexType element_idx, const BaryCoordType& bary_coord) const - { - IndexArray seed_elements; - seed_elements.push_back(element_idx); - - for(int i = 0; i < VERT_PER_ELEMENT; ++i) - { - if(axom::utilities::abs(bary_coord[i]) <= BARY_EPS) + // Fallback -- start from last valid element that was inserted + if(!m_mesh.isValidElement(element_i)) { - const IndexType nbr = m_mesh.adjacentElements(element_idx)[ModularFaceIndex(i) + 1]; - if(m_mesh.isValidElement(nbr)) - { - seed_elements.push_back(nbr); - } + element_i = m_mesh.getValidElementIndex(); } - } - - return seed_elements; - } - - static PointType perturbPointForPredicates(const PointType& pt) - { - if constexpr(DIM == 2) - { - return pt; - } - else - { - const double max_abs_coord = axom::utilities::max( - axom::utilities::abs(pt[0]), - axom::utilities::max(axom::utilities::abs(pt[1]), axom::utilities::abs(pt[2]))); - const double scale = PREDICATE_PERTURB_EPS * (1. + max_abs_coord); - - PointType perturbed = pt; - const double x = pt[0]; - const double y = pt[1]; - const double z = pt[2]; - - perturbed[0] += scale * (0.125 + y + z * z); - perturbed[1] += scale * (0.25 + z + x * x); - perturbed[2] += scale * (0.5 + x + y * y); - return perturbed; - } - } - /// \brief Walk from a starting element until the containing element is found or the walk cycles - PointLocationResult walkToContainingElement(const PointType& query_pt, - IndexType start_element, - std::vector* visited_elements_out = nullptr) const - { - if(!isSearchableElement(start_element)) - { - return {}; + SLIC_ASSERT(m_mesh.isValidElement(element_i)); } - static constexpr int MAX_WALK_STEPS = 256; - std::vector local_visited_elements; - std::vector& visited_elements = - visited_elements_out != nullptr ? *visited_elements_out : local_visited_elements; - visited_elements.clear(); - visited_elements.reserve(MAX_WALK_STEPS); - IndexType element_i = start_element; - while(1) { - if(std::find(visited_elements.begin(), visited_elements.end(), element_i) != - visited_elements.end()) - { - return {}; - } - visited_elements.push_back(element_i); - const BaryCoordType bary_coord = getBaryCoords(element_i, query_pt); + + //Find the index of the most negative barycentric coord + //Use modular index since it could wrap around to 0 ModularFaceIndex modular_idx(bary_coord.array().argMin()); if(bary_coord[modular_idx] >= -BARY_EPS) { - return {element_i, PointLocationStatus::Found}; - } - - if(static_cast(visited_elements.size()) >= MAX_WALK_STEPS) - { - return {}; - } - - const IndexType next_element = m_mesh.adjacentElements(element_i)[modular_idx + 1]; - if(!m_mesh.isValidElement(next_element)) - { - return {INVALID_INDEX, PointLocationStatus::Outside}; - } - - element_i = next_element; - if(!isSearchableElement(element_i)) - { - return {}; - } - } - } - - void appendCandidateElement(std::vector& candidate_elements, IndexType vertex_i) const - { - const IndexType element_i = m_mesh.coboundaryElement(vertex_i); - if(isSearchableElement(element_i) && - std::find(candidate_elements.begin(), candidate_elements.end(), element_i) == - candidate_elements.end()) - { - candidate_elements.push_back(element_i); - } - } - - void appendCandidateElementsFromVertices(std::vector& candidate_elements, - const std::vector& candidate_vertices) const - { - for(const auto vertex_i : candidate_vertices) - { - appendCandidateElement(candidate_elements, vertex_i); - } - } - - std::vector getInitialCandidateElements(const PointType& query_pt) const - { - std::vector candidate_elements; - candidate_elements.reserve(1); - - const IndexType initial_vertex = m_element_finder.getNearbyVertex(query_pt); - if(m_mesh.isValidVertex(initial_vertex)) - { - appendCandidateElement(candidate_elements, initial_vertex); - } - - if(candidate_elements.empty()) - { - for(auto elem : m_mesh.elements().positions()) - { - if(isSearchableElement(elem)) - { - candidate_elements.push_back(elem); - break; - } - } - } - - return candidate_elements; - } - - PointLocationResult walkCandidateElements(const PointType& query_pt, - const std::vector& candidate_elements, - std::size_t start_idx = 0, - std::vector* walked_elements = nullptr) const - { - for(std::size_t idx = start_idx; idx < candidate_elements.size(); ++idx) - { - std::vector* visited_elements = - (walked_elements != nullptr && idx == start_idx) ? walked_elements : nullptr; - PointLocationResult walk_result = - walkToContainingElement(query_pt, candidate_elements[idx], visited_elements); - if(walk_result.status != PointLocationStatus::Failed) - { - return walk_result; - } - } - - return {}; - } - - PointLocationResult findContainingElementWithQueryFallbacks( - const PointType& query_pt, - std::vector& candidate_elements, - const std::vector& walked_elements) const - { - const IndexType walk_region_elem = findContainingElementFromNeighbors(query_pt, walked_elements); - if(walk_region_elem != INVALID_INDEX) - { - return {walk_region_elem, PointLocationStatus::Found}; - } - - const auto fallback_vertices = - m_element_finder.getNearbyVertices(m_mesh, query_pt, QUERY_SEARCH_RADIUS, QUERY_CANDIDATE_LIMIT); - const std::size_t initial_candidate_count = candidate_elements.size(); - appendCandidateElementsFromVertices(candidate_elements, fallback_vertices); - - PointLocationResult walk_result = - walkCandidateElements(query_pt, candidate_elements, initial_candidate_count); - if(walk_result.status != PointLocationStatus::Failed) - { - return walk_result; - } - - const IndexType nearby_elem = findContainingElementNearby(query_pt, fallback_vertices); - if(nearby_elem != INVALID_INDEX) - { - return {nearby_elem, PointLocationStatus::Found}; - } - - return {}; - } - - /// \brief Scan a small adjacency region around a failed directed walk before falling back to a full scan - IndexType findContainingElementFromNeighbors(const PointType& query_pt, - const std::vector& seed_elements) const - { - if(seed_elements.empty()) - { - return INVALID_INDEX; - } - - std::vector nearby_elements; - nearby_elements.reserve(seed_elements.size() * (1 + WALK_NEIGHBORHOOD_LAYERS * VERT_PER_ELEMENT)); - - auto appendUniqueElement = [&](IndexType element_idx, std::vector& frontier) { - if(isSearchableElement(element_idx) && - std::find(nearby_elements.begin(), nearby_elements.end(), element_idx) == - nearby_elements.end()) - { - nearby_elements.push_back(element_idx); - frontier.push_back(element_idx); - } - }; - - std::vector frontier; - frontier.reserve(seed_elements.size()); - for(const IndexType element_idx : seed_elements) - { - appendUniqueElement(element_idx, frontier); - } - - for(int layer = 0; layer < WALK_NEIGHBORHOOD_LAYERS && !frontier.empty(); ++layer) - { - std::vector next_frontier; - next_frontier.reserve(frontier.size() * VERT_PER_ELEMENT); - - for(const IndexType element_idx : frontier) - { - const auto neighbors = m_mesh.adjacentElements(element_idx); - for(int i = 0; i < VERT_PER_ELEMENT; ++i) - { - appendUniqueElement(neighbors[ModularFaceIndex(i) + 1], next_frontier); - } - } - - frontier.swap(next_frontier); - } - - IndexType best_element = INVALID_INDEX; - DataType best_min_bary = -std::numeric_limits::max(); - for(const IndexType element_idx : nearby_elements) - { - const BaryCoordType bary_coord = getBaryCoords(element_idx, query_pt); - const DataType min_bary = bary_coord[bary_coord.array().argMin()]; - if(min_bary > best_min_bary) - { - best_min_bary = min_bary; - best_element = element_idx; - } - - if(min_bary >= -BARY_EPS) - { - return element_idx; - } - } - - return INVALID_INDEX; - } - - /// \brief Falls back to a linear scan when directed point location cycles on a boundary feature - IndexType findContainingElementLinear(const PointType& query_pt, bool warnOnInvalid) const - { - IndexType best_element = INVALID_INDEX; - DataType best_min_bary = -std::numeric_limits::max(); - - for(auto element_idx : m_mesh.elements().positions()) - { - if(!isSearchableElement(element_idx)) - { - continue; + return element_i; } - const BaryCoordType bary_coord = getBaryCoords(element_idx, query_pt); - const DataType min_bary = bary_coord[bary_coord.array().argMin()]; - if(min_bary > best_min_bary) - { - best_min_bary = min_bary; - best_element = element_idx; - } + // else, move to that neighbor + element_i = m_mesh.adjacentElements(element_i)[modular_idx + 1]; - if(min_bary >= -BARY_EPS) + // Either there is a hole in the m_mesh, or the point is outside of the m_mesh. + // Logically, this should never happen. + if(!m_mesh.isValidElement(element_i)) { - return element_idx; + SLIC_WARNING_IF(warnOnInvalid, + fmt::format("Entered invalid element in " + "Delaunay::findContainingElement(). Underlying mesh {} valid", + m_mesh.isValid() ? "is" : "is not")); + return INVALID_INDEX; } } - - SLIC_WARNING_IF(warnOnInvalid, - fmt::format("Unable to locate containing element for point {} after exhaustive " - "neighbor search; returning closest candidate with min barycentric " - "coordinate {:.17g}", - query_pt, - best_min_bary)); - - return best_element; } - /// \brief Scan the stars of nearby seed vertices as a cheaper local fallback for query point location - IndexType findContainingElementNearby(const PointType& query_pt, - const std::vector& nearby_vertices) const - { - std::vector nearby_elements; - for(const IndexType vertex_idx : nearby_vertices) - { - if(!m_mesh.isValidVertex(vertex_idx)) - { - continue; - } - - const auto star = m_mesh.vertexStar(vertex_idx); - for(const IndexType elem : star) - { - if(isSearchableElement(elem)) - { - nearby_elements.push_back(elem); - } - } - } - - std::sort(nearby_elements.begin(), nearby_elements.end()); - nearby_elements.erase(std::unique(nearby_elements.begin(), nearby_elements.end()), - nearby_elements.end()); - - IndexType best_element = INVALID_INDEX; - DataType best_min_bary = -std::numeric_limits::max(); - for(const IndexType element_idx : nearby_elements) - { - const BaryCoordType bary_coord = getBaryCoords(element_idx, query_pt); - const DataType min_bary = bary_coord[bary_coord.array().argMin()]; - if(min_bary > best_min_bary) - { - best_min_bary = min_bary; - best_element = element_idx; - } - - if(min_bary >= -BARY_EPS) - { - return element_idx; - } - } - - return INVALID_INDEX; - } + /** + * \brief helper function to retrieve the barycentric coordinate of the query point in the element + */ + BaryCoordType getBaryCoords(IndexType element_idx, const PointType& q_pt) const; private: /// \brief Predicate for when to compact internal mesh data structures after removing elements @@ -921,97 +550,6 @@ class Delaunay * \note Some bins might not point to a vertex, so users should check * that the returned index is a valid vertex, e.g. using \a mesh.isValidVertex(vertex_id) */ - inline std::vector getNearbyVertices(const IAMeshType& mesh, - const PointType& pt, - int search_radius = 1, - int max_candidates = 1) const - { - const auto cell = m_lattice.gridCell(pt); - std::vector> candidates; - - auto tryCandidate = [&](const typename LatticeType::GridCell& candidate_cell) { - const IndexType vertex_idx = flatIndex(candidate_cell); - if(mesh.isValidVertex(vertex_idx)) - { - const double sq_dist = primal::squared_distance(mesh.getVertexPosition(vertex_idx), pt); - candidates.emplace_back(sq_dist, vertex_idx); - } - }; - - if constexpr(DIM == 2) - { - for(int dj = -search_radius; dj <= search_radius; ++dj) - { - const IndexType j = cell[1] + dj; - if(j < 0 || j >= m_bins.shape()[1]) - { - continue; - } - - for(int di = -search_radius; di <= search_radius; ++di) - { - const IndexType i = cell[0] + di; - if(i < 0 || i >= m_bins.shape()[0]) - { - continue; - } - - tryCandidate(typename LatticeType::GridCell {{i, j}}); - } - } - } - else - { - for(int dk = -search_radius; dk <= search_radius; ++dk) - { - const IndexType k = cell[2] + dk; - if(k < 0 || k >= m_bins.shape()[2]) - { - continue; - } - - for(int dj = -search_radius; dj <= search_radius; ++dj) - { - const IndexType j = cell[1] + dj; - if(j < 0 || j >= m_bins.shape()[1]) - { - continue; - } - - for(int di = -search_radius; di <= search_radius; ++di) - { - const IndexType i = cell[0] + di; - if(i < 0 || i >= m_bins.shape()[0]) - { - continue; - } - - tryCandidate(typename LatticeType::GridCell {{i, j, k}}); - } - } - } - } - - std::sort(candidates.begin(), candidates.end(), [](const auto& lhs, const auto& rhs) { - return lhs.first < rhs.first; - }); - - std::vector nearby_vertices; - nearby_vertices.reserve( - axom::utilities::min(max_candidates, static_cast(candidates.size()))); - for(const auto& candidate : candidates) - { - nearby_vertices.push_back(candidate.second); - if(static_cast(nearby_vertices.size()) == max_candidates) - { - break; - } - } - - return nearby_vertices; - } - - /// \brief Returns the index of the vertex in the bin containing point \a pt inline IndexType getNearbyVertex(const PointType& pt) const { const auto cell = m_lattice.gridCell(pt); @@ -1105,7 +643,7 @@ class Delaunay while(!stack.empty()) { - const IndexType element_idx = stack.back(); + IndexType element_idx = stack.back(); stack.pop_back(); // Invariant: this element is valid, was checked and is in the cavity @@ -1312,32 +850,6 @@ inline typename Delaunay::BaryCoordType Delaunay::getBaryCoords(IndexT const PointType& query_pt) const { const auto verts = m_mesh.boundaryVertices(element_idx); - const PointType perturbed_query = perturbPointForPredicates(query_pt); - - if constexpr(DIM == 2) - { - const ElementType tri(m_mesh.getVertexPosition(verts[0]), - m_mesh.getVertexPosition(verts[1]), - m_mesh.getVertexPosition(verts[2])); - - return tri.physToBarycentric(perturbed_query); - } - else - { - const ElementType tet(perturbPointForPredicates(m_mesh.getVertexPosition(verts[0])), - perturbPointForPredicates(m_mesh.getVertexPosition(verts[1])), - perturbPointForPredicates(m_mesh.getVertexPosition(verts[2])), - perturbPointForPredicates(m_mesh.getVertexPosition(verts[3]))); - - return tet.physToBarycentric(perturbed_query); - } -} - -template -inline typename Delaunay::BaryCoordType Delaunay::getRawBaryCoords(IndexType element_idx, - const PointType& query_pt) const -{ - const auto verts = m_mesh.boundaryVertices(element_idx); if constexpr(DIM == 2) { @@ -1363,22 +875,21 @@ inline bool Delaunay::InsertionHelper::isPointInCircumsphere(const PointTyp IndexType element_idx) const { const auto verts = m_mesh.boundaryVertices(element_idx); - const PointType perturbed_query = Delaunay::perturbPointForPredicates(query_pt); if constexpr(DIM == 2) { const PointType& p0 = m_mesh.getVertexPosition(verts[0]); const PointType& p1 = m_mesh.getVertexPosition(verts[1]); const PointType& p2 = m_mesh.getVertexPosition(verts[2]); - return primal::in_sphere(perturbed_query, p0, p1, p2, primal::PRIMAL_TINY, false); + return primal::in_sphere(query_pt, p0, p1, p2, primal::PRIMAL_TINY, false); } else { - const PointType p0 = Delaunay::perturbPointForPredicates(m_mesh.getVertexPosition(verts[0])); - const PointType p1 = Delaunay::perturbPointForPredicates(m_mesh.getVertexPosition(verts[1])); - const PointType p2 = Delaunay::perturbPointForPredicates(m_mesh.getVertexPosition(verts[2])); - const PointType p3 = Delaunay::perturbPointForPredicates(m_mesh.getVertexPosition(verts[3])); - return primal::in_sphere(perturbed_query, p0, p1, p2, p3, primal::PRIMAL_TINY, true); + const PointType& p0 = m_mesh.getVertexPosition(verts[0]); + const PointType& p1 = m_mesh.getVertexPosition(verts[1]); + const PointType& p2 = m_mesh.getVertexPosition(verts[2]); + const PointType& p3 = m_mesh.getVertexPosition(verts[3]); + return primal::in_sphere(query_pt, p0, p1, p2, p3, primal::PRIMAL_TINY, false); } } diff --git a/src/axom/quest/tests/quest_delaunay.cpp b/src/axom/quest/tests/quest_delaunay.cpp index 728eaa88fc..36c284a03e 100644 --- a/src/axom/quest/tests/quest_delaunay.cpp +++ b/src/axom/quest/tests/quest_delaunay.cpp @@ -145,105 +145,6 @@ TEST(quest_delaunay, cospherical_cube_3d) expectValidDelaunay(dt, points); } -TEST(quest_delaunay, boundary_location_regular_grid_3d) -{ - using PointType = typename DelaunayType<3>::PointType; - using BoundingBox = typename DelaunayType<3>::BoundingBox; - - const double one_third = 1. / 3.; - const double two_thirds = 2. / 3.; - - DelaunayType<3> dt; - dt.initializeBoundary(BoundingBox(PointType {-0.5, -0.5, -0.5}, PointType {1.5, 1.5, 1.5})); - - std::vector inserted_points {PointType {0., two_thirds, 1.}, - PointType {0., two_thirds, one_third}, - PointType {two_thirds, two_thirds, one_third}, - PointType {two_thirds, two_thirds, two_thirds}, - PointType {one_third, one_third, one_third}, - PointType {0., 1., 1.}, - PointType {one_third, 1., one_third}, - PointType {two_thirds, 1., one_third}, - PointType {0., 0., two_thirds}, - PointType {one_third, two_thirds, two_thirds}, - PointType {1., 1., 0.}, - PointType {0., 0., 1.}, - PointType {one_third, 0., 1.}, - PointType {0., one_third, one_third}, - PointType {two_thirds, 0., one_third}, - PointType {one_third, two_thirds, one_third}}; - - insertPoints(dt, inserted_points); - - const PointType query_pt {one_third, 0., two_thirds}; - EXPECT_NE(DelaunayType<3>::INVALID_INDEX, dt.findContainingElement(query_pt)); - - inserted_points.push_back(query_pt); - dt.insertPoint(query_pt); - - expectValidDelaunay(dt, inserted_points); -} - -TEST(quest_delaunay, query_location_regular_grid_3d) -{ - using PointType = typename DelaunayType<3>::PointType; - using BoundingBox = typename DelaunayType<3>::BoundingBox; - - DelaunayType<3> dt; - dt.initializeBoundary(BoundingBox(PointType {-0.5, -0.5, -0.5}, PointType {1.5, 1.5, 1.5})); - - std::vector points; - points.reserve(4 * 4 * 4); - for(int z = 0; z < 4; ++z) - { - for(int y = 0; y < 4; ++y) - { - for(int x = 0; x < 4; ++x) - { - points.push_back(PointType {x / 3., y / 3., z / 3.}); - } - } - } - - insertPoints(dt, points); - dt.removeBoundary(); - - const std::vector queries {PointType {0.445693, 0.321066, 0.0526028}, - PointType {0.381996, 0.321531, 0.0811576}, - PointType {0.513459, 0.311519, 0.501756}, - PointType {0.499934, 0.32269, 0.0414115}, - PointType {0.541159, 0.307432, 0.546373}, - PointType {0.553746, 0.368503, 0.151107}, - PointType {0.514516, 0.372636, 0.575886}, - PointType {0.649638, 0.366708, 0.596617}}; - - for(const auto& query : queries) - { - EXPECT_NE(DelaunayType<3>::INVALID_INDEX, dt.findContainingElement(query, false)); - } -} - -TEST(quest_delaunay, query_outside_convex_hull_returns_invalid_3d) -{ - using PointType = typename DelaunayType<3>::PointType; - using BoundingBox = typename DelaunayType<3>::BoundingBox; - - DelaunayType<3> dt; - dt.initializeBoundary(BoundingBox(PointType {0., 0., 0.}, PointType {1., 1., 1.})); - - const std::vector points {PointType {0.2, 0.2, 0.2}, - PointType {0.8, 0.2, 0.2}, - PointType {0.2, 0.8, 0.2}, - PointType {0.2, 0.2, 0.8}}; - - insertPoints(dt, points); - dt.removeBoundary(); - - const PointType inside_bbox_outside_hull {0.8, 0.8, 0.8}; - EXPECT_EQ(DelaunayType<3>::INVALID_INDEX, - dt.findContainingElement(inside_bbox_outside_hull, false)); -} - //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ From fdc39ee2c6a0c2539ac19e710b692f6d12a5bca1 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 18 Mar 2026 22:31:31 -0700 Subject: [PATCH 484/986] Harden 3D Delaunay insertion on exact regular grids Improves point location, adds linear search fallback for failed insertions and slight perturbations on barycentric predicates. --- src/axom/quest/Delaunay.hpp | 161 ++++++++++++++++++++---- src/axom/quest/tests/quest_delaunay.cpp | 39 ++++++ 2 files changed, 178 insertions(+), 22 deletions(-) diff --git a/src/axom/quest/Delaunay.hpp b/src/axom/quest/Delaunay.hpp index f2c6edb5c1..c8e33ab5dd 100644 --- a/src/axom/quest/Delaunay.hpp +++ b/src/axom/quest/Delaunay.hpp @@ -21,6 +21,7 @@ #include #include #include +#include namespace axom { @@ -55,6 +56,7 @@ class Delaunay static constexpr int VERT_PER_ELEMENT = DIM + 1; static constexpr IndexType INVALID_INDEX = -1; + static constexpr double PREDICATE_PERTURB_EPS = 1e-10; private: using ModularFaceIndex = @@ -127,21 +129,8 @@ class Delaunay // Run the insertion operation by finding invalidated elements around the point (the "cavity") // and replacing them with new valid elements (the Delaunay "ball") - IndexArray seed_elements; - seed_elements.push_back(element_i); - - const BaryCoordType bary_coord = getBaryCoords(element_i, new_pt); - for(int i = 0; i < VERT_PER_ELEMENT; ++i) - { - if(axom::utilities::abs(bary_coord[i]) <= BARY_EPS) - { - const IndexType nbr = m_mesh.adjacentElements(element_i)[ModularFaceIndex(i) + 1]; - if(m_mesh.isValidElement(nbr)) - { - seed_elements.push_back(nbr); - } - } - } + const BaryCoordType bary_coord = getRawBaryCoords(element_i, new_pt); + const IndexArray seed_elements = getSeedElements(element_i, bary_coord); InsertionHelper insertionHelper(m_mesh); insertionHelper.findCavityElements(new_pt, seed_elements); @@ -435,8 +424,15 @@ class Delaunay SLIC_ASSERT(m_mesh.isValidElement(element_i)); } + static constexpr int MAX_WALK_STEPS = 256; + std::set visited_elements; while(1) { + if(!visited_elements.insert(element_i).second) + { + return findContainingElementLinear(query_pt, warnOnInvalid); + } + const BaryCoordType bary_coord = getBaryCoords(element_i, query_pt); //Find the index of the most negative barycentric coord @@ -448,6 +444,11 @@ class Delaunay return element_i; } + if(static_cast(visited_elements.size()) >= MAX_WALK_STEPS) + { + return findContainingElementLinear(query_pt, warnOnInvalid); + } + // else, move to that neighbor element_i = m_mesh.adjacentElements(element_i)[modular_idx + 1]; @@ -469,6 +470,95 @@ class Delaunay */ BaryCoordType getBaryCoords(IndexType element_idx, const PointType& q_pt) const; + /** + * \brief helper function to retrieve the barycentric coordinate of the query point + * in the element without predicate perturbation + */ + BaryCoordType getRawBaryCoords(IndexType element_idx, const PointType& q_pt) const; + + /// \brief Returns cavity seed elements based on the simplex feature containing the query point + IndexArray getSeedElements(IndexType element_idx, const BaryCoordType& bary_coord) const + { + IndexArray seed_elements; + seed_elements.push_back(element_idx); + + for(int i = 0; i < VERT_PER_ELEMENT; ++i) + { + if(axom::utilities::abs(bary_coord[i]) <= BARY_EPS) + { + const IndexType nbr = m_mesh.adjacentElements(element_idx)[ModularFaceIndex(i) + 1]; + if(m_mesh.isValidElement(nbr)) + { + seed_elements.push_back(nbr); + } + } + } + + return seed_elements; + } + + static PointType perturbPointForPredicates(const PointType& pt) + { + if constexpr(DIM == 2) + { + return pt; + } + else + { + const double max_abs_coord = + axom::utilities::max(axom::utilities::abs(pt[0]), + axom::utilities::max(axom::utilities::abs(pt[1]), + axom::utilities::abs(pt[2]))); + const double scale = PREDICATE_PERTURB_EPS * (1. + max_abs_coord); + + PointType perturbed = pt; + const double x = pt[0]; + const double y = pt[1]; + const double z = pt[2]; + + perturbed[0] += scale * (0.125 + y + z * z); + perturbed[1] += scale * (0.25 + z + x * x); + perturbed[2] += scale * (0.5 + x + y * y); + return perturbed; + } + } + + /// \brief Falls back to a linear scan when directed point location cycles on a boundary feature + IndexType findContainingElementLinear(const PointType& query_pt, bool warnOnInvalid) const + { + IndexType best_element = INVALID_INDEX; + DataType best_min_bary = -std::numeric_limits::max(); + + for(auto element_idx : m_mesh.elements().positions()) + { + if(!m_mesh.isValidElement(element_idx)) + { + continue; + } + + const BaryCoordType bary_coord = getBaryCoords(element_idx, query_pt); + const DataType min_bary = bary_coord[bary_coord.array().argMin()]; + if(min_bary > best_min_bary) + { + best_min_bary = min_bary; + best_element = element_idx; + } + + if(min_bary >= -BARY_EPS) + { + return element_idx; + } + } + + SLIC_WARNING_IF(warnOnInvalid, + fmt::format("Unable to locate containing element for point {} after exhaustive " + "neighbor search; returning closest candidate with min barycentric " + "coordinate {:.17g}", + query_pt, + best_min_bary)); + return best_element; + } + private: /// \brief Predicate for when to compact internal mesh data structures after removing elements bool shouldCompactMesh() const @@ -643,7 +733,7 @@ class Delaunay while(!stack.empty()) { - IndexType element_idx = stack.back(); + const IndexType element_idx = stack.back(); stack.pop_back(); // Invariant: this element is valid, was checked and is in the cavity @@ -850,6 +940,32 @@ inline typename Delaunay::BaryCoordType Delaunay::getBaryCoords(IndexT const PointType& query_pt) const { const auto verts = m_mesh.boundaryVertices(element_idx); + const PointType perturbed_query = perturbPointForPredicates(query_pt); + + if constexpr(DIM == 2) + { + const ElementType tri(m_mesh.getVertexPosition(verts[0]), + m_mesh.getVertexPosition(verts[1]), + m_mesh.getVertexPosition(verts[2])); + + return tri.physToBarycentric(perturbed_query); + } + else + { + const ElementType tet(perturbPointForPredicates(m_mesh.getVertexPosition(verts[0])), + perturbPointForPredicates(m_mesh.getVertexPosition(verts[1])), + perturbPointForPredicates(m_mesh.getVertexPosition(verts[2])), + perturbPointForPredicates(m_mesh.getVertexPosition(verts[3]))); + + return tet.physToBarycentric(perturbed_query); + } +} + +template +inline typename Delaunay::BaryCoordType Delaunay::getRawBaryCoords(IndexType element_idx, + const PointType& query_pt) const +{ + const auto verts = m_mesh.boundaryVertices(element_idx); if constexpr(DIM == 2) { @@ -875,21 +991,22 @@ inline bool Delaunay::InsertionHelper::isPointInCircumsphere(const PointTyp IndexType element_idx) const { const auto verts = m_mesh.boundaryVertices(element_idx); + const PointType perturbed_query = Delaunay::perturbPointForPredicates(query_pt); if constexpr(DIM == 2) { const PointType& p0 = m_mesh.getVertexPosition(verts[0]); const PointType& p1 = m_mesh.getVertexPosition(verts[1]); const PointType& p2 = m_mesh.getVertexPosition(verts[2]); - return primal::in_sphere(query_pt, p0, p1, p2, primal::PRIMAL_TINY, false); + return primal::in_sphere(perturbed_query, p0, p1, p2, primal::PRIMAL_TINY, false); } else { - const PointType& p0 = m_mesh.getVertexPosition(verts[0]); - const PointType& p1 = m_mesh.getVertexPosition(verts[1]); - const PointType& p2 = m_mesh.getVertexPosition(verts[2]); - const PointType& p3 = m_mesh.getVertexPosition(verts[3]); - return primal::in_sphere(query_pt, p0, p1, p2, p3, primal::PRIMAL_TINY, false); + const PointType p0 = Delaunay::perturbPointForPredicates(m_mesh.getVertexPosition(verts[0])); + const PointType p1 = Delaunay::perturbPointForPredicates(m_mesh.getVertexPosition(verts[1])); + const PointType p2 = Delaunay::perturbPointForPredicates(m_mesh.getVertexPosition(verts[2])); + const PointType p3 = Delaunay::perturbPointForPredicates(m_mesh.getVertexPosition(verts[3])); + return primal::in_sphere(perturbed_query, p0, p1, p2, p3, primal::PRIMAL_TINY, true); } } diff --git a/src/axom/quest/tests/quest_delaunay.cpp b/src/axom/quest/tests/quest_delaunay.cpp index 36c284a03e..923b455bca 100644 --- a/src/axom/quest/tests/quest_delaunay.cpp +++ b/src/axom/quest/tests/quest_delaunay.cpp @@ -145,6 +145,45 @@ TEST(quest_delaunay, cospherical_cube_3d) expectValidDelaunay(dt, points); } +TEST(quest_delaunay, boundary_location_regular_grid_3d) +{ + using PointType = typename DelaunayType<3>::PointType; + using BoundingBox = typename DelaunayType<3>::BoundingBox; + + const double one_third = 1. / 3.; + const double two_thirds = 2. / 3.; + + DelaunayType<3> dt; + dt.initializeBoundary(BoundingBox(PointType {-0.5, -0.5, -0.5}, PointType {1.5, 1.5, 1.5})); + + std::vector inserted_points {PointType {0., two_thirds, 1.}, + PointType {0., two_thirds, one_third}, + PointType {two_thirds, two_thirds, one_third}, + PointType {two_thirds, two_thirds, two_thirds}, + PointType {one_third, one_third, one_third}, + PointType {0., 1., 1.}, + PointType {one_third, 1., one_third}, + PointType {two_thirds, 1., one_third}, + PointType {0., 0., two_thirds}, + PointType {one_third, two_thirds, two_thirds}, + PointType {1., 1., 0.}, + PointType {0., 0., 1.}, + PointType {one_third, 0., 1.}, + PointType {0., one_third, one_third}, + PointType {two_thirds, 0., one_third}, + PointType {one_third, two_thirds, one_third}}; + + insertPoints(dt, inserted_points); + + const PointType query_pt {one_third, 0., two_thirds}; + EXPECT_NE(DelaunayType<3>::INVALID_INDEX, dt.findContainingElement(query_pt)); + + inserted_points.push_back(query_pt); + dt.insertPoint(query_pt); + + expectValidDelaunay(dt, inserted_points); +} + //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ From b6c1880a5e326bc586b7fce285b40961efee022d Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 18 Mar 2026 22:46:44 -0700 Subject: [PATCH 485/986] Recover 3D Delaunay query performance on grid and random inputs * When walking toward the query point, mark elements as Found, Outside and failed. * Adds fast exit when query point is outside the convex hull. * Adds local neighborhood search before reverting to linear search. --- src/axom/quest/Delaunay.hpp | 403 +++++++++++++++++++++--- src/axom/quest/tests/quest_delaunay.cpp | 60 ++++ 2 files changed, 427 insertions(+), 36 deletions(-) diff --git a/src/axom/quest/Delaunay.hpp b/src/axom/quest/Delaunay.hpp index c8e33ab5dd..735ae8b7f6 100644 --- a/src/axom/quest/Delaunay.hpp +++ b/src/axom/quest/Delaunay.hpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -389,6 +390,39 @@ class Delaunay return valid; } + /// \brief Returns true when an element and all of its vertices are valid for point-location predicates + bool isSearchableElement(IndexType element_idx) const + { + if(!m_mesh.isValidElement(element_idx)) + { + return false; + } + + const auto verts = m_mesh.boundaryVertices(element_idx); + for(auto idx : verts.positions()) + { + if(!m_mesh.isValidVertex(verts[idx])) + { + return false; + } + } + + return true; + } + + enum class PointLocationStatus + { + Found, + Outside, + Failed + }; + + struct PointLocationResult + { + IndexType element_idx {INVALID_INDEX}; + PointLocationStatus status {PointLocationStatus::Failed}; + }; + /// \brief Find the index of the element that contains the query point, or the element closest to the point. IndexType findContainingElement(const PointType& query_pt, bool warnOnInvalid = true) const { @@ -406,63 +440,98 @@ class Delaunay return INVALID_INDEX; } - // Find a starting element using ElementFinder helper class - IndexType element_i = INVALID_INDEX; - { - const auto vertex_i = m_element_finder.getNearbyVertex(query_pt); - if(m_mesh.isValidVertex(vertex_i)) + constexpr int query_search_radius = 6; + constexpr int query_candidate_limit = 128; + + std::vector candidate_elements; + auto appendCandidateElements = [&](const std::vector& candidate_vertices) { + for(const auto vertex_i : candidate_vertices) { - element_i = m_mesh.coboundaryElement(vertex_i); + const IndexType element_i = m_mesh.coboundaryElement(vertex_i); + if(isSearchableElement(element_i) && + std::find(candidate_elements.begin(), candidate_elements.end(), element_i) == + candidate_elements.end()) + { + candidate_elements.push_back(element_i); + } } + }; - // Fallback -- start from last valid element that was inserted - if(!m_mesh.isValidElement(element_i)) + { + const IndexType initial_vertex = m_element_finder.getNearbyVertex(query_pt); + if(m_mesh.isValidVertex(initial_vertex)) { - element_i = m_mesh.getValidElementIndex(); + appendCandidateElements(std::vector {initial_vertex}); } - SLIC_ASSERT(m_mesh.isValidElement(element_i)); + if(candidate_elements.empty()) + { + for(auto elem : m_mesh.elements().positions()) + { + if(isSearchableElement(elem)) + { + candidate_elements.push_back(elem); + break; + } + } + } } - static constexpr int MAX_WALK_STEPS = 256; - std::set visited_elements; - while(1) + std::vector walked_elements; + for(std::size_t idx = 0; idx < candidate_elements.size(); ++idx) { - if(!visited_elements.insert(element_i).second) + std::vector* visited_elements = + (!warnOnInvalid && DIM == 3 && idx == 0) ? &walked_elements : nullptr; + const PointLocationResult walk_result = + walkToContainingElement(query_pt, candidate_elements[idx], visited_elements); + if(walk_result.status == PointLocationStatus::Found) { - return findContainingElementLinear(query_pt, warnOnInvalid); + return walk_result.element_idx; } - const BaryCoordType bary_coord = getBaryCoords(element_i, query_pt); - - //Find the index of the most negative barycentric coord - //Use modular index since it could wrap around to 0 - ModularFaceIndex modular_idx(bary_coord.array().argMin()); - - if(bary_coord[modular_idx] >= -BARY_EPS) + if(walk_result.status == PointLocationStatus::Outside) { - return element_i; + return INVALID_INDEX; } + } - if(static_cast(visited_elements.size()) >= MAX_WALK_STEPS) + if(!warnOnInvalid && DIM == 3) + { + const IndexType walk_region_elem = + findContainingElementFromNeighbors(query_pt, walked_elements); + if(walk_region_elem != INVALID_INDEX) { - return findContainingElementLinear(query_pt, warnOnInvalid); + return walk_region_elem; } - // else, move to that neighbor - element_i = m_mesh.adjacentElements(element_i)[modular_idx + 1]; + const auto fallback_vertices = m_element_finder.getNearbyVertices( + m_mesh, query_pt, query_search_radius, query_candidate_limit); + const std::size_t initial_candidate_count = candidate_elements.size(); + appendCandidateElements(fallback_vertices); - // Either there is a hole in the m_mesh, or the point is outside of the m_mesh. - // Logically, this should never happen. - if(!m_mesh.isValidElement(element_i)) + for(std::size_t idx = initial_candidate_count; idx < candidate_elements.size(); ++idx) { - SLIC_WARNING_IF(warnOnInvalid, - fmt::format("Entered invalid element in " - "Delaunay::findContainingElement(). Underlying mesh {} valid", - m_mesh.isValid() ? "is" : "is not")); - return INVALID_INDEX; + const PointLocationResult walk_result = + walkToContainingElement(query_pt, candidate_elements[idx]); + if(walk_result.status == PointLocationStatus::Found) + { + return walk_result.element_idx; + } + + if(walk_result.status == PointLocationStatus::Outside) + { + return INVALID_INDEX; + } + } + + const IndexType nearby_elem = findContainingElementNearby(query_pt, fallback_vertices); + if(nearby_elem != INVALID_INDEX) + { + return nearby_elem; } } + + return findContainingElementLinear(query_pt, warnOnInvalid); } /** @@ -523,6 +592,129 @@ class Delaunay } } + /// \brief Walk from a starting element until the containing element is found or the walk cycles + PointLocationResult walkToContainingElement( + const PointType& query_pt, + IndexType start_element, + std::vector* visited_elements_out = nullptr) const + { + if(!isSearchableElement(start_element)) + { + return {}; + } + + static constexpr int MAX_WALK_STEPS = 256; + std::vector local_visited_elements; + std::vector& visited_elements = + visited_elements_out != nullptr ? *visited_elements_out : local_visited_elements; + visited_elements.clear(); + visited_elements.reserve(MAX_WALK_STEPS); + IndexType element_i = start_element; + + while(1) + { + if(std::find(visited_elements.begin(), visited_elements.end(), element_i) != + visited_elements.end()) + { + return {}; + } + visited_elements.push_back(element_i); + + const BaryCoordType bary_coord = getBaryCoords(element_i, query_pt); + ModularFaceIndex modular_idx(bary_coord.array().argMin()); + + if(bary_coord[modular_idx] >= -BARY_EPS) + { + return {element_i, PointLocationStatus::Found}; + } + + if(static_cast(visited_elements.size()) >= MAX_WALK_STEPS) + { + return {}; + } + + const IndexType next_element = m_mesh.adjacentElements(element_i)[modular_idx + 1]; + if(!m_mesh.isValidElement(next_element)) + { + return {INVALID_INDEX, PointLocationStatus::Outside}; + } + + element_i = next_element; + if(!isSearchableElement(element_i)) + { + return {}; + } + } + } + + /// \brief Scan a small adjacency region around a failed directed walk before falling back to a full scan + IndexType findContainingElementFromNeighbors(const PointType& query_pt, + const std::vector& seed_elements) const + { + if(seed_elements.empty()) + { + return INVALID_INDEX; + } + + constexpr int num_layers = 2; + std::vector nearby_elements; + nearby_elements.reserve(seed_elements.size() * (1 + num_layers * VERT_PER_ELEMENT)); + + auto appendUniqueElement = [&](IndexType element_idx, std::vector& frontier) { + if(isSearchableElement(element_idx) && + std::find(nearby_elements.begin(), nearby_elements.end(), element_idx) == + nearby_elements.end()) + { + nearby_elements.push_back(element_idx); + frontier.push_back(element_idx); + } + }; + + std::vector frontier; + frontier.reserve(seed_elements.size()); + for(const IndexType element_idx : seed_elements) + { + appendUniqueElement(element_idx, frontier); + } + + for(int layer = 0; layer < num_layers && !frontier.empty(); ++layer) + { + std::vector next_frontier; + next_frontier.reserve(frontier.size() * VERT_PER_ELEMENT); + + for(const IndexType element_idx : frontier) + { + const auto neighbors = m_mesh.adjacentElements(element_idx); + for(int i = 0; i < VERT_PER_ELEMENT; ++i) + { + appendUniqueElement(neighbors[ModularFaceIndex(i) + 1], next_frontier); + } + } + + frontier.swap(next_frontier); + } + + IndexType best_element = INVALID_INDEX; + DataType best_min_bary = -std::numeric_limits::max(); + for(const IndexType element_idx : nearby_elements) + { + const BaryCoordType bary_coord = getBaryCoords(element_idx, query_pt); + const DataType min_bary = bary_coord[bary_coord.array().argMin()]; + if(min_bary > best_min_bary) + { + best_min_bary = min_bary; + best_element = element_idx; + } + + if(min_bary >= -BARY_EPS) + { + return element_idx; + } + } + + return INVALID_INDEX; + } + /// \brief Falls back to a linear scan when directed point location cycles on a boundary feature IndexType findContainingElementLinear(const PointType& query_pt, bool warnOnInvalid) const { @@ -531,7 +723,7 @@ class Delaunay for(auto element_idx : m_mesh.elements().positions()) { - if(!m_mesh.isValidElement(element_idx)) + if(!isSearchableElement(element_idx)) { continue; } @@ -556,9 +748,57 @@ class Delaunay "coordinate {:.17g}", query_pt, best_min_bary)); + return best_element; } + /// \brief Scan the stars of nearby seed vertices as a cheaper local fallback for query point location + IndexType findContainingElementNearby(const PointType& query_pt, + const std::vector& nearby_vertices) const + { + std::vector nearby_elements; + for(const IndexType vertex_idx : nearby_vertices) + { + if(!m_mesh.isValidVertex(vertex_idx)) + { + continue; + } + + const auto star = m_mesh.vertexStar(vertex_idx); + for(const IndexType elem : star) + { + if(isSearchableElement(elem)) + { + nearby_elements.push_back(elem); + } + } + } + + std::sort(nearby_elements.begin(), nearby_elements.end()); + nearby_elements.erase(std::unique(nearby_elements.begin(), nearby_elements.end()), + nearby_elements.end()); + + IndexType best_element = INVALID_INDEX; + DataType best_min_bary = -std::numeric_limits::max(); + for(const IndexType element_idx : nearby_elements) + { + const BaryCoordType bary_coord = getBaryCoords(element_idx, query_pt); + const DataType min_bary = bary_coord[bary_coord.array().argMin()]; + if(min_bary > best_min_bary) + { + best_min_bary = min_bary; + best_element = element_idx; + } + + if(min_bary >= -BARY_EPS) + { + return element_idx; + } + } + + return INVALID_INDEX; + } + private: /// \brief Predicate for when to compact internal mesh data structures after removing elements bool shouldCompactMesh() const @@ -640,6 +880,97 @@ class Delaunay * \note Some bins might not point to a vertex, so users should check * that the returned index is a valid vertex, e.g. using \a mesh.isValidVertex(vertex_id) */ + inline std::vector getNearbyVertices(const IAMeshType& mesh, + const PointType& pt, + int search_radius = 1, + int max_candidates = 1) const + { + const auto cell = m_lattice.gridCell(pt); + std::vector> candidates; + + auto tryCandidate = [&](const typename LatticeType::GridCell& candidate_cell) { + const IndexType vertex_idx = flatIndex(candidate_cell); + if(mesh.isValidVertex(vertex_idx)) + { + const double sq_dist = + primal::squared_distance(mesh.getVertexPosition(vertex_idx), pt); + candidates.emplace_back(sq_dist, vertex_idx); + } + }; + + if constexpr(DIM == 2) + { + for(int dj = -search_radius; dj <= search_radius; ++dj) + { + const IndexType j = cell[1] + dj; + if(j < 0 || j >= m_bins.shape()[1]) + { + continue; + } + + for(int di = -search_radius; di <= search_radius; ++di) + { + const IndexType i = cell[0] + di; + if(i < 0 || i >= m_bins.shape()[0]) + { + continue; + } + + tryCandidate(typename LatticeType::GridCell {{i, j}}); + } + } + } + else + { + for(int dk = -search_radius; dk <= search_radius; ++dk) + { + const IndexType k = cell[2] + dk; + if(k < 0 || k >= m_bins.shape()[2]) + { + continue; + } + + for(int dj = -search_radius; dj <= search_radius; ++dj) + { + const IndexType j = cell[1] + dj; + if(j < 0 || j >= m_bins.shape()[1]) + { + continue; + } + + for(int di = -search_radius; di <= search_radius; ++di) + { + const IndexType i = cell[0] + di; + if(i < 0 || i >= m_bins.shape()[0]) + { + continue; + } + + tryCandidate(typename LatticeType::GridCell {{i, j, k}}); + } + } + } + } + + std::sort(candidates.begin(), + candidates.end(), + [](const auto& lhs, const auto& rhs) { return lhs.first < rhs.first; }); + + std::vector nearby_vertices; + nearby_vertices.reserve(axom::utilities::min(max_candidates, static_cast(candidates.size()))); + for(const auto& candidate : candidates) + { + nearby_vertices.push_back(candidate.second); + if(static_cast(nearby_vertices.size()) == max_candidates) + { + break; + } + } + + return nearby_vertices; + } + + /// \brief Returns the index of the vertex in the bin containing point \a pt inline IndexType getNearbyVertex(const PointType& pt) const { const auto cell = m_lattice.gridCell(pt); diff --git a/src/axom/quest/tests/quest_delaunay.cpp b/src/axom/quest/tests/quest_delaunay.cpp index 923b455bca..728eaa88fc 100644 --- a/src/axom/quest/tests/quest_delaunay.cpp +++ b/src/axom/quest/tests/quest_delaunay.cpp @@ -184,6 +184,66 @@ TEST(quest_delaunay, boundary_location_regular_grid_3d) expectValidDelaunay(dt, inserted_points); } +TEST(quest_delaunay, query_location_regular_grid_3d) +{ + using PointType = typename DelaunayType<3>::PointType; + using BoundingBox = typename DelaunayType<3>::BoundingBox; + + DelaunayType<3> dt; + dt.initializeBoundary(BoundingBox(PointType {-0.5, -0.5, -0.5}, PointType {1.5, 1.5, 1.5})); + + std::vector points; + points.reserve(4 * 4 * 4); + for(int z = 0; z < 4; ++z) + { + for(int y = 0; y < 4; ++y) + { + for(int x = 0; x < 4; ++x) + { + points.push_back(PointType {x / 3., y / 3., z / 3.}); + } + } + } + + insertPoints(dt, points); + dt.removeBoundary(); + + const std::vector queries {PointType {0.445693, 0.321066, 0.0526028}, + PointType {0.381996, 0.321531, 0.0811576}, + PointType {0.513459, 0.311519, 0.501756}, + PointType {0.499934, 0.32269, 0.0414115}, + PointType {0.541159, 0.307432, 0.546373}, + PointType {0.553746, 0.368503, 0.151107}, + PointType {0.514516, 0.372636, 0.575886}, + PointType {0.649638, 0.366708, 0.596617}}; + + for(const auto& query : queries) + { + EXPECT_NE(DelaunayType<3>::INVALID_INDEX, dt.findContainingElement(query, false)); + } +} + +TEST(quest_delaunay, query_outside_convex_hull_returns_invalid_3d) +{ + using PointType = typename DelaunayType<3>::PointType; + using BoundingBox = typename DelaunayType<3>::BoundingBox; + + DelaunayType<3> dt; + dt.initializeBoundary(BoundingBox(PointType {0., 0., 0.}, PointType {1., 1., 1.})); + + const std::vector points {PointType {0.2, 0.2, 0.2}, + PointType {0.8, 0.2, 0.2}, + PointType {0.2, 0.8, 0.2}, + PointType {0.2, 0.2, 0.8}}; + + insertPoints(dt, points); + dt.removeBoundary(); + + const PointType inside_bbox_outside_hull {0.8, 0.8, 0.8}; + EXPECT_EQ(DelaunayType<3>::INVALID_INDEX, + dt.findContainingElement(inside_bbox_outside_hull, false)); +} + //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ From c760e5faf1a5b023084357a4af7e7c83f276e5fb Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 18 Mar 2026 22:54:31 -0700 Subject: [PATCH 486/986] Refactors Delaunay 3D point location --- src/axom/quest/Delaunay.hpp | 225 +++++++++++++++++++++--------------- 1 file changed, 133 insertions(+), 92 deletions(-) diff --git a/src/axom/quest/Delaunay.hpp b/src/axom/quest/Delaunay.hpp index 735ae8b7f6..7a6bc62d68 100644 --- a/src/axom/quest/Delaunay.hpp +++ b/src/axom/quest/Delaunay.hpp @@ -423,6 +423,10 @@ class Delaunay PointLocationStatus status {PointLocationStatus::Failed}; }; + static constexpr int QUERY_SEARCH_RADIUS = 6; + static constexpr int QUERY_CANDIDATE_LIMIT = 128; + static constexpr int WALK_NEIGHBORHOOD_LAYERS = 2; + /// \brief Find the index of the element that contains the query point, or the element closest to the point. IndexType findContainingElement(const PointType& query_pt, bool warnOnInvalid = true) const { @@ -440,97 +444,39 @@ class Delaunay return INVALID_INDEX; } - constexpr int query_search_radius = 6; - constexpr int query_candidate_limit = 128; - - std::vector candidate_elements; - auto appendCandidateElements = [&](const std::vector& candidate_vertices) { - for(const auto vertex_i : candidate_vertices) - { - const IndexType element_i = m_mesh.coboundaryElement(vertex_i); - if(isSearchableElement(element_i) && - std::find(candidate_elements.begin(), candidate_elements.end(), element_i) == - candidate_elements.end()) - { - candidate_elements.push_back(element_i); - } - } - }; + const bool use_query_fallbacks = !warnOnInvalid && DIM == 3; + std::vector candidate_elements = getInitialCandidateElements(query_pt); + std::vector walked_elements; + PointLocationResult walk_result = + walkCandidateElements(query_pt, + candidate_elements, + 0, + use_query_fallbacks ? &walked_elements : nullptr); + if(walk_result.status == PointLocationStatus::Found) { - const IndexType initial_vertex = m_element_finder.getNearbyVertex(query_pt); - if(m_mesh.isValidVertex(initial_vertex)) - { - appendCandidateElements(std::vector {initial_vertex}); - } + return walk_result.element_idx; + } - if(candidate_elements.empty()) - { - for(auto elem : m_mesh.elements().positions()) - { - if(isSearchableElement(elem)) - { - candidate_elements.push_back(elem); - break; - } - } - } + if(walk_result.status == PointLocationStatus::Outside) + { + return INVALID_INDEX; } - std::vector walked_elements; - for(std::size_t idx = 0; idx < candidate_elements.size(); ++idx) + if(use_query_fallbacks) { - std::vector* visited_elements = - (!warnOnInvalid && DIM == 3 && idx == 0) ? &walked_elements : nullptr; - const PointLocationResult walk_result = - walkToContainingElement(query_pt, candidate_elements[idx], visited_elements); + walk_result = + findContainingElementWithQueryFallbacks(query_pt, candidate_elements, walked_elements); if(walk_result.status == PointLocationStatus::Found) { return walk_result.element_idx; } - if(walk_result.status == PointLocationStatus::Outside) { return INVALID_INDEX; } } - if(!warnOnInvalid && DIM == 3) - { - const IndexType walk_region_elem = - findContainingElementFromNeighbors(query_pt, walked_elements); - if(walk_region_elem != INVALID_INDEX) - { - return walk_region_elem; - } - - const auto fallback_vertices = m_element_finder.getNearbyVertices( - m_mesh, query_pt, query_search_radius, query_candidate_limit); - const std::size_t initial_candidate_count = candidate_elements.size(); - appendCandidateElements(fallback_vertices); - - for(std::size_t idx = initial_candidate_count; idx < candidate_elements.size(); ++idx) - { - const PointLocationResult walk_result = - walkToContainingElement(query_pt, candidate_elements[idx]); - if(walk_result.status == PointLocationStatus::Found) - { - return walk_result.element_idx; - } - - if(walk_result.status == PointLocationStatus::Outside) - { - return INVALID_INDEX; - } - } - - const IndexType nearby_elem = findContainingElementNearby(query_pt, fallback_vertices); - if(nearby_elem != INVALID_INDEX) - { - return nearby_elem; - } - } - return findContainingElementLinear(query_pt, warnOnInvalid); } @@ -574,10 +520,9 @@ class Delaunay } else { - const double max_abs_coord = - axom::utilities::max(axom::utilities::abs(pt[0]), - axom::utilities::max(axom::utilities::abs(pt[1]), - axom::utilities::abs(pt[2]))); + const double max_abs_coord = axom::utilities::max( + axom::utilities::abs(pt[0]), + axom::utilities::max(axom::utilities::abs(pt[1]), axom::utilities::abs(pt[2]))); const double scale = PREDICATE_PERTURB_EPS * (1. + max_abs_coord); PointType perturbed = pt; @@ -593,10 +538,9 @@ class Delaunay } /// \brief Walk from a starting element until the containing element is found or the walk cycles - PointLocationResult walkToContainingElement( - const PointType& query_pt, - IndexType start_element, - std::vector* visited_elements_out = nullptr) const + PointLocationResult walkToContainingElement(const PointType& query_pt, + IndexType start_element, + std::vector* visited_elements_out = nullptr) const { if(!isSearchableElement(start_element)) { @@ -647,6 +591,104 @@ class Delaunay } } + void appendCandidateElement(std::vector& candidate_elements, IndexType vertex_i) const + { + const IndexType element_i = m_mesh.coboundaryElement(vertex_i); + if(isSearchableElement(element_i) && + std::find(candidate_elements.begin(), candidate_elements.end(), element_i) == + candidate_elements.end()) + { + candidate_elements.push_back(element_i); + } + } + + void appendCandidateElementsFromVertices(std::vector& candidate_elements, + const std::vector& candidate_vertices) const + { + for(const auto vertex_i : candidate_vertices) + { + appendCandidateElement(candidate_elements, vertex_i); + } + } + + std::vector getInitialCandidateElements(const PointType& query_pt) const + { + std::vector candidate_elements; + candidate_elements.reserve(1); + + const IndexType initial_vertex = m_element_finder.getNearbyVertex(query_pt); + if(m_mesh.isValidVertex(initial_vertex)) + { + appendCandidateElement(candidate_elements, initial_vertex); + } + + if(candidate_elements.empty()) + { + for(auto elem : m_mesh.elements().positions()) + { + if(isSearchableElement(elem)) + { + candidate_elements.push_back(elem); + break; + } + } + } + + return candidate_elements; + } + + PointLocationResult walkCandidateElements(const PointType& query_pt, + const std::vector& candidate_elements, + std::size_t start_idx = 0, + std::vector* walked_elements = nullptr) const + { + for(std::size_t idx = start_idx; idx < candidate_elements.size(); ++idx) + { + std::vector* visited_elements = + (walked_elements != nullptr && idx == start_idx) ? walked_elements : nullptr; + PointLocationResult walk_result = + walkToContainingElement(query_pt, candidate_elements[idx], visited_elements); + if(walk_result.status != PointLocationStatus::Failed) + { + return walk_result; + } + } + + return {}; + } + + PointLocationResult findContainingElementWithQueryFallbacks( + const PointType& query_pt, + std::vector& candidate_elements, + const std::vector& walked_elements) const + { + const IndexType walk_region_elem = findContainingElementFromNeighbors(query_pt, walked_elements); + if(walk_region_elem != INVALID_INDEX) + { + return {walk_region_elem, PointLocationStatus::Found}; + } + + const auto fallback_vertices = + m_element_finder.getNearbyVertices(m_mesh, query_pt, QUERY_SEARCH_RADIUS, QUERY_CANDIDATE_LIMIT); + const std::size_t initial_candidate_count = candidate_elements.size(); + appendCandidateElementsFromVertices(candidate_elements, fallback_vertices); + + PointLocationResult walk_result = + walkCandidateElements(query_pt, candidate_elements, initial_candidate_count); + if(walk_result.status != PointLocationStatus::Failed) + { + return walk_result; + } + + const IndexType nearby_elem = findContainingElementNearby(query_pt, fallback_vertices); + if(nearby_elem != INVALID_INDEX) + { + return {nearby_elem, PointLocationStatus::Found}; + } + + return {}; + } + /// \brief Scan a small adjacency region around a failed directed walk before falling back to a full scan IndexType findContainingElementFromNeighbors(const PointType& query_pt, const std::vector& seed_elements) const @@ -656,9 +698,8 @@ class Delaunay return INVALID_INDEX; } - constexpr int num_layers = 2; std::vector nearby_elements; - nearby_elements.reserve(seed_elements.size() * (1 + num_layers * VERT_PER_ELEMENT)); + nearby_elements.reserve(seed_elements.size() * (1 + WALK_NEIGHBORHOOD_LAYERS * VERT_PER_ELEMENT)); auto appendUniqueElement = [&](IndexType element_idx, std::vector& frontier) { if(isSearchableElement(element_idx) && @@ -677,7 +718,7 @@ class Delaunay appendUniqueElement(element_idx, frontier); } - for(int layer = 0; layer < num_layers && !frontier.empty(); ++layer) + for(int layer = 0; layer < WALK_NEIGHBORHOOD_LAYERS && !frontier.empty(); ++layer) { std::vector next_frontier; next_frontier.reserve(frontier.size() * VERT_PER_ELEMENT); @@ -892,8 +933,7 @@ class Delaunay const IndexType vertex_idx = flatIndex(candidate_cell); if(mesh.isValidVertex(vertex_idx)) { - const double sq_dist = - primal::squared_distance(mesh.getVertexPosition(vertex_idx), pt); + const double sq_dist = primal::squared_distance(mesh.getVertexPosition(vertex_idx), pt); candidates.emplace_back(sq_dist, vertex_idx); } }; @@ -952,12 +992,13 @@ class Delaunay } } - std::sort(candidates.begin(), - candidates.end(), - [](const auto& lhs, const auto& rhs) { return lhs.first < rhs.first; }); + std::sort(candidates.begin(), candidates.end(), [](const auto& lhs, const auto& rhs) { + return lhs.first < rhs.first; + }); std::vector nearby_vertices; - nearby_vertices.reserve(axom::utilities::min(max_candidates, static_cast(candidates.size()))); + nearby_vertices.reserve( + axom::utilities::min(max_candidates, static_cast(candidates.size()))); for(const auto& candidate : candidates) { nearby_vertices.push_back(candidate.second); From 34ea18e65df7d216e851c641e18b05ab1daf9914 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 18 Mar 2026 23:29:16 -0700 Subject: [PATCH 487/986] Use symbolic rather than numeric perturbation as tie-breaker in Delaunay triangulation This is only used as a tie-breaker when the point-location is near a boundary. --- src/axom/quest/Delaunay.hpp | 330 +++++++++++++++++++++++++++--------- 1 file changed, 251 insertions(+), 79 deletions(-) diff --git a/src/axom/quest/Delaunay.hpp b/src/axom/quest/Delaunay.hpp index 7a6bc62d68..b37b8b7051 100644 --- a/src/axom/quest/Delaunay.hpp +++ b/src/axom/quest/Delaunay.hpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -57,7 +58,6 @@ class Delaunay static constexpr int VERT_PER_ELEMENT = DIM + 1; static constexpr IndexType INVALID_INDEX = -1; - static constexpr double PREDICATE_PERTURB_EPS = 1e-10; private: using ModularFaceIndex = @@ -130,7 +130,7 @@ class Delaunay // Run the insertion operation by finding invalidated elements around the point (the "cavity") // and replacing them with new valid elements (the Delaunay "ball") - const BaryCoordType bary_coord = getRawBaryCoords(element_i, new_pt); + const BaryCoordType bary_coord = getBaryCoords(element_i, new_pt); const IndexArray seed_elements = getSeedElements(element_i, bary_coord); InsertionHelper insertionHelper(m_mesh); @@ -423,6 +423,8 @@ class Delaunay PointLocationStatus status {PointLocationStatus::Failed}; }; + // These broader fallbacks are only used for 3D query point location after the + // initial directed walk fails. Insertions stay on the cheaper local path. static constexpr int QUERY_SEARCH_RADIUS = 6; static constexpr int QUERY_CANDIDATE_LIMIT = 128; static constexpr int WALK_NEIGHBORHOOD_LAYERS = 2; @@ -444,6 +446,8 @@ class Delaunay return INVALID_INDEX; } + // Query mode (`warnOnInvalid == false`) accepts points outside the convex hull, + // so it uses broader 3D recovery steps before falling back to a full scan. const bool use_query_fallbacks = !warnOnInvalid && DIM == 3; std::vector candidate_elements = getInitialCandidateElements(query_pt); std::vector walked_elements; @@ -485,12 +489,6 @@ class Delaunay */ BaryCoordType getBaryCoords(IndexType element_idx, const PointType& q_pt) const; - /** - * \brief helper function to retrieve the barycentric coordinate of the query point - * in the element without predicate perturbation - */ - BaryCoordType getRawBaryCoords(IndexType element_idx, const PointType& q_pt) const; - /// \brief Returns cavity seed elements based on the simplex feature containing the query point IndexArray getSeedElements(IndexType element_idx, const BaryCoordType& bary_coord) const { @@ -512,29 +510,173 @@ class Delaunay return seed_elements; } - static PointType perturbPointForPredicates(const PointType& pt) + static int signWithTolerance(double value, double tolerance) + { + return value > tolerance ? 1 : (value < -tolerance ? -1 : 0); + } + + static double getPointMagnitudeScale(const std::array& pts) + { + double max_abs_coord = 1.; + for(const auto& pt : pts) + { + for(int dim = 0; dim < DIM; ++dim) + { + max_abs_coord = axom::utilities::max(max_abs_coord, axom::utilities::abs(pt[dim])); + } + } + + return max_abs_coord; + } + + static double orientationTolerance(const std::array& pts) + { + const double scale = getPointMagnitudeScale(pts); + return 64. * std::numeric_limits::epsilon() * scale * scale * scale; + } + + static double determinant3(const PointType& p0, const PointType& p1, const PointType& p2) + { + return axom::numerics::determinant(p0[0], p0[1], p0[2], p1[0], p1[1], p1[2], p2[0], p2[1], p2[2]); + } + + static double orientationDeterminant(const std::array& pts) + { + return axom::numerics::determinant(pts[0][0], + pts[0][1], + pts[0][2], + 1., + pts[1][0], + pts[1][1], + pts[1][2], + 1., + pts[2][0], + pts[2][1], + pts[2][2], + 1., + pts[3][0], + pts[3][1], + pts[3][2], + 1.); + } + + static int symbolicOrientationSign(const std::array& pts, + const std::array& ranks) + { + // Simulation-of-simplicity style tie-break: if the 4x4 orientation + // determinant is effectively zero, use the earliest nonzero cofactor in a + // fixed symbolic rank order to choose one consistent sign. + const double det = orientationDeterminant(pts); + + const int det_sign = signWithTolerance(det, orientationTolerance(pts)); + if(det_sign != 0) + { + return det_sign; + } + + const std::array cofactors {-determinant3(pts[1], pts[2], pts[3]), + determinant3(pts[0], pts[2], pts[3]), + -determinant3(pts[0], pts[1], pts[3]), + determinant3(pts[0], pts[1], pts[2])}; + + std::array order {{0, 1, 2, 3}}; + std::sort(order.begin(), order.end(), [&](int lhs, int rhs) { return ranks[lhs] < ranks[rhs]; }); + + const double cofactor_tol = 64. * std::numeric_limits::epsilon() * + axom::utilities::max(1., orientationTolerance(pts)); + for(const int row : order) + { + const int sign = signWithTolerance(cofactors[row], cofactor_tol); + if(sign != 0) + { + return sign; + } + } + + return 0; + } + + int getBarycentricSign(IndexType element_idx, + const PointType& query_pt, + const BaryCoordType& bary_coord, + int bary_idx) const { - if constexpr(DIM == 2) + const double value = bary_coord[bary_idx]; + if(axom::utilities::abs(value) > BARY_EPS || DIM == 2) { - return pt; + return signWithTolerance(value, BARY_EPS); } - else + + // The only ambiguous case is a near-zero barycentric coordinate. Interpret + // that face test symbolically by replacing the corresponding tetrahedron + // vertex with the query point and evaluating the signed orientation. + const auto verts = m_mesh.boundaryVertices(element_idx); + const std::array pts { + {bary_idx == 0 ? query_pt : m_mesh.getVertexPosition(verts[0]), + bary_idx == 1 ? query_pt : m_mesh.getVertexPosition(verts[1]), + bary_idx == 2 ? query_pt : m_mesh.getVertexPosition(verts[2]), + bary_idx == 3 ? query_pt : m_mesh.getVertexPosition(verts[3])}}; + + const IndexType query_rank = 1 + + axom::utilities::max(axom::utilities::max(verts[0], verts[1]), + axom::utilities::max(verts[2], verts[3])); + // Give the query point the highest symbolic rank so zero-case face tests + // resolve deterministically without changing the stored simplex ordering. + const std::array ranks {{bary_idx == 0 ? query_rank : verts[0], + bary_idx == 1 ? query_rank : verts[1], + bary_idx == 2 ? query_rank : verts[2], + bary_idx == 3 ? query_rank : verts[3]}}; + + const std::array tet_pts {{m_mesh.getVertexPosition(verts[0]), + m_mesh.getVertexPosition(verts[1]), + m_mesh.getVertexPosition(verts[2]), + m_mesh.getVertexPosition(verts[3])}}; + const std::array tet_ranks {{verts[0], verts[1], verts[2], verts[3]}}; + + const int numerator_sign = symbolicOrientationSign(pts, ranks); + const int denominator_sign = symbolicOrientationSign(tet_pts, tet_ranks); + return numerator_sign * denominator_sign; + } + + bool isPointInsideForLocation(IndexType element_idx, + const PointType& query_pt, + const BaryCoordType& bary_coord, + ModularFaceIndex* exit_face = nullptr) const + { + int first_symbolic_negative = -1; + ModularFaceIndex min_face(bary_coord.array().argMin()); + + for(int i = 0; i < VERT_PER_ELEMENT; ++i) { - const double max_abs_coord = axom::utilities::max( - axom::utilities::abs(pt[0]), - axom::utilities::max(axom::utilities::abs(pt[1]), axom::utilities::abs(pt[2]))); - const double scale = PREDICATE_PERTURB_EPS * (1. + max_abs_coord); - - PointType perturbed = pt; - const double x = pt[0]; - const double y = pt[1]; - const double z = pt[2]; - - perturbed[0] += scale * (0.125 + y + z * z); - perturbed[1] += scale * (0.25 + z + x * x); - perturbed[2] += scale * (0.5 + x + y * y); - return perturbed; + if(bary_coord[i] < -BARY_EPS) + { + if(exit_face != nullptr) + { + *exit_face = min_face; + } + return false; + } + + if(axom::utilities::abs(bary_coord[i]) <= BARY_EPS && + getBarycentricSign(element_idx, query_pt, bary_coord, i) < 0) + { + if(first_symbolic_negative < 0) + { + first_symbolic_negative = i; + } + } + } + + if(first_symbolic_negative >= 0) + { + if(exit_face != nullptr) + { + *exit_face = ModularFaceIndex(first_symbolic_negative); + } + return false; } + + return true; } /// \brief Walk from a starting element until the containing element is found or the walk cycles @@ -565,9 +707,8 @@ class Delaunay visited_elements.push_back(element_i); const BaryCoordType bary_coord = getBaryCoords(element_i, query_pt); - ModularFaceIndex modular_idx(bary_coord.array().argMin()); - - if(bary_coord[modular_idx] >= -BARY_EPS) + ModularFaceIndex modular_idx(0); + if(isPointInsideForLocation(element_i, query_pt, bary_coord, &modular_idx)) { return {element_i, PointLocationStatus::Found}; } @@ -735,18 +876,13 @@ class Delaunay frontier.swap(next_frontier); } - IndexType best_element = INVALID_INDEX; - DataType best_min_bary = -std::numeric_limits::max(); for(const IndexType element_idx : nearby_elements) { const BaryCoordType bary_coord = getBaryCoords(element_idx, query_pt); const DataType min_bary = bary_coord[bary_coord.array().argMin()]; - if(min_bary > best_min_bary) - { - best_min_bary = min_bary; - best_element = element_idx; - } - + // Keep this recovery step strict: it only succeeds on a true containing + // element. Broader "closest element" behavior stays in the final linear + // fallback so outside-hull queries can still exit cleanly. if(min_bary >= -BARY_EPS) { return element_idx; @@ -756,7 +892,7 @@ class Delaunay return INVALID_INDEX; } - /// \brief Falls back to a linear scan when directed point location cycles on a boundary feature + /// \brief Last-resort exhaustive scan used when the cheaper local search path cannot classify the point IndexType findContainingElementLinear(const PointType& query_pt, bool warnOnInvalid) const { IndexType best_element = INVALID_INDEX; @@ -819,18 +955,10 @@ class Delaunay nearby_elements.erase(std::unique(nearby_elements.begin(), nearby_elements.end()), nearby_elements.end()); - IndexType best_element = INVALID_INDEX; - DataType best_min_bary = -std::numeric_limits::max(); for(const IndexType element_idx : nearby_elements) { const BaryCoordType bary_coord = getBaryCoords(element_idx, query_pt); const DataType min_bary = bary_coord[bary_coord.array().argMin()]; - if(min_bary > best_min_bary) - { - best_min_bary = min_bary; - best_element = element_idx; - } - if(min_bary >= -BARY_EPS) { return element_idx; @@ -1312,32 +1440,6 @@ inline typename Delaunay::BaryCoordType Delaunay::getBaryCoords(IndexT const PointType& query_pt) const { const auto verts = m_mesh.boundaryVertices(element_idx); - const PointType perturbed_query = perturbPointForPredicates(query_pt); - - if constexpr(DIM == 2) - { - const ElementType tri(m_mesh.getVertexPosition(verts[0]), - m_mesh.getVertexPosition(verts[1]), - m_mesh.getVertexPosition(verts[2])); - - return tri.physToBarycentric(perturbed_query); - } - else - { - const ElementType tet(perturbPointForPredicates(m_mesh.getVertexPosition(verts[0])), - perturbPointForPredicates(m_mesh.getVertexPosition(verts[1])), - perturbPointForPredicates(m_mesh.getVertexPosition(verts[2])), - perturbPointForPredicates(m_mesh.getVertexPosition(verts[3]))); - - return tet.physToBarycentric(perturbed_query); - } -} - -template -inline typename Delaunay::BaryCoordType Delaunay::getRawBaryCoords(IndexType element_idx, - const PointType& query_pt) const -{ - const auto verts = m_mesh.boundaryVertices(element_idx); if constexpr(DIM == 2) { @@ -1363,22 +1465,92 @@ inline bool Delaunay::InsertionHelper::isPointInCircumsphere(const PointTyp IndexType element_idx) const { const auto verts = m_mesh.boundaryVertices(element_idx); - const PointType perturbed_query = Delaunay::perturbPointForPredicates(query_pt); if constexpr(DIM == 2) { const PointType& p0 = m_mesh.getVertexPosition(verts[0]); const PointType& p1 = m_mesh.getVertexPosition(verts[1]); const PointType& p2 = m_mesh.getVertexPosition(verts[2]); - return primal::in_sphere(perturbed_query, p0, p1, p2, primal::PRIMAL_TINY, false); + return primal::in_sphere(query_pt, p0, p1, p2, primal::PRIMAL_TINY, false); } else { - const PointType p0 = Delaunay::perturbPointForPredicates(m_mesh.getVertexPosition(verts[0])); - const PointType p1 = Delaunay::perturbPointForPredicates(m_mesh.getVertexPosition(verts[1])); - const PointType p2 = Delaunay::perturbPointForPredicates(m_mesh.getVertexPosition(verts[2])); - const PointType p3 = Delaunay::perturbPointForPredicates(m_mesh.getVertexPosition(verts[3])); - return primal::in_sphere(perturbed_query, p0, p1, p2, p3, primal::PRIMAL_TINY, true); + const PointType& p0 = m_mesh.getVertexPosition(verts[0]); + const PointType& p1 = m_mesh.getVertexPosition(verts[1]); + const PointType& p2 = m_mesh.getVertexPosition(verts[2]); + const PointType& p3 = m_mesh.getVertexPosition(verts[3]); + + const auto ba = p1 - p0; + const auto ca = p2 - p0; + const auto da = p3 - p0; + const auto qa = query_pt - p0; + + const double det = axom::numerics::determinant(ba[0], + ba[1], + ba[2], + ba.squared_norm(), + ca[0], + ca[1], + ca[2], + ca.squared_norm(), + da[0], + da[1], + da[2], + da.squared_norm(), + qa[0], + qa[1], + qa[2], + qa.squared_norm()); + + const double scale = axom::utilities::max( + 1., + axom::utilities::max( + ba.norm(), + axom::utilities::max(ca.norm(), axom::utilities::max(da.norm(), qa.norm())))); + const double det_tol = + 128. * std::numeric_limits::epsilon() * scale * scale * scale * scale; + const int det_sign = Delaunay::signWithTolerance(det, det_tol); + if(det_sign != 0) + { + return det_sign < 0; + } + + // Resolve exact co-spherical ties symbolically from the lifted determinant + // cofactors, ordered by the fixed vertex/query ranks. The query point gets + // the highest rank so exact ties choose one deterministic inclusive result + // without perturbing coordinates. + const IndexType query_rank = 1 + + axom::utilities::max(axom::utilities::max(verts[0], verts[1]), + axom::utilities::max(verts[2], verts[3])); + const std::array lifted_pts {{p0, p1, p2, p3, query_pt}}; + const std::array ranks {{verts[0], verts[1], verts[2], verts[3], query_rank}}; + std::array order {{0, 1, 2, 3, 4}}; + std::sort(order.begin(), order.end(), [&](int lhs, int rhs) { return ranks[lhs] < ranks[rhs]; }); + + const double cofactor_tol = 128. * std::numeric_limits::epsilon() * scale * scale * scale; + for(const int row : order) + { + std::array cofactor_pts; + int next_idx = 0; + for(int pt_idx = 0; pt_idx < 5; ++pt_idx) + { + if(pt_idx == row) + { + continue; + } + cofactor_pts[next_idx++] = lifted_pts[pt_idx]; + } + + const double cofactor = + ((row + 3) % 2 == 0 ? 1. : -1.) * Delaunay::orientationDeterminant(cofactor_pts); + const int sign = Delaunay::signWithTolerance(cofactor, cofactor_tol); + if(sign != 0) + { + return sign < 0; + } + } + + return true; } } From 5ae65e5f36b9a8315b47ee512dc5e94a64808f0e Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 19 Mar 2026 18:16:59 -0700 Subject: [PATCH 488/986] Adds slam::MeshIA::isConforming() * Checks manifold (with boundary) condition that each facet is referenced twice (once if on boundary). * Checks that adjacencies found by facet-coboundary relation are consistent --- src/axom/slam/mesh_struct/IA.hpp | 25 ++++ src/axom/slam/mesh_struct/IA_impl.hpp | 180 ++++++++++++++++++++++++ src/axom/slam/tests/slam_IA.cpp | 191 ++++++++++++++++++++++++++ 3 files changed, 396 insertions(+) diff --git a/src/axom/slam/mesh_struct/IA.hpp b/src/axom/slam/mesh_struct/IA.hpp index 40b9ce45a2..2e6e73c0c5 100644 --- a/src/axom/slam/mesh_struct/IA.hpp +++ b/src/axom/slam/mesh_struct/IA.hpp @@ -31,6 +31,8 @@ #include "axom/slam/DynamicMap.hpp" #include "axom/slam/FieldRegistry.hpp" +#include + namespace axom { namespace slam @@ -116,6 +118,15 @@ class IAMesh using ModularFacetIndex = slam::ModularInt>; + using FacetKey = std::array; + + struct FacetRecord + { + IndexType element_idx {INVALID_ELEMENT_INDEX}; + IndexType facet_idx {INVALID_ELEMENT_INDEX}; + IndexType neighbor_idx {INVALID_ELEMENT_INDEX}; + }; + public: /// \brief Default Constructor for an empty mesh IAMesh(); @@ -135,6 +146,20 @@ class IAMesh */ bool isValid(bool verboseOutput = false) const; + /** + * \brief Checks that the simplicial complex is conforming (manifold faces and consistent adjacencies) + * + * This is stricter than \a isValid(): it verifies that each facet is used by + * at most two elements and that element adjacencies are reciprocal across + * shared facets. + * + * \note This function does not validate geometric orientation/volume. + */ + bool isConforming(bool verboseOutput = false) const; + + /// \brief Returns a canonical (sorted) facet key for element \a element_idx and local facet \a facet_idx + FacetKey getSortedFacetKey(IndexType element_idx, IndexType facet_idx) const; + /// \name Accessors for encoded Sets, Relations and Maps /// @{ diff --git a/src/axom/slam/mesh_struct/IA_impl.hpp b/src/axom/slam/mesh_struct/IA_impl.hpp index 53ab943262..2a272492d4 100644 --- a/src/axom/slam/mesh_struct/IA_impl.hpp +++ b/src/axom/slam/mesh_struct/IA_impl.hpp @@ -14,6 +14,7 @@ */ #include "axom/core/Macros.hpp" +#include "axom/core/StaticArray.hpp" #include "axom/slam/policies/SizePolicies.hpp" #include "axom/slam/ModularInt.hpp" @@ -21,6 +22,8 @@ #include #include +#include +#include namespace axom { @@ -894,6 +897,183 @@ bool IAMesh::isValid(bool verboseOutput) const return bValid; } +template +typename IAMesh::FacetKey IAMesh::getSortedFacetKey( + IndexType element_idx, + IndexType facet_idx) const +{ + FacetKey key {}; + if(!element_set.isValidEntry(element_idx)) + { + return key; + } + + SLIC_ASSERT_MSG(0 <= facet_idx && facet_idx < VERTS_PER_ELEM, "Face index is invalid."); + + const auto verts = ev_rel[element_idx]; + ModularVertexIndex mod_face(facet_idx); + for(int i = 0; i < VERTS_PER_ELEM - 1; ++i) + { + key[i] = verts[mod_face + i]; + } + + std::sort(key.begin(), key.end()); + return key; +} + +template +bool IAMesh::isConforming(bool verboseOutput) const +{ + fmt::memory_buffer out; + + bool valid = isValid(verboseOutput); + + struct FacetBucket + { + axom::StaticArray records; + int incident_count {0}; + }; + + std::map facet_records; + + auto facetKeyString = [](const FacetKey& facet_key) { + return fmt::format("[{}]", fmt::join(facet_key, ", ")); + }; + + for(auto element_idx : elements().positions()) + { + if(!isValidElement(element_idx)) + { + continue; + } + + // check that element vertices are all valid and non-repeating + const auto verts = boundaryVertices(element_idx); + for(int i = 0; i < VERTS_PER_ELEM; ++i) + { + if(!isValidVertex(verts[i])) + { + if(verboseOutput) + { + fmt::format_to(std::back_inserter(out), + "\n\tElement {} references invalid vertex {}", + element_idx, + verts[i]); + } + valid = false; + } + + for(int j = i + 1; j < VERTS_PER_ELEM; ++j) + { + if(verts[i] == verts[j]) + { + if(verboseOutput) + { + fmt::format_to(std::back_inserter(out), + "\n\tElement {} repeats vertex {}", + element_idx, + verts[i]); + } + valid = false; + } + } + } + + // build facet-element co-boundary in facet_records + const auto neighbors = adjacentElements(element_idx); + for(int facet_idx = 0; facet_idx < VERTS_PER_ELEM; ++facet_idx) + { + const FacetKey facet_key = getSortedFacetKey(element_idx, facet_idx); + FacetBucket& bucket = facet_records[facet_key]; + bucket.incident_count++; + if(bucket.incident_count <= 2) + { + bucket.records.push_back({element_idx, facet_idx, neighbors[facet_idx]}); + } + else + { + if(verboseOutput && bucket.incident_count == 3) + { + fmt::format_to(std::back_inserter(out), + "\n\tFacet {} is non-manifold (>=3 incident elements); " + "first two are ({}:{}) and ({}:{})", + facetKeyString(facet_key), + bucket.records[0].element_idx, + bucket.records[0].facet_idx, + bucket.records[1].element_idx, + bucket.records[1].facet_idx); + } + valid = false; + } + } + } + + // check for valid facet-coboundary relation + // each facet is referenced once if it is on mesh boundary; + // and twice otherwise, with consistent adjacencies + for(const auto& [facet_key, bucket] : facet_records) + { + if(bucket.incident_count == 1) + { + const FacetRecord& record = bucket.records[0]; + if(isValidElement(record.neighbor_idx)) + { + if(verboseOutput) + { + fmt::format_to(std::back_inserter(out), + "\n\tBoundary face {} on element {} facet {} points to neighbor {}", + facetKeyString(facet_key), + record.element_idx, + record.facet_idx, + record.neighbor_idx); + } + valid = false; + } + } + else if(bucket.incident_count == 2) + { + const FacetRecord& lhs = bucket.records[0]; + const FacetRecord& rhs = bucket.records[1]; + if(lhs.element_idx == rhs.element_idx || lhs.neighbor_idx != rhs.element_idx || + rhs.neighbor_idx != lhs.element_idx) + { + if(verboseOutput) + { + fmt::format_to(std::back_inserter(out), + "\n\tInterior facet {} has inconsistent adjacency: " + "({}:{}) -> {}, ({}:{}) -> {}", + facetKeyString(facet_key), + lhs.element_idx, + lhs.facet_idx, + lhs.neighbor_idx, + rhs.element_idx, + rhs.facet_idx, + rhs.neighbor_idx); + } + valid = false; + } + } + else + { + // bucket.incident_count > 2 was already reported, if requested + } + } + + if(verboseOutput) + { + if(valid) + { + SLIC_INFO("IA mesh was conforming"); + } + else + { + SLIC_INFO("IA mesh was not conforming.\n Summary: " << fmt::to_string(out)); + } + } + + return valid; +} + } // end namespace slam } // end namespace axom diff --git a/src/axom/slam/tests/slam_IA.cpp b/src/axom/slam/tests/slam_IA.cpp index ecfc72d14c..cf0fb4d73f 100644 --- a/src/axom/slam/tests/slam_IA.cpp +++ b/src/axom/slam/tests/slam_IA.cpp @@ -18,6 +18,8 @@ #include "axom/slam/mesh_struct/IA.hpp" #include "axom/slam/Utilities.hpp" +#include + namespace slam = axom::slam; namespace @@ -168,6 +170,34 @@ bool isAdjacent(const IAMeshType& ia_mesh, IndexType el_1, IndexType el_2) return false; } +template +void connectSharedFacets(IAMeshType& ia_mesh) +{ + using FacetKey = typename IAMeshType::FacetKey; + using FaceRef = std::pair; + + std::map first_facet; + for(auto element_idx : ia_mesh.elements().positions()) + { + if(!ia_mesh.isValidElement(element_idx)) + { + continue; + } + + for(IndexType face_idx = 0; face_idx < IAMeshType::VERTS_PER_ELEM; ++face_idx) + { + const FacetKey key = ia_mesh.getSortedFacetKey(element_idx, face_idx); + const auto insert_status = first_facet.insert({key, {element_idx, face_idx}}); + if(!insert_status.second) + { + const auto other = insert_status.first->second; + ia_mesh.adjacentElements(element_idx)[face_idx] = other.first; + ia_mesh.adjacentElements(other.first)[other.second] = element_idx; + } + } + } +} + } // end anonymous namespace TEST(slam_IA, empty_mesh) @@ -493,6 +523,101 @@ TEST(slam_IA, tri_mesh_remove_vert_and_compact) EXPECT_EQ(basic_mesh_data.numVertices() - 1, ia_mesh.getNumberOfValidVertices()); } +TEST(slam_IA, conforming_tri_mesh) +{ + constexpr int TDIM = 2; + constexpr int SDIM = 3; + using IAMeshType = slam::IAMesh; + + BasicTriMeshData basic_mesh_data; + IAMeshType ia_mesh(basic_mesh_data.points, basic_mesh_data.elem); + + EXPECT_TRUE(ia_mesh.isValid()); + EXPECT_TRUE(ia_mesh.isConforming(true)); +} + +TEST(slam_IA, non_manifold_edge_detected_2d) +{ + constexpr int TDIM = 2; + constexpr int SDIM = 3; + using IAMeshType = slam::IAMesh; + + IAMeshType ia_mesh; + const std::vector pts {PointType {0., 0., 0.}, + PointType {1., 0., 0.}, + PointType {0.5, 1., 0.}, + PointType {0.5, -1., 0.}, + PointType {0.5, 0., 1.}}; + for(const auto& p : pts) + { + ia_mesh.addVertex(p); + } + + const IndexType invalid = IAMeshType::ElementAdjacencyRelation::INVALID_INDEX; + IndexType neighbors[3] {invalid, invalid, invalid}; + IndexType tri0[3] {0, 1, 2}; + IndexType tri1[3] {0, 1, 3}; + IndexType tri2[3] {0, 1, 4}; + ia_mesh.addElement(tri0, neighbors); + ia_mesh.addElement(tri1, neighbors); + ia_mesh.addElement(tri2, neighbors); + + EXPECT_TRUE(ia_mesh.isValid()); + EXPECT_FALSE(ia_mesh.isConforming(true)); +} + +TEST(slam_IA, inconsistent_adjacency_detected_2d) +{ + constexpr int TDIM = 2; + constexpr int SDIM = 3; + using IAMeshType = slam::IAMesh; + + IAMeshType ia_mesh; + const std::vector pts {PointType {0., 0., 0.}, + PointType {1., 0., 0.}, + PointType {0., 1., 0.}, + PointType {1., 1., 0.}}; + for(const auto& p : pts) + { + ia_mesh.addVertex(p); + } + + const IndexType invalid = IAMeshType::ElementAdjacencyRelation::INVALID_INDEX; + IndexType neighbors[3] {invalid, invalid, invalid}; + IndexType tri0[3] {0, 1, 2}; + IndexType tri1[3] {2, 1, 3}; + const IndexType e0 = ia_mesh.addElement(tri0, neighbors); + const IndexType e1 = ia_mesh.addElement(tri1, neighbors); + + connectSharedFacets(ia_mesh); + + // Break reciprocity on the shared edge: one side points to a neighbor, the other does not. + typename IAMeshType::FacetKey shared_key {}; + bool found_shared_face = false; + for(IndexType face_idx = 0; face_idx < IAMeshType::VERTS_PER_ELEM; ++face_idx) + { + if(ia_mesh.adjacentElements(e0)[face_idx] == e1) + { + shared_key = ia_mesh.getSortedFacetKey(e0, face_idx); + found_shared_face = true; + break; + } + } + ASSERT_TRUE(found_shared_face); + + for(IndexType face_idx = 0; face_idx < IAMeshType::VERTS_PER_ELEM; ++face_idx) + { + if(ia_mesh.getSortedFacetKey(e1, face_idx) == shared_key) + { + ia_mesh.adjacentElements(e1)[face_idx] = invalid; + break; + } + } + + EXPECT_TRUE(ia_mesh.isValid()); + EXPECT_FALSE(ia_mesh.isConforming(true)); +} + TEST(slam_IA, basic_tet_mesh) { SLIC_INFO("Testing constructing basic tetrahedral mesh..."); @@ -548,6 +673,72 @@ TEST(slam_IA, basic_tet_mesh) SLIC_INFO("Done"); } +TEST(slam_IA, conforming_tet_mesh) +{ + constexpr int TDIM = 3; + constexpr int SDIM = 3; + using IAMeshType = slam::IAMesh; + + BasicTetMeshData basic_mesh_data; + IAMeshType ia_mesh(basic_mesh_data.points, basic_mesh_data.elem); + + EXPECT_TRUE(ia_mesh.isValid()); + EXPECT_TRUE(ia_mesh.isConforming(true)); +} + +TEST(slam_IA, inconsistent_adjacency_detected_3d) +{ + constexpr int TDIM = 3; + constexpr int SDIM = 3; + using IAMeshType = slam::IAMesh; + + IAMeshType ia_mesh; + const std::vector pts {PointType {0., 0., 0.}, + PointType {1., 0., 0.}, + PointType {0., 1., 0.}, + PointType {0., 0., 1.}, + PointType {0., 0., -1.}}; + for(const auto& p : pts) + { + ia_mesh.addVertex(p); + } + + const IndexType invalid = IAMeshType::ElementAdjacencyRelation::INVALID_INDEX; + IndexType neighbors[4] {invalid, invalid, invalid, invalid}; + IndexType tet0[4] {0, 1, 2, 3}; + IndexType tet1[4] {0, 1, 2, 4}; + const IndexType e0 = ia_mesh.addElement(tet0, neighbors); + const IndexType e1 = ia_mesh.addElement(tet1, neighbors); + + connectSharedFacets(ia_mesh); + + // Break reciprocity on the shared face. + typename IAMeshType::FacetKey shared_key {}; + bool found_shared_face = false; + for(IndexType face_idx = 0; face_idx < IAMeshType::VERTS_PER_ELEM; ++face_idx) + { + if(ia_mesh.adjacentElements(e0)[face_idx] == e1) + { + shared_key = ia_mesh.getSortedFacetKey(e0, face_idx); + found_shared_face = true; + break; + } + } + ASSERT_TRUE(found_shared_face); + + for(IndexType face_idx = 0; face_idx < IAMeshType::VERTS_PER_ELEM; ++face_idx) + { + if(ia_mesh.getSortedFacetKey(e1, face_idx) == shared_key) + { + ia_mesh.adjacentElements(e1)[face_idx] = invalid; + break; + } + } + + EXPECT_TRUE(ia_mesh.isValid()); + EXPECT_FALSE(ia_mesh.isConforming(true)); +} + TEST(slam_IA, dynamically_build_tet_mesh) { SLIC_INFO("Testing dynamically modifying a tetrahedral mesh..."); From 70a9e159d91186b65d922b5525f639cc221fbc45 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 19 Mar 2026 18:41:12 -0700 Subject: [PATCH 489/986] Adds functions to optionally validate the Delaunay complex as we build it up We add a runtime configuration enum to control the validation level, with three options: * None (default) -- no checks * ConformingMesh -- checks cavity and ball invariants and that the resulting mesh is conforming * Full -- also checks the Deluanay condition after every insertion. This can be extremely expensive. --- src/axom/quest/Delaunay.hpp | 565 +++++++++++++++++++++++- src/axom/quest/tests/quest_delaunay.cpp | 64 +++ 2 files changed, 625 insertions(+), 4 deletions(-) diff --git a/src/axom/quest/Delaunay.hpp b/src/axom/quest/Delaunay.hpp index b37b8b7051..62f38a83ee 100644 --- a/src/axom/quest/Delaunay.hpp +++ b/src/axom/quest/Delaunay.hpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -57,19 +58,36 @@ class Delaunay using IndexPairType = std::pair; static constexpr int VERT_PER_ELEMENT = DIM + 1; + static constexpr int VERTS_PER_FACET = VERT_PER_ELEMENT - 1; static constexpr IndexType INVALID_INDEX = -1; + enum class InsertionValidationMode + { + /// No additional insertion-time validation beyond existing asserts. + None, + /// Checks local cavity/ball invariants and that the resulting IA mesh is conforming. + /// Intended for debugging and unit tests. + ConformingMesh, + /// Additionally runs a global empty-circumsphere check after each insertion (very expensive). + Full + }; + private: using ModularFaceIndex = slam::ModularInt>; + using FacetKey = typename IAMeshType::FacetKey; + using FacetRecord = typename IAMeshType::FacetRecord; + private: struct ElementFinder; + struct InsertionHelper; IAMeshType m_mesh; BoundingBox m_bounding_box; bool m_has_boundary; int m_num_removed_elements_since_last_compact; + InsertionValidationMode m_insertion_validation_mode; ElementFinder m_element_finder; @@ -78,7 +96,23 @@ class Delaunay * \brief Default constructor * \note User must call initializeBoundary(BoundingBox) before adding points. */ - Delaunay() : m_has_boundary(false), m_num_removed_elements_since_last_compact(0) { } + Delaunay() + : m_has_boundary(false) + , m_num_removed_elements_since_last_compact(0) + , m_insertion_validation_mode(InsertionValidationMode::None) + { } + + /// \brief Controls the amount of validation performed around each point insertion + /// + /// \note This is intended for debugging. `InsertionValidationMode::Full` is a diagnostic mode + /// and should not be enabled in performance-sensitive runs. + void setInsertionValidationMode(InsertionValidationMode mode) + { + m_insertion_validation_mode = mode; + } + + /// \brief Returns the current insertion validation mode + InsertionValidationMode getInsertionValidationMode() const { return m_insertion_validation_mode; } /** * \brief Defines the boundary of the triangulation. @@ -132,12 +166,16 @@ class Delaunay // and replacing them with new valid elements (the Delaunay "ball") const BaryCoordType bary_coord = getBaryCoords(element_i, new_pt); const IndexArray seed_elements = getSeedElements(element_i, bary_coord); + validateInsertionSeed(element_i, new_pt, bary_coord, seed_elements); InsertionHelper insertionHelper(m_mesh); insertionHelper.findCavityElements(new_pt, seed_elements); + validateCavityBoundary(insertionHelper); insertionHelper.createCavity(); IndexType new_pt_i = m_mesh.addVertex(new_pt); insertionHelper.delaunayBall(new_pt_i); + validateInsertedBall(new_pt_i, insertionHelper); + validateInsertionResult(); m_element_finder.updateBin(new_pt, new_pt_i); m_num_removed_elements_since_last_compact += insertionHelper.numRemovedElements(); @@ -390,6 +428,292 @@ class Delaunay return valid; } + /** + * \brief Checks that the underlying mesh is conforming and consistently oriented + * + * Topological conformity (manifold facets and reciprocal adjacencies) is + * delegated to `slam::IAMesh::isConforming()`. This routine additionally + * verifies that the simplices remain positively oriented, and (when the + * initial bounding-box boundary is still present) that boundary facets lie on + * that bounding box. + */ + bool isConforming(bool verboseOutput = false) const + { + fmt::memory_buffer out; + + bool valid = m_mesh.isConforming(verboseOutput); + + // Geometry-specific checks that do not belong in slam::IAMesh. + for(auto element_idx : m_mesh.elements().positions()) + { + if(!m_mesh.isValidElement(element_idx)) + { + continue; + } + + // check orientations of top simplices + const double signed_measure = getElementSignedMeasure(element_idx); + if(signed_measure <= getElementMeasureTolerance()) + { + if(verboseOutput) + { + fmt::format_to(std::back_inserter(out), + "\n\tElement {} has non-positive signed measure {:.17g}", + element_idx, + signed_measure); + } + valid = false; + } + + // check that boundary elements are not on the Delaunay bounding box + if(m_has_boundary) + { + const auto neighbors = m_mesh.adjacentElements(element_idx); + for(int facet_idx = 0; facet_idx < VERT_PER_ELEMENT; ++facet_idx) + { + if(m_mesh.isValidElement(neighbors[facet_idx])) + { + continue; + } + + const FacetKey facet_key = m_mesh.getSortedFacetKey(element_idx, facet_idx); + if(!isFacetOnBoundingBox(facet_key)) + { + if(verboseOutput) + { + fmt::format_to(std::back_inserter(out), + "\n\tBoundary facet {} is not on the initial bounding box", + facetKeyString(facet_key)); + } + valid = false; + } + } + } + } + + if(verboseOutput) + { + if(valid) + { + SLIC_INFO("Delaunay mesh was conforming"); + } + else + { + SLIC_INFO("Delaunay mesh was NOT conforming. Summary: " << fmt::to_string(out)); + } + } + + return valid; + } + +private: + template + static FacetKey makeSortedFaceKey(const FacetSubsetType& facet) + { + // Canonical key for a simplex facet: store the vertex ids in sorted order + // so the same topological facet is mapped identically regardless of + // orientation or local indexing. + FacetKey key {}; + for(int i = 0; i < VERTS_PER_FACET; ++i) + { + key[i] = facet[i]; + } + std::sort(key.begin(), key.end()); + return key; + } + + static std::string facetKeyString(const FacetKey& facet_key) + { + return fmt::format("[{}]", fmt::join(facet_key, ", ")); + } + + double getBoundaryCoordinateTolerance() const + { + const auto min_pt = m_bounding_box.getMin(); + const auto max_pt = m_bounding_box.getMax(); + + double max_extent = 1.; + for(int dim = 0; dim < DIM; ++dim) + { + max_extent = axom::utilities::max(max_extent, max_pt[dim] - min_pt[dim]); + } + + return 64. * std::numeric_limits::epsilon() * max_extent; + } + + bool isFacetOnBoundingBox(const FacetKey& facet_key) const + { + // During incremental construction, the triangulation includes an initial + // bounding box (or cube) to guarantee the domain is closed. When that + // temporary boundary is present, any facet with an invalid neighbor must + // lie on one of the bounding box planes within a small coordinate tolerance. + const auto& min_pt = m_bounding_box.getMin(); + const auto& max_pt = m_bounding_box.getMax(); + const double tol = getBoundaryCoordinateTolerance(); + + for(int dim = 0; dim < DIM; ++dim) + { + bool on_min_face = true; + bool on_max_face = true; + for(const IndexType vertex_idx : facet_key) + { + const auto& vertex = m_mesh.getVertexPosition(vertex_idx); + on_min_face &= axom::utilities::abs(vertex[dim] - min_pt[dim]) <= tol; + on_max_face &= axom::utilities::abs(vertex[dim] - max_pt[dim]) <= tol; + } + + if(on_min_face || on_max_face) + { + return true; + } + } + + return false; + } + + double getElementSignedMeasure(IndexType element_idx) const + { + if constexpr(DIM == 2) + { + return getElement(element_idx).signedArea(); + } + else + { + return getElement(element_idx).signedVolume(); + } + } + + double getElementMeasureTolerance() const + { + const double scale = axom::utilities::max(1., m_bounding_box.range().norm()); + return 256. * std::numeric_limits::epsilon() * std::pow(scale, static_cast(DIM)); + } + + /** + * \brief Validates the point-location result and cavity seed selection before building the cavity + * + * This is a light-weight check meant to catch point-location failures early: + * - the reported containing element must be searchable and contain the point + * - the seed set must include that element (and may include face-adjacent neighbors) + */ + void validateInsertionSeed(IndexType element_idx, + const PointType& query_pt, + const BaryCoordType& bary_coord, + const IndexArray& seed_elements) const + { + if(m_insertion_validation_mode == InsertionValidationMode::None) + { + return; + } + + fmt::memory_buffer out; + bool valid = true; + + if(!isSearchableElement(element_idx)) + { + fmt::format_to(std::back_inserter(out), + "\n\tContaining element {} is not searchable", + element_idx); + valid = false; + } + + if(!isPointInsideForLocation(element_idx, query_pt, bary_coord)) + { + fmt::format_to(std::back_inserter(out), + "\n\tPoint {} is not inside containing element {}", + query_pt, + element_idx); + valid = false; + } + + if(seed_elements.empty()) + { + fmt::format_to(std::back_inserter(out), "\n\tNo cavity seed elements were generated"); + valid = false; + } + + bool found_containing_element = false; + for(const IndexType seed_element : seed_elements) + { + found_containing_element |= (seed_element == element_idx); + if(!m_mesh.isValidElement(seed_element)) + { + fmt::format_to(std::back_inserter(out), "\n\tSeed element {} is invalid", seed_element); + valid = false; + } + } + + if(!found_containing_element) + { + fmt::format_to(std::back_inserter(out), + "\n\tContaining element {} is missing from the seed set", + element_idx); + valid = false; + } + + SLIC_ERROR_IF(!valid, "Delaunay insertion seed validation failed:" << fmt::to_string(out)); + } + + /** + * \brief Validates that the cavity boundary facets match the faces between cavity and non-cavity elements + * + * The `InsertionHelper` collects a boundary facet for every cavity face that borders either: + * - an element outside the cavity, or + * - the temporary bounding-box boundary (invalid neighbor). + */ + void validateCavityBoundary(const InsertionHelper& insertion_helper) const; + + /** + * \brief Validates that the inserted ball covers the cavity boundary and is internally stitched + * + * Each inserted simplex must contain `new_pt_i`. Boundary facets must appear + * exactly once among the inserted elements and point to the recorded outside + * neighbor. Internal facets must be paired with reciprocal adjacency. + */ + void validateInsertedBall(IndexType new_pt_i, const InsertionHelper& insertion_helper) const; + + /** + * \brief Validates that the global mesh invariants still hold after insertion + * + * `ConformingMesh` checks IA validity and topological conformity, plus + * Delaunay's geometry-specific checks (positive simplex orientation and + * bounding-box boundary consistency while the fake boundary exists). + * `Full` additionally runs the global Delaunay empty-circumsphere validation. + */ + void validateInsertionResult() const + { + if(m_insertion_validation_mode == InsertionValidationMode::None) + { + return; + } + + // Note: These checks are intentionally global and can be expensive. They + // are only enabled when the caller opts into insertion validation. + if(!m_mesh.isValid(false)) + { + m_mesh.isValid(true); + SLIC_ERROR("Delaunay insertion produced an invalid IAMesh"); + } + + if(!isConforming(false)) + { + isConforming(true); + SLIC_ERROR("Delaunay insertion produced a non-conforming triangulation"); + } + + if(m_insertion_validation_mode == InsertionValidationMode::Full) + { + if(!isValid(false)) + { + isValid(true); + SLIC_ERROR( + "Delaunay insertion produced a triangulation that violates the empty-circumsphere " + "condition"); + } + } + } + +public: /// \brief Returns true when an element and all of its vertices are valid for point-location predicates bool isSearchableElement(IndexType element_idx) const { @@ -1308,6 +1632,7 @@ class Delaunay void delaunayBall(IndexType new_pt_i) { const int numFaces = facet_set.size(); + const IndexType invalid_neighbor = IAMeshType::ElementAdjacencyRelation::INVALID_INDEX; IndexType vlist[VERT_PER_ELEMENT] {}; IndexType neighbors[VERT_PER_ELEMENT] {}; @@ -1320,12 +1645,15 @@ class Delaunay } vlist[VERTS_PER_FACET] = new_pt_i; - // set all neighbors to nID; they'll be fixed in the fixVertexNeighborhood function below + // Face 0 is the cavity boundary face opposite the inserted point. + // The remaining faces stay invalid until fixVertexNeighborhood() stitches + // the new ball together around the inserted vertex. const auto nID = fc_rel[i][0]; - for(int d = 0; d < VERTS_PER_FACET; ++d) + for(int d = 0; d < VERT_PER_ELEMENT; ++d) { - neighbors[d] = nID; + neighbors[d] = invalid_neighbor; } + neighbors[0] = nID; IndexType new_el = m_mesh.addElement(vlist, neighbors); inserted_elems.insert(new_el); @@ -1374,6 +1702,235 @@ class Delaunay template constexpr typename Delaunay::IndexType Delaunay::INVALID_INDEX; +template +void Delaunay::validateCavityBoundary(const InsertionHelper& insertion_helper) const +{ + if(m_insertion_validation_mode == InsertionValidationMode::None) + { + return; + } + + // Cavity boundary invariant: + // - Every cavity face that borders a non-cavity neighbor (or the temporary + // bounding-box boundary) must appear exactly once in `facet_set`. + // - The facet's recorded neighbor (`fc_rel`) must match the mesh adjacency. + struct FacetInfo + { + IndexType neighbor_idx {INVALID_INDEX}; + bool matched {false}; + }; + + fmt::memory_buffer out; + bool valid = true; + std::map cavity_boundary; + + for(auto facet_idx : insertion_helper.facet_set.positions()) + { + const FacetKey facet_key = makeSortedFaceKey(insertion_helper.fv_rel[facet_idx]); + const auto insert_status = + cavity_boundary.insert({facet_key, {insertion_helper.fc_rel[facet_idx][0], false}}); + if(!insert_status.second) + { + fmt::format_to(std::back_inserter(out), + "\n\tCavity boundary facet {} was recorded more than once", + facetKeyString(facet_key)); + valid = false; + } + } + + for(const IndexType cavity_element : insertion_helper.cavity_elems) + { + const auto neighbors = m_mesh.adjacentElements(cavity_element); + for(int facet_idx = 0; facet_idx < VERT_PER_ELEMENT; ++facet_idx) + { + const IndexType neighbor_idx = neighbors[facet_idx]; + if(m_mesh.isValidElement(neighbor_idx) && + insertion_helper.cavity_elems.findIndex(neighbor_idx) != + InsertionHelper::ElementSet::INVALID_ENTRY) + { + continue; + } + + const FacetKey facet_key = m_mesh.getSortedFacetKey(cavity_element, facet_idx); + auto facet_it = cavity_boundary.find(facet_key); + if(facet_it == cavity_boundary.end()) + { + fmt::format_to(std::back_inserter(out), + "\n\tCavity facet {} on element {} face {} is missing from the " + "boundary facet set", + facetKeyString(facet_key), + cavity_element, + facet_idx); + valid = false; + continue; + } + + if(facet_it->second.neighbor_idx != neighbor_idx) + { + fmt::format_to(std::back_inserter(out), + "\n\tCavity face {} expects neighbor {} but facet relation stores {}", + facetKeyString(facet_key), + neighbor_idx, + facet_it->second.neighbor_idx); + valid = false; + } + + if(!m_mesh.isValidElement(neighbor_idx) && m_has_boundary && !isFacetOnBoundingBox(facet_key)) + { + fmt::format_to(std::back_inserter(out), + "\n\tCavity boundary face {} exits the mesh away from the bounding box", + facetKeyString(facet_key)); + valid = false; + } + + facet_it->second.matched = true; + } + } + + for(const auto& facet_entry : cavity_boundary) + { + if(!facet_entry.second.matched) + { + fmt::format_to(std::back_inserter(out), + "\n\tFacet {} does not correspond to a cavity boundary face", + facetKeyString(facet_entry.first)); + valid = false; + } + } + + SLIC_ERROR_IF(!valid, "Delaunay cavity validation failed:" << fmt::to_string(out)); +} + +template +void Delaunay::validateInsertedBall(IndexType new_pt_i, + const InsertionHelper& insertion_helper) const +{ + if(m_insertion_validation_mode == InsertionValidationMode::None) + { + return; + } + + // Ball invariant: + // - Every inserted element contains `new_pt_i` and is positively oriented. + // - Each cavity boundary facet is covered by exactly one inserted element and + // points to the recorded outside neighbor. + // - All remaining facets are internal to the ball and are paired with reciprocal adjacencies. + struct BoundaryFacetInfo + { + IndexType neighbor_idx {INVALID_INDEX}; + bool matched {false}; + }; + + fmt::memory_buffer out; + bool valid = true; + std::map cavity_boundary; + std::map> inserted_faces; + + if(insertion_helper.inserted_elems.size() != insertion_helper.facet_set.size()) + { + fmt::format_to( + std::back_inserter(out), + "\n\tInserted ball element count {} does not match cavity boundary facet count {}", + insertion_helper.inserted_elems.size(), + insertion_helper.facet_set.size()); + valid = false; + } + + for(auto facet_idx : insertion_helper.facet_set.positions()) + { + cavity_boundary.insert({makeSortedFaceKey(insertion_helper.fv_rel[facet_idx]), + {insertion_helper.fc_rel[facet_idx][0], false}}); + } + + for(const IndexType element_idx : insertion_helper.inserted_elems) + { + if(!m_mesh.isValidElement(element_idx)) + { + fmt::format_to(std::back_inserter(out), "\n\tInserted element {} is invalid", element_idx); + valid = false; + continue; + } + + const auto verts = m_mesh.boundaryVertices(element_idx); + if(!slam::is_subset(new_pt_i, verts)) + { + fmt::format_to(std::back_inserter(out), + "\n\tInserted element {} does not contain the new vertex {}", + element_idx, + new_pt_i); + valid = false; + } + + const double signed_measure = getElementSignedMeasure(element_idx); + if(signed_measure <= getElementMeasureTolerance()) + { + fmt::format_to(std::back_inserter(out), + "\n\tInserted element {} has non-positive signed measure {:.17g}", + element_idx, + signed_measure); + valid = false; + } + + const auto neighbors = m_mesh.adjacentElements(element_idx); + for(int facet_idx = 0; facet_idx < VERT_PER_ELEMENT; ++facet_idx) + { + inserted_faces[m_mesh.getSortedFacetKey(element_idx, facet_idx)].push_back( + {element_idx, facet_idx, neighbors[facet_idx]}); + } + } + + for(auto& face_entry : inserted_faces) + { + auto cavity_it = cavity_boundary.find(face_entry.first); + auto& records = face_entry.second; + if(cavity_it != cavity_boundary.end()) + { + if(records.size() != 1) + { + fmt::format_to(std::back_inserter(out), + "\n\tBoundary face {} of the inserted ball is used by {} new elements", + facetKeyString(face_entry.first), + records.size()); + valid = false; + continue; + } + + if(records.front().neighbor_idx != cavity_it->second.neighbor_idx) + { + fmt::format_to(std::back_inserter(out), + "\n\tInserted boundary face {} points to neighbor {} instead of {}", + facetKeyString(face_entry.first), + records.front().neighbor_idx, + cavity_it->second.neighbor_idx); + valid = false; + } + + cavity_it->second.matched = true; + } + else if(records.size() != 2 || records[0].neighbor_idx != records[1].element_idx || + records[1].neighbor_idx != records[0].element_idx) + { + fmt::format_to(std::back_inserter(out), + "\n\tInternal face {} of the inserted ball has inconsistent adjacency", + facetKeyString(face_entry.first)); + valid = false; + } + } + + for(const auto& facet_entry : cavity_boundary) + { + if(!facet_entry.second.matched) + { + fmt::format_to(std::back_inserter(out), + "\n\tCavity boundary face {} was not covered by the inserted ball", + facetKeyString(facet_entry.first)); + valid = false; + } + } + + SLIC_ERROR_IF(!valid, "Delaunay ball validation failed:" << fmt::to_string(out)); +} + //-------------------------------------------------------------------------------- // Below are 2D and 3D specializations for methods in the Delaunay class //-------------------------------------------------------------------------------- diff --git a/src/axom/quest/tests/quest_delaunay.cpp b/src/axom/quest/tests/quest_delaunay.cpp index 728eaa88fc..03ec8e78c6 100644 --- a/src/axom/quest/tests/quest_delaunay.cpp +++ b/src/axom/quest/tests/quest_delaunay.cpp @@ -49,6 +49,14 @@ void expectValidDelaunay(DelaunayType& dt, } } +template +void expectConformingMesh(const DelaunayType& dt) +{ + EXPECT_TRUE(dt.getMeshData()->isValid(true)); + EXPECT_TRUE(dt.getMeshData()->isConforming(true)); + EXPECT_TRUE(dt.isConforming(true)); +} + } // namespace TEST(quest_delaunay, cocircular_square_2d) @@ -244,6 +252,62 @@ TEST(quest_delaunay, query_outside_convex_hull_returns_invalid_3d) dt.findContainingElement(inside_bbox_outside_hull, false)); } +TEST(quest_delaunay, insertion_validation_regular_grid_3d) +{ + using PointType = typename DelaunayType<3>::PointType; + using BoundingBox = typename DelaunayType<3>::BoundingBox; + using ValidationMode = typename DelaunayType<3>::InsertionValidationMode; + + DelaunayType<3> dt; + dt.initializeBoundary(BoundingBox(PointType {-0.5, -0.5, -0.5}, PointType {1.5, 1.5, 1.5})); + dt.setInsertionValidationMode(ValidationMode::ConformingMesh); + + std::vector points; + points.reserve(4 * 4 * 4); + for(int z = 0; z < 4; ++z) + { + for(int y = 0; y < 4; ++y) + { + for(int x = 0; x < 4; ++x) + { + points.push_back(PointType {x / 3., y / 3., z / 3.}); + } + } + } + + insertPoints(dt, points); + expectConformingMesh(dt); + expectValidDelaunay(dt, points); +} + +TEST(quest_delaunay, insertion_validation_full_small_grid_3d) +{ + using PointType = typename DelaunayType<3>::PointType; + using BoundingBox = typename DelaunayType<3>::BoundingBox; + using ValidationMode = typename DelaunayType<3>::InsertionValidationMode; + + DelaunayType<3> dt; + dt.initializeBoundary(BoundingBox(PointType {-0.5, -0.5, -0.5}, PointType {1.5, 1.5, 1.5})); + dt.setInsertionValidationMode(ValidationMode::Full); + + std::vector points; + points.reserve(3 * 3 * 3); + for(int z = 0; z < 3; ++z) + { + for(int y = 0; y < 3; ++y) + { + for(int x = 0; x < 3; ++x) + { + points.push_back(PointType {x / 2., y / 2., z / 2.}); + } + } + } + + insertPoints(dt, points); + expectConformingMesh(dt); + expectValidDelaunay(dt, points); +} + //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ From 432b5d8a40aa9a9e2f2525953be70c5412430c69 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 19 Mar 2026 20:02:11 -0700 Subject: [PATCH 490/986] Improves robustness and refactors Delaunay orientation and in_sphere tests And adds some stress tests in 2D and 3D. --- src/axom/quest/Delaunay.hpp | 555 +++++++++++++++--------- src/axom/quest/tests/quest_delaunay.cpp | 100 +++++ 2 files changed, 442 insertions(+), 213 deletions(-) diff --git a/src/axom/quest/Delaunay.hpp b/src/axom/quest/Delaunay.hpp index 62f38a83ee..839d637067 100644 --- a/src/axom/quest/Delaunay.hpp +++ b/src/axom/quest/Delaunay.hpp @@ -255,19 +255,24 @@ class Delaunay //remove the boundary box, which will be the first 4 points for triangles, first 8 for tetrahedron const int num_boundary_pts = 1 << DIM; - //Collect a list of elements to remove first, because - //the list may be incomplete if generated during the removal. - IndexArray elements_to_remove; - for(int v = 0; v < num_boundary_pts; ++v) - { - IndexArray elems = m_mesh.vertexStar(v); - elements_to_remove.insert(elements_to_remove.end(), elems.begin(), elems.end()); - } - for(auto e : elements_to_remove) + // Remove all elements incident to boundary vertices. Avoid relying on + // `vertexStar()` here since it may be incomplete when the mesh is not + // manifold around the temporary boundary. + for(auto e : m_mesh.elements().positions()) { if(m_mesh.isValidElement(e)) { - m_mesh.removeElement(e); + const auto verts = m_mesh.boundaryVertices(e); + bool touches_boundary = false; + for(int i = 0; i < VERT_PER_ELEMENT; ++i) + { + touches_boundary |= (verts[i] >= 0 && verts[i] < num_boundary_pts); + } + + if(touches_boundary) + { + m_mesh.removeElement(e); + } } } @@ -276,6 +281,29 @@ class Delaunay m_mesh.removeVertex(v); } + // Defensive cleanup: ensure no valid element references a removed vertex. + // This can happen if the boundary-vertex star is non-manifold and + // `removeVertex()` cannot discover all incident elements via adjacency. + for(auto e : m_mesh.elements().positions()) + { + if(!m_mesh.isValidElement(e)) + { + continue; + } + + const auto verts = m_mesh.boundaryVertices(e); + bool has_invalid_vertex = false; + for(int i = 0; i < VERT_PER_ELEMENT; ++i) + { + has_invalid_vertex |= !m_mesh.isValidVertex(verts[i]); + } + + if(has_invalid_vertex) + { + m_mesh.removeElement(e); + } + } + this->compactMesh(); m_has_boundary = false; } @@ -304,6 +332,7 @@ class Delaunay bool valid = true; std::vector> invalidEntries; + std::vector invalidElements; const IndexType totalVertices = m_mesh.vertices().size(); const IndexType totalElements = m_mesh.elements().size(); @@ -313,6 +342,32 @@ class Delaunay // An array to cache the circumspheres associated with each element axom::Array circumspheres(totalElements); + auto vertexInsideCircumsphere = [&](const PointType& vertex, IndexType element_idx) { + // `Sphere::getOrientation()` depends on an explicitly constructed + // circumsphere (center + radius). For sliver tetrahedra this construction + // is ill-conditioned and can produce false positives when validating the + // empty-circumsphere property. Use the determinant-based predicate used + // during cavity construction and treat boundary cases as "not inside" for + // global validation. + return isPointInSphereOnMesh(m_mesh, vertex, element_idx, /*includeBoundary=*/false); + }; + + auto circumsphereSignedDistanceTol = [](const typename ElementType::SphereType& sphere, + const PointType& x) { + const auto& center = sphere.getCenter(); + double scale = axom::utilities::max(1., sphere.getRadius()); + for(int dim = 0; dim < DIM; ++dim) + { + scale = axom::utilities::max(scale, axom::utilities::abs(center[dim])); + scale = axom::utilities::max(scale, axom::utilities::abs(x[dim])); + } + + // Signed distance to a sphere involves a subtraction after a norm. Use a + // tolerance proportional to the coordinate/radius scale to give a + // meaningful diagnostic in the verbose output. + return 256. * std::numeric_limits::epsilon() * scale; + }; + // bootstrap the uniform grid using an implicit grid { using GridCell = typename ImplicitGridType::GridCell; @@ -324,6 +379,23 @@ class Delaunay { if(m_mesh.isValidElement(element_idx)) { + const auto verts = m_mesh.boundaryVertices(element_idx); + bool has_all_vertices = true; + for(int i = 0; i < VERT_PER_ELEMENT; ++i) + { + has_all_vertices &= m_mesh.isValidVertex(verts[i]); + } + + if(!has_all_vertices) + { + valid = false; + if(verboseOutput) + { + invalidElements.push_back(element_idx); + } + continue; + } + circumspheres[element_idx] = this->getElement(element_idx).circumsphere(); const auto& sphere = circumspheres[element_idx]; const auto& center = sphere.getCenter().array(); @@ -377,7 +449,7 @@ class Delaunay } // check insphere condition - if(circumspheres[element_idx].getOrientation(vertex) == primal::ON_NEGATIVE_SIDE) + if(vertexInsideCircumsphere(vertex, element_idx)) { valid = false; @@ -398,6 +470,13 @@ class Delaunay else { fmt::memory_buffer out; + if(!invalidElements.empty()) + { + fmt::format_to(std::back_inserter(out), + "\n\t{} valid elements referenced invalid vertices: {}", + invalidElements.size(), + fmt::join(invalidElements, ", ")); + } for(const auto& pr : invalidEntries) { const auto vertex_idx = pr.first; @@ -405,16 +484,18 @@ class Delaunay const auto& pos = m_mesh.getVertexPosition(vertex_idx); const auto element = this->getElement(element_idx); const auto circumsphere = element.circumsphere(); + const double tol = circumsphereSignedDistanceTol(circumsphere, pos); fmt::format_to(std::back_inserter(out), "\n\tVertex {} @ {}" "\n\tElement {}: {} w/ circumsphere: {}" - "\n\tDistance to circumcenter: {}", + "\n\tDistance to circumcenter: {} (tol={})", vertex_idx, pos, element_idx, element, circumsphere, - circumsphere.computeSignedDistance(pos)); + circumsphere.computeSignedDistance(pos), + tol); } SLIC_INFO( @@ -452,15 +533,17 @@ class Delaunay } // check orientations of top simplices - const double signed_measure = getElementSignedMeasure(element_idx); - if(signed_measure <= getElementMeasureTolerance()) + const auto orient = evaluateElementOrientationDeterminant(element_idx); + if(orient.location != OrientationLocation::Positive) { if(verboseOutput) { - fmt::format_to(std::back_inserter(out), - "\n\tElement {} has non-positive signed measure {:.17g}", - element_idx, - signed_measure); + fmt::format_to( + std::back_inserter(out), + "\n\tElement {} has non-positive orientation determinant {:.17g} (tol={:.3g})", + element_idx, + orient.det, + orient.tol); } valid = false; } @@ -839,127 +922,237 @@ class Delaunay return value > tolerance ? 1 : (value < -tolerance ? -1 : 0); } - static double getPointMagnitudeScale(const std::array& pts) + //----------------------------------------------------------------------------- + // In-sphere predicate helpers + // + // These helpers centralize the raw in-sphere determinants and their + // classification into {inside/outside/boundary} results. + // + // Convention: we follow primal::in_sphere(): a negative determinant means the + // query point is inside the circumsphere (for our consistently oriented + // simplices). + //----------------------------------------------------------------------------- + + enum class InSphereLocation : int { - double max_abs_coord = 1.; - for(const auto& pt : pts) - { - for(int dim = 0; dim < DIM; ++dim) - { - max_abs_coord = axom::utilities::max(max_abs_coord, axom::utilities::abs(pt[dim])); - } - } + Inside = -1, + OnBoundary = 0, + Outside = 1 + }; - return max_abs_coord; + struct InSphereEval + { + double det {0.}; + double tol {0.}; + InSphereLocation location {InSphereLocation::OnBoundary}; + }; + + static InSphereLocation classifyInSphereDeterminant(double det, double tol) + { + const int sign = signWithTolerance(det, tol); + return sign < 0 ? InSphereLocation::Inside + : (sign > 0 ? InSphereLocation::Outside : InSphereLocation::OnBoundary); } - static double orientationTolerance(const std::array& pts) + //----------------------------------------------------------------------------- + // Simplex orientation helpers + // + // Use the same determinant/tolerance pattern to validate simplex orientation. + // This returns the raw (unscaled) determinants: + // - 2D: determinant is twice signed area + // - 3D: determinant is six times signed volume + //----------------------------------------------------------------------------- + enum class OrientationLocation : int { - const double scale = getPointMagnitudeScale(pts); - return 64. * std::numeric_limits::epsilon() * scale * scale * scale; + Negative = -1, + OnBoundary = 0, + Positive = 1 + }; + + struct OrientationEval + { + double det {0.}; + double tol {0.}; + OrientationLocation location {OrientationLocation::OnBoundary}; + }; + + static OrientationLocation classifyOrientationDeterminant(double det, double tol) + { + const int sign = signWithTolerance(det, tol); + return sign < 0 ? OrientationLocation::Negative + : (sign > 0 ? OrientationLocation::Positive : OrientationLocation::OnBoundary); } - static double determinant3(const PointType& p0, const PointType& p1, const PointType& p2) + OrientationEval evaluateElementOrientationDeterminant(IndexType element_idx) const { - return axom::numerics::determinant(p0[0], p0[1], p0[2], p1[0], p1[1], p1[2], p2[0], p2[1], p2[2]); + const double scale = (DIM == 2) ? 2. : 6.; + const double det = scale * getElementSignedMeasure(element_idx); + const double tol = scale * getElementMeasureTolerance(); + return {det, tol, classifyOrientationDeterminant(det, tol)}; } - static double orientationDeterminant(const std::array& pts) - { - return axom::numerics::determinant(pts[0][0], - pts[0][1], - pts[0][2], - 1., - pts[1][0], - pts[1][1], - pts[1][2], - 1., - pts[2][0], - pts[2][1], - pts[2][2], - 1., - pts[3][0], - pts[3][1], - pts[3][2], - 1.); + static double inSphereDeterminant2D(const PointType& q, + const PointType& p0, + const PointType& p1, + const PointType& p2) + { + const auto ba = p1 - p0; + const auto ca = p2 - p0; + const auto qa = q - p0; + + return axom::numerics::determinant(ba[0], + ba[1], + ba.squared_norm(), + ca[0], + ca[1], + ca.squared_norm(), + qa[0], + qa[1], + qa.squared_norm()); } - static int symbolicOrientationSign(const std::array& pts, - const std::array& ranks) + static double inSphereDeterminant3D(const PointType& q, + const PointType& p0, + const PointType& p1, + const PointType& p2, + const PointType& p3) { - // Simulation-of-simplicity style tie-break: if the 4x4 orientation - // determinant is effectively zero, use the earliest nonzero cofactor in a - // fixed symbolic rank order to choose one consistent sign. - const double det = orientationDeterminant(pts); + const auto ba = p1 - p0; + const auto ca = p2 - p0; + const auto da = p3 - p0; + const auto qa = q - p0; + + return axom::numerics::determinant(ba[0], + ba[1], + ba[2], + ba.squared_norm(), + ca[0], + ca[1], + ca[2], + ca.squared_norm(), + da[0], + da[1], + da[2], + da.squared_norm(), + qa[0], + qa[1], + qa[2], + qa.squared_norm()); + } - const int det_sign = signWithTolerance(det, orientationTolerance(pts)); - if(det_sign != 0) + static double inSphereTolerance(double scale) + { + // Determinant magnitude scales like length^(DIM+2): L^4 in 2D, L^5 in 3D. + const double k = 128.; + if constexpr(DIM == 2) { - return det_sign; + return k * std::numeric_limits::epsilon() * scale * scale * scale * scale; } + else + { + return k * std::numeric_limits::epsilon() * scale * scale * scale * scale * scale; + } + } - const std::array cofactors {-determinant3(pts[1], pts[2], pts[3]), - determinant3(pts[0], pts[2], pts[3]), - -determinant3(pts[0], pts[1], pts[3]), - determinant3(pts[0], pts[1], pts[2])}; + BaryCoordType getRawBarycentricDeterminants(IndexType element_idx, const PointType& query_pt) const + { + const auto verts = m_mesh.boundaryVertices(element_idx); - std::array order {{0, 1, 2, 3}}; - std::sort(order.begin(), order.end(), [&](int lhs, int rhs) { return ranks[lhs] < ranks[rhs]; }); + if constexpr(DIM == 2) + { + const ElementType tri(m_mesh.getVertexPosition(verts[0]), + m_mesh.getVertexPosition(verts[1]), + m_mesh.getVertexPosition(verts[2])); + return tri.physToBarycentric(query_pt, /*skipNormalization=*/true); + } + else + { + const ElementType tet(m_mesh.getVertexPosition(verts[0]), + m_mesh.getVertexPosition(verts[1]), + m_mesh.getVertexPosition(verts[2]), + m_mesh.getVertexPosition(verts[3])); + return tet.physToBarycentric(query_pt, /*skipNormalization=*/true); + } + } - const double cofactor_tol = 64. * std::numeric_limits::epsilon() * - axom::utilities::max(1., orientationTolerance(pts)); - for(const int row : order) + double rawBarycentricDeterminantTolerance(IndexType element_idx, const PointType& query_pt) const + { + const auto verts = m_mesh.boundaryVertices(element_idx); + + double scale = 1.; + for(int i = 0; i < VERT_PER_ELEMENT; ++i) { - const int sign = signWithTolerance(cofactors[row], cofactor_tol); - if(sign != 0) - { - return sign; - } + const auto diff = m_mesh.getVertexPosition(verts[i]) - query_pt; + scale = axom::utilities::max(scale, diff.norm()); } - return 0; + const double k = 64.; + if constexpr(DIM == 2) + { + return k * std::numeric_limits::epsilon() * scale * scale; + } + else + { + return k * std::numeric_limits::epsilon() * scale * scale * scale; + } } - int getBarycentricSign(IndexType element_idx, - const PointType& query_pt, - const BaryCoordType& bary_coord, - int bary_idx) const + static InSphereEval evaluateInSphereOnMesh(const IAMeshType& mesh, + const PointType& q, + IndexType element_idx) { - const double value = bary_coord[bary_idx]; - if(axom::utilities::abs(value) > BARY_EPS || DIM == 2) + const auto verts = mesh.boundaryVertices(element_idx); + + if constexpr(DIM == 2) { - return signWithTolerance(value, BARY_EPS); + const PointType& p0 = mesh.getVertexPosition(verts[0]); + const PointType& p1 = mesh.getVertexPosition(verts[1]); + const PointType& p2 = mesh.getVertexPosition(verts[2]); + + const auto ba = p1 - p0; + const auto ca = p2 - p0; + const auto qa = q - p0; + + const double det = inSphereDeterminant2D(q, p0, p1, p2); + const double scale = axom::utilities::max( + 1., + axom::utilities::max(ba.norm(), axom::utilities::max(ca.norm(), qa.norm()))); + const double tol = inSphereTolerance(scale); + + return {det, tol, classifyInSphereDeterminant(det, tol)}; } + else + { + const PointType& p0 = mesh.getVertexPosition(verts[0]); + const PointType& p1 = mesh.getVertexPosition(verts[1]); + const PointType& p2 = mesh.getVertexPosition(verts[2]); + const PointType& p3 = mesh.getVertexPosition(verts[3]); - // The only ambiguous case is a near-zero barycentric coordinate. Interpret - // that face test symbolically by replacing the corresponding tetrahedron - // vertex with the query point and evaluating the signed orientation. - const auto verts = m_mesh.boundaryVertices(element_idx); - const std::array pts { - {bary_idx == 0 ? query_pt : m_mesh.getVertexPosition(verts[0]), - bary_idx == 1 ? query_pt : m_mesh.getVertexPosition(verts[1]), - bary_idx == 2 ? query_pt : m_mesh.getVertexPosition(verts[2]), - bary_idx == 3 ? query_pt : m_mesh.getVertexPosition(verts[3])}}; - - const IndexType query_rank = 1 + - axom::utilities::max(axom::utilities::max(verts[0], verts[1]), - axom::utilities::max(verts[2], verts[3])); - // Give the query point the highest symbolic rank so zero-case face tests - // resolve deterministically without changing the stored simplex ordering. - const std::array ranks {{bary_idx == 0 ? query_rank : verts[0], - bary_idx == 1 ? query_rank : verts[1], - bary_idx == 2 ? query_rank : verts[2], - bary_idx == 3 ? query_rank : verts[3]}}; - - const std::array tet_pts {{m_mesh.getVertexPosition(verts[0]), - m_mesh.getVertexPosition(verts[1]), - m_mesh.getVertexPosition(verts[2]), - m_mesh.getVertexPosition(verts[3])}}; - const std::array tet_ranks {{verts[0], verts[1], verts[2], verts[3]}}; - - const int numerator_sign = symbolicOrientationSign(pts, ranks); - const int denominator_sign = symbolicOrientationSign(tet_pts, tet_ranks); - return numerator_sign * denominator_sign; + const auto ba = p1 - p0; + const auto ca = p2 - p0; + const auto da = p3 - p0; + const auto qa = q - p0; + + const double det = inSphereDeterminant3D(q, p0, p1, p2, p3); + const double scale = axom::utilities::max( + 1., + axom::utilities::max( + ba.norm(), + axom::utilities::max(ca.norm(), axom::utilities::max(da.norm(), qa.norm())))); + const double tol = inSphereTolerance(scale); + + return {det, tol, classifyInSphereDeterminant(det, tol)}; + } + } + + static bool isPointInSphereOnMesh(const IAMeshType& mesh, + const PointType& q, + IndexType element_idx, + bool includeBoundary) + { + const auto eval = evaluateInSphereOnMesh(mesh, q, element_idx); + return includeBoundary ? (eval.location != InSphereLocation::Outside) + : (eval.location == InSphereLocation::Inside); } bool isPointInsideForLocation(IndexType element_idx, @@ -967,9 +1160,10 @@ class Delaunay const BaryCoordType& bary_coord, ModularFaceIndex* exit_face = nullptr) const { - int first_symbolic_negative = -1; ModularFaceIndex min_face(bary_coord.array().argMin()); + // Fast path: if any barycentric coordinate is clearly negative, the point + // lies outside the simplex across the most-negative face. for(int i = 0; i < VERT_PER_ELEMENT; ++i) { if(bary_coord[i] < -BARY_EPS) @@ -980,22 +1174,39 @@ class Delaunay } return false; } + } - if(axom::utilities::abs(bary_coord[i]) <= BARY_EPS && - getBarycentricSign(element_idx, query_pt, bary_coord, i) < 0) + // Ambiguous path: for near-zero barycentric coordinates, fall back to the + // underlying (unnormalized) determinants to decide the sign consistently. + int first_determinant_negative = -1; + if constexpr(DIM == 3) + { + bool has_near_zero = false; + for(int i = 0; i < VERT_PER_ELEMENT; ++i) { - if(first_symbolic_negative < 0) + has_near_zero |= axom::utilities::abs(bary_coord[i]) <= BARY_EPS; + } + + if(has_near_zero) + { + const BaryCoordType raw = getRawBarycentricDeterminants(element_idx, query_pt); + const double tol = rawBarycentricDeterminantTolerance(element_idx, query_pt); + for(int i = 0; i < VERT_PER_ELEMENT; ++i) { - first_symbolic_negative = i; + if(axom::utilities::abs(bary_coord[i]) <= BARY_EPS && signWithTolerance(raw[i], tol) < 0) + { + first_determinant_negative = i; + break; + } } } } - if(first_symbolic_negative >= 0) + if(first_determinant_negative >= 0) { if(exit_face != nullptr) { - *exit_face = ModularFaceIndex(first_symbolic_negative); + *exit_face = ModularFaceIndex(first_determinant_negative); } return false; } @@ -1861,13 +2072,15 @@ void Delaunay::validateInsertedBall(IndexType new_pt_i, valid = false; } - const double signed_measure = getElementSignedMeasure(element_idx); - if(signed_measure <= getElementMeasureTolerance()) + const auto orient = evaluateElementOrientationDeterminant(element_idx); + if(orient.location != OrientationLocation::Positive) { - fmt::format_to(std::back_inserter(out), - "\n\tInserted element {} has non-positive signed measure {:.17g}", - element_idx, - signed_measure); + fmt::format_to( + std::back_inserter(out), + "\n\tInserted element {} has non-positive orientation determinant {:.17g} (tol={:.3g})", + element_idx, + orient.det, + orient.tol); valid = false; } @@ -2021,94 +2234,10 @@ template inline bool Delaunay::InsertionHelper::isPointInCircumsphere(const PointType& query_pt, IndexType element_idx) const { - const auto verts = m_mesh.boundaryVertices(element_idx); - - if constexpr(DIM == 2) - { - const PointType& p0 = m_mesh.getVertexPosition(verts[0]); - const PointType& p1 = m_mesh.getVertexPosition(verts[1]); - const PointType& p2 = m_mesh.getVertexPosition(verts[2]); - return primal::in_sphere(query_pt, p0, p1, p2, primal::PRIMAL_TINY, false); - } - else - { - const PointType& p0 = m_mesh.getVertexPosition(verts[0]); - const PointType& p1 = m_mesh.getVertexPosition(verts[1]); - const PointType& p2 = m_mesh.getVertexPosition(verts[2]); - const PointType& p3 = m_mesh.getVertexPosition(verts[3]); - - const auto ba = p1 - p0; - const auto ca = p2 - p0; - const auto da = p3 - p0; - const auto qa = query_pt - p0; - - const double det = axom::numerics::determinant(ba[0], - ba[1], - ba[2], - ba.squared_norm(), - ca[0], - ca[1], - ca[2], - ca.squared_norm(), - da[0], - da[1], - da[2], - da.squared_norm(), - qa[0], - qa[1], - qa[2], - qa.squared_norm()); - - const double scale = axom::utilities::max( - 1., - axom::utilities::max( - ba.norm(), - axom::utilities::max(ca.norm(), axom::utilities::max(da.norm(), qa.norm())))); - const double det_tol = - 128. * std::numeric_limits::epsilon() * scale * scale * scale * scale; - const int det_sign = Delaunay::signWithTolerance(det, det_tol); - if(det_sign != 0) - { - return det_sign < 0; - } - - // Resolve exact co-spherical ties symbolically from the lifted determinant - // cofactors, ordered by the fixed vertex/query ranks. The query point gets - // the highest rank so exact ties choose one deterministic inclusive result - // without perturbing coordinates. - const IndexType query_rank = 1 + - axom::utilities::max(axom::utilities::max(verts[0], verts[1]), - axom::utilities::max(verts[2], verts[3])); - const std::array lifted_pts {{p0, p1, p2, p3, query_pt}}; - const std::array ranks {{verts[0], verts[1], verts[2], verts[3], query_rank}}; - std::array order {{0, 1, 2, 3, 4}}; - std::sort(order.begin(), order.end(), [&](int lhs, int rhs) { return ranks[lhs] < ranks[rhs]; }); - - const double cofactor_tol = 128. * std::numeric_limits::epsilon() * scale * scale * scale; - for(const int row : order) - { - std::array cofactor_pts; - int next_idx = 0; - for(int pt_idx = 0; pt_idx < 5; ++pt_idx) - { - if(pt_idx == row) - { - continue; - } - cofactor_pts[next_idx++] = lifted_pts[pt_idx]; - } - - const double cofactor = - ((row + 3) % 2 == 0 ? 1. : -1.) * Delaunay::orientationDeterminant(cofactor_pts); - const int sign = Delaunay::signWithTolerance(cofactor, cofactor_tol); - if(sign != 0) - { - return sign < 0; - } - } - - return true; - } + // The cavity is defined by elements whose circumspheres contain or touch the + // insertion point. Returning "true on boundary" ensures the cavity is + // topologically closed for co-spherical inputs (e.g. regular grids). + return Delaunay::isPointInSphereOnMesh(m_mesh, query_pt, element_idx, /*includeBoundary=*/true); } } // end namespace quest diff --git a/src/axom/quest/tests/quest_delaunay.cpp b/src/axom/quest/tests/quest_delaunay.cpp index 03ec8e78c6..115adf6c83 100644 --- a/src/axom/quest/tests/quest_delaunay.cpp +++ b/src/axom/quest/tests/quest_delaunay.cpp @@ -308,6 +308,106 @@ TEST(quest_delaunay, insertion_validation_full_small_grid_3d) expectValidDelaunay(dt, points); } +TEST(quest_delaunay, stress_nearly_coplanar_insertions_3d) +{ + using PointType = typename DelaunayType<3>::PointType; + using BoundingBox = typename DelaunayType<3>::BoundingBox; + + // A near-coplanar point set (most points lie close to a plane) tends to + // generate sliver tetrahedra and near-zero orientation / in-sphere determinants + // This is a good stress case for point-location and cavity predicates in 3D. + DelaunayType<3> dt; + dt.initializeBoundary(BoundingBox(PointType {-1., -1., -1.}, PointType {2., 2., 2.})); + + std::vector points; + points.reserve(5 * 5 + 2); + + constexpr double eps = 1e-6; + for(int y = 0; y < 5; ++y) + { + for(int x = 0; x < 5; ++x) + { + const double fx = static_cast(x) / 4.; + const double fy = static_cast(y) / 4.; + points.push_back(PointType {fx, fy, eps * (fx + 2. * fy)}); + } + } + + // Add a couple of off-plane points to ensure a full 3D convex hull. + points.push_back(PointType {0.2, 0.2, 0.4}); + points.push_back(PointType {0.8, 0.6, 0.7}); + + insertPoints(dt, points); + expectValidDelaunay(dt, points); +} + +TEST(quest_delaunay, stress_large_coordinate_scale_grid_3d) +{ + using PointType = typename DelaunayType<3>::PointType; + using BoundingBox = typename DelaunayType<3>::BoundingBox; + using ValidationMode = typename DelaunayType<3>::InsertionValidationMode; + + // Large absolute coordinates with small relative spacing increase the risk + // of cancellation in determinant-based predicates. This is a deterministic + // stress case for the 3D orientation / in-sphere computations. + constexpr double base = 1e9; + constexpr double h = 1.0; + + DelaunayType<3> dt; + dt.initializeBoundary(BoundingBox(PointType {base - 1., base - 1., base - 1.}, + PointType {base + 4., base + 4., base + 4.})); + dt.setInsertionValidationMode(ValidationMode::Full); + + std::vector points; + points.reserve(4 * 4 * 4); + for(int z = 0; z < 4; ++z) + { + for(int y = 0; y < 4; ++y) + { + for(int x = 0; x < 4; ++x) + { + points.push_back(PointType {base + h * x, base + h * y, base + h * z}); + } + } + } + + insertPoints(dt, points); + expectConformingMesh(dt); + expectValidDelaunay(dt, points); +} + +TEST(quest_delaunay, stress_large_coordinate_scale_grid_2d) +{ + using PointType = typename DelaunayType<2>::PointType; + using BoundingBox = typename DelaunayType<2>::BoundingBox; + using ValidationMode = typename DelaunayType<2>::InsertionValidationMode; + + // Large absolute coordinates with small relative spacing increase the risk + // of cancellation in determinant-based predicates. This stress case mirrors + // the 3D test but exercises the 2D in-circle predicate. + constexpr double base = 1e9; + constexpr double h = 1.0; + + DelaunayType<2> dt; + dt.initializeBoundary( + BoundingBox(PointType {base - 1., base - 1.}, PointType {base + 4., base + 4.})); + dt.setInsertionValidationMode(ValidationMode::Full); + + std::vector points; + points.reserve(4 * 4); + for(int y = 0; y < 4; ++y) + { + for(int x = 0; x < 4; ++x) + { + points.push_back(PointType {base + h * x, base + h * y}); + } + } + + insertPoints(dt, points); + expectConformingMesh(dt); + expectValidDelaunay(dt, points); +} + //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ From 1a67feeca59d0c16290a6fd932b09de1178a22ef Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 19 Mar 2026 20:39:51 -0700 Subject: [PATCH 491/986] Refactors primal's orientation and in_sphere predicates These are all based on helper functions that return the orientation/in_sphere determinant. We added variants of these primitives in the primal::robust namespace that return primal::OrientationResult (ON_POSITIVE_SIDE, ON_NEGATIVE_SIDE and ON_BOUNDARY) to defer the classification decision to a caller. This allows for cleaning up the implementation in the Delaunay class. --- src/axom/primal/CMakeLists.txt | 1 + .../detail/predicate_determinants.hpp | 123 ++++++++++ src/axom/primal/operators/in_sphere.hpp | 92 +++++-- src/axom/primal/operators/orientation.hpp | 76 +++--- src/axom/primal/tests/primal_in_sphere.cpp | 20 ++ src/axom/primal/tests/primal_orientation.cpp | 16 ++ src/axom/quest/Delaunay.hpp | 229 ++++++------------ 7 files changed, 342 insertions(+), 215 deletions(-) create mode 100644 src/axom/primal/operators/detail/predicate_determinants.hpp diff --git a/src/axom/primal/CMakeLists.txt b/src/axom/primal/CMakeLists.txt index d66a6443ae..905252da93 100644 --- a/src/axom/primal/CMakeLists.txt +++ b/src/axom/primal/CMakeLists.txt @@ -83,6 +83,7 @@ set( primal_headers operators/detail/intersect_patch_impl.hpp operators/detail/intersect_impl.hpp operators/detail/intersect_ray_impl.hpp + operators/detail/predicate_determinants.hpp operators/detail/slice_impl.hpp operators/detail/winding_number_2d_impl.hpp operators/detail/winding_number_2d_memoization.hpp diff --git a/src/axom/primal/operators/detail/predicate_determinants.hpp b/src/axom/primal/operators/detail/predicate_determinants.hpp new file mode 100644 index 0000000000..e499997d2a --- /dev/null +++ b/src/axom/primal/operators/detail/predicate_determinants.hpp @@ -0,0 +1,123 @@ +// 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) + +/*! + * \file predicate_determinants.hpp + * + * \brief Low-level determinant helpers for common computational geometry predicates. + * + * These routines centralize the core determinant computations used by primal + * predicates (e.g. orientation and in-sphere). They return the raw determinant + * values without interpreting tolerances or mapping to OrientationResult. + */ + +#ifndef AXOM_PRIMAL_PREDICATE_DETERMINANTS_HPP_ +#define AXOM_PRIMAL_PREDICATE_DETERMINANTS_HPP_ + +#include "axom/core/numerics/Determinants.hpp" + +#include "axom/primal/geometry/Point.hpp" + +namespace axom +{ +namespace primal +{ +namespace detail +{ + +/*! + * \brief Returns the raw 2D orientation determinant for three points. + * + * This determinant is twice the signed area of the triangle (a,b,c). + */ +template +inline double orientation_determinant(const Point& a, const Point& b, const Point& c) +{ + const auto ba = b - a; + const auto ca = c - a; + + return axom::numerics::determinant(ba[0], ba[1], ca[0], ca[1]); +} + +/*! + * \brief Returns the raw 3D orientation determinant for four points. + * + * This determinant is six times the signed volume of the tetrahedron (a,b,c,d). + */ +template +inline double orientation_determinant(const Point& a, + const Point& b, + const Point& c, + const Point& d) +{ + const auto ba = b - a; + const auto ca = c - a; + const auto da = d - a; + + // clang-format off + return axom::numerics::determinant( + ba[0], ba[1], ba[2], + ca[0], ca[1], ca[2], + da[0], da[1], da[2]); + // clang-format on +} + +/*! + * \brief Returns the raw in-sphere determinant for a 2D triangle circumcircle test. + * + * The sign convention matches primal::in_sphere(): a negative determinant means + * the query point is inside the circumcircle for a consistently oriented input. + */ +template +inline double in_sphere_determinant(const Point& q, + const Point& p0, + const Point& p1, + const Point& p2) +{ + const auto ba = p1 - p0; + const auto ca = p2 - p0; + const auto qa = q - p0; + + // clang-format off + return axom::numerics::determinant( + ba[0], ba[1], ba.squared_norm(), + ca[0], ca[1], ca.squared_norm(), + qa[0], qa[1], qa.squared_norm()); + // clang-format on +} + +/*! + * \brief Returns the raw in-sphere determinant for a 3D tetrahedron circumsphere test. + * + * The sign convention matches primal::in_sphere(): a negative determinant means + * the query point is inside the circumsphere for a consistently oriented input. + */ +template +inline double in_sphere_determinant(const Point& q, + const Point& p0, + const Point& p1, + const Point& p2, + const Point& p3) +{ + const auto ba = p1 - p0; + const auto ca = p2 - p0; + const auto da = p3 - p0; + const auto qa = q - p0; + + // clang-format off + return axom::numerics::determinant( + ba[0], ba[1], ba[2], ba.squared_norm(), + ca[0], ca[1], ca[2], ca.squared_norm(), + da[0], da[1], da[2], da.squared_norm(), + qa[0], qa[1], qa[2], qa.squared_norm()); + // clang-format on +} + +} // namespace detail +} // namespace primal +} // namespace axom + +#endif // AXOM_PRIMAL_PREDICATE_DETERMINANTS_HPP_ diff --git a/src/axom/primal/operators/in_sphere.hpp b/src/axom/primal/operators/in_sphere.hpp index 5b3913cb51..90397842c8 100644 --- a/src/axom/primal/operators/in_sphere.hpp +++ b/src/axom/primal/operators/in_sphere.hpp @@ -21,11 +21,73 @@ #include "axom/primal/geometry/Point.hpp" #include "axom/primal/geometry/Triangle.hpp" #include "axom/primal/geometry/Tetrahedron.hpp" +#include "axom/primal/geometry/OrientationResult.hpp" + +#include "axom/primal/operators/detail/predicate_determinants.hpp" namespace axom { namespace primal { +namespace robust +{ +/*! + * \brief Classifies a query point against a 2D triangle's circumcircle. + * + * \return ON_NEGATIVE_SIDE if inside, ON_POSITIVE_SIDE if outside, ON_BOUNDARY otherwise. + */ +template +inline int in_sphere(const Point& q, + const Point& p0, + const Point& p1, + const Point& p2, + double EPS = 1e-8) +{ + const double det = detail::in_sphere_determinant(q, p0, p1, p2); + if(axom::utilities::isNearlyEqual(det, 0., EPS)) + { + return primal::ON_BOUNDARY; + } + + return det < 0. ? primal::ON_NEGATIVE_SIDE : primal::ON_POSITIVE_SIDE; +} + +template +inline int in_sphere(const Point& q, const Triangle& tri, double EPS = 1e-8) +{ + return robust::in_sphere(q, tri[0], tri[1], tri[2], EPS); +} + +/*! + * \brief Classifies a query point against a 3D tetrahedron's circumsphere. + * + * \return ON_NEGATIVE_SIDE if inside, ON_POSITIVE_SIDE if outside, ON_BOUNDARY otherwise. + */ +template +inline int in_sphere(const Point& q, + const Point& p0, + const Point& p1, + const Point& p2, + const Point& p3, + double EPS = 1e-8) +{ + const double det = detail::in_sphere_determinant(q, p0, p1, p2, p3); + if(axom::utilities::isNearlyEqual(det, 0., EPS)) + { + return primal::ON_BOUNDARY; + } + + return det < 0. ? primal::ON_NEGATIVE_SIDE : primal::ON_POSITIVE_SIDE; +} + +template +inline int in_sphere(const Point& q, const Tetrahedron& tet, double EPS = 1e-8) +{ + return robust::in_sphere(q, tet[0], tet[1], tet[2], tet[3], EPS); +} + +} // namespace robust + /*! * \brief Tests whether a query point lies inside a 2D triangle's circumcircle * @@ -50,18 +112,8 @@ inline bool in_sphere(const Point& q, double EPS = 1e-8, bool includeBoundary = false) { - const auto ba = p1 - p0; - const auto ca = p2 - p0; - const auto qa = q - p0; - - // clang-format off - const double det = axom::numerics::determinant( - ba[0], ba[1], ba.squared_norm(), - ca[0], ca[1], ca.squared_norm(), - qa[0], qa[1], qa.squared_norm()); - // clang-format on - - return includeBoundary ? (det < 0 || axom::utilities::isNearlyEqual(det, 0., EPS)) : (det < 0); + const int res = robust::in_sphere(q, p0, p1, p2, EPS); + return includeBoundary ? (res != primal::ON_POSITIVE_SIDE) : (res == primal::ON_NEGATIVE_SIDE); } /*! @@ -110,20 +162,8 @@ inline bool in_sphere(const Point& q, double EPS = 1e-8, bool includeBoundary = false) { - const auto ba = p1 - p0; - const auto ca = p2 - p0; - const auto da = p3 - p0; - const auto qa = q - p0; - - // clang-format off - const double det = axom::numerics::determinant( - ba[0], ba[1], ba[2], ba.squared_norm(), - ca[0], ca[1], ca[2], ca.squared_norm(), - da[0], da[1], da[2], da.squared_norm(), - qa[0], qa[1], qa[2], qa.squared_norm()); - // clang-format on - - return includeBoundary ? (det < 0 || axom::utilities::isNearlyEqual(det, 0., EPS)) : (det < 0); + const int res = robust::in_sphere(q, p0, p1, p2, p3, EPS); + return includeBoundary ? (res != primal::ON_POSITIVE_SIDE) : (res == primal::ON_NEGATIVE_SIDE); } /*! diff --git a/src/axom/primal/operators/orientation.hpp b/src/axom/primal/operators/orientation.hpp index 4a71ed71f1..1cf5e5326e 100644 --- a/src/axom/primal/operators/orientation.hpp +++ b/src/axom/primal/operators/orientation.hpp @@ -24,12 +24,56 @@ #include "axom/primal/geometry/Triangle.hpp" #include "axom/primal/geometry/OrientationResult.hpp" +#include "axom/primal/operators/detail/predicate_determinants.hpp" + #include "axom/slic/interface/slic.hpp" namespace axom { namespace primal { +namespace robust +{ +/*! + * \brief Computes the orientation of a point \a p with respect to an oriented triangle \a tri. + * + * \return ON_BOUNDARY if within tolerance, ON_POSITIVE_SIDE / ON_NEGATIVE_SIDE otherwise. + */ +template +inline int orientation(const Point& p, const Triangle& tri, double EPS = 1e-9) +{ + const double det = detail::orientation_determinant(p, tri[0], tri[1], tri[2]); + + if(axom::utilities::isNearlyEqual(det, 0., EPS)) + { + return primal::ON_BOUNDARY; + } + + // Preserve existing convention: det < 0 implies ON_POSITIVE_SIDE. + return det < 0. ? primal::ON_POSITIVE_SIDE : primal::ON_NEGATIVE_SIDE; +} + +/*! + * \brief Computes the orientation of a point \a p with respect to an oriented segment \a seg. + * + * \return ON_BOUNDARY if within tolerance, ON_POSITIVE_SIDE / ON_NEGATIVE_SIDE otherwise. + */ +template +inline int orientation(const Point& p, const Segment& seg, double EPS = 1e-9) +{ + const double det = detail::orientation_determinant(p, seg[0], seg[1]); + + if(axom::utilities::isNearlyEqual(det, 0., EPS)) + { + return primal::ON_BOUNDARY; + } + + // Preserve existing convention: det < 0 implies ON_POSITIVE_SIDE. + return det < 0. ? primal::ON_POSITIVE_SIDE : primal::ON_NEGATIVE_SIDE; +} + +} // namespace robust + /*! * \brief Computes the orientation of a point \a p with respect to an * oriented triangle \a tri @@ -51,22 +95,7 @@ namespace primal template inline int orientation(const Point& p, const Triangle& tri, double EPS = 1e-9) { - const Vector A(p, tri[0]); - const Vector B(p, tri[1]); - const Vector C(p, tri[2]); - - // clang-format off - double det = numerics::determinant( A[0], A[1], A[2], - B[0], B[1], B[2], - C[0], C[1], C[2]); - // clang-format on - - if(axom::utilities::isNearlyEqual(det, 0., EPS)) - { - return primal::ON_BOUNDARY; - } - - return det < 0. ? primal::ON_POSITIVE_SIDE : primal::ON_NEGATIVE_SIDE; + return robust::orientation(p, tri, EPS); } /*! @@ -90,20 +119,7 @@ inline int orientation(const Point& p, const Triangle& tri, double E template inline int orientation(const Point& p, const Segment& seg, double EPS = 1e-9) { - const Vector A(p, seg[0]); - const Vector B(p, seg[1]); - - // clang-format off - double det = numerics::determinant( A[0], A[1], - B[0], B[1]); - // clang-format on - - if(axom::utilities::isNearlyEqual(det, 0., EPS)) - { - return primal::ON_BOUNDARY; - } - - return det < 0. ? primal::ON_POSITIVE_SIDE : primal::ON_NEGATIVE_SIDE; + return robust::orientation(p, seg, EPS); } } // namespace primal diff --git a/src/axom/primal/tests/primal_in_sphere.cpp b/src/axom/primal/tests/primal_in_sphere.cpp index 0e46e881c8..fa4d47a277 100644 --- a/src/axom/primal/tests/primal_in_sphere.cpp +++ b/src/axom/primal/tests/primal_in_sphere.cpp @@ -39,11 +39,15 @@ TEST(primal_in_sphere, test_in_sphere_2d) PointType q1 {0.1, 0.1}; EXPECT_TRUE(in_sphere(q1, p0, p1, p2)); EXPECT_TRUE(in_sphere(q1, tri)); + EXPECT_EQ(primal::ON_NEGATIVE_SIDE, primal::robust::in_sphere(q1, p0, p1, p2)); + EXPECT_EQ(primal::ON_NEGATIVE_SIDE, primal::robust::in_sphere(q1, tri)); // outside triangle PointType q2 {0.78, 0.6}; EXPECT_TRUE(in_sphere(q2, p0, p1, p2)); EXPECT_TRUE(in_sphere(q2, tri)); + EXPECT_EQ(primal::ON_NEGATIVE_SIDE, primal::robust::in_sphere(q2, p0, p1, p2)); + EXPECT_EQ(primal::ON_NEGATIVE_SIDE, primal::robust::in_sphere(q2, tri)); } // Test some points that are on the circumcircle @@ -53,12 +57,16 @@ TEST(primal_in_sphere, test_in_sphere_2d) EXPECT_FALSE(in_sphere(q1, tri)); EXPECT_TRUE(in_sphere(q1, p0, p1, p2, 1e-8, true)); EXPECT_TRUE(in_sphere(q1, tri, 1e-8, true)); + EXPECT_EQ(primal::ON_BOUNDARY, primal::robust::in_sphere(q1, p0, p1, p2, 1e-8)); + EXPECT_EQ(primal::ON_BOUNDARY, primal::robust::in_sphere(q1, tri, 1e-8)); PointType q2 {0, 1}; EXPECT_FALSE(in_sphere(q2, p0, p1, p2)); EXPECT_FALSE(in_sphere(q2, tri)); EXPECT_TRUE(in_sphere(q2, p0, p1, p2, 1e-8, true)); EXPECT_TRUE(in_sphere(q2, tri, 1e-8, true)); + EXPECT_EQ(primal::ON_BOUNDARY, primal::robust::in_sphere(q2, p0, p1, p2, 1e-8)); + EXPECT_EQ(primal::ON_BOUNDARY, primal::robust::in_sphere(q2, tri, 1e-8)); } // Test some points that are outside the circumcircle @@ -66,10 +74,12 @@ TEST(primal_in_sphere, test_in_sphere_2d) PointType q1 {1.1, 0}; EXPECT_FALSE(in_sphere(q1, p0, p1, p2)); EXPECT_FALSE(in_sphere(q1, tri)); + EXPECT_EQ(primal::ON_POSITIVE_SIDE, primal::robust::in_sphere(q1, p0, p1, p2)); PointType q2 {-5, -10}; EXPECT_FALSE(in_sphere(q2, p0, p1, p2)); EXPECT_FALSE(in_sphere(q2, tri)); + EXPECT_EQ(primal::ON_POSITIVE_SIDE, primal::robust::in_sphere(q2, p0, p1, p2)); } } @@ -91,10 +101,14 @@ TEST(primal_in_sphere, test_in_sphere_3d) PointType q1 {0.5, 0.5, 0.5}; EXPECT_TRUE(in_sphere(q1, p0, p1, p2, p3)); EXPECT_TRUE(in_sphere(q1, tet)); + EXPECT_EQ(primal::ON_NEGATIVE_SIDE, primal::robust::in_sphere(q1, p0, p1, p2, p3)); + EXPECT_EQ(primal::ON_NEGATIVE_SIDE, primal::robust::in_sphere(q1, tet)); PointType q2 {0., 0., 0.}; EXPECT_TRUE(in_sphere(q2, p0, p1, p2, p3)); EXPECT_TRUE(in_sphere(q2, tet)); + EXPECT_EQ(primal::ON_NEGATIVE_SIDE, primal::robust::in_sphere(q2, p0, p1, p2, p3)); + EXPECT_EQ(primal::ON_NEGATIVE_SIDE, primal::robust::in_sphere(q2, tet)); } // Test some points that are on the circumsphere @@ -104,12 +118,16 @@ TEST(primal_in_sphere, test_in_sphere_3d) EXPECT_FALSE(in_sphere(q1, tet)); EXPECT_TRUE(in_sphere(q1, p0, p1, p2, p3, 1e-8, true)); EXPECT_TRUE(in_sphere(q1, tet, 1e-8, true)); + EXPECT_EQ(primal::ON_BOUNDARY, primal::robust::in_sphere(q1, p0, p1, p2, p3, 1e-8)); + EXPECT_EQ(primal::ON_BOUNDARY, primal::robust::in_sphere(q1, tet, 1e-8)); PointType q2 {-1, 1, 1}; EXPECT_FALSE(in_sphere(q2, p0, p1, p2, p3)); EXPECT_FALSE(in_sphere(q2, tet)); EXPECT_TRUE(in_sphere(q2, p0, p1, p2, p3, 1e-8, true)); EXPECT_TRUE(in_sphere(q2, tet, 1e-8, true)); + EXPECT_EQ(primal::ON_BOUNDARY, primal::robust::in_sphere(q2, p0, p1, p2, p3, 1e-8)); + EXPECT_EQ(primal::ON_BOUNDARY, primal::robust::in_sphere(q2, tet, 1e-8)); } // Test some points that are outside the circumsphere @@ -117,10 +135,12 @@ TEST(primal_in_sphere, test_in_sphere_3d) PointType q1 {1.1, 1, 1}; EXPECT_FALSE(in_sphere(q1, p0, p1, p2, p3)); EXPECT_FALSE(in_sphere(q1, tet)); + EXPECT_EQ(primal::ON_POSITIVE_SIDE, primal::robust::in_sphere(q1, p0, p1, p2, p3)); PointType q2 {-1.1, 1, 1}; EXPECT_FALSE(in_sphere(q2, p0, p1, p2, p3)); EXPECT_FALSE(in_sphere(q2, tet)); + EXPECT_EQ(primal::ON_POSITIVE_SIDE, primal::robust::in_sphere(q2, p0, p1, p2, p3)); } } diff --git a/src/axom/primal/tests/primal_orientation.cpp b/src/axom/primal/tests/primal_orientation.cpp index 8fab225bd4..b1495b6c90 100644 --- a/src/axom/primal/tests/primal_orientation.cpp +++ b/src/axom/primal/tests/primal_orientation.cpp @@ -51,14 +51,19 @@ TEST(primal_orientation, orient3D) // check orientation of a few offset points // Without offset, the point should be on the same plane EXPECT_EQ(primal::ON_BOUNDARY, primal::orientation(phys, tri)); + EXPECT_EQ(primal::orientation(phys, tri), primal::robust::orientation(phys, tri)); // Offset along negative normal should have negative orientation EXPECT_EQ(primal::ON_NEGATIVE_SIDE, primal::orientation(phys - normal, tri)); EXPECT_EQ(primal::ON_NEGATIVE_SIDE, primal::orientation(phys - 0.25 * normal, tri)); + EXPECT_EQ(primal::orientation(phys - normal, tri), + primal::robust::orientation(phys - normal, tri)); // Offset along positive normal should have positive orientation EXPECT_EQ(primal::ON_POSITIVE_SIDE, primal::orientation(phys + normal, tri)); EXPECT_EQ(primal::ON_POSITIVE_SIDE, primal::orientation(phys + 0.25 * normal, tri)); + EXPECT_EQ(primal::orientation(phys + normal, tri), + primal::robust::orientation(phys + normal, tri)); // check that orientation is equivalent to half-space definition { @@ -96,11 +101,15 @@ TEST(primal_orientation, orient3D) EXPECT_EQ(primal::ON_BOUNDARY, primal::orientation(phys - smallOff * unitNormal, tri, TOL)); EXPECT_EQ(primal::ON_NEGATIVE_SIDE, primal::orientation(phys - largeOff * unitNormal, tri, TOL)); + EXPECT_EQ(primal::orientation(phys - largeOff * unitNormal, tri, TOL), + primal::robust::orientation(phys - largeOff * unitNormal, tri, TOL)); EXPECT_EQ(primal::ON_BOUNDARY, primal::orientation(phys + smallOff * unitNormal, tri, TOL)); EXPECT_EQ(primal::ON_POSITIVE_SIDE, primal::orientation(phys + largeOff * unitNormal, tri, TOL)); + EXPECT_EQ(primal::orientation(phys + largeOff * unitNormal, tri, TOL), + primal::robust::orientation(phys + largeOff * unitNormal, tri, TOL)); } } } @@ -133,14 +142,19 @@ TEST(primal_orientation, orient2D) // check orientation of a few offset points // Without offset, the point should be on the same plane EXPECT_EQ(primal::ON_BOUNDARY, primal::orientation(phys, seg)); + EXPECT_EQ(primal::orientation(phys, seg), primal::robust::orientation(phys, seg)); // Offset along negative normal should have negative orientation EXPECT_EQ(primal::ON_NEGATIVE_SIDE, primal::orientation(phys - normal, seg)); EXPECT_EQ(primal::ON_NEGATIVE_SIDE, primal::orientation(phys - 0.25 * normal, seg)); + EXPECT_EQ(primal::orientation(phys - normal, seg), + primal::robust::orientation(phys - normal, seg)); // Offset along positive normal should have positive orientation EXPECT_EQ(primal::ON_POSITIVE_SIDE, primal::orientation(phys + normal, seg)); EXPECT_EQ(primal::ON_POSITIVE_SIDE, primal::orientation(phys + 0.25 * normal, seg)); + EXPECT_EQ(primal::orientation(phys + normal, seg), + primal::robust::orientation(phys + normal, seg)); // check that orientation is equivalent to half-space definition { @@ -182,6 +196,8 @@ TEST(primal_orientation, orient2D) EXPECT_EQ(primal::ON_BOUNDARY, primal::orientation(phys + smallOff * unitNormal, seg, TOL)); EXPECT_EQ(primal::ON_POSITIVE_SIDE, primal::orientation(phys + largeOff * unitNormal, seg, TOL)); + EXPECT_EQ(primal::orientation(phys + largeOff * unitNormal, seg, TOL), + primal::robust::orientation(phys + largeOff * unitNormal, seg, TOL)); } } } diff --git a/src/axom/quest/Delaunay.hpp b/src/axom/quest/Delaunay.hpp index 839d637067..ae4191bd4d 100644 --- a/src/axom/quest/Delaunay.hpp +++ b/src/axom/quest/Delaunay.hpp @@ -534,7 +534,7 @@ class Delaunay // check orientations of top simplices const auto orient = evaluateElementOrientationDeterminant(element_idx); - if(orient.location != OrientationLocation::Positive) + if(orient.orientation != primal::ON_POSITIVE_SIDE) { if(verboseOutput) { @@ -925,62 +925,94 @@ class Delaunay //----------------------------------------------------------------------------- // In-sphere predicate helpers // - // These helpers centralize the raw in-sphere determinants and their - // classification into {inside/outside/boundary} results. - // - // Convention: we follow primal::in_sphere(): a negative determinant means the - // query point is inside the circumsphere (for our consistently oriented - // simplices). + // Delaunay uses determinant-based in-sphere tests for both cavity growth and + // global empty-circumsphere validation. We rely on primal::robust::in_sphere() + // to classify {inside, outside, on boundary} and only compute a + // scale-dependent determinant tolerance here. //----------------------------------------------------------------------------- - - enum class InSphereLocation : int + static double inSphereDeterminantTolerance(double scale) { - Inside = -1, - OnBoundary = 0, - Outside = 1 - }; + // Determinant magnitude scales like length^(DIM+2): L^4 in 2D, L^5 in 3D. + const double k = 128.; + if constexpr(DIM == 2) + { + return k * std::numeric_limits::epsilon() * scale * scale * scale * scale; + } + else + { + return k * std::numeric_limits::epsilon() * scale * scale * scale * scale * scale; + } + } - struct InSphereEval + static int inSphereOrientationOnMesh(const IAMeshType& mesh, const PointType& q, IndexType element_idx) { - double det {0.}; - double tol {0.}; - InSphereLocation location {InSphereLocation::OnBoundary}; - }; + const auto verts = mesh.boundaryVertices(element_idx); + + if constexpr(DIM == 2) + { + const PointType& p0 = mesh.getVertexPosition(verts[0]); + const PointType& p1 = mesh.getVertexPosition(verts[1]); + const PointType& p2 = mesh.getVertexPosition(verts[2]); - static InSphereLocation classifyInSphereDeterminant(double det, double tol) + const auto ba = p1 - p0; + const auto ca = p2 - p0; + const auto qa = q - p0; + const double scale = axom::utilities::max( + 1., + axom::utilities::max(ba.norm(), axom::utilities::max(ca.norm(), qa.norm()))); + const double eps = inSphereDeterminantTolerance(scale); + + return primal::robust::in_sphere(q, p0, p1, p2, eps); + } + else + { + const PointType& p0 = mesh.getVertexPosition(verts[0]); + const PointType& p1 = mesh.getVertexPosition(verts[1]); + const PointType& p2 = mesh.getVertexPosition(verts[2]); + const PointType& p3 = mesh.getVertexPosition(verts[3]); + + const auto ba = p1 - p0; + const auto ca = p2 - p0; + const auto da = p3 - p0; + const auto qa = q - p0; + const double scale = axom::utilities::max( + 1., + axom::utilities::max( + ba.norm(), + axom::utilities::max(ca.norm(), axom::utilities::max(da.norm(), qa.norm())))); + const double eps = inSphereDeterminantTolerance(scale); + + return primal::robust::in_sphere(q, p0, p1, p2, p3, eps); + } + } + + static bool isPointInSphereOnMesh(const IAMeshType& mesh, + const PointType& q, + IndexType element_idx, + bool includeBoundary) { - const int sign = signWithTolerance(det, tol); - return sign < 0 ? InSphereLocation::Inside - : (sign > 0 ? InSphereLocation::Outside : InSphereLocation::OnBoundary); + const int res = inSphereOrientationOnMesh(mesh, q, element_idx); + return includeBoundary ? (res != primal::ON_POSITIVE_SIDE) : (res == primal::ON_NEGATIVE_SIDE); } //----------------------------------------------------------------------------- // Simplex orientation helpers // - // Use the same determinant/tolerance pattern to validate simplex orientation. - // This returns the raw (unscaled) determinants: - // - 2D: determinant is twice signed area - // - 3D: determinant is six times signed volume + // Validate that simplices are positively oriented using the same + // determinant/tolerance pattern (determinants are 2*area in 2D and 6*volume in 3D). //----------------------------------------------------------------------------- - enum class OrientationLocation : int - { - Negative = -1, - OnBoundary = 0, - Positive = 1 - }; - struct OrientationEval { double det {0.}; double tol {0.}; - OrientationLocation location {OrientationLocation::OnBoundary}; + int orientation {primal::ON_BOUNDARY}; }; - static OrientationLocation classifyOrientationDeterminant(double det, double tol) + static int classifyOrientationDeterminant(double det, double tol) { const int sign = signWithTolerance(det, tol); - return sign < 0 ? OrientationLocation::Negative - : (sign > 0 ? OrientationLocation::Positive : OrientationLocation::OnBoundary); + return sign < 0 ? primal::ON_NEGATIVE_SIDE + : (sign > 0 ? primal::ON_POSITIVE_SIDE : primal::ON_BOUNDARY); } OrientationEval evaluateElementOrientationDeterminant(IndexType element_idx) const @@ -991,69 +1023,6 @@ class Delaunay return {det, tol, classifyOrientationDeterminant(det, tol)}; } - static double inSphereDeterminant2D(const PointType& q, - const PointType& p0, - const PointType& p1, - const PointType& p2) - { - const auto ba = p1 - p0; - const auto ca = p2 - p0; - const auto qa = q - p0; - - return axom::numerics::determinant(ba[0], - ba[1], - ba.squared_norm(), - ca[0], - ca[1], - ca.squared_norm(), - qa[0], - qa[1], - qa.squared_norm()); - } - - static double inSphereDeterminant3D(const PointType& q, - const PointType& p0, - const PointType& p1, - const PointType& p2, - const PointType& p3) - { - const auto ba = p1 - p0; - const auto ca = p2 - p0; - const auto da = p3 - p0; - const auto qa = q - p0; - - return axom::numerics::determinant(ba[0], - ba[1], - ba[2], - ba.squared_norm(), - ca[0], - ca[1], - ca[2], - ca.squared_norm(), - da[0], - da[1], - da[2], - da.squared_norm(), - qa[0], - qa[1], - qa[2], - qa.squared_norm()); - } - - static double inSphereTolerance(double scale) - { - // Determinant magnitude scales like length^(DIM+2): L^4 in 2D, L^5 in 3D. - const double k = 128.; - if constexpr(DIM == 2) - { - return k * std::numeric_limits::epsilon() * scale * scale * scale * scale; - } - else - { - return k * std::numeric_limits::epsilon() * scale * scale * scale * scale * scale; - } - } - BaryCoordType getRawBarycentricDeterminants(IndexType element_idx, const PointType& query_pt) const { const auto verts = m_mesh.boundaryVertices(element_idx); @@ -1097,64 +1066,6 @@ class Delaunay } } - static InSphereEval evaluateInSphereOnMesh(const IAMeshType& mesh, - const PointType& q, - IndexType element_idx) - { - const auto verts = mesh.boundaryVertices(element_idx); - - if constexpr(DIM == 2) - { - const PointType& p0 = mesh.getVertexPosition(verts[0]); - const PointType& p1 = mesh.getVertexPosition(verts[1]); - const PointType& p2 = mesh.getVertexPosition(verts[2]); - - const auto ba = p1 - p0; - const auto ca = p2 - p0; - const auto qa = q - p0; - - const double det = inSphereDeterminant2D(q, p0, p1, p2); - const double scale = axom::utilities::max( - 1., - axom::utilities::max(ba.norm(), axom::utilities::max(ca.norm(), qa.norm()))); - const double tol = inSphereTolerance(scale); - - return {det, tol, classifyInSphereDeterminant(det, tol)}; - } - else - { - const PointType& p0 = mesh.getVertexPosition(verts[0]); - const PointType& p1 = mesh.getVertexPosition(verts[1]); - const PointType& p2 = mesh.getVertexPosition(verts[2]); - const PointType& p3 = mesh.getVertexPosition(verts[3]); - - const auto ba = p1 - p0; - const auto ca = p2 - p0; - const auto da = p3 - p0; - const auto qa = q - p0; - - const double det = inSphereDeterminant3D(q, p0, p1, p2, p3); - const double scale = axom::utilities::max( - 1., - axom::utilities::max( - ba.norm(), - axom::utilities::max(ca.norm(), axom::utilities::max(da.norm(), qa.norm())))); - const double tol = inSphereTolerance(scale); - - return {det, tol, classifyInSphereDeterminant(det, tol)}; - } - } - - static bool isPointInSphereOnMesh(const IAMeshType& mesh, - const PointType& q, - IndexType element_idx, - bool includeBoundary) - { - const auto eval = evaluateInSphereOnMesh(mesh, q, element_idx); - return includeBoundary ? (eval.location != InSphereLocation::Outside) - : (eval.location == InSphereLocation::Inside); - } - bool isPointInsideForLocation(IndexType element_idx, const PointType& query_pt, const BaryCoordType& bary_coord, @@ -2073,7 +1984,7 @@ void Delaunay::validateInsertedBall(IndexType new_pt_i, } const auto orient = evaluateElementOrientationDeterminant(element_idx); - if(orient.location != OrientationLocation::Positive) + if(orient.orientation != primal::ON_POSITIVE_SIDE) { fmt::format_to( std::back_inserter(out), From 3d8492c723eb26f0a7aa2bec023267fc864a61ca Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Fri, 20 Mar 2026 13:39:15 -0700 Subject: [PATCH 492/986] slam: Adds reserve function to dynamic set, map and relation --- src/axom/slam/DynamicConstantRelation.hpp | 6 ++++++ src/axom/slam/DynamicMap.hpp | 7 +++++-- src/axom/slam/DynamicSet.hpp | 7 ++++--- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/axom/slam/DynamicConstantRelation.hpp b/src/axom/slam/DynamicConstantRelation.hpp index e6d56bc415..58d3b922c0 100644 --- a/src/axom/slam/DynamicConstantRelation.hpp +++ b/src/axom/slam/DynamicConstantRelation.hpp @@ -384,6 +384,12 @@ class DynamicConstantRelation : public /*Relation,*/ CardinalityPolicy } } + /// \brief Reserves storage for at least \a fromSetSize relation entries. + void reserve(SetPosition fromSetSize) + { + m_relationsVec.reserve(fromSetSize * relationCardinality()); + } + void updateSizes() { m_currentFromSize = m_fromSet->size(); diff --git a/src/axom/slam/DynamicMap.hpp b/src/axom/slam/DynamicMap.hpp index e0035df945..ea5673fed6 100644 --- a/src/axom/slam/DynamicMap.hpp +++ b/src/axom/slam/DynamicMap.hpp @@ -94,12 +94,15 @@ class DynamicMap /// @} - /** \brief Access to underlying data */ + /// \brief Access to underlying data OrderedMap& data() { return m_data; } - /** \brief Const access to underlying data */ + /// \brief Const access to underlying data const OrderedMap& data() const { return m_data; } + /// \brief Reserves storage for at least \a s entries. + void reserve(SetPosition s) { m_data.reserve(s); } + /// \name DynamicMap cardinality functions /// @{ diff --git a/src/axom/slam/DynamicSet.hpp b/src/axom/slam/DynamicSet.hpp index 7bc78cd38d..bb799f982a 100644 --- a/src/axom/slam/DynamicSet.hpp +++ b/src/axom/slam/DynamicSet.hpp @@ -396,11 +396,12 @@ class DynamicSet : public Set, SizePolicy /// \name Functions that modify the set cardinality /// @{ - /** - * \brief Insert an entry at the end of the set with value = ( size()-1 ) - */ + /// \brief Insert an entry at the end of the set with value = ( size()-1 ) IndexType insert() { return insert(size()); } + /// \brief Reserves storage for at least \a sz entries. + void reserve(PositionType sz) { m_data.reserve(sz); } + /** * \brief Insert an entry at the end of the set with the given value. * \param val the value of the inserted entry From 8fb8c5df8aa2b26edf945ee6b9530f1a0ad3fba1 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Fri, 20 Mar 2026 13:40:43 -0700 Subject: [PATCH 493/986] slam: Reduces dynamic allocations in MeshIA --- src/axom/slam/mesh_struct/IA.hpp | 6 ++++ src/axom/slam/mesh_struct/IA_impl.hpp | 46 +++++++++++++++++++-------- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/src/axom/slam/mesh_struct/IA.hpp b/src/axom/slam/mesh_struct/IA.hpp index 2e6e73c0c5..fdf3224975 100644 --- a/src/axom/slam/mesh_struct/IA.hpp +++ b/src/axom/slam/mesh_struct/IA.hpp @@ -372,6 +372,12 @@ class IAMesh */ void compact(); + /// \brief Reserves storage for at least \a vertex_capacity vertices. + void reserveVertices(IndexType vertex_capacity); + + /// \brief Reserves storage for at least \a element_capacity elements. + void reserveElements(IndexType element_capacity); + /** * \brief Prints the IA mesh structure, for debug purpose. */ diff --git a/src/axom/slam/mesh_struct/IA_impl.hpp b/src/axom/slam/mesh_struct/IA_impl.hpp index 2a272492d4..d333154cd6 100644 --- a/src/axom/slam/mesh_struct/IA_impl.hpp +++ b/src/axom/slam/mesh_struct/IA_impl.hpp @@ -24,6 +24,7 @@ #include #include #include +#include namespace axom { @@ -596,7 +597,8 @@ void IAMesh::fixVertexNeighborhood(IndexType vertex_idx, }; const IndexType totalVerts = elements().size(); - std::vector mapping; + static thread_local std::vector mapping; + mapping.clear(); mapping.reserve(TDIM * new_elements.size()); // helper lambda for determining if a face on one element (given by boundary verts nbr_verts) @@ -704,8 +706,8 @@ void IAMesh::compact() constexpr IndexType INVALID_ELEMENT = ElementSet::INVALID_ENTRY; //Construct an array that maps original set indices to new compacted indices - IndexArray vertex_set_map(vertex_set.size(), INVALID_VERTEX); - IndexArray element_set_map(element_set.size(), INVALID_ELEMENT); + std::unique_ptr vertex_set_map(new IndexType[vertex_set.size()]); + std::unique_ptr element_set_map(new IndexType[element_set.size()]); int v_count = 0; for(auto v : vertex_set.positions()) @@ -728,15 +730,15 @@ void IAMesh::compact() //update the EV boundary relation for(auto e : element_set.positions()) { - const auto new_e = element_set_map[e]; - if(new_e != INVALID_ELEMENT) + if(element_set.isValidEntry(e)) { + const auto new_e = element_set_map[e]; const auto ev_old = ev_rel[e]; auto ev_new = ev_rel[new_e]; for(auto i : ev_new.positions()) { const auto old = ev_old[i]; - ev_new[i] = (old != INVALID_VERTEX) ? vertex_set_map[old] : INVALID_VERTEX; + ev_new[i] = vertex_set.isValidEntry(old) ? vertex_set_map[old] : INVALID_VERTEX; } } } @@ -744,27 +746,27 @@ void IAMesh::compact() //update the VE coboundary relation for(auto v : vertex_set.positions()) { - const auto new_v = vertex_set_map[v]; - if(new_v != INVALID_VERTEX) + if(vertex_set.isValidEntry(v)) { + const auto new_v = vertex_set_map[v]; // cardinality of VE relation is 1 const auto old = ve_rel[v][0]; - ve_rel[new_v][0] = (old != INVALID_ELEMENT) ? element_set_map[old] : INVALID_ELEMENT; + ve_rel[new_v][0] = element_set.isValidEntry(old) ? element_set_map[old] : INVALID_ELEMENT; } } //update the EE adjacency relation for(auto e : element_set.positions()) { - int new_e = element_set_map[e]; - if(new_e != INVALID_ELEMENT) + if(element_set.isValidEntry(e)) { + const auto new_e = element_set_map[e]; const auto ee_old = ee_rel[e]; auto ee_new = ee_rel[new_e]; for(auto i : ee_new.positions()) { const auto old = ee_old[i]; - ee_new[i] = (old != INVALID_ELEMENT) ? element_set_map[old] : INVALID_ELEMENT; + ee_new[i] = element_set.isValidEntry(old) ? element_set_map[old] : INVALID_ELEMENT; } } } @@ -772,9 +774,9 @@ void IAMesh::compact() //Update the coordinate positions map for(auto v : vertex_set.positions()) { - int new_entry_index = vertex_set_map[v]; - if(new_entry_index != INVALID_VERTEX) + if(vertex_set.isValidEntry(v)) { + const IndexType new_entry_index = vertex_set_map[v]; vcoord_map[new_entry_index] = vcoord_map[v]; } } @@ -789,6 +791,22 @@ void IAMesh::compact() vcoord_map.resize(v_count); } +template +void IAMesh::reserveVertices(IndexType vertex_capacity) +{ + vertex_set.reserve(vertex_capacity); + ve_rel.reserve(vertex_capacity); + vcoord_map.reserve(vertex_capacity); +} + +template +void IAMesh::reserveElements(IndexType element_capacity) +{ + element_set.reserve(element_capacity); + ev_rel.reserve(element_capacity); + ee_rel.reserve(element_capacity); +} + template bool IAMesh::isEmpty() const { From becc1882a3ab82e5c830db1d139a5285d01387e2 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Fri, 20 Mar 2026 13:41:13 -0700 Subject: [PATCH 494/986] quest: Performance tuning in Delaunay class --- src/axom/quest/Delaunay.hpp | 647 ++++++++++++------ .../quest/examples/delaunay_triangulation.cpp | 1 + 2 files changed, 449 insertions(+), 199 deletions(-) diff --git a/src/axom/quest/Delaunay.hpp b/src/axom/quest/Delaunay.hpp index ae4191bd4d..a51250d50f 100644 --- a/src/axom/quest/Delaunay.hpp +++ b/src/axom/quest/Delaunay.hpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -90,6 +91,79 @@ class Delaunay InsertionValidationMode m_insertion_validation_mode; ElementFinder m_element_finder; + IndexType m_next_regrid_vertex_count {0}; + mutable slam::BitSet m_walk_visited; + std::uint64_t m_total_removed_elements {0}; + std::uint64_t m_max_removed_elements {0}; + std::uint64_t m_num_insertions {0}; + bool m_collect_location_stats {false}; + mutable std::uint64_t m_num_walk_calls {0}; + mutable std::uint64_t m_num_walk_found {0}; + mutable std::uint64_t m_num_walk_outside {0}; + mutable std::uint64_t m_num_walk_failed {0}; + mutable std::uint64_t m_total_walk_steps {0}; + mutable std::uint64_t m_max_walk_steps {0}; + mutable std::uint64_t m_num_linear_fallbacks {0}; + mutable std::uint64_t m_num_empty_seed_fallbacks {0}; + + // Scratch buffers used by point location to avoid per-call heap allocations. + // Delaunay is not thread-safe, so these are safe to reuse between calls. + mutable std::vector m_candidate_elements_scratch; + mutable std::vector m_walked_elements_scratch; + mutable std::vector m_walk_local_elements_scratch; + mutable std::vector m_initial_vertices_scratch; + mutable std::vector m_fallback_vertices_scratch; + std::unique_ptr m_insertion_helper; + +public: + struct InsertionStats + { + std::uint64_t insertions {0}; + std::uint64_t total_removed {0}; + std::uint64_t max_removed {0}; + double mean_removed() const + { + return insertions > 0 ? static_cast(total_removed) / static_cast(insertions) + : 0.0; + } + }; + + InsertionStats getInsertionStats() const + { + return {m_num_insertions, m_total_removed_elements, m_max_removed_elements}; + } + + struct PointLocationStats + { + std::uint64_t walk_calls {0}; + std::uint64_t walk_found {0}; + std::uint64_t walk_outside {0}; + std::uint64_t walk_failed {0}; + std::uint64_t total_walk_steps {0}; + std::uint64_t max_walk_steps {0}; + std::uint64_t linear_fallbacks {0}; + std::uint64_t empty_seed_fallbacks {0}; + + double mean_walk_steps() const + { + return walk_calls > 0 ? static_cast(total_walk_steps) / static_cast(walk_calls) + : 0.0; + } + }; + + void setCollectPointLocationStats(bool enabled) { m_collect_location_stats = enabled; } + + PointLocationStats getPointLocationStats() const + { + return {m_num_walk_calls, + m_num_walk_found, + m_num_walk_outside, + m_num_walk_failed, + m_total_walk_steps, + m_max_walk_steps, + m_num_linear_fallbacks, + m_num_empty_seed_fallbacks}; + } public: /** @@ -118,18 +192,30 @@ class Delaunay * \brief Defines the boundary of the triangulation. * \details subsequent points added to the triangulation must not be outside of this boundary. */ - void initializeBoundary(const BoundingBox& bb) - { - std::vector points; - IndexArray elem; + void initializeBoundary(const BoundingBox& bb); - generateInitialMesh(points, elem, bb); + /// \brief Reserve storage for an expected number of inserted points. + /// + /// Uses a dimension-specific heuristic for the total simplex count so large + /// bulk-builds can avoid repeated mesh-container reallocations. + void reserveForPointCount(IndexType num_points) + { + if(!m_has_boundary || num_points <= 0) + { + return; + } - m_mesh = IAMeshType(points, elem); - m_element_finder.recomputeGrid(m_mesh, bb); + const IndexType expected_vertices = m_mesh.vertices().size() + num_points; + constexpr double ELEMENTS_PER_POINT = DIM == 2 ? 3.0 : 9.0; + const IndexType expected_elements = m_mesh.elements().size() + + static_cast(std::ceil(ELEMENTS_PER_POINT * static_cast(num_points))); - m_bounding_box = bb; - m_has_boundary = true; + m_mesh.reserveVertices(expected_vertices); + m_mesh.reserveElements(expected_elements); + if(m_walk_visited.size() < static_cast(expected_elements)) + { + m_walk_visited = slam::BitSet(static_cast(expected_elements)); + } } /** @@ -141,51 +227,7 @@ class Delaunay * * \pre The current mesh must already be Delaunay. */ - void insertPoint(const PointType& new_pt) - { - //Make sure initializeBoundary(...) is called first - SLIC_ASSERT_MSG(m_has_boundary, "Error: Need a predefined boundary box prior to adding points."); - - //Make sure the new point is inside the boundary box - SLIC_ASSERT_MSG(m_bounding_box.contains(new_pt), - "Error: new point is outside of the boundary box."); - - // Find the mesh element containing the insertion point - IndexType element_i = findContainingElement(new_pt); - - if(element_i == INVALID_INDEX) - { - SLIC_WARNING( - fmt::format("Could not insert point {} into Delaunay triangulation: " - "Element containing that point was not found", - new_pt)); - return; - } - - // Run the insertion operation by finding invalidated elements around the point (the "cavity") - // and replacing them with new valid elements (the Delaunay "ball") - const BaryCoordType bary_coord = getBaryCoords(element_i, new_pt); - const IndexArray seed_elements = getSeedElements(element_i, bary_coord); - validateInsertionSeed(element_i, new_pt, bary_coord, seed_elements); - - InsertionHelper insertionHelper(m_mesh); - insertionHelper.findCavityElements(new_pt, seed_elements); - validateCavityBoundary(insertionHelper); - insertionHelper.createCavity(); - IndexType new_pt_i = m_mesh.addVertex(new_pt); - insertionHelper.delaunayBall(new_pt_i); - validateInsertedBall(new_pt_i, insertionHelper); - validateInsertionResult(); - - m_element_finder.updateBin(new_pt, new_pt_i); - m_num_removed_elements_since_last_compact += insertionHelper.numRemovedElements(); - - // Compact the mesh if there are too many removed elements - if(shouldCompactMesh()) - { - this->compactMesh(); - } - } + void insertPoint(const PointType& new_pt); template typename std::enable_if::type getElement(int element_index) const @@ -853,16 +895,11 @@ class Delaunay return INVALID_INDEX; } - // Query mode (`warnOnInvalid == false`) accepts points outside the convex hull, - // so it uses broader 3D recovery steps before falling back to a full scan. - const bool use_query_fallbacks = !warnOnInvalid && DIM == 3; - std::vector candidate_elements = getInitialCandidateElements(query_pt); - std::vector walked_elements; + m_candidate_elements_scratch.clear(); + getInitialCandidateElements(query_pt, m_candidate_elements_scratch); + m_walked_elements_scratch.clear(); PointLocationResult walk_result = - walkCandidateElements(query_pt, - candidate_elements, - 0, - use_query_fallbacks ? &walked_elements : nullptr); + walkCandidateElements(query_pt, m_candidate_elements_scratch, 0, &m_walked_elements_scratch); if(walk_result.status == PointLocationStatus::Found) { @@ -874,20 +911,25 @@ class Delaunay return INVALID_INDEX; } - if(use_query_fallbacks) + // Local recovery before falling back to a global scan. These steps are + // intentionally conservative: they only succeed when they find a simplex + // whose barycentric coordinates are non-negative (up to tolerance). + if(!m_candidate_elements_scratch.empty()) { - walk_result = - findContainingElementWithQueryFallbacks(query_pt, candidate_elements, walked_elements); - if(walk_result.status == PointLocationStatus::Found) - { - return walk_result.element_idx; - } - if(walk_result.status == PointLocationStatus::Outside) + const PointLocationResult fallback_result = + findContainingElementWithQueryFallbacks(query_pt, + m_candidate_elements_scratch, + m_walked_elements_scratch); + if(fallback_result.status == PointLocationStatus::Found) { - return INVALID_INDEX; + return fallback_result.element_idx; } } + if(m_collect_location_stats) + { + ++m_num_linear_fallbacks; + } return findContainingElementLinear(query_pt, warnOnInvalid); } @@ -1136,43 +1178,94 @@ class Delaunay } static constexpr int MAX_WALK_STEPS = 256; - std::vector local_visited_elements; std::vector& visited_elements = - visited_elements_out != nullptr ? *visited_elements_out : local_visited_elements; + visited_elements_out != nullptr ? *visited_elements_out : m_walk_local_elements_scratch; visited_elements.clear(); - visited_elements.reserve(MAX_WALK_STEPS); + if(static_cast(visited_elements.capacity()) < MAX_WALK_STEPS) + { + visited_elements.reserve(MAX_WALK_STEPS); + } IndexType element_i = start_element; + if(m_walk_visited.size() < static_cast(m_mesh.elements().size())) + { + m_walk_visited = slam::BitSet(static_cast(m_mesh.elements().size())); + } + + auto clearVisitedBits = [&]() { + for(const IndexType visited : visited_elements) + { + m_walk_visited.clear(static_cast(visited)); + } + }; + + int step_count = 0; + + auto recordWalk = [&](PointLocationStatus status) { + if(!m_collect_location_stats) + { + return; + } + + ++m_num_walk_calls; + m_total_walk_steps += static_cast(step_count); + m_max_walk_steps = + axom::utilities::max(m_max_walk_steps, static_cast(step_count)); + switch(status) + { + case PointLocationStatus::Found: + ++m_num_walk_found; + break; + case PointLocationStatus::Outside: + ++m_num_walk_outside; + break; + default: + ++m_num_walk_failed; + break; + } + }; + while(1) { - if(std::find(visited_elements.begin(), visited_elements.end(), element_i) != - visited_elements.end()) + ++step_count; + if(m_walk_visited.test(static_cast(element_i))) { + recordWalk(PointLocationStatus::Failed); + clearVisitedBits(); return {}; } + m_walk_visited.set(static_cast(element_i)); visited_elements.push_back(element_i); const BaryCoordType bary_coord = getBaryCoords(element_i, query_pt); ModularFaceIndex modular_idx(0); if(isPointInsideForLocation(element_i, query_pt, bary_coord, &modular_idx)) { + recordWalk(PointLocationStatus::Found); + clearVisitedBits(); return {element_i, PointLocationStatus::Found}; } if(static_cast(visited_elements.size()) >= MAX_WALK_STEPS) { + recordWalk(PointLocationStatus::Failed); + clearVisitedBits(); return {}; } const IndexType next_element = m_mesh.adjacentElements(element_i)[modular_idx + 1]; if(!m_mesh.isValidElement(next_element)) { + recordWalk(PointLocationStatus::Outside); + clearVisitedBits(); return {INVALID_INDEX, PointLocationStatus::Outside}; } element_i = next_element; if(!isSearchableElement(element_i)) { + recordWalk(PointLocationStatus::Failed); + clearVisitedBits(); return {}; } } @@ -1198,19 +1291,31 @@ class Delaunay } } - std::vector getInitialCandidateElements(const PointType& query_pt) const + void getInitialCandidateElements(const PointType& query_pt, + std::vector& candidate_elements) const { - std::vector candidate_elements; - candidate_elements.reserve(1); - - const IndexType initial_vertex = m_element_finder.getNearbyVertex(query_pt); - if(m_mesh.isValidVertex(initial_vertex)) - { - appendCandidateElement(candidate_elements, initial_vertex); - } + candidate_elements.clear(); + candidate_elements.reserve(16); + + // Prefer a small set of vertices from nearby bins rather than a single bin + // representative. For large meshes, a single cached vertex can be far (in + // terms of simplex-to-simplex walks) from the query point even if it lies + // in the same bin; providing a few local candidates keeps directed walks + // short and avoids expensive fallbacks. + m_initial_vertices_scratch.clear(); + m_element_finder.getNearbyVertices(m_mesh, + query_pt, + m_initial_vertices_scratch, + /*search_radius=*/1, + /*max_candidates=*/8); + appendCandidateElementsFromVertices(candidate_elements, m_initial_vertices_scratch); if(candidate_elements.empty()) { + if(m_collect_location_stats) + { + ++m_num_empty_seed_fallbacks; + } for(auto elem : m_mesh.elements().positions()) { if(isSearchableElement(elem)) @@ -1220,8 +1325,6 @@ class Delaunay } } } - - return candidate_elements; } PointLocationResult walkCandidateElements(const PointType& query_pt, @@ -1255,10 +1358,15 @@ class Delaunay return {walk_region_elem, PointLocationStatus::Found}; } - const auto fallback_vertices = - m_element_finder.getNearbyVertices(m_mesh, query_pt, QUERY_SEARCH_RADIUS, QUERY_CANDIDATE_LIMIT); + m_fallback_vertices_scratch.clear(); + m_element_finder.getNearbyVertices(m_mesh, + query_pt, + m_fallback_vertices_scratch, + QUERY_SEARCH_RADIUS, + QUERY_CANDIDATE_LIMIT); const std::size_t initial_candidate_count = candidate_elements.size(); - appendCandidateElementsFromVertices(candidate_elements, fallback_vertices); + candidate_elements.reserve(candidate_elements.size() + m_fallback_vertices_scratch.size()); + appendCandidateElementsFromVertices(candidate_elements, m_fallback_vertices_scratch); PointLocationResult walk_result = walkCandidateElements(query_pt, candidate_elements, initial_candidate_count); @@ -1267,7 +1375,7 @@ class Delaunay return walk_result; } - const IndexType nearby_elem = findContainingElementNearby(query_pt, fallback_vertices); + const IndexType nearby_elem = findContainingElementNearby(query_pt, m_fallback_vertices_scratch); if(nearby_elem != INVALID_INDEX) { return {nearby_elem, PointLocationStatus::Found}; @@ -1430,6 +1538,13 @@ class Delaunay m_mesh.compact(); m_num_removed_elements_since_last_compact = 0; m_element_finder.recomputeGrid(m_mesh, m_bounding_box); + if(m_next_regrid_vertex_count > 0) + { + while(m_next_regrid_vertex_count <= m_mesh.vertices().size()) + { + m_next_regrid_vertex_count *= 2; + } + } } /** @@ -1458,12 +1573,17 @@ class Delaunay { const auto& verts = mesh.vertices(); - // Use heuristic for resolution in each dimension to minimize storage - // Use 2*square root of nth root (n==DIM) - // e.g. for 1,000,000 verts in 2D, sqrt root is 1000, leading to ~ 60^2 grid w/ ~250 verts per bin - // e.g. for 1,000,000 verts in 3D, cube root is 100, leading to a 20^3 grid w/ ~125 verts per bin - const double res_root = std::pow(verts.size(), 1.0 / DIM); - const IndexType res = axom::utilities::max(2, 2 * static_cast(std::sqrt(res_root))); + // Choose the grid resolution so that each bin contains ~O(1) points per + // dimension on average. This keeps the "nearby bin" seed used by point + // insertion and query walks close (in terms of simplex adjacency hops) + // even for very large point sets. + // + // Target occupancy is ~ 4^DIM points per bin (16 in 2D, 64 in 3D). + constexpr double BIN_SIDE_SPACING = 4.0; + const double res_root = std::pow(static_cast(verts.size()), 1.0 / DIM); + const IndexType res = + axom::utilities::max(IndexType {2}, + static_cast(std::ceil(res_root / BIN_SIDE_SPACING))); auto expandedBB = BoundingBox(bb).scale(1.05); @@ -1482,9 +1602,22 @@ class Delaunay continue; } + // Skip vertices that are no longer incident to any valid element (can + // occur for interior vertices of removed cavities). Using them as + // point-location seeds forces expensive global fallbacks. + const IndexType coboundary = mesh.coboundaryElement(idx); + if(!mesh.isValidElement(coboundary)) + { + continue; + } + const auto& pos = mesh.getVertexPosition(idx); const auto cell = m_lattice.gridCell(pos); - flatIndex(cell) = idx; + IndexType& slot = flatIndex(cell); + if(!mesh.isValidVertex(slot) || !mesh.isValidElement(mesh.coboundaryElement(slot))) + { + slot = idx; + } } } @@ -1495,20 +1628,24 @@ class Delaunay * \note Some bins might not point to a vertex, so users should check * that the returned index is a valid vertex, e.g. using \a mesh.isValidVertex(vertex_id) */ - inline std::vector getNearbyVertices(const IAMeshType& mesh, - const PointType& pt, - int search_radius = 1, - int max_candidates = 1) const + inline void getNearbyVertices(const IAMeshType& mesh, + const PointType& pt, + std::vector& nearby_vertices, + int search_radius = 1, + int max_candidates = 1) const { const auto cell = m_lattice.gridCell(pt); - std::vector> candidates; + m_candidate_scratch.clear(); + const int span = 2 * search_radius + 1; + const int max_bins = (DIM == 2) ? (span * span) : (span * span * span); + m_candidate_scratch.reserve(static_cast(max_bins)); auto tryCandidate = [&](const typename LatticeType::GridCell& candidate_cell) { const IndexType vertex_idx = flatIndex(candidate_cell); - if(mesh.isValidVertex(vertex_idx)) + if(mesh.isValidVertex(vertex_idx) && mesh.isValidElement(mesh.coboundaryElement(vertex_idx))) { const double sq_dist = primal::squared_distance(mesh.getVertexPosition(vertex_idx), pt); - candidates.emplace_back(sq_dist, vertex_idx); + m_candidate_scratch.emplace_back(sq_dist, vertex_idx); } }; @@ -1566,23 +1703,27 @@ class Delaunay } } - std::sort(candidates.begin(), candidates.end(), [](const auto& lhs, const auto& rhs) { - return lhs.first < rhs.first; - }); + if(static_cast(m_candidate_scratch.size()) > max_candidates) + { + auto kth = m_candidate_scratch.begin() + max_candidates; + std::nth_element(m_candidate_scratch.begin(), + kth, + m_candidate_scratch.end(), + [](const auto& lhs, const auto& rhs) { return lhs.first < rhs.first; }); + m_candidate_scratch.resize(static_cast(max_candidates)); + } + + std::sort(m_candidate_scratch.begin(), + m_candidate_scratch.end(), + [](const auto& lhs, const auto& rhs) { return lhs.first < rhs.first; }); - std::vector nearby_vertices; + nearby_vertices.clear(); nearby_vertices.reserve( - axom::utilities::min(max_candidates, static_cast(candidates.size()))); - for(const auto& candidate : candidates) + axom::utilities::min(max_candidates, static_cast(m_candidate_scratch.size()))); + for(const auto& candidate : m_candidate_scratch) { nearby_vertices.push_back(candidate.second); - if(static_cast(nearby_vertices.size()) == max_candidates) - { - break; - } } - - return nearby_vertices; } /// \brief Returns the index of the vertex in the bin containing point \a pt @@ -1630,20 +1771,28 @@ class Delaunay private: axom::Array m_bins; LatticeType m_lattice; + mutable std::vector> m_candidate_scratch; }; /// Helper struct to locally insert a new point into a Delaunay complex while keeping the mesh Delaunay struct InsertionHelper { public: - InsertionHelper(IAMeshType& mesh) - : m_mesh(mesh) - , facet_set(0) - , fv_rel(&facet_set, &m_mesh.vertices()) - , fc_rel(&facet_set, &m_mesh.elements()) - , cavity_elems(0) - , inserted_elems(0) - { } + struct BoundaryFacet + { + std::array vertices {}; + IndexType neighbor {IAMeshType::ElementAdjacencyRelation::INVALID_INDEX}; + }; + + InsertionHelper(IAMeshType& mesh) : m_mesh(mesh) { } + + void reset() + { + boundary_facets.clear(); + cavity_elems.clear(); + inserted_elems.clear(); + m_stack.clear(); + } /** * \brief Find the Delaunay cavity: the elements whose circumspheres contain the query point @@ -1661,8 +1810,22 @@ class Delaunay { constexpr int reserveSize = (DIM == 2) ? 16 : 64; - IndexArray stack; - stack.reserve(reserveSize); + if(m_stack.capacity() < reserveSize) + { + m_stack.reserve(reserveSize); + } + if(cavity_elems.capacity() < reserveSize) + { + cavity_elems.reserve(reserveSize); + } + if(boundary_facets.capacity() < reserveSize) + { + boundary_facets.reserve(reserveSize); + } + if(inserted_elems.capacity() < reserveSize) + { + inserted_elems.reserve(reserveSize); + } // Seed the cavity with the containing element, and with any face-adjacent // neighbors when the insertion point lies on the containing simplex @@ -1670,17 +1833,17 @@ class Delaunay // insert directly onto existing edges/faces. for(const IndexType element_i : seed_elements) { - if(m_mesh.isValidElement(element_i) && m_checked_element_set.insert(element_i).second) + if(m_mesh.isValidElement(element_i) && !containsCavityElement(element_i)) { - cavity_elems.insert(element_i); - stack.push_back(element_i); + cavity_elems.push_back(element_i); + m_stack.push_back(element_i); } } - while(!stack.empty()) + while(!m_stack.empty()) { - const IndexType element_idx = stack.back(); - stack.pop_back(); + const IndexType element_idx = m_stack.back(); + m_stack.pop_back(); // Invariant: this element is valid, was checked and is in the cavity // Each neighbor is either in the cavity or the shared face is on the cavity boundary @@ -1692,51 +1855,47 @@ class Delaunay // invalid neighbor means face is on domain boundary, and thus on cavity boundary if(m_mesh.isValidElement(nbr)) { - // neighbor is valid; check circumsphere (if necesary), and add to cavity as appropriate - if(m_checked_element_set.insert(nbr).second) + // If the neighbor is already in the cavity, the shared face is + // internal and not part of the cavity boundary. + if(containsCavityElement(nbr)) { - if(isPointInCircumsphere(query_pt, nbr)) - { - cavity_elems.insert(nbr); - stack.push_back(nbr); - continue; // face is internal to cavity, nothing left to do for this face - } + continue; // both elem and neighbor along face are in cavity } - // check if neighbor is already in the cavity - else if(cavity_elems.findIndex(nbr) != ElementSet::INVALID_ENTRY) + + // neighbor is valid but not in cavity; check circumsphere and add when appropriate + if(isPointInCircumsphere(query_pt, nbr)) { - continue; // both elem and neighbor along face are in cavity + cavity_elems.push_back(nbr); + m_stack.push_back(nbr); + continue; // face is internal to cavity, nothing left to do for this face } } // if we got here, the face is on the boundary of the Delaunay cavity - // add it to facet sets and associated relations + // add it to the boundary facet list { - auto fIdx = facet_set.insert(); - fv_rel.updateSizes(); - fc_rel.updateSizes(); - const auto bdry = m_mesh.boundaryVertices(element_idx); - auto faceVerts = fv_rel[fIdx]; typename IAMeshType::ModularVertexIndex mod_idx(n_idx); + BoundaryFacet facet; for(int i = 0; i < VERTS_PER_FACET; i++) { - faceVerts[i] = bdry[mod_idx++]; + facet.vertices[i] = bdry[mod_idx++]; } //For tetrahedron, if the element face is odd, reverse vertex order if(DIM == 3 && n_idx % 2 == 1) { - axom::utilities::swap(faceVerts[1], faceVerts[2]); + axom::utilities::swap(facet.vertices[1], facet.vertices[2]); } - fc_rel.insert(fIdx, nbr); + facet.neighbor = nbr; + boundary_facets.push_back(facet); } } } SLIC_ASSERT_MSG(!cavity_elems.empty(), "Error: New point is not contained in the mesh"); - SLIC_ASSERT(!facet_set.empty()); + SLIC_ASSERT(!boundary_facets.empty()); } /** @@ -1744,7 +1903,7 @@ class Delaunay */ void createCavity() { - for(auto elem : cavity_elems) + for(const auto elem : cavity_elems) { m_mesh.removeElement(elem); } @@ -1753,7 +1912,7 @@ class Delaunay /// \brief Fill in the Delaunay cavity with new elements containing the insertion point void delaunayBall(IndexType new_pt_i) { - const int numFaces = facet_set.size(); + const int numFaces = static_cast(boundary_facets.size()); const IndexType invalid_neighbor = IAMeshType::ElementAdjacencyRelation::INVALID_INDEX; IndexType vlist[VERT_PER_ELEMENT] {}; @@ -1763,14 +1922,14 @@ class Delaunay // Create a new element from the face and the inserted point for(int d = 0; d < VERTS_PER_FACET; ++d) { - vlist[d] = fv_rel[i][d]; + vlist[d] = boundary_facets[static_cast(i)].vertices[d]; } vlist[VERTS_PER_FACET] = new_pt_i; // Face 0 is the cavity boundary face opposite the inserted point. // The remaining faces stay invalid until fixVertexNeighborhood() stitches // the new ball together around the inserted vertex. - const auto nID = fc_rel[i][0]; + const auto nID = boundary_facets[static_cast(i)].neighbor; for(int d = 0; d < VERT_PER_ELEMENT; ++d) { neighbors[d] = invalid_neighbor; @@ -1778,52 +1937,146 @@ class Delaunay neighbors[0] = nID; IndexType new_el = m_mesh.addElement(vlist, neighbors); - inserted_elems.insert(new_el); + inserted_elems.push_back(new_el); } // Fix neighborhood around the new point - m_mesh.fixVertexNeighborhood(new_pt_i, inserted_elems.data()); + m_mesh.fixVertexNeighborhood(new_pt_i, inserted_elems); } /// \brief Returns the number of elements removed during this insertion - int numRemovedElements() const { return cavity_elems.size(); } + int numRemovedElements() const { return static_cast(cavity_elems.size()); } + + bool containsCavityElement(IndexType element_idx) const + { + return std::find(cavity_elems.begin(), cavity_elems.end(), element_idx) != cavity_elems.end(); + } /// \brief Returns true when the query point is inside or on the element circumsphere bool isPointInCircumsphere(const PointType& query_pt, IndexType element_idx) const; - public: - // we create a surface mesh - // sets: vertex, facet - using PositionType = typename IAMeshType::PositionType; - using ElementType = typename IAMeshType::ElementType; - - using ElementSet = typename IAMeshType::ElementSet; - using VertexSet = typename IAMeshType::VertexSet; - using FacetSet = slam::DynamicSet; - - // relations: facet->vertex, facet->cell - static constexpr int VERTS_PER_FACET = IAMeshType::VERTS_PER_ELEM - 1; - using FacetBoundaryRelation = - typename IAMeshType::template IADynamicConstantRelation; - using FacetCoboundaryRelation = typename IAMeshType::template IADynamicConstantRelation<1>; - public: IAMeshType& m_mesh; - FacetSet facet_set; - FacetBoundaryRelation fv_rel; - FacetCoboundaryRelation fc_rel; + std::vector boundary_facets; + std::vector cavity_elems; + std::vector inserted_elems; - ElementSet cavity_elems; - ElementSet inserted_elems; - - std::set m_checked_element_set; + IndexArray m_stack; }; }; template constexpr typename Delaunay::IndexType Delaunay::INVALID_INDEX; +template +void Delaunay::initializeBoundary(const BoundingBox& bb) +{ + std::vector points; + IndexArray elem; + + generateInitialMesh(points, elem, bb); + + m_mesh = IAMeshType(points, elem); + m_element_finder.recomputeGrid(m_mesh, bb); + m_next_regrid_vertex_count = 1024; + m_walk_visited = slam::BitSet(static_cast(m_mesh.elements().size())); + m_total_removed_elements = 0; + m_max_removed_elements = 0; + m_num_insertions = 0; + m_num_walk_calls = 0; + m_num_walk_found = 0; + m_num_walk_outside = 0; + m_num_walk_failed = 0; + m_total_walk_steps = 0; + m_max_walk_steps = 0; + m_num_linear_fallbacks = 0; + m_num_empty_seed_fallbacks = 0; + + m_candidate_elements_scratch.clear(); + m_candidate_elements_scratch.reserve(QUERY_CANDIDATE_LIMIT); + m_walked_elements_scratch.clear(); + m_walked_elements_scratch.reserve(256); + m_walk_local_elements_scratch.clear(); + m_walk_local_elements_scratch.reserve(256); + m_initial_vertices_scratch.clear(); + m_initial_vertices_scratch.reserve(8); + m_fallback_vertices_scratch.clear(); + m_fallback_vertices_scratch.reserve(QUERY_CANDIDATE_LIMIT); + + if(!m_insertion_helper) + { + m_insertion_helper = std::make_unique(m_mesh); + } + + m_bounding_box = bb; + m_has_boundary = true; +} + +template +void Delaunay::insertPoint(const PointType& new_pt) +{ + //Make sure initializeBoundary(...) is called first + SLIC_ASSERT_MSG(m_has_boundary, "Error: Need a predefined boundary box prior to adding points."); + SLIC_ASSERT_MSG(m_insertion_helper != nullptr, + "Error: Insertion helper was not initialized. " + "Delaunay::initializeBoundary() needs to be called first."); + + //Make sure the new point is inside the boundary box + SLIC_ASSERT_MSG(m_bounding_box.contains(new_pt), + "Error: new point is outside of the boundary box."); + + // Find the mesh element containing the insertion point + IndexType element_i = findContainingElement(new_pt); + + if(element_i == INVALID_INDEX) + { + SLIC_WARNING( + fmt::format("Could not insert point {} into Delaunay triangulation: " + "Element containing that point was not found", + new_pt)); + return; + } + + // Run the insertion operation by finding invalidated elements around the point (the "cavity") + // and replacing them with new valid elements (the Delaunay "ball") + const BaryCoordType bary_coord = getBaryCoords(element_i, new_pt); + const IndexArray seed_elements = getSeedElements(element_i, bary_coord); + validateInsertionSeed(element_i, new_pt, bary_coord, seed_elements); + + auto& insertionHelper = *m_insertion_helper; + insertionHelper.reset(); + insertionHelper.findCavityElements(new_pt, seed_elements); + ++m_num_insertions; + m_total_removed_elements += static_cast(insertionHelper.numRemovedElements()); + m_max_removed_elements = + axom::utilities::max(m_max_removed_elements, + static_cast(insertionHelper.numRemovedElements())); + validateCavityBoundary(insertionHelper); + insertionHelper.createCavity(); + IndexType new_pt_i = m_mesh.addVertex(new_pt); + insertionHelper.delaunayBall(new_pt_i); + validateInsertedBall(new_pt_i, insertionHelper); + validateInsertionResult(); + + m_element_finder.updateBin(new_pt, new_pt_i); + m_num_removed_elements_since_last_compact += insertionHelper.numRemovedElements(); + if(m_next_regrid_vertex_count > 0 && m_mesh.vertices().size() >= m_next_regrid_vertex_count) + { + m_element_finder.recomputeGrid(m_mesh, m_bounding_box); + while(m_next_regrid_vertex_count <= m_mesh.vertices().size()) + { + m_next_regrid_vertex_count *= 2; + } + } + + // Compact the mesh if there are too many removed elements + if(shouldCompactMesh()) + { + this->compactMesh(); + } +} + template void Delaunay::validateCavityBoundary(const InsertionHelper& insertion_helper) const { @@ -1834,8 +2087,8 @@ void Delaunay::validateCavityBoundary(const InsertionHelper& insertion_help // Cavity boundary invariant: // - Every cavity face that borders a non-cavity neighbor (or the temporary - // bounding-box boundary) must appear exactly once in `facet_set`. - // - The facet's recorded neighbor (`fc_rel`) must match the mesh adjacency. + // bounding-box boundary) must appear exactly once in `boundary_facets`. + // - The facet's recorded neighbor must match the mesh adjacency. struct FacetInfo { IndexType neighbor_idx {INVALID_INDEX}; @@ -1846,11 +2099,10 @@ void Delaunay::validateCavityBoundary(const InsertionHelper& insertion_help bool valid = true; std::map cavity_boundary; - for(auto facet_idx : insertion_helper.facet_set.positions()) + for(const auto& facet : insertion_helper.boundary_facets) { - const FacetKey facet_key = makeSortedFaceKey(insertion_helper.fv_rel[facet_idx]); - const auto insert_status = - cavity_boundary.insert({facet_key, {insertion_helper.fc_rel[facet_idx][0], false}}); + const FacetKey facet_key = makeSortedFaceKey(facet.vertices); + const auto insert_status = cavity_boundary.insert({facet_key, {facet.neighbor, false}}); if(!insert_status.second) { fmt::format_to(std::back_inserter(out), @@ -1866,9 +2118,7 @@ void Delaunay::validateCavityBoundary(const InsertionHelper& insertion_help for(int facet_idx = 0; facet_idx < VERT_PER_ELEMENT; ++facet_idx) { const IndexType neighbor_idx = neighbors[facet_idx]; - if(m_mesh.isValidElement(neighbor_idx) && - insertion_helper.cavity_elems.findIndex(neighbor_idx) != - InsertionHelper::ElementSet::INVALID_ENTRY) + if(m_mesh.isValidElement(neighbor_idx) && insertion_helper.containsCavityElement(neighbor_idx)) { continue; } @@ -1948,20 +2198,19 @@ void Delaunay::validateInsertedBall(IndexType new_pt_i, std::map cavity_boundary; std::map> inserted_faces; - if(insertion_helper.inserted_elems.size() != insertion_helper.facet_set.size()) + if(insertion_helper.inserted_elems.size() != insertion_helper.boundary_facets.size()) { fmt::format_to( std::back_inserter(out), "\n\tInserted ball element count {} does not match cavity boundary facet count {}", insertion_helper.inserted_elems.size(), - insertion_helper.facet_set.size()); + insertion_helper.boundary_facets.size()); valid = false; } - for(auto facet_idx : insertion_helper.facet_set.positions()) + for(const auto& facet : insertion_helper.boundary_facets) { - cavity_boundary.insert({makeSortedFaceKey(insertion_helper.fv_rel[facet_idx]), - {insertion_helper.fc_rel[facet_idx][0], false}}); + cavity_boundary.insert({makeSortedFaceKey(facet.vertices), {facet.neighbor, false}}); } for(const IndexType element_idx : insertion_helper.inserted_elems) diff --git a/src/axom/quest/examples/delaunay_triangulation.cpp b/src/axom/quest/examples/delaunay_triangulation.cpp index 8b4486d288..02ef08aa16 100644 --- a/src/axom/quest/examples/delaunay_triangulation.cpp +++ b/src/axom/quest/examples/delaunay_triangulation.cpp @@ -135,6 +135,7 @@ void run_delaunay(const Input& params) // Create initial Delaunay triangulation over bounding box Delaunay dt; dt.initializeBoundary(bbox); + dt.reserveForPointCount(numPoints); // Incrementally insert random points within bounding box for(int i = 0; i < numPoints; ++i) From c18655336c3856bc42acc12ad49fcff52a282ce0 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Fri, 20 Mar 2026 13:41:24 -0700 Subject: [PATCH 495/986] quest: Adds some timers to ScatteredInterpolation class --- src/axom/quest/ScatteredInterpolation.hpp | 139 +++++++++++++++++++--- 1 file changed, 124 insertions(+), 15 deletions(-) diff --git a/src/axom/quest/ScatteredInterpolation.hpp b/src/axom/quest/ScatteredInterpolation.hpp index 351dcbb975..2b2f2439bd 100644 --- a/src/axom/quest/ScatteredInterpolation.hpp +++ b/src/axom/quest/ScatteredInterpolation.hpp @@ -20,7 +20,13 @@ #include "conduit.hpp" #include "conduit_blueprint.hpp" +#include #include +#include +#include +#include +#include +#include namespace { @@ -293,15 +299,36 @@ class ScatteredInterpolation // Each point has a 50% chance of being at the max level; of the remaining points // from the previous level, there's a 50% chance of being at the current level. // Any remaining points are at level 0. - auto computeLevel = [nlevels]() { - for(int level = nlevels; level > 0; --level) + // + // Implementation note: + // The original implementation used repeated calls to `random_real()` to + // simulate coin flips. At large N, this becomes costly (millions of calls + // into `std::uniform_real_distribution`). We can generate the same level + // distribution using a single 64-bit random integer per point: + // - Take the top `nlevels` bits. + // - The BRIO level is the position of the highest set bit (1..nlevels), or 0 if all are 0. + std::random_device rd; + std::mt19937_64 mt(rd()); + auto computeLevel = [&mt, nlevels]() -> int { + constexpr int RNG_BITS = 64; + AXOM_STATIC_ASSERT_MSG(std::numeric_limits::digits == RNG_BITS, + "Expected 64-bit RNG output"); + + const int used_bits = axom::utilities::min(nlevels, RNG_BITS); + std::uint64_t bits = mt(); + if(used_bits < RNG_BITS) { - if(axom::utilities::random_real(0., 1.) <= 0.5) - { - return level; - } + bits >>= (RNG_BITS - used_bits); + } + + if(bits == 0) + { + return 0; } - return 0; + + // level in [1, used_bits] + const int level = RNG_BITS - __builtin_clzll(bits); + return axom::utilities::min(level, nlevels); }; // We use a Morton index, quantized over the mesh bounding box to @@ -315,20 +342,59 @@ class ScatteredInterpolation auto quantizer = spin::rectangular_lattice_from_bounding_box(bb, res); - // Add points and sort following BRIO - axom::Array brio(0, npts); + // Phase 1: compute keys and count points per level (for a counting-sort by level). + std::vector levels(static_cast(npts)); + std::vector> keys(static_cast(npts)); + std::vector level_counts(static_cast(nlevels + 1), 0); + + for(int idx = 0; idx < npts; ++idx) + { + const int level = computeLevel(); + levels[static_cast(idx)] = static_cast(level); + ++level_counts[static_cast(level)]; + + keys[static_cast(idx)] = {MortonizerType::mortonize(quantizer.gridCell(pts[idx])), + idx}; + } + + // Phase 2: counting-sort keys by level into a single contiguous array in level order. + std::vector level_offsets(static_cast(nlevels + 1), 0); + for(int level = 1; level <= nlevels; ++level) + { + level_offsets[static_cast(level)] = + level_offsets[static_cast(level - 1)] + + level_counts[static_cast(level - 1)]; + } + std::vector level_write = level_offsets; + + std::vector> bucketed(static_cast(npts)); for(int idx = 0; idx < npts; ++idx) { - brio.emplace_back( - BrioComparator(idx, computeLevel(), MortonizerType::mortonize(quantizer.gridCell(pts[idx])))); + const int level = static_cast(levels[static_cast(idx)]); + const axom::IndexType out_pos = level_write[static_cast(level)]++; + bucketed[static_cast(out_pos)] = keys[static_cast(idx)]; + } + + // Phase 3: sort within each level by Morton index. + for(int level = 0; level <= nlevels; ++level) + { + const axom::IndexType begin = level_offsets[static_cast(level)]; + const axom::IndexType end = begin + level_counts[static_cast(level)]; + if(end - begin <= 1) + { + continue; + } + + auto first = bucketed.begin() + begin; + auto last = bucketed.begin() + end; + std::sort(first, last, [](const auto& lhs, const auto& rhs) { return lhs.first < rhs.first; }); } - std::sort(brio.begin(), brio.end()); - // extract and return the reordered points - axom::Array reordered(0, npts); + // Extract and return the reordered point indices. + axom::Array reordered(npts, npts); for(int idx = 0; idx < npts; ++idx) { - reordered.push_back(brio[idx].m_index); + reordered[idx] = bucketed[static_cast(idx)].second; } return reordered; @@ -361,7 +427,22 @@ class ScatteredInterpolation // Reorder the points according to the Biased Random Insertion Order (BRIO) algorithm // and store the mapping since we'll need to apply it during interpolation + const bool report_timing = std::getenv("AXOM_SCATTERED_INTERP_TIMING") != nullptr; + m_delaunay.setCollectPointLocationStats(report_timing); + axom::utilities::Timer phase_timer(false); + if(report_timing) + { + phase_timer.start(); + } + m_brio_data = computeInsertionOrder(coords, m_bounding_box); + + if(report_timing) + { + phase_timer.stop(); + SLIC_INFO(axom::fmt::format("ScatteredInterpolation BRIO ordering: {:.6f} sec", + phase_timer.elapsedTimeInSec())); + } m_brio = VertexIndirectionSet(typename VertexIndirectionSet::SetBuilder().size(npts).data(&m_brio_data)); @@ -370,10 +451,38 @@ class ScatteredInterpolation bb.scale(1.5); m_delaunay.initializeBoundary(bb); + m_delaunay.reserveForPointCount(npts); + + if(report_timing) + { + phase_timer.reset(); + phase_timer.start(); + } for(int i = 0; i < npts; ++i) { m_delaunay.insertPoint(coords[m_brio[i]]); } + if(report_timing) + { + phase_timer.stop(); + SLIC_INFO(axom::fmt::format("ScatteredInterpolation Delaunay insertion: {:.6f} sec", + phase_timer.elapsedTimeInSec())); + const auto stats = m_delaunay.getInsertionStats(); + SLIC_INFO(axom::fmt::format( + "ScatteredInterpolation Delaunay cavity removals: mean {:.2f}, max {}, over {} insertions", + stats.mean_removed(), + stats.max_removed, + stats.insertions)); + const auto location_stats = m_delaunay.getPointLocationStats(); + SLIC_INFO(axom::fmt::format( + "ScatteredInterpolation point location: walks {}, mean steps {:.2f}, max steps {}, " + "linear fallbacks {}, empty-seed fallbacks {}", + location_stats.walk_calls, + location_stats.mean_walk_steps(), + location_stats.max_walk_steps, + location_stats.linear_fallbacks, + location_stats.empty_seed_fallbacks)); + } m_delaunay.removeBoundary(); } From 6091798725b1b94d7713a8efbe47590ec6fc12df Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Fri, 20 Mar 2026 13:58:50 -0700 Subject: [PATCH 496/986] quest: Improves Delaunay cavity membership test --- src/axom/quest/Delaunay.hpp | 45 ++++++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/src/axom/quest/Delaunay.hpp b/src/axom/quest/Delaunay.hpp index a51250d50f..5af122526c 100644 --- a/src/axom/quest/Delaunay.hpp +++ b/src/axom/quest/Delaunay.hpp @@ -1788,6 +1788,14 @@ class Delaunay void reset() { + for(const IndexType element_idx : cavity_elems) + { + if(element_idx >= 0 && static_cast(element_idx) < m_cavity_membership.size()) + { + m_cavity_membership[static_cast(element_idx)] = 0; + } + } + boundary_facets.clear(); cavity_elems.clear(); inserted_elems.clear(); @@ -1809,6 +1817,7 @@ class Delaunay void findCavityElements(const PointType& query_pt, const IndexArray& seed_elements) { constexpr int reserveSize = (DIM == 2) ? 16 : 64; + ensureCavityMembershipCapacity(); if(m_stack.capacity() < reserveSize) { @@ -1833,10 +1842,9 @@ class Delaunay // insert directly onto existing edges/faces. for(const IndexType element_i : seed_elements) { - if(m_mesh.isValidElement(element_i) && !containsCavityElement(element_i)) + if(m_mesh.isValidElement(element_i)) { - cavity_elems.push_back(element_i); - m_stack.push_back(element_i); + addCavityElement(element_i); } } @@ -1865,8 +1873,7 @@ class Delaunay // neighbor is valid but not in cavity; check circumsphere and add when appropriate if(isPointInCircumsphere(query_pt, nbr)) { - cavity_elems.push_back(nbr); - m_stack.push_back(nbr); + addCavityElement(nbr); continue; // face is internal to cavity, nothing left to do for this face } } @@ -1949,7 +1956,8 @@ class Delaunay bool containsCavityElement(IndexType element_idx) const { - return std::find(cavity_elems.begin(), cavity_elems.end(), element_idx) != cavity_elems.end(); + return element_idx >= 0 && static_cast(element_idx) < m_cavity_membership.size() && + m_cavity_membership[static_cast(element_idx)] != 0; } /// \brief Returns true when the query point is inside or on the element circumsphere @@ -1963,6 +1971,31 @@ class Delaunay std::vector inserted_elems; IndexArray m_stack; + + private: + void ensureCavityMembershipCapacity() + { + const std::size_t required_size = static_cast(m_mesh.elements().size()); + if(m_cavity_membership.size() < required_size) + { + m_cavity_membership.resize(required_size, 0); + } + } + + void addCavityElement(IndexType element_idx) + { + ensureCavityMembershipCapacity(); + if(containsCavityElement(element_idx)) + { + return; + } + + m_cavity_membership[static_cast(element_idx)] = 1; + cavity_elems.push_back(element_idx); + m_stack.push_back(element_idx); + } + + std::vector m_cavity_membership; }; }; From 55aed89ceee229e4327f3b5b6743ec8e26fdbcca Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Fri, 20 Mar 2026 17:32:32 -0700 Subject: [PATCH 497/986] Adds some functionality to modify the random number seed for Delaunay --- src/axom/quest/Delaunay.hpp | 7 +++- src/axom/quest/ScatteredInterpolation.hpp | 38 ++++++++++++++++++- .../examples/scattered_interpolation.cpp | 30 +++++++++++++-- 3 files changed, 68 insertions(+), 7 deletions(-) diff --git a/src/axom/quest/Delaunay.hpp b/src/axom/quest/Delaunay.hpp index 5af122526c..671f5a2d6b 100644 --- a/src/axom/quest/Delaunay.hpp +++ b/src/axom/quest/Delaunay.hpp @@ -1528,8 +1528,11 @@ class Delaunay { // Note: This auto-compacting feature is hard coded. // It may be good to let user have control of this option in the future. - return m_num_removed_elements_since_last_compact > 512 && - (m_num_removed_elements_since_last_compact > .2 * m_mesh.elements().size()); + constexpr int MIN_REMOVED_ELEMENTS = DIM == 2 ? 512 : 2048; + constexpr double REMOVED_ELEMENT_FRACTION = DIM == 2 ? 0.25 : 0.35; + return m_num_removed_elements_since_last_compact > MIN_REMOVED_ELEMENTS && + (m_num_removed_elements_since_last_compact > + REMOVED_ELEMENT_FRACTION * m_mesh.elements().size()); } /// \brief Compacts the underlying mesh diff --git a/src/axom/quest/ScatteredInterpolation.hpp b/src/axom/quest/ScatteredInterpolation.hpp index 2b2f2439bd..dd60bcfac1 100644 --- a/src/axom/quest/ScatteredInterpolation.hpp +++ b/src/axom/quest/ScatteredInterpolation.hpp @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -46,6 +47,25 @@ inline int extractSize(const conduit::Node& values_node) return values_node["x"].dtype().number_of_elements(); } +inline bool getScatteredInterpSeed(std::uint64_t& seed) +{ + const char* env = std::getenv("AXOM_SCATTERED_INTERP_SEED"); + if(env == nullptr || env[0] == '\0') + { + return false; + } + + char* end = nullptr; + const auto parsed = std::strtoull(env, &end, 10); + if(end == env) + { + return false; + } + + seed = static_cast(parsed); + return true; +} + /** * \brief Utility function to create an axom::ArrayView over the array * of native types stored by a conduit::Node @@ -307,8 +327,22 @@ class ScatteredInterpolation // distribution using a single 64-bit random integer per point: // - Take the top `nlevels` bits. // - The BRIO level is the position of the highest set bit (1..nlevels), or 0 if all are 0. - std::random_device rd; - std::mt19937_64 mt(rd()); + std::mt19937_64 mt; + std::uint64_t seed = 0; + if(::getScatteredInterpSeed(seed)) + { + // Use a dimension/size-specific offset so the BRIO ordering remains + // reproducible without sharing the example's point-generation stream. + mt.seed(seed ^ + (0x9e3779b97f4a7c15ULL + static_cast(DIM) + + (static_cast(npts) << 8))); + } + else + { + std::random_device rd; + mt.seed(rd()); + } + auto computeLevel = [&mt, nlevels]() -> int { constexpr int RNG_BITS = 64; AXOM_STATIC_ASSERT_MSG(std::numeric_limits::digits == RNG_BITS, diff --git a/src/axom/quest/examples/scattered_interpolation.cpp b/src/axom/quest/examples/scattered_interpolation.cpp index 06aea1d2ba..bf160ad5a1 100644 --- a/src/axom/quest/examples/scattered_interpolation.cpp +++ b/src/axom/quest/examples/scattered_interpolation.cpp @@ -26,6 +26,8 @@ #include #include +#include +#include namespace primal = axom::primal; namespace quest = axom::quest; @@ -586,8 +588,6 @@ axom::Array> generatePts(int numPts, const std::vector& bb_min, const std::vector& bb_max) { - using axom::utilities::random_real; - using PointType = typename primal::Point; using BoundingBox = typename primal::BoundingBox; @@ -595,13 +595,37 @@ axom::Array> generatePts(int numPts, BoundingBox bbox {PointType(bb_min.data()), PointType(bb_max.data())}; + std::mt19937_64 mt; + if(const char* env = std::getenv("AXOM_SCATTERED_INTERP_SEED")) + { + char* end = nullptr; + const auto parsed = std::strtoull(env, &end, 10); + if(end != env) + { + mt.seed(static_cast(parsed)); + } + else + { + std::random_device rd; + mt.seed(rd()); + } + } + else + { + std::random_device rd; + mt.seed(rd()); + } + + std::uniform_real_distribution unit_dist(0., 1.); + // generate random points within bounding box for(int i = 0; i < numPts; ++i) { PointType& pt = pts[i]; for(int d = 0; d < DIM; ++d) { - pt[d] = random_real(bbox.getMin()[d], bbox.getMax()[d]); + const double t = unit_dist(mt); + pt[d] = t * (bbox.getMax()[d] - bbox.getMin()[d]) + bbox.getMin()[d]; } } From 621e934bfe7de7b82bda96452c36a953166741b1 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 14 Apr 2026 13:26:34 -0700 Subject: [PATCH 498/986] Optimizes IA compaction and neighborhood fixing --- src/axom/slam/mesh_struct/IA_impl.hpp | 293 +++++++++++++++++--------- src/axom/slam/tests/slam_IA.cpp | 47 +++++ 2 files changed, 244 insertions(+), 96 deletions(-) diff --git a/src/axom/slam/mesh_struct/IA_impl.hpp b/src/axom/slam/mesh_struct/IA_impl.hpp index d333154cd6..7245d79188 100644 --- a/src/axom/slam/mesh_struct/IA_impl.hpp +++ b/src/axom/slam/mesh_struct/IA_impl.hpp @@ -25,6 +25,7 @@ #include #include #include +#include namespace axom { @@ -586,20 +587,35 @@ template void IAMesh::fixVertexNeighborhood(IndexType vertex_idx, const std::vector& new_elements) { - using FaceLinkVerts = axom::StackArray; + constexpr IndexType EMPTY_SLOT = INVALID_ELEMENT_INDEX; + constexpr IndexType TOMBSTONE_SLOT = INVALID_ELEMENT_INDEX - 1; - // Struct used to associate a collection of vertex indices with a face of the mesh - struct FaceLinkMapping + struct PendingFace { - IndexType boundary_hash; // unique identifier for collection of face link verts - IndexType element_idx; // the element containing this face - IndexType face_idx; // the index of the face w.r.t. the element + IndexType key0 {INVALID_VERTEX_INDEX}; + IndexType key1 {INVALID_VERTEX_INDEX}; + IndexType element_idx {EMPTY_SLOT}; + IndexType face_idx {INVALID_ELEMENT_INDEX}; }; - const IndexType totalVerts = elements().size(); - static thread_local std::vector mapping; - mapping.clear(); - mapping.reserve(TDIM * new_elements.size()); + static thread_local std::vector pending_faces; + static thread_local std::vector used_slots; + for(const auto slot : used_slots) + { + pending_faces[slot].element_idx = EMPTY_SLOT; + } + used_slots.clear(); + + std::size_t table_size = 8; + const std::size_t target_slots = std::max(8, 4 * new_elements.size()); + while(table_size < target_slots) + { + table_size <<= 1; + } + if(pending_faces.size() < table_size) + { + pending_faces.resize(table_size); + } // helper lambda for determining if a face on one element (given by boundary verts nbr_verts) // is shared with another element (given by boundary verts elem_verts) @@ -618,6 +634,44 @@ void IAMesh::fixVertexNeighborhood(IndexType vertex_idx, return true; }; + auto getPendingFaceKey = [vertex_idx](const BoundarySubset& bdry, IndexType vert_i) { + PendingFace key; + for(int i = 0, idx = 0; i < TDIM; ++i) + { + if(i != vert_i && bdry[i] != vertex_idx) + { + if(idx == 0) + { + key.key0 = bdry[i]; + } + else + { + key.key1 = bdry[i]; + } + ++idx; + } + } + + if constexpr(TDIM == 3) + { + if(key.key1 < key.key0) + { + axom::utilities::swap(key.key0, key.key1); + } + } + + return key; + }; + + auto pendingFaceHash = [](IndexType key0, IndexType key1) -> std::size_t { + std::size_t seed = static_cast(key0); + seed ^= static_cast(key1) + 0x9e3779b97f4a7c15ULL + (seed << 6) + (seed >> 2); + return seed; + }; + + int num_pending_faces = 0; + int num_incident_faces = 0; + for(auto el : new_elements) { const auto bdry = ev_rel[el]; @@ -648,54 +702,60 @@ void IAMesh::fixVertexNeighborhood(IndexType vertex_idx, // ... or it is incident in the common vertex: vertex_idx else { - // Add all element-face associations to an array; - // we'll update the adjacencies outside this loop - FaceLinkVerts face_link_verts {}; - for(int i = 0, idx = 0; i < TDIM; ++i) + const PendingFace face = getPendingFaceKey(bdry, vert_i); + const std::size_t mask = pending_faces.size() - 1; + std::size_t slot = pendingFaceHash(face.key0, face.key1) & mask; + std::size_t insert_slot = pending_faces.size(); + + for(;; slot = (slot + 1) & mask) { - if(i != vert_i && // i is a vertex in face_i - bdry[i] != vertex_idx) // and not the common vertex + auto& pending = pending_faces[slot]; + if(pending.element_idx == EMPTY_SLOT) { - face_link_verts[idx++] = bdry[i]; + if(insert_slot == pending_faces.size()) + { + insert_slot = slot; + } + + auto& insert_entry = pending_faces[insert_slot]; + insert_entry = face; + insert_entry.element_idx = el; + insert_entry.face_idx = face_i; + used_slots.push_back(insert_slot); + ++num_pending_faces; + break; } - } - FaceLinkMapping m; - if(TDIM == 2) - { - m.boundary_hash = face_link_verts[0]; - } - else // TDIM == 3 - { - // compute unique identifier based on sorted face link vertices - m.boundary_hash = (face_link_verts[0] < face_link_verts[1]) - ? face_link_verts[0] * totalVerts + face_link_verts[1] - : face_link_verts[1] * totalVerts + face_link_verts[0]; - } + if(pending.element_idx == TOMBSTONE_SLOT) + { + if(insert_slot == pending_faces.size()) + { + insert_slot = slot; + } + continue; + } - m.element_idx = el; - m.face_idx = face_i; - mapping.emplace_back(m); + if(pending.key0 == face.key0 && pending.key1 == face.key1) + { + SLIC_ASSERT_MSG(pending.element_idx != el, + "Each face in the inserted star should be shared by two elements"); + ee_rel.modify(pending.element_idx, pending.face_idx, el); + ee_rel.modify(el, face_i, pending.element_idx); + pending.element_idx = TOMBSTONE_SLOT; + --num_pending_faces; + break; + } + } + ++num_incident_faces; } } } - SLIC_ASSERT(mapping.size() == new_elements.size() * TDIM); - - // Sort by face vertices - std::sort(mapping.begin(), mapping.end(), [](const FaceLinkMapping& lhs, const FaceLinkMapping& rhs) { - return lhs.boundary_hash < rhs.boundary_hash; - }); - - // Apply neighbor data from matching face pairs - const int SZ = mapping.size(); - for(int idx = 1; idx < SZ; idx += 2) - { - const auto& left = mapping[idx - 1]; - const auto& right = mapping[idx]; - ee_rel.modify(left.element_idx, left.face_idx, right.element_idx); - ee_rel.modify(right.element_idx, right.face_idx, left.element_idx); - } + AXOM_UNUSED_VAR(num_incident_faces); + AXOM_UNUSED_VAR(num_pending_faces); + SLIC_ASSERT(num_incident_faces == static_cast(new_elements.size()) * TDIM); + SLIC_ASSERT_MSG(num_pending_faces == 0, + "All faces in the inserted star should be paired exactly once"); } // Remove all the invalid entries in the IA structure @@ -704,84 +764,125 @@ void IAMesh::compact() { constexpr IndexType INVALID_VERTEX = VertexSet::INVALID_ENTRY; constexpr IndexType INVALID_ELEMENT = ElementSet::INVALID_ENTRY; - - //Construct an array that maps original set indices to new compacted indices - std::unique_ptr vertex_set_map(new IndexType[vertex_set.size()]); - std::unique_ptr element_set_map(new IndexType[element_set.size()]); - - int v_count = 0; - for(auto v : vertex_set.positions()) + const IndexType vertex_size = vertex_set.size(); + const IndexType element_size = element_set.size(); + const auto& vertex_data = vertex_set.data(); + const auto& element_data = element_set.data(); + auto& ev_data = ev_rel.data(); + auto& ve_data = ve_rel.data(); + auto& ee_data = ee_rel.data(); + + bool has_invalid_vertices = false; + IndexType v_count = 0; + for(IndexType v = 0; v < vertex_size; ++v) { - if(vertex_set.isValidEntry(v)) - { - vertex_set_map[v] = v_count++; - } + has_invalid_vertices |= vertex_data[v] == INVALID_VERTEX; + v_count += (vertex_data[v] != INVALID_VERTEX) ? 1 : 0; } - int e_count = 0; - for(auto e : element_set.positions()) + bool has_invalid_elements = false; + std::unique_ptr element_set_map(new IndexType[element_size]); + IndexType e_count = 0; + for(IndexType e = 0; e < element_size; ++e) { - if(element_set.isValidEntry(e)) + if(element_data[e] != INVALID_ELEMENT) { element_set_map[e] = e_count++; } + else + { + has_invalid_elements = true; + } + } + + if(!has_invalid_vertices && !has_invalid_elements) + { + return; } - //update the EV boundary relation - for(auto e : element_set.positions()) + auto remapElement = [&](IndexType old_element) { + return (old_element >= 0 && old_element < element_size && + element_data[old_element] != INVALID_ELEMENT) + ? element_set_map[old_element] + : INVALID_ELEMENT; + }; + + if(!has_invalid_vertices) { - if(element_set.isValidEntry(e)) + for(IndexType e = 0; e < element_size; ++e) { - const auto new_e = element_set_map[e]; - const auto ev_old = ev_rel[e]; - auto ev_new = ev_rel[new_e]; - for(auto i : ev_new.positions()) + if(element_data[e] == INVALID_ELEMENT) { - const auto old = ev_old[i]; - ev_new[i] = vertex_set.isValidEntry(old) ? vertex_set_map[old] : INVALID_VERTEX; + continue; } + + const IndexType new_e = element_set_map[e]; + const IndexType old_base = e * VERTS_PER_ELEM; + const IndexType new_base = new_e * VERTS_PER_ELEM; + for(int i = 0; i < VERTS_PER_ELEM; ++i) + { + ev_data[new_base + i] = ev_data[old_base + i]; + ee_data[new_base + i] = remapElement(ee_data[old_base + i]); + } + } + + for(IndexType v = 0; v < vertex_size; ++v) + { + ve_data[v] = remapElement(ve_data[v]); } + + element_set.reset(e_count); + ev_rel.updateSizes(); + ee_rel.updateSizes(); + return; } - //update the VE coboundary relation - for(auto v : vertex_set.positions()) + std::unique_ptr vertex_set_map(new IndexType[vertex_size]); + v_count = 0; + for(IndexType v = 0; v < vertex_size; ++v) { - if(vertex_set.isValidEntry(v)) + if(vertex_data[v] != INVALID_VERTEX) { - const auto new_v = vertex_set_map[v]; - // cardinality of VE relation is 1 - const auto old = ve_rel[v][0]; - ve_rel[new_v][0] = element_set.isValidEntry(old) ? element_set_map[old] : INVALID_ELEMENT; + vertex_set_map[v] = v_count++; } } - //update the EE adjacency relation - for(auto e : element_set.positions()) + auto remapVertex = [&](IndexType old_vertex) { + return (old_vertex >= 0 && old_vertex < vertex_size && vertex_data[old_vertex] != INVALID_VERTEX) + ? vertex_set_map[old_vertex] + : INVALID_VERTEX; + }; + + for(IndexType e = 0; e < element_size; ++e) { - if(element_set.isValidEntry(e)) + if(element_data[e] == INVALID_ELEMENT) { - const auto new_e = element_set_map[e]; - const auto ee_old = ee_rel[e]; - auto ee_new = ee_rel[new_e]; - for(auto i : ee_new.positions()) - { - const auto old = ee_old[i]; - ee_new[i] = element_set.isValidEntry(old) ? element_set_map[old] : INVALID_ELEMENT; - } + continue; + } + + const IndexType new_e = element_set_map[e]; + const IndexType old_base = e * VERTS_PER_ELEM; + const IndexType new_base = new_e * VERTS_PER_ELEM; + for(int i = 0; i < VERTS_PER_ELEM; ++i) + { + ev_data[new_base + i] = remapVertex(ev_data[old_base + i]); + ee_data[new_base + i] = remapElement(ee_data[old_base + i]); } } - //Update the coordinate positions map - for(auto v : vertex_set.positions()) + auto& coord_data = vcoord_map.data(); + for(IndexType v = 0; v < vertex_size; ++v) { - if(vertex_set.isValidEntry(v)) + if(vertex_data[v] == INVALID_VERTEX) { - const IndexType new_entry_index = vertex_set_map[v]; - vcoord_map[new_entry_index] = vcoord_map[v]; + continue; } + + const IndexType new_v = vertex_set_map[v]; + ve_data[new_v] = remapElement(ve_data[v]); + coord_data[new_v] = coord_data[v]; } - //update the sets vertex_set.reset(v_count); element_set.reset(e_count); diff --git a/src/axom/slam/tests/slam_IA.cpp b/src/axom/slam/tests/slam_IA.cpp index cf0fb4d73f..34fb54eb00 100644 --- a/src/axom/slam/tests/slam_IA.cpp +++ b/src/axom/slam/tests/slam_IA.cpp @@ -951,6 +951,53 @@ TEST(slam_IA, tet_mesh_remove_vert_and_compact) EXPECT_EQ(basic_mesh_data.numVertices() - 1, ia_mesh.getNumberOfValidVertices()); } +TEST(slam_IA, fix_vertex_neighborhood_handles_large_vertex_ids) +{ + const int TDIM = 3; + const int SDIM = 3; + using IAMeshType = slam::IAMesh; + + IAMeshType mesh; + for(int i = 0; i < 9; ++i) + { + mesh.addVertex(PointType(i, 0, 0)); + } + + const IndexType invalid = IAMeshType::INVALID_ELEMENT_INDEX; + const IndexType neighbors[4] = {invalid, invalid, invalid, invalid}; + const IndexType tet0[4] = {1, 2, 3, 8}; + const IndexType tet1[4] = {1, 2, 7, 8}; + const IndexType tet2[4] = {1, 3, 7, 8}; + const IndexType tet3[4] = {2, 3, 7, 8}; + + std::vector star; + star.push_back(mesh.addElement(tet0, neighbors)); + star.push_back(mesh.addElement(tet1, neighbors)); + star.push_back(mesh.addElement(tet2, neighbors)); + star.push_back(mesh.addElement(tet3, neighbors)); + + mesh.fixVertexNeighborhood(8, star); + + EXPECT_TRUE(mesh.isValid()); + EXPECT_TRUE(mesh.isConforming()); + + for(std::size_t i = 0; i < star.size(); ++i) + { + int valid_neighbors = 0; + for(auto nbr : mesh.adjacentElements(star[i])) + { + valid_neighbors += mesh.isValidElement(nbr) ? 1 : 0; + } + EXPECT_EQ(3, valid_neighbors); + + for(std::size_t j = i + 1; j < star.size(); ++j) + { + EXPECT_TRUE(isAdjacent(mesh, star[i], star[j])); + EXPECT_TRUE(isAdjacent(mesh, star[j], star[i])); + } + } +} + //---------------------------------------------------------------------- int main(int argc, char* argv[]) From 8cd44ab66c7f8720b0b18b688a9d1f5a3f70a042 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 14 Apr 2026 14:10:20 -0700 Subject: [PATCH 499/986] In Delaunay triangulation, reuse spaces of deleted elements before pushing to end --- src/axom/quest/Delaunay.hpp | 44 +++++++++++++++++---------- src/axom/slam/mesh_struct/IA.hpp | 11 +++++++ src/axom/slam/mesh_struct/IA_impl.hpp | 43 ++++++++++++++++++++++++++ src/axom/slam/tests/slam_IA.cpp | 33 ++++++++++++++++++++ 4 files changed, 115 insertions(+), 16 deletions(-) diff --git a/src/axom/quest/Delaunay.hpp b/src/axom/quest/Delaunay.hpp index 671f5a2d6b..35fadf6156 100644 --- a/src/axom/quest/Delaunay.hpp +++ b/src/axom/quest/Delaunay.hpp @@ -87,8 +87,8 @@ class Delaunay IAMeshType m_mesh; BoundingBox m_bounding_box; bool m_has_boundary; - int m_num_removed_elements_since_last_compact; InsertionValidationMode m_insertion_validation_mode; + std::vector m_deleted_elements; ElementFinder m_element_finder; IndexType m_next_regrid_vertex_count {0}; @@ -170,11 +170,7 @@ class Delaunay * \brief Default constructor * \note User must call initializeBoundary(BoundingBox) before adding points. */ - Delaunay() - : m_has_boundary(false) - , m_num_removed_elements_since_last_compact(0) - , m_insertion_validation_mode(InsertionValidationMode::None) - { } + Delaunay() : m_has_boundary(false), m_insertion_validation_mode(InsertionValidationMode::None) { } /// \brief Controls the amount of validation performed around each point insertion /// @@ -216,6 +212,10 @@ class Delaunay { m_walk_visited = slam::BitSet(static_cast(expected_elements)); } + if(static_cast(m_deleted_elements.capacity()) < expected_elements / 8) + { + m_deleted_elements.reserve(expected_elements / 8); + } } /** @@ -1530,16 +1530,16 @@ class Delaunay // It may be good to let user have control of this option in the future. constexpr int MIN_REMOVED_ELEMENTS = DIM == 2 ? 512 : 2048; constexpr double REMOVED_ELEMENT_FRACTION = DIM == 2 ? 0.25 : 0.35; - return m_num_removed_elements_since_last_compact > MIN_REMOVED_ELEMENTS && - (m_num_removed_elements_since_last_compact > - REMOVED_ELEMENT_FRACTION * m_mesh.elements().size()); + return static_cast(m_deleted_elements.size()) > MIN_REMOVED_ELEMENTS && + (static_cast(m_deleted_elements.size()) > + REMOVED_ELEMENT_FRACTION * static_cast(m_mesh.elements().size())); } /// \brief Compacts the underlying mesh void compactMesh() { m_mesh.compact(); - m_num_removed_elements_since_last_compact = 0; + m_deleted_elements.clear(); m_element_finder.recomputeGrid(m_mesh, m_bounding_box); if(m_next_regrid_vertex_count > 0) { @@ -1911,16 +1911,17 @@ class Delaunay /** * \brief Remove the elements in the Delaunay cavity */ - void createCavity() + void createCavity(std::vector& deleted_elements) { for(const auto elem : cavity_elems) { m_mesh.removeElement(elem); + deleted_elements.push_back(elem); } } /// \brief Fill in the Delaunay cavity with new elements containing the insertion point - void delaunayBall(IndexType new_pt_i) + void delaunayBall(IndexType new_pt_i, std::vector& deleted_elements) { const int numFaces = static_cast(boundary_facets.size()); const IndexType invalid_neighbor = IAMeshType::ElementAdjacencyRelation::INVALID_INDEX; @@ -1946,7 +1947,17 @@ class Delaunay } neighbors[0] = nID; - IndexType new_el = m_mesh.addElement(vlist, neighbors); + IndexType new_el = invalid_neighbor; + if(!deleted_elements.empty()) + { + new_el = deleted_elements.back(); + deleted_elements.pop_back(); + m_mesh.reuseElement(new_el, vlist, neighbors); + } + else + { + new_el = m_mesh.addElement(vlist, neighbors); + } inserted_elems.push_back(new_el); } @@ -2039,6 +2050,8 @@ void Delaunay::initializeBoundary(const BoundingBox& bb) m_initial_vertices_scratch.reserve(8); m_fallback_vertices_scratch.clear(); m_fallback_vertices_scratch.reserve(QUERY_CANDIDATE_LIMIT); + m_deleted_elements.clear(); + m_deleted_elements.reserve(DIM == 2 ? 128 : 512); if(!m_insertion_helper) { @@ -2089,14 +2102,13 @@ void Delaunay::insertPoint(const PointType& new_pt) axom::utilities::max(m_max_removed_elements, static_cast(insertionHelper.numRemovedElements())); validateCavityBoundary(insertionHelper); - insertionHelper.createCavity(); + insertionHelper.createCavity(m_deleted_elements); IndexType new_pt_i = m_mesh.addVertex(new_pt); - insertionHelper.delaunayBall(new_pt_i); + insertionHelper.delaunayBall(new_pt_i, m_deleted_elements); validateInsertedBall(new_pt_i, insertionHelper); validateInsertionResult(); m_element_finder.updateBin(new_pt, new_pt_i); - m_num_removed_elements_since_last_compact += insertionHelper.numRemovedElements(); if(m_next_regrid_vertex_count > 0 && m_mesh.vertices().size() >= m_next_regrid_vertex_count) { m_element_finder.recomputeGrid(m_mesh, m_bounding_box); diff --git a/src/axom/slam/mesh_struct/IA.hpp b/src/axom/slam/mesh_struct/IA.hpp index fdf3224975..6869289e1a 100644 --- a/src/axom/slam/mesh_struct/IA.hpp +++ b/src/axom/slam/mesh_struct/IA.hpp @@ -344,6 +344,17 @@ class IAMesh */ IndexType addElement(const IndexType* vlist, const IndexType* neighbors); + /** + * \brief Reactivates an invalid element slot with new boundary and adjacency data. + * + * \param element_idx The invalid element slot to reuse. + * \param vlist A pointer to the vertex indices of the recycled element. + * The array size should be at least VERTS_PER_ELEM. + * \param neighbors A pointer to the neighbor indices of the recycled element. + * The array size should be at least VERTS_PER_ELEM. + */ + IndexType reuseElement(IndexType element_idx, const IndexType* vlist, const IndexType* neighbors); + /** * \brief Removes an element from the mesh * diff --git a/src/axom/slam/mesh_struct/IA_impl.hpp b/src/axom/slam/mesh_struct/IA_impl.hpp index 7245d79188..5dbe590bff 100644 --- a/src/axom/slam/mesh_struct/IA_impl.hpp +++ b/src/axom/slam/mesh_struct/IA_impl.hpp @@ -583,6 +583,49 @@ typename IAMesh::IndexType IAMesh::addElement(cons return element_idx; } +template +typename IAMesh::IndexType IAMesh::reuseElement(IndexType element_idx, + const IndexType* vlist, + const IndexType* neighbors) +{ + SLIC_ASSERT_MSG(element_idx >= 0 && element_idx < element_set.size(), + "Trying to reuse an out-of-range element index:" << element_idx); + SLIC_ASSERT_MSG(!element_set.isValidEntry(element_idx), + "Trying to reuse an element slot that is already valid:" << element_idx); + + for(int i = 0; i < VERTS_PER_ELEM; ++i) + { + SLIC_ASSERT_MSG(vertex_set.isValidEntry(vlist[i]), + "Trying to reuse an element with invalid vertex index:" << vlist[i]); + } + + element_set[element_idx] = element_idx; + + auto bdry = ev_rel[element_idx]; + for(int i = 0; i < VERTS_PER_ELEM; ++i) + { + bdry[i] = vlist[i]; + } + + auto adj = ee_rel[element_idx]; + for(int i = 0; i < VERTS_PER_ELEM; ++i) + { + adj[i] = neighbors[i]; + } + + for(int i = 0; i < VERTS_PER_ELEM; ++i) + { + const IndexType v = vlist[i]; + IndexType& cbdry = coboundaryElement(v); + if(!element_set.isValidEntry(cbdry)) + { + cbdry = element_idx; + } + } + + return element_idx; +} + template void IAMesh::fixVertexNeighborhood(IndexType vertex_idx, const std::vector& new_elements) diff --git a/src/axom/slam/tests/slam_IA.cpp b/src/axom/slam/tests/slam_IA.cpp index 34fb54eb00..243a3011f1 100644 --- a/src/axom/slam/tests/slam_IA.cpp +++ b/src/axom/slam/tests/slam_IA.cpp @@ -497,6 +497,39 @@ TEST(slam_IA, tri_mesh_remove_elem_and_compact) EXPECT_EQ(basic_mesh_data.numVertices(), ia_mesh.getNumberOfValidVertices()); } +TEST(slam_IA, tri_mesh_reuse_deleted_element_slot) +{ + constexpr int TDIM = 2; + constexpr int SDIM = 3; + using IAMeshType = slam::IAMesh; + + BasicTriMeshData basic_mesh_data; + IAMeshType ia_mesh(basic_mesh_data.points, basic_mesh_data.elem); + + const IndexType reused_idx = 4; + IndexType tri[3] {0, 3, 7}; + const IndexType invalid = IAMeshType::ElementAdjacencyRelation::INVALID_INDEX; + IndexType neighbors[3] {invalid, invalid, invalid}; + + ia_mesh.removeElement(reused_idx); + ASSERT_FALSE(ia_mesh.isValidElement(reused_idx)); + + EXPECT_EQ(reused_idx, ia_mesh.reuseElement(reused_idx, tri, neighbors)); + EXPECT_TRUE(ia_mesh.isValidElement(reused_idx)); + + const auto bdry = ia_mesh.boundaryVertices(reused_idx); + EXPECT_EQ(tri[0], bdry[0]); + EXPECT_EQ(tri[1], bdry[1]); + EXPECT_EQ(tri[2], bdry[2]); + + const auto adj = ia_mesh.adjacentElements(reused_idx); + EXPECT_EQ(invalid, adj[0]); + EXPECT_EQ(invalid, adj[1]); + EXPECT_EQ(invalid, adj[2]); + + EXPECT_TRUE(ia_mesh.isValid()); +} + TEST(slam_IA, tri_mesh_remove_vert_and_compact) { SLIC_INFO("Testing removing a vertex and compacting a triangle mesh..."); From 9faec0bbc452a1df0cd56a731f545c661ce3f6a4 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 14 Apr 2026 14:58:22 -0700 Subject: [PATCH 500/986] Optimizes checks for valid elements in Delaunay Reduces redundant checks in hot-path --- src/axom/quest/Delaunay.hpp | 93 ++++++++++++++++++++++++------------- 1 file changed, 60 insertions(+), 33 deletions(-) diff --git a/src/axom/quest/Delaunay.hpp b/src/axom/quest/Delaunay.hpp index 35fadf6156..47a21877fd 100644 --- a/src/axom/quest/Delaunay.hpp +++ b/src/axom/quest/Delaunay.hpp @@ -84,11 +84,41 @@ class Delaunay struct ElementFinder; struct InsertionHelper; + /** + * \brief Lightweight LIFO pool of invalid simplex slots that can be reused + * during cavity retriangulation before growing the mesh element array. + */ + struct RecycledElementPool + { + void reserve(IndexType count) { m_slots.reserve(count); } + + void clear() { m_slots.clear(); } + + void release(IndexType element_idx) { m_slots.push_back(element_idx); } + + bool empty() const { return m_slots.empty(); } + + IndexType size() const { return m_slots.size(); } + + IndexType capacity() const { return m_slots.capacity(); } + + IndexType acquire() + { + SLIC_ASSERT(!m_slots.empty()); + const IndexType element_idx = m_slots.back(); + m_slots.resize(m_slots.size() - 1); + return element_idx; + } + + private: + axom::Array m_slots; + }; + IAMeshType m_mesh; BoundingBox m_bounding_box; bool m_has_boundary; InsertionValidationMode m_insertion_validation_mode; - std::vector m_deleted_elements; + RecycledElementPool m_deleted_elements; ElementFinder m_element_finder; IndexType m_next_regrid_vertex_count {0}; @@ -741,6 +771,22 @@ class Delaunay element_idx); valid = false; } + else + { + const auto verts = m_mesh.boundaryVertices(element_idx); + for(auto idx : verts.positions()) + { + if(!m_mesh.isValidVertex(verts[idx])) + { + fmt::format_to(std::back_inserter(out), + "\n\tContaining element {} references invalid vertex {}", + element_idx, + verts[idx]); + valid = false; + break; + } + } + } if(!isPointInsideForLocation(element_idx, query_pt, bary_coord)) { @@ -839,24 +885,14 @@ class Delaunay } public: - /// \brief Returns true when an element and all of its vertices are valid for point-location predicates + /// \brief Returns true when an element slot is active and can participate in point-location + /// + /// Point-location only needs to reject tombstones here. During incremental + /// insertion all active simplices retain valid vertices, and `removeBoundary()` + /// compacts before post-build query use. bool isSearchableElement(IndexType element_idx) const { - if(!m_mesh.isValidElement(element_idx)) - { - return false; - } - - const auto verts = m_mesh.boundaryVertices(element_idx); - for(auto idx : verts.positions()) - { - if(!m_mesh.isValidVertex(verts[idx])) - { - return false; - } - } - - return true; + return m_mesh.isValidElement(element_idx); } enum class PointLocationStatus @@ -1172,7 +1208,7 @@ class Delaunay IndexType start_element, std::vector* visited_elements_out = nullptr) const { - if(!isSearchableElement(start_element)) + if(!m_mesh.isValidElement(start_element)) { return {}; } @@ -1262,12 +1298,6 @@ class Delaunay } element_i = next_element; - if(!isSearchableElement(element_i)) - { - recordWalk(PointLocationStatus::Failed); - clearVisitedBits(); - return {}; - } } } @@ -1845,10 +1875,8 @@ class Delaunay // insert directly onto existing edges/faces. for(const IndexType element_i : seed_elements) { - if(m_mesh.isValidElement(element_i)) - { - addCavityElement(element_i); - } + SLIC_ASSERT(m_mesh.isValidElement(element_i)); + addCavityElement(element_i); } while(!m_stack.empty()) @@ -1911,17 +1939,17 @@ class Delaunay /** * \brief Remove the elements in the Delaunay cavity */ - void createCavity(std::vector& deleted_elements) + void createCavity(RecycledElementPool& deleted_elements) { for(const auto elem : cavity_elems) { m_mesh.removeElement(elem); - deleted_elements.push_back(elem); + deleted_elements.release(elem); } } /// \brief Fill in the Delaunay cavity with new elements containing the insertion point - void delaunayBall(IndexType new_pt_i, std::vector& deleted_elements) + void delaunayBall(IndexType new_pt_i, RecycledElementPool& deleted_elements) { const int numFaces = static_cast(boundary_facets.size()); const IndexType invalid_neighbor = IAMeshType::ElementAdjacencyRelation::INVALID_INDEX; @@ -1950,8 +1978,7 @@ class Delaunay IndexType new_el = invalid_neighbor; if(!deleted_elements.empty()) { - new_el = deleted_elements.back(); - deleted_elements.pop_back(); + new_el = deleted_elements.acquire(); m_mesh.reuseElement(new_el, vlist, neighbors); } else From 625f517648f70a36cba046ccb1ec080a4007b308 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 14 Apr 2026 20:10:12 -0700 Subject: [PATCH 501/986] Fixes predicates in 2D and 3D Delaunay triangulation Previously, there were Delaunay violations in large meshes (w/ >1,000,000 verts) --- src/axom/quest/Delaunay.hpp | 425 +++++++++++++++++- .../quest/examples/delaunay_triangulation.cpp | 69 ++- 2 files changed, 463 insertions(+), 31 deletions(-) diff --git a/src/axom/quest/Delaunay.hpp b/src/axom/quest/Delaunay.hpp index 47a21877fd..645142eefb 100644 --- a/src/axom/quest/Delaunay.hpp +++ b/src/axom/quest/Delaunay.hpp @@ -66,6 +66,8 @@ class Delaunay { /// No additional insertion-time validation beyond existing asserts. None, + /// Checks only insertion-local seed/cavity/ball invariants. + Local, /// Checks local cavity/ball invariants and that the resulting IA mesh is conforming. /// Intended for debugging and unit tests. ConformingMesh, @@ -858,6 +860,11 @@ class Delaunay return; } + if(m_insertion_validation_mode == InsertionValidationMode::Local) + { + return; + } + // Note: These checks are intentionally global and can be expensive. They // are only enabled when the caller opts into insertion validation. if(!m_mesh.isValid(false)) @@ -1000,6 +1007,91 @@ class Delaunay return value > tolerance ? 1 : (value < -tolerance ? -1 : 0); } + template + static double getPointMagnitudeScale(const std::array& pts) + { + double max_abs_coord = 1.; + for(const auto& pt : pts) + { + for(int dim = 0; dim < DIM; ++dim) + { + max_abs_coord = axom::utilities::max(max_abs_coord, axom::utilities::abs(pt[dim])); + } + } + + return max_abs_coord; + } + + static double orientationTolerance(const std::array& pts) + { + const double scale = getPointMagnitudeScale(pts); + if constexpr(DIM == 2) + { + return 64. * std::numeric_limits::epsilon() * scale * scale; + } + else + { + return 64. * std::numeric_limits::epsilon() * scale * scale * scale; + } + } + + static double determinant3(const PointType& p0, const PointType& p1, const PointType& p2) + { + return axom::numerics::determinant(p0[0], p0[1], p0[2], p1[0], p1[1], p1[2], p2[0], p2[1], p2[2]); + } + + static double orientationDeterminant(const std::array& pts) + { + return axom::numerics::determinant(pts[0][0], + pts[0][1], + pts[0][2], + 1., + pts[1][0], + pts[1][1], + pts[1][2], + 1., + pts[2][0], + pts[2][1], + pts[2][2], + 1., + pts[3][0], + pts[3][1], + pts[3][2], + 1.); + } + + static int symbolicOrientationSign(const std::array& pts, + const std::array& ranks) + { + const double det = orientationDeterminant(pts); + const int det_sign = signWithTolerance(det, orientationTolerance(pts)); + if(det_sign != 0) + { + return det_sign; + } + + const std::array cofactors {-determinant3(pts[1], pts[2], pts[3]), + determinant3(pts[0], pts[2], pts[3]), + -determinant3(pts[0], pts[1], pts[3]), + determinant3(pts[0], pts[1], pts[2])}; + + std::array order {{0, 1, 2, 3}}; + std::sort(order.begin(), order.end(), [&](int lhs, int rhs) { return ranks[lhs] < ranks[rhs]; }); + + const double cofactor_tol = 64. * std::numeric_limits::epsilon() * + axom::utilities::max(1., orientationTolerance(pts)); + for(const int row : order) + { + const int sign = signWithTolerance(cofactors[row], cofactor_tol); + if(sign != 0) + { + return sign; + } + } + + return 0; + } + //----------------------------------------------------------------------------- // In-sphere predicate helpers // @@ -1010,8 +1102,11 @@ class Delaunay //----------------------------------------------------------------------------- static double inSphereDeterminantTolerance(double scale) { - // Determinant magnitude scales like length^(DIM+2): L^4 in 2D, L^5 in 3D. - const double k = 128.; + // The in-sphere determinant is formed from coordinate differences and + // squared norms, so near-cospherical slivers can lose precision based on + // the absolute coordinate magnitudes before translation, not just on the + // local edge lengths. + const double k = (DIM == 2) ? 128. : 512.; if constexpr(DIM == 2) { return k * std::numeric_limits::epsilon() * scale * scale * scale * scale; @@ -1022,6 +1117,33 @@ class Delaunay } } + static double orientationDeterminantTolerance(double scale) + { + const double k = (DIM == 2) ? 128. : 512.; + if constexpr(DIM == 2) + { + return k * std::numeric_limits::epsilon() * scale * scale; + } + else + { + return k * std::numeric_limits::epsilon() * scale * scale * scale; + } + } + + template + static double sphereSignedDistanceTolerance(const SphereType& sphere, const PointType& x) + { + const auto& center = sphere.getCenter(); + double scale = axom::utilities::max(1., sphere.getRadius()); + for(int dim = 0; dim < DIM; ++dim) + { + scale = axom::utilities::max(scale, axom::utilities::abs(center[dim])); + scale = axom::utilities::max(scale, axom::utilities::abs(x[dim])); + } + + return 256. * std::numeric_limits::epsilon() * scale; + } + static int inSphereOrientationOnMesh(const IAMeshType& mesh, const PointType& q, IndexType element_idx) { const auto verts = mesh.boundaryVertices(element_idx); @@ -1032,15 +1154,16 @@ class Delaunay const PointType& p1 = mesh.getVertexPosition(verts[1]); const PointType& p2 = mesh.getVertexPosition(verts[2]); - const auto ba = p1 - p0; - const auto ca = p2 - p0; - const auto qa = q - p0; - const double scale = axom::utilities::max( - 1., - axom::utilities::max(ba.norm(), axom::utilities::max(ca.norm(), qa.norm()))); - const double eps = inSphereDeterminantTolerance(scale); + const ElementType tri(p0, p1, p2); + const auto sphere = tri.circumsphere(); + const double signed_distance = sphere.computeSignedDistance(q); + const double tol = sphereSignedDistanceTolerance(sphere, q); + if(axom::utilities::abs(signed_distance) <= tol) + { + return primal::ON_BOUNDARY; + } - return primal::robust::in_sphere(q, p0, p1, p2, eps); + return signed_distance < 0. ? primal::ON_NEGATIVE_SIDE : primal::ON_POSITIVE_SIDE; } else { @@ -1049,18 +1172,54 @@ class Delaunay const PointType& p2 = mesh.getVertexPosition(verts[2]); const PointType& p3 = mesh.getVertexPosition(verts[3]); - const auto ba = p1 - p0; - const auto ca = p2 - p0; - const auto da = p3 - p0; - const auto qa = q - p0; - const double scale = axom::utilities::max( - 1., - axom::utilities::max( - ba.norm(), - axom::utilities::max(ca.norm(), axom::utilities::max(da.norm(), qa.norm())))); - const double eps = inSphereDeterminantTolerance(scale); + const ElementType tet(p0, p1, p2, p3); + const auto sphere = tet.circumsphere(); + const double signed_distance = sphere.computeSignedDistance(q); + const double tol = sphereSignedDistanceTolerance(sphere, q); + if(axom::utilities::abs(signed_distance) <= tol) + { + return primal::ON_BOUNDARY; + } + + return signed_distance < 0. ? primal::ON_NEGATIVE_SIDE : primal::ON_POSITIVE_SIDE; + } + } + + static double inSphereDeterminantOnMesh(const IAMeshType& mesh, + const PointType& q, + IndexType element_idx) + { + const auto verts = mesh.boundaryVertices(element_idx); + + if constexpr(DIM == 2) + { + return primal::detail::in_sphere_determinant(q, + mesh.getVertexPosition(verts[0]), + mesh.getVertexPosition(verts[1]), + mesh.getVertexPosition(verts[2])); + } + else + { + return primal::detail::in_sphere_determinant(q, + mesh.getVertexPosition(verts[0]), + mesh.getVertexPosition(verts[1]), + mesh.getVertexPosition(verts[2]), + mesh.getVertexPosition(verts[3])); + } + } - return primal::robust::in_sphere(q, p0, p1, p2, p3, eps); + static const char* orientationResultName(int result) + { + switch(result) + { + case primal::ON_NEGATIVE_SIDE: + return "inside"; + case primal::ON_BOUNDARY: + return "boundary"; + case primal::ON_POSITIVE_SIDE: + return "outside"; + default: + return "unknown"; } } @@ -1832,6 +1991,8 @@ class Delaunay boundary_facets.clear(); cavity_elems.clear(); inserted_elems.clear(); + containing_element = INVALID_INDEX; + seed_elements_debug.clear(); m_stack.clear(); } @@ -1953,6 +2114,7 @@ class Delaunay { const int numFaces = static_cast(boundary_facets.size()); const IndexType invalid_neighbor = IAMeshType::ElementAdjacencyRelation::INVALID_INDEX; + const PointType& new_pt = m_mesh.getVertexPosition(new_pt_i); IndexType vlist[VERT_PER_ELEMENT] {}; IndexType neighbors[VERT_PER_ELEMENT] {}; @@ -1965,6 +2127,28 @@ class Delaunay } vlist[VERTS_PER_FACET] = new_pt_i; + // Preserve positive simplex orientation regardless of the boundary-face + // ordering used to describe the cavity facet. + if constexpr(DIM == 2) + { + const PointType& p0 = m_mesh.getVertexPosition(vlist[0]); + const PointType& p1 = m_mesh.getVertexPosition(vlist[1]); + if(primal::detail::orientation_determinant(p0, p1, new_pt) < 0.) + { + axom::utilities::swap(vlist[0], vlist[1]); + } + } + else + { + const PointType& p0 = m_mesh.getVertexPosition(vlist[0]); + const PointType& p1 = m_mesh.getVertexPosition(vlist[1]); + const PointType& p2 = m_mesh.getVertexPosition(vlist[2]); + if(primal::detail::orientation_determinant(p0, p1, p2, new_pt) < 0.) + { + axom::utilities::swap(vlist[1], vlist[2]); + } + } + // Face 0 is the cavity boundary face opposite the inserted point. // The remaining faces stay invalid until fixVertexNeighborhood() stitches // the new ball together around the inserted vertex. @@ -2010,6 +2194,9 @@ class Delaunay std::vector boundary_facets; std::vector cavity_elems; std::vector inserted_elems; + IndexType containing_element {INVALID_INDEX}; + BaryCoordType containing_bary; + IndexArray seed_elements_debug; IndexArray m_stack; @@ -2122,6 +2309,9 @@ void Delaunay::insertPoint(const PointType& new_pt) auto& insertionHelper = *m_insertion_helper; insertionHelper.reset(); + insertionHelper.containing_element = element_i; + insertionHelper.containing_bary = bary_coord; + insertionHelper.seed_elements_debug = seed_elements; insertionHelper.findCavityElements(new_pt, seed_elements); ++m_num_insertions; m_total_removed_elements += static_cast(insertionHelper.numRemovedElements()); @@ -2245,7 +2435,9 @@ void Delaunay::validateCavityBoundary(const InsertionHelper& insertion_help } } - SLIC_ERROR_IF(!valid, "Delaunay cavity validation failed:" << fmt::to_string(out)); + SLIC_ERROR_IF(!valid, + "Delaunay cavity validation failed after insertion " << m_num_insertions << ":" + << fmt::to_string(out)); } template @@ -2272,6 +2464,23 @@ void Delaunay::validateInsertedBall(IndexType new_pt_i, bool valid = true; std::map cavity_boundary; std::map> inserted_faces; + auto findOppositeVertex = [&](IndexType element_idx, const FacetKey& facet_key) { + const auto verts = m_mesh.boundaryVertices(element_idx); + for(int i = 0; i < VERT_PER_ELEMENT; ++i) + { + bool on_face = false; + for(int j = 0; j < VERTS_PER_FACET; ++j) + { + on_face |= (verts[i] == facet_key[j]); + } + if(!on_face) + { + return verts[i]; + } + } + + return INVALID_INDEX; + }; if(insertion_helper.inserted_elems.size() != insertion_helper.boundary_facets.size()) { @@ -2283,6 +2492,20 @@ void Delaunay::validateInsertedBall(IndexType new_pt_i, valid = false; } + if(insertion_helper.containing_element != INVALID_INDEX) + { + fmt::format_to(std::back_inserter(out), + "\n\tContaining element {} barycentric coordinates {}", + insertion_helper.containing_element, + insertion_helper.containing_bary); + if(!insertion_helper.seed_elements_debug.empty()) + { + fmt::format_to(std::back_inserter(out), + "\n\tInsertion seeds: [{}]", + fmt::join(insertion_helper.seed_elements_debug, ", ")); + } + } + for(const auto& facet : insertion_helper.boundary_facets) { cavity_boundary.insert({makeSortedFaceKey(facet.vertices), {facet.neighbor, false}}); @@ -2353,6 +2576,88 @@ void Delaunay::validateInsertedBall(IndexType new_pt_i, valid = false; } + if(m_mesh.isValidElement(cavity_it->second.neighbor_idx)) + { + const IndexType inserted_opposite = + findOppositeVertex(records.front().element_idx, face_entry.first); + const IndexType neighbor_opposite = + findOppositeVertex(cavity_it->second.neighbor_idx, face_entry.first); + + if(inserted_opposite == INVALID_INDEX || neighbor_opposite == INVALID_INDEX) + { + fmt::format_to( + std::back_inserter(out), + "\n\tCould not identify opposite vertices across inserted boundary face {}", + facetKeyString(face_entry.first)); + valid = false; + } + else + { + const PointType& inserted_point = m_mesh.getVertexPosition(inserted_opposite); + const PointType& neighbor_point = m_mesh.getVertexPosition(neighbor_opposite); + + if(isPointInSphereOnMesh(m_mesh, + inserted_point, + cavity_it->second.neighbor_idx, + /*includeBoundary=*/false)) + { + const int query_in_neighbor = + inSphereOrientationOnMesh(m_mesh, + m_mesh.getVertexPosition(new_pt_i), + cavity_it->second.neighbor_idx); + const int inserted_in_neighbor = + inSphereOrientationOnMesh(m_mesh, inserted_point, cavity_it->second.neighbor_idx); + fmt::format_to( + std::back_inserter(out), + "\n\tInserted boundary face {} leaves new vertex {} inside neighbor {} " + "circumsphere (query {}={}, det={:.17g}; opposite {}={}, det={:.17g})", + facetKeyString(face_entry.first), + inserted_opposite, + cavity_it->second.neighbor_idx, + new_pt_i, + orientationResultName(query_in_neighbor), + inSphereDeterminantOnMesh(m_mesh, + m_mesh.getVertexPosition(new_pt_i), + cavity_it->second.neighbor_idx), + inserted_opposite, + orientationResultName(inserted_in_neighbor), + inSphereDeterminantOnMesh(m_mesh, inserted_point, cavity_it->second.neighbor_idx)); + valid = false; + } + + if(isPointInSphereOnMesh(m_mesh, + neighbor_point, + records.front().element_idx, + /*includeBoundary=*/false)) + { + const int query_in_neighbor = + inSphereOrientationOnMesh(m_mesh, + m_mesh.getVertexPosition(new_pt_i), + cavity_it->second.neighbor_idx); + const int neighbor_in_inserted = + inSphereOrientationOnMesh(m_mesh, neighbor_point, records.front().element_idx); + fmt::format_to( + std::back_inserter(out), + "\n\tInserted boundary face {} leaves neighbor vertex {} inside new element {} " + "circumsphere (query {} in neighbor {} is {}, det={:.17g}; opposite {} in new " + "element is {}, det={:.17g})", + facetKeyString(face_entry.first), + neighbor_opposite, + records.front().element_idx, + new_pt_i, + cavity_it->second.neighbor_idx, + orientationResultName(query_in_neighbor), + inSphereDeterminantOnMesh(m_mesh, + m_mesh.getVertexPosition(new_pt_i), + cavity_it->second.neighbor_idx), + neighbor_opposite, + orientationResultName(neighbor_in_inserted), + inSphereDeterminantOnMesh(m_mesh, neighbor_point, records.front().element_idx)); + valid = false; + } + } + } + cavity_it->second.matched = true; } else if(records.size() != 2 || records[0].neighbor_idx != records[1].element_idx || @@ -2363,6 +2668,78 @@ void Delaunay::validateInsertedBall(IndexType new_pt_i, facetKeyString(face_entry.first)); valid = false; } + else + { + const IndexType lhs_opposite = findOppositeVertex(records[0].element_idx, face_entry.first); + const IndexType rhs_opposite = findOppositeVertex(records[1].element_idx, face_entry.first); + + if(lhs_opposite == INVALID_INDEX || rhs_opposite == INVALID_INDEX) + { + fmt::format_to(std::back_inserter(out), + "\n\tCould not identify opposite vertices across inserted internal face {}", + facetKeyString(face_entry.first)); + valid = false; + } + else + { + const PointType& lhs_point = m_mesh.getVertexPosition(lhs_opposite); + const PointType& rhs_point = m_mesh.getVertexPosition(rhs_opposite); + + if(isPointInSphereOnMesh(m_mesh, + lhs_point, + records[1].element_idx, + /*includeBoundary=*/false)) + { + const int query_in_rhs = inSphereOrientationOnMesh(m_mesh, + m_mesh.getVertexPosition(new_pt_i), + records[1].element_idx); + const int lhs_in_rhs = inSphereOrientationOnMesh(m_mesh, lhs_point, records[1].element_idx); + fmt::format_to( + std::back_inserter(out), + "\n\tInserted internal face {} leaves vertex {} inside adjacent element {} " + "circumsphere (query {}={}, det={:.17g}; opposite {}={}, det={:.17g})", + facetKeyString(face_entry.first), + lhs_opposite, + records[1].element_idx, + new_pt_i, + orientationResultName(query_in_rhs), + inSphereDeterminantOnMesh(m_mesh, + m_mesh.getVertexPosition(new_pt_i), + records[1].element_idx), + lhs_opposite, + orientationResultName(lhs_in_rhs), + inSphereDeterminantOnMesh(m_mesh, lhs_point, records[1].element_idx)); + valid = false; + } + + if(isPointInSphereOnMesh(m_mesh, + rhs_point, + records[0].element_idx, + /*includeBoundary=*/false)) + { + const int query_in_lhs = inSphereOrientationOnMesh(m_mesh, + m_mesh.getVertexPosition(new_pt_i), + records[0].element_idx); + const int rhs_in_lhs = inSphereOrientationOnMesh(m_mesh, rhs_point, records[0].element_idx); + fmt::format_to( + std::back_inserter(out), + "\n\tInserted internal face {} leaves vertex {} inside adjacent element {} " + "circumsphere (query {}={}, det={:.17g}; opposite {}={}, det={:.17g})", + facetKeyString(face_entry.first), + rhs_opposite, + records[0].element_idx, + new_pt_i, + orientationResultName(query_in_lhs), + inSphereDeterminantOnMesh(m_mesh, + m_mesh.getVertexPosition(new_pt_i), + records[0].element_idx), + rhs_opposite, + orientationResultName(rhs_in_lhs), + inSphereDeterminantOnMesh(m_mesh, rhs_point, records[0].element_idx)); + valid = false; + } + } + } } for(const auto& facet_entry : cavity_boundary) @@ -2376,7 +2753,9 @@ void Delaunay::validateInsertedBall(IndexType new_pt_i, } } - SLIC_ERROR_IF(!valid, "Delaunay ball validation failed:" << fmt::to_string(out)); + SLIC_ERROR_IF(!valid, + "Delaunay ball validation failed after insertion " << m_num_insertions << ":" + << fmt::to_string(out)); } //-------------------------------------------------------------------------------- diff --git a/src/axom/quest/examples/delaunay_triangulation.cpp b/src/axom/quest/examples/delaunay_triangulation.cpp index 02ef08aa16..7476a95d07 100644 --- a/src/axom/quest/examples/delaunay_triangulation.cpp +++ b/src/axom/quest/examples/delaunay_triangulation.cpp @@ -28,6 +28,11 @@ struct Input int numRandPoints {20}; int numOutputSteps {0}; int dimension {2}; + int randomSeed {-1}; + int validateAfter {-1}; + bool validateLocal {false}; + bool validateInsertions {false}; + bool validateFull {false}; std::vector boundsMin; std::vector boundsMax; @@ -56,6 +61,23 @@ struct Input ->description("The VTK output file") ->capture_default_str(); + app.add_option("--seed", randomSeed) + ->description("Optional deterministic seed for point generation"); + + app.add_flag("--validate-insertions", validateInsertions) + ->description("Validate mesh conformity after each insertion"); + + app.add_flag("--validate-full", validateFull) + ->description( + "Validate conformity and the empty-circumsphere condition after each insertion"); + + app.add_flag("--validate-local", validateLocal) + ->description( + "Validate only insertion-local cavity and ball invariants after each insertion"); + + app.add_option("--validate-after", validateAfter) + ->description("Enable insertion validation only after the given insertion index"); + // Optional bounding box for query region auto* minbb = app.add_option("--min", boundsMin) ->description("Min bounds for query box (x,y[,z])") @@ -91,7 +113,8 @@ struct Input } } - SLIC_INFO(axom::fmt::format(R"(Using parameter values: + SLIC_INFO(axom::fmt::format( + R"(Using parameter values: {{ dimension: {} nrandpt: {} @@ -99,13 +122,19 @@ struct Input bounding box max: {{{}}} outfile = '{}' intermediate output steps: {} + random seed: {} + insertion validation: {} + validate after insertion: {} }})", - dimension, - numRandPoints, - axom::fmt::join(boundsMin, ", "), - axom::fmt::join(boundsMax, ", "), - outputVTKFile, - numOutputSteps)); + dimension, + numRandPoints, + axom::fmt::join(boundsMin, ", "), + axom::fmt::join(boundsMax, ", "), + outputVTKFile, + numOutputSteps, + randomSeed >= 0 ? std::to_string(randomSeed) : std::string("none"), + validateFull ? "full" : (validateInsertions ? "conforming" : (validateLocal ? "local" : "none")), + validateAfter >= 0 ? std::to_string(validateAfter) : std::string("start"))); } }; @@ -136,14 +165,37 @@ void run_delaunay(const Input& params) Delaunay dt; dt.initializeBoundary(bbox); dt.reserveForPointCount(numPoints); + const auto validationMode = params.validateFull + ? Delaunay::InsertionValidationMode::Full + : (params.validateInsertions ? Delaunay::InsertionValidationMode::ConformingMesh + : (params.validateLocal ? Delaunay::InsertionValidationMode::Local + : Delaunay::InsertionValidationMode::None)); + if(validationMode != Delaunay::InsertionValidationMode::None && params.validateAfter < 0) + { + dt.setInsertionValidationMode(validationMode); + } // Incrementally insert random points within bounding box for(int i = 0; i < numPoints; ++i) { + if(validationMode != Delaunay::InsertionValidationMode::None && i == params.validateAfter) + { + dt.setInsertionValidationMode(validationMode); + } + PointType new_pt; for(int d = 0; d < DIM; ++d) { - new_pt[d] = random_real(bbox.getMin()[d], bbox.getMax()[d]); + if(params.randomSeed >= 0) + { + const unsigned int seed = static_cast(params.randomSeed) + + 747796405u * static_cast(DIM * i + d + 1); + new_pt[d] = random_real(bbox.getMin()[d], bbox.getMax()[d], seed); + } + else + { + new_pt[d] = random_real(bbox.getMin()[d], bbox.getMax()[d]); + } } // Insert the point into the triangulation @@ -177,6 +229,7 @@ void run_delaunay(const Input& params) timer.reset(); timer.start(); dt.getMeshData()->isValid(true); + dt.isConforming(true); dt.isValid(true); timer.stop(); SLIC_INFO(axom::fmt::format("Validation took {} seconds", timer.elapsedTimeInSec())); From f380aa167eb434c35240aaa1689dcbb7ef1758c5 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 14 Apr 2026 21:22:34 -0700 Subject: [PATCH 502/986] Adds regression tests for 2D and 3D invalid Delaunay configurations --- src/axom/quest/tests/quest_delaunay.cpp | 88 +++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/src/axom/quest/tests/quest_delaunay.cpp b/src/axom/quest/tests/quest_delaunay.cpp index 115adf6c83..0010092572 100644 --- a/src/axom/quest/tests/quest_delaunay.cpp +++ b/src/axom/quest/tests/quest_delaunay.cpp @@ -57,8 +57,96 @@ void expectConformingMesh(const DelaunayType& dt) EXPECT_TRUE(dt.isConforming(true)); } +template +void expectValidDelaunayWithBoundary(const DelaunayType& dt) +{ + EXPECT_TRUE(dt.getMeshData()->isValid(true)); + EXPECT_TRUE(dt.getMeshData()->isConforming(true)); + EXPECT_TRUE(dt.isConforming(true)); + EXPECT_TRUE(dt.isValid(true)); +} + } // namespace +TEST(quest_delaunay, local_bad_geometry_pool_2d) +{ + using PointType = typename DelaunayType<2>::PointType; + using BoundingBox = typename DelaunayType<2>::BoundingBox; + using ValidationMode = typename DelaunayType<2>::InsertionValidationMode; + + const std::vector points { + PointType {0.72835559683934026, 0.82937230204996126}, + PointType {0.62600920572645069, 0.84649465468911222}, + PointType {0.057066612786023117, 0.98518528899105051}, + PointType {0.50420652587117898, 0.88131216460192052}, + PointType {0.64623860530040367, 0.84784930386894053}, + PointType {0.63047324496924584, 0.8517482773644065}, + PointType {0.61321189052614333, 0.8508361023097063}, + PointType {0.6153686267133367, 0.85060225303567505}, + PointType {0.57633479792557119, 0.86309485101808103}, + PointType {0.5734747290517247, 0.85999079345999363}, + PointType {0.59008692995299927, 0.85528988587964816}, + PointType {0.59200361573061988, 0.86005025488638098}, + PointType {0.60412589378177151, 0.85608666941414724}, + PointType {0.59008239905455229, 0.86082490771226516}, + PointType {0.59108093308168341, 0.85843584821320762}, + PointType {0.63956447880378053, 0.849279316704322}, + PointType {0.55233739946809535, 0.86640226672486664}, + }; + + DelaunayType<2> dt; + dt.initializeBoundary(BoundingBox(PointType {0., 0.}, PointType {1., 1.})); + dt.setInsertionValidationMode(ValidationMode::Local); + insertPoints(dt, std::vector(points.begin(), points.end() - 1)); + expectValidDelaunayWithBoundary(dt); + + dt.insertPoint(points.back()); + expectValidDelaunayWithBoundary(dt); +} + +TEST(quest_delaunay, local_bad_geometry_pool_3d) +{ + using PointType = typename DelaunayType<3>::PointType; + using BoundingBox = typename DelaunayType<3>::BoundingBox; + + const std::vector points { + PointType {0.51599885628789743, 0.75470155608398004, 0.40855633126704233}, + PointType {0.50343513905278869, 0.76256644865428691, 0.41550002353843529}, + PointType {0.52181994461717562, 0.75507132048650449, 0.40434771160709904}, + PointType {0.51222305794382439, 0.76931259688762987, 0.41173021886913774}, + PointType {0.51893390889491309, 0.7608695730433962, 0.39023418626911949}, + PointType {0.49870963994503914, 0.73975835990074734, 0.40518385970598131}, + PointType {0.52105735259915775, 0.77213020101602725, 0.40722478047503557}, + PointType {0.49002686184837962, 0.75462235742107142, 0.41591466571185198}, + PointType {0.50501491031893275, 0.76598310556724625, 0.39817919534292223}, + PointType {0.48943926088386502, 0.75900280017153021, 0.40203090542081277}, + PointType {0.5113764391413268, 0.76710422391570976, 0.40773506111442864}, + PointType {0.51178826597709415, 0.77818416231550269, 0.3925790516462227}, + PointType {0.52301040315781211, 0.77096219982230552, 0.40733482938935728}, + PointType {0.53346303899079406, 0.77993659887621847, 0.39369144158113362}, + PointType {0.50029750641338455, 0.76954990769049825, 0.39799982472078405}, + PointType {0.52770121131451853, 0.76483515658193735, 0.40353420628419612}, + PointType {0.50952730554172287, 0.74599842639434555, 0.41708835434555841}, + PointType {0.52956607163853775, 0.75418313145478433, 0.40980177397908779}, + PointType {0.51225516061546172, 0.75892162524570184, 0.38822059123401836}, + PointType {0.51473015232418118, 0.74851193285704143, 0.39787104734305007}, + PointType {0.51352196009694051, 0.76305382863756566, 0.39295743334708788}, + PointType {0.51822520982029341, 0.76176127807719429, 0.42723370773918606}, + PointType {0.51440345815174604, 0.7705063813945725, 0.41385100134174907}, + PointType {0.50863912619096874, 0.76214178681356526, 0.39419055286657634}, + PointType {0.51550017071903309, 0.75494779413607704, 0.39942038195430957}, + PointType {0.51299271231867682, 0.76122513269993519, 0.4067726415350541}, + }; + + DelaunayType<3> dt; + dt.initializeBoundary(BoundingBox(PointType {0., 0., 0.}, PointType {1., 1., 1.})); + insertPoints(dt, std::vector(points.begin(), points.end() - 1)); + expectValidDelaunayWithBoundary(dt); + + dt.insertPoint(points.back()); + expectValidDelaunayWithBoundary(dt); +} + TEST(quest_delaunay, cocircular_square_2d) { using PointType = typename DelaunayType<2>::PointType; From 8ee6fa7c21120148bc2b4dd5d582d04f9a6df5f3 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 14 Apr 2026 21:23:17 -0700 Subject: [PATCH 503/986] Fixes stress_nearly_coplanar_insertions_3d test --- src/axom/quest/tests/quest_delaunay.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/axom/quest/tests/quest_delaunay.cpp b/src/axom/quest/tests/quest_delaunay.cpp index 0010092572..8f46aa4a60 100644 --- a/src/axom/quest/tests/quest_delaunay.cpp +++ b/src/axom/quest/tests/quest_delaunay.cpp @@ -410,7 +410,9 @@ TEST(quest_delaunay, stress_nearly_coplanar_insertions_3d) std::vector points; points.reserve(5 * 5 + 2); - constexpr double eps = 1e-6; + // Keep the grid close to planar, but not so close that the geometric + // circumsphere validator is dominated by sliver-conditioning noise. + constexpr double eps = 1e-5; for(int y = 0; y < 5; ++y) { for(int x = 0; x < 5; ++x) From 96b17e591295654ba681b127f97126bfe03392ac Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 14 Apr 2026 22:04:42 -0700 Subject: [PATCH 504/986] Simplifies predicate API -- we did not need primal::robust::{orientation,in_sphere} Instead, we have `primal::in_sphere_determinant` and `primal::in_sphere_orientation` --- src/axom/primal/operators/in_sphere.hpp | 83 ++++++++++++++------ src/axom/primal/operators/orientation.hpp | 59 +++++++------- src/axom/primal/tests/primal_in_sphere.cpp | 48 ++++++----- src/axom/primal/tests/primal_orientation.cpp | 39 +++++---- src/axom/quest/Delaunay.hpp | 61 ++++---------- 5 files changed, 157 insertions(+), 133 deletions(-) diff --git a/src/axom/primal/operators/in_sphere.hpp b/src/axom/primal/operators/in_sphere.hpp index 90397842c8..f1fe94f8cc 100644 --- a/src/axom/primal/operators/in_sphere.hpp +++ b/src/axom/primal/operators/in_sphere.hpp @@ -29,21 +29,62 @@ namespace axom { namespace primal { -namespace robust +/*! + * \brief Returns the raw 2D in-sphere determinant for a circumcircle test. + * + * A negative determinant means the query point is inside the circumcircle for + * a consistently oriented input triangle. + */ +template +inline double in_sphere_determinant(const Point& q, + const Point& p0, + const Point& p1, + const Point& p2) +{ + return detail::in_sphere_determinant(q, p0, p1, p2); +} + +template +inline double in_sphere_determinant(const Point& q, const Triangle& tri) +{ + return in_sphere_determinant(q, tri[0], tri[1], tri[2]); +} + +/*! + * \brief Returns the raw 3D in-sphere determinant for a circumsphere test. + * + * A negative determinant means the query point is inside the circumsphere for + * a consistently oriented input tetrahedron. + */ +template +inline double in_sphere_determinant(const Point& q, + const Point& p0, + const Point& p1, + const Point& p2, + const Point& p3) { + return detail::in_sphere_determinant(q, p0, p1, p2, p3); +} + +template +inline double in_sphere_determinant(const Point& q, const Tetrahedron& tet) +{ + return in_sphere_determinant(q, tet[0], tet[1], tet[2], tet[3]); +} + /*! * \brief Classifies a query point against a 2D triangle's circumcircle. * * \return ON_NEGATIVE_SIDE if inside, ON_POSITIVE_SIDE if outside, ON_BOUNDARY otherwise. */ template -inline int in_sphere(const Point& q, - const Point& p0, - const Point& p1, - const Point& p2, - double EPS = 1e-8) +inline int in_sphere_orientation(const Point& q, + const Point& p0, + const Point& p1, + const Point& p2, + double EPS = 1e-8) { - const double det = detail::in_sphere_determinant(q, p0, p1, p2); + const double det = in_sphere_determinant(q, p0, p1, p2); if(axom::utilities::isNearlyEqual(det, 0., EPS)) { return primal::ON_BOUNDARY; @@ -53,9 +94,9 @@ inline int in_sphere(const Point& q, } template -inline int in_sphere(const Point& q, const Triangle& tri, double EPS = 1e-8) +inline int in_sphere_orientation(const Point& q, const Triangle& tri, double EPS = 1e-8) { - return robust::in_sphere(q, tri[0], tri[1], tri[2], EPS); + return in_sphere_orientation(q, tri[0], tri[1], tri[2], EPS); } /*! @@ -64,14 +105,14 @@ inline int in_sphere(const Point& q, const Triangle& tri, double EPS * \return ON_NEGATIVE_SIDE if inside, ON_POSITIVE_SIDE if outside, ON_BOUNDARY otherwise. */ template -inline int in_sphere(const Point& q, - const Point& p0, - const Point& p1, - const Point& p2, - const Point& p3, - double EPS = 1e-8) +inline int in_sphere_orientation(const Point& q, + const Point& p0, + const Point& p1, + const Point& p2, + const Point& p3, + double EPS = 1e-8) { - const double det = detail::in_sphere_determinant(q, p0, p1, p2, p3); + const double det = in_sphere_determinant(q, p0, p1, p2, p3); if(axom::utilities::isNearlyEqual(det, 0., EPS)) { return primal::ON_BOUNDARY; @@ -81,13 +122,11 @@ inline int in_sphere(const Point& q, } template -inline int in_sphere(const Point& q, const Tetrahedron& tet, double EPS = 1e-8) +inline int in_sphere_orientation(const Point& q, const Tetrahedron& tet, double EPS = 1e-8) { - return robust::in_sphere(q, tet[0], tet[1], tet[2], tet[3], EPS); + return in_sphere_orientation(q, tet[0], tet[1], tet[2], tet[3], EPS); } -} // namespace robust - /*! * \brief Tests whether a query point lies inside a 2D triangle's circumcircle * @@ -112,7 +151,7 @@ inline bool in_sphere(const Point& q, double EPS = 1e-8, bool includeBoundary = false) { - const int res = robust::in_sphere(q, p0, p1, p2, EPS); + const int res = in_sphere_orientation(q, p0, p1, p2, EPS); return includeBoundary ? (res != primal::ON_POSITIVE_SIDE) : (res == primal::ON_NEGATIVE_SIDE); } @@ -162,7 +201,7 @@ inline bool in_sphere(const Point& q, double EPS = 1e-8, bool includeBoundary = false) { - const int res = robust::in_sphere(q, p0, p1, p2, p3, EPS); + const int res = in_sphere_orientation(q, p0, p1, p2, p3, EPS); return includeBoundary ? (res != primal::ON_POSITIVE_SIDE) : (res == primal::ON_NEGATIVE_SIDE); } diff --git a/src/axom/primal/operators/orientation.hpp b/src/axom/primal/operators/orientation.hpp index 1cf5e5326e..7897b2ae2d 100644 --- a/src/axom/primal/operators/orientation.hpp +++ b/src/axom/primal/operators/orientation.hpp @@ -32,48 +32,31 @@ namespace axom { namespace primal { -namespace robust -{ /*! - * \brief Computes the orientation of a point \a p with respect to an oriented triangle \a tri. + * \brief Returns the raw 2D orientation determinant for three points. * - * \return ON_BOUNDARY if within tolerance, ON_POSITIVE_SIDE / ON_NEGATIVE_SIDE otherwise. + * This determinant is twice the signed area of triangle `(a,b,c)`. */ template -inline int orientation(const Point& p, const Triangle& tri, double EPS = 1e-9) +inline double orientation_determinant(const Point& a, const Point& b, const Point& c) { - const double det = detail::orientation_determinant(p, tri[0], tri[1], tri[2]); - - if(axom::utilities::isNearlyEqual(det, 0., EPS)) - { - return primal::ON_BOUNDARY; - } - - // Preserve existing convention: det < 0 implies ON_POSITIVE_SIDE. - return det < 0. ? primal::ON_POSITIVE_SIDE : primal::ON_NEGATIVE_SIDE; + return detail::orientation_determinant(a, b, c); } /*! - * \brief Computes the orientation of a point \a p with respect to an oriented segment \a seg. + * \brief Returns the raw 3D orientation determinant for four points. * - * \return ON_BOUNDARY if within tolerance, ON_POSITIVE_SIDE / ON_NEGATIVE_SIDE otherwise. + * This determinant is six times the signed volume of tetrahedron `(a,b,c,d)`. */ template -inline int orientation(const Point& p, const Segment& seg, double EPS = 1e-9) +inline double orientation_determinant(const Point& a, + const Point& b, + const Point& c, + const Point& d) { - const double det = detail::orientation_determinant(p, seg[0], seg[1]); - - if(axom::utilities::isNearlyEqual(det, 0., EPS)) - { - return primal::ON_BOUNDARY; - } - - // Preserve existing convention: det < 0 implies ON_POSITIVE_SIDE. - return det < 0. ? primal::ON_POSITIVE_SIDE : primal::ON_NEGATIVE_SIDE; + return detail::orientation_determinant(a, b, c, d); } -} // namespace robust - /*! * \brief Computes the orientation of a point \a p with respect to an * oriented triangle \a tri @@ -95,7 +78,15 @@ inline int orientation(const Point& p, const Segment& seg, double EP template inline int orientation(const Point& p, const Triangle& tri, double EPS = 1e-9) { - return robust::orientation(p, tri, EPS); + const double det = detail::orientation_determinant(p, tri[0], tri[1], tri[2]); + + if(axom::utilities::isNearlyEqual(det, 0., EPS)) + { + return primal::ON_BOUNDARY; + } + + // Preserve existing convention: det < 0 implies ON_POSITIVE_SIDE. + return det < 0. ? primal::ON_POSITIVE_SIDE : primal::ON_NEGATIVE_SIDE; } /*! @@ -119,7 +110,15 @@ inline int orientation(const Point& p, const Triangle& tri, double E template inline int orientation(const Point& p, const Segment& seg, double EPS = 1e-9) { - return robust::orientation(p, seg, EPS); + const double det = detail::orientation_determinant(p, seg[0], seg[1]); + + if(axom::utilities::isNearlyEqual(det, 0., EPS)) + { + return primal::ON_BOUNDARY; + } + + // Preserve existing convention: det < 0 implies ON_POSITIVE_SIDE. + return det < 0. ? primal::ON_POSITIVE_SIDE : primal::ON_NEGATIVE_SIDE; } } // namespace primal diff --git a/src/axom/primal/tests/primal_in_sphere.cpp b/src/axom/primal/tests/primal_in_sphere.cpp index fa4d47a277..74b9f6ae84 100644 --- a/src/axom/primal/tests/primal_in_sphere.cpp +++ b/src/axom/primal/tests/primal_in_sphere.cpp @@ -39,15 +39,17 @@ TEST(primal_in_sphere, test_in_sphere_2d) PointType q1 {0.1, 0.1}; EXPECT_TRUE(in_sphere(q1, p0, p1, p2)); EXPECT_TRUE(in_sphere(q1, tri)); - EXPECT_EQ(primal::ON_NEGATIVE_SIDE, primal::robust::in_sphere(q1, p0, p1, p2)); - EXPECT_EQ(primal::ON_NEGATIVE_SIDE, primal::robust::in_sphere(q1, tri)); + EXPECT_LT(primal::in_sphere_determinant(q1, p0, p1, p2), 0.); + EXPECT_EQ(primal::ON_NEGATIVE_SIDE, primal::in_sphere_orientation(q1, p0, p1, p2)); + EXPECT_EQ(primal::ON_NEGATIVE_SIDE, primal::in_sphere_orientation(q1, tri)); // outside triangle PointType q2 {0.78, 0.6}; EXPECT_TRUE(in_sphere(q2, p0, p1, p2)); EXPECT_TRUE(in_sphere(q2, tri)); - EXPECT_EQ(primal::ON_NEGATIVE_SIDE, primal::robust::in_sphere(q2, p0, p1, p2)); - EXPECT_EQ(primal::ON_NEGATIVE_SIDE, primal::robust::in_sphere(q2, tri)); + EXPECT_LT(primal::in_sphere_determinant(q2, p0, p1, p2), 0.); + EXPECT_EQ(primal::ON_NEGATIVE_SIDE, primal::in_sphere_orientation(q2, p0, p1, p2)); + EXPECT_EQ(primal::ON_NEGATIVE_SIDE, primal::in_sphere_orientation(q2, tri)); } // Test some points that are on the circumcircle @@ -57,16 +59,16 @@ TEST(primal_in_sphere, test_in_sphere_2d) EXPECT_FALSE(in_sphere(q1, tri)); EXPECT_TRUE(in_sphere(q1, p0, p1, p2, 1e-8, true)); EXPECT_TRUE(in_sphere(q1, tri, 1e-8, true)); - EXPECT_EQ(primal::ON_BOUNDARY, primal::robust::in_sphere(q1, p0, p1, p2, 1e-8)); - EXPECT_EQ(primal::ON_BOUNDARY, primal::robust::in_sphere(q1, tri, 1e-8)); + EXPECT_EQ(primal::ON_BOUNDARY, primal::in_sphere_orientation(q1, p0, p1, p2, 1e-8)); + EXPECT_EQ(primal::ON_BOUNDARY, primal::in_sphere_orientation(q1, tri, 1e-8)); PointType q2 {0, 1}; EXPECT_FALSE(in_sphere(q2, p0, p1, p2)); EXPECT_FALSE(in_sphere(q2, tri)); EXPECT_TRUE(in_sphere(q2, p0, p1, p2, 1e-8, true)); EXPECT_TRUE(in_sphere(q2, tri, 1e-8, true)); - EXPECT_EQ(primal::ON_BOUNDARY, primal::robust::in_sphere(q2, p0, p1, p2, 1e-8)); - EXPECT_EQ(primal::ON_BOUNDARY, primal::robust::in_sphere(q2, tri, 1e-8)); + EXPECT_EQ(primal::ON_BOUNDARY, primal::in_sphere_orientation(q2, p0, p1, p2, 1e-8)); + EXPECT_EQ(primal::ON_BOUNDARY, primal::in_sphere_orientation(q2, tri, 1e-8)); } // Test some points that are outside the circumcircle @@ -74,12 +76,14 @@ TEST(primal_in_sphere, test_in_sphere_2d) PointType q1 {1.1, 0}; EXPECT_FALSE(in_sphere(q1, p0, p1, p2)); EXPECT_FALSE(in_sphere(q1, tri)); - EXPECT_EQ(primal::ON_POSITIVE_SIDE, primal::robust::in_sphere(q1, p0, p1, p2)); + EXPECT_GT(primal::in_sphere_determinant(q1, p0, p1, p2), 0.); + EXPECT_EQ(primal::ON_POSITIVE_SIDE, primal::in_sphere_orientation(q1, p0, p1, p2)); PointType q2 {-5, -10}; EXPECT_FALSE(in_sphere(q2, p0, p1, p2)); EXPECT_FALSE(in_sphere(q2, tri)); - EXPECT_EQ(primal::ON_POSITIVE_SIDE, primal::robust::in_sphere(q2, p0, p1, p2)); + EXPECT_GT(primal::in_sphere_determinant(q2, p0, p1, p2), 0.); + EXPECT_EQ(primal::ON_POSITIVE_SIDE, primal::in_sphere_orientation(q2, p0, p1, p2)); } } @@ -101,14 +105,16 @@ TEST(primal_in_sphere, test_in_sphere_3d) PointType q1 {0.5, 0.5, 0.5}; EXPECT_TRUE(in_sphere(q1, p0, p1, p2, p3)); EXPECT_TRUE(in_sphere(q1, tet)); - EXPECT_EQ(primal::ON_NEGATIVE_SIDE, primal::robust::in_sphere(q1, p0, p1, p2, p3)); - EXPECT_EQ(primal::ON_NEGATIVE_SIDE, primal::robust::in_sphere(q1, tet)); + EXPECT_LT(primal::in_sphere_determinant(q1, p0, p1, p2, p3), 0.); + EXPECT_EQ(primal::ON_NEGATIVE_SIDE, primal::in_sphere_orientation(q1, p0, p1, p2, p3)); + EXPECT_EQ(primal::ON_NEGATIVE_SIDE, primal::in_sphere_orientation(q1, tet)); PointType q2 {0., 0., 0.}; EXPECT_TRUE(in_sphere(q2, p0, p1, p2, p3)); EXPECT_TRUE(in_sphere(q2, tet)); - EXPECT_EQ(primal::ON_NEGATIVE_SIDE, primal::robust::in_sphere(q2, p0, p1, p2, p3)); - EXPECT_EQ(primal::ON_NEGATIVE_SIDE, primal::robust::in_sphere(q2, tet)); + EXPECT_LT(primal::in_sphere_determinant(q2, p0, p1, p2, p3), 0.); + EXPECT_EQ(primal::ON_NEGATIVE_SIDE, primal::in_sphere_orientation(q2, p0, p1, p2, p3)); + EXPECT_EQ(primal::ON_NEGATIVE_SIDE, primal::in_sphere_orientation(q2, tet)); } // Test some points that are on the circumsphere @@ -118,16 +124,16 @@ TEST(primal_in_sphere, test_in_sphere_3d) EXPECT_FALSE(in_sphere(q1, tet)); EXPECT_TRUE(in_sphere(q1, p0, p1, p2, p3, 1e-8, true)); EXPECT_TRUE(in_sphere(q1, tet, 1e-8, true)); - EXPECT_EQ(primal::ON_BOUNDARY, primal::robust::in_sphere(q1, p0, p1, p2, p3, 1e-8)); - EXPECT_EQ(primal::ON_BOUNDARY, primal::robust::in_sphere(q1, tet, 1e-8)); + EXPECT_EQ(primal::ON_BOUNDARY, primal::in_sphere_orientation(q1, p0, p1, p2, p3, 1e-8)); + EXPECT_EQ(primal::ON_BOUNDARY, primal::in_sphere_orientation(q1, tet, 1e-8)); PointType q2 {-1, 1, 1}; EXPECT_FALSE(in_sphere(q2, p0, p1, p2, p3)); EXPECT_FALSE(in_sphere(q2, tet)); EXPECT_TRUE(in_sphere(q2, p0, p1, p2, p3, 1e-8, true)); EXPECT_TRUE(in_sphere(q2, tet, 1e-8, true)); - EXPECT_EQ(primal::ON_BOUNDARY, primal::robust::in_sphere(q2, p0, p1, p2, p3, 1e-8)); - EXPECT_EQ(primal::ON_BOUNDARY, primal::robust::in_sphere(q2, tet, 1e-8)); + EXPECT_EQ(primal::ON_BOUNDARY, primal::in_sphere_orientation(q2, p0, p1, p2, p3, 1e-8)); + EXPECT_EQ(primal::ON_BOUNDARY, primal::in_sphere_orientation(q2, tet, 1e-8)); } // Test some points that are outside the circumsphere @@ -135,12 +141,14 @@ TEST(primal_in_sphere, test_in_sphere_3d) PointType q1 {1.1, 1, 1}; EXPECT_FALSE(in_sphere(q1, p0, p1, p2, p3)); EXPECT_FALSE(in_sphere(q1, tet)); - EXPECT_EQ(primal::ON_POSITIVE_SIDE, primal::robust::in_sphere(q1, p0, p1, p2, p3)); + EXPECT_GT(primal::in_sphere_determinant(q1, p0, p1, p2, p3), 0.); + EXPECT_EQ(primal::ON_POSITIVE_SIDE, primal::in_sphere_orientation(q1, p0, p1, p2, p3)); PointType q2 {-1.1, 1, 1}; EXPECT_FALSE(in_sphere(q2, p0, p1, p2, p3)); EXPECT_FALSE(in_sphere(q2, tet)); - EXPECT_EQ(primal::ON_POSITIVE_SIDE, primal::robust::in_sphere(q2, p0, p1, p2, p3)); + EXPECT_GT(primal::in_sphere_determinant(q2, p0, p1, p2, p3), 0.); + EXPECT_EQ(primal::ON_POSITIVE_SIDE, primal::in_sphere_orientation(q2, p0, p1, p2, p3)); } } diff --git a/src/axom/primal/tests/primal_orientation.cpp b/src/axom/primal/tests/primal_orientation.cpp index b1495b6c90..861d4398cf 100644 --- a/src/axom/primal/tests/primal_orientation.cpp +++ b/src/axom/primal/tests/primal_orientation.cpp @@ -51,19 +51,14 @@ TEST(primal_orientation, orient3D) // check orientation of a few offset points // Without offset, the point should be on the same plane EXPECT_EQ(primal::ON_BOUNDARY, primal::orientation(phys, tri)); - EXPECT_EQ(primal::orientation(phys, tri), primal::robust::orientation(phys, tri)); // Offset along negative normal should have negative orientation EXPECT_EQ(primal::ON_NEGATIVE_SIDE, primal::orientation(phys - normal, tri)); EXPECT_EQ(primal::ON_NEGATIVE_SIDE, primal::orientation(phys - 0.25 * normal, tri)); - EXPECT_EQ(primal::orientation(phys - normal, tri), - primal::robust::orientation(phys - normal, tri)); // Offset along positive normal should have positive orientation EXPECT_EQ(primal::ON_POSITIVE_SIDE, primal::orientation(phys + normal, tri)); EXPECT_EQ(primal::ON_POSITIVE_SIDE, primal::orientation(phys + 0.25 * normal, tri)); - EXPECT_EQ(primal::orientation(phys + normal, tri), - primal::robust::orientation(phys + normal, tri)); // check that orientation is equivalent to half-space definition { @@ -101,15 +96,11 @@ TEST(primal_orientation, orient3D) EXPECT_EQ(primal::ON_BOUNDARY, primal::orientation(phys - smallOff * unitNormal, tri, TOL)); EXPECT_EQ(primal::ON_NEGATIVE_SIDE, primal::orientation(phys - largeOff * unitNormal, tri, TOL)); - EXPECT_EQ(primal::orientation(phys - largeOff * unitNormal, tri, TOL), - primal::robust::orientation(phys - largeOff * unitNormal, tri, TOL)); EXPECT_EQ(primal::ON_BOUNDARY, primal::orientation(phys + smallOff * unitNormal, tri, TOL)); EXPECT_EQ(primal::ON_POSITIVE_SIDE, primal::orientation(phys + largeOff * unitNormal, tri, TOL)); - EXPECT_EQ(primal::orientation(phys + largeOff * unitNormal, tri, TOL), - primal::robust::orientation(phys + largeOff * unitNormal, tri, TOL)); } } } @@ -142,19 +133,14 @@ TEST(primal_orientation, orient2D) // check orientation of a few offset points // Without offset, the point should be on the same plane EXPECT_EQ(primal::ON_BOUNDARY, primal::orientation(phys, seg)); - EXPECT_EQ(primal::orientation(phys, seg), primal::robust::orientation(phys, seg)); // Offset along negative normal should have negative orientation EXPECT_EQ(primal::ON_NEGATIVE_SIDE, primal::orientation(phys - normal, seg)); EXPECT_EQ(primal::ON_NEGATIVE_SIDE, primal::orientation(phys - 0.25 * normal, seg)); - EXPECT_EQ(primal::orientation(phys - normal, seg), - primal::robust::orientation(phys - normal, seg)); // Offset along positive normal should have positive orientation EXPECT_EQ(primal::ON_POSITIVE_SIDE, primal::orientation(phys + normal, seg)); EXPECT_EQ(primal::ON_POSITIVE_SIDE, primal::orientation(phys + 0.25 * normal, seg)); - EXPECT_EQ(primal::orientation(phys + normal, seg), - primal::robust::orientation(phys + normal, seg)); // check that orientation is equivalent to half-space definition { @@ -196,13 +182,34 @@ TEST(primal_orientation, orient2D) EXPECT_EQ(primal::ON_BOUNDARY, primal::orientation(phys + smallOff * unitNormal, seg, TOL)); EXPECT_EQ(primal::ON_POSITIVE_SIDE, primal::orientation(phys + largeOff * unitNormal, seg, TOL)); - EXPECT_EQ(primal::orientation(phys + largeOff * unitNormal, seg, TOL), - primal::robust::orientation(phys + largeOff * unitNormal, seg, TOL)); } } } } +//------------------------------------------------------------------------------ +TEST(primal_orientation, determinant_helpers) +{ + namespace primal = axom::primal; + + using Point2 = primal::Point; + using Point3 = primal::Point; + + EXPECT_GT(primal::orientation_determinant(Point2 {0., 0.}, Point2 {1., 0.}, Point2 {0., 1.}), 0.); + EXPECT_LT(primal::orientation_determinant(Point2 {0., 0.}, Point2 {0., 1.}, Point2 {1., 0.}), 0.); + + EXPECT_GT(primal::orientation_determinant(Point3 {0., 0., 0.}, + Point3 {1., 0., 0.}, + Point3 {0., 1., 0.}, + Point3 {0., 0., 1.}), + 0.); + EXPECT_LT(primal::orientation_determinant(Point3 {0., 0., 0.}, + Point3 {0., 1., 0.}, + Point3 {1., 0., 0.}, + Point3 {0., 0., 1.}), + 0.); +} + //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ diff --git a/src/axom/quest/Delaunay.hpp b/src/axom/quest/Delaunay.hpp index 645142eefb..2fb521680e 100644 --- a/src/axom/quest/Delaunay.hpp +++ b/src/axom/quest/Delaunay.hpp @@ -1095,41 +1095,12 @@ class Delaunay //----------------------------------------------------------------------------- // In-sphere predicate helpers // - // Delaunay uses determinant-based in-sphere tests for both cavity growth and - // global empty-circumsphere validation. We rely on primal::robust::in_sphere() - // to classify {inside, outside, on boundary} and only compute a - // scale-dependent determinant tolerance here. + // Delaunay intentionally classifies points against element circumspheres + // using geometric signed distance rather than primal's determinant-based + // in-sphere classifier. This keeps cavity growth, insertion validation, and + // the global empty-circumsphere check consistent on ill-conditioned slivers. + // Raw determinant values are still exposed separately for diagnostics. //----------------------------------------------------------------------------- - static double inSphereDeterminantTolerance(double scale) - { - // The in-sphere determinant is formed from coordinate differences and - // squared norms, so near-cospherical slivers can lose precision based on - // the absolute coordinate magnitudes before translation, not just on the - // local edge lengths. - const double k = (DIM == 2) ? 128. : 512.; - if constexpr(DIM == 2) - { - return k * std::numeric_limits::epsilon() * scale * scale * scale * scale; - } - else - { - return k * std::numeric_limits::epsilon() * scale * scale * scale * scale * scale; - } - } - - static double orientationDeterminantTolerance(double scale) - { - const double k = (DIM == 2) ? 128. : 512.; - if constexpr(DIM == 2) - { - return k * std::numeric_limits::epsilon() * scale * scale; - } - else - { - return k * std::numeric_limits::epsilon() * scale * scale * scale; - } - } - template static double sphereSignedDistanceTolerance(const SphereType& sphere, const PointType& x) { @@ -1193,18 +1164,18 @@ class Delaunay if constexpr(DIM == 2) { - return primal::detail::in_sphere_determinant(q, - mesh.getVertexPosition(verts[0]), - mesh.getVertexPosition(verts[1]), - mesh.getVertexPosition(verts[2])); + return primal::in_sphere_determinant(q, + mesh.getVertexPosition(verts[0]), + mesh.getVertexPosition(verts[1]), + mesh.getVertexPosition(verts[2])); } else { - return primal::detail::in_sphere_determinant(q, - mesh.getVertexPosition(verts[0]), - mesh.getVertexPosition(verts[1]), - mesh.getVertexPosition(verts[2]), - mesh.getVertexPosition(verts[3])); + return primal::in_sphere_determinant(q, + mesh.getVertexPosition(verts[0]), + mesh.getVertexPosition(verts[1]), + mesh.getVertexPosition(verts[2]), + mesh.getVertexPosition(verts[3])); } } @@ -2133,7 +2104,7 @@ class Delaunay { const PointType& p0 = m_mesh.getVertexPosition(vlist[0]); const PointType& p1 = m_mesh.getVertexPosition(vlist[1]); - if(primal::detail::orientation_determinant(p0, p1, new_pt) < 0.) + if(primal::orientation_determinant(p0, p1, new_pt) < 0.) { axom::utilities::swap(vlist[0], vlist[1]); } @@ -2143,7 +2114,7 @@ class Delaunay const PointType& p0 = m_mesh.getVertexPosition(vlist[0]); const PointType& p1 = m_mesh.getVertexPosition(vlist[1]); const PointType& p2 = m_mesh.getVertexPosition(vlist[2]); - if(primal::detail::orientation_determinant(p0, p1, p2, new_pt) < 0.) + if(primal::orientation_determinant(p0, p1, p2, new_pt) < 0.) { axom::utilities::swap(vlist[1], vlist[2]); } From 31512b510a28be5b123c8fd9064a25b9f3d5b5c1 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 14 Apr 2026 23:16:15 -0700 Subject: [PATCH 505/986] Avoids virtual function call in inlined isValidElement() and avoids unnecessarily calling this function --- src/axom/quest/Delaunay.hpp | 14 +++++++++++--- src/axom/slam/DynamicSet.hpp | 2 +- src/axom/slam/mesh_struct/IA_impl.hpp | 18 ++++++++++++------ 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/axom/quest/Delaunay.hpp b/src/axom/quest/Delaunay.hpp index 2fb521680e..2b94e2a5fe 100644 --- a/src/axom/quest/Delaunay.hpp +++ b/src/axom/quest/Delaunay.hpp @@ -986,14 +986,16 @@ class Delaunay { IndexArray seed_elements; seed_elements.push_back(element_idx); + constexpr IndexType invalid_element = IAMeshType::INVALID_ELEMENT_INDEX; for(int i = 0; i < VERT_PER_ELEMENT; ++i) { if(axom::utilities::abs(bary_coord[i]) <= BARY_EPS) { const IndexType nbr = m_mesh.adjacentElements(element_idx)[ModularFaceIndex(i) + 1]; - if(m_mesh.isValidElement(nbr)) + if(nbr != invalid_element) { + SLIC_ASSERT(m_mesh.isValidElement(nbr)); seed_elements.push_back(nbr); } } @@ -1338,6 +1340,8 @@ class Delaunay IndexType start_element, std::vector* visited_elements_out = nullptr) const { + constexpr IndexType invalid_element = IAMeshType::INVALID_ELEMENT_INDEX; + if(!m_mesh.isValidElement(start_element)) { return {}; @@ -1420,13 +1424,14 @@ class Delaunay } const IndexType next_element = m_mesh.adjacentElements(element_i)[modular_idx + 1]; - if(!m_mesh.isValidElement(next_element)) + if(next_element == invalid_element) { recordWalk(PointLocationStatus::Outside); clearVisitedBits(); return {INVALID_INDEX, PointLocationStatus::Outside}; } + SLIC_ASSERT(m_mesh.isValidElement(next_element)); element_i = next_element; } } @@ -2000,6 +2005,7 @@ class Delaunay { inserted_elems.reserve(reserveSize); } + constexpr IndexType invalid_element = IAMeshType::INVALID_ELEMENT_INDEX; // Seed the cavity with the containing element, and with any face-adjacent // neighbors when the insertion point lies on the containing simplex @@ -2024,8 +2030,10 @@ class Delaunay const IndexType nbr = neighbors[n_idx]; // invalid neighbor means face is on domain boundary, and thus on cavity boundary - if(m_mesh.isValidElement(nbr)) + if(nbr != invalid_element) { + SLIC_ASSERT(m_mesh.isValidElement(nbr)); + // If the neighbor is already in the cavity, the shared face is // internal and not part of the cavity boundary. if(containsCavityElement(nbr)) diff --git a/src/axom/slam/DynamicSet.hpp b/src/axom/slam/DynamicSet.hpp index bb799f982a..baa13030e3 100644 --- a/src/axom/slam/DynamicSet.hpp +++ b/src/axom/slam/DynamicSet.hpp @@ -375,7 +375,7 @@ class DynamicSet : public Set, SizePolicy */ inline bool isValidEntry(IndexType i) const { - return i >= 0 && i < size() && m_data[i] != INVALID_ENTRY; + return i >= 0 && i < SizePolicy::size() && m_data[i] != INVALID_ENTRY; }; /** diff --git a/src/axom/slam/mesh_struct/IA_impl.hpp b/src/axom/slam/mesh_struct/IA_impl.hpp index 5dbe590bff..43ab052c81 100644 --- a/src/axom/slam/mesh_struct/IA_impl.hpp +++ b/src/axom/slam/mesh_struct/IA_impl.hpp @@ -300,10 +300,11 @@ typename IAMesh::IndexArray IAMesh::vertexStar(Ind { // If nbr is valid, has not already been found and contains the vertex in question, // add it and enqueue to check neighbors. - if(element_set.isValidEntry(nbr) // - && !is_subset(nbr, ret) // + if(nbr != INVALID_ELEMENT_INDEX // + && !is_subset(nbr, ret) // && is_subset(vertex_idx, ev_rel[nbr])) { + SLIC_ASSERT(element_set.isValidEntry(nbr)); ret.push_back(nbr); element_traverse_queue.push_back(nbr); } @@ -391,8 +392,9 @@ void IAMesh::removeElement(IndexType element_idx) for(auto nbr : ee_rel[element_idx]) { // update to a valid neighbor that is incident in vertex_i - if(element_set.isValidEntry(nbr) && is_subset(vertex_i, ev_rel[nbr])) + if(nbr != INVALID_ELEMENT_INDEX && is_subset(vertex_i, ev_rel[nbr])) { + SLIC_ASSERT(element_set.isValidEntry(nbr)); new_elem = nbr; break; } @@ -407,8 +409,9 @@ void IAMesh::removeElement(IndexType element_idx) //erase neighbor element's adjacency data pointing to deleted element for(auto nbr : ee_rel[element_idx]) { - if(isValidElement(nbr)) + if(nbr != INVALID_ELEMENT_INDEX) { + SLIC_ASSERT(isValidElement(nbr)); auto nbr_ee = ee_rel[nbr]; for(auto idx : nbr_ee.positions()) { @@ -727,14 +730,17 @@ void IAMesh::fixVertexNeighborhood(IndexType vertex_idx, // figure out which face this is on the neighbor // and update neighbor's adjacency to point to current element const IndexType nbr = ee_rel[el][face_i]; - if(element_set.isValidEntry(nbr)) + if(nbr != INVALID_ELEMENT_INDEX) { + SLIC_ASSERT(element_set.isValidEntry(nbr)); const auto nbr_ev = ev_rel[nbr]; auto nbr_ee = ee_rel[nbr]; for(auto face_j : nbr_ev.positions()) { - if(!element_set.isValidEntry(nbr_ee[face_j]) && isSharedFace(nbr_ev, face_j, bdry)) + const IndexType nbr_face = nbr_ee[face_j]; + SLIC_ASSERT(nbr_face == INVALID_ELEMENT_INDEX || element_set.isValidEntry(nbr_face)); + if(nbr_face == INVALID_ELEMENT_INDEX && isSharedFace(nbr_ev, face_j, bdry)) { nbr_ee[face_j] = el; break; From 0fc0ce1d6b04d5b79034c2e4c31dd9e584bd462e Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 14 Apr 2026 23:20:54 -0700 Subject: [PATCH 506/986] Optimizes IAMesh::fixVertexNeighborhood Use known link neighbors instead of searching for them --- src/axom/slam/mesh_struct/IA_impl.hpp | 178 ++++++++++++++++---------- 1 file changed, 113 insertions(+), 65 deletions(-) diff --git a/src/axom/slam/mesh_struct/IA_impl.hpp b/src/axom/slam/mesh_struct/IA_impl.hpp index 43ab052c81..21f632c1d2 100644 --- a/src/axom/slam/mesh_struct/IA_impl.hpp +++ b/src/axom/slam/mesh_struct/IA_impl.hpp @@ -642,18 +642,23 @@ void IAMesh::fixVertexNeighborhood(IndexType vertex_idx, IndexType key1 {INVALID_VERTEX_INDEX}; IndexType element_idx {EMPTY_SLOT}; IndexType face_idx {INVALID_ELEMENT_INDEX}; + unsigned int generation {0u}; }; static thread_local std::vector pending_faces; - static thread_local std::vector used_slots; - for(const auto slot : used_slots) + static thread_local unsigned int pending_generation = 0u; + if(++pending_generation == 0u) { - pending_faces[slot].element_idx = EMPTY_SLOT; + for(auto& pending : pending_faces) + { + pending.generation = 0u; + } + pending_generation = 1u; } - used_slots.clear(); std::size_t table_size = 8; - const std::size_t target_slots = std::max(8, 4 * new_elements.size()); + const std::size_t target_slots = + std::max(8, (TDIM == 2 ? 4 : 8) * new_elements.size()); while(table_size < target_slots) { table_size <<= 1; @@ -663,43 +668,99 @@ void IAMesh::fixVertexNeighborhood(IndexType vertex_idx, pending_faces.resize(table_size); } - // helper lambda for determining if a face on one element (given by boundary verts nbr_verts) - // is shared with another element (given by boundary verts elem_verts) - auto isSharedFace = - [](const BoundarySubset& nbr_verts, IndexType face_idx, const BoundarySubset& elem_verts) { - // v_skip is not a vertex in this face - const auto v_skip = (face_idx == 0) ? VERTS_PER_ELEM - 1 : face_idx - 1; - for(int v_idx = 0; v_idx < VERTS_PER_ELEM; ++v_idx) + auto pendingFaceHash = [](IndexType key0, IndexType key1) -> std::size_t { + std::size_t seed = static_cast(key0); + seed ^= static_cast(key1) + 0x9e3779b97f4a7c15ULL + (seed << 6) + (seed >> 2); + return seed; + }; + + auto getFaceOffset = [](IndexType element_idx, int face_idx) { + return static_cast(element_idx) * VERTS_PER_ELEM + face_idx; + }; + + auto getBoundaryBase = [](IndexType element_idx) { + return static_cast(element_idx) * VERTS_PER_ELEM; + }; + + auto skippedVertexToFace = [](int skipped_vertex_idx) { + return (skipped_vertex_idx + 1) % VERTS_PER_ELEM; + }; + + const auto& ev_data = ev_rel.data(); + auto& ee_data = ee_rel.data(); + const IndexType* const ev_ptr = ev_data.data(); + + auto getBoundaryNeighborFace = [&](IndexType nbr, const IndexType* bdry) { + const IndexType* const nbr_bdry = ev_ptr + getBoundaryBase(nbr); + + if constexpr(TDIM == 2) + { + const IndexType v0 = bdry[0]; + const IndexType v1 = bdry[1]; + + if(nbr_bdry[0] != v0 && nbr_bdry[0] != v1) { - if(v_idx != v_skip && // v_idx is a vertex in this face - !is_subset(nbr_verts[v_idx], elem_verts)) //and is not in other elem - { - return false; - } + return skippedVertexToFace(0); + } + if(nbr_bdry[1] != v0 && nbr_bdry[1] != v1) + { + return skippedVertexToFace(1); } - return true; - }; - auto getPendingFaceKey = [vertex_idx](const BoundarySubset& bdry, IndexType vert_i) { - PendingFace key; - for(int i = 0, idx = 0; i < TDIM; ++i) + SLIC_ASSERT(nbr_bdry[2] != v0 && nbr_bdry[2] != v1); + return skippedVertexToFace(2); + } + else { - if(i != vert_i && bdry[i] != vertex_idx) + const IndexType v0 = bdry[0]; + const IndexType v1 = bdry[1]; + const IndexType v2 = bdry[2]; + + if(nbr_bdry[0] != v0 && nbr_bdry[0] != v1 && nbr_bdry[0] != v2) { - if(idx == 0) - { - key.key0 = bdry[i]; - } - else - { - key.key1 = bdry[i]; - } - ++idx; + return skippedVertexToFace(0); } + if(nbr_bdry[1] != v0 && nbr_bdry[1] != v1 && nbr_bdry[1] != v2) + { + return skippedVertexToFace(1); + } + if(nbr_bdry[2] != v0 && nbr_bdry[2] != v1 && nbr_bdry[2] != v2) + { + return skippedVertexToFace(2); + } + + SLIC_ASSERT(nbr_bdry[3] != v0 && nbr_bdry[3] != v1 && nbr_bdry[3] != v2); + return skippedVertexToFace(3); } + }; + + auto getPendingFaceKey = [](const IndexType* bdry, int face_idx) { + PendingFace key; - if constexpr(TDIM == 3) + if constexpr(TDIM == 2) { + SLIC_ASSERT(face_idx == 1 || face_idx == 2); + key.key0 = (face_idx == 1) ? bdry[1] : bdry[0]; + } + else + { + SLIC_ASSERT(face_idx >= 1 && face_idx <= 3); + switch(face_idx) + { + case 1: + key.key0 = bdry[1]; + key.key1 = bdry[2]; + break; + case 2: + key.key0 = bdry[2]; + key.key1 = bdry[0]; + break; + default: + key.key0 = bdry[0]; + key.key1 = bdry[1]; + break; + } + if(key.key1 < key.key0) { axom::utilities::swap(key.key0, key.key1); @@ -709,49 +770,40 @@ void IAMesh::fixVertexNeighborhood(IndexType vertex_idx, return key; }; - auto pendingFaceHash = [](IndexType key0, IndexType key1) -> std::size_t { - std::size_t seed = static_cast(key0); - seed ^= static_cast(key1) + 0x9e3779b97f4a7c15ULL + (seed << 6) + (seed >> 2); - return seed; - }; - int num_pending_faces = 0; int num_incident_faces = 0; for(auto el : new_elements) { - const auto bdry = ev_rel[el]; + const std::size_t el_base = getBoundaryBase(el); + const IndexType* const bdry = ev_ptr + el_base; + for(int face_i = 0; face_i < VERTS_PER_ELEM; ++face_i) { // This face is either a boundary facet of the star... const auto vert_i = (face_i == 0) ? VERTS_PER_ELEM - 1 : face_i - 1; if(bdry[vert_i] == vertex_idx) { + SLIC_ASSERT(face_i == 0); + SLIC_ASSERT(vert_i == VERTS_PER_ELEM - 1); + // figure out which face this is on the neighbor // and update neighbor's adjacency to point to current element - const IndexType nbr = ee_rel[el][face_i]; + const IndexType nbr = ee_data[getFaceOffset(el, face_i)]; if(nbr != INVALID_ELEMENT_INDEX) { SLIC_ASSERT(element_set.isValidEntry(nbr)); - const auto nbr_ev = ev_rel[nbr]; - auto nbr_ee = ee_rel[nbr]; - - for(auto face_j : nbr_ev.positions()) - { - const IndexType nbr_face = nbr_ee[face_j]; - SLIC_ASSERT(nbr_face == INVALID_ELEMENT_INDEX || element_set.isValidEntry(nbr_face)); - if(nbr_face == INVALID_ELEMENT_INDEX && isSharedFace(nbr_ev, face_j, bdry)) - { - nbr_ee[face_j] = el; - break; - } - } + const int nbr_face = getBoundaryNeighborFace(nbr, bdry); + const std::size_t nbr_face_offset = getFaceOffset(nbr, nbr_face); + SLIC_ASSERT(ee_data[nbr_face_offset] == INVALID_ELEMENT_INDEX || + ee_data[nbr_face_offset] == el); + ee_data[nbr_face_offset] = el; } } // ... or it is incident in the common vertex: vertex_idx else { - const PendingFace face = getPendingFaceKey(bdry, vert_i); + const PendingFace face = getPendingFaceKey(bdry, face_i); const std::size_t mask = pending_faces.size() - 1; std::size_t slot = pendingFaceHash(face.key0, face.key1) & mask; std::size_t insert_slot = pending_faces.size(); @@ -759,18 +811,14 @@ void IAMesh::fixVertexNeighborhood(IndexType vertex_idx, for(;; slot = (slot + 1) & mask) { auto& pending = pending_faces[slot]; - if(pending.element_idx == EMPTY_SLOT) + if(pending.generation != pending_generation) { - if(insert_slot == pending_faces.size()) - { - insert_slot = slot; - } - - auto& insert_entry = pending_faces[insert_slot]; + auto& insert_entry = + pending_faces[(insert_slot == pending_faces.size()) ? slot : insert_slot]; insert_entry = face; insert_entry.element_idx = el; insert_entry.face_idx = face_i; - used_slots.push_back(insert_slot); + insert_entry.generation = pending_generation; ++num_pending_faces; break; } @@ -788,8 +836,8 @@ void IAMesh::fixVertexNeighborhood(IndexType vertex_idx, { SLIC_ASSERT_MSG(pending.element_idx != el, "Each face in the inserted star should be shared by two elements"); - ee_rel.modify(pending.element_idx, pending.face_idx, el); - ee_rel.modify(el, face_i, pending.element_idx); + ee_data[getFaceOffset(pending.element_idx, pending.face_idx)] = el; + ee_data[getFaceOffset(el, face_i)] = pending.element_idx; pending.element_idx = TOMBSTONE_SLOT; --num_pending_faces; break; From df2b01d99762152c968a21cdbfe3ebcca6fc215d Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 15 Apr 2026 08:11:25 -0700 Subject: [PATCH 507/986] Improves Delaunay inSphere check --- src/axom/quest/Delaunay.hpp | 151 ++++++++++++++++++++++++++---------- 1 file changed, 111 insertions(+), 40 deletions(-) diff --git a/src/axom/quest/Delaunay.hpp b/src/axom/quest/Delaunay.hpp index 2b94e2a5fe..86fbdb967e 100644 --- a/src/axom/quest/Delaunay.hpp +++ b/src/axom/quest/Delaunay.hpp @@ -47,6 +47,7 @@ class Delaunay using DataType = double; using PointType = primal::Point; + using VectorType = primal::Vector; using ElementType = typename std::conditional, primal::Tetrahedron>::type; using BaryCoordType = primal::Point; @@ -417,12 +418,9 @@ class Delaunay axom::Array circumspheres(totalElements); auto vertexInsideCircumsphere = [&](const PointType& vertex, IndexType element_idx) { - // `Sphere::getOrientation()` depends on an explicitly constructed - // circumsphere (center + radius). For sliver tetrahedra this construction - // is ill-conditioned and can produce false positives when validating the - // empty-circumsphere property. Use the determinant-based predicate used - // during cavity construction and treat boundary cases as "not inside" for - // global validation. + // Use the same mesh-side geometric circumsphere classifier as insertion so + // the empty-circumsphere validation follows the exact same inside/boundary + // decisions. Boundary cases are treated as "not inside" for global checks. return isPointInSphereOnMesh(m_mesh, vertex, element_idx, /*includeBoundary=*/false); }; @@ -1098,27 +1096,30 @@ class Delaunay // In-sphere predicate helpers // // Delaunay intentionally classifies points against element circumspheres - // using geometric signed distance rather than primal's determinant-based - // in-sphere classifier. This keeps cavity growth, insertion validation, and - // the global empty-circumsphere check consistent on ill-conditioned slivers. - // Raw determinant values are still exposed separately for diagnostics. + // using a shared geometric circumsphere classifier rather than primal's + // determinant-based in-sphere classifier. This keeps cavity growth, + // insertion validation, and the global empty-circumsphere check consistent + // on ill-conditioned slivers while still letting the hot path avoid the + // explicit sphere signed-distance sqrt. Raw determinant values are still + // exposed separately for diagnostics. //----------------------------------------------------------------------------- - template - static double sphereSignedDistanceTolerance(const SphereType& sphere, const PointType& x) + struct CircumsphereEval { - const auto& center = sphere.getCenter(); - double scale = axom::utilities::max(1., sphere.getRadius()); - for(int dim = 0; dim < DIM; ++dim) - { - scale = axom::utilities::max(scale, axom::utilities::abs(center[dim])); - scale = axom::utilities::max(scale, axom::utilities::abs(x[dim])); - } + PointType center {}; + double radius_sq {0.}; - return 256. * std::numeric_limits::epsilon() * scale; - } + CircumsphereEval() = default; - static int inSphereOrientationOnMesh(const IAMeshType& mesh, const PointType& q, IndexType element_idx) + CircumsphereEval(const PointType& origin, const VectorType& center_offset) + : center(origin + center_offset) + , radius_sq(center_offset.squared_norm()) + { } + }; + + static CircumsphereEval evaluateCircumsphereOnMesh(const IAMeshType& mesh, IndexType element_idx) { + using axom::numerics::determinant; + const auto verts = mesh.boundaryVertices(element_idx); if constexpr(DIM == 2) @@ -1127,16 +1128,22 @@ class Delaunay const PointType& p1 = mesh.getVertexPosition(verts[1]); const PointType& p2 = mesh.getVertexPosition(verts[2]); - const ElementType tri(p0, p1, p2); - const auto sphere = tri.circumsphere(); - const double signed_distance = sphere.computeSignedDistance(q); - const double tol = sphereSignedDistanceTolerance(sphere, q); - if(axom::utilities::abs(signed_distance) <= tol) - { - return primal::ON_BOUNDARY; - } + const double vx0 = p1[0] - p0[0]; + const double vx1 = p2[0] - p0[0]; + const double vy0 = p1[1] - p0[1]; + const double vy1 = p2[1] - p0[1]; + + const double sq0 = vx0 * vx0 + vy0 * vy0; + const double sq1 = vx1 * vx1 + vy1 * vy1; + + const double a = determinant(vx0, vx1, vy0, vy1); + const double eps = (a >= 0.) ? primal::PRIMAL_TINY : -primal::PRIMAL_TINY; + const double ood = 1. / (2. * a + eps); - return signed_distance < 0. ? primal::ON_NEGATIVE_SIDE : primal::ON_POSITIVE_SIDE; + const double center_offset_x = determinant(sq0, sq1, vy0, vy1) * ood; + const double center_offset_y = -determinant(sq0, sq1, vx0, vx1) * ood; + + return CircumsphereEval(p0, VectorType {center_offset_x, center_offset_y}); } else { @@ -1145,17 +1152,81 @@ class Delaunay const PointType& p2 = mesh.getVertexPosition(verts[2]); const PointType& p3 = mesh.getVertexPosition(verts[3]); - const ElementType tet(p0, p1, p2, p3); - const auto sphere = tet.circumsphere(); - const double signed_distance = sphere.computeSignedDistance(q); - const double tol = sphereSignedDistanceTolerance(sphere, q); - if(axom::utilities::abs(signed_distance) <= tol) - { - return primal::ON_BOUNDARY; - } + const double vx0 = p1[0] - p0[0]; + const double vx1 = p2[0] - p0[0]; + const double vx2 = p3[0] - p0[0]; + const double vy0 = p1[1] - p0[1]; + const double vy1 = p2[1] - p0[1]; + const double vy2 = p3[1] - p0[1]; + const double vz0 = p1[2] - p0[2]; + const double vz1 = p2[2] - p0[2]; + const double vz2 = p3[2] - p0[2]; + + const double sq0 = vx0 * vx0 + vy0 * vy0 + vz0 * vz0; + const double sq1 = vx1 * vx1 + vy1 * vy1 + vz1 * vz1; + const double sq2 = vx2 * vx2 + vy2 * vy2 + vz2 * vz2; + + const double a = determinant(vx0, vx1, vx2, vy0, vy1, vy2, vz0, vz1, vz2); + const double eps = (a >= 0.) ? primal::PRIMAL_TINY : -primal::PRIMAL_TINY; + const double ood = 1. / (2. * a + eps); + + const double center_offset_x = determinant(sq0, sq1, sq2, vy0, vy1, vy2, vz0, vz1, vz2) * ood; + const double center_offset_y = determinant(sq0, sq1, sq2, vz0, vz1, vz2, vx0, vx1, vx2) * ood; + const double center_offset_z = determinant(sq0, sq1, sq2, vx0, vx1, vx2, vy0, vy1, vy2) * ood; + + return CircumsphereEval(p0, VectorType {center_offset_x, center_offset_y, center_offset_z}); + } + } + + static double sphereSquaredDistanceTolerance(const CircumsphereEval& sphere, + const PointType& x, + double distance_sq) + { + double scale = 1.; - return signed_distance < 0. ? primal::ON_NEGATIVE_SIDE : primal::ON_POSITIVE_SIDE; + for(int dim = 0; dim < DIM; ++dim) + { + scale = axom::utilities::max(scale, axom::utilities::abs(sphere.center[dim])); + scale = axom::utilities::max(scale, axom::utilities::abs(x[dim])); } + + const double local_span = + axom::utilities::max(1., 2. * std::sqrt(axom::utilities::max(distance_sq, sphere.radius_sq))); + + // Since d^2 - r^2 = (d - r)(d + r), map the old signed-distance tolerance to + // squared distance with a local bound on (d + r) rather than another global + // coordinate-scale factor. This preserves the large-coordinate sliver cases + // that motivated the geometric classifier in the first place. + return 256. * std::numeric_limits::epsilon() * scale * local_span; + } + + template + static double sphereSignedDistanceTolerance(const SphereType& sphere, const PointType& x) + { + const auto& center = sphere.getCenter(); + double scale = axom::utilities::max(1., sphere.getRadius()); + for(int dim = 0; dim < DIM; ++dim) + { + scale = axom::utilities::max(scale, axom::utilities::abs(center[dim])); + scale = axom::utilities::max(scale, axom::utilities::abs(x[dim])); + } + + return 256. * std::numeric_limits::epsilon() * scale; + } + + static int inSphereOrientationOnMesh(const IAMeshType& mesh, const PointType& q, IndexType element_idx) + { + const auto sphere = evaluateCircumsphereOnMesh(mesh, element_idx); + const double distance_sq = VectorType(sphere.center, q).squared_norm(); + + const double delta_sq = distance_sq - sphere.radius_sq; + const double tol = sphereSquaredDistanceTolerance(sphere, q, distance_sq); + if(axom::utilities::abs(delta_sq) <= tol) + { + return primal::ON_BOUNDARY; + } + + return delta_sq < 0. ? primal::ON_NEGATIVE_SIDE : primal::ON_POSITIVE_SIDE; } static double inSphereDeterminantOnMesh(const IAMeshType& mesh, From b7208a35d17210e0d90ebceee923de19f737eef2 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 15 Apr 2026 09:30:10 -0700 Subject: [PATCH 508/986] Adds benchmarks for different circumsphere eval tests --- src/axom/quest/CMakeLists.txt | 4 + src/axom/quest/benchmarks/CMakeLists.txt | 32 ++ .../quest_delaunay_circumsphere.cpp | 300 ++++++++++++++++++ 3 files changed, 336 insertions(+) create mode 100644 src/axom/quest/benchmarks/CMakeLists.txt create mode 100644 src/axom/quest/benchmarks/quest_delaunay_circumsphere.cpp diff --git a/src/axom/quest/CMakeLists.txt b/src/axom/quest/CMakeLists.txt index 35e0451286..4cf6454823 100644 --- a/src/axom/quest/CMakeLists.txt +++ b/src/axom/quest/CMakeLists.txt @@ -278,6 +278,10 @@ if (AXOM_ENABLE_EXAMPLES) add_subdirectory(examples) endif() +if (ENABLE_BENCHMARKS) + add_subdirectory(benchmarks) +endif() + if (AXOM_ENABLE_TESTS) add_subdirectory(tests) endif() diff --git a/src/axom/quest/benchmarks/CMakeLists.txt b/src/axom/quest/benchmarks/CMakeLists.txt new file mode 100644 index 0000000000..681a9e9ac1 --- /dev/null +++ b/src/axom/quest/benchmarks/CMakeLists.txt @@ -0,0 +1,32 @@ +# 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) +#------------------------------------------------------------------------------ +# Quest benchmarks +#------------------------------------------------------------------------------ + +set(quest_benchmark_files + quest_delaunay_circumsphere.cpp + ) + +if (ENABLE_BENCHMARKS) + foreach(test ${quest_benchmark_files}) + get_filename_component(test_name ${test} NAME_WE) + set(test_name "${test_name}_benchmark") + + axom_add_executable( + NAME ${test_name} + SOURCES ${test} + OUTPUT_DIR ${TEST_OUTPUT_DIRECTORY} + DEPENDS_ON quest gbenchmark + FOLDER axom/quest/benchmarks + ) + + blt_add_benchmark( + NAME ${test_name} + COMMAND ${test_name} --benchmark_min_time=0.0001s + ) + endforeach() +endif() diff --git a/src/axom/quest/benchmarks/quest_delaunay_circumsphere.cpp b/src/axom/quest/benchmarks/quest_delaunay_circumsphere.cpp new file mode 100644 index 0000000000..fd6b861a13 --- /dev/null +++ b/src/axom/quest/benchmarks/quest_delaunay_circumsphere.cpp @@ -0,0 +1,300 @@ +// 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 "benchmark/benchmark.h" + +#include "axom/core/numerics/Determinants.hpp" +#include "axom/primal.hpp" + +#include +#include + +namespace +{ +using axom::numerics::determinant; + +template +using PointType = axom::primal::Point; + +template +using VectorType = axom::primal::Vector; + +template +struct CircumsphereEval +{ + PointType center {}; + double radius_sq {0.}; + + CircumsphereEval() = default; + + CircumsphereEval(const PointType& origin, const VectorType& center_offset) + : center(origin + center_offset) + , radius_sq(center_offset.squared_norm()) + { } +}; + +template +using SimplexType = std::array, DIM + 1>; + +template +using SampleSet = std::array, 4096>; + +double unitInterval(std::uint64_t bits) +{ + return static_cast(bits & 0xFFFFFFFFu) / static_cast(0x100000000ULL); +} + +std::uint64_t stepLcg(std::uint64_t state) +{ + return state * 6364136223846793005ULL + 1442695040888963407ULL; +} + +template +SampleSet makeSamples(); + +template <> +SampleSet<2> makeSamples<2>() +{ + SampleSet<2> samples; + std::uint64_t state = 0x1234abcdu; + + for(auto& simplex : samples) + { + state = stepLcg(state); + const double bx = 0.8 * unitInterval(state); + state = stepLcg(state); + const double by = 0.8 * unitInterval(state); + state = stepLcg(state); + const double s0 = 0.01 + 0.04 * unitInterval(state); + state = stepLcg(state); + const double s1 = 0.01 + 0.04 * unitInterval(state); + state = stepLcg(state); + const double t0 = 0.01 + 0.04 * unitInterval(state); + state = stepLcg(state); + const double t1 = 0.01 + 0.04 * unitInterval(state); + + simplex[0] = PointType<2> {bx, by}; + simplex[1] = PointType<2> {bx + s0, by + 0.15 * s1}; + simplex[2] = PointType<2> {bx + 0.2 * t0, by + t1}; + } + + return samples; +} + +template <> +SampleSet<3> makeSamples<3>() +{ + SampleSet<3> samples; + std::uint64_t state = 0x9e3779b97f4a7c15ULL; + + for(auto& simplex : samples) + { + state = stepLcg(state); + const double bx = 0.75 * unitInterval(state); + state = stepLcg(state); + const double by = 0.75 * unitInterval(state); + state = stepLcg(state); + const double bz = 0.75 * unitInterval(state); + + state = stepLcg(state); + const double a0 = 0.01 + 0.03 * unitInterval(state); + state = stepLcg(state); + const double a1 = 0.01 + 0.03 * unitInterval(state); + state = stepLcg(state); + const double b0 = 0.01 + 0.03 * unitInterval(state); + state = stepLcg(state); + const double b1 = 0.01 + 0.03 * unitInterval(state); + state = stepLcg(state); + const double c0 = 0.01 + 0.03 * unitInterval(state); + state = stepLcg(state); + const double c1 = 0.01 + 0.03 * unitInterval(state); + + simplex[0] = PointType<3> {bx, by, bz}; + simplex[1] = PointType<3> {bx + a0, by + 0.1 * a1, bz + 0.05 * a1}; + simplex[2] = PointType<3> {bx + 0.15 * b0, by + b1, bz + 0.12 * b0}; + simplex[3] = PointType<3> {bx + 0.08 * c0, by + 0.18 * c0, bz + c1}; + } + + return samples; +} + +template +inline double consumeEval(const CircumsphereEval& eval) +{ + double value = eval.radius_sq; + for(int dim = 0; dim < DIM; ++dim) + { + value += eval.center[dim]; + } + return value; +} + +inline CircumsphereEval<2> evaluateCircumsphereScalarEdges(const SimplexType<2>& simplex) +{ + const PointType<2>& p0 = simplex[0]; + const PointType<2>& p1 = simplex[1]; + const PointType<2>& p2 = simplex[2]; + + const double vx0 = p1[0] - p0[0]; + const double vx1 = p2[0] - p0[0]; + const double vy0 = p1[1] - p0[1]; + const double vy1 = p2[1] - p0[1]; + + const double sq0 = vx0 * vx0 + vy0 * vy0; + const double sq1 = vx1 * vx1 + vy1 * vy1; + + const double a = determinant(vx0, vx1, vy0, vy1); + const double eps = (a >= 0.) ? axom::primal::PRIMAL_TINY : -axom::primal::PRIMAL_TINY; + const double ood = 1. / (2. * a + eps); + + const double center_offset_x = determinant(sq0, sq1, vy0, vy1) * ood; + const double center_offset_y = -determinant(sq0, sq1, vx0, vx1) * ood; + + return CircumsphereEval<2>(p0, VectorType<2> {center_offset_x, center_offset_y}); +} + +inline CircumsphereEval<2> evaluateCircumsphereVectorEdges(const SimplexType<2>& simplex) +{ + const PointType<2>& p0 = simplex[0]; + const PointType<2>& p1 = simplex[1]; + const PointType<2>& p2 = simplex[2]; + + const VectorType<2> v0(p0, p1); + const VectorType<2> v1(p0, p2); + + const double sq0 = v0.squared_norm(); + const double sq1 = v1.squared_norm(); + + const double a = determinant(v0[0], v1[0], v0[1], v1[1]); + const double eps = (a >= 0.) ? axom::primal::PRIMAL_TINY : -axom::primal::PRIMAL_TINY; + const double ood = 1. / (2. * a + eps); + + const double center_offset_x = determinant(sq0, sq1, v0[1], v1[1]) * ood; + const double center_offset_y = -determinant(sq0, sq1, v0[0], v1[0]) * ood; + + return CircumsphereEval<2>(p0, VectorType<2> {center_offset_x, center_offset_y}); +} + +inline CircumsphereEval<3> evaluateCircumsphereScalarEdges(const SimplexType<3>& simplex) +{ + const PointType<3>& p0 = simplex[0]; + const PointType<3>& p1 = simplex[1]; + const PointType<3>& p2 = simplex[2]; + const PointType<3>& p3 = simplex[3]; + + const double vx0 = p1[0] - p0[0]; + const double vx1 = p2[0] - p0[0]; + const double vx2 = p3[0] - p0[0]; + const double vy0 = p1[1] - p0[1]; + const double vy1 = p2[1] - p0[1]; + const double vy2 = p3[1] - p0[1]; + const double vz0 = p1[2] - p0[2]; + const double vz1 = p2[2] - p0[2]; + const double vz2 = p3[2] - p0[2]; + + const double sq0 = vx0 * vx0 + vy0 * vy0 + vz0 * vz0; + const double sq1 = vx1 * vx1 + vy1 * vy1 + vz1 * vz1; + const double sq2 = vx2 * vx2 + vy2 * vy2 + vz2 * vz2; + + const double a = determinant(vx0, vx1, vx2, vy0, vy1, vy2, vz0, vz1, vz2); + const double eps = (a >= 0.) ? axom::primal::PRIMAL_TINY : -axom::primal::PRIMAL_TINY; + const double ood = 1. / (2. * a + eps); + + const double center_offset_x = determinant(sq0, sq1, sq2, vy0, vy1, vy2, vz0, vz1, vz2) * ood; + const double center_offset_y = determinant(sq0, sq1, sq2, vz0, vz1, vz2, vx0, vx1, vx2) * ood; + const double center_offset_z = determinant(sq0, sq1, sq2, vx0, vx1, vx2, vy0, vy1, vy2) * ood; + + return CircumsphereEval<3>(p0, VectorType<3> {center_offset_x, center_offset_y, center_offset_z}); +} + +inline CircumsphereEval<3> evaluateCircumsphereVectorEdges(const SimplexType<3>& simplex) +{ + const PointType<3>& p0 = simplex[0]; + const PointType<3>& p1 = simplex[1]; + const PointType<3>& p2 = simplex[2]; + const PointType<3>& p3 = simplex[3]; + + const VectorType<3> v0(p0, p1); + const VectorType<3> v1(p0, p2); + const VectorType<3> v2(p0, p3); + + const double sq0 = v0.squared_norm(); + const double sq1 = v1.squared_norm(); + const double sq2 = v2.squared_norm(); + + const double a = determinant(v0[0], v1[0], v2[0], v0[1], v1[1], v2[1], v0[2], v1[2], v2[2]); + const double eps = (a >= 0.) ? axom::primal::PRIMAL_TINY : -axom::primal::PRIMAL_TINY; + const double ood = 1. / (2. * a + eps); + + const double center_offset_x = + determinant(sq0, sq1, sq2, v0[1], v1[1], v2[1], v0[2], v1[2], v2[2]) * ood; + const double center_offset_y = + determinant(sq0, sq1, sq2, v0[2], v1[2], v2[2], v0[0], v1[0], v2[0]) * ood; + const double center_offset_z = + determinant(sq0, sq1, sq2, v0[0], v1[0], v2[0], v0[1], v1[1], v2[1]) * ood; + + return CircumsphereEval<3>(p0, VectorType<3> {center_offset_x, center_offset_y, center_offset_z}); +} + +template +void runCircumsphereBenchmark(benchmark::State& state, Kernel&& kernel) +{ + const auto& samples = []() -> const SampleSet& { + static const SampleSet value = makeSamples(); + return value; + }(); + + const std::size_t mask = samples.size() - 1; + std::size_t idx = 0; + double checksum = 0.; + + for(auto _ : state) + { + const auto& simplex = samples[idx]; + checksum += consumeEval(kernel(simplex)); + idx = (idx + 1) & mask; + } + + benchmark::DoNotOptimize(checksum); + state.SetItemsProcessed(static_cast(state.iterations())); +} + +void benchmark_scalar_edges_2d(benchmark::State& state) +{ + runCircumsphereBenchmark<2>(state, [](const SimplexType<2>& simplex) { + return evaluateCircumsphereScalarEdges(simplex); + }); +} + +void benchmark_vector_edges_2d(benchmark::State& state) +{ + runCircumsphereBenchmark<2>(state, [](const SimplexType<2>& simplex) { + return evaluateCircumsphereVectorEdges(simplex); + }); +} + +void benchmark_scalar_edges_3d(benchmark::State& state) +{ + runCircumsphereBenchmark<3>(state, [](const SimplexType<3>& simplex) { + return evaluateCircumsphereScalarEdges(simplex); + }); +} + +void benchmark_vector_edges_3d(benchmark::State& state) +{ + runCircumsphereBenchmark<3>(state, [](const SimplexType<3>& simplex) { + return evaluateCircumsphereVectorEdges(simplex); + }); +} + +} // namespace + +BENCHMARK(benchmark_scalar_edges_2d); +BENCHMARK(benchmark_vector_edges_2d); +BENCHMARK(benchmark_scalar_edges_3d); +BENCHMARK(benchmark_vector_edges_3d); + +BENCHMARK_MAIN(); From af47da08cc4215b8f07c8866741b13a54033c8aa Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 15 Apr 2026 10:44:03 -0700 Subject: [PATCH 509/986] Refactors Delauanay class into several detail files separating concerns --- src/axom/quest/CMakeLists.txt | 4 + src/axom/quest/Delaunay.hpp | 2782 ++--------------- .../quest_delaunay_circumsphere.cpp | 322 ++ .../quest/detail/DelaunayElementFinder.hpp | 226 ++ src/axom/quest/detail/DelaunayImpl.hpp | 1360 ++++++++ .../quest/detail/DelaunayInsertionHelper.hpp | 258 ++ .../quest/detail/DelaunayPointLocation.hpp | 554 ++++ 7 files changed, 2959 insertions(+), 2547 deletions(-) create mode 100644 src/axom/quest/detail/DelaunayElementFinder.hpp create mode 100644 src/axom/quest/detail/DelaunayImpl.hpp create mode 100644 src/axom/quest/detail/DelaunayInsertionHelper.hpp create mode 100644 src/axom/quest/detail/DelaunayPointLocation.hpp diff --git a/src/axom/quest/CMakeLists.txt b/src/axom/quest/CMakeLists.txt index 4cf6454823..f8df13e6be 100644 --- a/src/axom/quest/CMakeLists.txt +++ b/src/axom/quest/CMakeLists.txt @@ -21,6 +21,10 @@ axom_component_requires(NAME QUEST set( quest_headers Delaunay.hpp + detail/DelaunayElementFinder.hpp + detail/DelaunayImpl.hpp + detail/DelaunayInsertionHelper.hpp + detail/DelaunayPointLocation.hpp LinearizeCurves.hpp SignedDistance.hpp diff --git a/src/axom/quest/Delaunay.hpp b/src/axom/quest/Delaunay.hpp index 86fbdb967e..727c30d77a 100644 --- a/src/axom/quest/Delaunay.hpp +++ b/src/axom/quest/Delaunay.hpp @@ -4,6 +4,12 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +/** + * \file Delaunay.hpp + * + * \brief Defines an incremental 2D/3D Delaunay triangulation. + */ + #ifndef QUEST_DELAUNAY_H_ #define QUEST_DELAUNAY_H_ @@ -14,23 +20,27 @@ #include "axom/mint.hpp" #include "axom/spin.hpp" +#include "detail/DelaunayElementFinder.hpp" +#include "detail/DelaunayInsertionHelper.hpp" + #include "axom/fmt.hpp" -#include -#include -#include -#include #include #include -#include -#include #include +#include #include +#include +#include +#include +#include +#include namespace axom { namespace quest { + /** * \brief A class for incremental generation of a 2D or 3D Delaunay triangulation * @@ -76,6 +86,70 @@ class Delaunay Full }; + enum class PointLocationStatus + { + Found, + Outside, + Failed + }; + + struct PointLocationResult + { + IndexType element_idx {INVALID_INDEX}; + PointLocationStatus status {PointLocationStatus::Failed}; + }; + + struct InsertionStats + { + std::uint64_t insertions {0}; + std::uint64_t total_removed {0}; + std::uint64_t max_removed {0}; + + double mean_removed() const + { + return insertions > 0 ? static_cast(total_removed) / static_cast(insertions) + : 0.0; + } + }; + + struct PointLocationStats + { + std::uint64_t walk_calls {0}; + std::uint64_t walk_found {0}; + std::uint64_t walk_outside {0}; + std::uint64_t walk_failed {0}; + std::uint64_t total_walk_steps {0}; + std::uint64_t max_walk_steps {0}; + std::uint64_t linear_fallbacks {0}; + std::uint64_t empty_seed_fallbacks {0}; + + double mean_walk_steps() const + { + return walk_calls > 0 ? static_cast(total_walk_steps) / static_cast(walk_calls) + : 0.0; + } + }; + + struct CircumsphereEval + { + PointType center {}; + double radius_sq {0.}; + + CircumsphereEval() = default; + + CircumsphereEval(const PointType& origin, const VectorType& center_offset) + : center(origin + center_offset) + , radius_sq(center_offset.squared_norm()) + { } + }; + + struct OrientationEval + { + double det {0.}; + double tol {0.}; + int orientation {primal::ON_BOUNDARY}; + }; + private: using ModularFaceIndex = slam::ModularInt>; @@ -83,9 +157,16 @@ class Delaunay using FacetKey = typename IAMeshType::FacetKey; using FacetRecord = typename IAMeshType::FacetRecord; -private: - struct ElementFinder; - struct InsertionHelper; + using ElementFinder = + detail::DelaunayElementFinder; + using InsertionHelper = + detail::DelaunayInsertionHelper; + + // These broader fallbacks are only used for 3D query point location after the + // initial directed walk fails. Insertions stay on the cheaper local path. + static constexpr int QUERY_SEARCH_RADIUS = 6; + static constexpr int QUERY_CANDIDATE_LIMIT = 128; + static constexpr int WALK_NEIGHBORHOOD_LAYERS = 2; /** * \brief Lightweight LIFO pool of invalid simplex slots that can be reused @@ -149,41 +230,17 @@ class Delaunay std::unique_ptr m_insertion_helper; public: - struct InsertionStats - { - std::uint64_t insertions {0}; - std::uint64_t total_removed {0}; - std::uint64_t max_removed {0}; - double mean_removed() const - { - return insertions > 0 ? static_cast(total_removed) / static_cast(insertions) - : 0.0; - } - }; + /** + * \brief Default constructor + * \note User must call initializeBoundary(BoundingBox) before adding points. + */ + Delaunay() : m_has_boundary(false), m_insertion_validation_mode(InsertionValidationMode::None) { } InsertionStats getInsertionStats() const { return {m_num_insertions, m_total_removed_elements, m_max_removed_elements}; } - struct PointLocationStats - { - std::uint64_t walk_calls {0}; - std::uint64_t walk_found {0}; - std::uint64_t walk_outside {0}; - std::uint64_t walk_failed {0}; - std::uint64_t total_walk_steps {0}; - std::uint64_t max_walk_steps {0}; - std::uint64_t linear_fallbacks {0}; - std::uint64_t empty_seed_fallbacks {0}; - - double mean_walk_steps() const - { - return walk_calls > 0 ? static_cast(total_walk_steps) / static_cast(walk_calls) - : 0.0; - } - }; - void setCollectPointLocationStats(bool enabled) { m_collect_location_stats = enabled; } PointLocationStats getPointLocationStats() const @@ -198,13 +255,6 @@ class Delaunay m_num_empty_seed_fallbacks}; } -public: - /** - * \brief Default constructor - * \note User must call initializeBoundary(BoundingBox) before adding points. - */ - Delaunay() : m_has_boundary(false), m_insertion_validation_mode(InsertionValidationMode::None) { } - /// \brief Controls the amount of validation performed around each point insertion /// /// \note This is intended for debugging. `InsertionValidationMode::Full` is a diagnostic mode @@ -297,25 +347,7 @@ class Delaunay * \note The suffix ".vtk" will be appended to the provided filename * \details This function uses mint to write the m_mesh to VTK format. */ - void writeToVTKFile(const std::string& filename) - { - const auto CELL_TYPE = DIM == 2 ? mint::TRIANGLE : mint::TET; - mint::UnstructuredMesh mint_mesh(DIM, CELL_TYPE); - - this->compactMesh(); - - for(auto v : m_mesh.vertices().positions()) - { - mint_mesh.appendNodes(m_mesh.getVertexPosition(v).data(), 1); - } - - for(auto e : m_mesh.elements().positions()) - { - mint_mesh.appendCell(&(m_mesh.boundaryVertices(e)[0]), CELL_TYPE); - } - - mint::write_vtk(&mint_mesh, filename); - } + void writeToVTKFile(const std::string& filename); /** * \brief Removes the vertices that defines the boundary of the mesh, @@ -323,66 +355,7 @@ class Delaunay * * \details After this function is called, no more points can be added to the m_mesh. */ - void removeBoundary() - { - if(m_has_boundary) - { - //remove the boundary box, which will be the first 4 points for triangles, first 8 for tetrahedron - const int num_boundary_pts = 1 << DIM; - - // Remove all elements incident to boundary vertices. Avoid relying on - // `vertexStar()` here since it may be incomplete when the mesh is not - // manifold around the temporary boundary. - for(auto e : m_mesh.elements().positions()) - { - if(m_mesh.isValidElement(e)) - { - const auto verts = m_mesh.boundaryVertices(e); - bool touches_boundary = false; - for(int i = 0; i < VERT_PER_ELEMENT; ++i) - { - touches_boundary |= (verts[i] >= 0 && verts[i] < num_boundary_pts); - } - - if(touches_boundary) - { - m_mesh.removeElement(e); - } - } - } - - for(int v = 0; v < num_boundary_pts; ++v) - { - m_mesh.removeVertex(v); - } - - // Defensive cleanup: ensure no valid element references a removed vertex. - // This can happen if the boundary-vertex star is non-manifold and - // `removeVertex()` cannot discover all incident elements via adjacency. - for(auto e : m_mesh.elements().positions()) - { - if(!m_mesh.isValidElement(e)) - { - continue; - } - - const auto verts = m_mesh.boundaryVertices(e); - bool has_invalid_vertex = false; - for(int i = 0; i < VERT_PER_ELEMENT; ++i) - { - has_invalid_vertex |= !m_mesh.isValidVertex(verts[i]); - } - - if(has_invalid_vertex) - { - m_mesh.removeElement(e); - } - } - - this->compactMesh(); - m_has_boundary = false; - } - } + void removeBoundary(); /// \brief Get the IA mesh data pointer const IAMeshType* getMeshData() const { return &m_mesh; } @@ -390,196 +363,10 @@ class Delaunay /** * \brief Checks that the underlying mesh is a valid Delaunay triangulation of the point set * - * A Delaunay triangulation is valid when none of the vertices are inside the circumspheres + * A Delaunay triangulation is valid when none of the vertices are inside the circumspheres * of any of the elements of the mesh */ - bool isValid(bool verboseOutput = false) const - { - // Implementation note: We use an UniformGrid spatial index to find the candidate elements - // whose circumsphere might contain the vertices of the mesh - // To build this faster, we bootstrap the UniformGrid with an ImplicitGrid - - using ImplicitGridType = spin::ImplicitGrid; - using UniformGridType = spin::UniformGrid; - using NumericArrayType = NumericArray; - using axom::numerics::dot_product; - - bool valid = true; - - std::vector> invalidEntries; - std::vector invalidElements; - - const IndexType totalVertices = m_mesh.vertices().size(); - const IndexType totalElements = m_mesh.elements().size(); - const IndexType res = axom::utilities::ceil(0.33 * std::pow(totalVertices, 1. / DIM)); - UniformGridType grid(m_bounding_box, NumericArray(res).data()); - - // An array to cache the circumspheres associated with each element - axom::Array circumspheres(totalElements); - - auto vertexInsideCircumsphere = [&](const PointType& vertex, IndexType element_idx) { - // Use the same mesh-side geometric circumsphere classifier as insertion so - // the empty-circumsphere validation follows the exact same inside/boundary - // decisions. Boundary cases are treated as "not inside" for global checks. - return isPointInSphereOnMesh(m_mesh, vertex, element_idx, /*includeBoundary=*/false); - }; - - auto circumsphereSignedDistanceTol = [](const typename ElementType::SphereType& sphere, - const PointType& x) { - const auto& center = sphere.getCenter(); - double scale = axom::utilities::max(1., sphere.getRadius()); - for(int dim = 0; dim < DIM; ++dim) - { - scale = axom::utilities::max(scale, axom::utilities::abs(center[dim])); - scale = axom::utilities::max(scale, axom::utilities::abs(x[dim])); - } - - // Signed distance to a sphere involves a subtraction after a norm. Use a - // tolerance proportional to the coordinate/radius scale to give a - // meaningful diagnostic in the verbose output. - return 256. * std::numeric_limits::epsilon() * scale; - }; - - // bootstrap the uniform grid using an implicit grid - { - using GridCell = typename ImplicitGridType::GridCell; - - // Add (bounding boxes of) element circumspheres to temporary implicit grid - const auto resCell = GridCell(res); - ImplicitGridType implicitGrid(m_bounding_box, &resCell, totalElements); - for(auto element_idx : m_mesh.elements().positions()) - { - if(m_mesh.isValidElement(element_idx)) - { - const auto verts = m_mesh.boundaryVertices(element_idx); - bool has_all_vertices = true; - for(int i = 0; i < VERT_PER_ELEMENT; ++i) - { - has_all_vertices &= m_mesh.isValidVertex(verts[i]); - } - - if(!has_all_vertices) - { - valid = false; - if(verboseOutput) - { - invalidElements.push_back(element_idx); - } - continue; - } - - circumspheres[element_idx] = this->getElement(element_idx).circumsphere(); - const auto& sphere = circumspheres[element_idx]; - const auto& center = sphere.getCenter().array(); - const auto offset = NumericArrayType(sphere.getRadius()); - - BoundingBox bb; - bb.addPoint(PointType(center - offset)); - bb.addPoint(PointType(center + offset)); - - implicitGrid.insert(bb, element_idx); // insert valid entries into grid - } - } - - // copy candidates from implicit grid directly into uniform grid - const int kUpper = (DIM == 2) ? 0 : res; - const IndexType stride[3] = {1, res, (DIM == 2) ? 0 : res * res}; - for(IndexType k = 0; k < kUpper; ++k) - { - for(IndexType j = 0; j < res; ++j) - { - for(IndexType i = 0; i < res; ++i) - { - const IndexType vals[3] = {i, j, k}; - const GridCell cell(vals); - const auto idx = dot_product(cell.data(), stride, DIM); - const auto binValues = implicitGrid.getCandidatesAsArray(cell); - grid.getBinContents(idx).insert(0, binValues.size(), binValues.data()); - } - } - } - } - - // for each vertex -- check in_sphere condition for candidate element - for(auto vertex_idx : m_mesh.vertices().positions()) - { - // skip if vertex at this index is not valid - if(!m_mesh.isValidVertex(vertex_idx)) - { - continue; - } - - const auto& vertex = m_mesh.getVertexPosition(vertex_idx); - for(const auto element_idx : grid.getBinContents(grid.getBinIndex(vertex))) - { - // no need to check for invalid elements -- only valid elements were added to grid - - // skip if this is a vertex of the element - if(slam::is_subset(vertex_idx, m_mesh.boundaryVertices(element_idx))) - { - continue; - } - - // check insphere condition - if(vertexInsideCircumsphere(vertex, element_idx)) - { - valid = false; - - if(verboseOutput) - { - invalidEntries.push_back(std::make_pair(vertex_idx, element_idx)); - } - } - } - } - - if(verboseOutput) - { - if(valid) - { - SLIC_INFO("Delaunay complex was valid"); - } - else - { - fmt::memory_buffer out; - if(!invalidElements.empty()) - { - fmt::format_to(std::back_inserter(out), - "\n\t{} valid elements referenced invalid vertices: {}", - invalidElements.size(), - fmt::join(invalidElements, ", ")); - } - for(const auto& pr : invalidEntries) - { - const auto vertex_idx = pr.first; - const auto element_idx = pr.second; - const auto& pos = m_mesh.getVertexPosition(vertex_idx); - const auto element = this->getElement(element_idx); - const auto circumsphere = element.circumsphere(); - const double tol = circumsphereSignedDistanceTol(circumsphere, pos); - fmt::format_to(std::back_inserter(out), - "\n\tVertex {} @ {}" - "\n\tElement {}: {} w/ circumsphere: {}" - "\n\tDistance to circumcenter: {} (tol={})", - vertex_idx, - pos, - element_idx, - element, - circumsphere, - circumsphere.computeSignedDistance(pos), - tol); - } - - SLIC_INFO( - fmt::format("Delaunay complex was NOT valid. There were {} " - "vertices in the circumsphere of an element. {}", - invalidEntries.size(), - fmt::to_string(out))); - } - } - - return valid; - } + bool isValid(bool verboseOutput = false) const; /** * \brief Checks that the underlying mesh is conforming and consistently oriented @@ -590,159 +377,116 @@ class Delaunay * initial bounding-box boundary is still present) that boundary facets lie on * that bounding box. */ - bool isConforming(bool verboseOutput = false) const - { - fmt::memory_buffer out; + bool isConforming(bool verboseOutput = false) const; + + /// \brief Returns true when an element slot is active and can participate in point-location + /// + /// Point-location only needs to reject tombstones here. During incremental + /// insertion all active simplices retain valid vertices, and `removeBoundary()` + /// compacts before post-build query use. + bool isSearchableElement(IndexType element_idx) const; - bool valid = m_mesh.isConforming(verboseOutput); + /// \brief Find the index of the element that contains the query point, or the element closest to the point. + IndexType findContainingElement(const PointType& query_pt, bool warnOnInvalid = true) const; - // Geometry-specific checks that do not belong in slam::IAMesh. - for(auto element_idx : m_mesh.elements().positions()) - { - if(!m_mesh.isValidElement(element_idx)) - { - continue; - } + /** + * \brief helper function to retrieve the barycentric coordinate of the query point in the element + */ + BaryCoordType getBaryCoords(IndexType element_idx, const PointType& q_pt) const; - // check orientations of top simplices - const auto orient = evaluateElementOrientationDeterminant(element_idx); - if(orient.orientation != primal::ON_POSITIVE_SIDE) - { - if(verboseOutput) - { - fmt::format_to( - std::back_inserter(out), - "\n\tElement {} has non-positive orientation determinant {:.17g} (tol={:.3g})", - element_idx, - orient.det, - orient.tol); - } - valid = false; - } + /// \brief Returns cavity seed elements based on the simplex feature containing the query point + IndexArray getSeedElements(IndexType element_idx, const BaryCoordType& bary_coord) const + { + IndexArray seed_elements; + seed_elements.push_back(element_idx); + constexpr IndexType invalid_element = IAMeshType::INVALID_ELEMENT_INDEX; - // check that boundary elements are not on the Delaunay bounding box - if(m_has_boundary) + for(int i = 0; i < VERT_PER_ELEMENT; ++i) + { + if(axom::utilities::abs(bary_coord[i]) <= BARY_EPS) { - const auto neighbors = m_mesh.adjacentElements(element_idx); - for(int facet_idx = 0; facet_idx < VERT_PER_ELEMENT; ++facet_idx) + const IndexType nbr = m_mesh.adjacentElements(element_idx)[ModularFaceIndex(i) + 1]; + if(nbr != invalid_element) { - if(m_mesh.isValidElement(neighbors[facet_idx])) - { - continue; - } - - const FacetKey facet_key = m_mesh.getSortedFacetKey(element_idx, facet_idx); - if(!isFacetOnBoundingBox(facet_key)) - { - if(verboseOutput) - { - fmt::format_to(std::back_inserter(out), - "\n\tBoundary facet {} is not on the initial bounding box", - facetKeyString(facet_key)); - } - valid = false; - } + SLIC_ASSERT(m_mesh.isValidElement(nbr)); + seed_elements.push_back(nbr); } } } - if(verboseOutput) + return seed_elements; + } + + static int signWithTolerance(double value, double tolerance) + { + return value > tolerance ? 1 : (value < -tolerance ? -1 : 0); + } + + template + static double getPointMagnitudeScale(const std::array& pts) + { + double max_abs_coord = 1.; + for(const auto& pt : pts) { - if(valid) - { - SLIC_INFO("Delaunay mesh was conforming"); - } - else + for(int dim = 0; dim < DIM; ++dim) { - SLIC_INFO("Delaunay mesh was NOT conforming. Summary: " << fmt::to_string(out)); + max_abs_coord = axom::utilities::max(max_abs_coord, axom::utilities::abs(pt[dim])); } } - return valid; + return max_abs_coord; } -private: - template - static FacetKey makeSortedFaceKey(const FacetSubsetType& facet) - { - // Canonical key for a simplex facet: store the vertex ids in sorted order - // so the same topological facet is mapped identically regardless of - // orientation or local indexing. - FacetKey key {}; - for(int i = 0; i < VERTS_PER_FACET; ++i) - { - key[i] = facet[i]; - } - std::sort(key.begin(), key.end()); - return key; - } + static double orientationTolerance(const std::array& pts); - static std::string facetKeyString(const FacetKey& facet_key) - { - return fmt::format("[{}]", fmt::join(facet_key, ", ")); - } + static double determinant3(const PointType& p0, const PointType& p1, const PointType& p2); - double getBoundaryCoordinateTolerance() const - { - const auto min_pt = m_bounding_box.getMin(); - const auto max_pt = m_bounding_box.getMax(); + static double orientationDeterminant(const std::array& pts); - double max_extent = 1.; - for(int dim = 0; dim < DIM; ++dim) - { - max_extent = axom::utilities::max(max_extent, max_pt[dim] - min_pt[dim]); - } + static int symbolicOrientationSign(const std::array& pts, + const std::array& ranks); - return 64. * std::numeric_limits::epsilon() * max_extent; - } + static CircumsphereEval evaluateCircumsphereOnMesh(const IAMeshType& mesh, IndexType element_idx); - bool isFacetOnBoundingBox(const FacetKey& facet_key) const - { - // During incremental construction, the triangulation includes an initial - // bounding box (or cube) to guarantee the domain is closed. When that - // temporary boundary is present, any facet with an invalid neighbor must - // lie on one of the bounding box planes within a small coordinate tolerance. - const auto& min_pt = m_bounding_box.getMin(); - const auto& max_pt = m_bounding_box.getMax(); - const double tol = getBoundaryCoordinateTolerance(); - - for(int dim = 0; dim < DIM; ++dim) - { - bool on_min_face = true; - bool on_max_face = true; - for(const IndexType vertex_idx : facet_key) - { - const auto& vertex = m_mesh.getVertexPosition(vertex_idx); - on_min_face &= axom::utilities::abs(vertex[dim] - min_pt[dim]) <= tol; - on_max_face &= axom::utilities::abs(vertex[dim] - max_pt[dim]) <= tol; - } + static double sphereSquaredDistanceTolerance(const CircumsphereEval& sphere, + const PointType& x, + double distance_sq); - if(on_min_face || on_max_face) - { - return true; - } - } + template + static double sphereSignedDistanceTolerance(const SphereType& sphere, const PointType& x); - return false; - } + static int inSphereOrientationOnMesh(const IAMeshType& mesh, + const PointType& q, + IndexType element_idx); - double getElementSignedMeasure(IndexType element_idx) const - { - if constexpr(DIM == 2) - { - return getElement(element_idx).signedArea(); - } - else - { - return getElement(element_idx).signedVolume(); - } - } + static double inSphereDeterminantOnMesh(const IAMeshType& mesh, + const PointType& q, + IndexType element_idx); - double getElementMeasureTolerance() const - { - const double scale = axom::utilities::max(1., m_bounding_box.range().norm()); - return 256. * std::numeric_limits::epsilon() * std::pow(scale, static_cast(DIM)); - } + static const char* orientationResultName(int result); + + static bool isPointInSphereOnMesh(const IAMeshType& mesh, + const PointType& q, + IndexType element_idx, + bool includeBoundary); + + static int classifyOrientationDeterminant(double det, double tol); + + OrientationEval evaluateElementOrientationDeterminant(IndexType element_idx) const; + +private: + template + static FacetKey makeSortedFaceKey(const FacetSubsetType& facet); + + static std::string facetKeyString(const FacetKey& facet_key); + + double getBoundaryCoordinateTolerance() const; + + bool isFacetOnBoundingBox(const FacetKey& facet_key) const; + + double getElementSignedMeasure(IndexType element_idx) const; + + double getElementMeasureTolerance() const; /** * \brief Validates the point-location result and cavity seed selection before building the cavity @@ -754,76 +498,7 @@ class Delaunay void validateInsertionSeed(IndexType element_idx, const PointType& query_pt, const BaryCoordType& bary_coord, - const IndexArray& seed_elements) const - { - if(m_insertion_validation_mode == InsertionValidationMode::None) - { - return; - } - - fmt::memory_buffer out; - bool valid = true; - - if(!isSearchableElement(element_idx)) - { - fmt::format_to(std::back_inserter(out), - "\n\tContaining element {} is not searchable", - element_idx); - valid = false; - } - else - { - const auto verts = m_mesh.boundaryVertices(element_idx); - for(auto idx : verts.positions()) - { - if(!m_mesh.isValidVertex(verts[idx])) - { - fmt::format_to(std::back_inserter(out), - "\n\tContaining element {} references invalid vertex {}", - element_idx, - verts[idx]); - valid = false; - break; - } - } - } - - if(!isPointInsideForLocation(element_idx, query_pt, bary_coord)) - { - fmt::format_to(std::back_inserter(out), - "\n\tPoint {} is not inside containing element {}", - query_pt, - element_idx); - valid = false; - } - - if(seed_elements.empty()) - { - fmt::format_to(std::back_inserter(out), "\n\tNo cavity seed elements were generated"); - valid = false; - } - - bool found_containing_element = false; - for(const IndexType seed_element : seed_elements) - { - found_containing_element |= (seed_element == element_idx); - if(!m_mesh.isValidElement(seed_element)) - { - fmt::format_to(std::back_inserter(out), "\n\tSeed element {} is invalid", seed_element); - valid = false; - } - } - - if(!found_containing_element) - { - fmt::format_to(std::back_inserter(out), - "\n\tContaining element {} is missing from the seed set", - element_idx); - valid = false; - } - - SLIC_ERROR_IF(!valid, "Delaunay insertion seed validation failed:" << fmt::to_string(out)); - } + const IndexArray& seed_elements) const; /** * \brief Validates that the cavity boundary facets match the faces between cavity and non-cavity elements @@ -847,2064 +522,77 @@ class Delaunay * \brief Validates that the global mesh invariants still hold after insertion * * `ConformingMesh` checks IA validity and topological conformity, plus - * Delaunay's geometry-specific checks (positive simplex orientation and - * bounding-box boundary consistency while the fake boundary exists). + * Delaunay's geometry-specific checks (positive simplex orientation and + * bounding-box boundary consistency while the fake boundary exists). * `Full` additionally runs the global Delaunay empty-circumsphere validation. */ - void validateInsertionResult() const - { - if(m_insertion_validation_mode == InsertionValidationMode::None) - { - return; - } + void validateInsertionResult() const; - if(m_insertion_validation_mode == InsertionValidationMode::Local) - { - return; - } + /// \brief Predicate for when to compact internal mesh data structures after removing elements + bool shouldCompactMesh() const; - // Note: These checks are intentionally global and can be expensive. They - // are only enabled when the caller opts into insertion validation. - if(!m_mesh.isValid(false)) - { - m_mesh.isValid(true); - SLIC_ERROR("Delaunay insertion produced an invalid IAMesh"); - } + /// \brief Compacts the underlying mesh + void compactMesh(); - if(!isConforming(false)) - { - isConforming(true); - SLIC_ERROR("Delaunay insertion produced a non-conforming triangulation"); - } + BaryCoordType getRawBarycentricDeterminants(IndexType element_idx, const PointType& query_pt) const; - if(m_insertion_validation_mode == InsertionValidationMode::Full) - { - if(!isValid(false)) - { - isValid(true); - SLIC_ERROR( - "Delaunay insertion produced a triangulation that violates the empty-circumsphere " - "condition"); - } - } - } + double rawBarycentricDeterminantTolerance(IndexType element_idx, const PointType& query_pt) const; -public: - /// \brief Returns true when an element slot is active and can participate in point-location - /// - /// Point-location only needs to reject tombstones here. During incremental - /// insertion all active simplices retain valid vertices, and `removeBoundary()` - /// compacts before post-build query use. - bool isSearchableElement(IndexType element_idx) const - { - return m_mesh.isValidElement(element_idx); - } + bool isPointInsideForLocation(IndexType element_idx, + const PointType& query_pt, + const BaryCoordType& bary_coord, + ModularFaceIndex* exit_face = nullptr) const; - enum class PointLocationStatus - { - Found, - Outside, - Failed - }; + PointLocationResult walkToContainingElement( + const PointType& query_pt, + IndexType start_element, + std::vector* visited_elements_out = nullptr) const; - struct PointLocationResult - { - IndexType element_idx {INVALID_INDEX}; - PointLocationStatus status {PointLocationStatus::Failed}; - }; + void appendCandidateElement(std::vector& candidate_elements, IndexType vertex_i) const; - // These broader fallbacks are only used for 3D query point location after the - // initial directed walk fails. Insertions stay on the cheaper local path. - static constexpr int QUERY_SEARCH_RADIUS = 6; - static constexpr int QUERY_CANDIDATE_LIMIT = 128; - static constexpr int WALK_NEIGHBORHOOD_LAYERS = 2; + void appendCandidateElementsFromVertices(std::vector& candidate_elements, + const std::vector& candidate_vertices) const; - /// \brief Find the index of the element that contains the query point, or the element closest to the point. - IndexType findContainingElement(const PointType& query_pt, bool warnOnInvalid = true) const - { - if(m_mesh.isEmpty()) - { - SLIC_ERROR_IF(warnOnInvalid, - "Attempting to insert point into empty Delaunay triangulation." - "Delaunay::initializeBoundary() needs to be called first"); - return INVALID_INDEX; - } - if(!m_bounding_box.contains(query_pt)) - { - SLIC_WARNING_IF(warnOnInvalid, - "Attempting to locate element at location outside valid domain"); - return INVALID_INDEX; - } + void getInitialCandidateElements(const PointType& query_pt, + std::vector& candidate_elements) const; - m_candidate_elements_scratch.clear(); - getInitialCandidateElements(query_pt, m_candidate_elements_scratch); - m_walked_elements_scratch.clear(); - PointLocationResult walk_result = - walkCandidateElements(query_pt, m_candidate_elements_scratch, 0, &m_walked_elements_scratch); + PointLocationResult walkCandidateElements(const PointType& query_pt, + const std::vector& candidate_elements, + std::size_t start_idx = 0, + std::vector* walked_elements = nullptr) const; - if(walk_result.status == PointLocationStatus::Found) - { - return walk_result.element_idx; - } + PointLocationResult findContainingElementWithQueryFallbacks( + const PointType& query_pt, + std::vector& candidate_elements, + const std::vector& walked_elements) const; - if(walk_result.status == PointLocationStatus::Outside) - { - return INVALID_INDEX; - } + /// \brief Scan a small adjacency region around a failed directed walk before falling back to a full scan + IndexType findContainingElementFromNeighbors(const PointType& query_pt, + const std::vector& seed_elements) const; - // Local recovery before falling back to a global scan. These steps are - // intentionally conservative: they only succeed when they find a simplex - // whose barycentric coordinates are non-negative (up to tolerance). - if(!m_candidate_elements_scratch.empty()) - { - const PointLocationResult fallback_result = - findContainingElementWithQueryFallbacks(query_pt, - m_candidate_elements_scratch, - m_walked_elements_scratch); - if(fallback_result.status == PointLocationStatus::Found) - { - return fallback_result.element_idx; - } - } + /// \brief Last-resort exhaustive scan used when the cheaper local search path cannot classify the point + IndexType findContainingElementLinear(const PointType& query_pt, bool warnOnInvalid) const; - if(m_collect_location_stats) - { - ++m_num_linear_fallbacks; - } - return findContainingElementLinear(query_pt, warnOnInvalid); - } + /// \brief Scan the stars of nearby seed vertices as a cheaper local fallback for query point location + IndexType findContainingElementNearby(const PointType& query_pt, + const std::vector& nearby_vertices) const; /** - * \brief helper function to retrieve the barycentric coordinate of the query point in the element + * \brief Helper function to fill the array with the initial mesh. + * \details create a rectangle for 2D, cube for 3D, and fill the array with the mesh data. */ - BaryCoordType getBaryCoords(IndexType element_idx, const PointType& q_pt) const; - - /// \brief Returns cavity seed elements based on the simplex feature containing the query point - IndexArray getSeedElements(IndexType element_idx, const BaryCoordType& bary_coord) const - { - IndexArray seed_elements; - seed_elements.push_back(element_idx); - constexpr IndexType invalid_element = IAMeshType::INVALID_ELEMENT_INDEX; - - for(int i = 0; i < VERT_PER_ELEMENT; ++i) - { - if(axom::utilities::abs(bary_coord[i]) <= BARY_EPS) - { - const IndexType nbr = m_mesh.adjacentElements(element_idx)[ModularFaceIndex(i) + 1]; - if(nbr != invalid_element) - { - SLIC_ASSERT(m_mesh.isValidElement(nbr)); - seed_elements.push_back(nbr); - } - } - } - - return seed_elements; - } - - static int signWithTolerance(double value, double tolerance) - { - return value > tolerance ? 1 : (value < -tolerance ? -1 : 0); - } - - template - static double getPointMagnitudeScale(const std::array& pts) - { - double max_abs_coord = 1.; - for(const auto& pt : pts) - { - for(int dim = 0; dim < DIM; ++dim) - { - max_abs_coord = axom::utilities::max(max_abs_coord, axom::utilities::abs(pt[dim])); - } - } - - return max_abs_coord; - } - - static double orientationTolerance(const std::array& pts) - { - const double scale = getPointMagnitudeScale(pts); - if constexpr(DIM == 2) - { - return 64. * std::numeric_limits::epsilon() * scale * scale; - } - else - { - return 64. * std::numeric_limits::epsilon() * scale * scale * scale; - } - } - - static double determinant3(const PointType& p0, const PointType& p1, const PointType& p2) - { - return axom::numerics::determinant(p0[0], p0[1], p0[2], p1[0], p1[1], p1[2], p2[0], p2[1], p2[2]); - } - - static double orientationDeterminant(const std::array& pts) - { - return axom::numerics::determinant(pts[0][0], - pts[0][1], - pts[0][2], - 1., - pts[1][0], - pts[1][1], - pts[1][2], - 1., - pts[2][0], - pts[2][1], - pts[2][2], - 1., - pts[3][0], - pts[3][1], - pts[3][2], - 1.); - } - - static int symbolicOrientationSign(const std::array& pts, - const std::array& ranks) - { - const double det = orientationDeterminant(pts); - const int det_sign = signWithTolerance(det, orientationTolerance(pts)); - if(det_sign != 0) - { - return det_sign; - } - - const std::array cofactors {-determinant3(pts[1], pts[2], pts[3]), - determinant3(pts[0], pts[2], pts[3]), - -determinant3(pts[0], pts[1], pts[3]), - determinant3(pts[0], pts[1], pts[2])}; - - std::array order {{0, 1, 2, 3}}; - std::sort(order.begin(), order.end(), [&](int lhs, int rhs) { return ranks[lhs] < ranks[rhs]; }); - - const double cofactor_tol = 64. * std::numeric_limits::epsilon() * - axom::utilities::max(1., orientationTolerance(pts)); - for(const int row : order) - { - const int sign = signWithTolerance(cofactors[row], cofactor_tol); - if(sign != 0) - { - return sign; - } - } - - return 0; - } - - //----------------------------------------------------------------------------- - // In-sphere predicate helpers - // - // Delaunay intentionally classifies points against element circumspheres - // using a shared geometric circumsphere classifier rather than primal's - // determinant-based in-sphere classifier. This keeps cavity growth, - // insertion validation, and the global empty-circumsphere check consistent - // on ill-conditioned slivers while still letting the hot path avoid the - // explicit sphere signed-distance sqrt. Raw determinant values are still - // exposed separately for diagnostics. - //----------------------------------------------------------------------------- - struct CircumsphereEval - { - PointType center {}; - double radius_sq {0.}; - - CircumsphereEval() = default; - - CircumsphereEval(const PointType& origin, const VectorType& center_offset) - : center(origin + center_offset) - , radius_sq(center_offset.squared_norm()) - { } - }; - - static CircumsphereEval evaluateCircumsphereOnMesh(const IAMeshType& mesh, IndexType element_idx) - { - using axom::numerics::determinant; - - const auto verts = mesh.boundaryVertices(element_idx); - - if constexpr(DIM == 2) - { - const PointType& p0 = mesh.getVertexPosition(verts[0]); - const PointType& p1 = mesh.getVertexPosition(verts[1]); - const PointType& p2 = mesh.getVertexPosition(verts[2]); - - const double vx0 = p1[0] - p0[0]; - const double vx1 = p2[0] - p0[0]; - const double vy0 = p1[1] - p0[1]; - const double vy1 = p2[1] - p0[1]; - - const double sq0 = vx0 * vx0 + vy0 * vy0; - const double sq1 = vx1 * vx1 + vy1 * vy1; - - const double a = determinant(vx0, vx1, vy0, vy1); - const double eps = (a >= 0.) ? primal::PRIMAL_TINY : -primal::PRIMAL_TINY; - const double ood = 1. / (2. * a + eps); + void generateInitialMesh(std::vector& points, + std::vector& elem, + const BoundingBox& bb); +}; - const double center_offset_x = determinant(sq0, sq1, vy0, vy1) * ood; - const double center_offset_y = -determinant(sq0, sq1, vx0, vx1) * ood; +template +constexpr typename Delaunay::IndexType Delaunay::INVALID_INDEX; - return CircumsphereEval(p0, VectorType {center_offset_x, center_offset_y}); - } - else - { - const PointType& p0 = mesh.getVertexPosition(verts[0]); - const PointType& p1 = mesh.getVertexPosition(verts[1]); - const PointType& p2 = mesh.getVertexPosition(verts[2]); - const PointType& p3 = mesh.getVertexPosition(verts[3]); - - const double vx0 = p1[0] - p0[0]; - const double vx1 = p2[0] - p0[0]; - const double vx2 = p3[0] - p0[0]; - const double vy0 = p1[1] - p0[1]; - const double vy1 = p2[1] - p0[1]; - const double vy2 = p3[1] - p0[1]; - const double vz0 = p1[2] - p0[2]; - const double vz1 = p2[2] - p0[2]; - const double vz2 = p3[2] - p0[2]; - - const double sq0 = vx0 * vx0 + vy0 * vy0 + vz0 * vz0; - const double sq1 = vx1 * vx1 + vy1 * vy1 + vz1 * vz1; - const double sq2 = vx2 * vx2 + vy2 * vy2 + vz2 * vz2; - - const double a = determinant(vx0, vx1, vx2, vy0, vy1, vy2, vz0, vz1, vz2); - const double eps = (a >= 0.) ? primal::PRIMAL_TINY : -primal::PRIMAL_TINY; - const double ood = 1. / (2. * a + eps); - - const double center_offset_x = determinant(sq0, sq1, sq2, vy0, vy1, vy2, vz0, vz1, vz2) * ood; - const double center_offset_y = determinant(sq0, sq1, sq2, vz0, vz1, vz2, vx0, vx1, vx2) * ood; - const double center_offset_z = determinant(sq0, sq1, sq2, vx0, vx1, vx2, vy0, vy1, vy2) * ood; - - return CircumsphereEval(p0, VectorType {center_offset_x, center_offset_y, center_offset_z}); - } - } - - static double sphereSquaredDistanceTolerance(const CircumsphereEval& sphere, - const PointType& x, - double distance_sq) - { - double scale = 1.; - - for(int dim = 0; dim < DIM; ++dim) - { - scale = axom::utilities::max(scale, axom::utilities::abs(sphere.center[dim])); - scale = axom::utilities::max(scale, axom::utilities::abs(x[dim])); - } - - const double local_span = - axom::utilities::max(1., 2. * std::sqrt(axom::utilities::max(distance_sq, sphere.radius_sq))); - - // Since d^2 - r^2 = (d - r)(d + r), map the old signed-distance tolerance to - // squared distance with a local bound on (d + r) rather than another global - // coordinate-scale factor. This preserves the large-coordinate sliver cases - // that motivated the geometric classifier in the first place. - return 256. * std::numeric_limits::epsilon() * scale * local_span; - } - - template - static double sphereSignedDistanceTolerance(const SphereType& sphere, const PointType& x) - { - const auto& center = sphere.getCenter(); - double scale = axom::utilities::max(1., sphere.getRadius()); - for(int dim = 0; dim < DIM; ++dim) - { - scale = axom::utilities::max(scale, axom::utilities::abs(center[dim])); - scale = axom::utilities::max(scale, axom::utilities::abs(x[dim])); - } - - return 256. * std::numeric_limits::epsilon() * scale; - } - - static int inSphereOrientationOnMesh(const IAMeshType& mesh, const PointType& q, IndexType element_idx) - { - const auto sphere = evaluateCircumsphereOnMesh(mesh, element_idx); - const double distance_sq = VectorType(sphere.center, q).squared_norm(); - - const double delta_sq = distance_sq - sphere.radius_sq; - const double tol = sphereSquaredDistanceTolerance(sphere, q, distance_sq); - if(axom::utilities::abs(delta_sq) <= tol) - { - return primal::ON_BOUNDARY; - } - - return delta_sq < 0. ? primal::ON_NEGATIVE_SIDE : primal::ON_POSITIVE_SIDE; - } - - static double inSphereDeterminantOnMesh(const IAMeshType& mesh, - const PointType& q, - IndexType element_idx) - { - const auto verts = mesh.boundaryVertices(element_idx); - - if constexpr(DIM == 2) - { - return primal::in_sphere_determinant(q, - mesh.getVertexPosition(verts[0]), - mesh.getVertexPosition(verts[1]), - mesh.getVertexPosition(verts[2])); - } - else - { - return primal::in_sphere_determinant(q, - mesh.getVertexPosition(verts[0]), - mesh.getVertexPosition(verts[1]), - mesh.getVertexPosition(verts[2]), - mesh.getVertexPosition(verts[3])); - } - } - - static const char* orientationResultName(int result) - { - switch(result) - { - case primal::ON_NEGATIVE_SIDE: - return "inside"; - case primal::ON_BOUNDARY: - return "boundary"; - case primal::ON_POSITIVE_SIDE: - return "outside"; - default: - return "unknown"; - } - } - - static bool isPointInSphereOnMesh(const IAMeshType& mesh, - const PointType& q, - IndexType element_idx, - bool includeBoundary) - { - const int res = inSphereOrientationOnMesh(mesh, q, element_idx); - return includeBoundary ? (res != primal::ON_POSITIVE_SIDE) : (res == primal::ON_NEGATIVE_SIDE); - } - - //----------------------------------------------------------------------------- - // Simplex orientation helpers - // - // Validate that simplices are positively oriented using the same - // determinant/tolerance pattern (determinants are 2*area in 2D and 6*volume in 3D). - //----------------------------------------------------------------------------- - struct OrientationEval - { - double det {0.}; - double tol {0.}; - int orientation {primal::ON_BOUNDARY}; - }; - - static int classifyOrientationDeterminant(double det, double tol) - { - const int sign = signWithTolerance(det, tol); - return sign < 0 ? primal::ON_NEGATIVE_SIDE - : (sign > 0 ? primal::ON_POSITIVE_SIDE : primal::ON_BOUNDARY); - } - - OrientationEval evaluateElementOrientationDeterminant(IndexType element_idx) const - { - const double scale = (DIM == 2) ? 2. : 6.; - const double det = scale * getElementSignedMeasure(element_idx); - const double tol = scale * getElementMeasureTolerance(); - return {det, tol, classifyOrientationDeterminant(det, tol)}; - } - - BaryCoordType getRawBarycentricDeterminants(IndexType element_idx, const PointType& query_pt) const - { - const auto verts = m_mesh.boundaryVertices(element_idx); - - if constexpr(DIM == 2) - { - const ElementType tri(m_mesh.getVertexPosition(verts[0]), - m_mesh.getVertexPosition(verts[1]), - m_mesh.getVertexPosition(verts[2])); - return tri.physToBarycentric(query_pt, /*skipNormalization=*/true); - } - else - { - const ElementType tet(m_mesh.getVertexPosition(verts[0]), - m_mesh.getVertexPosition(verts[1]), - m_mesh.getVertexPosition(verts[2]), - m_mesh.getVertexPosition(verts[3])); - return tet.physToBarycentric(query_pt, /*skipNormalization=*/true); - } - } - - double rawBarycentricDeterminantTolerance(IndexType element_idx, const PointType& query_pt) const - { - const auto verts = m_mesh.boundaryVertices(element_idx); - - double scale = 1.; - for(int i = 0; i < VERT_PER_ELEMENT; ++i) - { - const auto diff = m_mesh.getVertexPosition(verts[i]) - query_pt; - scale = axom::utilities::max(scale, diff.norm()); - } - - const double k = 64.; - if constexpr(DIM == 2) - { - return k * std::numeric_limits::epsilon() * scale * scale; - } - else - { - return k * std::numeric_limits::epsilon() * scale * scale * scale; - } - } - - bool isPointInsideForLocation(IndexType element_idx, - const PointType& query_pt, - const BaryCoordType& bary_coord, - ModularFaceIndex* exit_face = nullptr) const - { - ModularFaceIndex min_face(bary_coord.array().argMin()); - - // Fast path: if any barycentric coordinate is clearly negative, the point - // lies outside the simplex across the most-negative face. - for(int i = 0; i < VERT_PER_ELEMENT; ++i) - { - if(bary_coord[i] < -BARY_EPS) - { - if(exit_face != nullptr) - { - *exit_face = min_face; - } - return false; - } - } - - // Ambiguous path: for near-zero barycentric coordinates, fall back to the - // underlying (unnormalized) determinants to decide the sign consistently. - int first_determinant_negative = -1; - if constexpr(DIM == 3) - { - bool has_near_zero = false; - for(int i = 0; i < VERT_PER_ELEMENT; ++i) - { - has_near_zero |= axom::utilities::abs(bary_coord[i]) <= BARY_EPS; - } - - if(has_near_zero) - { - const BaryCoordType raw = getRawBarycentricDeterminants(element_idx, query_pt); - const double tol = rawBarycentricDeterminantTolerance(element_idx, query_pt); - for(int i = 0; i < VERT_PER_ELEMENT; ++i) - { - if(axom::utilities::abs(bary_coord[i]) <= BARY_EPS && signWithTolerance(raw[i], tol) < 0) - { - first_determinant_negative = i; - break; - } - } - } - } - - if(first_determinant_negative >= 0) - { - if(exit_face != nullptr) - { - *exit_face = ModularFaceIndex(first_determinant_negative); - } - return false; - } - - return true; - } - - /// \brief Walk from a starting element until the containing element is found or the walk cycles - PointLocationResult walkToContainingElement(const PointType& query_pt, - IndexType start_element, - std::vector* visited_elements_out = nullptr) const - { - constexpr IndexType invalid_element = IAMeshType::INVALID_ELEMENT_INDEX; - - if(!m_mesh.isValidElement(start_element)) - { - return {}; - } - - static constexpr int MAX_WALK_STEPS = 256; - std::vector& visited_elements = - visited_elements_out != nullptr ? *visited_elements_out : m_walk_local_elements_scratch; - visited_elements.clear(); - if(static_cast(visited_elements.capacity()) < MAX_WALK_STEPS) - { - visited_elements.reserve(MAX_WALK_STEPS); - } - IndexType element_i = start_element; - - if(m_walk_visited.size() < static_cast(m_mesh.elements().size())) - { - m_walk_visited = slam::BitSet(static_cast(m_mesh.elements().size())); - } - - auto clearVisitedBits = [&]() { - for(const IndexType visited : visited_elements) - { - m_walk_visited.clear(static_cast(visited)); - } - }; - - int step_count = 0; - - auto recordWalk = [&](PointLocationStatus status) { - if(!m_collect_location_stats) - { - return; - } - - ++m_num_walk_calls; - m_total_walk_steps += static_cast(step_count); - m_max_walk_steps = - axom::utilities::max(m_max_walk_steps, static_cast(step_count)); - switch(status) - { - case PointLocationStatus::Found: - ++m_num_walk_found; - break; - case PointLocationStatus::Outside: - ++m_num_walk_outside; - break; - default: - ++m_num_walk_failed; - break; - } - }; - - while(1) - { - ++step_count; - if(m_walk_visited.test(static_cast(element_i))) - { - recordWalk(PointLocationStatus::Failed); - clearVisitedBits(); - return {}; - } - m_walk_visited.set(static_cast(element_i)); - visited_elements.push_back(element_i); - - const BaryCoordType bary_coord = getBaryCoords(element_i, query_pt); - ModularFaceIndex modular_idx(0); - if(isPointInsideForLocation(element_i, query_pt, bary_coord, &modular_idx)) - { - recordWalk(PointLocationStatus::Found); - clearVisitedBits(); - return {element_i, PointLocationStatus::Found}; - } - - if(static_cast(visited_elements.size()) >= MAX_WALK_STEPS) - { - recordWalk(PointLocationStatus::Failed); - clearVisitedBits(); - return {}; - } - - const IndexType next_element = m_mesh.adjacentElements(element_i)[modular_idx + 1]; - if(next_element == invalid_element) - { - recordWalk(PointLocationStatus::Outside); - clearVisitedBits(); - return {INVALID_INDEX, PointLocationStatus::Outside}; - } - - SLIC_ASSERT(m_mesh.isValidElement(next_element)); - element_i = next_element; - } - } - - void appendCandidateElement(std::vector& candidate_elements, IndexType vertex_i) const - { - const IndexType element_i = m_mesh.coboundaryElement(vertex_i); - if(isSearchableElement(element_i) && - std::find(candidate_elements.begin(), candidate_elements.end(), element_i) == - candidate_elements.end()) - { - candidate_elements.push_back(element_i); - } - } - - void appendCandidateElementsFromVertices(std::vector& candidate_elements, - const std::vector& candidate_vertices) const - { - for(const auto vertex_i : candidate_vertices) - { - appendCandidateElement(candidate_elements, vertex_i); - } - } - - void getInitialCandidateElements(const PointType& query_pt, - std::vector& candidate_elements) const - { - candidate_elements.clear(); - candidate_elements.reserve(16); - - // Prefer a small set of vertices from nearby bins rather than a single bin - // representative. For large meshes, a single cached vertex can be far (in - // terms of simplex-to-simplex walks) from the query point even if it lies - // in the same bin; providing a few local candidates keeps directed walks - // short and avoids expensive fallbacks. - m_initial_vertices_scratch.clear(); - m_element_finder.getNearbyVertices(m_mesh, - query_pt, - m_initial_vertices_scratch, - /*search_radius=*/1, - /*max_candidates=*/8); - appendCandidateElementsFromVertices(candidate_elements, m_initial_vertices_scratch); - - if(candidate_elements.empty()) - { - if(m_collect_location_stats) - { - ++m_num_empty_seed_fallbacks; - } - for(auto elem : m_mesh.elements().positions()) - { - if(isSearchableElement(elem)) - { - candidate_elements.push_back(elem); - break; - } - } - } - } - - PointLocationResult walkCandidateElements(const PointType& query_pt, - const std::vector& candidate_elements, - std::size_t start_idx = 0, - std::vector* walked_elements = nullptr) const - { - for(std::size_t idx = start_idx; idx < candidate_elements.size(); ++idx) - { - std::vector* visited_elements = - (walked_elements != nullptr && idx == start_idx) ? walked_elements : nullptr; - PointLocationResult walk_result = - walkToContainingElement(query_pt, candidate_elements[idx], visited_elements); - if(walk_result.status != PointLocationStatus::Failed) - { - return walk_result; - } - } - - return {}; - } - - PointLocationResult findContainingElementWithQueryFallbacks( - const PointType& query_pt, - std::vector& candidate_elements, - const std::vector& walked_elements) const - { - const IndexType walk_region_elem = findContainingElementFromNeighbors(query_pt, walked_elements); - if(walk_region_elem != INVALID_INDEX) - { - return {walk_region_elem, PointLocationStatus::Found}; - } - - m_fallback_vertices_scratch.clear(); - m_element_finder.getNearbyVertices(m_mesh, - query_pt, - m_fallback_vertices_scratch, - QUERY_SEARCH_RADIUS, - QUERY_CANDIDATE_LIMIT); - const std::size_t initial_candidate_count = candidate_elements.size(); - candidate_elements.reserve(candidate_elements.size() + m_fallback_vertices_scratch.size()); - appendCandidateElementsFromVertices(candidate_elements, m_fallback_vertices_scratch); - - PointLocationResult walk_result = - walkCandidateElements(query_pt, candidate_elements, initial_candidate_count); - if(walk_result.status != PointLocationStatus::Failed) - { - return walk_result; - } - - const IndexType nearby_elem = findContainingElementNearby(query_pt, m_fallback_vertices_scratch); - if(nearby_elem != INVALID_INDEX) - { - return {nearby_elem, PointLocationStatus::Found}; - } - - return {}; - } - - /// \brief Scan a small adjacency region around a failed directed walk before falling back to a full scan - IndexType findContainingElementFromNeighbors(const PointType& query_pt, - const std::vector& seed_elements) const - { - if(seed_elements.empty()) - { - return INVALID_INDEX; - } - - std::vector nearby_elements; - nearby_elements.reserve(seed_elements.size() * (1 + WALK_NEIGHBORHOOD_LAYERS * VERT_PER_ELEMENT)); - - auto appendUniqueElement = [&](IndexType element_idx, std::vector& frontier) { - if(isSearchableElement(element_idx) && - std::find(nearby_elements.begin(), nearby_elements.end(), element_idx) == - nearby_elements.end()) - { - nearby_elements.push_back(element_idx); - frontier.push_back(element_idx); - } - }; - - std::vector frontier; - frontier.reserve(seed_elements.size()); - for(const IndexType element_idx : seed_elements) - { - appendUniqueElement(element_idx, frontier); - } - - for(int layer = 0; layer < WALK_NEIGHBORHOOD_LAYERS && !frontier.empty(); ++layer) - { - std::vector next_frontier; - next_frontier.reserve(frontier.size() * VERT_PER_ELEMENT); - - for(const IndexType element_idx : frontier) - { - const auto neighbors = m_mesh.adjacentElements(element_idx); - for(int i = 0; i < VERT_PER_ELEMENT; ++i) - { - appendUniqueElement(neighbors[ModularFaceIndex(i) + 1], next_frontier); - } - } - - frontier.swap(next_frontier); - } - - for(const IndexType element_idx : nearby_elements) - { - const BaryCoordType bary_coord = getBaryCoords(element_idx, query_pt); - const DataType min_bary = bary_coord[bary_coord.array().argMin()]; - // Keep this recovery step strict: it only succeeds on a true containing - // element. Broader "closest element" behavior stays in the final linear - // fallback so outside-hull queries can still exit cleanly. - if(min_bary >= -BARY_EPS) - { - return element_idx; - } - } - - return INVALID_INDEX; - } - - /// \brief Last-resort exhaustive scan used when the cheaper local search path cannot classify the point - IndexType findContainingElementLinear(const PointType& query_pt, bool warnOnInvalid) const - { - IndexType best_element = INVALID_INDEX; - DataType best_min_bary = -std::numeric_limits::max(); - - for(auto element_idx : m_mesh.elements().positions()) - { - if(!isSearchableElement(element_idx)) - { - continue; - } - - const BaryCoordType bary_coord = getBaryCoords(element_idx, query_pt); - const DataType min_bary = bary_coord[bary_coord.array().argMin()]; - if(min_bary > best_min_bary) - { - best_min_bary = min_bary; - best_element = element_idx; - } - - if(min_bary >= -BARY_EPS) - { - return element_idx; - } - } - - SLIC_WARNING_IF(warnOnInvalid, - fmt::format("Unable to locate containing element for point {} after exhaustive " - "neighbor search; returning closest candidate with min barycentric " - "coordinate {:.17g}", - query_pt, - best_min_bary)); - - return best_element; - } - - /// \brief Scan the stars of nearby seed vertices as a cheaper local fallback for query point location - IndexType findContainingElementNearby(const PointType& query_pt, - const std::vector& nearby_vertices) const - { - std::vector nearby_elements; - for(const IndexType vertex_idx : nearby_vertices) - { - if(!m_mesh.isValidVertex(vertex_idx)) - { - continue; - } - - const auto star = m_mesh.vertexStar(vertex_idx); - for(const IndexType elem : star) - { - if(isSearchableElement(elem)) - { - nearby_elements.push_back(elem); - } - } - } - - std::sort(nearby_elements.begin(), nearby_elements.end()); - nearby_elements.erase(std::unique(nearby_elements.begin(), nearby_elements.end()), - nearby_elements.end()); - - for(const IndexType element_idx : nearby_elements) - { - const BaryCoordType bary_coord = getBaryCoords(element_idx, query_pt); - const DataType min_bary = bary_coord[bary_coord.array().argMin()]; - if(min_bary >= -BARY_EPS) - { - return element_idx; - } - } - - return INVALID_INDEX; - } - -private: - /// \brief Predicate for when to compact internal mesh data structures after removing elements - bool shouldCompactMesh() const - { - // Note: This auto-compacting feature is hard coded. - // It may be good to let user have control of this option in the future. - constexpr int MIN_REMOVED_ELEMENTS = DIM == 2 ? 512 : 2048; - constexpr double REMOVED_ELEMENT_FRACTION = DIM == 2 ? 0.25 : 0.35; - return static_cast(m_deleted_elements.size()) > MIN_REMOVED_ELEMENTS && - (static_cast(m_deleted_elements.size()) > - REMOVED_ELEMENT_FRACTION * static_cast(m_mesh.elements().size())); - } - - /// \brief Compacts the underlying mesh - void compactMesh() - { - m_mesh.compact(); - m_deleted_elements.clear(); - m_element_finder.recomputeGrid(m_mesh, m_bounding_box); - if(m_next_regrid_vertex_count > 0) - { - while(m_next_regrid_vertex_count <= m_mesh.vertices().size()) - { - m_next_regrid_vertex_count *= 2; - } - } - } - - /** - * \brief Helper function to fill the array with the initial mesh. - * \details create a rectangle for 2D, cube for 3D, and fill the array with the mesh data. - */ - void generateInitialMesh(std::vector& points, - std::vector& elem, - const BoundingBox& bb); - -private: - /// Helper struct to find the first element near a point to be inserted - struct ElementFinder - { - using NumericArrayType = NumericArray; - using LatticeType = spin::RectangularLattice; - - explicit ElementFinder() = default; - - /** - * \brief Resizes the grid and reinserts vertices - * - * Resizes using a heuristic based on the number of vertices in the mesh. - */ - void recomputeGrid(const IAMeshType& mesh, const BoundingBox& bb) - { - const auto& verts = mesh.vertices(); - - // Choose the grid resolution so that each bin contains ~O(1) points per - // dimension on average. This keeps the "nearby bin" seed used by point - // insertion and query walks close (in terms of simplex adjacency hops) - // even for very large point sets. - // - // Target occupancy is ~ 4^DIM points per bin (16 in 2D, 64 in 3D). - constexpr double BIN_SIDE_SPACING = 4.0; - const double res_root = std::pow(static_cast(verts.size()), 1.0 / DIM); - const IndexType res = - axom::utilities::max(IndexType {2}, - static_cast(std::ceil(res_root / BIN_SIDE_SPACING))); - - auto expandedBB = BoundingBox(bb).scale(1.05); - - // regenerate lattice - m_lattice = spin::rectangular_lattice_from_bounding_box(expandedBB, NumericArrayType(res)); - - // resize m_bins - resizeArray(res); - m_bins.fill(INVALID_INDEX); - - // insert vertices into lattice - for(auto idx : verts.positions()) - { - if(!mesh.isValidVertex(idx)) - { - continue; - } - - // Skip vertices that are no longer incident to any valid element (can - // occur for interior vertices of removed cavities). Using them as - // point-location seeds forces expensive global fallbacks. - const IndexType coboundary = mesh.coboundaryElement(idx); - if(!mesh.isValidElement(coboundary)) - { - continue; - } - - const auto& pos = mesh.getVertexPosition(idx); - const auto cell = m_lattice.gridCell(pos); - IndexType& slot = flatIndex(cell); - if(!mesh.isValidVertex(slot) || !mesh.isValidElement(mesh.coboundaryElement(slot))) - { - slot = idx; - } - } - } - - /** - * \brief Returns the index of the vertex in the bin containing point \a pt - * - * \param pt The position in space of the vertex that we're checking - * \note Some bins might not point to a vertex, so users should check - * that the returned index is a valid vertex, e.g. using \a mesh.isValidVertex(vertex_id) - */ - inline void getNearbyVertices(const IAMeshType& mesh, - const PointType& pt, - std::vector& nearby_vertices, - int search_radius = 1, - int max_candidates = 1) const - { - const auto cell = m_lattice.gridCell(pt); - m_candidate_scratch.clear(); - const int span = 2 * search_radius + 1; - const int max_bins = (DIM == 2) ? (span * span) : (span * span * span); - m_candidate_scratch.reserve(static_cast(max_bins)); - - auto tryCandidate = [&](const typename LatticeType::GridCell& candidate_cell) { - const IndexType vertex_idx = flatIndex(candidate_cell); - if(mesh.isValidVertex(vertex_idx) && mesh.isValidElement(mesh.coboundaryElement(vertex_idx))) - { - const double sq_dist = primal::squared_distance(mesh.getVertexPosition(vertex_idx), pt); - m_candidate_scratch.emplace_back(sq_dist, vertex_idx); - } - }; - - if constexpr(DIM == 2) - { - for(int dj = -search_radius; dj <= search_radius; ++dj) - { - const IndexType j = cell[1] + dj; - if(j < 0 || j >= m_bins.shape()[1]) - { - continue; - } - - for(int di = -search_radius; di <= search_radius; ++di) - { - const IndexType i = cell[0] + di; - if(i < 0 || i >= m_bins.shape()[0]) - { - continue; - } - - tryCandidate(typename LatticeType::GridCell {{i, j}}); - } - } - } - else - { - for(int dk = -search_radius; dk <= search_radius; ++dk) - { - const IndexType k = cell[2] + dk; - if(k < 0 || k >= m_bins.shape()[2]) - { - continue; - } - - for(int dj = -search_radius; dj <= search_radius; ++dj) - { - const IndexType j = cell[1] + dj; - if(j < 0 || j >= m_bins.shape()[1]) - { - continue; - } - - for(int di = -search_radius; di <= search_radius; ++di) - { - const IndexType i = cell[0] + di; - if(i < 0 || i >= m_bins.shape()[0]) - { - continue; - } - - tryCandidate(typename LatticeType::GridCell {{i, j, k}}); - } - } - } - } - - if(static_cast(m_candidate_scratch.size()) > max_candidates) - { - auto kth = m_candidate_scratch.begin() + max_candidates; - std::nth_element(m_candidate_scratch.begin(), - kth, - m_candidate_scratch.end(), - [](const auto& lhs, const auto& rhs) { return lhs.first < rhs.first; }); - m_candidate_scratch.resize(static_cast(max_candidates)); - } - - std::sort(m_candidate_scratch.begin(), - m_candidate_scratch.end(), - [](const auto& lhs, const auto& rhs) { return lhs.first < rhs.first; }); - - nearby_vertices.clear(); - nearby_vertices.reserve( - axom::utilities::min(max_candidates, static_cast(m_candidate_scratch.size()))); - for(const auto& candidate : m_candidate_scratch) - { - nearby_vertices.push_back(candidate.second); - } - } - - /// \brief Returns the index of the vertex in the bin containing point \a pt - inline IndexType getNearbyVertex(const PointType& pt) const - { - const auto cell = m_lattice.gridCell(pt); - return flatIndex(cell); - } - - /// \brief Updates the cached value of the bin containing point \a pt to \a vertex_id - inline void updateBin(const PointType& pt, IndexType vertex_id) - { - const auto cell = m_lattice.gridCell(pt); - flatIndex(cell) = vertex_id; - } - - private: - /// Returns a reference to the index in the array for the ND point with grid index \a cell - inline IndexType& flatIndex(const typename LatticeType::GridCell& cell) - { - const IndexType idx = numerics::dot_product(cell.data(), m_bins.strides().begin(), DIM); - return m_bins.flatIndex(idx); - } - - inline const IndexType& flatIndex(const typename LatticeType::GridCell& cell) const - { - const IndexType idx = numerics::dot_product(cell.data(), m_bins.strides().begin(), DIM); - return m_bins.flatIndex(idx); - } - - /// Dimension-specific helper for resizing the ND array in 2D - template - typename std::enable_if::type resizeArray(IndexType res) - { - m_bins.resize(res, res); - } - - /// Dimension-specific helper for resizing the ND array in 3D - template - typename std::enable_if::type resizeArray(IndexType res) - { - m_bins.resize(res, res, res); - } - - private: - axom::Array m_bins; - LatticeType m_lattice; - mutable std::vector> m_candidate_scratch; - }; - - /// Helper struct to locally insert a new point into a Delaunay complex while keeping the mesh Delaunay - struct InsertionHelper - { - public: - struct BoundaryFacet - { - std::array vertices {}; - IndexType neighbor {IAMeshType::ElementAdjacencyRelation::INVALID_INDEX}; - }; - - InsertionHelper(IAMeshType& mesh) : m_mesh(mesh) { } - - void reset() - { - for(const IndexType element_idx : cavity_elems) - { - if(element_idx >= 0 && static_cast(element_idx) < m_cavity_membership.size()) - { - m_cavity_membership[static_cast(element_idx)] = 0; - } - } - - boundary_facets.clear(); - cavity_elems.clear(); - inserted_elems.clear(); - containing_element = INVALID_INDEX; - seed_elements_debug.clear(); - m_stack.clear(); - } - - /** - * \brief Find the Delaunay cavity: the elements whose circumspheres contain the query point - * - * \details This function starts from an element \a element_i and searches through - * neighboring elements for a list of element indices whose circumspheres - * contain or touch the query point. - * It also finds the faces on the boundaries of the cavity to help with filling the cavity - * in the \a delaunayBall function. - * - * \param query_pt the query point - * \param element_i the element to start the search at - */ - void findCavityElements(const PointType& query_pt, const IndexArray& seed_elements) - { - constexpr int reserveSize = (DIM == 2) ? 16 : 64; - ensureCavityMembershipCapacity(); - - if(m_stack.capacity() < reserveSize) - { - m_stack.reserve(reserveSize); - } - if(cavity_elems.capacity() < reserveSize) - { - cavity_elems.reserve(reserveSize); - } - if(boundary_facets.capacity() < reserveSize) - { - boundary_facets.reserve(reserveSize); - } - if(inserted_elems.capacity() < reserveSize) - { - inserted_elems.reserve(reserveSize); - } - constexpr IndexType invalid_element = IAMeshType::INVALID_ELEMENT_INDEX; - - // Seed the cavity with the containing element, and with any face-adjacent - // neighbors when the insertion point lies on the containing simplex - // boundary. This avoids repeatedly retriangulating structured inputs that - // insert directly onto existing edges/faces. - for(const IndexType element_i : seed_elements) - { - SLIC_ASSERT(m_mesh.isValidElement(element_i)); - addCavityElement(element_i); - } - - while(!m_stack.empty()) - { - const IndexType element_idx = m_stack.back(); - m_stack.pop_back(); - - // Invariant: this element is valid, was checked and is in the cavity - // Each neighbor is either in the cavity or the shared face is on the cavity boundary - const auto neighbors = m_mesh.adjacentElements(element_idx); - for(auto n_idx : neighbors.positions()) - { - const IndexType nbr = neighbors[n_idx]; - - // invalid neighbor means face is on domain boundary, and thus on cavity boundary - if(nbr != invalid_element) - { - SLIC_ASSERT(m_mesh.isValidElement(nbr)); - - // If the neighbor is already in the cavity, the shared face is - // internal and not part of the cavity boundary. - if(containsCavityElement(nbr)) - { - continue; // both elem and neighbor along face are in cavity - } - - // neighbor is valid but not in cavity; check circumsphere and add when appropriate - if(isPointInCircumsphere(query_pt, nbr)) - { - addCavityElement(nbr); - continue; // face is internal to cavity, nothing left to do for this face - } - } - - // if we got here, the face is on the boundary of the Delaunay cavity - // add it to the boundary facet list - { - const auto bdry = m_mesh.boundaryVertices(element_idx); - - typename IAMeshType::ModularVertexIndex mod_idx(n_idx); - BoundaryFacet facet; - for(int i = 0; i < VERTS_PER_FACET; i++) - { - facet.vertices[i] = bdry[mod_idx++]; - } - //For tetrahedron, if the element face is odd, reverse vertex order - if(DIM == 3 && n_idx % 2 == 1) - { - axom::utilities::swap(facet.vertices[1], facet.vertices[2]); - } - - facet.neighbor = nbr; - boundary_facets.push_back(facet); - } - } - } - - SLIC_ASSERT_MSG(!cavity_elems.empty(), "Error: New point is not contained in the mesh"); - SLIC_ASSERT(!boundary_facets.empty()); - } - - /** - * \brief Remove the elements in the Delaunay cavity - */ - void createCavity(RecycledElementPool& deleted_elements) - { - for(const auto elem : cavity_elems) - { - m_mesh.removeElement(elem); - deleted_elements.release(elem); - } - } - - /// \brief Fill in the Delaunay cavity with new elements containing the insertion point - void delaunayBall(IndexType new_pt_i, RecycledElementPool& deleted_elements) - { - const int numFaces = static_cast(boundary_facets.size()); - const IndexType invalid_neighbor = IAMeshType::ElementAdjacencyRelation::INVALID_INDEX; - const PointType& new_pt = m_mesh.getVertexPosition(new_pt_i); - - IndexType vlist[VERT_PER_ELEMENT] {}; - IndexType neighbors[VERT_PER_ELEMENT] {}; - for(int i = 0; i < numFaces; ++i) - { - // Create a new element from the face and the inserted point - for(int d = 0; d < VERTS_PER_FACET; ++d) - { - vlist[d] = boundary_facets[static_cast(i)].vertices[d]; - } - vlist[VERTS_PER_FACET] = new_pt_i; - - // Preserve positive simplex orientation regardless of the boundary-face - // ordering used to describe the cavity facet. - if constexpr(DIM == 2) - { - const PointType& p0 = m_mesh.getVertexPosition(vlist[0]); - const PointType& p1 = m_mesh.getVertexPosition(vlist[1]); - if(primal::orientation_determinant(p0, p1, new_pt) < 0.) - { - axom::utilities::swap(vlist[0], vlist[1]); - } - } - else - { - const PointType& p0 = m_mesh.getVertexPosition(vlist[0]); - const PointType& p1 = m_mesh.getVertexPosition(vlist[1]); - const PointType& p2 = m_mesh.getVertexPosition(vlist[2]); - if(primal::orientation_determinant(p0, p1, p2, new_pt) < 0.) - { - axom::utilities::swap(vlist[1], vlist[2]); - } - } - - // Face 0 is the cavity boundary face opposite the inserted point. - // The remaining faces stay invalid until fixVertexNeighborhood() stitches - // the new ball together around the inserted vertex. - const auto nID = boundary_facets[static_cast(i)].neighbor; - for(int d = 0; d < VERT_PER_ELEMENT; ++d) - { - neighbors[d] = invalid_neighbor; - } - neighbors[0] = nID; - - IndexType new_el = invalid_neighbor; - if(!deleted_elements.empty()) - { - new_el = deleted_elements.acquire(); - m_mesh.reuseElement(new_el, vlist, neighbors); - } - else - { - new_el = m_mesh.addElement(vlist, neighbors); - } - inserted_elems.push_back(new_el); - } - - // Fix neighborhood around the new point - m_mesh.fixVertexNeighborhood(new_pt_i, inserted_elems); - } - - /// \brief Returns the number of elements removed during this insertion - int numRemovedElements() const { return static_cast(cavity_elems.size()); } - - bool containsCavityElement(IndexType element_idx) const - { - return element_idx >= 0 && static_cast(element_idx) < m_cavity_membership.size() && - m_cavity_membership[static_cast(element_idx)] != 0; - } - - /// \brief Returns true when the query point is inside or on the element circumsphere - bool isPointInCircumsphere(const PointType& query_pt, IndexType element_idx) const; - - public: - IAMeshType& m_mesh; - - std::vector boundary_facets; - std::vector cavity_elems; - std::vector inserted_elems; - IndexType containing_element {INVALID_INDEX}; - BaryCoordType containing_bary; - IndexArray seed_elements_debug; - - IndexArray m_stack; - - private: - void ensureCavityMembershipCapacity() - { - const std::size_t required_size = static_cast(m_mesh.elements().size()); - if(m_cavity_membership.size() < required_size) - { - m_cavity_membership.resize(required_size, 0); - } - } - - void addCavityElement(IndexType element_idx) - { - ensureCavityMembershipCapacity(); - if(containsCavityElement(element_idx)) - { - return; - } - - m_cavity_membership[static_cast(element_idx)] = 1; - cavity_elems.push_back(element_idx); - m_stack.push_back(element_idx); - } - - std::vector m_cavity_membership; - }; -}; - -template -constexpr typename Delaunay::IndexType Delaunay::INVALID_INDEX; - -template -void Delaunay::initializeBoundary(const BoundingBox& bb) -{ - std::vector points; - IndexArray elem; - - generateInitialMesh(points, elem, bb); - - m_mesh = IAMeshType(points, elem); - m_element_finder.recomputeGrid(m_mesh, bb); - m_next_regrid_vertex_count = 1024; - m_walk_visited = slam::BitSet(static_cast(m_mesh.elements().size())); - m_total_removed_elements = 0; - m_max_removed_elements = 0; - m_num_insertions = 0; - m_num_walk_calls = 0; - m_num_walk_found = 0; - m_num_walk_outside = 0; - m_num_walk_failed = 0; - m_total_walk_steps = 0; - m_max_walk_steps = 0; - m_num_linear_fallbacks = 0; - m_num_empty_seed_fallbacks = 0; - - m_candidate_elements_scratch.clear(); - m_candidate_elements_scratch.reserve(QUERY_CANDIDATE_LIMIT); - m_walked_elements_scratch.clear(); - m_walked_elements_scratch.reserve(256); - m_walk_local_elements_scratch.clear(); - m_walk_local_elements_scratch.reserve(256); - m_initial_vertices_scratch.clear(); - m_initial_vertices_scratch.reserve(8); - m_fallback_vertices_scratch.clear(); - m_fallback_vertices_scratch.reserve(QUERY_CANDIDATE_LIMIT); - m_deleted_elements.clear(); - m_deleted_elements.reserve(DIM == 2 ? 128 : 512); - - if(!m_insertion_helper) - { - m_insertion_helper = std::make_unique(m_mesh); - } - - m_bounding_box = bb; - m_has_boundary = true; -} - -template -void Delaunay::insertPoint(const PointType& new_pt) -{ - //Make sure initializeBoundary(...) is called first - SLIC_ASSERT_MSG(m_has_boundary, "Error: Need a predefined boundary box prior to adding points."); - SLIC_ASSERT_MSG(m_insertion_helper != nullptr, - "Error: Insertion helper was not initialized. " - "Delaunay::initializeBoundary() needs to be called first."); - - //Make sure the new point is inside the boundary box - SLIC_ASSERT_MSG(m_bounding_box.contains(new_pt), - "Error: new point is outside of the boundary box."); - - // Find the mesh element containing the insertion point - IndexType element_i = findContainingElement(new_pt); - - if(element_i == INVALID_INDEX) - { - SLIC_WARNING( - fmt::format("Could not insert point {} into Delaunay triangulation: " - "Element containing that point was not found", - new_pt)); - return; - } - - // Run the insertion operation by finding invalidated elements around the point (the "cavity") - // and replacing them with new valid elements (the Delaunay "ball") - const BaryCoordType bary_coord = getBaryCoords(element_i, new_pt); - const IndexArray seed_elements = getSeedElements(element_i, bary_coord); - validateInsertionSeed(element_i, new_pt, bary_coord, seed_elements); - - auto& insertionHelper = *m_insertion_helper; - insertionHelper.reset(); - insertionHelper.containing_element = element_i; - insertionHelper.containing_bary = bary_coord; - insertionHelper.seed_elements_debug = seed_elements; - insertionHelper.findCavityElements(new_pt, seed_elements); - ++m_num_insertions; - m_total_removed_elements += static_cast(insertionHelper.numRemovedElements()); - m_max_removed_elements = - axom::utilities::max(m_max_removed_elements, - static_cast(insertionHelper.numRemovedElements())); - validateCavityBoundary(insertionHelper); - insertionHelper.createCavity(m_deleted_elements); - IndexType new_pt_i = m_mesh.addVertex(new_pt); - insertionHelper.delaunayBall(new_pt_i, m_deleted_elements); - validateInsertedBall(new_pt_i, insertionHelper); - validateInsertionResult(); - - m_element_finder.updateBin(new_pt, new_pt_i); - if(m_next_regrid_vertex_count > 0 && m_mesh.vertices().size() >= m_next_regrid_vertex_count) - { - m_element_finder.recomputeGrid(m_mesh, m_bounding_box); - while(m_next_regrid_vertex_count <= m_mesh.vertices().size()) - { - m_next_regrid_vertex_count *= 2; - } - } - - // Compact the mesh if there are too many removed elements - if(shouldCompactMesh()) - { - this->compactMesh(); - } -} - -template -void Delaunay::validateCavityBoundary(const InsertionHelper& insertion_helper) const -{ - if(m_insertion_validation_mode == InsertionValidationMode::None) - { - return; - } - - // Cavity boundary invariant: - // - Every cavity face that borders a non-cavity neighbor (or the temporary - // bounding-box boundary) must appear exactly once in `boundary_facets`. - // - The facet's recorded neighbor must match the mesh adjacency. - struct FacetInfo - { - IndexType neighbor_idx {INVALID_INDEX}; - bool matched {false}; - }; - - fmt::memory_buffer out; - bool valid = true; - std::map cavity_boundary; - - for(const auto& facet : insertion_helper.boundary_facets) - { - const FacetKey facet_key = makeSortedFaceKey(facet.vertices); - const auto insert_status = cavity_boundary.insert({facet_key, {facet.neighbor, false}}); - if(!insert_status.second) - { - fmt::format_to(std::back_inserter(out), - "\n\tCavity boundary facet {} was recorded more than once", - facetKeyString(facet_key)); - valid = false; - } - } - - for(const IndexType cavity_element : insertion_helper.cavity_elems) - { - const auto neighbors = m_mesh.adjacentElements(cavity_element); - for(int facet_idx = 0; facet_idx < VERT_PER_ELEMENT; ++facet_idx) - { - const IndexType neighbor_idx = neighbors[facet_idx]; - if(m_mesh.isValidElement(neighbor_idx) && insertion_helper.containsCavityElement(neighbor_idx)) - { - continue; - } - - const FacetKey facet_key = m_mesh.getSortedFacetKey(cavity_element, facet_idx); - auto facet_it = cavity_boundary.find(facet_key); - if(facet_it == cavity_boundary.end()) - { - fmt::format_to(std::back_inserter(out), - "\n\tCavity facet {} on element {} face {} is missing from the " - "boundary facet set", - facetKeyString(facet_key), - cavity_element, - facet_idx); - valid = false; - continue; - } - - if(facet_it->second.neighbor_idx != neighbor_idx) - { - fmt::format_to(std::back_inserter(out), - "\n\tCavity face {} expects neighbor {} but facet relation stores {}", - facetKeyString(facet_key), - neighbor_idx, - facet_it->second.neighbor_idx); - valid = false; - } - - if(!m_mesh.isValidElement(neighbor_idx) && m_has_boundary && !isFacetOnBoundingBox(facet_key)) - { - fmt::format_to(std::back_inserter(out), - "\n\tCavity boundary face {} exits the mesh away from the bounding box", - facetKeyString(facet_key)); - valid = false; - } - - facet_it->second.matched = true; - } - } - - for(const auto& facet_entry : cavity_boundary) - { - if(!facet_entry.second.matched) - { - fmt::format_to(std::back_inserter(out), - "\n\tFacet {} does not correspond to a cavity boundary face", - facetKeyString(facet_entry.first)); - valid = false; - } - } - - SLIC_ERROR_IF(!valid, - "Delaunay cavity validation failed after insertion " << m_num_insertions << ":" - << fmt::to_string(out)); -} - -template -void Delaunay::validateInsertedBall(IndexType new_pt_i, - const InsertionHelper& insertion_helper) const -{ - if(m_insertion_validation_mode == InsertionValidationMode::None) - { - return; - } - - // Ball invariant: - // - Every inserted element contains `new_pt_i` and is positively oriented. - // - Each cavity boundary facet is covered by exactly one inserted element and - // points to the recorded outside neighbor. - // - All remaining facets are internal to the ball and are paired with reciprocal adjacencies. - struct BoundaryFacetInfo - { - IndexType neighbor_idx {INVALID_INDEX}; - bool matched {false}; - }; - - fmt::memory_buffer out; - bool valid = true; - std::map cavity_boundary; - std::map> inserted_faces; - auto findOppositeVertex = [&](IndexType element_idx, const FacetKey& facet_key) { - const auto verts = m_mesh.boundaryVertices(element_idx); - for(int i = 0; i < VERT_PER_ELEMENT; ++i) - { - bool on_face = false; - for(int j = 0; j < VERTS_PER_FACET; ++j) - { - on_face |= (verts[i] == facet_key[j]); - } - if(!on_face) - { - return verts[i]; - } - } - - return INVALID_INDEX; - }; - - if(insertion_helper.inserted_elems.size() != insertion_helper.boundary_facets.size()) - { - fmt::format_to( - std::back_inserter(out), - "\n\tInserted ball element count {} does not match cavity boundary facet count {}", - insertion_helper.inserted_elems.size(), - insertion_helper.boundary_facets.size()); - valid = false; - } - - if(insertion_helper.containing_element != INVALID_INDEX) - { - fmt::format_to(std::back_inserter(out), - "\n\tContaining element {} barycentric coordinates {}", - insertion_helper.containing_element, - insertion_helper.containing_bary); - if(!insertion_helper.seed_elements_debug.empty()) - { - fmt::format_to(std::back_inserter(out), - "\n\tInsertion seeds: [{}]", - fmt::join(insertion_helper.seed_elements_debug, ", ")); - } - } - - for(const auto& facet : insertion_helper.boundary_facets) - { - cavity_boundary.insert({makeSortedFaceKey(facet.vertices), {facet.neighbor, false}}); - } - - for(const IndexType element_idx : insertion_helper.inserted_elems) - { - if(!m_mesh.isValidElement(element_idx)) - { - fmt::format_to(std::back_inserter(out), "\n\tInserted element {} is invalid", element_idx); - valid = false; - continue; - } - - const auto verts = m_mesh.boundaryVertices(element_idx); - if(!slam::is_subset(new_pt_i, verts)) - { - fmt::format_to(std::back_inserter(out), - "\n\tInserted element {} does not contain the new vertex {}", - element_idx, - new_pt_i); - valid = false; - } - - const auto orient = evaluateElementOrientationDeterminant(element_idx); - if(orient.orientation != primal::ON_POSITIVE_SIDE) - { - fmt::format_to( - std::back_inserter(out), - "\n\tInserted element {} has non-positive orientation determinant {:.17g} (tol={:.3g})", - element_idx, - orient.det, - orient.tol); - valid = false; - } - - const auto neighbors = m_mesh.adjacentElements(element_idx); - for(int facet_idx = 0; facet_idx < VERT_PER_ELEMENT; ++facet_idx) - { - inserted_faces[m_mesh.getSortedFacetKey(element_idx, facet_idx)].push_back( - {element_idx, facet_idx, neighbors[facet_idx]}); - } - } - - for(auto& face_entry : inserted_faces) - { - auto cavity_it = cavity_boundary.find(face_entry.first); - auto& records = face_entry.second; - if(cavity_it != cavity_boundary.end()) - { - if(records.size() != 1) - { - fmt::format_to(std::back_inserter(out), - "\n\tBoundary face {} of the inserted ball is used by {} new elements", - facetKeyString(face_entry.first), - records.size()); - valid = false; - continue; - } - - if(records.front().neighbor_idx != cavity_it->second.neighbor_idx) - { - fmt::format_to(std::back_inserter(out), - "\n\tInserted boundary face {} points to neighbor {} instead of {}", - facetKeyString(face_entry.first), - records.front().neighbor_idx, - cavity_it->second.neighbor_idx); - valid = false; - } - - if(m_mesh.isValidElement(cavity_it->second.neighbor_idx)) - { - const IndexType inserted_opposite = - findOppositeVertex(records.front().element_idx, face_entry.first); - const IndexType neighbor_opposite = - findOppositeVertex(cavity_it->second.neighbor_idx, face_entry.first); - - if(inserted_opposite == INVALID_INDEX || neighbor_opposite == INVALID_INDEX) - { - fmt::format_to( - std::back_inserter(out), - "\n\tCould not identify opposite vertices across inserted boundary face {}", - facetKeyString(face_entry.first)); - valid = false; - } - else - { - const PointType& inserted_point = m_mesh.getVertexPosition(inserted_opposite); - const PointType& neighbor_point = m_mesh.getVertexPosition(neighbor_opposite); - - if(isPointInSphereOnMesh(m_mesh, - inserted_point, - cavity_it->second.neighbor_idx, - /*includeBoundary=*/false)) - { - const int query_in_neighbor = - inSphereOrientationOnMesh(m_mesh, - m_mesh.getVertexPosition(new_pt_i), - cavity_it->second.neighbor_idx); - const int inserted_in_neighbor = - inSphereOrientationOnMesh(m_mesh, inserted_point, cavity_it->second.neighbor_idx); - fmt::format_to( - std::back_inserter(out), - "\n\tInserted boundary face {} leaves new vertex {} inside neighbor {} " - "circumsphere (query {}={}, det={:.17g}; opposite {}={}, det={:.17g})", - facetKeyString(face_entry.first), - inserted_opposite, - cavity_it->second.neighbor_idx, - new_pt_i, - orientationResultName(query_in_neighbor), - inSphereDeterminantOnMesh(m_mesh, - m_mesh.getVertexPosition(new_pt_i), - cavity_it->second.neighbor_idx), - inserted_opposite, - orientationResultName(inserted_in_neighbor), - inSphereDeterminantOnMesh(m_mesh, inserted_point, cavity_it->second.neighbor_idx)); - valid = false; - } - - if(isPointInSphereOnMesh(m_mesh, - neighbor_point, - records.front().element_idx, - /*includeBoundary=*/false)) - { - const int query_in_neighbor = - inSphereOrientationOnMesh(m_mesh, - m_mesh.getVertexPosition(new_pt_i), - cavity_it->second.neighbor_idx); - const int neighbor_in_inserted = - inSphereOrientationOnMesh(m_mesh, neighbor_point, records.front().element_idx); - fmt::format_to( - std::back_inserter(out), - "\n\tInserted boundary face {} leaves neighbor vertex {} inside new element {} " - "circumsphere (query {} in neighbor {} is {}, det={:.17g}; opposite {} in new " - "element is {}, det={:.17g})", - facetKeyString(face_entry.first), - neighbor_opposite, - records.front().element_idx, - new_pt_i, - cavity_it->second.neighbor_idx, - orientationResultName(query_in_neighbor), - inSphereDeterminantOnMesh(m_mesh, - m_mesh.getVertexPosition(new_pt_i), - cavity_it->second.neighbor_idx), - neighbor_opposite, - orientationResultName(neighbor_in_inserted), - inSphereDeterminantOnMesh(m_mesh, neighbor_point, records.front().element_idx)); - valid = false; - } - } - } - - cavity_it->second.matched = true; - } - else if(records.size() != 2 || records[0].neighbor_idx != records[1].element_idx || - records[1].neighbor_idx != records[0].element_idx) - { - fmt::format_to(std::back_inserter(out), - "\n\tInternal face {} of the inserted ball has inconsistent adjacency", - facetKeyString(face_entry.first)); - valid = false; - } - else - { - const IndexType lhs_opposite = findOppositeVertex(records[0].element_idx, face_entry.first); - const IndexType rhs_opposite = findOppositeVertex(records[1].element_idx, face_entry.first); - - if(lhs_opposite == INVALID_INDEX || rhs_opposite == INVALID_INDEX) - { - fmt::format_to(std::back_inserter(out), - "\n\tCould not identify opposite vertices across inserted internal face {}", - facetKeyString(face_entry.first)); - valid = false; - } - else - { - const PointType& lhs_point = m_mesh.getVertexPosition(lhs_opposite); - const PointType& rhs_point = m_mesh.getVertexPosition(rhs_opposite); - - if(isPointInSphereOnMesh(m_mesh, - lhs_point, - records[1].element_idx, - /*includeBoundary=*/false)) - { - const int query_in_rhs = inSphereOrientationOnMesh(m_mesh, - m_mesh.getVertexPosition(new_pt_i), - records[1].element_idx); - const int lhs_in_rhs = inSphereOrientationOnMesh(m_mesh, lhs_point, records[1].element_idx); - fmt::format_to( - std::back_inserter(out), - "\n\tInserted internal face {} leaves vertex {} inside adjacent element {} " - "circumsphere (query {}={}, det={:.17g}; opposite {}={}, det={:.17g})", - facetKeyString(face_entry.first), - lhs_opposite, - records[1].element_idx, - new_pt_i, - orientationResultName(query_in_rhs), - inSphereDeterminantOnMesh(m_mesh, - m_mesh.getVertexPosition(new_pt_i), - records[1].element_idx), - lhs_opposite, - orientationResultName(lhs_in_rhs), - inSphereDeterminantOnMesh(m_mesh, lhs_point, records[1].element_idx)); - valid = false; - } - - if(isPointInSphereOnMesh(m_mesh, - rhs_point, - records[0].element_idx, - /*includeBoundary=*/false)) - { - const int query_in_lhs = inSphereOrientationOnMesh(m_mesh, - m_mesh.getVertexPosition(new_pt_i), - records[0].element_idx); - const int rhs_in_lhs = inSphereOrientationOnMesh(m_mesh, rhs_point, records[0].element_idx); - fmt::format_to( - std::back_inserter(out), - "\n\tInserted internal face {} leaves vertex {} inside adjacent element {} " - "circumsphere (query {}={}, det={:.17g}; opposite {}={}, det={:.17g})", - facetKeyString(face_entry.first), - rhs_opposite, - records[0].element_idx, - new_pt_i, - orientationResultName(query_in_lhs), - inSphereDeterminantOnMesh(m_mesh, - m_mesh.getVertexPosition(new_pt_i), - records[0].element_idx), - rhs_opposite, - orientationResultName(rhs_in_lhs), - inSphereDeterminantOnMesh(m_mesh, rhs_point, records[0].element_idx)); - valid = false; - } - } - } - } - - for(const auto& facet_entry : cavity_boundary) - { - if(!facet_entry.second.matched) - { - fmt::format_to(std::back_inserter(out), - "\n\tCavity boundary face {} was not covered by the inserted ball", - facetKeyString(facet_entry.first)); - valid = false; - } - } - - SLIC_ERROR_IF(!valid, - "Delaunay ball validation failed after insertion " << m_num_insertions << ":" - << fmt::to_string(out)); -} - -//-------------------------------------------------------------------------------- -// Below are 2D and 3D specializations for methods in the Delaunay class -//-------------------------------------------------------------------------------- - -// 2D specialization for generateInitialMesh(...) -template <> -inline void Delaunay<2>::generateInitialMesh(std::vector& points, - std::vector& elem, - const BoundingBox& bb) -{ - //Set up the initial IA mesh of 2 triangles forming a rectangle - - const PointType& mins = bb.getMin(); - const PointType& maxs = bb.getMax(); - - // clang-format off - std::vector pt { mins[0], mins[1], - mins[0], maxs[1], - maxs[0], mins[1], - maxs[0], maxs[1] }; - - std::vector el { 0, 2, 1, - 3, 1, 2 }; - // clang-format on - - points.swap(pt); - elem.swap(el); -} - -// 3D specialization for generateInitialMesh(...) -template <> -inline void Delaunay<3>::generateInitialMesh(std::vector& points, - std::vector& elem, - const BoundingBox& bb) -{ - //Set up the initial IA mesh of 6 tetrahedrons forming a cube - const PointType& mins = bb.getMin(); - const PointType& maxs = bb.getMax(); - - // clang-format off - std::vector pt { mins[0], mins[1], mins[2], - mins[0], mins[1], maxs[2], - mins[0], maxs[1], mins[2], - mins[0], maxs[1], maxs[2], - maxs[0], mins[1], mins[2], - maxs[0], mins[1], maxs[2], - maxs[0], maxs[1], mins[2], - maxs[0], maxs[1], maxs[2] }; - - std::vector el { 3, 2, 4, 0, - 3, 4, 1, 0, - 3, 2, 6, 4, - 3, 6, 7, 4, - 3, 5, 1, 4, - 3, 7, 5, 4 }; - // clang-format on - - points.swap(pt); - elem.swap(el); -} - -template -inline typename Delaunay::BaryCoordType Delaunay::getBaryCoords(IndexType element_idx, - const PointType& query_pt) const -{ - const auto verts = m_mesh.boundaryVertices(element_idx); - - if constexpr(DIM == 2) - { - const ElementType tri(m_mesh.getVertexPosition(verts[0]), - m_mesh.getVertexPosition(verts[1]), - m_mesh.getVertexPosition(verts[2])); - - return tri.physToBarycentric(query_pt); - } - else - { - const ElementType tet(m_mesh.getVertexPosition(verts[0]), - m_mesh.getVertexPosition(verts[1]), - m_mesh.getVertexPosition(verts[2]), - m_mesh.getVertexPosition(verts[3])); - - return tet.physToBarycentric(query_pt); - } -} - -template -inline bool Delaunay::InsertionHelper::isPointInCircumsphere(const PointType& query_pt, - IndexType element_idx) const -{ - // The cavity is defined by elements whose circumspheres contain or touch the - // insertion point. Returning "true on boundary" ensures the cavity is - // topologically closed for co-spherical inputs (e.g. regular grids). - return Delaunay::isPointInSphereOnMesh(m_mesh, query_pt, element_idx, /*includeBoundary=*/true); -} +} // namespace quest +} // namespace axom -} // end namespace quest -} // end namespace axom +#include "detail/DelaunayPointLocation.hpp" +#include "detail/DelaunayImpl.hpp" -#endif // QUEST_DELAUNAY_H_ +#endif // QUEST_DELAUNAY_H_ diff --git a/src/axom/quest/benchmarks/quest_delaunay_circumsphere.cpp b/src/axom/quest/benchmarks/quest_delaunay_circumsphere.cpp index fd6b861a13..dace130735 100644 --- a/src/axom/quest/benchmarks/quest_delaunay_circumsphere.cpp +++ b/src/axom/quest/benchmarks/quest_delaunay_circumsphere.cpp @@ -42,6 +42,17 @@ using SimplexType = std::array, DIM + 1>; template using SampleSet = std::array, 4096>; +template +using IndexTuple = std::array; + +template +struct IndexedSampleSet +{ + std::array, (DIM + 1) * 4096> points; + std::array, 4096> simplices; + std::array, 4096> queries; +}; + double unitInterval(std::uint64_t bits) { return static_cast(bits & 0xFFFFFFFFu) / static_cast(0x100000000ULL); @@ -55,6 +66,9 @@ std::uint64_t stepLcg(std::uint64_t state) template SampleSet makeSamples(); +template +IndexedSampleSet makeIndexedSamples(); + template <> SampleSet<2> makeSamples<2>() { @@ -132,6 +146,62 @@ inline double consumeEval(const CircumsphereEval& eval) return value; } +template +inline double manualSquaredDistance(const PointType& a, const PointType& b) +{ + double retval = 0.; + for(int dim = 0; dim < DIM; ++dim) + { + const double d = b[dim] - a[dim]; + retval += d * d; + } + return retval; +} + +template <> +IndexedSampleSet<2> makeIndexedSamples<2>() +{ + IndexedSampleSet<2> samples; + const auto simplex_samples = makeSamples<2>(); + + for(std::size_t i = 0; i < simplex_samples.size(); ++i) + { + const int base = static_cast(3 * i); + for(int j = 0; j < 3; ++j) + { + samples.points[base + j] = simplex_samples[i][j]; + samples.simplices[i][j] = base + j; + } + + const auto& p0 = simplex_samples[i][0]; + samples.queries[i] = PointType<2> {p0[0] + 0.013, p0[1] + 0.017}; + } + + return samples; +} + +template <> +IndexedSampleSet<3> makeIndexedSamples<3>() +{ + IndexedSampleSet<3> samples; + const auto simplex_samples = makeSamples<3>(); + + for(std::size_t i = 0; i < simplex_samples.size(); ++i) + { + const int base = static_cast(4 * i); + for(int j = 0; j < 4; ++j) + { + samples.points[base + j] = simplex_samples[i][j]; + samples.simplices[i][j] = base + j; + } + + const auto& p0 = simplex_samples[i][0]; + samples.queries[i] = PointType<3> {p0[0] + 0.013, p0[1] + 0.017, p0[2] + 0.019}; + } + + return samples; +} + inline CircumsphereEval<2> evaluateCircumsphereScalarEdges(const SimplexType<2>& simplex) { const PointType<2>& p0 = simplex[0]; @@ -178,6 +248,38 @@ inline CircumsphereEval<2> evaluateCircumsphereVectorEdges(const SimplexType<2>& return CircumsphereEval<2>(p0, VectorType<2> {center_offset_x, center_offset_y}); } +inline CircumsphereEval<2> evaluateCircumsphereScopedVectorEdges(const SimplexType<2>& simplex) +{ + const PointType<2>& p0 = simplex[0]; + const PointType<2>& p1 = simplex[1]; + const PointType<2>& p2 = simplex[2]; + + double vx0, vx1, vy0, vy1, sq0, sq1; + + { + const VectorType<2> v0(p0, p1); + vx0 = v0[0]; + vy0 = v0[1]; + sq0 = v0.squared_norm(); + } + + { + const VectorType<2> v1(p0, p2); + vx1 = v1[0]; + vy1 = v1[1]; + sq1 = v1.squared_norm(); + } + + const double a = determinant(vx0, vx1, vy0, vy1); + const double eps = (a >= 0.) ? axom::primal::PRIMAL_TINY : -axom::primal::PRIMAL_TINY; + const double ood = 1. / (2. * a + eps); + + const double center_offset_x = determinant(sq0, sq1, vy0, vy1) * ood; + const double center_offset_y = -determinant(sq0, sq1, vx0, vx1) * ood; + + return CircumsphereEval<2>(p0, VectorType<2> {center_offset_x, center_offset_y}); +} + inline CircumsphereEval<3> evaluateCircumsphereScalarEdges(const SimplexType<3>& simplex) { const PointType<3>& p0 = simplex[0]; @@ -239,6 +341,66 @@ inline CircumsphereEval<3> evaluateCircumsphereVectorEdges(const SimplexType<3>& return CircumsphereEval<3>(p0, VectorType<3> {center_offset_x, center_offset_y, center_offset_z}); } +inline CircumsphereEval<3> evaluateCircumsphereScopedVectorEdges(const SimplexType<3>& simplex) +{ + const PointType<3>& p0 = simplex[0]; + const PointType<3>& p1 = simplex[1]; + const PointType<3>& p2 = simplex[2]; + const PointType<3>& p3 = simplex[3]; + + double vx0, vx1, vx2; + double vy0, vy1, vy2; + double vz0, vz1, vz2; + double sq0, sq1, sq2; + + { + const VectorType<3> v0(p0, p1); + vx0 = v0[0]; + vy0 = v0[1]; + vz0 = v0[2]; + sq0 = v0.squared_norm(); + } + + { + const VectorType<3> v1(p0, p2); + vx1 = v1[0]; + vy1 = v1[1]; + vz1 = v1[2]; + sq1 = v1.squared_norm(); + } + + { + const VectorType<3> v2(p0, p3); + vx2 = v2[0]; + vy2 = v2[1]; + vz2 = v2[2]; + sq2 = v2.squared_norm(); + } + + const double a = determinant(vx0, vx1, vx2, vy0, vy1, vy2, vz0, vz1, vz2); + const double eps = (a >= 0.) ? axom::primal::PRIMAL_TINY : -axom::primal::PRIMAL_TINY; + const double ood = 1. / (2. * a + eps); + + const double center_offset_x = determinant(sq0, sq1, sq2, vy0, vy1, vy2, vz0, vz1, vz2) * ood; + const double center_offset_y = determinant(sq0, sq1, sq2, vz0, vz1, vz2, vx0, vx1, vx2) * ood; + const double center_offset_z = determinant(sq0, sq1, sq2, vx0, vx1, vx2, vy0, vy1, vy2) * ood; + + return CircumsphereEval<3>(p0, VectorType<3> {center_offset_x, center_offset_y, center_offset_z}); +} + +template +inline CircumsphereEval evaluateIndexed(const IndexedSampleSet& samples, + const IndexTuple& verts, + Kernel&& kernel) +{ + SimplexType simplex; + for(int j = 0; j < DIM + 1; ++j) + { + simplex[j] = samples.points[verts[j]]; + } + return kernel(simplex); +} + template void runCircumsphereBenchmark(benchmark::State& state, Kernel&& kernel) { @@ -262,6 +424,54 @@ void runCircumsphereBenchmark(benchmark::State& state, Kernel&& kernel) state.SetItemsProcessed(static_cast(state.iterations())); } +template +void runIndexedCircumsphereBenchmark(benchmark::State& state, Kernel&& kernel) +{ + const auto& samples = []() -> const IndexedSampleSet& { + static const IndexedSampleSet value = makeIndexedSamples(); + return value; + }(); + + const std::size_t mask = samples.simplices.size() - 1; + std::size_t idx = 0; + double checksum = 0.; + + for(auto _ : state) + { + const auto& verts = samples.simplices[idx]; + checksum += consumeEval(evaluateIndexed(samples, verts, kernel)); + idx = (idx + 1) & mask; + } + + benchmark::DoNotOptimize(checksum); + state.SetItemsProcessed(static_cast(state.iterations())); +} + +template +void runQueryDistanceBenchmark(benchmark::State& state, Kernel&& kernel) +{ + const auto& samples = []() -> const IndexedSampleSet& { + static const IndexedSampleSet value = makeIndexedSamples(); + return value; + }(); + + const std::size_t mask = samples.simplices.size() - 1; + std::size_t idx = 0; + double checksum = 0.; + + for(auto _ : state) + { + const auto& verts = samples.simplices[idx]; + const PointType& center = samples.points[verts[0]]; + const PointType& q = samples.queries[idx]; + checksum += kernel(center, q); + idx = (idx + 1) & mask; + } + + benchmark::DoNotOptimize(checksum); + state.SetItemsProcessed(static_cast(state.iterations())); +} + void benchmark_scalar_edges_2d(benchmark::State& state) { runCircumsphereBenchmark<2>(state, [](const SimplexType<2>& simplex) { @@ -276,6 +486,13 @@ void benchmark_vector_edges_2d(benchmark::State& state) }); } +void benchmark_scoped_vector_edges_2d(benchmark::State& state) +{ + runCircumsphereBenchmark<2>(state, [](const SimplexType<2>& simplex) { + return evaluateCircumsphereScopedVectorEdges(simplex); + }); +} + void benchmark_scalar_edges_3d(benchmark::State& state) { runCircumsphereBenchmark<3>(state, [](const SimplexType<3>& simplex) { @@ -290,11 +507,116 @@ void benchmark_vector_edges_3d(benchmark::State& state) }); } +void benchmark_scoped_vector_edges_3d(benchmark::State& state) +{ + runCircumsphereBenchmark<3>(state, [](const SimplexType<3>& simplex) { + return evaluateCircumsphereScopedVectorEdges(simplex); + }); +} + +void benchmark_indexed_scalar_edges_2d(benchmark::State& state) +{ + runIndexedCircumsphereBenchmark<2>(state, [](const SimplexType<2>& simplex) { + return evaluateCircumsphereScalarEdges(simplex); + }); +} + +void benchmark_indexed_vector_edges_2d(benchmark::State& state) +{ + runIndexedCircumsphereBenchmark<2>(state, [](const SimplexType<2>& simplex) { + return evaluateCircumsphereVectorEdges(simplex); + }); +} + +void benchmark_indexed_scoped_vector_edges_2d(benchmark::State& state) +{ + runIndexedCircumsphereBenchmark<2>(state, [](const SimplexType<2>& simplex) { + return evaluateCircumsphereScopedVectorEdges(simplex); + }); +} + +void benchmark_indexed_scalar_edges_3d(benchmark::State& state) +{ + runIndexedCircumsphereBenchmark<3>(state, [](const SimplexType<3>& simplex) { + return evaluateCircumsphereScalarEdges(simplex); + }); +} + +void benchmark_indexed_vector_edges_3d(benchmark::State& state) +{ + runIndexedCircumsphereBenchmark<3>(state, [](const SimplexType<3>& simplex) { + return evaluateCircumsphereVectorEdges(simplex); + }); +} + +void benchmark_indexed_scoped_vector_edges_3d(benchmark::State& state) +{ + runIndexedCircumsphereBenchmark<3>(state, [](const SimplexType<3>& simplex) { + return evaluateCircumsphereScopedVectorEdges(simplex); + }); +} + +void benchmark_query_vector_2d(benchmark::State& state) +{ + runQueryDistanceBenchmark<2>(state, [](const PointType<2>& center, const PointType<2>& q) { + return VectorType<2>(center, q).squared_norm(); + }); +} + +void benchmark_query_squared_distance_2d(benchmark::State& state) +{ + runQueryDistanceBenchmark<2>(state, [](const PointType<2>& center, const PointType<2>& q) { + return axom::primal::squared_distance(center, q); + }); +} + +void benchmark_query_manual_squared_distance_2d(benchmark::State& state) +{ + runQueryDistanceBenchmark<2>(state, [](const PointType<2>& center, const PointType<2>& q) { + return manualSquaredDistance(center, q); + }); +} + +void benchmark_query_vector_3d(benchmark::State& state) +{ + runQueryDistanceBenchmark<3>(state, [](const PointType<3>& center, const PointType<3>& q) { + return VectorType<3>(center, q).squared_norm(); + }); +} + +void benchmark_query_squared_distance_3d(benchmark::State& state) +{ + runQueryDistanceBenchmark<3>(state, [](const PointType<3>& center, const PointType<3>& q) { + return axom::primal::squared_distance(center, q); + }); +} + +void benchmark_query_manual_squared_distance_3d(benchmark::State& state) +{ + runQueryDistanceBenchmark<3>(state, [](const PointType<3>& center, const PointType<3>& q) { + return manualSquaredDistance(center, q); + }); +} + } // namespace BENCHMARK(benchmark_scalar_edges_2d); BENCHMARK(benchmark_vector_edges_2d); +BENCHMARK(benchmark_scoped_vector_edges_2d); BENCHMARK(benchmark_scalar_edges_3d); BENCHMARK(benchmark_vector_edges_3d); +BENCHMARK(benchmark_scoped_vector_edges_3d); +BENCHMARK(benchmark_indexed_scalar_edges_2d); +BENCHMARK(benchmark_indexed_vector_edges_2d); +BENCHMARK(benchmark_indexed_scoped_vector_edges_2d); +BENCHMARK(benchmark_indexed_scalar_edges_3d); +BENCHMARK(benchmark_indexed_vector_edges_3d); +BENCHMARK(benchmark_indexed_scoped_vector_edges_3d); +BENCHMARK(benchmark_query_vector_2d); +BENCHMARK(benchmark_query_squared_distance_2d); +BENCHMARK(benchmark_query_manual_squared_distance_2d); +BENCHMARK(benchmark_query_vector_3d); +BENCHMARK(benchmark_query_squared_distance_3d); +BENCHMARK(benchmark_query_manual_squared_distance_3d); BENCHMARK_MAIN(); diff --git a/src/axom/quest/detail/DelaunayElementFinder.hpp b/src/axom/quest/detail/DelaunayElementFinder.hpp new file mode 100644 index 0000000000..90eaf7a6fd --- /dev/null +++ b/src/axom/quest/detail/DelaunayElementFinder.hpp @@ -0,0 +1,226 @@ +// 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_DETAIL_DELAUNAY_ELEMENT_FINDER_HPP_ +#define AXOM_QUEST_DETAIL_DELAUNAY_ELEMENT_FINDER_HPP_ + +#include "axom/core.hpp" +#include "axom/primal.hpp" +#include "axom/spin.hpp" + +#include +#include + +namespace axom +{ +namespace quest +{ +namespace detail +{ + +template +class DelaunayElementFinder +{ +public: + using NumericArrayType = NumericArray; + using LatticeType = spin::RectangularLattice; + + explicit DelaunayElementFinder() = default; + + void recomputeGrid(const IAMeshType& mesh, const BoundingBox& bb) + { + const auto& verts = mesh.vertices(); + + // Choose the grid resolution so that each bin contains ~O(1) points per + // dimension on average. This keeps the "nearby bin" seed used by point + // insertion and query walks close (in terms of simplex adjacency hops) + // even for very large point sets. + // + // Target occupancy is ~ 4^DIM points per bin (16 in 2D, 64 in 3D). + constexpr double BIN_SIDE_SPACING = 4.0; + const double res_root = std::pow(static_cast(verts.size()), 1.0 / DIM); + const IndexType res = + axom::utilities::max(IndexType {2}, + static_cast(std::ceil(res_root / BIN_SIDE_SPACING))); + + auto expandedBB = BoundingBox(bb).scale(1.05); + + m_lattice = spin::rectangular_lattice_from_bounding_box(expandedBB, NumericArrayType(res)); + + resizeArray(res); + m_bins.fill(INVALID_INDEX); + + for(auto idx : verts.positions()) + { + if(!mesh.isValidVertex(idx)) + { + continue; + } + + const IndexType coboundary = mesh.coboundaryElement(idx); + if(!mesh.isValidElement(coboundary)) + { + continue; + } + + const auto& pos = mesh.getVertexPosition(idx); + const auto cell = m_lattice.gridCell(pos); + IndexType& slot = flatIndex(cell); + if(!mesh.isValidVertex(slot) || !mesh.isValidElement(mesh.coboundaryElement(slot))) + { + slot = idx; + } + } + } + + inline void getNearbyVertices(const IAMeshType& mesh, + const PointType& pt, + std::vector& nearby_vertices, + int search_radius = 1, + int max_candidates = 1) const + { + const auto cell = m_lattice.gridCell(pt); + m_candidate_scratch.clear(); + const int span = 2 * search_radius + 1; + const int max_bins = (DIM == 2) ? (span * span) : (span * span * span); + m_candidate_scratch.reserve(static_cast(max_bins)); + + auto tryCandidate = [&](const typename LatticeType::GridCell& candidate_cell) { + const IndexType vertex_idx = flatIndex(candidate_cell); + if(mesh.isValidVertex(vertex_idx) && mesh.isValidElement(mesh.coboundaryElement(vertex_idx))) + { + const double sq_dist = primal::squared_distance(mesh.getVertexPosition(vertex_idx), pt); + m_candidate_scratch.emplace_back(sq_dist, vertex_idx); + } + }; + + if constexpr(DIM == 2) + { + for(int dj = -search_radius; dj <= search_radius; ++dj) + { + const IndexType j = cell[1] + dj; + if(j < 0 || j >= m_bins.shape()[1]) + { + continue; + } + + for(int di = -search_radius; di <= search_radius; ++di) + { + const IndexType i = cell[0] + di; + if(i < 0 || i >= m_bins.shape()[0]) + { + continue; + } + + tryCandidate(typename LatticeType::GridCell {{i, j}}); + } + } + } + else + { + for(int dk = -search_radius; dk <= search_radius; ++dk) + { + const IndexType k = cell[2] + dk; + if(k < 0 || k >= m_bins.shape()[2]) + { + continue; + } + + for(int dj = -search_radius; dj <= search_radius; ++dj) + { + const IndexType j = cell[1] + dj; + if(j < 0 || j >= m_bins.shape()[1]) + { + continue; + } + + for(int di = -search_radius; di <= search_radius; ++di) + { + const IndexType i = cell[0] + di; + if(i < 0 || i >= m_bins.shape()[0]) + { + continue; + } + + tryCandidate(typename LatticeType::GridCell {{i, j, k}}); + } + } + } + } + + if(static_cast(m_candidate_scratch.size()) > max_candidates) + { + auto kth = m_candidate_scratch.begin() + max_candidates; + std::nth_element(m_candidate_scratch.begin(), + kth, + m_candidate_scratch.end(), + [](const auto& lhs, const auto& rhs) { return lhs.first < rhs.first; }); + m_candidate_scratch.resize(static_cast(max_candidates)); + } + + std::sort(m_candidate_scratch.begin(), + m_candidate_scratch.end(), + [](const auto& lhs, const auto& rhs) { return lhs.first < rhs.first; }); + + nearby_vertices.clear(); + nearby_vertices.reserve( + axom::utilities::min(max_candidates, static_cast(m_candidate_scratch.size()))); + for(const auto& candidate : m_candidate_scratch) + { + nearby_vertices.push_back(candidate.second); + } + } + + inline IndexType getNearbyVertex(const PointType& pt) const + { + const auto cell = m_lattice.gridCell(pt); + return flatIndex(cell); + } + + inline void updateBin(const PointType& pt, IndexType vertex_id) + { + const auto cell = m_lattice.gridCell(pt); + flatIndex(cell) = vertex_id; + } + +private: + static constexpr IndexType INVALID_INDEX = IndexType {-1}; + + inline IndexType& flatIndex(const typename LatticeType::GridCell& cell) + { + const IndexType idx = numerics::dot_product(cell.data(), m_bins.strides().begin(), DIM); + return m_bins.flatIndex(idx); + } + + inline const IndexType& flatIndex(const typename LatticeType::GridCell& cell) const + { + const IndexType idx = numerics::dot_product(cell.data(), m_bins.strides().begin(), DIM); + return m_bins.flatIndex(idx); + } + + template + typename std::enable_if::type resizeArray(IndexType res) + { + m_bins.resize(res, res); + } + + template + typename std::enable_if::type resizeArray(IndexType res) + { + m_bins.resize(res, res, res); + } + +private: + axom::Array m_bins; + LatticeType m_lattice; + mutable std::vector> m_candidate_scratch; +}; + +} // namespace detail +} // namespace quest +} // namespace axom + +#endif diff --git a/src/axom/quest/detail/DelaunayImpl.hpp b/src/axom/quest/detail/DelaunayImpl.hpp new file mode 100644 index 0000000000..9412a71f3c --- /dev/null +++ b/src/axom/quest/detail/DelaunayImpl.hpp @@ -0,0 +1,1360 @@ +// 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_DETAIL_DELAUNAY_IMPL_HPP_ +#define AXOM_QUEST_DETAIL_DELAUNAY_IMPL_HPP_ + +namespace axom +{ +namespace quest +{ + +template +template +inline typename Delaunay::FacetKey Delaunay::makeSortedFaceKey(const FacetSubsetType& facet) +{ + FacetKey key {}; + for(int i = 0; i < VERTS_PER_FACET; ++i) + { + key[i] = facet[i]; + } + std::sort(key.begin(), key.end()); + return key; +} + +template +inline std::string Delaunay::facetKeyString(const FacetKey& facet_key) +{ + return fmt::format("[{}]", fmt::join(facet_key, ", ")); +} + +template +inline double Delaunay::getBoundaryCoordinateTolerance() const +{ + const auto min_pt = m_bounding_box.getMin(); + const auto max_pt = m_bounding_box.getMax(); + + double max_extent = 1.; + for(int dim = 0; dim < DIM; ++dim) + { + max_extent = axom::utilities::max(max_extent, max_pt[dim] - min_pt[dim]); + } + + return 64. * std::numeric_limits::epsilon() * max_extent; +} + +template +inline bool Delaunay::isFacetOnBoundingBox(const FacetKey& facet_key) const +{ + const auto& min_pt = m_bounding_box.getMin(); + const auto& max_pt = m_bounding_box.getMax(); + const double tol = getBoundaryCoordinateTolerance(); + + for(int dim = 0; dim < DIM; ++dim) + { + bool on_min_face = true; + bool on_max_face = true; + for(const IndexType vertex_idx : facet_key) + { + const auto& vertex = m_mesh.getVertexPosition(vertex_idx); + on_min_face &= axom::utilities::abs(vertex[dim] - min_pt[dim]) <= tol; + on_max_face &= axom::utilities::abs(vertex[dim] - max_pt[dim]) <= tol; + } + + if(on_min_face || on_max_face) + { + return true; + } + } + + return false; +} + +template +inline double Delaunay::getElementSignedMeasure(IndexType element_idx) const +{ + if constexpr(DIM == 2) + { + return getElement(element_idx).signedArea(); + } + else + { + return getElement(element_idx).signedVolume(); + } +} + +template +inline double Delaunay::getElementMeasureTolerance() const +{ + const double scale = axom::utilities::max(1., m_bounding_box.range().norm()); + return 256. * std::numeric_limits::epsilon() * std::pow(scale, static_cast(DIM)); +} + +template +inline void Delaunay::validateInsertionSeed(IndexType element_idx, + const PointType& query_pt, + const BaryCoordType& bary_coord, + const IndexArray& seed_elements) const +{ + if(m_insertion_validation_mode == InsertionValidationMode::None) + { + return; + } + + fmt::memory_buffer out; + bool valid = true; + + if(!isSearchableElement(element_idx)) + { + fmt::format_to(std::back_inserter(out), "\n\tContaining element {} is not searchable", element_idx); + valid = false; + } + else + { + const auto verts = m_mesh.boundaryVertices(element_idx); + for(auto idx : verts.positions()) + { + if(!m_mesh.isValidVertex(verts[idx])) + { + fmt::format_to(std::back_inserter(out), + "\n\tContaining element {} references invalid vertex {}", + element_idx, + verts[idx]); + valid = false; + break; + } + } + } + + if(!isPointInsideForLocation(element_idx, query_pt, bary_coord)) + { + fmt::format_to(std::back_inserter(out), + "\n\tPoint {} is not inside containing element {}", + query_pt, + element_idx); + valid = false; + } + + if(seed_elements.empty()) + { + fmt::format_to(std::back_inserter(out), "\n\tNo cavity seed elements were generated"); + valid = false; + } + + bool found_containing_element = false; + for(const IndexType seed_element : seed_elements) + { + found_containing_element |= (seed_element == element_idx); + if(!m_mesh.isValidElement(seed_element)) + { + fmt::format_to(std::back_inserter(out), "\n\tSeed element {} is invalid", seed_element); + valid = false; + } + } + + if(!found_containing_element) + { + fmt::format_to(std::back_inserter(out), + "\n\tContaining element {} is missing from the seed set", + element_idx); + valid = false; + } + + SLIC_ERROR_IF(!valid, "Delaunay insertion seed validation failed:" << fmt::to_string(out)); +} + +template +inline bool Delaunay::isSearchableElement(IndexType element_idx) const +{ + return m_mesh.isValidElement(element_idx); +} + +template +inline double Delaunay::orientationTolerance(const std::array& pts) +{ + const double scale = getPointMagnitudeScale(pts); + if constexpr(DIM == 2) + { + return 64. * std::numeric_limits::epsilon() * scale * scale; + } + else + { + return 64. * std::numeric_limits::epsilon() * scale * scale * scale; + } +} + +template +inline double Delaunay::determinant3(const PointType& p0, const PointType& p1, const PointType& p2) +{ + return axom::numerics::determinant(p0[0], p0[1], p0[2], p1[0], p1[1], p1[2], p2[0], p2[1], p2[2]); +} + +template +inline double Delaunay::orientationDeterminant(const std::array& pts) +{ + return axom::numerics::determinant(pts[0][0], + pts[0][1], + pts[0][2], + 1., + pts[1][0], + pts[1][1], + pts[1][2], + 1., + pts[2][0], + pts[2][1], + pts[2][2], + 1., + pts[3][0], + pts[3][1], + pts[3][2], + 1.); +} + +template +inline int Delaunay::symbolicOrientationSign(const std::array& pts, + const std::array& ranks) +{ + const double det = orientationDeterminant(pts); + const int det_sign = signWithTolerance(det, orientationTolerance(pts)); + if(det_sign != 0) + { + return det_sign; + } + + const std::array cofactors {-determinant3(pts[1], pts[2], pts[3]), + determinant3(pts[0], pts[2], pts[3]), + -determinant3(pts[0], pts[1], pts[3]), + determinant3(pts[0], pts[1], pts[2])}; + + std::array order {{0, 1, 2, 3}}; + std::sort(order.begin(), order.end(), [&](int lhs, int rhs) { return ranks[lhs] < ranks[rhs]; }); + + const double cofactor_tol = 64. * std::numeric_limits::epsilon() * + axom::utilities::max(1., orientationTolerance(pts)); + for(const int row : order) + { + const int sign = signWithTolerance(cofactors[row], cofactor_tol); + if(sign != 0) + { + return sign; + } + } + + return 0; +} + +template +inline typename Delaunay::CircumsphereEval Delaunay::evaluateCircumsphereOnMesh( + const IAMeshType& mesh, + IndexType element_idx) +{ + using axom::numerics::determinant; + + const auto verts = mesh.boundaryVertices(element_idx); + + if constexpr(DIM == 2) + { + const PointType& p0 = mesh.getVertexPosition(verts[0]); + const PointType& p1 = mesh.getVertexPosition(verts[1]); + const PointType& p2 = mesh.getVertexPosition(verts[2]); + + const double vx0 = p1[0] - p0[0]; + const double vx1 = p2[0] - p0[0]; + const double vy0 = p1[1] - p0[1]; + const double vy1 = p2[1] - p0[1]; + + const double sq0 = vx0 * vx0 + vy0 * vy0; + const double sq1 = vx1 * vx1 + vy1 * vy1; + + const double a = determinant(vx0, vx1, vy0, vy1); + const double eps = (a >= 0.) ? primal::PRIMAL_TINY : -primal::PRIMAL_TINY; + const double ood = 1. / (2. * a + eps); + + const double center_offset_x = determinant(sq0, sq1, vy0, vy1) * ood; + const double center_offset_y = -determinant(sq0, sq1, vx0, vx1) * ood; + + return CircumsphereEval(p0, VectorType {center_offset_x, center_offset_y}); + } + else + { + const PointType& p0 = mesh.getVertexPosition(verts[0]); + const PointType& p1 = mesh.getVertexPosition(verts[1]); + const PointType& p2 = mesh.getVertexPosition(verts[2]); + const PointType& p3 = mesh.getVertexPosition(verts[3]); + + const double vx0 = p1[0] - p0[0]; + const double vx1 = p2[0] - p0[0]; + const double vx2 = p3[0] - p0[0]; + const double vy0 = p1[1] - p0[1]; + const double vy1 = p2[1] - p0[1]; + const double vy2 = p3[1] - p0[1]; + const double vz0 = p1[2] - p0[2]; + const double vz1 = p2[2] - p0[2]; + const double vz2 = p3[2] - p0[2]; + + const double sq0 = vx0 * vx0 + vy0 * vy0 + vz0 * vz0; + const double sq1 = vx1 * vx1 + vy1 * vy1 + vz1 * vz1; + const double sq2 = vx2 * vx2 + vy2 * vy2 + vz2 * vz2; + + const double a = determinant(vx0, vx1, vx2, vy0, vy1, vy2, vz0, vz1, vz2); + const double eps = (a >= 0.) ? primal::PRIMAL_TINY : -primal::PRIMAL_TINY; + const double ood = 1. / (2. * a + eps); + + const double center_offset_x = determinant(sq0, sq1, sq2, vy0, vy1, vy2, vz0, vz1, vz2) * ood; + const double center_offset_y = determinant(sq0, sq1, sq2, vz0, vz1, vz2, vx0, vx1, vx2) * ood; + const double center_offset_z = determinant(sq0, sq1, sq2, vx0, vx1, vx2, vy0, vy1, vy2) * ood; + + return CircumsphereEval(p0, VectorType {center_offset_x, center_offset_y, center_offset_z}); + } +} + +template +inline double Delaunay::sphereSquaredDistanceTolerance(const CircumsphereEval& sphere, + const PointType& x, + double distance_sq) +{ + double scale = 1.; + + for(int dim = 0; dim < DIM; ++dim) + { + scale = axom::utilities::max(scale, axom::utilities::abs(sphere.center[dim])); + scale = axom::utilities::max(scale, axom::utilities::abs(x[dim])); + } + + const double local_span = + axom::utilities::max(1., 2. * std::sqrt(axom::utilities::max(distance_sq, sphere.radius_sq))); + + return 256. * std::numeric_limits::epsilon() * scale * local_span; +} + +template +template +inline double Delaunay::sphereSignedDistanceTolerance(const SphereType& sphere, + const PointType& x) +{ + const auto& center = sphere.getCenter(); + double scale = axom::utilities::max(1., sphere.getRadius()); + for(int dim = 0; dim < DIM; ++dim) + { + scale = axom::utilities::max(scale, axom::utilities::abs(center[dim])); + scale = axom::utilities::max(scale, axom::utilities::abs(x[dim])); + } + + return 256. * std::numeric_limits::epsilon() * scale; +} + +template +inline int Delaunay::inSphereOrientationOnMesh(const IAMeshType& mesh, + const PointType& q, + IndexType element_idx) +{ + const auto sphere = evaluateCircumsphereOnMesh(mesh, element_idx); + const double distance_sq = primal::squared_distance(sphere.center, q); + + const double delta_sq = distance_sq - sphere.radius_sq; + const double tol = sphereSquaredDistanceTolerance(sphere, q, distance_sq); + if(axom::utilities::abs(delta_sq) <= tol) + { + return primal::ON_BOUNDARY; + } + + return delta_sq < 0. ? primal::ON_NEGATIVE_SIDE : primal::ON_POSITIVE_SIDE; +} + +template +inline double Delaunay::inSphereDeterminantOnMesh(const IAMeshType& mesh, + const PointType& q, + IndexType element_idx) +{ + const auto verts = mesh.boundaryVertices(element_idx); + + if constexpr(DIM == 2) + { + return primal::in_sphere_determinant(q, + mesh.getVertexPosition(verts[0]), + mesh.getVertexPosition(verts[1]), + mesh.getVertexPosition(verts[2])); + } + else + { + return primal::in_sphere_determinant(q, + mesh.getVertexPosition(verts[0]), + mesh.getVertexPosition(verts[1]), + mesh.getVertexPosition(verts[2]), + mesh.getVertexPosition(verts[3])); + } +} + +template +inline const char* Delaunay::orientationResultName(int result) +{ + switch(result) + { + case primal::ON_NEGATIVE_SIDE: + return "inside"; + case primal::ON_BOUNDARY: + return "boundary"; + case primal::ON_POSITIVE_SIDE: + return "outside"; + default: + return "unknown"; + } +} + +template +inline bool Delaunay::isPointInSphereOnMesh(const IAMeshType& mesh, + const PointType& q, + IndexType element_idx, + bool includeBoundary) +{ + const int res = inSphereOrientationOnMesh(mesh, q, element_idx); + return includeBoundary ? (res != primal::ON_POSITIVE_SIDE) : (res == primal::ON_NEGATIVE_SIDE); +} + +template +inline int Delaunay::classifyOrientationDeterminant(double det, double tol) +{ + const int sign = signWithTolerance(det, tol); + return sign < 0 ? primal::ON_NEGATIVE_SIDE + : (sign > 0 ? primal::ON_POSITIVE_SIDE : primal::ON_BOUNDARY); +} + +template +inline typename Delaunay::OrientationEval Delaunay::evaluateElementOrientationDeterminant( + IndexType element_idx) const +{ + const double scale = (DIM == 2) ? 2. : 6.; + const double det = scale * getElementSignedMeasure(element_idx); + const double tol = scale * getElementMeasureTolerance(); + return {det, tol, classifyOrientationDeterminant(det, tol)}; +} + +template +inline void Delaunay::validateInsertionResult() const +{ + if(m_insertion_validation_mode == InsertionValidationMode::None) + { + return; + } + + if(m_insertion_validation_mode == InsertionValidationMode::Local) + { + return; + } + + if(!m_mesh.isValid(false)) + { + m_mesh.isValid(true); + SLIC_ERROR("Delaunay insertion produced an invalid IAMesh"); + } + + if(!isConforming(false)) + { + isConforming(true); + SLIC_ERROR("Delaunay insertion produced a non-conforming triangulation"); + } + + if(m_insertion_validation_mode == InsertionValidationMode::Full) + { + if(!isValid(false)) + { + isValid(true); + SLIC_ERROR( + "Delaunay insertion produced a triangulation that violates the empty-circumsphere " + "condition"); + } + } +} + +template +inline bool Delaunay::shouldCompactMesh() const +{ + constexpr int MIN_REMOVED_ELEMENTS = DIM == 2 ? 512 : 2048; + constexpr double REMOVED_ELEMENT_FRACTION = DIM == 2 ? 0.25 : 0.35; + return static_cast(m_deleted_elements.size()) > MIN_REMOVED_ELEMENTS && + (static_cast(m_deleted_elements.size()) > + REMOVED_ELEMENT_FRACTION * static_cast(m_mesh.elements().size())); +} + +template +inline void Delaunay::compactMesh() +{ + m_mesh.compact(); + m_deleted_elements.clear(); + m_element_finder.recomputeGrid(m_mesh, m_bounding_box); + if(m_next_regrid_vertex_count > 0) + { + while(m_next_regrid_vertex_count <= m_mesh.vertices().size()) + { + m_next_regrid_vertex_count *= 2; + } + } +} + +template +inline void Delaunay::writeToVTKFile(const std::string& filename) +{ + const auto CELL_TYPE = DIM == 2 ? mint::TRIANGLE : mint::TET; + mint::UnstructuredMesh mint_mesh(DIM, CELL_TYPE); + + this->compactMesh(); + + for(auto v : m_mesh.vertices().positions()) + { + mint_mesh.appendNodes(m_mesh.getVertexPosition(v).data(), 1); + } + + for(auto e : m_mesh.elements().positions()) + { + mint_mesh.appendCell(&(m_mesh.boundaryVertices(e)[0]), CELL_TYPE); + } + + mint::write_vtk(&mint_mesh, filename); +} + +template +inline void Delaunay::removeBoundary() +{ + if(m_has_boundary) + { + const int num_boundary_pts = 1 << DIM; + + for(auto e : m_mesh.elements().positions()) + { + if(m_mesh.isValidElement(e)) + { + const auto verts = m_mesh.boundaryVertices(e); + bool touches_boundary = false; + for(int i = 0; i < VERT_PER_ELEMENT; ++i) + { + touches_boundary |= (verts[i] >= 0 && verts[i] < num_boundary_pts); + } + + if(touches_boundary) + { + m_mesh.removeElement(e); + } + } + } + + for(int v = 0; v < num_boundary_pts; ++v) + { + m_mesh.removeVertex(v); + } + + for(auto e : m_mesh.elements().positions()) + { + if(!m_mesh.isValidElement(e)) + { + continue; + } + + const auto verts = m_mesh.boundaryVertices(e); + bool has_invalid_vertex = false; + for(int i = 0; i < VERT_PER_ELEMENT; ++i) + { + has_invalid_vertex |= !m_mesh.isValidVertex(verts[i]); + } + + if(has_invalid_vertex) + { + m_mesh.removeElement(e); + } + } + + this->compactMesh(); + m_has_boundary = false; + } +} + +template +inline bool Delaunay::isValid(bool verboseOutput) const +{ + using ImplicitGridType = spin::ImplicitGrid; + using UniformGridType = spin::UniformGrid; + using NumericArrayType = NumericArray; + using axom::numerics::dot_product; + + bool valid = true; + + std::vector> invalidEntries; + std::vector invalidElements; + + const IndexType totalVertices = m_mesh.vertices().size(); + const IndexType totalElements = m_mesh.elements().size(); + const IndexType res = axom::utilities::ceil(0.33 * std::pow(totalVertices, 1. / DIM)); + UniformGridType grid(m_bounding_box, NumericArray(res).data()); + + axom::Array circumspheres(totalElements); + + auto vertexInsideCircumsphere = [&](const PointType& vertex, IndexType element_idx) { + return isPointInSphereOnMesh(m_mesh, vertex, element_idx, /*includeBoundary=*/false); + }; + + auto circumsphereSignedDistanceTol = [](const typename ElementType::SphereType& sphere, + const PointType& x) { + const auto& center = sphere.getCenter(); + double scale = axom::utilities::max(1., sphere.getRadius()); + for(int dim = 0; dim < DIM; ++dim) + { + scale = axom::utilities::max(scale, axom::utilities::abs(center[dim])); + scale = axom::utilities::max(scale, axom::utilities::abs(x[dim])); + } + + return 256. * std::numeric_limits::epsilon() * scale; + }; + + { + using GridCell = typename ImplicitGridType::GridCell; + + const auto resCell = GridCell(res); + ImplicitGridType implicitGrid(m_bounding_box, &resCell, totalElements); + for(auto element_idx : m_mesh.elements().positions()) + { + if(m_mesh.isValidElement(element_idx)) + { + const auto verts = m_mesh.boundaryVertices(element_idx); + bool has_all_vertices = true; + for(int i = 0; i < VERT_PER_ELEMENT; ++i) + { + has_all_vertices &= m_mesh.isValidVertex(verts[i]); + } + + if(!has_all_vertices) + { + valid = false; + if(verboseOutput) + { + invalidElements.push_back(element_idx); + } + continue; + } + + circumspheres[element_idx] = this->getElement(element_idx).circumsphere(); + const auto& sphere = circumspheres[element_idx]; + const auto& center = sphere.getCenter().array(); + const auto offset = NumericArrayType(sphere.getRadius()); + + BoundingBox bb; + bb.addPoint(PointType(center - offset)); + bb.addPoint(PointType(center + offset)); + + implicitGrid.insert(bb, element_idx); + } + } + + const int kUpper = (DIM == 2) ? 0 : res; + const IndexType stride[3] = {1, res, (DIM == 2) ? 0 : res * res}; + for(IndexType k = 0; k < kUpper; ++k) + { + for(IndexType j = 0; j < res; ++j) + { + for(IndexType i = 0; i < res; ++i) + { + const IndexType vals[3] = {i, j, k}; + const GridCell cell(vals); + const auto idx = dot_product(cell.data(), stride, DIM); + const auto binValues = implicitGrid.getCandidatesAsArray(cell); + grid.getBinContents(idx).insert(0, binValues.size(), binValues.data()); + } + } + } + } + + for(auto vertex_idx : m_mesh.vertices().positions()) + { + if(!m_mesh.isValidVertex(vertex_idx)) + { + continue; + } + + const auto& vertex = m_mesh.getVertexPosition(vertex_idx); + for(const auto element_idx : grid.getBinContents(grid.getBinIndex(vertex))) + { + if(slam::is_subset(vertex_idx, m_mesh.boundaryVertices(element_idx))) + { + continue; + } + + if(vertexInsideCircumsphere(vertex, element_idx)) + { + valid = false; + + if(verboseOutput) + { + invalidEntries.push_back(std::make_pair(vertex_idx, element_idx)); + } + } + } + } + + if(verboseOutput) + { + if(valid) + { + SLIC_INFO("Delaunay complex was valid"); + } + else + { + fmt::memory_buffer out; + if(!invalidElements.empty()) + { + fmt::format_to(std::back_inserter(out), + "\n\t{} valid elements referenced invalid vertices: {}", + invalidElements.size(), + fmt::join(invalidElements, ", ")); + } + for(const auto& pr : invalidEntries) + { + const auto vertex_idx = pr.first; + const auto element_idx = pr.second; + const auto& pos = m_mesh.getVertexPosition(vertex_idx); + const auto element = this->getElement(element_idx); + const auto circumsphere = element.circumsphere(); + const double tol = circumsphereSignedDistanceTol(circumsphere, pos); + fmt::format_to(std::back_inserter(out), + "\n\tVertex {} @ {}" + "\n\tElement {}: {} w/ circumsphere: {}" + "\n\tDistance to circumcenter: {} (tol={})", + vertex_idx, + pos, + element_idx, + element, + circumsphere, + circumsphere.computeSignedDistance(pos), + tol); + } + + SLIC_INFO( + fmt::format("Delaunay complex was NOT valid. There were {} " + "vertices in the circumsphere of an element. {}", + invalidEntries.size(), + fmt::to_string(out))); + } + } + + return valid; +} + +template +inline bool Delaunay::isConforming(bool verboseOutput) const +{ + fmt::memory_buffer out; + + bool valid = m_mesh.isConforming(verboseOutput); + + for(auto element_idx : m_mesh.elements().positions()) + { + if(!m_mesh.isValidElement(element_idx)) + { + continue; + } + + const auto orient = evaluateElementOrientationDeterminant(element_idx); + if(orient.orientation != primal::ON_POSITIVE_SIDE) + { + if(verboseOutput) + { + fmt::format_to( + std::back_inserter(out), + "\n\tElement {} has non-positive orientation determinant {:.17g} (tol={:.3g})", + element_idx, + orient.det, + orient.tol); + } + valid = false; + } + + if(m_has_boundary) + { + const auto neighbors = m_mesh.adjacentElements(element_idx); + for(int facet_idx = 0; facet_idx < VERT_PER_ELEMENT; ++facet_idx) + { + if(m_mesh.isValidElement(neighbors[facet_idx])) + { + continue; + } + + const FacetKey facet_key = m_mesh.getSortedFacetKey(element_idx, facet_idx); + if(!isFacetOnBoundingBox(facet_key)) + { + if(verboseOutput) + { + fmt::format_to(std::back_inserter(out), + "\n\tBoundary facet {} is not on the initial bounding box", + facetKeyString(facet_key)); + } + valid = false; + } + } + } + } + + if(verboseOutput) + { + if(valid) + { + SLIC_INFO("Delaunay mesh was conforming"); + } + else + { + SLIC_INFO("Delaunay mesh was NOT conforming. Summary: " << fmt::to_string(out)); + } + } + + return valid; +} + +template +inline void Delaunay::initializeBoundary(const BoundingBox& bb) +{ + std::vector points; + IndexArray elem; + + generateInitialMesh(points, elem, bb); + + m_mesh = IAMeshType(points, elem); + m_element_finder.recomputeGrid(m_mesh, bb); + m_next_regrid_vertex_count = 1024; + m_walk_visited = slam::BitSet(static_cast(m_mesh.elements().size())); + m_total_removed_elements = 0; + m_max_removed_elements = 0; + m_num_insertions = 0; + m_num_walk_calls = 0; + m_num_walk_found = 0; + m_num_walk_outside = 0; + m_num_walk_failed = 0; + m_total_walk_steps = 0; + m_max_walk_steps = 0; + m_num_linear_fallbacks = 0; + m_num_empty_seed_fallbacks = 0; + + m_candidate_elements_scratch.clear(); + m_candidate_elements_scratch.reserve(QUERY_CANDIDATE_LIMIT); + m_walked_elements_scratch.clear(); + m_walked_elements_scratch.reserve(256); + m_walk_local_elements_scratch.clear(); + m_walk_local_elements_scratch.reserve(256); + m_initial_vertices_scratch.clear(); + m_initial_vertices_scratch.reserve(8); + m_fallback_vertices_scratch.clear(); + m_fallback_vertices_scratch.reserve(QUERY_CANDIDATE_LIMIT); + m_deleted_elements.clear(); + m_deleted_elements.reserve(DIM == 2 ? 128 : 512); + + if(!m_insertion_helper) + { + m_insertion_helper = std::make_unique(m_mesh); + } + + m_bounding_box = bb; + m_has_boundary = true; +} + +template +inline void Delaunay::insertPoint(const PointType& new_pt) +{ + SLIC_ASSERT_MSG(m_has_boundary, "Error: Need a predefined boundary box prior to adding points."); + SLIC_ASSERT_MSG(m_insertion_helper != nullptr, + "Error: Insertion helper was not initialized. " + "Delaunay::initializeBoundary() needs to be called first."); + SLIC_ASSERT_MSG(m_bounding_box.contains(new_pt), + "Error: new point is outside of the boundary box."); + + IndexType element_i = findContainingElement(new_pt); + + if(element_i == INVALID_INDEX) + { + SLIC_WARNING( + fmt::format("Could not insert point {} into Delaunay triangulation: " + "Element containing that point was not found", + new_pt)); + return; + } + + const BaryCoordType bary_coord = getBaryCoords(element_i, new_pt); + const IndexArray seed_elements = getSeedElements(element_i, bary_coord); + validateInsertionSeed(element_i, new_pt, bary_coord, seed_elements); + + auto& insertionHelper = *m_insertion_helper; + insertionHelper.reset(); + insertionHelper.containing_element = element_i; + insertionHelper.containing_bary = bary_coord; + insertionHelper.seed_elements_debug = seed_elements; + insertionHelper.findCavityElements( + new_pt, + seed_elements, + [&](const PointType& query_pt, IndexType element_idx) { + return isPointInSphereOnMesh(m_mesh, query_pt, element_idx, /*includeBoundary=*/true); + }); + ++m_num_insertions; + m_total_removed_elements += static_cast(insertionHelper.numRemovedElements()); + m_max_removed_elements = + axom::utilities::max(m_max_removed_elements, + static_cast(insertionHelper.numRemovedElements())); + validateCavityBoundary(insertionHelper); + insertionHelper.createCavity(m_deleted_elements); + IndexType new_pt_i = m_mesh.addVertex(new_pt); + insertionHelper.delaunayBall(new_pt_i, m_deleted_elements); + validateInsertedBall(new_pt_i, insertionHelper); + validateInsertionResult(); + + m_element_finder.updateBin(new_pt, new_pt_i); + if(m_next_regrid_vertex_count > 0 && m_mesh.vertices().size() >= m_next_regrid_vertex_count) + { + m_element_finder.recomputeGrid(m_mesh, m_bounding_box); + while(m_next_regrid_vertex_count <= m_mesh.vertices().size()) + { + m_next_regrid_vertex_count *= 2; + } + } + + if(shouldCompactMesh()) + { + this->compactMesh(); + } +} + +template +inline void Delaunay::validateCavityBoundary(const InsertionHelper& insertion_helper) const +{ + if(m_insertion_validation_mode == InsertionValidationMode::None) + { + return; + } + + struct FacetInfo + { + IndexType neighbor_idx {INVALID_INDEX}; + bool matched {false}; + }; + + fmt::memory_buffer out; + bool valid = true; + std::map cavity_boundary; + + for(const auto& facet : insertion_helper.boundary_facets) + { + const FacetKey facet_key = makeSortedFaceKey(facet.vertices); + const auto insert_status = cavity_boundary.insert({facet_key, {facet.neighbor, false}}); + if(!insert_status.second) + { + fmt::format_to(std::back_inserter(out), + "\n\tCavity boundary facet {} was recorded more than once", + facetKeyString(facet_key)); + valid = false; + } + } + + for(const IndexType cavity_element : insertion_helper.cavity_elems) + { + const auto neighbors = m_mesh.adjacentElements(cavity_element); + for(int facet_idx = 0; facet_idx < VERT_PER_ELEMENT; ++facet_idx) + { + const IndexType neighbor_idx = neighbors[facet_idx]; + if(m_mesh.isValidElement(neighbor_idx) && insertion_helper.containsCavityElement(neighbor_idx)) + { + continue; + } + + const FacetKey facet_key = m_mesh.getSortedFacetKey(cavity_element, facet_idx); + auto facet_it = cavity_boundary.find(facet_key); + if(facet_it == cavity_boundary.end()) + { + fmt::format_to(std::back_inserter(out), + "\n\tCavity facet {} on element {} face {} is missing from the " + "boundary facet set", + facetKeyString(facet_key), + cavity_element, + facet_idx); + valid = false; + continue; + } + + if(facet_it->second.neighbor_idx != neighbor_idx) + { + fmt::format_to(std::back_inserter(out), + "\n\tCavity face {} expects neighbor {} but facet relation stores {}", + facetKeyString(facet_key), + neighbor_idx, + facet_it->second.neighbor_idx); + valid = false; + } + + if(!m_mesh.isValidElement(neighbor_idx) && m_has_boundary && !isFacetOnBoundingBox(facet_key)) + { + fmt::format_to(std::back_inserter(out), + "\n\tCavity boundary face {} exits the mesh away from the bounding box", + facetKeyString(facet_key)); + valid = false; + } + + facet_it->second.matched = true; + } + } + + for(const auto& facet_entry : cavity_boundary) + { + if(!facet_entry.second.matched) + { + fmt::format_to(std::back_inserter(out), + "\n\tFacet {} does not correspond to a cavity boundary face", + facetKeyString(facet_entry.first)); + valid = false; + } + } + + SLIC_ERROR_IF(!valid, + "Delaunay cavity validation failed after insertion " << m_num_insertions << ":" + << fmt::to_string(out)); +} + +template +inline void Delaunay::validateInsertedBall(IndexType new_pt_i, + const InsertionHelper& insertion_helper) const +{ + if(m_insertion_validation_mode == InsertionValidationMode::None) + { + return; + } + + struct BoundaryFacetInfo + { + IndexType neighbor_idx {INVALID_INDEX}; + bool matched {false}; + }; + + fmt::memory_buffer out; + bool valid = true; + std::map cavity_boundary; + std::map> inserted_faces; + auto findOppositeVertex = [&](IndexType element_idx, const FacetKey& facet_key) { + const auto verts = m_mesh.boundaryVertices(element_idx); + for(int i = 0; i < VERT_PER_ELEMENT; ++i) + { + bool on_face = false; + for(int j = 0; j < VERTS_PER_FACET; ++j) + { + on_face |= (verts[i] == facet_key[j]); + } + if(!on_face) + { + return verts[i]; + } + } + + return INVALID_INDEX; + }; + + if(insertion_helper.inserted_elems.size() != insertion_helper.boundary_facets.size()) + { + fmt::format_to( + std::back_inserter(out), + "\n\tInserted ball element count {} does not match cavity boundary facet count {}", + insertion_helper.inserted_elems.size(), + insertion_helper.boundary_facets.size()); + valid = false; + } + + if(insertion_helper.containing_element != INVALID_INDEX) + { + fmt::format_to(std::back_inserter(out), + "\n\tContaining element {} barycentric coordinates {}", + insertion_helper.containing_element, + insertion_helper.containing_bary); + if(!insertion_helper.seed_elements_debug.empty()) + { + fmt::format_to(std::back_inserter(out), + "\n\tInsertion seeds: [{}]", + fmt::join(insertion_helper.seed_elements_debug, ", ")); + } + } + + for(const auto& facet : insertion_helper.boundary_facets) + { + cavity_boundary.insert({makeSortedFaceKey(facet.vertices), {facet.neighbor, false}}); + } + + for(const IndexType element_idx : insertion_helper.inserted_elems) + { + if(!m_mesh.isValidElement(element_idx)) + { + fmt::format_to(std::back_inserter(out), "\n\tInserted element {} is invalid", element_idx); + valid = false; + continue; + } + + const auto verts = m_mesh.boundaryVertices(element_idx); + if(!slam::is_subset(new_pt_i, verts)) + { + fmt::format_to(std::back_inserter(out), + "\n\tInserted element {} does not contain the new vertex {}", + element_idx, + new_pt_i); + valid = false; + } + + const auto orient = evaluateElementOrientationDeterminant(element_idx); + if(orient.orientation != primal::ON_POSITIVE_SIDE) + { + fmt::format_to( + std::back_inserter(out), + "\n\tInserted element {} has non-positive orientation determinant {:.17g} (tol={:.3g})", + element_idx, + orient.det, + orient.tol); + valid = false; + } + + const auto neighbors = m_mesh.adjacentElements(element_idx); + for(int facet_idx = 0; facet_idx < VERT_PER_ELEMENT; ++facet_idx) + { + inserted_faces[m_mesh.getSortedFacetKey(element_idx, facet_idx)].push_back( + {element_idx, facet_idx, neighbors[facet_idx]}); + } + } + + for(auto& face_entry : inserted_faces) + { + auto cavity_it = cavity_boundary.find(face_entry.first); + auto& records = face_entry.second; + if(cavity_it != cavity_boundary.end()) + { + if(records.size() != 1) + { + fmt::format_to(std::back_inserter(out), + "\n\tBoundary face {} of the inserted ball is used by {} new elements", + facetKeyString(face_entry.first), + records.size()); + valid = false; + continue; + } + + if(records.front().neighbor_idx != cavity_it->second.neighbor_idx) + { + fmt::format_to(std::back_inserter(out), + "\n\tInserted boundary face {} points to neighbor {} instead of {}", + facetKeyString(face_entry.first), + records.front().neighbor_idx, + cavity_it->second.neighbor_idx); + valid = false; + } + + if(m_mesh.isValidElement(cavity_it->second.neighbor_idx)) + { + const IndexType inserted_opposite = + findOppositeVertex(records.front().element_idx, face_entry.first); + const IndexType neighbor_opposite = + findOppositeVertex(cavity_it->second.neighbor_idx, face_entry.first); + + if(inserted_opposite == INVALID_INDEX || neighbor_opposite == INVALID_INDEX) + { + fmt::format_to( + std::back_inserter(out), + "\n\tCould not identify opposite vertices across inserted boundary face {}", + facetKeyString(face_entry.first)); + valid = false; + } + else + { + const PointType& inserted_point = m_mesh.getVertexPosition(inserted_opposite); + const PointType& neighbor_point = m_mesh.getVertexPosition(neighbor_opposite); + + if(isPointInSphereOnMesh(m_mesh, + inserted_point, + cavity_it->second.neighbor_idx, + /*includeBoundary=*/false)) + { + const int query_in_neighbor = + inSphereOrientationOnMesh(m_mesh, + m_mesh.getVertexPosition(new_pt_i), + cavity_it->second.neighbor_idx); + const int inserted_in_neighbor = + inSphereOrientationOnMesh(m_mesh, inserted_point, cavity_it->second.neighbor_idx); + fmt::format_to( + std::back_inserter(out), + "\n\tInserted boundary face {} leaves new vertex {} inside neighbor {} " + "circumsphere (query {}={}, det={:.17g}; opposite {}={}, det={:.17g})", + facetKeyString(face_entry.first), + inserted_opposite, + cavity_it->second.neighbor_idx, + new_pt_i, + orientationResultName(query_in_neighbor), + inSphereDeterminantOnMesh(m_mesh, + m_mesh.getVertexPosition(new_pt_i), + cavity_it->second.neighbor_idx), + inserted_opposite, + orientationResultName(inserted_in_neighbor), + inSphereDeterminantOnMesh(m_mesh, inserted_point, cavity_it->second.neighbor_idx)); + valid = false; + } + + if(isPointInSphereOnMesh(m_mesh, + neighbor_point, + records.front().element_idx, + /*includeBoundary=*/false)) + { + const int query_in_neighbor = + inSphereOrientationOnMesh(m_mesh, + m_mesh.getVertexPosition(new_pt_i), + cavity_it->second.neighbor_idx); + const int neighbor_in_inserted = + inSphereOrientationOnMesh(m_mesh, neighbor_point, records.front().element_idx); + fmt::format_to( + std::back_inserter(out), + "\n\tInserted boundary face {} leaves neighbor vertex {} inside new element {} " + "circumsphere (query {} in neighbor {} is {}, det={:.17g}; opposite {} in new " + "element is {}, det={:.17g})", + facetKeyString(face_entry.first), + neighbor_opposite, + records.front().element_idx, + new_pt_i, + cavity_it->second.neighbor_idx, + orientationResultName(query_in_neighbor), + inSphereDeterminantOnMesh(m_mesh, + m_mesh.getVertexPosition(new_pt_i), + cavity_it->second.neighbor_idx), + neighbor_opposite, + orientationResultName(neighbor_in_inserted), + inSphereDeterminantOnMesh(m_mesh, neighbor_point, records.front().element_idx)); + valid = false; + } + } + } + + cavity_it->second.matched = true; + } + else if(records.size() != 2 || records[0].neighbor_idx != records[1].element_idx || + records[1].neighbor_idx != records[0].element_idx) + { + fmt::format_to(std::back_inserter(out), + "\n\tInternal face {} of the inserted ball has inconsistent adjacency", + facetKeyString(face_entry.first)); + valid = false; + } + else + { + const IndexType lhs_opposite = findOppositeVertex(records[0].element_idx, face_entry.first); + const IndexType rhs_opposite = findOppositeVertex(records[1].element_idx, face_entry.first); + + if(lhs_opposite == INVALID_INDEX || rhs_opposite == INVALID_INDEX) + { + fmt::format_to(std::back_inserter(out), + "\n\tCould not identify opposite vertices across inserted internal face {}", + facetKeyString(face_entry.first)); + valid = false; + } + else + { + const PointType& lhs_point = m_mesh.getVertexPosition(lhs_opposite); + const PointType& rhs_point = m_mesh.getVertexPosition(rhs_opposite); + + if(isPointInSphereOnMesh(m_mesh, lhs_point, records[1].element_idx, /*includeBoundary=*/false)) + { + const int query_in_rhs = inSphereOrientationOnMesh(m_mesh, + m_mesh.getVertexPosition(new_pt_i), + records[1].element_idx); + const int lhs_in_rhs = inSphereOrientationOnMesh(m_mesh, lhs_point, records[1].element_idx); + fmt::format_to( + std::back_inserter(out), + "\n\tInserted internal face {} leaves vertex {} inside adjacent element {} " + "circumsphere (query {}={}, det={:.17g}; opposite {}={}, det={:.17g})", + facetKeyString(face_entry.first), + lhs_opposite, + records[1].element_idx, + new_pt_i, + orientationResultName(query_in_rhs), + inSphereDeterminantOnMesh(m_mesh, + m_mesh.getVertexPosition(new_pt_i), + records[1].element_idx), + lhs_opposite, + orientationResultName(lhs_in_rhs), + inSphereDeterminantOnMesh(m_mesh, lhs_point, records[1].element_idx)); + valid = false; + } + + if(isPointInSphereOnMesh(m_mesh, rhs_point, records[0].element_idx, /*includeBoundary=*/false)) + { + const int query_in_lhs = inSphereOrientationOnMesh(m_mesh, + m_mesh.getVertexPosition(new_pt_i), + records[0].element_idx); + const int rhs_in_lhs = inSphereOrientationOnMesh(m_mesh, rhs_point, records[0].element_idx); + fmt::format_to( + std::back_inserter(out), + "\n\tInserted internal face {} leaves vertex {} inside adjacent element {} " + "circumsphere (query {}={}, det={:.17g}; opposite {}={}, det={:.17g})", + facetKeyString(face_entry.first), + rhs_opposite, + records[0].element_idx, + new_pt_i, + orientationResultName(query_in_lhs), + inSphereDeterminantOnMesh(m_mesh, + m_mesh.getVertexPosition(new_pt_i), + records[0].element_idx), + rhs_opposite, + orientationResultName(rhs_in_lhs), + inSphereDeterminantOnMesh(m_mesh, rhs_point, records[0].element_idx)); + valid = false; + } + } + } + } + + for(const auto& facet_entry : cavity_boundary) + { + if(!facet_entry.second.matched) + { + fmt::format_to(std::back_inserter(out), + "\n\tCavity boundary face {} was not covered by the inserted ball", + facetKeyString(facet_entry.first)); + valid = false; + } + } + + SLIC_ERROR_IF(!valid, + "Delaunay ball validation failed after insertion " << m_num_insertions << ":" + << fmt::to_string(out)); +} + +template <> +inline void Delaunay<2>::generateInitialMesh(std::vector& points, + std::vector& elem, + const BoundingBox& bb) +{ + const PointType& mins = bb.getMin(); + const PointType& maxs = bb.getMax(); + + std::vector pt {mins[0], mins[1], mins[0], maxs[1], maxs[0], mins[1], maxs[0], maxs[1]}; + + std::vector el {0, 2, 1, 3, 1, 2}; + + points.swap(pt); + elem.swap(el); +} + +template <> +inline void Delaunay<3>::generateInitialMesh(std::vector& points, + std::vector& elem, + const BoundingBox& bb) +{ + const PointType& mins = bb.getMin(); + const PointType& maxs = bb.getMax(); + + std::vector pt {mins[0], mins[1], mins[2], mins[0], mins[1], maxs[2], mins[0], maxs[1], + mins[2], mins[0], maxs[1], maxs[2], maxs[0], mins[1], mins[2], maxs[0], + mins[1], maxs[2], maxs[0], maxs[1], mins[2], maxs[0], maxs[1], maxs[2]}; + + std::vector el {3, 2, 4, 0, 3, 4, 1, 0, 3, 2, 6, 4, + 3, 6, 7, 4, 3, 5, 1, 4, 3, 7, 5, 4}; + + points.swap(pt); + elem.swap(el); +} + +} // namespace quest +} // namespace axom + +#endif diff --git a/src/axom/quest/detail/DelaunayInsertionHelper.hpp b/src/axom/quest/detail/DelaunayInsertionHelper.hpp new file mode 100644 index 0000000000..0fb0f37d37 --- /dev/null +++ b/src/axom/quest/detail/DelaunayInsertionHelper.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_DETAIL_DELAUNAY_INSERTION_HELPER_HPP_ +#define AXOM_QUEST_DETAIL_DELAUNAY_INSERTION_HELPER_HPP_ + +#include "axom/core.hpp" +#include "axom/primal.hpp" +#include "axom/slam.hpp" + +#include +#include + +namespace axom +{ +namespace quest +{ +namespace detail +{ + +template +class DelaunayInsertionHelper +{ +public: + static constexpr int VERT_PER_ELEMENT = DIM + 1; + static constexpr int VERTS_PER_FACET = VERT_PER_ELEMENT - 1; + static constexpr IndexType INVALID_INDEX = IndexType {-1}; + + struct BoundaryFacet + { + std::array vertices {}; + IndexType neighbor {INVALID_INDEX}; + }; + + explicit DelaunayInsertionHelper(IAMeshType& mesh) : m_mesh(mesh) { } + + void reset() + { + for(const IndexType element_idx : cavity_elems) + { + if(element_idx >= 0 && static_cast(element_idx) < m_cavity_membership.size()) + { + m_cavity_membership[static_cast(element_idx)] = 0; + } + } + + boundary_facets.clear(); + cavity_elems.clear(); + inserted_elems.clear(); + containing_element = INVALID_INDEX; + seed_elements_debug.clear(); + m_stack.clear(); + } + + template + void findCavityElements(const PointType& query_pt, + const IndexArray& seed_elements, + CircumspherePredicate&& isPointInCircumsphere) + { + constexpr int reserveSize = (DIM == 2) ? 16 : 64; + ensureCavityMembershipCapacity(); + + if(m_stack.capacity() < reserveSize) + { + m_stack.reserve(reserveSize); + } + if(cavity_elems.capacity() < reserveSize) + { + cavity_elems.reserve(reserveSize); + } + if(boundary_facets.capacity() < reserveSize) + { + boundary_facets.reserve(reserveSize); + } + if(inserted_elems.capacity() < reserveSize) + { + inserted_elems.reserve(reserveSize); + } + constexpr IndexType invalid_element = IAMeshType::INVALID_ELEMENT_INDEX; + + for(const IndexType element_i : seed_elements) + { + SLIC_ASSERT(m_mesh.isValidElement(element_i)); + addCavityElement(element_i); + } + + while(!m_stack.empty()) + { + const IndexType element_idx = m_stack.back(); + m_stack.pop_back(); + + const auto neighbors = m_mesh.adjacentElements(element_idx); + for(auto n_idx : neighbors.positions()) + { + const IndexType nbr = neighbors[n_idx]; + + if(nbr != invalid_element) + { + SLIC_ASSERT(m_mesh.isValidElement(nbr)); + + if(containsCavityElement(nbr)) + { + continue; + } + + if(isPointInCircumsphere(query_pt, nbr)) + { + addCavityElement(nbr); + continue; + } + } + + const auto bdry = m_mesh.boundaryVertices(element_idx); + + typename IAMeshType::ModularVertexIndex mod_idx(n_idx); + BoundaryFacet facet; + for(int i = 0; i < VERTS_PER_FACET; i++) + { + facet.vertices[i] = bdry[mod_idx++]; + } + if(DIM == 3 && n_idx % 2 == 1) + { + axom::utilities::swap(facet.vertices[1], facet.vertices[2]); + } + + facet.neighbor = nbr; + boundary_facets.push_back(facet); + } + } + + SLIC_ASSERT_MSG(!cavity_elems.empty(), "Error: New point is not contained in the mesh"); + SLIC_ASSERT(!boundary_facets.empty()); + } + + template + void createCavity(DeletedElementPool& deleted_elements) + { + for(const auto elem : cavity_elems) + { + m_mesh.removeElement(elem); + deleted_elements.release(elem); + } + } + + template + void delaunayBall(IndexType new_pt_i, DeletedElementPool& deleted_elements) + { + const int numFaces = static_cast(boundary_facets.size()); + const IndexType invalid_neighbor = IAMeshType::ElementAdjacencyRelation::INVALID_INDEX; + const PointType& new_pt = m_mesh.getVertexPosition(new_pt_i); + + IndexType vlist[VERT_PER_ELEMENT] {}; + IndexType neighbors[VERT_PER_ELEMENT] {}; + for(int i = 0; i < numFaces; ++i) + { + for(int d = 0; d < VERTS_PER_FACET; ++d) + { + vlist[d] = boundary_facets[static_cast(i)].vertices[d]; + } + vlist[VERTS_PER_FACET] = new_pt_i; + + if constexpr(DIM == 2) + { + const PointType& p0 = m_mesh.getVertexPosition(vlist[0]); + const PointType& p1 = m_mesh.getVertexPosition(vlist[1]); + if(primal::orientation_determinant(p0, p1, new_pt) < 0.) + { + axom::utilities::swap(vlist[0], vlist[1]); + } + } + else + { + const PointType& p0 = m_mesh.getVertexPosition(vlist[0]); + const PointType& p1 = m_mesh.getVertexPosition(vlist[1]); + const PointType& p2 = m_mesh.getVertexPosition(vlist[2]); + if(primal::orientation_determinant(p0, p1, p2, new_pt) < 0.) + { + axom::utilities::swap(vlist[1], vlist[2]); + } + } + + const auto nID = boundary_facets[static_cast(i)].neighbor; + for(int d = 0; d < VERT_PER_ELEMENT; ++d) + { + neighbors[d] = invalid_neighbor; + } + neighbors[0] = nID; + + IndexType new_el = invalid_neighbor; + if(!deleted_elements.empty()) + { + new_el = deleted_elements.acquire(); + m_mesh.reuseElement(new_el, vlist, neighbors); + } + else + { + new_el = m_mesh.addElement(vlist, neighbors); + } + inserted_elems.push_back(new_el); + } + + m_mesh.fixVertexNeighborhood(new_pt_i, inserted_elems); + } + + int numRemovedElements() const { return static_cast(cavity_elems.size()); } + + bool containsCavityElement(IndexType element_idx) const + { + return element_idx >= 0 && static_cast(element_idx) < m_cavity_membership.size() && + m_cavity_membership[static_cast(element_idx)] != 0; + } + +public: + IAMeshType& m_mesh; + + std::vector boundary_facets; + std::vector cavity_elems; + std::vector inserted_elems; + IndexType containing_element {INVALID_INDEX}; + BaryCoordType containing_bary; + IndexArray seed_elements_debug; + + IndexArray m_stack; + +private: + void ensureCavityMembershipCapacity() + { + const std::size_t required_size = static_cast(m_mesh.elements().size()); + if(m_cavity_membership.size() < required_size) + { + m_cavity_membership.resize(required_size, 0); + } + } + + void addCavityElement(IndexType element_idx) + { + ensureCavityMembershipCapacity(); + if(containsCavityElement(element_idx)) + { + return; + } + + m_cavity_membership[static_cast(element_idx)] = 1; + cavity_elems.push_back(element_idx); + m_stack.push_back(element_idx); + } + + std::vector m_cavity_membership; +}; + +} // namespace detail +} // namespace quest +} // namespace axom + +#endif diff --git a/src/axom/quest/detail/DelaunayPointLocation.hpp b/src/axom/quest/detail/DelaunayPointLocation.hpp new file mode 100644 index 0000000000..9c1b44e588 --- /dev/null +++ b/src/axom/quest/detail/DelaunayPointLocation.hpp @@ -0,0 +1,554 @@ +// 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_DETAIL_DELAUNAY_POINT_LOCATION_HPP_ +#define AXOM_QUEST_DETAIL_DELAUNAY_POINT_LOCATION_HPP_ + +namespace axom +{ +namespace quest +{ + +template +inline typename Delaunay::BaryCoordType Delaunay::getBaryCoords(IndexType element_idx, + const PointType& query_pt) const +{ + const auto verts = m_mesh.boundaryVertices(element_idx); + + if constexpr(DIM == 2) + { + const ElementType tri(m_mesh.getVertexPosition(verts[0]), + m_mesh.getVertexPosition(verts[1]), + m_mesh.getVertexPosition(verts[2])); + + return tri.physToBarycentric(query_pt); + } + else + { + const ElementType tet(m_mesh.getVertexPosition(verts[0]), + m_mesh.getVertexPosition(verts[1]), + m_mesh.getVertexPosition(verts[2]), + m_mesh.getVertexPosition(verts[3])); + + return tet.physToBarycentric(query_pt); + } +} + +template +inline typename Delaunay::BaryCoordType Delaunay::getRawBarycentricDeterminants( + IndexType element_idx, + const PointType& query_pt) const +{ + const auto verts = m_mesh.boundaryVertices(element_idx); + + if constexpr(DIM == 2) + { + const ElementType tri(m_mesh.getVertexPosition(verts[0]), + m_mesh.getVertexPosition(verts[1]), + m_mesh.getVertexPosition(verts[2])); + return tri.physToBarycentric(query_pt, /*skipNormalization=*/true); + } + else + { + const ElementType tet(m_mesh.getVertexPosition(verts[0]), + m_mesh.getVertexPosition(verts[1]), + m_mesh.getVertexPosition(verts[2]), + m_mesh.getVertexPosition(verts[3])); + return tet.physToBarycentric(query_pt, /*skipNormalization=*/true); + } +} + +template +inline double Delaunay::rawBarycentricDeterminantTolerance(IndexType element_idx, + const PointType& query_pt) const +{ + const auto verts = m_mesh.boundaryVertices(element_idx); + + double scale = 1.; + for(int i = 0; i < VERT_PER_ELEMENT; ++i) + { + const auto diff = m_mesh.getVertexPosition(verts[i]) - query_pt; + scale = axom::utilities::max(scale, diff.norm()); + } + + const double k = 64.; + if constexpr(DIM == 2) + { + return k * std::numeric_limits::epsilon() * scale * scale; + } + else + { + return k * std::numeric_limits::epsilon() * scale * scale * scale; + } +} + +template +inline bool Delaunay::isPointInsideForLocation(IndexType element_idx, + const PointType& query_pt, + const BaryCoordType& bary_coord, + ModularFaceIndex* exit_face) const +{ + ModularFaceIndex min_face(bary_coord.array().argMin()); + + for(int i = 0; i < VERT_PER_ELEMENT; ++i) + { + if(bary_coord[i] < -BARY_EPS) + { + if(exit_face != nullptr) + { + *exit_face = min_face; + } + return false; + } + } + + int first_determinant_negative = -1; + if constexpr(DIM == 3) + { + bool has_near_zero = false; + for(int i = 0; i < VERT_PER_ELEMENT; ++i) + { + has_near_zero |= axom::utilities::abs(bary_coord[i]) <= BARY_EPS; + } + + if(has_near_zero) + { + const BaryCoordType raw = getRawBarycentricDeterminants(element_idx, query_pt); + const double tol = rawBarycentricDeterminantTolerance(element_idx, query_pt); + for(int i = 0; i < VERT_PER_ELEMENT; ++i) + { + if(axom::utilities::abs(bary_coord[i]) <= BARY_EPS && signWithTolerance(raw[i], tol) < 0) + { + first_determinant_negative = i; + break; + } + } + } + } + + if(first_determinant_negative >= 0) + { + if(exit_face != nullptr) + { + *exit_face = ModularFaceIndex(first_determinant_negative); + } + return false; + } + + return true; +} + +template +inline typename Delaunay::PointLocationResult Delaunay::walkToContainingElement( + const PointType& query_pt, + IndexType start_element, + std::vector* visited_elements_out) const +{ + constexpr IndexType invalid_element = IAMeshType::INVALID_ELEMENT_INDEX; + + if(!m_mesh.isValidElement(start_element)) + { + return {}; + } + + static constexpr int MAX_WALK_STEPS = 256; + std::vector& visited_elements = + visited_elements_out != nullptr ? *visited_elements_out : m_walk_local_elements_scratch; + visited_elements.clear(); + if(static_cast(visited_elements.capacity()) < MAX_WALK_STEPS) + { + visited_elements.reserve(MAX_WALK_STEPS); + } + IndexType element_i = start_element; + + if(m_walk_visited.size() < static_cast(m_mesh.elements().size())) + { + m_walk_visited = slam::BitSet(static_cast(m_mesh.elements().size())); + } + + auto clearVisitedBits = [&]() { + for(const IndexType visited : visited_elements) + { + m_walk_visited.clear(static_cast(visited)); + } + }; + + int step_count = 0; + + auto recordWalk = [&](PointLocationStatus status) { + if(!m_collect_location_stats) + { + return; + } + + ++m_num_walk_calls; + m_total_walk_steps += static_cast(step_count); + m_max_walk_steps = axom::utilities::max(m_max_walk_steps, static_cast(step_count)); + switch(status) + { + case PointLocationStatus::Found: + ++m_num_walk_found; + break; + case PointLocationStatus::Outside: + ++m_num_walk_outside; + break; + default: + ++m_num_walk_failed; + break; + } + }; + + while(1) + { + ++step_count; + if(m_walk_visited.test(static_cast(element_i))) + { + recordWalk(PointLocationStatus::Failed); + clearVisitedBits(); + return {}; + } + m_walk_visited.set(static_cast(element_i)); + visited_elements.push_back(element_i); + + const BaryCoordType bary_coord = getBaryCoords(element_i, query_pt); + ModularFaceIndex modular_idx(0); + if(isPointInsideForLocation(element_i, query_pt, bary_coord, &modular_idx)) + { + recordWalk(PointLocationStatus::Found); + clearVisitedBits(); + return {element_i, PointLocationStatus::Found}; + } + + if(static_cast(visited_elements.size()) >= MAX_WALK_STEPS) + { + recordWalk(PointLocationStatus::Failed); + clearVisitedBits(); + return {}; + } + + const IndexType next_element = m_mesh.adjacentElements(element_i)[modular_idx + 1]; + if(next_element == invalid_element) + { + recordWalk(PointLocationStatus::Outside); + clearVisitedBits(); + return {INVALID_INDEX, PointLocationStatus::Outside}; + } + + SLIC_ASSERT(m_mesh.isValidElement(next_element)); + element_i = next_element; + } +} + +template +inline void Delaunay::appendCandidateElement(std::vector& candidate_elements, + IndexType vertex_i) const +{ + const IndexType element_i = m_mesh.coboundaryElement(vertex_i); + if(isSearchableElement(element_i) && + std::find(candidate_elements.begin(), candidate_elements.end(), element_i) == + candidate_elements.end()) + { + candidate_elements.push_back(element_i); + } +} + +template +inline void Delaunay::appendCandidateElementsFromVertices( + std::vector& candidate_elements, + const std::vector& candidate_vertices) const +{ + for(const auto vertex_i : candidate_vertices) + { + appendCandidateElement(candidate_elements, vertex_i); + } +} + +template +inline void Delaunay::getInitialCandidateElements(const PointType& query_pt, + std::vector& candidate_elements) const +{ + candidate_elements.clear(); + candidate_elements.reserve(16); + + m_initial_vertices_scratch.clear(); + m_element_finder.getNearbyVertices(m_mesh, + query_pt, + m_initial_vertices_scratch, + /*search_radius=*/1, + /*max_candidates=*/8); + appendCandidateElementsFromVertices(candidate_elements, m_initial_vertices_scratch); + + if(candidate_elements.empty()) + { + if(m_collect_location_stats) + { + ++m_num_empty_seed_fallbacks; + } + for(auto elem : m_mesh.elements().positions()) + { + if(isSearchableElement(elem)) + { + candidate_elements.push_back(elem); + break; + } + } + } +} + +template +inline typename Delaunay::PointLocationResult Delaunay::walkCandidateElements( + const PointType& query_pt, + const std::vector& candidate_elements, + std::size_t start_idx, + std::vector* walked_elements) const +{ + for(std::size_t idx = start_idx; idx < candidate_elements.size(); ++idx) + { + std::vector* visited_elements = + (walked_elements != nullptr && idx == start_idx) ? walked_elements : nullptr; + PointLocationResult walk_result = + walkToContainingElement(query_pt, candidate_elements[idx], visited_elements); + if(walk_result.status != PointLocationStatus::Failed) + { + return walk_result; + } + } + + return {}; +} + +template +inline typename Delaunay::PointLocationResult Delaunay::findContainingElementWithQueryFallbacks( + const PointType& query_pt, + std::vector& candidate_elements, + const std::vector& walked_elements) const +{ + const IndexType walk_region_elem = findContainingElementFromNeighbors(query_pt, walked_elements); + if(walk_region_elem != INVALID_INDEX) + { + return {walk_region_elem, PointLocationStatus::Found}; + } + + m_fallback_vertices_scratch.clear(); + m_element_finder.getNearbyVertices(m_mesh, + query_pt, + m_fallback_vertices_scratch, + QUERY_SEARCH_RADIUS, + QUERY_CANDIDATE_LIMIT); + const std::size_t initial_candidate_count = candidate_elements.size(); + candidate_elements.reserve(candidate_elements.size() + m_fallback_vertices_scratch.size()); + appendCandidateElementsFromVertices(candidate_elements, m_fallback_vertices_scratch); + + PointLocationResult walk_result = + walkCandidateElements(query_pt, candidate_elements, initial_candidate_count); + if(walk_result.status != PointLocationStatus::Failed) + { + return walk_result; + } + + const IndexType nearby_elem = findContainingElementNearby(query_pt, m_fallback_vertices_scratch); + if(nearby_elem != INVALID_INDEX) + { + return {nearby_elem, PointLocationStatus::Found}; + } + + return {}; +} + +template +inline typename Delaunay::IndexType Delaunay::findContainingElementFromNeighbors( + const PointType& query_pt, + const std::vector& seed_elements) const +{ + if(seed_elements.empty()) + { + return INVALID_INDEX; + } + + std::vector nearby_elements; + nearby_elements.reserve(seed_elements.size() * (1 + WALK_NEIGHBORHOOD_LAYERS * VERT_PER_ELEMENT)); + + auto appendUniqueElement = [&](IndexType element_idx, std::vector& frontier) { + if(isSearchableElement(element_idx) && + std::find(nearby_elements.begin(), nearby_elements.end(), element_idx) == nearby_elements.end()) + { + nearby_elements.push_back(element_idx); + frontier.push_back(element_idx); + } + }; + + std::vector frontier; + frontier.reserve(seed_elements.size()); + for(const IndexType element_idx : seed_elements) + { + appendUniqueElement(element_idx, frontier); + } + + for(int layer = 0; layer < WALK_NEIGHBORHOOD_LAYERS && !frontier.empty(); ++layer) + { + std::vector next_frontier; + next_frontier.reserve(frontier.size() * VERT_PER_ELEMENT); + + for(const IndexType element_idx : frontier) + { + const auto neighbors = m_mesh.adjacentElements(element_idx); + for(int i = 0; i < VERT_PER_ELEMENT; ++i) + { + appendUniqueElement(neighbors[ModularFaceIndex(i) + 1], next_frontier); + } + } + + frontier.swap(next_frontier); + } + + for(const IndexType element_idx : nearby_elements) + { + const BaryCoordType bary_coord = getBaryCoords(element_idx, query_pt); + const DataType min_bary = bary_coord[bary_coord.array().argMin()]; + if(min_bary >= -BARY_EPS) + { + return element_idx; + } + } + + return INVALID_INDEX; +} + +template +inline typename Delaunay::IndexType Delaunay::findContainingElementLinear( + const PointType& query_pt, + bool warnOnInvalid) const +{ + IndexType best_element = INVALID_INDEX; + DataType best_min_bary = -std::numeric_limits::max(); + + for(auto element_idx : m_mesh.elements().positions()) + { + if(!isSearchableElement(element_idx)) + { + continue; + } + + const BaryCoordType bary_coord = getBaryCoords(element_idx, query_pt); + const DataType min_bary = bary_coord[bary_coord.array().argMin()]; + if(min_bary > best_min_bary) + { + best_min_bary = min_bary; + best_element = element_idx; + } + + if(min_bary >= -BARY_EPS) + { + return element_idx; + } + } + + SLIC_WARNING_IF(warnOnInvalid, + fmt::format("Unable to locate containing element for point {} after exhaustive " + "neighbor search; returning closest candidate with min barycentric " + "coordinate {:.17g}", + query_pt, + best_min_bary)); + + return best_element; +} + +template +inline typename Delaunay::IndexType Delaunay::findContainingElementNearby( + const PointType& query_pt, + const std::vector& nearby_vertices) const +{ + std::vector nearby_elements; + for(const IndexType vertex_idx : nearby_vertices) + { + if(!m_mesh.isValidVertex(vertex_idx)) + { + continue; + } + + const auto star = m_mesh.vertexStar(vertex_idx); + for(const IndexType elem : star) + { + if(isSearchableElement(elem)) + { + nearby_elements.push_back(elem); + } + } + } + + std::sort(nearby_elements.begin(), nearby_elements.end()); + nearby_elements.erase(std::unique(nearby_elements.begin(), nearby_elements.end()), + nearby_elements.end()); + + for(const IndexType element_idx : nearby_elements) + { + const BaryCoordType bary_coord = getBaryCoords(element_idx, query_pt); + const DataType min_bary = bary_coord[bary_coord.array().argMin()]; + if(min_bary >= -BARY_EPS) + { + return element_idx; + } + } + + return INVALID_INDEX; +} + +template +inline typename Delaunay::IndexType Delaunay::findContainingElement(const PointType& query_pt, + bool warnOnInvalid) const +{ + if(m_mesh.isEmpty()) + { + SLIC_ERROR_IF(warnOnInvalid, + "Attempting to insert point into empty Delaunay triangulation." + "Delaunay::initializeBoundary() needs to be called first"); + return INVALID_INDEX; + } + if(!m_bounding_box.contains(query_pt)) + { + SLIC_WARNING_IF(warnOnInvalid, "Attempting to locate element at location outside valid domain"); + return INVALID_INDEX; + } + + m_candidate_elements_scratch.clear(); + getInitialCandidateElements(query_pt, m_candidate_elements_scratch); + m_walked_elements_scratch.clear(); + PointLocationResult walk_result = + walkCandidateElements(query_pt, m_candidate_elements_scratch, 0, &m_walked_elements_scratch); + + if(walk_result.status == PointLocationStatus::Found) + { + return walk_result.element_idx; + } + + if(walk_result.status == PointLocationStatus::Outside) + { + return INVALID_INDEX; + } + + if(!m_candidate_elements_scratch.empty()) + { + const PointLocationResult fallback_result = + findContainingElementWithQueryFallbacks(query_pt, + m_candidate_elements_scratch, + m_walked_elements_scratch); + if(fallback_result.status == PointLocationStatus::Found) + { + return fallback_result.element_idx; + } + } + + if(m_collect_location_stats) + { + ++m_num_linear_fallbacks; + } + return findContainingElementLinear(query_pt, warnOnInvalid); +} + +} // namespace quest +} // namespace axom + +#endif From 119f321c143a00af7b6ef9b57f5699934207f631 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 15 Apr 2026 11:25:43 -0700 Subject: [PATCH 510/986] More refactoring -- outsources Delaunay validation routines --- src/axom/quest/CMakeLists.txt | 1 + src/axom/quest/Delaunay.hpp | 11 + src/axom/quest/detail/DelaunayImpl.hpp | 916 +----------------- .../quest/detail/DelaunayPointLocation.hpp | 64 +- src/axom/quest/detail/DelaunayValidation.hpp | 905 +++++++++++++++++ 5 files changed, 965 insertions(+), 932 deletions(-) create mode 100644 src/axom/quest/detail/DelaunayValidation.hpp diff --git a/src/axom/quest/CMakeLists.txt b/src/axom/quest/CMakeLists.txt index f8df13e6be..63fbeb364e 100644 --- a/src/axom/quest/CMakeLists.txt +++ b/src/axom/quest/CMakeLists.txt @@ -25,6 +25,7 @@ set( quest_headers detail/DelaunayImpl.hpp detail/DelaunayInsertionHelper.hpp detail/DelaunayPointLocation.hpp + detail/DelaunayValidation.hpp LinearizeCurves.hpp SignedDistance.hpp diff --git a/src/axom/quest/Delaunay.hpp b/src/axom/quest/Delaunay.hpp index 727c30d77a..ad0bb77152 100644 --- a/src/axom/quest/Delaunay.hpp +++ b/src/axom/quest/Delaunay.hpp @@ -36,6 +36,14 @@ #include #include +#if defined(_MSC_VER) + #define AXOM_QUEST_DELAUNAY_FORCE_INLINE __forceinline +#elif defined(__GNUC__) || defined(__clang__) + #define AXOM_QUEST_DELAUNAY_FORCE_INLINE inline __attribute__((always_inline)) +#else + #define AXOM_QUEST_DELAUNAY_FORCE_INLINE inline +#endif + namespace axom { namespace quest @@ -593,6 +601,9 @@ constexpr typename Delaunay::IndexType Delaunay::INVALID_INDEX; } // namespace axom #include "detail/DelaunayPointLocation.hpp" +#include "detail/DelaunayValidation.hpp" #include "detail/DelaunayImpl.hpp" +#undef AXOM_QUEST_DELAUNAY_FORCE_INLINE + #endif // QUEST_DELAUNAY_H_ diff --git a/src/axom/quest/detail/DelaunayImpl.hpp b/src/axom/quest/detail/DelaunayImpl.hpp index 9412a71f3c..2fe587d1cb 100644 --- a/src/axom/quest/detail/DelaunayImpl.hpp +++ b/src/axom/quest/detail/DelaunayImpl.hpp @@ -13,161 +13,7 @@ namespace quest { template -template -inline typename Delaunay::FacetKey Delaunay::makeSortedFaceKey(const FacetSubsetType& facet) -{ - FacetKey key {}; - for(int i = 0; i < VERTS_PER_FACET; ++i) - { - key[i] = facet[i]; - } - std::sort(key.begin(), key.end()); - return key; -} - -template -inline std::string Delaunay::facetKeyString(const FacetKey& facet_key) -{ - return fmt::format("[{}]", fmt::join(facet_key, ", ")); -} - -template -inline double Delaunay::getBoundaryCoordinateTolerance() const -{ - const auto min_pt = m_bounding_box.getMin(); - const auto max_pt = m_bounding_box.getMax(); - - double max_extent = 1.; - for(int dim = 0; dim < DIM; ++dim) - { - max_extent = axom::utilities::max(max_extent, max_pt[dim] - min_pt[dim]); - } - - return 64. * std::numeric_limits::epsilon() * max_extent; -} - -template -inline bool Delaunay::isFacetOnBoundingBox(const FacetKey& facet_key) const -{ - const auto& min_pt = m_bounding_box.getMin(); - const auto& max_pt = m_bounding_box.getMax(); - const double tol = getBoundaryCoordinateTolerance(); - - for(int dim = 0; dim < DIM; ++dim) - { - bool on_min_face = true; - bool on_max_face = true; - for(const IndexType vertex_idx : facet_key) - { - const auto& vertex = m_mesh.getVertexPosition(vertex_idx); - on_min_face &= axom::utilities::abs(vertex[dim] - min_pt[dim]) <= tol; - on_max_face &= axom::utilities::abs(vertex[dim] - max_pt[dim]) <= tol; - } - - if(on_min_face || on_max_face) - { - return true; - } - } - - return false; -} - -template -inline double Delaunay::getElementSignedMeasure(IndexType element_idx) const -{ - if constexpr(DIM == 2) - { - return getElement(element_idx).signedArea(); - } - else - { - return getElement(element_idx).signedVolume(); - } -} - -template -inline double Delaunay::getElementMeasureTolerance() const -{ - const double scale = axom::utilities::max(1., m_bounding_box.range().norm()); - return 256. * std::numeric_limits::epsilon() * std::pow(scale, static_cast(DIM)); -} - -template -inline void Delaunay::validateInsertionSeed(IndexType element_idx, - const PointType& query_pt, - const BaryCoordType& bary_coord, - const IndexArray& seed_elements) const -{ - if(m_insertion_validation_mode == InsertionValidationMode::None) - { - return; - } - - fmt::memory_buffer out; - bool valid = true; - - if(!isSearchableElement(element_idx)) - { - fmt::format_to(std::back_inserter(out), "\n\tContaining element {} is not searchable", element_idx); - valid = false; - } - else - { - const auto verts = m_mesh.boundaryVertices(element_idx); - for(auto idx : verts.positions()) - { - if(!m_mesh.isValidVertex(verts[idx])) - { - fmt::format_to(std::back_inserter(out), - "\n\tContaining element {} references invalid vertex {}", - element_idx, - verts[idx]); - valid = false; - break; - } - } - } - - if(!isPointInsideForLocation(element_idx, query_pt, bary_coord)) - { - fmt::format_to(std::back_inserter(out), - "\n\tPoint {} is not inside containing element {}", - query_pt, - element_idx); - valid = false; - } - - if(seed_elements.empty()) - { - fmt::format_to(std::back_inserter(out), "\n\tNo cavity seed elements were generated"); - valid = false; - } - - bool found_containing_element = false; - for(const IndexType seed_element : seed_elements) - { - found_containing_element |= (seed_element == element_idx); - if(!m_mesh.isValidElement(seed_element)) - { - fmt::format_to(std::back_inserter(out), "\n\tSeed element {} is invalid", seed_element); - valid = false; - } - } - - if(!found_containing_element) - { - fmt::format_to(std::back_inserter(out), - "\n\tContaining element {} is missing from the seed set", - element_idx); - valid = false; - } - - SLIC_ERROR_IF(!valid, "Delaunay insertion seed validation failed:" << fmt::to_string(out)); -} - -template -inline bool Delaunay::isSearchableElement(IndexType element_idx) const +AXOM_QUEST_DELAUNAY_FORCE_INLINE bool Delaunay::isSearchableElement(IndexType element_idx) const { return m_mesh.isValidElement(element_idx); } @@ -247,9 +93,8 @@ inline int Delaunay::symbolicOrientationSign(const std::array } template -inline typename Delaunay::CircumsphereEval Delaunay::evaluateCircumsphereOnMesh( - const IAMeshType& mesh, - IndexType element_idx) +AXOM_QUEST_DELAUNAY_FORCE_INLINE typename Delaunay::CircumsphereEval +Delaunay::evaluateCircumsphereOnMesh(const IAMeshType& mesh, IndexType element_idx) { using axom::numerics::determinant; @@ -312,9 +157,10 @@ inline typename Delaunay::CircumsphereEval Delaunay::evaluateCircumsph } template -inline double Delaunay::sphereSquaredDistanceTolerance(const CircumsphereEval& sphere, - const PointType& x, - double distance_sq) +AXOM_QUEST_DELAUNAY_FORCE_INLINE double Delaunay::sphereSquaredDistanceTolerance( + const CircumsphereEval& sphere, + const PointType& x, + double distance_sq) { double scale = 1.; @@ -347,9 +193,9 @@ inline double Delaunay::sphereSignedDistanceTolerance(const SphereType& sph } template -inline int Delaunay::inSphereOrientationOnMesh(const IAMeshType& mesh, - const PointType& q, - IndexType element_idx) +AXOM_QUEST_DELAUNAY_FORCE_INLINE int Delaunay::inSphereOrientationOnMesh(const IAMeshType& mesh, + const PointType& q, + IndexType element_idx) { const auto sphere = evaluateCircumsphereOnMesh(mesh, element_idx); const double distance_sq = primal::squared_distance(sphere.center, q); @@ -365,110 +211,15 @@ inline int Delaunay::inSphereOrientationOnMesh(const IAMeshType& mesh, } template -inline double Delaunay::inSphereDeterminantOnMesh(const IAMeshType& mesh, - const PointType& q, - IndexType element_idx) -{ - const auto verts = mesh.boundaryVertices(element_idx); - - if constexpr(DIM == 2) - { - return primal::in_sphere_determinant(q, - mesh.getVertexPosition(verts[0]), - mesh.getVertexPosition(verts[1]), - mesh.getVertexPosition(verts[2])); - } - else - { - return primal::in_sphere_determinant(q, - mesh.getVertexPosition(verts[0]), - mesh.getVertexPosition(verts[1]), - mesh.getVertexPosition(verts[2]), - mesh.getVertexPosition(verts[3])); - } -} - -template -inline const char* Delaunay::orientationResultName(int result) -{ - switch(result) - { - case primal::ON_NEGATIVE_SIDE: - return "inside"; - case primal::ON_BOUNDARY: - return "boundary"; - case primal::ON_POSITIVE_SIDE: - return "outside"; - default: - return "unknown"; - } -} - -template -inline bool Delaunay::isPointInSphereOnMesh(const IAMeshType& mesh, - const PointType& q, - IndexType element_idx, - bool includeBoundary) +AXOM_QUEST_DELAUNAY_FORCE_INLINE bool Delaunay::isPointInSphereOnMesh(const IAMeshType& mesh, + const PointType& q, + IndexType element_idx, + bool includeBoundary) { const int res = inSphereOrientationOnMesh(mesh, q, element_idx); return includeBoundary ? (res != primal::ON_POSITIVE_SIDE) : (res == primal::ON_NEGATIVE_SIDE); } -template -inline int Delaunay::classifyOrientationDeterminant(double det, double tol) -{ - const int sign = signWithTolerance(det, tol); - return sign < 0 ? primal::ON_NEGATIVE_SIDE - : (sign > 0 ? primal::ON_POSITIVE_SIDE : primal::ON_BOUNDARY); -} - -template -inline typename Delaunay::OrientationEval Delaunay::evaluateElementOrientationDeterminant( - IndexType element_idx) const -{ - const double scale = (DIM == 2) ? 2. : 6.; - const double det = scale * getElementSignedMeasure(element_idx); - const double tol = scale * getElementMeasureTolerance(); - return {det, tol, classifyOrientationDeterminant(det, tol)}; -} - -template -inline void Delaunay::validateInsertionResult() const -{ - if(m_insertion_validation_mode == InsertionValidationMode::None) - { - return; - } - - if(m_insertion_validation_mode == InsertionValidationMode::Local) - { - return; - } - - if(!m_mesh.isValid(false)) - { - m_mesh.isValid(true); - SLIC_ERROR("Delaunay insertion produced an invalid IAMesh"); - } - - if(!isConforming(false)) - { - isConforming(true); - SLIC_ERROR("Delaunay insertion produced a non-conforming triangulation"); - } - - if(m_insertion_validation_mode == InsertionValidationMode::Full) - { - if(!isValid(false)) - { - isValid(true); - SLIC_ERROR( - "Delaunay insertion produced a triangulation that violates the empty-circumsphere " - "condition"); - } - } -} - template inline bool Delaunay::shouldCompactMesh() const { @@ -570,244 +321,6 @@ inline void Delaunay::removeBoundary() } } -template -inline bool Delaunay::isValid(bool verboseOutput) const -{ - using ImplicitGridType = spin::ImplicitGrid; - using UniformGridType = spin::UniformGrid; - using NumericArrayType = NumericArray; - using axom::numerics::dot_product; - - bool valid = true; - - std::vector> invalidEntries; - std::vector invalidElements; - - const IndexType totalVertices = m_mesh.vertices().size(); - const IndexType totalElements = m_mesh.elements().size(); - const IndexType res = axom::utilities::ceil(0.33 * std::pow(totalVertices, 1. / DIM)); - UniformGridType grid(m_bounding_box, NumericArray(res).data()); - - axom::Array circumspheres(totalElements); - - auto vertexInsideCircumsphere = [&](const PointType& vertex, IndexType element_idx) { - return isPointInSphereOnMesh(m_mesh, vertex, element_idx, /*includeBoundary=*/false); - }; - - auto circumsphereSignedDistanceTol = [](const typename ElementType::SphereType& sphere, - const PointType& x) { - const auto& center = sphere.getCenter(); - double scale = axom::utilities::max(1., sphere.getRadius()); - for(int dim = 0; dim < DIM; ++dim) - { - scale = axom::utilities::max(scale, axom::utilities::abs(center[dim])); - scale = axom::utilities::max(scale, axom::utilities::abs(x[dim])); - } - - return 256. * std::numeric_limits::epsilon() * scale; - }; - - { - using GridCell = typename ImplicitGridType::GridCell; - - const auto resCell = GridCell(res); - ImplicitGridType implicitGrid(m_bounding_box, &resCell, totalElements); - for(auto element_idx : m_mesh.elements().positions()) - { - if(m_mesh.isValidElement(element_idx)) - { - const auto verts = m_mesh.boundaryVertices(element_idx); - bool has_all_vertices = true; - for(int i = 0; i < VERT_PER_ELEMENT; ++i) - { - has_all_vertices &= m_mesh.isValidVertex(verts[i]); - } - - if(!has_all_vertices) - { - valid = false; - if(verboseOutput) - { - invalidElements.push_back(element_idx); - } - continue; - } - - circumspheres[element_idx] = this->getElement(element_idx).circumsphere(); - const auto& sphere = circumspheres[element_idx]; - const auto& center = sphere.getCenter().array(); - const auto offset = NumericArrayType(sphere.getRadius()); - - BoundingBox bb; - bb.addPoint(PointType(center - offset)); - bb.addPoint(PointType(center + offset)); - - implicitGrid.insert(bb, element_idx); - } - } - - const int kUpper = (DIM == 2) ? 0 : res; - const IndexType stride[3] = {1, res, (DIM == 2) ? 0 : res * res}; - for(IndexType k = 0; k < kUpper; ++k) - { - for(IndexType j = 0; j < res; ++j) - { - for(IndexType i = 0; i < res; ++i) - { - const IndexType vals[3] = {i, j, k}; - const GridCell cell(vals); - const auto idx = dot_product(cell.data(), stride, DIM); - const auto binValues = implicitGrid.getCandidatesAsArray(cell); - grid.getBinContents(idx).insert(0, binValues.size(), binValues.data()); - } - } - } - } - - for(auto vertex_idx : m_mesh.vertices().positions()) - { - if(!m_mesh.isValidVertex(vertex_idx)) - { - continue; - } - - const auto& vertex = m_mesh.getVertexPosition(vertex_idx); - for(const auto element_idx : grid.getBinContents(grid.getBinIndex(vertex))) - { - if(slam::is_subset(vertex_idx, m_mesh.boundaryVertices(element_idx))) - { - continue; - } - - if(vertexInsideCircumsphere(vertex, element_idx)) - { - valid = false; - - if(verboseOutput) - { - invalidEntries.push_back(std::make_pair(vertex_idx, element_idx)); - } - } - } - } - - if(verboseOutput) - { - if(valid) - { - SLIC_INFO("Delaunay complex was valid"); - } - else - { - fmt::memory_buffer out; - if(!invalidElements.empty()) - { - fmt::format_to(std::back_inserter(out), - "\n\t{} valid elements referenced invalid vertices: {}", - invalidElements.size(), - fmt::join(invalidElements, ", ")); - } - for(const auto& pr : invalidEntries) - { - const auto vertex_idx = pr.first; - const auto element_idx = pr.second; - const auto& pos = m_mesh.getVertexPosition(vertex_idx); - const auto element = this->getElement(element_idx); - const auto circumsphere = element.circumsphere(); - const double tol = circumsphereSignedDistanceTol(circumsphere, pos); - fmt::format_to(std::back_inserter(out), - "\n\tVertex {} @ {}" - "\n\tElement {}: {} w/ circumsphere: {}" - "\n\tDistance to circumcenter: {} (tol={})", - vertex_idx, - pos, - element_idx, - element, - circumsphere, - circumsphere.computeSignedDistance(pos), - tol); - } - - SLIC_INFO( - fmt::format("Delaunay complex was NOT valid. There were {} " - "vertices in the circumsphere of an element. {}", - invalidEntries.size(), - fmt::to_string(out))); - } - } - - return valid; -} - -template -inline bool Delaunay::isConforming(bool verboseOutput) const -{ - fmt::memory_buffer out; - - bool valid = m_mesh.isConforming(verboseOutput); - - for(auto element_idx : m_mesh.elements().positions()) - { - if(!m_mesh.isValidElement(element_idx)) - { - continue; - } - - const auto orient = evaluateElementOrientationDeterminant(element_idx); - if(orient.orientation != primal::ON_POSITIVE_SIDE) - { - if(verboseOutput) - { - fmt::format_to( - std::back_inserter(out), - "\n\tElement {} has non-positive orientation determinant {:.17g} (tol={:.3g})", - element_idx, - orient.det, - orient.tol); - } - valid = false; - } - - if(m_has_boundary) - { - const auto neighbors = m_mesh.adjacentElements(element_idx); - for(int facet_idx = 0; facet_idx < VERT_PER_ELEMENT; ++facet_idx) - { - if(m_mesh.isValidElement(neighbors[facet_idx])) - { - continue; - } - - const FacetKey facet_key = m_mesh.getSortedFacetKey(element_idx, facet_idx); - if(!isFacetOnBoundingBox(facet_key)) - { - if(verboseOutput) - { - fmt::format_to(std::back_inserter(out), - "\n\tBoundary facet {} is not on the initial bounding box", - facetKeyString(facet_key)); - } - valid = false; - } - } - } - } - - if(verboseOutput) - { - if(valid) - { - SLIC_INFO("Delaunay mesh was conforming"); - } - else - { - SLIC_INFO("Delaunay mesh was NOT conforming. Summary: " << fmt::to_string(out)); - } - } - - return valid; -} - template inline void Delaunay::initializeBoundary(const BoundingBox& bb) { @@ -918,407 +431,6 @@ inline void Delaunay::insertPoint(const PointType& new_pt) } } -template -inline void Delaunay::validateCavityBoundary(const InsertionHelper& insertion_helper) const -{ - if(m_insertion_validation_mode == InsertionValidationMode::None) - { - return; - } - - struct FacetInfo - { - IndexType neighbor_idx {INVALID_INDEX}; - bool matched {false}; - }; - - fmt::memory_buffer out; - bool valid = true; - std::map cavity_boundary; - - for(const auto& facet : insertion_helper.boundary_facets) - { - const FacetKey facet_key = makeSortedFaceKey(facet.vertices); - const auto insert_status = cavity_boundary.insert({facet_key, {facet.neighbor, false}}); - if(!insert_status.second) - { - fmt::format_to(std::back_inserter(out), - "\n\tCavity boundary facet {} was recorded more than once", - facetKeyString(facet_key)); - valid = false; - } - } - - for(const IndexType cavity_element : insertion_helper.cavity_elems) - { - const auto neighbors = m_mesh.adjacentElements(cavity_element); - for(int facet_idx = 0; facet_idx < VERT_PER_ELEMENT; ++facet_idx) - { - const IndexType neighbor_idx = neighbors[facet_idx]; - if(m_mesh.isValidElement(neighbor_idx) && insertion_helper.containsCavityElement(neighbor_idx)) - { - continue; - } - - const FacetKey facet_key = m_mesh.getSortedFacetKey(cavity_element, facet_idx); - auto facet_it = cavity_boundary.find(facet_key); - if(facet_it == cavity_boundary.end()) - { - fmt::format_to(std::back_inserter(out), - "\n\tCavity facet {} on element {} face {} is missing from the " - "boundary facet set", - facetKeyString(facet_key), - cavity_element, - facet_idx); - valid = false; - continue; - } - - if(facet_it->second.neighbor_idx != neighbor_idx) - { - fmt::format_to(std::back_inserter(out), - "\n\tCavity face {} expects neighbor {} but facet relation stores {}", - facetKeyString(facet_key), - neighbor_idx, - facet_it->second.neighbor_idx); - valid = false; - } - - if(!m_mesh.isValidElement(neighbor_idx) && m_has_boundary && !isFacetOnBoundingBox(facet_key)) - { - fmt::format_to(std::back_inserter(out), - "\n\tCavity boundary face {} exits the mesh away from the bounding box", - facetKeyString(facet_key)); - valid = false; - } - - facet_it->second.matched = true; - } - } - - for(const auto& facet_entry : cavity_boundary) - { - if(!facet_entry.second.matched) - { - fmt::format_to(std::back_inserter(out), - "\n\tFacet {} does not correspond to a cavity boundary face", - facetKeyString(facet_entry.first)); - valid = false; - } - } - - SLIC_ERROR_IF(!valid, - "Delaunay cavity validation failed after insertion " << m_num_insertions << ":" - << fmt::to_string(out)); -} - -template -inline void Delaunay::validateInsertedBall(IndexType new_pt_i, - const InsertionHelper& insertion_helper) const -{ - if(m_insertion_validation_mode == InsertionValidationMode::None) - { - return; - } - - struct BoundaryFacetInfo - { - IndexType neighbor_idx {INVALID_INDEX}; - bool matched {false}; - }; - - fmt::memory_buffer out; - bool valid = true; - std::map cavity_boundary; - std::map> inserted_faces; - auto findOppositeVertex = [&](IndexType element_idx, const FacetKey& facet_key) { - const auto verts = m_mesh.boundaryVertices(element_idx); - for(int i = 0; i < VERT_PER_ELEMENT; ++i) - { - bool on_face = false; - for(int j = 0; j < VERTS_PER_FACET; ++j) - { - on_face |= (verts[i] == facet_key[j]); - } - if(!on_face) - { - return verts[i]; - } - } - - return INVALID_INDEX; - }; - - if(insertion_helper.inserted_elems.size() != insertion_helper.boundary_facets.size()) - { - fmt::format_to( - std::back_inserter(out), - "\n\tInserted ball element count {} does not match cavity boundary facet count {}", - insertion_helper.inserted_elems.size(), - insertion_helper.boundary_facets.size()); - valid = false; - } - - if(insertion_helper.containing_element != INVALID_INDEX) - { - fmt::format_to(std::back_inserter(out), - "\n\tContaining element {} barycentric coordinates {}", - insertion_helper.containing_element, - insertion_helper.containing_bary); - if(!insertion_helper.seed_elements_debug.empty()) - { - fmt::format_to(std::back_inserter(out), - "\n\tInsertion seeds: [{}]", - fmt::join(insertion_helper.seed_elements_debug, ", ")); - } - } - - for(const auto& facet : insertion_helper.boundary_facets) - { - cavity_boundary.insert({makeSortedFaceKey(facet.vertices), {facet.neighbor, false}}); - } - - for(const IndexType element_idx : insertion_helper.inserted_elems) - { - if(!m_mesh.isValidElement(element_idx)) - { - fmt::format_to(std::back_inserter(out), "\n\tInserted element {} is invalid", element_idx); - valid = false; - continue; - } - - const auto verts = m_mesh.boundaryVertices(element_idx); - if(!slam::is_subset(new_pt_i, verts)) - { - fmt::format_to(std::back_inserter(out), - "\n\tInserted element {} does not contain the new vertex {}", - element_idx, - new_pt_i); - valid = false; - } - - const auto orient = evaluateElementOrientationDeterminant(element_idx); - if(orient.orientation != primal::ON_POSITIVE_SIDE) - { - fmt::format_to( - std::back_inserter(out), - "\n\tInserted element {} has non-positive orientation determinant {:.17g} (tol={:.3g})", - element_idx, - orient.det, - orient.tol); - valid = false; - } - - const auto neighbors = m_mesh.adjacentElements(element_idx); - for(int facet_idx = 0; facet_idx < VERT_PER_ELEMENT; ++facet_idx) - { - inserted_faces[m_mesh.getSortedFacetKey(element_idx, facet_idx)].push_back( - {element_idx, facet_idx, neighbors[facet_idx]}); - } - } - - for(auto& face_entry : inserted_faces) - { - auto cavity_it = cavity_boundary.find(face_entry.first); - auto& records = face_entry.second; - if(cavity_it != cavity_boundary.end()) - { - if(records.size() != 1) - { - fmt::format_to(std::back_inserter(out), - "\n\tBoundary face {} of the inserted ball is used by {} new elements", - facetKeyString(face_entry.first), - records.size()); - valid = false; - continue; - } - - if(records.front().neighbor_idx != cavity_it->second.neighbor_idx) - { - fmt::format_to(std::back_inserter(out), - "\n\tInserted boundary face {} points to neighbor {} instead of {}", - facetKeyString(face_entry.first), - records.front().neighbor_idx, - cavity_it->second.neighbor_idx); - valid = false; - } - - if(m_mesh.isValidElement(cavity_it->second.neighbor_idx)) - { - const IndexType inserted_opposite = - findOppositeVertex(records.front().element_idx, face_entry.first); - const IndexType neighbor_opposite = - findOppositeVertex(cavity_it->second.neighbor_idx, face_entry.first); - - if(inserted_opposite == INVALID_INDEX || neighbor_opposite == INVALID_INDEX) - { - fmt::format_to( - std::back_inserter(out), - "\n\tCould not identify opposite vertices across inserted boundary face {}", - facetKeyString(face_entry.first)); - valid = false; - } - else - { - const PointType& inserted_point = m_mesh.getVertexPosition(inserted_opposite); - const PointType& neighbor_point = m_mesh.getVertexPosition(neighbor_opposite); - - if(isPointInSphereOnMesh(m_mesh, - inserted_point, - cavity_it->second.neighbor_idx, - /*includeBoundary=*/false)) - { - const int query_in_neighbor = - inSphereOrientationOnMesh(m_mesh, - m_mesh.getVertexPosition(new_pt_i), - cavity_it->second.neighbor_idx); - const int inserted_in_neighbor = - inSphereOrientationOnMesh(m_mesh, inserted_point, cavity_it->second.neighbor_idx); - fmt::format_to( - std::back_inserter(out), - "\n\tInserted boundary face {} leaves new vertex {} inside neighbor {} " - "circumsphere (query {}={}, det={:.17g}; opposite {}={}, det={:.17g})", - facetKeyString(face_entry.first), - inserted_opposite, - cavity_it->second.neighbor_idx, - new_pt_i, - orientationResultName(query_in_neighbor), - inSphereDeterminantOnMesh(m_mesh, - m_mesh.getVertexPosition(new_pt_i), - cavity_it->second.neighbor_idx), - inserted_opposite, - orientationResultName(inserted_in_neighbor), - inSphereDeterminantOnMesh(m_mesh, inserted_point, cavity_it->second.neighbor_idx)); - valid = false; - } - - if(isPointInSphereOnMesh(m_mesh, - neighbor_point, - records.front().element_idx, - /*includeBoundary=*/false)) - { - const int query_in_neighbor = - inSphereOrientationOnMesh(m_mesh, - m_mesh.getVertexPosition(new_pt_i), - cavity_it->second.neighbor_idx); - const int neighbor_in_inserted = - inSphereOrientationOnMesh(m_mesh, neighbor_point, records.front().element_idx); - fmt::format_to( - std::back_inserter(out), - "\n\tInserted boundary face {} leaves neighbor vertex {} inside new element {} " - "circumsphere (query {} in neighbor {} is {}, det={:.17g}; opposite {} in new " - "element is {}, det={:.17g})", - facetKeyString(face_entry.first), - neighbor_opposite, - records.front().element_idx, - new_pt_i, - cavity_it->second.neighbor_idx, - orientationResultName(query_in_neighbor), - inSphereDeterminantOnMesh(m_mesh, - m_mesh.getVertexPosition(new_pt_i), - cavity_it->second.neighbor_idx), - neighbor_opposite, - orientationResultName(neighbor_in_inserted), - inSphereDeterminantOnMesh(m_mesh, neighbor_point, records.front().element_idx)); - valid = false; - } - } - } - - cavity_it->second.matched = true; - } - else if(records.size() != 2 || records[0].neighbor_idx != records[1].element_idx || - records[1].neighbor_idx != records[0].element_idx) - { - fmt::format_to(std::back_inserter(out), - "\n\tInternal face {} of the inserted ball has inconsistent adjacency", - facetKeyString(face_entry.first)); - valid = false; - } - else - { - const IndexType lhs_opposite = findOppositeVertex(records[0].element_idx, face_entry.first); - const IndexType rhs_opposite = findOppositeVertex(records[1].element_idx, face_entry.first); - - if(lhs_opposite == INVALID_INDEX || rhs_opposite == INVALID_INDEX) - { - fmt::format_to(std::back_inserter(out), - "\n\tCould not identify opposite vertices across inserted internal face {}", - facetKeyString(face_entry.first)); - valid = false; - } - else - { - const PointType& lhs_point = m_mesh.getVertexPosition(lhs_opposite); - const PointType& rhs_point = m_mesh.getVertexPosition(rhs_opposite); - - if(isPointInSphereOnMesh(m_mesh, lhs_point, records[1].element_idx, /*includeBoundary=*/false)) - { - const int query_in_rhs = inSphereOrientationOnMesh(m_mesh, - m_mesh.getVertexPosition(new_pt_i), - records[1].element_idx); - const int lhs_in_rhs = inSphereOrientationOnMesh(m_mesh, lhs_point, records[1].element_idx); - fmt::format_to( - std::back_inserter(out), - "\n\tInserted internal face {} leaves vertex {} inside adjacent element {} " - "circumsphere (query {}={}, det={:.17g}; opposite {}={}, det={:.17g})", - facetKeyString(face_entry.first), - lhs_opposite, - records[1].element_idx, - new_pt_i, - orientationResultName(query_in_rhs), - inSphereDeterminantOnMesh(m_mesh, - m_mesh.getVertexPosition(new_pt_i), - records[1].element_idx), - lhs_opposite, - orientationResultName(lhs_in_rhs), - inSphereDeterminantOnMesh(m_mesh, lhs_point, records[1].element_idx)); - valid = false; - } - - if(isPointInSphereOnMesh(m_mesh, rhs_point, records[0].element_idx, /*includeBoundary=*/false)) - { - const int query_in_lhs = inSphereOrientationOnMesh(m_mesh, - m_mesh.getVertexPosition(new_pt_i), - records[0].element_idx); - const int rhs_in_lhs = inSphereOrientationOnMesh(m_mesh, rhs_point, records[0].element_idx); - fmt::format_to( - std::back_inserter(out), - "\n\tInserted internal face {} leaves vertex {} inside adjacent element {} " - "circumsphere (query {}={}, det={:.17g}; opposite {}={}, det={:.17g})", - facetKeyString(face_entry.first), - rhs_opposite, - records[0].element_idx, - new_pt_i, - orientationResultName(query_in_lhs), - inSphereDeterminantOnMesh(m_mesh, - m_mesh.getVertexPosition(new_pt_i), - records[0].element_idx), - rhs_opposite, - orientationResultName(rhs_in_lhs), - inSphereDeterminantOnMesh(m_mesh, rhs_point, records[0].element_idx)); - valid = false; - } - } - } - } - - for(const auto& facet_entry : cavity_boundary) - { - if(!facet_entry.second.matched) - { - fmt::format_to(std::back_inserter(out), - "\n\tCavity boundary face {} was not covered by the inserted ball", - facetKeyString(facet_entry.first)); - valid = false; - } - } - - SLIC_ERROR_IF(!valid, - "Delaunay ball validation failed after insertion " << m_num_insertions << ":" - << fmt::to_string(out)); -} - template <> inline void Delaunay<2>::generateInitialMesh(std::vector& points, std::vector& elem, diff --git a/src/axom/quest/detail/DelaunayPointLocation.hpp b/src/axom/quest/detail/DelaunayPointLocation.hpp index 9c1b44e588..de81e9c37a 100644 --- a/src/axom/quest/detail/DelaunayPointLocation.hpp +++ b/src/axom/quest/detail/DelaunayPointLocation.hpp @@ -13,8 +13,9 @@ namespace quest { template -inline typename Delaunay::BaryCoordType Delaunay::getBaryCoords(IndexType element_idx, - const PointType& query_pt) const +AXOM_QUEST_DELAUNAY_FORCE_INLINE typename Delaunay::BaryCoordType Delaunay::getBaryCoords( + IndexType element_idx, + const PointType& query_pt) const { const auto verts = m_mesh.boundaryVertices(element_idx); @@ -86,10 +87,11 @@ inline double Delaunay::rawBarycentricDeterminantTolerance(IndexType elemen } template -inline bool Delaunay::isPointInsideForLocation(IndexType element_idx, - const PointType& query_pt, - const BaryCoordType& bary_coord, - ModularFaceIndex* exit_face) const +AXOM_QUEST_DELAUNAY_FORCE_INLINE bool Delaunay::isPointInsideForLocation( + IndexType element_idx, + const PointType& query_pt, + const BaryCoordType& bary_coord, + ModularFaceIndex* exit_face) const { ModularFaceIndex min_face(bary_coord.array().argMin()); @@ -142,10 +144,10 @@ inline bool Delaunay::isPointInsideForLocation(IndexType element_idx, } template -inline typename Delaunay::PointLocationResult Delaunay::walkToContainingElement( - const PointType& query_pt, - IndexType start_element, - std::vector* visited_elements_out) const +AXOM_QUEST_DELAUNAY_FORCE_INLINE typename Delaunay::PointLocationResult +Delaunay::walkToContainingElement(const PointType& query_pt, + IndexType start_element, + std::vector* visited_elements_out) const { constexpr IndexType invalid_element = IAMeshType::INVALID_ELEMENT_INDEX; @@ -243,8 +245,9 @@ inline typename Delaunay::PointLocationResult Delaunay::walkToContaini } template -inline void Delaunay::appendCandidateElement(std::vector& candidate_elements, - IndexType vertex_i) const +AXOM_QUEST_DELAUNAY_FORCE_INLINE void Delaunay::appendCandidateElement( + std::vector& candidate_elements, + IndexType vertex_i) const { const IndexType element_i = m_mesh.coboundaryElement(vertex_i); if(isSearchableElement(element_i) && @@ -256,7 +259,7 @@ inline void Delaunay::appendCandidateElement(std::vector& candid } template -inline void Delaunay::appendCandidateElementsFromVertices( +AXOM_QUEST_DELAUNAY_FORCE_INLINE void Delaunay::appendCandidateElementsFromVertices( std::vector& candidate_elements, const std::vector& candidate_vertices) const { @@ -267,8 +270,9 @@ inline void Delaunay::appendCandidateElementsFromVertices( } template -inline void Delaunay::getInitialCandidateElements(const PointType& query_pt, - std::vector& candidate_elements) const +AXOM_QUEST_DELAUNAY_FORCE_INLINE void Delaunay::getInitialCandidateElements( + const PointType& query_pt, + std::vector& candidate_elements) const { candidate_elements.clear(); candidate_elements.reserve(16); @@ -299,11 +303,11 @@ inline void Delaunay::getInitialCandidateElements(const PointType& query_pt } template -inline typename Delaunay::PointLocationResult Delaunay::walkCandidateElements( - const PointType& query_pt, - const std::vector& candidate_elements, - std::size_t start_idx, - std::vector* walked_elements) const +AXOM_QUEST_DELAUNAY_FORCE_INLINE typename Delaunay::PointLocationResult +Delaunay::walkCandidateElements(const PointType& query_pt, + const std::vector& candidate_elements, + std::size_t start_idx, + std::vector* walked_elements) const { for(std::size_t idx = start_idx; idx < candidate_elements.size(); ++idx) { @@ -321,10 +325,10 @@ inline typename Delaunay::PointLocationResult Delaunay::walkCandidateE } template -inline typename Delaunay::PointLocationResult Delaunay::findContainingElementWithQueryFallbacks( - const PointType& query_pt, - std::vector& candidate_elements, - const std::vector& walked_elements) const +AXOM_QUEST_DELAUNAY_FORCE_INLINE typename Delaunay::PointLocationResult +Delaunay::findContainingElementWithQueryFallbacks(const PointType& query_pt, + std::vector& candidate_elements, + const std::vector& walked_elements) const { const IndexType walk_region_elem = findContainingElementFromNeighbors(query_pt, walked_elements); if(walk_region_elem != INVALID_INDEX) @@ -359,9 +363,9 @@ inline typename Delaunay::PointLocationResult Delaunay::findContaining } template -inline typename Delaunay::IndexType Delaunay::findContainingElementFromNeighbors( - const PointType& query_pt, - const std::vector& seed_elements) const +AXOM_QUEST_DELAUNAY_FORCE_INLINE typename Delaunay::IndexType +Delaunay::findContainingElementFromNeighbors(const PointType& query_pt, + const std::vector& seed_elements) const { if(seed_elements.empty()) { @@ -457,9 +461,9 @@ inline typename Delaunay::IndexType Delaunay::findContainingElementLin } template -inline typename Delaunay::IndexType Delaunay::findContainingElementNearby( - const PointType& query_pt, - const std::vector& nearby_vertices) const +AXOM_QUEST_DELAUNAY_FORCE_INLINE typename Delaunay::IndexType +Delaunay::findContainingElementNearby(const PointType& query_pt, + const std::vector& nearby_vertices) const { std::vector nearby_elements; for(const IndexType vertex_idx : nearby_vertices) diff --git a/src/axom/quest/detail/DelaunayValidation.hpp b/src/axom/quest/detail/DelaunayValidation.hpp new file mode 100644 index 0000000000..65d20ed3d8 --- /dev/null +++ b/src/axom/quest/detail/DelaunayValidation.hpp @@ -0,0 +1,905 @@ +// 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_DETAIL_DELAUNAY_VALIDATION_HPP_ +#define AXOM_QUEST_DETAIL_DELAUNAY_VALIDATION_HPP_ + +namespace axom +{ +namespace quest +{ + +template +template +inline typename Delaunay::FacetKey Delaunay::makeSortedFaceKey(const FacetSubsetType& facet) +{ + FacetKey key {}; + for(int i = 0; i < VERTS_PER_FACET; ++i) + { + key[i] = facet[i]; + } + std::sort(key.begin(), key.end()); + return key; +} + +template +inline std::string Delaunay::facetKeyString(const FacetKey& facet_key) +{ + return fmt::format("[{}]", fmt::join(facet_key, ", ")); +} + +template +inline double Delaunay::getBoundaryCoordinateTolerance() const +{ + const auto min_pt = m_bounding_box.getMin(); + const auto max_pt = m_bounding_box.getMax(); + + double max_extent = 1.; + for(int dim = 0; dim < DIM; ++dim) + { + max_extent = axom::utilities::max(max_extent, max_pt[dim] - min_pt[dim]); + } + + return 64. * std::numeric_limits::epsilon() * max_extent; +} + +template +inline bool Delaunay::isFacetOnBoundingBox(const FacetKey& facet_key) const +{ + const auto& min_pt = m_bounding_box.getMin(); + const auto& max_pt = m_bounding_box.getMax(); + const double tol = getBoundaryCoordinateTolerance(); + + for(int dim = 0; dim < DIM; ++dim) + { + bool on_min_face = true; + bool on_max_face = true; + for(const IndexType vertex_idx : facet_key) + { + const auto& vertex = m_mesh.getVertexPosition(vertex_idx); + on_min_face &= axom::utilities::abs(vertex[dim] - min_pt[dim]) <= tol; + on_max_face &= axom::utilities::abs(vertex[dim] - max_pt[dim]) <= tol; + } + + if(on_min_face || on_max_face) + { + return true; + } + } + + return false; +} + +template +inline double Delaunay::getElementSignedMeasure(IndexType element_idx) const +{ + if constexpr(DIM == 2) + { + return getElement(element_idx).signedArea(); + } + else + { + return getElement(element_idx).signedVolume(); + } +} + +template +inline double Delaunay::getElementMeasureTolerance() const +{ + const double scale = axom::utilities::max(1., m_bounding_box.range().norm()); + return 256. * std::numeric_limits::epsilon() * std::pow(scale, static_cast(DIM)); +} + +template +inline void Delaunay::validateInsertionSeed(IndexType element_idx, + const PointType& query_pt, + const BaryCoordType& bary_coord, + const IndexArray& seed_elements) const +{ + if(m_insertion_validation_mode == InsertionValidationMode::None) + { + return; + } + + fmt::memory_buffer out; + bool valid = true; + + if(!isSearchableElement(element_idx)) + { + fmt::format_to(std::back_inserter(out), "\n\tContaining element {} is not searchable", element_idx); + valid = false; + } + else + { + const auto verts = m_mesh.boundaryVertices(element_idx); + for(auto idx : verts.positions()) + { + if(!m_mesh.isValidVertex(verts[idx])) + { + fmt::format_to(std::back_inserter(out), + "\n\tContaining element {} references invalid vertex {}", + element_idx, + verts[idx]); + valid = false; + break; + } + } + } + + if(!isPointInsideForLocation(element_idx, query_pt, bary_coord)) + { + fmt::format_to(std::back_inserter(out), + "\n\tPoint {} is not inside containing element {}", + query_pt, + element_idx); + valid = false; + } + + if(seed_elements.empty()) + { + fmt::format_to(std::back_inserter(out), "\n\tNo cavity seed elements were generated"); + valid = false; + } + + bool found_containing_element = false; + for(const IndexType seed_element : seed_elements) + { + found_containing_element |= (seed_element == element_idx); + if(!m_mesh.isValidElement(seed_element)) + { + fmt::format_to(std::back_inserter(out), "\n\tSeed element {} is invalid", seed_element); + valid = false; + } + } + + if(!found_containing_element) + { + fmt::format_to(std::back_inserter(out), + "\n\tContaining element {} is missing from the seed set", + element_idx); + valid = false; + } + + SLIC_ERROR_IF(!valid, "Delaunay insertion seed validation failed:" << fmt::to_string(out)); +} + +template +inline double Delaunay::inSphereDeterminantOnMesh(const IAMeshType& mesh, + const PointType& q, + IndexType element_idx) +{ + const auto verts = mesh.boundaryVertices(element_idx); + + if constexpr(DIM == 2) + { + return primal::in_sphere_determinant(q, + mesh.getVertexPosition(verts[0]), + mesh.getVertexPosition(verts[1]), + mesh.getVertexPosition(verts[2])); + } + else + { + return primal::in_sphere_determinant(q, + mesh.getVertexPosition(verts[0]), + mesh.getVertexPosition(verts[1]), + mesh.getVertexPosition(verts[2]), + mesh.getVertexPosition(verts[3])); + } +} + +template +inline const char* Delaunay::orientationResultName(int result) +{ + switch(result) + { + case primal::ON_NEGATIVE_SIDE: + return "inside"; + case primal::ON_BOUNDARY: + return "boundary"; + case primal::ON_POSITIVE_SIDE: + return "outside"; + default: + return "unknown"; + } +} + +template +inline int Delaunay::classifyOrientationDeterminant(double det, double tol) +{ + const int sign = signWithTolerance(det, tol); + return sign < 0 ? primal::ON_NEGATIVE_SIDE + : (sign > 0 ? primal::ON_POSITIVE_SIDE : primal::ON_BOUNDARY); +} + +template +inline typename Delaunay::OrientationEval Delaunay::evaluateElementOrientationDeterminant( + IndexType element_idx) const +{ + const double scale = (DIM == 2) ? 2. : 6.; + const double det = scale * getElementSignedMeasure(element_idx); + const double tol = scale * getElementMeasureTolerance(); + return {det, tol, classifyOrientationDeterminant(det, tol)}; +} + +template +inline void Delaunay::validateInsertionResult() const +{ + if(m_insertion_validation_mode == InsertionValidationMode::None) + { + return; + } + + if(m_insertion_validation_mode == InsertionValidationMode::Local) + { + return; + } + + if(!m_mesh.isValid(false)) + { + m_mesh.isValid(true); + SLIC_ERROR("Delaunay insertion produced an invalid IAMesh"); + } + + if(!isConforming(false)) + { + isConforming(true); + SLIC_ERROR("Delaunay insertion produced a non-conforming triangulation"); + } + + if(m_insertion_validation_mode == InsertionValidationMode::Full) + { + if(!isValid(false)) + { + isValid(true); + SLIC_ERROR( + "Delaunay insertion produced a triangulation that violates the empty-circumsphere " + "condition"); + } + } +} + +template +inline bool Delaunay::isValid(bool verboseOutput) const +{ + using ImplicitGridType = spin::ImplicitGrid; + using UniformGridType = spin::UniformGrid; + using NumericArrayType = NumericArray; + using axom::numerics::dot_product; + + bool valid = true; + + std::vector> invalidEntries; + std::vector invalidElements; + + const IndexType totalVertices = m_mesh.vertices().size(); + const IndexType totalElements = m_mesh.elements().size(); + const IndexType res = axom::utilities::ceil(0.33 * std::pow(totalVertices, 1. / DIM)); + UniformGridType grid(m_bounding_box, NumericArray(res).data()); + + axom::Array circumspheres(totalElements); + + auto vertexInsideCircumsphere = [&](const PointType& vertex, IndexType element_idx) { + return isPointInSphereOnMesh(m_mesh, vertex, element_idx, /*includeBoundary=*/false); + }; + + auto circumsphereSignedDistanceTol = [](const typename ElementType::SphereType& sphere, + const PointType& x) { + const auto& center = sphere.getCenter(); + double scale = axom::utilities::max(1., sphere.getRadius()); + for(int dim = 0; dim < DIM; ++dim) + { + scale = axom::utilities::max(scale, axom::utilities::abs(center[dim])); + scale = axom::utilities::max(scale, axom::utilities::abs(x[dim])); + } + + return 256. * std::numeric_limits::epsilon() * scale; + }; + + { + using GridCell = typename ImplicitGridType::GridCell; + + const auto resCell = GridCell(res); + ImplicitGridType implicitGrid(m_bounding_box, &resCell, totalElements); + for(auto element_idx : m_mesh.elements().positions()) + { + if(m_mesh.isValidElement(element_idx)) + { + const auto verts = m_mesh.boundaryVertices(element_idx); + bool has_all_vertices = true; + for(int i = 0; i < VERT_PER_ELEMENT; ++i) + { + has_all_vertices &= m_mesh.isValidVertex(verts[i]); + } + + if(!has_all_vertices) + { + valid = false; + if(verboseOutput) + { + invalidElements.push_back(element_idx); + } + continue; + } + + circumspheres[element_idx] = this->getElement(element_idx).circumsphere(); + const auto& sphere = circumspheres[element_idx]; + const auto& center = sphere.getCenter().array(); + const auto offset = NumericArrayType(sphere.getRadius()); + + BoundingBox bb; + bb.addPoint(PointType(center - offset)); + bb.addPoint(PointType(center + offset)); + + implicitGrid.insert(bb, element_idx); + } + } + + const int kUpper = (DIM == 2) ? 0 : res; + const IndexType stride[3] = {1, res, (DIM == 2) ? 0 : res * res}; + for(IndexType k = 0; k < kUpper; ++k) + { + for(IndexType j = 0; j < res; ++j) + { + for(IndexType i = 0; i < res; ++i) + { + const IndexType vals[3] = {i, j, k}; + const GridCell cell(vals); + const auto idx = dot_product(cell.data(), stride, DIM); + const auto binValues = implicitGrid.getCandidatesAsArray(cell); + grid.getBinContents(idx).insert(0, binValues.size(), binValues.data()); + } + } + } + } + + for(auto vertex_idx : m_mesh.vertices().positions()) + { + if(!m_mesh.isValidVertex(vertex_idx)) + { + continue; + } + + const auto& vertex = m_mesh.getVertexPosition(vertex_idx); + for(const auto element_idx : grid.getBinContents(grid.getBinIndex(vertex))) + { + if(slam::is_subset(vertex_idx, m_mesh.boundaryVertices(element_idx))) + { + continue; + } + + if(vertexInsideCircumsphere(vertex, element_idx)) + { + valid = false; + + if(verboseOutput) + { + invalidEntries.push_back(std::make_pair(vertex_idx, element_idx)); + } + } + } + } + + if(verboseOutput) + { + if(valid) + { + SLIC_INFO("Delaunay complex was valid"); + } + else + { + fmt::memory_buffer out; + if(!invalidElements.empty()) + { + fmt::format_to(std::back_inserter(out), + "\n\t{} valid elements referenced invalid vertices: {}", + invalidElements.size(), + fmt::join(invalidElements, ", ")); + } + for(const auto& pr : invalidEntries) + { + const auto vertex_idx = pr.first; + const auto element_idx = pr.second; + const auto& pos = m_mesh.getVertexPosition(vertex_idx); + const auto element = this->getElement(element_idx); + const auto circumsphere = element.circumsphere(); + const double tol = circumsphereSignedDistanceTol(circumsphere, pos); + fmt::format_to(std::back_inserter(out), + "\n\tVertex {} @ {}" + "\n\tElement {}: {} w/ circumsphere: {}" + "\n\tDistance to circumcenter: {} (tol={})", + vertex_idx, + pos, + element_idx, + element, + circumsphere, + circumsphere.computeSignedDistance(pos), + tol); + } + + SLIC_INFO( + fmt::format("Delaunay complex was NOT valid. There were {} " + "vertices in the circumsphere of an element. {}", + invalidEntries.size(), + fmt::to_string(out))); + } + } + + return valid; +} + +template +inline bool Delaunay::isConforming(bool verboseOutput) const +{ + fmt::memory_buffer out; + + bool valid = m_mesh.isConforming(verboseOutput); + + for(auto element_idx : m_mesh.elements().positions()) + { + if(!m_mesh.isValidElement(element_idx)) + { + continue; + } + + const auto orient = evaluateElementOrientationDeterminant(element_idx); + if(orient.orientation != primal::ON_POSITIVE_SIDE) + { + if(verboseOutput) + { + fmt::format_to(std::back_inserter(out), + "\n\tElement {} has non-positive orientation determinant {:.17g} (tol={:.3g})", + element_idx, + orient.det, + orient.tol); + } + valid = false; + } + + if(m_has_boundary) + { + const auto neighbors = m_mesh.adjacentElements(element_idx); + for(int facet_idx = 0; facet_idx < VERT_PER_ELEMENT; ++facet_idx) + { + if(m_mesh.isValidElement(neighbors[facet_idx])) + { + continue; + } + + const FacetKey facet_key = m_mesh.getSortedFacetKey(element_idx, facet_idx); + if(!isFacetOnBoundingBox(facet_key)) + { + if(verboseOutput) + { + fmt::format_to(std::back_inserter(out), + "\n\tBoundary facet {} is not on the initial bounding box", + facetKeyString(facet_key)); + } + valid = false; + } + } + } + } + + if(verboseOutput) + { + if(valid) + { + SLIC_INFO("Delaunay mesh was conforming"); + } + else + { + SLIC_INFO("Delaunay mesh was NOT conforming. Summary: " << fmt::to_string(out)); + } + } + + return valid; +} + +template +inline void Delaunay::validateCavityBoundary(const InsertionHelper& insertion_helper) const +{ + if(m_insertion_validation_mode == InsertionValidationMode::None) + { + return; + } + + struct FacetInfo + { + IndexType neighbor_idx {INVALID_INDEX}; + bool matched {false}; + }; + + fmt::memory_buffer out; + bool valid = true; + std::map cavity_boundary; + + for(const auto& facet : insertion_helper.boundary_facets) + { + const FacetKey facet_key = makeSortedFaceKey(facet.vertices); + const auto insert_status = cavity_boundary.insert({facet_key, {facet.neighbor, false}}); + if(!insert_status.second) + { + fmt::format_to(std::back_inserter(out), + "\n\tCavity boundary facet {} was recorded more than once", + facetKeyString(facet_key)); + valid = false; + } + } + + for(const IndexType cavity_element : insertion_helper.cavity_elems) + { + const auto neighbors = m_mesh.adjacentElements(cavity_element); + for(int facet_idx = 0; facet_idx < VERT_PER_ELEMENT; ++facet_idx) + { + const IndexType neighbor_idx = neighbors[facet_idx]; + if(m_mesh.isValidElement(neighbor_idx) && insertion_helper.containsCavityElement(neighbor_idx)) + { + continue; + } + + const FacetKey facet_key = m_mesh.getSortedFacetKey(cavity_element, facet_idx); + auto facet_it = cavity_boundary.find(facet_key); + if(facet_it == cavity_boundary.end()) + { + fmt::format_to(std::back_inserter(out), + "\n\tCavity facet {} on element {} face {} is missing from the " + "boundary facet set", + facetKeyString(facet_key), + cavity_element, + facet_idx); + valid = false; + continue; + } + + if(facet_it->second.neighbor_idx != neighbor_idx) + { + fmt::format_to(std::back_inserter(out), + "\n\tCavity face {} expects neighbor {} but facet relation stores {}", + facetKeyString(facet_key), + neighbor_idx, + facet_it->second.neighbor_idx); + valid = false; + } + + if(!m_mesh.isValidElement(neighbor_idx) && m_has_boundary && !isFacetOnBoundingBox(facet_key)) + { + fmt::format_to(std::back_inserter(out), + "\n\tCavity boundary face {} exits the mesh away from the bounding box", + facetKeyString(facet_key)); + valid = false; + } + + facet_it->second.matched = true; + } + } + + for(const auto& facet_entry : cavity_boundary) + { + if(!facet_entry.second.matched) + { + fmt::format_to(std::back_inserter(out), + "\n\tFacet {} does not correspond to a cavity boundary face", + facetKeyString(facet_entry.first)); + valid = false; + } + } + + SLIC_ERROR_IF(!valid, + "Delaunay cavity validation failed after insertion " << m_num_insertions << ":" + << fmt::to_string(out)); +} + +template +inline void Delaunay::validateInsertedBall(IndexType new_pt_i, + const InsertionHelper& insertion_helper) const +{ + if(m_insertion_validation_mode == InsertionValidationMode::None) + { + return; + } + + struct BoundaryFacetInfo + { + IndexType neighbor_idx {INVALID_INDEX}; + bool matched {false}; + }; + + fmt::memory_buffer out; + bool valid = true; + std::map cavity_boundary; + std::map> inserted_faces; + auto findOppositeVertex = [&](IndexType element_idx, const FacetKey& facet_key) { + const auto verts = m_mesh.boundaryVertices(element_idx); + for(int i = 0; i < VERT_PER_ELEMENT; ++i) + { + bool on_face = false; + for(int j = 0; j < VERTS_PER_FACET; ++j) + { + on_face |= (verts[i] == facet_key[j]); + } + if(!on_face) + { + return verts[i]; + } + } + + return INVALID_INDEX; + }; + + if(insertion_helper.inserted_elems.size() != insertion_helper.boundary_facets.size()) + { + fmt::format_to( + std::back_inserter(out), + "\n\tInserted ball element count {} does not match cavity boundary facet count {}", + insertion_helper.inserted_elems.size(), + insertion_helper.boundary_facets.size()); + valid = false; + } + + if(insertion_helper.containing_element != INVALID_INDEX) + { + fmt::format_to(std::back_inserter(out), + "\n\tContaining element {} barycentric coordinates {}", + insertion_helper.containing_element, + insertion_helper.containing_bary); + if(!insertion_helper.seed_elements_debug.empty()) + { + fmt::format_to(std::back_inserter(out), + "\n\tInsertion seeds: [{}]", + fmt::join(insertion_helper.seed_elements_debug, ", ")); + } + } + + for(const auto& facet : insertion_helper.boundary_facets) + { + cavity_boundary.insert({makeSortedFaceKey(facet.vertices), {facet.neighbor, false}}); + } + + for(const IndexType element_idx : insertion_helper.inserted_elems) + { + if(!m_mesh.isValidElement(element_idx)) + { + fmt::format_to(std::back_inserter(out), "\n\tInserted element {} is invalid", element_idx); + valid = false; + continue; + } + + const auto verts = m_mesh.boundaryVertices(element_idx); + if(!slam::is_subset(new_pt_i, verts)) + { + fmt::format_to(std::back_inserter(out), + "\n\tInserted element {} does not contain the new vertex {}", + element_idx, + new_pt_i); + valid = false; + } + + const auto orient = evaluateElementOrientationDeterminant(element_idx); + if(orient.orientation != primal::ON_POSITIVE_SIDE) + { + fmt::format_to( + std::back_inserter(out), + "\n\tInserted element {} has non-positive orientation determinant {:.17g} (tol={:.3g})", + element_idx, + orient.det, + orient.tol); + valid = false; + } + + const auto neighbors = m_mesh.adjacentElements(element_idx); + for(int facet_idx = 0; facet_idx < VERT_PER_ELEMENT; ++facet_idx) + { + inserted_faces[m_mesh.getSortedFacetKey(element_idx, facet_idx)].push_back( + {element_idx, facet_idx, neighbors[facet_idx]}); + } + } + + for(auto& face_entry : inserted_faces) + { + auto cavity_it = cavity_boundary.find(face_entry.first); + auto& records = face_entry.second; + if(cavity_it != cavity_boundary.end()) + { + if(records.size() != 1) + { + fmt::format_to(std::back_inserter(out), + "\n\tBoundary face {} of the inserted ball is used by {} new elements", + facetKeyString(face_entry.first), + records.size()); + valid = false; + continue; + } + + if(records.front().neighbor_idx != cavity_it->second.neighbor_idx) + { + fmt::format_to(std::back_inserter(out), + "\n\tInserted boundary face {} points to neighbor {} instead of {}", + facetKeyString(face_entry.first), + records.front().neighbor_idx, + cavity_it->second.neighbor_idx); + valid = false; + } + + if(m_mesh.isValidElement(cavity_it->second.neighbor_idx)) + { + const IndexType inserted_opposite = + findOppositeVertex(records.front().element_idx, face_entry.first); + const IndexType neighbor_opposite = + findOppositeVertex(cavity_it->second.neighbor_idx, face_entry.first); + + if(inserted_opposite == INVALID_INDEX || neighbor_opposite == INVALID_INDEX) + { + fmt::format_to( + std::back_inserter(out), + "\n\tCould not identify opposite vertices across inserted boundary face {}", + facetKeyString(face_entry.first)); + valid = false; + } + else + { + const PointType& inserted_point = m_mesh.getVertexPosition(inserted_opposite); + const PointType& neighbor_point = m_mesh.getVertexPosition(neighbor_opposite); + + if(isPointInSphereOnMesh(m_mesh, + inserted_point, + cavity_it->second.neighbor_idx, + /*includeBoundary=*/false)) + { + const int query_in_neighbor = + inSphereOrientationOnMesh(m_mesh, + m_mesh.getVertexPosition(new_pt_i), + cavity_it->second.neighbor_idx); + const int inserted_in_neighbor = + inSphereOrientationOnMesh(m_mesh, inserted_point, cavity_it->second.neighbor_idx); + fmt::format_to( + std::back_inserter(out), + "\n\tInserted boundary face {} leaves new vertex {} inside neighbor {} " + "circumsphere (query {}={}, det={:.17g}; opposite {}={}, det={:.17g})", + facetKeyString(face_entry.first), + inserted_opposite, + cavity_it->second.neighbor_idx, + new_pt_i, + orientationResultName(query_in_neighbor), + inSphereDeterminantOnMesh(m_mesh, + m_mesh.getVertexPosition(new_pt_i), + cavity_it->second.neighbor_idx), + inserted_opposite, + orientationResultName(inserted_in_neighbor), + inSphereDeterminantOnMesh(m_mesh, inserted_point, cavity_it->second.neighbor_idx)); + valid = false; + } + + if(isPointInSphereOnMesh(m_mesh, + neighbor_point, + records.front().element_idx, + /*includeBoundary=*/false)) + { + const int query_in_neighbor = + inSphereOrientationOnMesh(m_mesh, + m_mesh.getVertexPosition(new_pt_i), + cavity_it->second.neighbor_idx); + const int neighbor_in_inserted = + inSphereOrientationOnMesh(m_mesh, neighbor_point, records.front().element_idx); + fmt::format_to( + std::back_inserter(out), + "\n\tInserted boundary face {} leaves neighbor vertex {} inside new element {} " + "circumsphere (query {} in neighbor {} is {}, det={:.17g}; opposite {} in new " + "element is {}, det={:.17g})", + facetKeyString(face_entry.first), + neighbor_opposite, + records.front().element_idx, + new_pt_i, + cavity_it->second.neighbor_idx, + orientationResultName(query_in_neighbor), + inSphereDeterminantOnMesh(m_mesh, + m_mesh.getVertexPosition(new_pt_i), + cavity_it->second.neighbor_idx), + neighbor_opposite, + orientationResultName(neighbor_in_inserted), + inSphereDeterminantOnMesh(m_mesh, neighbor_point, records.front().element_idx)); + valid = false; + } + } + } + + cavity_it->second.matched = true; + } + else if(records.size() != 2 || records[0].neighbor_idx != records[1].element_idx || + records[1].neighbor_idx != records[0].element_idx) + { + fmt::format_to(std::back_inserter(out), + "\n\tInternal face {} of the inserted ball has inconsistent adjacency", + facetKeyString(face_entry.first)); + valid = false; + } + else + { + const IndexType lhs_opposite = findOppositeVertex(records[0].element_idx, face_entry.first); + const IndexType rhs_opposite = findOppositeVertex(records[1].element_idx, face_entry.first); + + if(lhs_opposite == INVALID_INDEX || rhs_opposite == INVALID_INDEX) + { + fmt::format_to(std::back_inserter(out), + "\n\tCould not identify opposite vertices across inserted internal face {}", + facetKeyString(face_entry.first)); + valid = false; + } + else + { + const PointType& lhs_point = m_mesh.getVertexPosition(lhs_opposite); + const PointType& rhs_point = m_mesh.getVertexPosition(rhs_opposite); + + if(isPointInSphereOnMesh(m_mesh, lhs_point, records[1].element_idx, /*includeBoundary=*/false)) + { + const int query_in_rhs = inSphereOrientationOnMesh(m_mesh, + m_mesh.getVertexPosition(new_pt_i), + records[1].element_idx); + const int lhs_in_rhs = inSphereOrientationOnMesh(m_mesh, lhs_point, records[1].element_idx); + fmt::format_to( + std::back_inserter(out), + "\n\tInserted internal face {} leaves vertex {} inside adjacent element {} " + "circumsphere (query {}={}, det={:.17g}; opposite {}={}, det={:.17g})", + facetKeyString(face_entry.first), + lhs_opposite, + records[1].element_idx, + new_pt_i, + orientationResultName(query_in_rhs), + inSphereDeterminantOnMesh(m_mesh, + m_mesh.getVertexPosition(new_pt_i), + records[1].element_idx), + lhs_opposite, + orientationResultName(lhs_in_rhs), + inSphereDeterminantOnMesh(m_mesh, lhs_point, records[1].element_idx)); + valid = false; + } + + if(isPointInSphereOnMesh(m_mesh, rhs_point, records[0].element_idx, /*includeBoundary=*/false)) + { + const int query_in_lhs = inSphereOrientationOnMesh(m_mesh, + m_mesh.getVertexPosition(new_pt_i), + records[0].element_idx); + const int rhs_in_lhs = inSphereOrientationOnMesh(m_mesh, rhs_point, records[0].element_idx); + fmt::format_to( + std::back_inserter(out), + "\n\tInserted internal face {} leaves vertex {} inside adjacent element {} " + "circumsphere (query {}={}, det={:.17g}; opposite {}={}, det={:.17g})", + facetKeyString(face_entry.first), + rhs_opposite, + records[0].element_idx, + new_pt_i, + orientationResultName(query_in_lhs), + inSphereDeterminantOnMesh(m_mesh, + m_mesh.getVertexPosition(new_pt_i), + records[0].element_idx), + rhs_opposite, + orientationResultName(rhs_in_lhs), + inSphereDeterminantOnMesh(m_mesh, rhs_point, records[0].element_idx)); + valid = false; + } + } + } + } + + for(const auto& facet_entry : cavity_boundary) + { + if(!facet_entry.second.matched) + { + fmt::format_to(std::back_inserter(out), + "\n\tCavity boundary face {} was not covered by the inserted ball", + facetKeyString(facet_entry.first)); + valid = false; + } + } + + SLIC_ERROR_IF(!valid, + "Delaunay ball validation failed after insertion " << m_num_insertions << ":" + << fmt::to_string(out)); +} + +} // namespace quest +} // namespace axom + +#endif From bb6f2daa911d970cb94bc708c5dfd79b75d1661c Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 15 Apr 2026 11:47:56 -0700 Subject: [PATCH 511/986] Improves documentation of Delaunay classes Slight refactor of template specialization helpers. --- src/axom/quest/Delaunay.hpp | 50 +++++++-------- .../quest/detail/DelaunayElementFinder.hpp | 34 +++++++--- src/axom/quest/detail/DelaunayImpl.hpp | 64 +++++++++---------- .../quest/detail/DelaunayInsertionHelper.hpp | 14 ++++ .../quest/detail/DelaunayPointLocation.hpp | 6 ++ src/axom/quest/detail/DelaunayValidation.hpp | 6 ++ 6 files changed, 105 insertions(+), 69 deletions(-) diff --git a/src/axom/quest/Delaunay.hpp b/src/axom/quest/Delaunay.hpp index ad0bb77152..3579dd81dc 100644 --- a/src/axom/quest/Delaunay.hpp +++ b/src/axom/quest/Delaunay.hpp @@ -7,7 +7,7 @@ /** * \file Delaunay.hpp * - * \brief Defines an incremental 2D/3D Delaunay triangulation. + * \brief Declares the public `quest::Delaunay` incremental 2D/3D triangulation API. */ #ifndef QUEST_DELAUNAY_H_ @@ -50,11 +50,14 @@ namespace quest { /** - * \brief A class for incremental generation of a 2D or 3D Delaunay triangulation - * - * Construct a Delaunay triangulation incrementally by inserting points one by one. - * A bounding box of the points needs to be defined first via \a initializeBoundary(...) - */ + * \brief A class for incremental generation of a 2D or 3D Delaunay triangulation + * + * Construct a Delaunay triangulation incrementally by inserting points one by one. + * The public API lives in this header while the larger insertion, point-location, + * and validation routines are split into companion `detail/` headers. + * + * A bounding box of the points needs to be defined first via \a initializeBoundary(...). + */ template class Delaunay { @@ -320,27 +323,22 @@ class Delaunay */ void insertPoint(const PointType& new_pt); - template - typename std::enable_if::type getElement(int element_index) const - { - const auto verts = m_mesh.boundaryVertices(element_index); - const PointType& p0 = m_mesh.getVertexPosition(verts[0]); - const PointType& p1 = m_mesh.getVertexPosition(verts[1]); - const PointType& p2 = m_mesh.getVertexPosition(verts[2]); - - return ElementType(p0, p1, p2); - } - - template - typename std::enable_if::type getElement(int element_index) const + ElementType getElement(int element_index) const { const auto verts = m_mesh.boundaryVertices(element_index); - const PointType& p0 = m_mesh.getVertexPosition(verts[0]); - const PointType& p1 = m_mesh.getVertexPosition(verts[1]); - const PointType& p2 = m_mesh.getVertexPosition(verts[2]); - const PointType& p3 = m_mesh.getVertexPosition(verts[3]); - - return ElementType(p0, p1, p2, p3); + if constexpr(DIM == 2) + { + return ElementType(m_mesh.getVertexPosition(verts[0]), + m_mesh.getVertexPosition(verts[1]), + m_mesh.getVertexPosition(verts[2])); + } + else + { + return ElementType(m_mesh.getVertexPosition(verts[0]), + m_mesh.getVertexPosition(verts[1]), + m_mesh.getVertexPosition(verts[2]), + m_mesh.getVertexPosition(verts[3])); + } } /** @@ -447,8 +445,6 @@ class Delaunay static double orientationTolerance(const std::array& pts); - static double determinant3(const PointType& p0, const PointType& p1, const PointType& p2); - static double orientationDeterminant(const std::array& pts); static int symbolicOrientationSign(const std::array& pts, diff --git a/src/axom/quest/detail/DelaunayElementFinder.hpp b/src/axom/quest/detail/DelaunayElementFinder.hpp index 90eaf7a6fd..033d751436 100644 --- a/src/axom/quest/detail/DelaunayElementFinder.hpp +++ b/src/axom/quest/detail/DelaunayElementFinder.hpp @@ -4,6 +4,13 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +/** + * \file DelaunayElementFinder.hpp + * + * \brief Defines the spatial binning helper used to seed Delaunay point + * location walks from nearby inserted vertices. + */ + #ifndef AXOM_QUEST_DETAIL_DELAUNAY_ELEMENT_FINDER_HPP_ #define AXOM_QUEST_DETAIL_DELAUNAY_ELEMENT_FINDER_HPP_ @@ -21,6 +28,13 @@ namespace quest namespace detail { +/** + * \brief Maintains a coarse vertex binning structure for fast point-location seeds. + * + * The Delaunay walk still performs exact simplex traversal, but this helper + * keeps the starting simplex close to the query point by tracking one recent + * vertex representative per lattice bin. + */ template class DelaunayElementFinder { @@ -50,7 +64,7 @@ class DelaunayElementFinder m_lattice = spin::rectangular_lattice_from_bounding_box(expandedBB, NumericArrayType(res)); - resizeArray(res); + resizeArray(res); m_bins.fill(INVALID_INDEX); for(auto idx : verts.positions()) @@ -201,16 +215,16 @@ class DelaunayElementFinder return m_bins.flatIndex(idx); } - template - typename std::enable_if::type resizeArray(IndexType res) + void resizeArray(IndexType res) { - m_bins.resize(res, res); - } - - template - typename std::enable_if::type resizeArray(IndexType res) - { - m_bins.resize(res, res, res); + if constexpr(DIM == 2) + { + m_bins.resize(res, res); + } + else + { + m_bins.resize(res, res, res); + } } private: diff --git a/src/axom/quest/detail/DelaunayImpl.hpp b/src/axom/quest/detail/DelaunayImpl.hpp index 2fe587d1cb..85d6ba28ed 100644 --- a/src/axom/quest/detail/DelaunayImpl.hpp +++ b/src/axom/quest/detail/DelaunayImpl.hpp @@ -4,6 +4,12 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +/** + * \file DelaunayImpl.hpp + * + * \brief Defines the main incremental insertion and mesh-update routines for `quest::Delaunay`. + */ + #ifndef AXOM_QUEST_DETAIL_DELAUNAY_IMPL_HPP_ #define AXOM_QUEST_DETAIL_DELAUNAY_IMPL_HPP_ @@ -32,12 +38,6 @@ inline double Delaunay::orientationTolerance(const std::array } } -template -inline double Delaunay::determinant3(const PointType& p0, const PointType& p1, const PointType& p2) -{ - return axom::numerics::determinant(p0[0], p0[1], p0[2], p1[0], p1[1], p1[2], p2[0], p2[1], p2[2]); -} - template inline double Delaunay::orientationDeterminant(const std::array& pts) { @@ -63,6 +63,10 @@ template inline int Delaunay::symbolicOrientationSign(const std::array& pts, const std::array& ranks) { + auto determinant3 = [](const PointType& p0, const PointType& p1, const PointType& p2) { + return axom::numerics::determinant(p0[0], p0[1], p0[2], p1[0], p1[1], p1[2], p2[0], p2[1], p2[2]); + }; + const double det = orientationDeterminant(pts); const int det_sign = signWithTolerance(det, orientationTolerance(pts)); if(det_sign != 0) @@ -431,39 +435,35 @@ inline void Delaunay::insertPoint(const PointType& new_pt) } } -template <> -inline void Delaunay<2>::generateInitialMesh(std::vector& points, - std::vector& elem, - const BoundingBox& bb) +template +inline void Delaunay::generateInitialMesh(std::vector& points, + std::vector& elem, + const BoundingBox& bb) { const PointType& mins = bb.getMin(); const PointType& maxs = bb.getMax(); - std::vector pt {mins[0], mins[1], mins[0], maxs[1], maxs[0], mins[1], maxs[0], maxs[1]}; - - std::vector el {0, 2, 1, 3, 1, 2}; - - points.swap(pt); - elem.swap(el); -} - -template <> -inline void Delaunay<3>::generateInitialMesh(std::vector& points, - std::vector& elem, - const BoundingBox& bb) -{ - const PointType& mins = bb.getMin(); - const PointType& maxs = bb.getMax(); + if constexpr(DIM == 2) + { + std::vector pt {mins[0], mins[1], mins[0], maxs[1], maxs[0], mins[1], maxs[0], maxs[1]}; + std::vector el {0, 2, 1, 3, 1, 2}; - std::vector pt {mins[0], mins[1], mins[2], mins[0], mins[1], maxs[2], mins[0], maxs[1], - mins[2], mins[0], maxs[1], maxs[2], maxs[0], mins[1], mins[2], maxs[0], - mins[1], maxs[2], maxs[0], maxs[1], mins[2], maxs[0], maxs[1], maxs[2]}; + points.swap(pt); + elem.swap(el); + } + else + { + std::vector pt {mins[0], mins[1], mins[2], mins[0], mins[1], maxs[2], + mins[0], maxs[1], mins[2], mins[0], maxs[1], maxs[2], + maxs[0], mins[1], mins[2], maxs[0], mins[1], maxs[2], + maxs[0], maxs[1], mins[2], maxs[0], maxs[1], maxs[2]}; - std::vector el {3, 2, 4, 0, 3, 4, 1, 0, 3, 2, 6, 4, - 3, 6, 7, 4, 3, 5, 1, 4, 3, 7, 5, 4}; + std::vector el {3, 2, 4, 0, 3, 4, 1, 0, 3, 2, 6, 4, + 3, 6, 7, 4, 3, 5, 1, 4, 3, 7, 5, 4}; - points.swap(pt); - elem.swap(el); + points.swap(pt); + elem.swap(el); + } } } // namespace quest diff --git a/src/axom/quest/detail/DelaunayInsertionHelper.hpp b/src/axom/quest/detail/DelaunayInsertionHelper.hpp index 0fb0f37d37..4540ea902b 100644 --- a/src/axom/quest/detail/DelaunayInsertionHelper.hpp +++ b/src/axom/quest/detail/DelaunayInsertionHelper.hpp @@ -4,6 +4,13 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +/** + * \file DelaunayInsertionHelper.hpp + * + * \brief Defines the cavity-construction and retriangulation helper used by + * incremental Delaunay insertion. + */ + #ifndef AXOM_QUEST_DETAIL_DELAUNAY_INSERTION_HELPER_HPP_ #define AXOM_QUEST_DETAIL_DELAUNAY_INSERTION_HELPER_HPP_ @@ -21,6 +28,13 @@ namespace quest namespace detail { +/** + * \brief Tracks the local cavity state for a single Bowyer-Watson insertion. + * + * The owning `quest::Delaunay` instance reuses one helper across insertions so + * cavity membership, boundary facets, and inserted-element scratch storage can + * be cleared without reallocation on every point. + */ template class DelaunayInsertionHelper { diff --git a/src/axom/quest/detail/DelaunayPointLocation.hpp b/src/axom/quest/detail/DelaunayPointLocation.hpp index de81e9c37a..c31911fa2d 100644 --- a/src/axom/quest/detail/DelaunayPointLocation.hpp +++ b/src/axom/quest/detail/DelaunayPointLocation.hpp @@ -4,6 +4,12 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +/** + * \file DelaunayPointLocation.hpp + * + * \brief Defines the point-location helpers for `quest::Delaunay`. + */ + #ifndef AXOM_QUEST_DETAIL_DELAUNAY_POINT_LOCATION_HPP_ #define AXOM_QUEST_DETAIL_DELAUNAY_POINT_LOCATION_HPP_ diff --git a/src/axom/quest/detail/DelaunayValidation.hpp b/src/axom/quest/detail/DelaunayValidation.hpp index 65d20ed3d8..70d7f26c79 100644 --- a/src/axom/quest/detail/DelaunayValidation.hpp +++ b/src/axom/quest/detail/DelaunayValidation.hpp @@ -4,6 +4,12 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +/** + * \file DelaunayValidation.hpp + * + * \brief Defines optional insertion-time and post-build validation helpers for `quest::Delaunay`. + */ + #ifndef AXOM_QUEST_DETAIL_DELAUNAY_VALIDATION_HPP_ #define AXOM_QUEST_DETAIL_DELAUNAY_VALIDATION_HPP_ From 5856791face5131effa24af9af63c37c8e05e1f4 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 15 Apr 2026 13:28:59 -0700 Subject: [PATCH 512/986] Removes earlier fallback option in Delaunay that performs linear search No longer necessary w/ improved robustness. --- src/axom/quest/Delaunay.hpp | 27 +-- src/axom/quest/ScatteredInterpolation.hpp | 7 +- src/axom/quest/detail/DelaunayImpl.hpp | 6 +- .../quest/detail/DelaunayPointLocation.hpp | 159 ++---------------- src/axom/quest/tests/quest_delaunay.cpp | 34 ++++ 5 files changed, 52 insertions(+), 181 deletions(-) diff --git a/src/axom/quest/Delaunay.hpp b/src/axom/quest/Delaunay.hpp index 3579dd81dc..855eef7922 100644 --- a/src/axom/quest/Delaunay.hpp +++ b/src/axom/quest/Delaunay.hpp @@ -131,8 +131,6 @@ class Delaunay std::uint64_t walk_failed {0}; std::uint64_t total_walk_steps {0}; std::uint64_t max_walk_steps {0}; - std::uint64_t linear_fallbacks {0}; - std::uint64_t empty_seed_fallbacks {0}; double mean_walk_steps() const { @@ -173,10 +171,8 @@ class Delaunay using InsertionHelper = detail::DelaunayInsertionHelper; - // These broader fallbacks are only used for 3D query point location after the - // initial directed walk fails. Insertions stay on the cheaper local path. - static constexpr int QUERY_SEARCH_RADIUS = 6; - static constexpr int QUERY_CANDIDATE_LIMIT = 128; + // If a directed walk cycles or exhausts its local step budget, probe a small + // neighborhood around the visited simplices before reporting failure. static constexpr int WALK_NEIGHBORHOOD_LAYERS = 2; /** @@ -228,8 +224,6 @@ class Delaunay mutable std::uint64_t m_num_walk_failed {0}; mutable std::uint64_t m_total_walk_steps {0}; mutable std::uint64_t m_max_walk_steps {0}; - mutable std::uint64_t m_num_linear_fallbacks {0}; - mutable std::uint64_t m_num_empty_seed_fallbacks {0}; // Scratch buffers used by point location to avoid per-call heap allocations. // Delaunay is not thread-safe, so these are safe to reuse between calls. @@ -237,7 +231,6 @@ class Delaunay mutable std::vector m_walked_elements_scratch; mutable std::vector m_walk_local_elements_scratch; mutable std::vector m_initial_vertices_scratch; - mutable std::vector m_fallback_vertices_scratch; std::unique_ptr m_insertion_helper; public: @@ -261,9 +254,7 @@ class Delaunay m_num_walk_outside, m_num_walk_failed, m_total_walk_steps, - m_max_walk_steps, - m_num_linear_fallbacks, - m_num_empty_seed_fallbacks}; + m_max_walk_steps}; } /// \brief Controls the amount of validation performed around each point insertion @@ -565,22 +556,10 @@ class Delaunay std::size_t start_idx = 0, std::vector* walked_elements = nullptr) const; - PointLocationResult findContainingElementWithQueryFallbacks( - const PointType& query_pt, - std::vector& candidate_elements, - const std::vector& walked_elements) const; - /// \brief Scan a small adjacency region around a failed directed walk before falling back to a full scan IndexType findContainingElementFromNeighbors(const PointType& query_pt, const std::vector& seed_elements) const; - /// \brief Last-resort exhaustive scan used when the cheaper local search path cannot classify the point - IndexType findContainingElementLinear(const PointType& query_pt, bool warnOnInvalid) const; - - /// \brief Scan the stars of nearby seed vertices as a cheaper local fallback for query point location - IndexType findContainingElementNearby(const PointType& query_pt, - const std::vector& nearby_vertices) const; - /** * \brief Helper function to fill the array with the initial mesh. * \details create a rectangle for 2D, cube for 3D, and fill the array with the mesh data. diff --git a/src/axom/quest/ScatteredInterpolation.hpp b/src/axom/quest/ScatteredInterpolation.hpp index dd60bcfac1..91adc6146b 100644 --- a/src/axom/quest/ScatteredInterpolation.hpp +++ b/src/axom/quest/ScatteredInterpolation.hpp @@ -509,13 +509,10 @@ class ScatteredInterpolation stats.insertions)); const auto location_stats = m_delaunay.getPointLocationStats(); SLIC_INFO(axom::fmt::format( - "ScatteredInterpolation point location: walks {}, mean steps {:.2f}, max steps {}, " - "linear fallbacks {}, empty-seed fallbacks {}", + "ScatteredInterpolation point location: walks {}, mean steps {:.2f}, max steps {}", location_stats.walk_calls, location_stats.mean_walk_steps(), - location_stats.max_walk_steps, - location_stats.linear_fallbacks, - location_stats.empty_seed_fallbacks)); + location_stats.max_walk_steps)); } m_delaunay.removeBoundary(); diff --git a/src/axom/quest/detail/DelaunayImpl.hpp b/src/axom/quest/detail/DelaunayImpl.hpp index 85d6ba28ed..5c12564f03 100644 --- a/src/axom/quest/detail/DelaunayImpl.hpp +++ b/src/axom/quest/detail/DelaunayImpl.hpp @@ -346,19 +346,15 @@ inline void Delaunay::initializeBoundary(const BoundingBox& bb) m_num_walk_failed = 0; m_total_walk_steps = 0; m_max_walk_steps = 0; - m_num_linear_fallbacks = 0; - m_num_empty_seed_fallbacks = 0; m_candidate_elements_scratch.clear(); - m_candidate_elements_scratch.reserve(QUERY_CANDIDATE_LIMIT); + m_candidate_elements_scratch.reserve(16); m_walked_elements_scratch.clear(); m_walked_elements_scratch.reserve(256); m_walk_local_elements_scratch.clear(); m_walk_local_elements_scratch.reserve(256); m_initial_vertices_scratch.clear(); m_initial_vertices_scratch.reserve(8); - m_fallback_vertices_scratch.clear(); - m_fallback_vertices_scratch.reserve(QUERY_CANDIDATE_LIMIT); m_deleted_elements.clear(); m_deleted_elements.reserve(DIM == 2 ? 128 : 512); diff --git a/src/axom/quest/detail/DelaunayPointLocation.hpp b/src/axom/quest/detail/DelaunayPointLocation.hpp index c31911fa2d..c969997cb4 100644 --- a/src/axom/quest/detail/DelaunayPointLocation.hpp +++ b/src/axom/quest/detail/DelaunayPointLocation.hpp @@ -290,22 +290,6 @@ AXOM_QUEST_DELAUNAY_FORCE_INLINE void Delaunay::getInitialCandidateElements /*search_radius=*/1, /*max_candidates=*/8); appendCandidateElementsFromVertices(candidate_elements, m_initial_vertices_scratch); - - if(candidate_elements.empty()) - { - if(m_collect_location_stats) - { - ++m_num_empty_seed_fallbacks; - } - for(auto elem : m_mesh.elements().positions()) - { - if(isSearchableElement(elem)) - { - candidate_elements.push_back(elem); - break; - } - } - } } template @@ -330,44 +314,6 @@ Delaunay::walkCandidateElements(const PointType& query_pt, return {}; } -template -AXOM_QUEST_DELAUNAY_FORCE_INLINE typename Delaunay::PointLocationResult -Delaunay::findContainingElementWithQueryFallbacks(const PointType& query_pt, - std::vector& candidate_elements, - const std::vector& walked_elements) const -{ - const IndexType walk_region_elem = findContainingElementFromNeighbors(query_pt, walked_elements); - if(walk_region_elem != INVALID_INDEX) - { - return {walk_region_elem, PointLocationStatus::Found}; - } - - m_fallback_vertices_scratch.clear(); - m_element_finder.getNearbyVertices(m_mesh, - query_pt, - m_fallback_vertices_scratch, - QUERY_SEARCH_RADIUS, - QUERY_CANDIDATE_LIMIT); - const std::size_t initial_candidate_count = candidate_elements.size(); - candidate_elements.reserve(candidate_elements.size() + m_fallback_vertices_scratch.size()); - appendCandidateElementsFromVertices(candidate_elements, m_fallback_vertices_scratch); - - PointLocationResult walk_result = - walkCandidateElements(query_pt, candidate_elements, initial_candidate_count); - if(walk_result.status != PointLocationStatus::Failed) - { - return walk_result; - } - - const IndexType nearby_elem = findContainingElementNearby(query_pt, m_fallback_vertices_scratch); - if(nearby_elem != INVALID_INDEX) - { - return {nearby_elem, PointLocationStatus::Found}; - } - - return {}; -} - template AXOM_QUEST_DELAUNAY_FORCE_INLINE typename Delaunay::IndexType Delaunay::findContainingElementFromNeighbors(const PointType& query_pt, @@ -417,87 +363,7 @@ Delaunay::findContainingElementFromNeighbors(const PointType& query_pt, for(const IndexType element_idx : nearby_elements) { const BaryCoordType bary_coord = getBaryCoords(element_idx, query_pt); - const DataType min_bary = bary_coord[bary_coord.array().argMin()]; - if(min_bary >= -BARY_EPS) - { - return element_idx; - } - } - - return INVALID_INDEX; -} - -template -inline typename Delaunay::IndexType Delaunay::findContainingElementLinear( - const PointType& query_pt, - bool warnOnInvalid) const -{ - IndexType best_element = INVALID_INDEX; - DataType best_min_bary = -std::numeric_limits::max(); - - for(auto element_idx : m_mesh.elements().positions()) - { - if(!isSearchableElement(element_idx)) - { - continue; - } - - const BaryCoordType bary_coord = getBaryCoords(element_idx, query_pt); - const DataType min_bary = bary_coord[bary_coord.array().argMin()]; - if(min_bary > best_min_bary) - { - best_min_bary = min_bary; - best_element = element_idx; - } - - if(min_bary >= -BARY_EPS) - { - return element_idx; - } - } - - SLIC_WARNING_IF(warnOnInvalid, - fmt::format("Unable to locate containing element for point {} after exhaustive " - "neighbor search; returning closest candidate with min barycentric " - "coordinate {:.17g}", - query_pt, - best_min_bary)); - - return best_element; -} - -template -AXOM_QUEST_DELAUNAY_FORCE_INLINE typename Delaunay::IndexType -Delaunay::findContainingElementNearby(const PointType& query_pt, - const std::vector& nearby_vertices) const -{ - std::vector nearby_elements; - for(const IndexType vertex_idx : nearby_vertices) - { - if(!m_mesh.isValidVertex(vertex_idx)) - { - continue; - } - - const auto star = m_mesh.vertexStar(vertex_idx); - for(const IndexType elem : star) - { - if(isSearchableElement(elem)) - { - nearby_elements.push_back(elem); - } - } - } - - std::sort(nearby_elements.begin(), nearby_elements.end()); - nearby_elements.erase(std::unique(nearby_elements.begin(), nearby_elements.end()), - nearby_elements.end()); - - for(const IndexType element_idx : nearby_elements) - { - const BaryCoordType bary_coord = getBaryCoords(element_idx, query_pt); - const DataType min_bary = bary_coord[bary_coord.array().argMin()]; - if(min_bary >= -BARY_EPS) + if(isPointInsideForLocation(element_idx, query_pt, bary_coord)) { return element_idx; } @@ -539,23 +405,22 @@ inline typename Delaunay::IndexType Delaunay::findContainingElement(co return INVALID_INDEX; } - if(!m_candidate_elements_scratch.empty()) + if(!m_walked_elements_scratch.empty()) { - const PointLocationResult fallback_result = - findContainingElementWithQueryFallbacks(query_pt, - m_candidate_elements_scratch, - m_walked_elements_scratch); - if(fallback_result.status == PointLocationStatus::Found) + const IndexType walk_region_elem = + findContainingElementFromNeighbors(query_pt, m_walked_elements_scratch); + if(walk_region_elem != INVALID_INDEX) { - return fallback_result.element_idx; + return walk_region_elem; } } - if(m_collect_location_stats) - { - ++m_num_linear_fallbacks; - } - return findContainingElementLinear(query_pt, warnOnInvalid); + SLIC_WARNING_IF(warnOnInvalid, + fmt::format("Unable to locate containing element for point {} after directed " + "walk and local neighborhood search.", + query_pt)); + + return INVALID_INDEX; } } // namespace quest diff --git a/src/axom/quest/tests/quest_delaunay.cpp b/src/axom/quest/tests/quest_delaunay.cpp index 8f46aa4a60..0680c50233 100644 --- a/src/axom/quest/tests/quest_delaunay.cpp +++ b/src/axom/quest/tests/quest_delaunay.cpp @@ -214,6 +214,40 @@ TEST(quest_delaunay, boundary_location_regular_grid_2d) expectValidDelaunay(dt, points, 2 * (NX - 1) * (NY - 1)); } +TEST(quest_delaunay, query_location_regular_grid_2d) +{ + using PointType = typename DelaunayType<2>::PointType; + using BoundingBox = typename DelaunayType<2>::BoundingBox; + + DelaunayType<2> dt; + dt.initializeBoundary(BoundingBox(PointType {-0.5, -0.5}, PointType {1.5, 1.5})); + + std::vector points; + points.reserve(4 * 4); + for(int y = 0; y < 4; ++y) + { + for(int x = 0; x < 4; ++x) + { + points.push_back(PointType {x / 3., y / 3.}); + } + } + + insertPoints(dt, points); + dt.removeBoundary(); + + const std::vector queries {PointType {0.125, 0.125}, + PointType {0.42, 0.18}, + PointType {0.61, 0.27}, + PointType {0.33, 0.67}, + PointType {0.81, 0.55}, + PointType {0.24, 0.91}}; + + for(const auto& query : queries) + { + EXPECT_NE(DelaunayType<2>::INVALID_INDEX, dt.findContainingElement(query, false)); + } +} + TEST(quest_delaunay, cospherical_cube_3d) { using PointType = typename DelaunayType<3>::PointType; From bd81b8446c356d172f1fd4c7d2bbb7519414ea9e Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 15 Apr 2026 14:29:19 -0700 Subject: [PATCH 513/986] Removes functions that we're no longer using --- src/axom/quest/Delaunay.hpp | 25 -------- src/axom/quest/detail/DelaunayImpl.hpp | 88 -------------------------- 2 files changed, 113 deletions(-) diff --git a/src/axom/quest/Delaunay.hpp b/src/axom/quest/Delaunay.hpp index 855eef7922..bd55974d93 100644 --- a/src/axom/quest/Delaunay.hpp +++ b/src/axom/quest/Delaunay.hpp @@ -419,37 +419,12 @@ class Delaunay return value > tolerance ? 1 : (value < -tolerance ? -1 : 0); } - template - static double getPointMagnitudeScale(const std::array& pts) - { - double max_abs_coord = 1.; - for(const auto& pt : pts) - { - for(int dim = 0; dim < DIM; ++dim) - { - max_abs_coord = axom::utilities::max(max_abs_coord, axom::utilities::abs(pt[dim])); - } - } - - return max_abs_coord; - } - - static double orientationTolerance(const std::array& pts); - - static double orientationDeterminant(const std::array& pts); - - static int symbolicOrientationSign(const std::array& pts, - const std::array& ranks); - static CircumsphereEval evaluateCircumsphereOnMesh(const IAMeshType& mesh, IndexType element_idx); static double sphereSquaredDistanceTolerance(const CircumsphereEval& sphere, const PointType& x, double distance_sq); - template - static double sphereSignedDistanceTolerance(const SphereType& sphere, const PointType& x); - static int inSphereOrientationOnMesh(const IAMeshType& mesh, const PointType& q, IndexType element_idx); diff --git a/src/axom/quest/detail/DelaunayImpl.hpp b/src/axom/quest/detail/DelaunayImpl.hpp index 5c12564f03..6729654f3b 100644 --- a/src/axom/quest/detail/DelaunayImpl.hpp +++ b/src/axom/quest/detail/DelaunayImpl.hpp @@ -24,78 +24,6 @@ AXOM_QUEST_DELAUNAY_FORCE_INLINE bool Delaunay::isSearchableElement(IndexTy return m_mesh.isValidElement(element_idx); } -template -inline double Delaunay::orientationTolerance(const std::array& pts) -{ - const double scale = getPointMagnitudeScale(pts); - if constexpr(DIM == 2) - { - return 64. * std::numeric_limits::epsilon() * scale * scale; - } - else - { - return 64. * std::numeric_limits::epsilon() * scale * scale * scale; - } -} - -template -inline double Delaunay::orientationDeterminant(const std::array& pts) -{ - return axom::numerics::determinant(pts[0][0], - pts[0][1], - pts[0][2], - 1., - pts[1][0], - pts[1][1], - pts[1][2], - 1., - pts[2][0], - pts[2][1], - pts[2][2], - 1., - pts[3][0], - pts[3][1], - pts[3][2], - 1.); -} - -template -inline int Delaunay::symbolicOrientationSign(const std::array& pts, - const std::array& ranks) -{ - auto determinant3 = [](const PointType& p0, const PointType& p1, const PointType& p2) { - return axom::numerics::determinant(p0[0], p0[1], p0[2], p1[0], p1[1], p1[2], p2[0], p2[1], p2[2]); - }; - - const double det = orientationDeterminant(pts); - const int det_sign = signWithTolerance(det, orientationTolerance(pts)); - if(det_sign != 0) - { - return det_sign; - } - - const std::array cofactors {-determinant3(pts[1], pts[2], pts[3]), - determinant3(pts[0], pts[2], pts[3]), - -determinant3(pts[0], pts[1], pts[3]), - determinant3(pts[0], pts[1], pts[2])}; - - std::array order {{0, 1, 2, 3}}; - std::sort(order.begin(), order.end(), [&](int lhs, int rhs) { return ranks[lhs] < ranks[rhs]; }); - - const double cofactor_tol = 64. * std::numeric_limits::epsilon() * - axom::utilities::max(1., orientationTolerance(pts)); - for(const int row : order) - { - const int sign = signWithTolerance(cofactors[row], cofactor_tol); - if(sign != 0) - { - return sign; - } - } - - return 0; -} - template AXOM_QUEST_DELAUNAY_FORCE_INLINE typename Delaunay::CircumsphereEval Delaunay::evaluateCircumsphereOnMesh(const IAMeshType& mesh, IndexType element_idx) @@ -180,22 +108,6 @@ AXOM_QUEST_DELAUNAY_FORCE_INLINE double Delaunay::sphereSquaredDistanceTole return 256. * std::numeric_limits::epsilon() * scale * local_span; } -template -template -inline double Delaunay::sphereSignedDistanceTolerance(const SphereType& sphere, - const PointType& x) -{ - const auto& center = sphere.getCenter(); - double scale = axom::utilities::max(1., sphere.getRadius()); - for(int dim = 0; dim < DIM; ++dim) - { - scale = axom::utilities::max(scale, axom::utilities::abs(center[dim])); - scale = axom::utilities::max(scale, axom::utilities::abs(x[dim])); - } - - return 256. * std::numeric_limits::epsilon() * scale; -} - template AXOM_QUEST_DELAUNAY_FORCE_INLINE int Delaunay::inSphereOrientationOnMesh(const IAMeshType& mesh, const PointType& q, From 96a7c37ce7bf69b8e5bf5738c44838cfe5be7f3b Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 15 Apr 2026 15:21:39 -0700 Subject: [PATCH 514/986] Updates/improves documentation of Delaunay classes --- src/axom/quest/Delaunay.hpp | 189 +++++++++++++----- .../quest/detail/DelaunayElementFinder.hpp | 26 +++ src/axom/quest/detail/DelaunayImpl.hpp | 9 + .../quest/detail/DelaunayInsertionHelper.hpp | 13 ++ .../quest/detail/DelaunayPointLocation.hpp | 5 + src/axom/quest/detail/DelaunayValidation.hpp | 12 ++ 6 files changed, 202 insertions(+), 52 deletions(-) diff --git a/src/axom/quest/Delaunay.hpp b/src/axom/quest/Delaunay.hpp index bd55974d93..677d08b300 100644 --- a/src/axom/quest/Delaunay.hpp +++ b/src/axom/quest/Delaunay.hpp @@ -57,12 +57,22 @@ namespace quest * and validation routines are split into companion `detail/` headers. * * A bounding box of the points needs to be defined first via \a initializeBoundary(...). + * The algorithm uses the Bowyer-Watson incremental insertion approach with robust + * geometric predicates for handling degenerate cases including regular grids and + * co-spherical point configurations. + * + * \note This class is not thread-safe. Multiple concurrent insertions are not supported. + * + * \tparam DIM The spatial dimension (2 for triangulation, 3 for tetrahedralization) */ template class Delaunay { public: AXOM_STATIC_ASSERT_MSG(DIM == 2 || DIM == 3, "The template parameter DIM can only be 2 or 3. "); + + /// Tolerance for barycentric coordinate comparisons (distinguishes interior from boundary) + /// Value chosen to handle typical floating-point error accumulation in coordinate computation static constexpr double BARY_EPS = 1e-12; using DataType = double; @@ -84,9 +94,10 @@ class Delaunay static constexpr int VERTS_PER_FACET = VERT_PER_ELEMENT - 1; static constexpr IndexType INVALID_INDEX = -1; + /// Controls the level of validation performed during point insertion enum class InsertionValidationMode { - /// No additional insertion-time validation beyond existing asserts. + /// No additional insertion-time validation beyond existing asserts (production mode). None, /// Checks only insertion-local seed/cavity/ball invariants. Local, @@ -94,28 +105,33 @@ class Delaunay /// Intended for debugging and unit tests. ConformingMesh, /// Additionally runs a global empty-circumsphere check after each insertion (very expensive). + /// Use only for deep debugging; unsuitable for large meshes. Full }; + /// Result status from point location queries enum class PointLocationStatus { - Found, - Outside, - Failed + Found, ///< Query point was successfully located inside an element + Outside, ///< Query point lies outside the triangulation boundary + Failed ///< Location failed (internal error or degenerate case) }; + /// Combined result from point location including element index and status struct PointLocationResult { - IndexType element_idx {INVALID_INDEX}; - PointLocationStatus status {PointLocationStatus::Failed}; + IndexType element_idx {INVALID_INDEX}; ///< Index of containing element (or INVALID_INDEX) + PointLocationStatus status {PointLocationStatus::Failed}; ///< Status of the location query }; + /// Statistics about point insertion operations (cavity size during Bowyer-Watson insertion) struct InsertionStats { - std::uint64_t insertions {0}; - std::uint64_t total_removed {0}; - std::uint64_t max_removed {0}; + std::uint64_t insertions {0}; ///< Total number of points inserted + std::uint64_t total_removed {0}; ///< Cumulative elements removed across all insertions + std::uint64_t max_removed {0}; ///< Maximum elements removed in a single insertion + /// Average number of elements removed per insertion (cavity size) double mean_removed() const { return insertions > 0 ? static_cast(total_removed) / static_cast(insertions) @@ -123,15 +139,17 @@ class Delaunay } }; + /// Statistics about point location query performance struct PointLocationStats { - std::uint64_t walk_calls {0}; - std::uint64_t walk_found {0}; - std::uint64_t walk_outside {0}; - std::uint64_t walk_failed {0}; - std::uint64_t total_walk_steps {0}; - std::uint64_t max_walk_steps {0}; - + std::uint64_t walk_calls {0}; ///< Number of walk attempts + std::uint64_t walk_found {0}; ///< Walks that successfully found containing element + std::uint64_t walk_outside {0}; ///< Walks that terminated outside boundary + std::uint64_t walk_failed {0}; ///< Walks that failed (should be rare with robust implementation) + std::uint64_t total_walk_steps {0}; ///< Cumulative simplex-to-simplex steps across all walks + std::uint64_t max_walk_steps {0}; ///< Maximum steps in a single walk + + /// Average number of simplex traversals per walk double mean_walk_steps() const { return walk_calls > 0 ? static_cast(total_walk_steps) / static_cast(walk_calls) @@ -139,24 +157,28 @@ class Delaunay } }; + /// Precomputed circumsphere center and squared radius for in-sphere tests struct CircumsphereEval { - PointType center {}; - double radius_sq {0.}; + PointType center {}; ///< Circumsphere center point + double radius_sq {0.}; ///< Squared radius (avoids sqrt in distance comparisons) CircumsphereEval() = default; + /// Construct from origin point and offset vector to center CircumsphereEval(const PointType& origin, const VectorType& center_offset) : center(origin + center_offset) , radius_sq(center_offset.squared_norm()) { } }; + /// Orientation determinant evaluation with tolerance for robust geometric tests struct OrientationEval { - double det {0.}; - double tol {0.}; - int orientation {primal::ON_BOUNDARY}; + double det {0.}; ///< Raw determinant value + double tol {0.}; ///< Context-aware tolerance for this determinant + int orientation { + primal::ON_BOUNDARY}; ///< Classified orientation (ON_NEGATIVE_SIDE/ON_BOUNDARY/ON_POSITIVE_SIDE) }; private: @@ -171,8 +193,9 @@ class Delaunay using InsertionHelper = detail::DelaunayInsertionHelper; - // If a directed walk cycles or exhausts its local step budget, probe a small - // neighborhood around the visited simplices before reporting failure. + /// Number of adjacency layers to search around visited simplices when a directed walk fails. + /// If a walk cycles or reaches its step budget, we probe this many layers of neighbors + /// before giving up. Value of 2 balances coverage vs. cost for typical meshes. static constexpr int WALK_NEIGHBORHOOD_LAYERS = 2; /** @@ -240,13 +263,21 @@ class Delaunay */ Delaunay() : m_has_boundary(false), m_insertion_validation_mode(InsertionValidationMode::None) { } + /// \brief Returns statistics about insertion operations + /// \return InsertionStats containing cavity size metrics InsertionStats getInsertionStats() const { return {m_num_insertions, m_total_removed_elements, m_max_removed_elements}; } + /// \brief Enable or disable collection of point location statistics + /// \param enabled If true, track walk performance metrics (adds minimal overhead) + /// \note Stats collection is disabled by default void setCollectPointLocationStats(bool enabled) { m_collect_location_stats = enabled; } + /// \brief Returns statistics about point location query performance + /// \return PointLocationStats containing walk performance metrics + /// \note Returns zeros if setCollectPointLocationStats(true) was not called PointLocationStats getPointLocationStats() const { return {m_num_walk_calls, @@ -271,7 +302,10 @@ class Delaunay /** * \brief Defines the boundary of the triangulation. - * \details subsequent points added to the triangulation must not be outside of this boundary. + * \details Subsequent points added to the triangulation must lie within this boundary. + * Creates an initial bounding box triangulation (2 triangles in 2D, 6 tetrahedra in 3D). + * \param bb The bounding box that will contain all inserted points + * \pre Must be called before any points are inserted */ void initializeBoundary(const BoundingBox& bb); @@ -279,6 +313,9 @@ class Delaunay /// /// Uses a dimension-specific heuristic for the total simplex count so large /// bulk-builds can avoid repeated mesh-container reallocations. + /// \param num_points Expected number of points to be inserted + /// \note Based on Euler characteristic: 2D expects ~2n triangles for n points, + /// 3D expects ~6n tetrahedra. Heuristics account for bounding box and over-tessellation. void reserveForPointCount(IndexType num_points) { if(!m_has_boundary || num_points <= 0) @@ -287,6 +324,9 @@ class Delaunay } const IndexType expected_vertices = m_mesh.vertices().size() + num_points; + + // Heuristic element count: 2D triangle count ≈ 2n (Euler), 3D tet count ≈ 6n (Euler). + // Use conservative estimates (3.0 and 9.0) to account for boundary and over-tessellation. constexpr double ELEMENTS_PER_POINT = DIM == 2 ? 3.0 : 9.0; const IndexType expected_elements = m_mesh.elements().size() + static_cast(std::ceil(ELEMENTS_PER_POINT * static_cast(num_points))); @@ -304,16 +344,25 @@ class Delaunay } /** - * \brief Adds a new point and locally re-triangulates the mesh to ensure that it stays Delaunay + * \brief Adds a new point and locally re-triangulates the mesh to ensure it remains Delaunay * - * This function will traverse the mesh to find the element that contains - * this point, creates the Delaunay cavity, which takes out all the elements - * that contains the point in its sphere, and fill it with a Delaunay ball. + * Uses the Bowyer-Watson incremental insertion algorithm: + * 1. Locate the element containing the new point via directed walk + * 2. Expand a cavity of all elements whose circumspheres contain the new point + * 3. Retriangulate the cavity by connecting the new point to the cavity boundary * - * \pre The current mesh must already be Delaunay. + * \param new_pt The point to insert + * \pre initializeBoundary() must have been called + * \pre new_pt must lie within the bounding box + * \pre The current mesh must be a valid Delaunay triangulation + * \post The mesh remains a valid Delaunay triangulation + * \note If insertion fails (rare), a warning is logged and the mesh is unchanged */ void insertPoint(const PointType& new_pt); + /// \brief Retrieves the geometric element (triangle or tetrahedron) at the given index + /// \param element_index The index of the element to retrieve + /// \return Triangle (2D) or Tetrahedron (3D) with vertex coordinates ElementType getElement(int element_index) const { const auto verts = m_mesh.boundaryVertices(element_index); @@ -338,66 +387,98 @@ class Delaunay void printMesh() { m_mesh.print_all(); } /** - * \brief Write the m_mesh to a legacy VTK file + * \brief Write the mesh to a legacy VTK file for visualization * - * \param filename The name of the file to write to, + * \param filename The name of the file to write to * \note The suffix ".vtk" will be appended to the provided filename - * \details This function uses mint to write the m_mesh to VTK format. + * \note This method compacts the mesh before export to eliminate deleted elements */ void writeToVTKFile(const std::string& filename); /** - * \brief Removes the vertices that defines the boundary of the mesh, - * and the elements attached to them. + * \brief Removes the bounding box vertices and all attached elements + * + * The initial bounding box (created by initializeBoundary) consists of 2^DIM vertices + * that define a rectangular boundary. This method removes those vertices and all + * simplices incident to them, leaving only the Delaunay triangulation of the inserted points. * - * \details After this function is called, no more points can be added to the m_mesh. + * \post No more points can be inserted after calling this method + * \post The mesh is compacted (deleted element slots are removed) */ void removeBoundary(); - /// \brief Get the IA mesh data pointer + /// \brief Get the underlying IA mesh data structure + /// \return Pointer to the IAMesh (for advanced users or ScatteredInterpolation) const IAMeshType* getMeshData() const { return &m_mesh; } /** - * \brief Checks that the underlying mesh is a valid Delaunay triangulation of the point set + * \brief Checks that the mesh satisfies the Delaunay empty-circumsphere property * - * A Delaunay triangulation is valid when none of the vertices are inside the circumspheres - * of any of the elements of the mesh + * A Delaunay triangulation is valid when no vertex lies strictly inside the + * circumsphere of any simplex. This method checks all vertex-simplex pairs. + * + * \param verboseOutput If true, prints detailed diagnostic information + * \return true if the Delaunay property holds, false otherwise + * \note This is an O(n·m) check where n = vertices, m = elements. Use for validation. */ bool isValid(bool verboseOutput = false) const; /** - * \brief Checks that the underlying mesh is conforming and consistently oriented + * \brief Checks mesh conformity, orientation, and boundary consistency + * + * Verifies three properties: + * 1. Topological conformity (manifold facets, reciprocal adjacencies) via IAMesh::isConforming() + * 2. All simplices are positively oriented (positive signed volume/area) + * 3. If boundary exists, all boundary facets lie on the bounding box faces * - * Topological conformity (manifold facets and reciprocal adjacencies) is - * delegated to `slam::IAMesh::isConforming()`. This routine additionally - * verifies that the simplices remain positively oriented, and (when the - * initial bounding-box boundary is still present) that boundary facets lie on - * that bounding box. + * \param verboseOutput If true, prints detailed diagnostic information + * \return true if all checks pass, false otherwise */ bool isConforming(bool verboseOutput = false) const; - /// \brief Returns true when an element slot is active and can participate in point-location + /// \brief Returns true if an element is active and can participate in point-location queries /// - /// Point-location only needs to reject tombstones here. During incremental - /// insertion all active simplices retain valid vertices, and `removeBoundary()` - /// compacts before post-build query use. + /// \param element_idx The element index to check + /// \return true if element is valid (not a deleted/recycled slot) + /// \note During insertion, all active elements have valid vertices. After removeBoundary(), + /// the mesh is compacted so all element indices are valid. bool isSearchableElement(IndexType element_idx) const; - /// \brief Find the index of the element that contains the query point, or the element closest to the point. + /// \brief Locate the element containing a query point using directed walk + /// + /// \param query_pt The point to locate + /// \param warnOnInvalid If true, log a warning if location fails + /// \return Element index containing the point, or INVALID_INDEX if not found + /// \note Uses grid-seeded directed walk for O(n^(1/DIM)) expected performance IndexType findContainingElement(const PointType& query_pt, bool warnOnInvalid = true) const; /** - * \brief helper function to retrieve the barycentric coordinate of the query point in the element + * \brief Compute barycentric coordinates of a query point within an element + * + * \param element_idx The element containing (or near) the query point + * \param q_pt The query point + * \return Barycentric coordinates (DIM+1 values that sum to 1) + * \note If the point is inside, all coordinates are non-negative (within tolerance) */ BaryCoordType getBaryCoords(IndexType element_idx, const PointType& q_pt) const; - /// \brief Returns cavity seed elements based on the simplex feature containing the query point + /// \brief Returns initial seed elements for cavity expansion based on point location + /// + /// If the query point lies on a face/edge of the containing element (indicated by a + /// barycentric coordinate near zero), the neighbor across that face is also included + /// as a seed. This ensures the cavity expansion starts from all elements that might + /// contain the point in their circumsphere. + /// + /// \param element_idx The element containing (or nearest to) the query point + /// \param bary_coord Barycentric coordinates of the query point in that element + /// \return Array of seed element indices (at least 1, possibly more if point is on boundary feature) IndexArray getSeedElements(IndexType element_idx, const BaryCoordType& bary_coord) const { IndexArray seed_elements; seed_elements.push_back(element_idx); constexpr IndexType invalid_element = IAMeshType::INVALID_ELEMENT_INDEX; + // If query point lies on a facet (bary coord approximately 0), include the neighbor across that facet for(int i = 0; i < VERT_PER_ELEMENT; ++i) { if(axom::utilities::abs(bary_coord[i]) <= BARY_EPS) @@ -414,6 +495,10 @@ class Delaunay return seed_elements; } + /// \brief Classify a value as positive, negative, or zero within tolerance + /// \param value The value to classify + /// \param tolerance The tolerance for considering the value as zero + /// \return +1 if value > tolerance, -1 if value < -tolerance, 0 otherwise static int signWithTolerance(double value, double tolerance) { return value > tolerance ? 1 : (value < -tolerance ? -1 : 0); diff --git a/src/axom/quest/detail/DelaunayElementFinder.hpp b/src/axom/quest/detail/DelaunayElementFinder.hpp index 033d751436..3c81ec634d 100644 --- a/src/axom/quest/detail/DelaunayElementFinder.hpp +++ b/src/axom/quest/detail/DelaunayElementFinder.hpp @@ -34,6 +34,10 @@ namespace detail * The Delaunay walk still performs exact simplex traversal, but this helper * keeps the starting simplex close to the query point by tracking one recent * vertex representative per lattice bin. + * + * The grid adapts to point set size: resolution is O(n^(1/DIM)) to maintain + * constant expected bin occupancy. This provides O(1) expected distance (in hops) + * from grid cell to query point's containing simplex. */ template class DelaunayElementFinder @@ -44,6 +48,11 @@ class DelaunayElementFinder explicit DelaunayElementFinder() = default; + /// \brief Rebuild the spatial bin structure based on current vertex positions + /// + /// \param mesh The current Delaunay mesh + /// \param bb The bounding box of the triangulation + /// \note Grid resolution adapts to vertex count: ~n^(1/DIM) / 4 bins per dimension void recomputeGrid(const IAMeshType& mesh, const BoundingBox& bb) { const auto& verts = mesh.vertices(); @@ -54,6 +63,7 @@ class DelaunayElementFinder // even for very large point sets. // // Target occupancy is ~ 4^DIM points per bin (16 in 2D, 64 in 3D). + // Value of 4.0 is empirically chosen to balance grid overhead vs. walk distance. constexpr double BIN_SIDE_SPACING = 4.0; const double res_root = std::pow(static_cast(verts.size()), 1.0 / DIM); const IndexType res = @@ -90,6 +100,13 @@ class DelaunayElementFinder } } + /// \brief Find vertices in bins near a query point, sorted by distance + /// + /// \param mesh The current Delaunay mesh + /// \param pt The query point + /// \param[out] nearby_vertices Output array of vertex indices sorted by distance to pt + /// \param search_radius Number of bin layers to search (1 = immediate neighbors, 2 = two layers, etc.) + /// \param max_candidates Maximum number of vertices to return inline void getNearbyVertices(const IAMeshType& mesh, const PointType& pt, std::vector& nearby_vertices, @@ -188,12 +205,21 @@ class DelaunayElementFinder } } + /// \brief Get the vertex stored in the bin containing a query point + /// + /// \param pt The query point + /// \return Vertex index of a representative vertex in that bin (or INVALID_INDEX if bin is empty) inline IndexType getNearbyVertex(const PointType& pt) const { const auto cell = m_lattice.gridCell(pt); return flatIndex(cell); } + /// \brief Update a bin to reference a newly inserted vertex + /// + /// \param pt The position of the newly inserted vertex + /// \param vertex_id The index of the newly inserted vertex + /// \note Called after each successful point insertion inline void updateBin(const PointType& pt, IndexType vertex_id) { const auto cell = m_lattice.gridCell(pt); diff --git a/src/axom/quest/detail/DelaunayImpl.hpp b/src/axom/quest/detail/DelaunayImpl.hpp index 6729654f3b..edf0c5b773 100644 --- a/src/axom/quest/detail/DelaunayImpl.hpp +++ b/src/axom/quest/detail/DelaunayImpl.hpp @@ -8,6 +8,15 @@ * \file DelaunayImpl.hpp * * \brief Defines the main incremental insertion and mesh-update routines for `quest::Delaunay`. + * + * Implements: + * - Circumsphere evaluation (center and squared radius from element vertices) + * - In-sphere orientation tests (used during Bowyer-Watson cavity expansion) + * - Mesh initialization (creates bounding box triangulation) + * - Point insertion (coordinates InsertionHelper to build cavity and ball) + * - Boundary removal (eliminates bounding box vertices post-construction) + * - Mesh compaction (removes deleted element slots) + * - VTK export for visualization */ #ifndef AXOM_QUEST_DETAIL_DELAUNAY_IMPL_HPP_ diff --git a/src/axom/quest/detail/DelaunayInsertionHelper.hpp b/src/axom/quest/detail/DelaunayInsertionHelper.hpp index 4540ea902b..4e6011e39f 100644 --- a/src/axom/quest/detail/DelaunayInsertionHelper.hpp +++ b/src/axom/quest/detail/DelaunayInsertionHelper.hpp @@ -9,6 +9,13 @@ * * \brief Defines the cavity-construction and retriangulation helper used by * incremental Delaunay insertion. + * + * Implements the Bowyer-Watson algorithm for incremental point insertion: + * 1. findCavityElements(): Expands cavity from seed elements using circumsphere test + * 2. createCavity(): Removes cavity elements from the mesh + * 3. delaunayBall(): Retriangulates by connecting new point to cavity boundary + * + * The helper is reused across insertions to avoid repeated allocations. */ #ifndef AXOM_QUEST_DETAIL_DELAUNAY_INSERTION_HELPER_HPP_ @@ -34,6 +41,12 @@ namespace detail * The owning `quest::Delaunay` instance reuses one helper across insertions so * cavity membership, boundary facets, and inserted-element scratch storage can * be cleared without reallocation on every point. + * + * Public members track insertion state for validation: + * - cavity_elems: Elements whose circumspheres contain the new point + * - boundary_facets: Faces between cavity and non-cavity elements + * - inserted_elems: New simplices created by connecting new point to boundary + * - containing_element, containing_bary, seed_elements_debug: For diagnostics */ template class DelaunayInsertionHelper diff --git a/src/axom/quest/detail/DelaunayPointLocation.hpp b/src/axom/quest/detail/DelaunayPointLocation.hpp index c969997cb4..ba69291fbe 100644 --- a/src/axom/quest/detail/DelaunayPointLocation.hpp +++ b/src/axom/quest/detail/DelaunayPointLocation.hpp @@ -8,6 +8,11 @@ * \file DelaunayPointLocation.hpp * * \brief Defines the point-location helpers for `quest::Delaunay`. + * + * Implements grid-seeded directed walk for locating query points within the + * triangulation. The walk traverses simplices via facet adjacencies, moving + * toward the query point until it is found or the walk exits the boundary. + * Fallback strategies handle edge cases (cycles, numerical issues). */ #ifndef AXOM_QUEST_DETAIL_DELAUNAY_POINT_LOCATION_HPP_ diff --git a/src/axom/quest/detail/DelaunayValidation.hpp b/src/axom/quest/detail/DelaunayValidation.hpp index 70d7f26c79..37aaff0616 100644 --- a/src/axom/quest/detail/DelaunayValidation.hpp +++ b/src/axom/quest/detail/DelaunayValidation.hpp @@ -8,6 +8,18 @@ * \file DelaunayValidation.hpp * * \brief Defines optional insertion-time and post-build validation helpers for `quest::Delaunay`. + * + * Implements four-tier validation system: + * - None: Production mode (asserts only) + * - Local: Validates insertion-local cavity/ball invariants + * - ConformingMesh: Adds topological conformity and orientation checks + * - Full: Adds global empty-circumsphere validation (expensive, for debugging) + * + * Also provides: + * - isValid(): Global Delaunay property check (empty circumsphere for all simplices) + * - isConforming(): Topological conformity and orientation checks + * - Determinant evaluation helpers with context-aware tolerances + * - Boundary coordinate tolerance computation */ #ifndef AXOM_QUEST_DETAIL_DELAUNAY_VALIDATION_HPP_ From ee9c0ede2879cc9fa92877f2fa45f8553405904c Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 15 Apr 2026 17:16:10 -0700 Subject: [PATCH 515/986] Oursources the FacetPair map from IA's fixConnectivity implementation to its own class And adds comments and tests. --- src/axom/quest/detail/DelaunayValidation.hpp | 11 +- src/axom/slam/mesh_struct/IA.hpp | 14 + src/axom/slam/mesh_struct/IA_impl.hpp | 349 ++++++----- .../mesh_struct/detail/FacetPairingMap.hpp | 419 +++++++++++++ src/axom/slam/tests/CMakeLists.txt | 3 +- .../tests/slam_detail_FacetPairingMap.cpp | 570 ++++++++++++++++++ 6 files changed, 1184 insertions(+), 182 deletions(-) create mode 100644 src/axom/slam/mesh_struct/detail/FacetPairingMap.hpp create mode 100644 src/axom/slam/tests/slam_detail_FacetPairingMap.cpp diff --git a/src/axom/quest/detail/DelaunayValidation.hpp b/src/axom/quest/detail/DelaunayValidation.hpp index 37aaff0616..9079369110 100644 --- a/src/axom/quest/detail/DelaunayValidation.hpp +++ b/src/axom/quest/detail/DelaunayValidation.hpp @@ -467,11 +467,12 @@ inline bool Delaunay::isConforming(bool verboseOutput) const { if(verboseOutput) { - fmt::format_to(std::back_inserter(out), - "\n\tElement {} has non-positive orientation determinant {:.17g} (tol={:.3g})", - element_idx, - orient.det, - orient.tol); + fmt::format_to( + std::back_inserter(out), + "\n\tElement {} has non-positive orientation determinant {:.17g} (tol={:.3g})", + element_idx, + orient.det, + orient.tol); } valid = false; } diff --git a/src/axom/slam/mesh_struct/IA.hpp b/src/axom/slam/mesh_struct/IA.hpp index 6869289e1a..9bf7808fa2 100644 --- a/src/axom/slam/mesh_struct/IA.hpp +++ b/src/axom/slam/mesh_struct/IA.hpp @@ -31,6 +31,8 @@ #include "axom/slam/DynamicMap.hpp" #include "axom/slam/FieldRegistry.hpp" +#include "axom/slam/mesh_struct/detail/FacetPairingMap.hpp" + #include namespace axom @@ -413,6 +415,18 @@ class IAMesh IndexType element_i, IndexType side_i); + // Helper methods for fixVertexNeighborhood + static constexpr std::size_t getBoundaryBase(IndexType element_idx); + static constexpr std::size_t getFaceOffset(IndexType element_idx, int face_idx); + static constexpr int skippedVertexToFace(int skipped_vertex_idx); + static constexpr int getVertexPositionInFace(int face_idx); + + typename detail::FacetPairingMap::KeyType createFacetKey( + const IndexType* element_vertices, + int face_idx) const; + + int findNeighborFaceIndex(IndexType neighbor_idx, const IndexType* boundary_vertices) const; + private: VertexSet vertex_set; //Set of vertices ElementSet element_set; //Set of elements diff --git a/src/axom/slam/mesh_struct/IA_impl.hpp b/src/axom/slam/mesh_struct/IA_impl.hpp index 21f632c1d2..57bbaccace 100644 --- a/src/axom/slam/mesh_struct/IA_impl.hpp +++ b/src/axom/slam/mesh_struct/IA_impl.hpp @@ -17,6 +17,7 @@ #include "axom/core/StaticArray.hpp" #include "axom/slam/policies/SizePolicies.hpp" #include "axom/slam/ModularInt.hpp" +#include "axom/slam/mesh_struct/detail/FacetPairingMap.hpp" #include "axom/fmt.hpp" @@ -629,229 +630,225 @@ typename IAMesh::IndexType IAMesh::reuseElement(In return element_idx; } +//============================================================================== +// Helper methods for fixVertexNeighborhood +//============================================================================== + +/// \brief Get the base offset for an element's boundary vertices in the flat array template -void IAMesh::fixVertexNeighborhood(IndexType vertex_idx, - const std::vector& new_elements) +constexpr std::size_t IAMesh::getBoundaryBase(IndexType element_idx) { - constexpr IndexType EMPTY_SLOT = INVALID_ELEMENT_INDEX; - constexpr IndexType TOMBSTONE_SLOT = INVALID_ELEMENT_INDEX - 1; + return static_cast(element_idx) * VERTS_PER_ELEM; +} - struct PendingFace - { - IndexType key0 {INVALID_VERTEX_INDEX}; - IndexType key1 {INVALID_VERTEX_INDEX}; - IndexType element_idx {EMPTY_SLOT}; - IndexType face_idx {INVALID_ELEMENT_INDEX}; - unsigned int generation {0u}; - }; +/// \brief Get the flat offset for a face in the element-element adjacency array +template +constexpr std::size_t IAMesh::getFaceOffset(IndexType element_idx, int face_idx) +{ + return static_cast(element_idx) * VERTS_PER_ELEM + face_idx; +} - static thread_local std::vector pending_faces; - static thread_local unsigned int pending_generation = 0u; - if(++pending_generation == 0u) - { - for(auto& pending : pending_faces) - { - pending.generation = 0u; - } - pending_generation = 1u; - } +/// \brief Convert skipped vertex index to face index (modular arithmetic) +template +constexpr int IAMesh::skippedVertexToFace(int skipped_vertex_idx) +{ + return (skipped_vertex_idx + 1) % VERTS_PER_ELEM; +} + +/// \brief Get the vertex position in a face (opposite vertex for simplicial meshes) +template +constexpr int IAMesh::getVertexPositionInFace(int face_idx) +{ + return (face_idx == 0) ? VERTS_PER_ELEM - 1 : face_idx - 1; +} + +/** + * \brief Create a facet key for a given face of an element. + * + * In 2D (triangles): Returns the single non-shared vertex + * In 3D (tetrahedra): Returns the two non-shared vertices (will be auto-sorted by FacetKey) + * + * \param element_vertices Pointer to the element's boundary vertices + * \param face_idx Local face index (1..VERTS_PER_ELEM-1, face 0 is boundary) + * \return FacetKey identifying this face + */ +template +typename detail::FacetPairingMap::IndexType>::KeyType +IAMesh::createFacetKey(const IndexType* element_vertices, int face_idx) const +{ + using KeyType = typename detail::FacetPairingMap::KeyType; - std::size_t table_size = 8; - const std::size_t target_slots = - std::max(8, (TDIM == 2 ? 4 : 8) * new_elements.size()); - while(table_size < target_slots) + if constexpr(TDIM == 2) { - table_size <<= 1; + // 2D: face is identified by the single non-shared vertex + SLIC_ASSERT(face_idx == 1 || face_idx == 2); + return KeyType(face_idx == 1 ? element_vertices[1] : element_vertices[0]); } - if(pending_faces.size() < table_size) + else { - pending_faces.resize(table_size); + // 3D: face is identified by the two non-shared vertices + SLIC_ASSERT(face_idx >= 1 && face_idx <= 3); + IndexType key0, key1; + switch(face_idx) + { + case 1: + key0 = element_vertices[1]; + key1 = element_vertices[2]; + break; + case 2: + key0 = element_vertices[2]; + key1 = element_vertices[0]; + break; + default: // face 3 + key0 = element_vertices[0]; + key1 = element_vertices[1]; + break; + } + return KeyType(key0, key1); // Auto-sorted by FacetKey constructor } +} - auto pendingFaceHash = [](IndexType key0, IndexType key1) -> std::size_t { - std::size_t seed = static_cast(key0); - seed ^= static_cast(key1) + 0x9e3779b97f4a7c15ULL + (seed << 6) + (seed >> 2); - return seed; - }; - - auto getFaceOffset = [](IndexType element_idx, int face_idx) { - return static_cast(element_idx) * VERTS_PER_ELEM + face_idx; - }; - - auto getBoundaryBase = [](IndexType element_idx) { - return static_cast(element_idx) * VERTS_PER_ELEM; - }; - - auto skippedVertexToFace = [](int skipped_vertex_idx) { - return (skipped_vertex_idx + 1) % VERTS_PER_ELEM; - }; - - const auto& ev_data = ev_rel.data(); - auto& ee_data = ee_rel.data(); - const IndexType* const ev_ptr = ev_data.data(); +/** + * \brief Find which face of a neighbor element corresponds to a given boundary face. + * + * Given an element's boundary vertices, find which face of the neighbor shares those vertices. + * + * \param neighbor_idx Index of the neighbor element + * \param boundary_vertices Pointer to the current element's boundary vertices + * \return Local face index on the neighbor element + */ +template +int IAMesh::findNeighborFaceIndex(IndexType neighbor_idx, + const IndexType* boundary_vertices) const +{ + const IndexType* const neighbor_vertices = ev_rel.data().data() + getBoundaryBase(neighbor_idx); - auto getBoundaryNeighborFace = [&](IndexType nbr, const IndexType* bdry) { - const IndexType* const nbr_bdry = ev_ptr + getBoundaryBase(nbr); + if constexpr(TDIM == 2) + { + // In 2D, find which vertex of the neighbor is not in {v0, v1} + const IndexType v0 = boundary_vertices[0]; + const IndexType v1 = boundary_vertices[1]; - if constexpr(TDIM == 2) + for(int i = 0; i < VERTS_PER_ELEM; ++i) { - const IndexType v0 = bdry[0]; - const IndexType v1 = bdry[1]; - - if(nbr_bdry[0] != v0 && nbr_bdry[0] != v1) - { - return skippedVertexToFace(0); - } - if(nbr_bdry[1] != v0 && nbr_bdry[1] != v1) + if(neighbor_vertices[i] != v0 && neighbor_vertices[i] != v1) { - return skippedVertexToFace(1); + return skippedVertexToFace(i); } - - SLIC_ASSERT(nbr_bdry[2] != v0 && nbr_bdry[2] != v1); - return skippedVertexToFace(2); } - else - { - const IndexType v0 = bdry[0]; - const IndexType v1 = bdry[1]; - const IndexType v2 = bdry[2]; - if(nbr_bdry[0] != v0 && nbr_bdry[0] != v1 && nbr_bdry[0] != v2) - { - return skippedVertexToFace(0); - } - if(nbr_bdry[1] != v0 && nbr_bdry[1] != v1 && nbr_bdry[1] != v2) - { - return skippedVertexToFace(1); - } - if(nbr_bdry[2] != v0 && nbr_bdry[2] != v1 && nbr_bdry[2] != v2) + SLIC_ASSERT_MSG(false, "Failed to find neighbor face in 2D"); + return -1; + } + else + { + // In 3D, find which vertex of the neighbor is not in {v0, v1, v2} + const IndexType v0 = boundary_vertices[0]; + const IndexType v1 = boundary_vertices[1]; + const IndexType v2 = boundary_vertices[2]; + + for(int i = 0; i < VERTS_PER_ELEM; ++i) + { + if(neighbor_vertices[i] != v0 && neighbor_vertices[i] != v1 && neighbor_vertices[i] != v2) { - return skippedVertexToFace(2); + return skippedVertexToFace(i); } - - SLIC_ASSERT(nbr_bdry[3] != v0 && nbr_bdry[3] != v1 && nbr_bdry[3] != v2); - return skippedVertexToFace(3); } - }; - auto getPendingFaceKey = [](const IndexType* bdry, int face_idx) { - PendingFace key; + SLIC_ASSERT_MSG(false, "Failed to find neighbor face in 3D"); + return -1; + } +} - if constexpr(TDIM == 2) - { - SLIC_ASSERT(face_idx == 1 || face_idx == 2); - key.key0 = (face_idx == 1) ? bdry[1] : bdry[0]; - } - else - { - SLIC_ASSERT(face_idx >= 1 && face_idx <= 3); - switch(face_idx) - { - case 1: - key.key0 = bdry[1]; - key.key1 = bdry[2]; - break; - case 2: - key.key0 = bdry[2]; - key.key1 = bdry[0]; - break; - default: - key.key0 = bdry[0]; - key.key1 = bdry[1]; - break; - } +//============================================================================== +// fixVertexNeighborhood - Refactored to use FacetPairingMap +//============================================================================== - if(key.key1 < key.key0) - { - axom::utilities::swap(key.key0, key.key1); - } - } +template +void IAMesh::fixVertexNeighborhood(IndexType vertex_idx, + const std::vector& new_elements) +{ + using FacetMap = detail::FacetPairingMap; + using FacetKey = typename FacetMap::KeyType; + using FacetData = typename FacetMap::DataType; - return key; - }; + // Prepare hash table for expected facet count + FacetMap facet_map; + facet_map.prepareForInsertions(new_elements.size()); - int num_pending_faces = 0; + const auto& ev_data = ev_rel.data(); + auto& ee_data = ee_rel.data(); + const IndexType* const ev_ptr = ev_data.data(); + + // Statistics tracking (for validation) + int num_boundary_faces = 0; int num_incident_faces = 0; - for(auto el : new_elements) + // Process all faces in the new elements + for(IndexType element_idx : new_elements) { - const std::size_t el_base = getBoundaryBase(el); - const IndexType* const bdry = ev_ptr + el_base; + const IndexType* const element_vertices = ev_ptr + getBoundaryBase(element_idx); - for(int face_i = 0; face_i < VERTS_PER_ELEM; ++face_i) + for(int local_face_idx = 0; local_face_idx < VERTS_PER_ELEM; ++local_face_idx) { - // This face is either a boundary facet of the star... - const auto vert_i = (face_i == 0) ? VERTS_PER_ELEM - 1 : face_i - 1; - if(bdry[vert_i] == vertex_idx) + const int vertex_position = getVertexPositionInFace(local_face_idx); + + // Check if this face contains the vertex we're fixing + if(element_vertices[vertex_position] == vertex_idx) { - SLIC_ASSERT(face_i == 0); - SLIC_ASSERT(vert_i == VERTS_PER_ELEM - 1); + // This is a BOUNDARY face of the vertex star + // (face 0, which is opposite the last vertex) + ++num_boundary_faces; - // figure out which face this is on the neighbor - // and update neighbor's adjacency to point to current element - const IndexType nbr = ee_data[getFaceOffset(el, face_i)]; - if(nbr != INVALID_ELEMENT_INDEX) + // Update neighbor's adjacency to point back to this element + const IndexType neighbor = ee_data[getFaceOffset(element_idx, local_face_idx)]; + if(neighbor != INVALID_ELEMENT_INDEX) { - SLIC_ASSERT(element_set.isValidEntry(nbr)); - const int nbr_face = getBoundaryNeighborFace(nbr, bdry); - const std::size_t nbr_face_offset = getFaceOffset(nbr, nbr_face); - SLIC_ASSERT(ee_data[nbr_face_offset] == INVALID_ELEMENT_INDEX || - ee_data[nbr_face_offset] == el); - ee_data[nbr_face_offset] = el; + SLIC_ASSERT(element_set.isValidEntry(neighbor)); + + // Find which face of the neighbor shares these vertices + const int neighbor_face_idx = findNeighborFaceIndex(neighbor, element_vertices); + const std::size_t neighbor_face_offset = getFaceOffset(neighbor, neighbor_face_idx); + + // Update or verify neighbor's adjacency + SLIC_ASSERT(ee_data[neighbor_face_offset] == INVALID_ELEMENT_INDEX || + ee_data[neighbor_face_offset] == element_idx); + ee_data[neighbor_face_offset] = element_idx; } } - // ... or it is incident in the common vertex: vertex_idx else { - const PendingFace face = getPendingFaceKey(bdry, face_i); - const std::size_t mask = pending_faces.size() - 1; - std::size_t slot = pendingFaceHash(face.key0, face.key1) & mask; - std::size_t insert_slot = pending_faces.size(); + // This is an INCIDENT face (touches vertex_idx but doesn't have it opposite) + ++num_incident_faces; - for(;; slot = (slot + 1) & mask) - { - auto& pending = pending_faces[slot]; - if(pending.generation != pending_generation) - { - auto& insert_entry = - pending_faces[(insert_slot == pending_faces.size()) ? slot : insert_slot]; - insert_entry = face; - insert_entry.element_idx = el; - insert_entry.face_idx = face_i; - insert_entry.generation = pending_generation; - ++num_pending_faces; - break; - } + // Create facet key for matching + const FacetKey face_key = createFacetKey(element_vertices, local_face_idx); - if(pending.element_idx == TOMBSTONE_SLOT) - { - if(insert_slot == pending_faces.size()) - { - insert_slot = slot; - } - continue; - } + // Try to find matching facet from another element + if(auto match = facet_map.findAndRemove(face_key)) + { + // Found the matching face - update both adjacencies + SLIC_ASSERT_MSG(match->element_idx != element_idx, + "Each face in the inserted star should be shared by two elements"); - if(pending.key0 == face.key0 && pending.key1 == face.key1) - { - SLIC_ASSERT_MSG(pending.element_idx != el, - "Each face in the inserted star should be shared by two elements"); - ee_data[getFaceOffset(pending.element_idx, pending.face_idx)] = el; - ee_data[getFaceOffset(el, face_i)] = pending.element_idx; - pending.element_idx = TOMBSTONE_SLOT; - --num_pending_faces; - break; - } + ee_data[getFaceOffset(match->element_idx, match->face_idx)] = element_idx; + ee_data[getFaceOffset(element_idx, local_face_idx)] = match->element_idx; + } + else + { + // First time seeing this facet - store it for later matching + FacetData face_data(element_idx, local_face_idx); + facet_map.insert(face_key, face_data); } - ++num_incident_faces; } } } + // Validate star topology AXOM_UNUSED_VAR(num_incident_faces); - AXOM_UNUSED_VAR(num_pending_faces); + AXOM_UNUSED_VAR(num_boundary_faces); SLIC_ASSERT(num_incident_faces == static_cast(new_elements.size()) * TDIM); - SLIC_ASSERT_MSG(num_pending_faces == 0, + SLIC_ASSERT_MSG(facet_map.allFacetsPaired(), "All faces in the inserted star should be paired exactly once"); } diff --git a/src/axom/slam/mesh_struct/detail/FacetPairingMap.hpp b/src/axom/slam/mesh_struct/detail/FacetPairingMap.hpp new file mode 100644 index 0000000000..35a72d0857 --- /dev/null +++ b/src/axom/slam/mesh_struct/detail/FacetPairingMap.hpp @@ -0,0 +1,419 @@ +// 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_SLAM_MESH_STRUCT_DETAIL_FACET_PAIRING_MAP_HPP_ +#define AXOM_SLAM_MESH_STRUCT_DETAIL_FACET_PAIRING_MAP_HPP_ + +/** + * \file FacetPairingMap.hpp + * + * \brief Specialized hash table for matching pairs of facets during mesh connectivity repair. + * + * This hash table is used by IAMesh::fixVertexNeighborhood() to efficiently match + * facets between elements in a vertex star. It uses Robin Hood open addressing with + * generational marking to provide O(1) expected performance without allocations. + * + * ## Algorithm: Robin Hood Open Addressing with Generational Marking + * + * **Generational Marking:** Each entry stores a generation counter. Entries with + * generation != current_generation are treated as empty without actually clearing them. + * This avoids O(n) clear operations between uses. + * + * **Generation Wraparound:** On 32-bit wraparound (every ~4 billion calls), we reset + * all entry generations to 0 and restart at 1. This is the only O(n) operation in + * the table's lifetime. + * + * **Tombstone Markers:** Deleted entries are marked with a special TOMBSTONE value + * to preserve probe chains. Tombstones can be reused for new insertions. + * + * **Thread-Local Storage:** Uses static thread_local storage to amortize allocation + * cost across many operations. This provides zero-allocation operation after the + * first resize. + * + * ## Performance Characteristics + * + * - **Lookup:** O(1) expected + * - **Insert:** O(1) expected + * - **Space:** 4x-8x oversizing for ~12.5-25% load factor + * - **Allocations:** Zero after first use (thread-local reuse) + * + * ## Usage Pattern + * + * ```cpp + * FacetPairingMap map; + * map.prepareForInsertions(expected_facet_count); + * + * for (each facet in new elements): + * if (auto match = map.findAndRemove(facet_key)) + * // Found matching facet - update both adjacencies + * updateAdjacencies(facet, match.value()); + * else + * // First time seeing this facet - store it + * map.insert(facet_key, facet_data); + * + * assert(map.allFacetsPaired()); // Validate all facets matched + * ``` + */ + +#include "axom/core.hpp" +#include "axom/slic.hpp" + +#include +#include +#include +#include + +namespace axom +{ +namespace slam +{ +namespace detail +{ + +/** + * \brief Facet key for 2D (edge) or 3D (triangle) face matching. + * + * In 2D (triangles): key0 is the single non-shared vertex, key1 is unused + * In 3D (tetrahedra): (key0, key1) are the two non-shared vertices in sorted order + * + * The key is normalized so that equivalent facets from different elements + * produce identical keys for matching. + */ +template +struct FacetKey +{ + static constexpr IndexType INVALID = static_cast(-1); + + IndexType key0 {INVALID}; ///< First key component (only component in 2D) + IndexType key1 {INVALID}; ///< Second key component (unused in 2D) + + /// Default constructor - creates invalid key + FacetKey() = default; + + /// Construct from key components (automatically sorted in 3D) + /// \param k0 First key component + /// \param k1 Second key component (unused in 2D, sorted in 3D) + FacetKey(IndexType k0, IndexType k1 = INVALID) : key0(k0), key1(k1) + { + // In 3D, ensure consistent ordering: key0 <= key1 + // This allows facets from different elements to match + if constexpr(TDIM == 3) + { + if(k1 < k0) + { + axom::utilities::swap(key0, key1); + } + } + } + + /// Equality comparison + bool operator==(const FacetKey& other) const { return key0 == other.key0 && key1 == other.key1; } + + /// Inequality comparison + bool operator!=(const FacetKey& other) const { return !(*this == other); } +}; + +/** + * \brief Facet data stored in the hash table during face matching. + * + * Associates a facet key with the element that owns it and the local + * face index within that element. + */ +template +struct FacetData +{ + static constexpr IndexType INVALID = static_cast(-1); + + IndexType element_idx {INVALID}; ///< Element owning this facet + IndexType face_idx {INVALID}; ///< Local face index (0..VERTS_PER_ELEM-1) + + /// Default constructor + FacetData() = default; + + /// Construct from element and face indices + FacetData(IndexType elem, IndexType face) : element_idx(elem), face_idx(face) { } +}; + +/** + * \brief Thread-local hash table for matching facets during mesh connectivity repair. + * + * This class implements a specialized hash table optimized for the facet-pairing + * problem in mesh connectivity repair. It provides: + * - O(1) expected lookup and insert + * - Zero allocations after first use (thread-local storage) + * - Generational marking to avoid clearing + * - Tombstone deletion to maintain probe chains + * + * \tparam TDIM Topological dimension (2 for triangles, 3 for tetrahedra) + * \tparam IndexType Index type for element and face indices (typically int) + */ +template +class FacetPairingMap +{ +public: + using KeyType = FacetKey; + using DataType = FacetData; + + static constexpr IndexType INVALID = static_cast(-1); + static constexpr IndexType EMPTY_SLOT = INVALID; + static constexpr IndexType TOMBSTONE_SLOT = INVALID - 1; + + /// Golden ratio constant for Knuth's multiplicative hashing + /// This constant (phi * 2^64) provides excellent bit distribution + static constexpr std::uint64_t HASH_MULTIPLIER = 0x9e3779b97f4a7c15ULL; + +private: + /// Hash table entry combining key, data, and generation tracking + struct Entry + { + KeyType key; ///< Facet key for matching + DataType data; ///< Associated element/face data + unsigned int generation {0}; ///< Generation marker for fast clearing + + Entry() = default; + }; + + // Thread-local storage for zero-allocation operation across calls + static thread_local std::vector s_table; + static thread_local unsigned int s_generation; + + std::size_t m_mask {0}; ///< Table size - 1 (for fast modulo via bitwise AND) + int m_pending_count {0}; ///< Number of unpaired facets currently in table + +public: + /// Default constructor + FacetPairingMap() = default; + + /** + * \brief Prepare the hash table for an expected number of facet insertions. + * + * This method sizes the table appropriately and advances the generation counter. + * It should be called before each batch of insertions. + * + * The table is sized to maintain a low load factor (~12.5-25%) for O(1) performance. + * In 2D, each element contributes 2 incident faces (face 0 is on boundary). + * In 3D, each element contributes 3 incident faces (face 0 is on boundary). + * + * \param expected_facet_count Estimated number of facets to insert + * + * \note Table size is rounded up to the next power of 2 for fast modulo via bitwise AND. + */ + void prepareForInsertions(std::size_t expected_facet_count) + { + // Heuristic sizing to maintain low load factor: + // - 2D: ~2 incident faces per element, 4x oversizing = 50% load + // - 3D: ~3 incident faces per element, 8/3x oversizing ≈ 37.5% load + // These values are tuned for performance based on empirical testing. + const std::size_t target_slots = + axom::utilities::max(8, (TDIM == 2 ? 4 : 8) * expected_facet_count); + + // Round up to power of 2 for fast modulo via bitwise AND + std::size_t table_size = 8; + while(table_size < target_slots) + { + table_size <<= 1; + } + + // Resize if needed (reuses existing allocation if possible) + if(s_table.size() < table_size) + { + s_table.resize(table_size); + } + + m_mask = s_table.size() - 1; + m_pending_count = 0; + + // Advance generation counter (handles wraparound) + advanceGeneration(); + } + + /** + * \brief Try to find and remove a matching facet from the table. + * + * If a match is found, it's removed (marked as tombstone) and its data returned. + * If no match exists, returns empty optional. + * + * \param key The facet key to search for + * \return Optional containing matching facet data, or empty if not found + * + * \note This uses linear probing with generational marking. Empty slots + * (including stale generations) terminate the search. + */ + std::optional findAndRemove(const KeyType& key) + { + const std::size_t hash = computeHash(key); + std::size_t slot = hash & m_mask; + + // Linear probe until we find the key or an empty slot + while(true) + { + Entry& entry = s_table[slot]; + + // Empty slot (including stale generation) - key not present + if(entry.generation != s_generation) + { + return std::nullopt; + } + + // Tombstone - continue searching (must maintain probe chain) + if(entry.data.element_idx == TOMBSTONE_SLOT) + { + slot = (slot + 1) & m_mask; + continue; + } + + // Found matching key - remove it and return data + if(entry.key == key) + { + DataType result = entry.data; + entry.data.element_idx = TOMBSTONE_SLOT; // Mark as deleted + --m_pending_count; + return result; + } + + // Key mismatch - continue probing + slot = (slot + 1) & m_mask; + } + } + + /** + * \brief Insert a new facet into the table. + * + * Should only be called after findAndRemove() returns empty for this key. + * Calling insert() for an already-present key is an error (assertion). + * + * \param key The facet key + * \param data The facet data (element index and face index) + * + * \note This uses linear probing with tombstone reuse. If a tombstone is + * encountered during probing, it will be reused for insertion. + */ + void insert(const KeyType& key, const DataType& data) + { + const std::size_t hash = computeHash(key); + std::size_t slot = hash & m_mask; + std::size_t first_tombstone = s_table.size(); // Track first tombstone for reuse + + while(true) + { + Entry& entry = s_table[slot]; + + // Empty slot (including stale generation) - insert here (or at tombstone if found) + if(entry.generation != s_generation) + { + Entry& target = (first_tombstone < s_table.size()) ? s_table[first_tombstone] : entry; + target.key = key; + target.data = data; + target.generation = s_generation; + ++m_pending_count; + return; + } + + // Tombstone - remember it for potential reuse + if(entry.data.element_idx == TOMBSTONE_SLOT) + { + if(first_tombstone == s_table.size()) + { + first_tombstone = slot; + } + slot = (slot + 1) & m_mask; + continue; + } + + // Collision with existing key - this is an error + SLIC_ASSERT_MSG(entry.key != key, + "FacetPairingMap::insert() called with duplicate key. " + "Each facet should appear exactly once per element."); + + // Different key - continue probing + slot = (slot + 1) & m_mask; + } + } + + /// \brief Returns the number of unpaired facets currently in the table + /// \return Number of facets waiting for a match + int pendingCount() const { return m_pending_count; } + + /** + * \brief Validates that all facets were successfully paired. + * + * Should be called after all insertions complete. A non-zero pending count + * indicates a topological error (non-manifold vertex or broken adjacency). + * + * \return true if all facets paired successfully (pending count is zero) + */ + bool allFacetsPaired() const { return m_pending_count == 0; } + + /// \brief Returns the current generation counter value (for testing) + unsigned int currentGeneration() const { return s_generation; } + + /// \brief Returns the table size (for testing/diagnostics) + std::size_t tableSize() const { return s_table.size(); } + + /// \brief Returns the table mask (for testing/diagnostics) + std::size_t tableMask() const { return m_mask; } + +private: + /** + * \brief Compute hash for a facet key using Knuth's multiplicative method. + * + * Uses the golden ratio constant (phi * 2^64) which provides excellent + * bit distribution. In 3D, the two key components are mixed together. + * + * \param key The facet key to hash + * \return Hash value + */ + std::size_t computeHash(const KeyType& key) const + { + std::size_t seed = static_cast(key.key0); + + if constexpr(TDIM == 3) + { + // Mix in second key component using bit shifting and XOR + // This formula provides good avalanche properties + seed ^= static_cast(key.key1) + HASH_MULTIPLIER + (seed << 6) + (seed >> 2); + } + + return seed; + } + + /** + * \brief Advance the generation counter, handling wraparound. + * + * On wraparound (every ~4 billion calls), resets all entries to generation 0 + * and restarts at 1. This is the only O(n) operation in the table's lifetime. + * + * Wraparound is extremely rare in practice (would require billions of + * prepareForInsertions() calls), but this ensures correctness. + */ + void advanceGeneration() + { + if(++s_generation == 0u) + { + // Generation counter wrapped around - reset all entries + // This is rare but ensures correctness + for(auto& entry : s_table) + { + entry.generation = 0u; + } + s_generation = 1u; + } + } +}; + +// Static member initialization +template +thread_local std::vector::Entry> + FacetPairingMap::s_table; + +template +thread_local unsigned int FacetPairingMap::s_generation = 0; + +} // namespace detail +} // namespace slam +} // namespace axom + +#endif // AXOM_SLAM_MESH_STRUCT_DETAIL_FACET_PAIRING_MAP_HPP_ diff --git a/src/axom/slam/tests/CMakeLists.txt b/src/axom/slam/tests/CMakeLists.txt index cd5468622b..b4b514c62a 100644 --- a/src/axom/slam/tests/CMakeLists.txt +++ b/src/axom/slam/tests/CMakeLists.txt @@ -40,9 +40,10 @@ set(gtest_slam_tests # aux tests slam_ModularInt.cpp - + #mesh structure test slam_IA.cpp + slam_detail_FacetPairingMap.cpp ) diff --git a/src/axom/slam/tests/slam_detail_FacetPairingMap.cpp b/src/axom/slam/tests/slam_detail_FacetPairingMap.cpp new file mode 100644 index 0000000000..02e6688c0e --- /dev/null +++ b/src/axom/slam/tests/slam_detail_FacetPairingMap.cpp @@ -0,0 +1,570 @@ +// 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/slam/mesh_struct/detail/FacetPairingMap.hpp" + +#include + +using namespace axom; +using namespace axom::slam; + +//------------------------------------------------------------------------------ +// FacetKey Tests +//------------------------------------------------------------------------------ + +TEST(slam_detail_FacetKey, construction_2d) +{ + using KeyType = axom::slam::detail::FacetKey<2, int>; + + // Default construction + KeyType key1; + EXPECT_EQ(key1.key0, KeyType::INVALID); + EXPECT_EQ(key1.key1, KeyType::INVALID); + + // Construction with single key (2D) + KeyType key2(42); + EXPECT_EQ(key2.key0, 42); + EXPECT_EQ(key2.key1, KeyType::INVALID); + + // key1 parameter should be ignored in 2D + KeyType key3(42, 100); + EXPECT_EQ(key3.key0, 42); + // key1 is present but not used for matching in 2D +} + +TEST(slam_detail_FacetKey, construction_3d) +{ + using KeyType = axom::slam::detail::FacetKey<3, int>; + + // Default construction + KeyType key1; + EXPECT_EQ(key1.key0, KeyType::INVALID); + EXPECT_EQ(key1.key1, KeyType::INVALID); + + // Construction with sorted keys + KeyType key2(10, 20); + EXPECT_EQ(key2.key0, 10); + EXPECT_EQ(key2.key1, 20); + + // Construction with unsorted keys (should auto-sort) + KeyType key3(20, 10); + EXPECT_EQ(key3.key0, 10); // Sorted + EXPECT_EQ(key3.key1, 20); // Sorted +} + +TEST(slam_detail_FacetKey, equality_2d) +{ + using KeyType = axom::slam::detail::FacetKey<2, int>; + + KeyType key1(42); + KeyType key2(42); + KeyType key3(43); + + EXPECT_TRUE(key1 == key2); + EXPECT_FALSE(key1 == key3); + EXPECT_TRUE(key1 != key3); +} + +TEST(slam_detail_FacetKey, equality_3d_with_sorting) +{ + using KeyType = axom::slam::detail::FacetKey<3, int>; + + // Same keys in different order should match + KeyType key1(10, 20); + KeyType key2(20, 10); // Will be sorted to (10, 20) + KeyType key3(10, 21); + + EXPECT_TRUE(key1 == key2); // Should match after sorting + EXPECT_FALSE(key1 == key3); +} + +//------------------------------------------------------------------------------ +// FacetData Tests +//------------------------------------------------------------------------------ + +TEST(slam_detail_FacetData, construction) +{ + using DataType = axom::slam::detail::FacetData; + + // Default construction + DataType data1; + EXPECT_EQ(data1.element_idx, DataType::INVALID); + EXPECT_EQ(data1.face_idx, DataType::INVALID); + + // Construction with values + DataType data2(100, 2); + EXPECT_EQ(data2.element_idx, 100); + EXPECT_EQ(data2.face_idx, 2); +} + +//------------------------------------------------------------------------------ +// FacetPairingMap 2D Tests +//------------------------------------------------------------------------------ + +TEST(slam_detail_FacetPairingMap, basic_2d_insert_and_match) +{ + using MapType = axom::slam::detail::FacetPairingMap<2, int>; + using KeyType = typename MapType::KeyType; + using DataType = typename MapType::DataType; + + MapType map; + map.prepareForInsertions(10); + + // Insert a facet + KeyType key1(42); + DataType data1(100, 1); + map.insert(key1, data1); + + EXPECT_EQ(map.pendingCount(), 1); + EXPECT_FALSE(map.allFacetsPaired()); + + // Match with same key + KeyType key2(42); + auto match = map.findAndRemove(key2); + + ASSERT_TRUE(match.has_value()); + EXPECT_EQ(match->element_idx, 100); + EXPECT_EQ(match->face_idx, 1); + EXPECT_EQ(map.pendingCount(), 0); + EXPECT_TRUE(map.allFacetsPaired()); +} + +TEST(slam_detail_FacetPairingMap, multiple_2d_facets) +{ + using MapType = axom::slam::detail::FacetPairingMap<2, int>; + using KeyType = typename MapType::KeyType; + using DataType = typename MapType::DataType; + + MapType map; + map.prepareForInsertions(20); + + // Insert several facets + for(int i = 0; i < 10; ++i) + { + KeyType key(i * 10); + DataType data(i, 0); + map.insert(key, data); + } + + EXPECT_EQ(map.pendingCount(), 10); + + // Match them all + for(int i = 0; i < 10; ++i) + { + KeyType key(i * 10); + auto match = map.findAndRemove(key); + + ASSERT_TRUE(match.has_value()); + EXPECT_EQ(match->element_idx, i); + EXPECT_EQ(match->face_idx, 0); + } + + EXPECT_EQ(map.pendingCount(), 0); + EXPECT_TRUE(map.allFacetsPaired()); +} + +TEST(slam_detail_FacetPairingMap, not_found_2d) +{ + using MapType = axom::slam::detail::FacetPairingMap<2, int>; + using KeyType = typename MapType::KeyType; + using DataType = typename MapType::DataType; + + MapType map; + map.prepareForInsertions(10); + + // Insert one facet + KeyType key1(42); + DataType data1(100, 1); + map.insert(key1, data1); + + // Try to find a different key + KeyType key2(43); + auto match = map.findAndRemove(key2); + + EXPECT_FALSE(match.has_value()); + EXPECT_EQ(map.pendingCount(), 1); // Original still there +} + +//------------------------------------------------------------------------------ +// FacetPairingMap 3D Tests +//------------------------------------------------------------------------------ + +TEST(slam_detail_FacetPairingMap, basic_3d_insert_and_match) +{ + using MapType = axom::slam::detail::FacetPairingMap<3, int>; + using KeyType = typename MapType::KeyType; + using DataType = typename MapType::DataType; + + MapType map; + map.prepareForInsertions(10); + + // Insert a facet + KeyType key1(10, 20); + DataType data1(100, 1); + map.insert(key1, data1); + + EXPECT_EQ(map.pendingCount(), 1); + + // Match with equivalent key (different order) + KeyType key2(20, 10); // Should match (10, 20) after sorting + auto match = map.findAndRemove(key2); + + ASSERT_TRUE(match.has_value()); + EXPECT_EQ(match->element_idx, 100); + EXPECT_EQ(match->face_idx, 1); + EXPECT_EQ(map.pendingCount(), 0); +} + +TEST(slam_detail_FacetPairingMap, multiple_3d_facets) +{ + using MapType = axom::slam::detail::FacetPairingMap<3, int>; + using KeyType = typename MapType::KeyType; + using DataType = typename MapType::DataType; + + MapType map; + map.prepareForInsertions(50); + + // Insert many facets to stress-test collision handling + for(int i = 0; i < 50; ++i) + { + KeyType key(i, i + 1000); + DataType data(i, 2); + map.insert(key, data); + } + + EXPECT_EQ(map.pendingCount(), 50); + + // Match them all (including with reversed key order) + for(int i = 0; i < 50; ++i) + { + // Use reversed order to test sorting + KeyType key(i + 1000, i); + auto match = map.findAndRemove(key); + + ASSERT_TRUE(match.has_value()) << "Failed to find key (" << i << ", " << (i + 1000) << ")"; + EXPECT_EQ(match->element_idx, i); + EXPECT_EQ(match->face_idx, 2); + } + + EXPECT_EQ(map.pendingCount(), 0); + EXPECT_TRUE(map.allFacetsPaired()); +} + +TEST(slam_detail_FacetPairingMap, 3d_key_ordering_invariant) +{ + using MapType = axom::slam::detail::FacetPairingMap<3, int>; + using KeyType = typename MapType::KeyType; + using DataType = typename MapType::DataType; + + MapType map; + map.prepareForInsertions(10); + + // Insert with keys in sorted order + KeyType key1(5, 10); + DataType data1(100, 1); + map.insert(key1, data1); + + // Should match with reversed order + KeyType key2(10, 5); + auto match = map.findAndRemove(key2); + + ASSERT_TRUE(match.has_value()); + EXPECT_EQ(match->element_idx, 100); + + // Insert again with reversed order + KeyType key3(15, 10); // Will be sorted to (10, 15) + DataType data2(200, 2); + map.insert(key3, data2); + + // Match with normal order + KeyType key4(10, 15); + auto match2 = map.findAndRemove(key4); + + ASSERT_TRUE(match2.has_value()); + EXPECT_EQ(match2->element_idx, 200); +} + +//------------------------------------------------------------------------------ +// Collision and Tombstone Tests +//------------------------------------------------------------------------------ + +TEST(slam_detail_FacetPairingMap, collision_handling) +{ + using MapType = axom::slam::detail::FacetPairingMap<3, int>; + using KeyType = typename MapType::KeyType; + using DataType = typename MapType::DataType; + + MapType map; + map.prepareForInsertions(100); + + // Insert many facets to force collisions + std::vector keys; + for(int i = 0; i < 100; ++i) + { + KeyType key(i, i + 5000); + keys.push_back(key); + DataType data(i, 0); + map.insert(key, data); + } + + EXPECT_EQ(map.pendingCount(), 100); + + // Match all keys in random order + std::vector indices(100); + for(int i = 0; i < 100; ++i) indices[i] = i; + // Simple shuffle + for(int i = 0; i < 100; ++i) + { + int j = (i * 7 + 13) % 100; + std::swap(indices[i], indices[j]); + } + + for(int idx : indices) + { + auto match = map.findAndRemove(keys[idx]); + ASSERT_TRUE(match.has_value()) << "Failed to find key at index " << idx; + EXPECT_EQ(match->element_idx, idx); + } + + EXPECT_EQ(map.pendingCount(), 0); +} + +TEST(slam_detail_FacetPairingMap, tombstone_reuse) +{ + using MapType = axom::slam::detail::FacetPairingMap<2, int>; + using KeyType = typename MapType::KeyType; + using DataType = typename MapType::DataType; + + MapType map; + map.prepareForInsertions(10); + + // Insert and remove to create tombstones + for(int i = 0; i < 5; ++i) + { + KeyType key1(i * 10); + DataType data1(i, 0); + map.insert(key1, data1); + + KeyType key2(i * 10); + auto match = map.findAndRemove(key2); + ASSERT_TRUE(match.has_value()); + } + + EXPECT_EQ(map.pendingCount(), 0); + + // Now insert new keys - should reuse tombstone slots + for(int i = 0; i < 5; ++i) + { + KeyType key(i * 10 + 5); // Different keys + DataType data(i + 100, 1); + map.insert(key, data); + } + + EXPECT_EQ(map.pendingCount(), 5); + + // Verify new keys are findable + for(int i = 0; i < 5; ++i) + { + KeyType key(i * 10 + 5); + auto match = map.findAndRemove(key); + ASSERT_TRUE(match.has_value()); + EXPECT_EQ(match->element_idx, i + 100); + } +} + +//------------------------------------------------------------------------------ +// Generation and Reuse Tests +//------------------------------------------------------------------------------ + +TEST(slam_detail_FacetPairingMap, multiple_prepare_cycles) +{ + using MapType = axom::slam::detail::FacetPairingMap<2, int>; + using KeyType = typename MapType::KeyType; + using DataType = typename MapType::DataType; + + MapType map; + + // Run multiple prepare/insert/match cycles + for(int cycle = 0; cycle < 10; ++cycle) + { + map.prepareForInsertions(10); + + // Insert facets + for(int i = 0; i < 5; ++i) + { + KeyType key(cycle * 100 + i); + DataType data(i, cycle); + map.insert(key, data); + } + + EXPECT_EQ(map.pendingCount(), 5); + + // Match them + for(int i = 0; i < 5; ++i) + { + KeyType key(cycle * 100 + i); + auto match = map.findAndRemove(key); + ASSERT_TRUE(match.has_value()); + EXPECT_EQ(match->element_idx, i); + EXPECT_EQ(match->face_idx, cycle); + } + + EXPECT_EQ(map.pendingCount(), 0); + } +} + +TEST(slam_detail_FacetPairingMap, generation_isolation) +{ + using MapType = axom::slam::detail::FacetPairingMap<2, int>; + using KeyType = typename MapType::KeyType; + using DataType = typename MapType::DataType; + + MapType map; + + // First generation + map.prepareForInsertions(10); + KeyType key1(42); + DataType data1(100, 1); + map.insert(key1, data1); + EXPECT_EQ(map.pendingCount(), 1); + + // Second generation (should not see first generation's data) + map.prepareForInsertions(10); + EXPECT_EQ(map.pendingCount(), 0); // Reset + + // Try to find old key - should not be found + KeyType key2(42); + auto match = map.findAndRemove(key2); + EXPECT_FALSE(match.has_value()); +} + +//------------------------------------------------------------------------------ +// Table Sizing Tests +//------------------------------------------------------------------------------ + +TEST(slam_detail_FacetPairingMap, table_sizing_2d) +{ + using MapType = axom::slam::detail::FacetPairingMap<2, int>; + + MapType map; + + // Small count + map.prepareForInsertions(10); + EXPECT_GE(map.tableSize(), 8); // Minimum size + + // Larger count - should scale appropriately + map.prepareForInsertions(100); + EXPECT_GE(map.tableSize(), 400); // 4x in 2D + + // Table size should be power of 2 + EXPECT_EQ(map.tableSize() & (map.tableSize() - 1), 0); +} + +TEST(slam_detail_FacetPairingMap, table_sizing_3d) +{ + using MapType = axom::slam::detail::FacetPairingMap<3, int>; + + MapType map; + + // Small count + map.prepareForInsertions(10); + EXPECT_GE(map.tableSize(), 8); + + // Larger count - should scale appropriately + map.prepareForInsertions(100); + EXPECT_GE(map.tableSize(), 800); // 8x in 3D + + // Table size should be power of 2 + EXPECT_EQ(map.tableSize() & (map.tableSize() - 1), 0); +} + +//------------------------------------------------------------------------------ +// Edge Cases +//------------------------------------------------------------------------------ + +TEST(slam_detail_FacetPairingMap, empty_map_operations) +{ + using MapType = axom::slam::detail::FacetPairingMap<2, int>; + using KeyType = typename MapType::KeyType; + + MapType map; + map.prepareForInsertions(10); + + // Try to find in empty map + KeyType key(42); + auto match = map.findAndRemove(key); + + EXPECT_FALSE(match.has_value()); + EXPECT_EQ(map.pendingCount(), 0); + EXPECT_TRUE(map.allFacetsPaired()); +} + +TEST(slam_detail_FacetPairingMap, single_facet) +{ + using MapType = axom::slam::detail::FacetPairingMap<2, int>; + using KeyType = typename MapType::KeyType; + using DataType = typename MapType::DataType; + + MapType map; + map.prepareForInsertions(1); + + KeyType key(42); + DataType data(100, 1); + map.insert(key, data); + + EXPECT_EQ(map.pendingCount(), 1); + + auto match = map.findAndRemove(key); + ASSERT_TRUE(match.has_value()); + EXPECT_EQ(map.pendingCount(), 0); +} + +TEST(slam_detail_FacetPairingMap, large_vertex_ids) +{ + using MapType = axom::slam::detail::FacetPairingMap<3, int>; + using KeyType = typename MapType::KeyType; + using DataType = typename MapType::DataType; + + MapType map; + map.prepareForInsertions(10); + + // Use large vertex IDs to test hash collision behavior + const int LARGE_ID = 1000000; + + for(int i = 0; i < 10; ++i) + { + KeyType key(LARGE_ID + i, LARGE_ID + i + 1); + DataType data(i, 0); + map.insert(key, data); + } + + EXPECT_EQ(map.pendingCount(), 10); + + for(int i = 0; i < 10; ++i) + { + KeyType key(LARGE_ID + i, LARGE_ID + i + 1); + auto match = map.findAndRemove(key); + ASSERT_TRUE(match.has_value()); + } + + EXPECT_TRUE(map.allFacetsPaired()); +} + +//------------------------------------------------------------------------------ +// main +//------------------------------------------------------------------------------ + +int main(int argc, char** argv) +{ + ::testing::InitGoogleTest(&argc, argv); + axom::slic::SimpleLogger logger; + + int result = RUN_ALL_TESTS(); + + return result; +} From 4cbd838076119a4f877d982be7dcf0a23ae2a064 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 15 Apr 2026 22:06:36 -0700 Subject: [PATCH 516/986] More slam usage in fixVertexNeighborhood --- src/axom/slam/mesh_struct/IA.hpp | 19 ++--- src/axom/slam/mesh_struct/IA_impl.hpp | 118 ++++++++++++-------------- 2 files changed, 61 insertions(+), 76 deletions(-) diff --git a/src/axom/slam/mesh_struct/IA.hpp b/src/axom/slam/mesh_struct/IA.hpp index 9bf7808fa2..c401b74b7f 100644 --- a/src/axom/slam/mesh_struct/IA.hpp +++ b/src/axom/slam/mesh_struct/IA.hpp @@ -415,17 +415,14 @@ class IAMesh IndexType element_i, IndexType side_i); - // Helper methods for fixVertexNeighborhood - static constexpr std::size_t getBoundaryBase(IndexType element_idx); - static constexpr std::size_t getFaceOffset(IndexType element_idx, int face_idx); - static constexpr int skippedVertexToFace(int skipped_vertex_idx); - static constexpr int getVertexPositionInFace(int face_idx); - - typename detail::FacetPairingMap::KeyType createFacetKey( - const IndexType* element_vertices, - int face_idx) const; - - int findNeighborFaceIndex(IndexType neighbor_idx, const IndexType* boundary_vertices) const; + /// \brief Create a facet key for matching faces during connectivity repair + typename detail::FacetPairingMap::KeyType createFacetKey(IndexType element_idx, + int face_idx) const; + + /// \brief Find which face of a neighbor element shares vertices with a given face + int findNeighborFaceIndex(IndexType neighbor_idx, + IndexType current_element_idx, + int current_face_idx) const; private: VertexSet vertex_set; //Set of vertices diff --git a/src/axom/slam/mesh_struct/IA_impl.hpp b/src/axom/slam/mesh_struct/IA_impl.hpp index 57bbaccace..af37c3435c 100644 --- a/src/axom/slam/mesh_struct/IA_impl.hpp +++ b/src/axom/slam/mesh_struct/IA_impl.hpp @@ -630,54 +630,25 @@ typename IAMesh::IndexType IAMesh::reuseElement(In return element_idx; } -//============================================================================== -// Helper methods for fixVertexNeighborhood -//============================================================================== - -/// \brief Get the base offset for an element's boundary vertices in the flat array -template -constexpr std::size_t IAMesh::getBoundaryBase(IndexType element_idx) -{ - return static_cast(element_idx) * VERTS_PER_ELEM; -} - -/// \brief Get the flat offset for a face in the element-element adjacency array -template -constexpr std::size_t IAMesh::getFaceOffset(IndexType element_idx, int face_idx) -{ - return static_cast(element_idx) * VERTS_PER_ELEM + face_idx; -} - -/// \brief Convert skipped vertex index to face index (modular arithmetic) -template -constexpr int IAMesh::skippedVertexToFace(int skipped_vertex_idx) -{ - return (skipped_vertex_idx + 1) % VERTS_PER_ELEM; -} - -/// \brief Get the vertex position in a face (opposite vertex for simplicial meshes) -template -constexpr int IAMesh::getVertexPositionInFace(int face_idx) -{ - return (face_idx == 0) ? VERTS_PER_ELEM - 1 : face_idx - 1; -} - /** * \brief Create a facet key for a given face of an element. * * In 2D (triangles): Returns the single non-shared vertex * In 3D (tetrahedra): Returns the two non-shared vertices (will be auto-sorted by FacetKey) * - * \param element_vertices Pointer to the element's boundary vertices + * \param element_idx Index of the element * \param face_idx Local face index (1..VERTS_PER_ELEM-1, face 0 is boundary) * \return FacetKey identifying this face */ template typename detail::FacetPairingMap::IndexType>::KeyType -IAMesh::createFacetKey(const IndexType* element_vertices, int face_idx) const +IAMesh::createFacetKey(IndexType element_idx, int face_idx) const { using KeyType = typename detail::FacetPairingMap::KeyType; + // Get element vertices using SLAM relation accessor + const auto element_vertices = ev_rel[element_idx]; + if constexpr(TDIM == 2) { // 2D: face is identified by the single non-shared vertex @@ -709,31 +680,43 @@ IAMesh::createFacetKey(const IndexType* element_vertices, int fac } /** - * \brief Find which face of a neighbor element corresponds to a given boundary face. + * \brief Find which face of a neighbor element shares vertices with a given face. * - * Given an element's boundary vertices, find which face of the neighbor shares those vertices. + * Uses SLAM's ModularInt for wraparound arithmetic and relation accessors + * to find which vertex is opposite to the shared face. * * \param neighbor_idx Index of the neighbor element - * \param boundary_vertices Pointer to the current element's boundary vertices + * \param current_element_idx Index of the current element + * \param current_face_idx Local face index on the current element * \return Local face index on the neighbor element */ template int IAMesh::findNeighborFaceIndex(IndexType neighbor_idx, - const IndexType* boundary_vertices) const + IndexType current_element_idx, + int current_face_idx) const { - const IndexType* const neighbor_vertices = ev_rel.data().data() + getBoundaryBase(neighbor_idx); + // Get vertices using SLAM relation accessors + const auto current_vertices = ev_rel[current_element_idx]; + const auto neighbor_vertices = ev_rel[neighbor_idx]; + + // Use ModularInt for face indexing + ModularFacetIndex mod_face(current_face_idx); if constexpr(TDIM == 2) { - // In 2D, find which vertex of the neighbor is not in {v0, v1} - const IndexType v0 = boundary_vertices[0]; - const IndexType v1 = boundary_vertices[1]; + // In 2D, the shared edge has two vertices (all except the opposite vertex) + // Face i is opposite to vertex (i-1), so the shared vertices are at positions i and i+1 + const IndexType v0 = current_vertices[mod_face]; + const IndexType v1 = current_vertices[mod_face + 1]; + // Find which vertex of the neighbor is NOT in the shared edge for(int i = 0; i < VERTS_PER_ELEM; ++i) { if(neighbor_vertices[i] != v0 && neighbor_vertices[i] != v1) { - return skippedVertexToFace(i); + // The face opposite to vertex i is (i+1) % VERTS_PER_ELEM + ModularVertexIndex mod_vert(i); + return mod_vert + 1; } } @@ -742,16 +725,20 @@ int IAMesh::findNeighborFaceIndex(IndexType neighbor_idx, } else { - // In 3D, find which vertex of the neighbor is not in {v0, v1, v2} - const IndexType v0 = boundary_vertices[0]; - const IndexType v1 = boundary_vertices[1]; - const IndexType v2 = boundary_vertices[2]; + // In 3D, the shared face has three vertices (all except the opposite vertex) + // Face i is opposite to vertex (i-1), so shared vertices are at positions i, i+1, i+2 + const IndexType v0 = current_vertices[mod_face]; + const IndexType v1 = current_vertices[mod_face + 1]; + const IndexType v2 = current_vertices[mod_face + 2]; + // Find which vertex of the neighbor is NOT in the shared face for(int i = 0; i < VERTS_PER_ELEM; ++i) { if(neighbor_vertices[i] != v0 && neighbor_vertices[i] != v1 && neighbor_vertices[i] != v2) { - return skippedVertexToFace(i); + // The face opposite to vertex i is (i+1) % VERTS_PER_ELEM + ModularVertexIndex mod_vert(i); + return mod_vert + 1; } } @@ -776,10 +763,6 @@ void IAMesh::fixVertexNeighborhood(IndexType vertex_idx, FacetMap facet_map; facet_map.prepareForInsertions(new_elements.size()); - const auto& ev_data = ev_rel.data(); - auto& ee_data = ee_rel.data(); - const IndexType* const ev_ptr = ev_data.data(); - // Statistics tracking (for validation) int num_boundary_faces = 0; int num_incident_faces = 0; @@ -787,11 +770,15 @@ void IAMesh::fixVertexNeighborhood(IndexType vertex_idx, // Process all faces in the new elements for(IndexType element_idx : new_elements) { - const IndexType* const element_vertices = ev_ptr + getBoundaryBase(element_idx); + // Get element vertices and adjacencies using SLAM relation accessors + const auto element_vertices = ev_rel[element_idx]; + auto element_neighbors = ee_rel[element_idx]; for(int local_face_idx = 0; local_face_idx < VERTS_PER_ELEM; ++local_face_idx) { - const int vertex_position = getVertexPositionInFace(local_face_idx); + // Use ModularInt to find the vertex opposite to this face + ModularFacetIndex mod_face(local_face_idx); + const int vertex_position = mod_face - 1; // Check if this face contains the vertex we're fixing if(element_vertices[vertex_position] == vertex_idx) @@ -801,19 +788,19 @@ void IAMesh::fixVertexNeighborhood(IndexType vertex_idx, ++num_boundary_faces; // Update neighbor's adjacency to point back to this element - const IndexType neighbor = ee_data[getFaceOffset(element_idx, local_face_idx)]; + const IndexType neighbor = element_neighbors[local_face_idx]; if(neighbor != INVALID_ELEMENT_INDEX) { SLIC_ASSERT(element_set.isValidEntry(neighbor)); // Find which face of the neighbor shares these vertices - const int neighbor_face_idx = findNeighborFaceIndex(neighbor, element_vertices); - const std::size_t neighbor_face_offset = getFaceOffset(neighbor, neighbor_face_idx); + const int neighbor_face_idx = findNeighborFaceIndex(neighbor, element_idx, local_face_idx); - // Update or verify neighbor's adjacency - SLIC_ASSERT(ee_data[neighbor_face_offset] == INVALID_ELEMENT_INDEX || - ee_data[neighbor_face_offset] == element_idx); - ee_data[neighbor_face_offset] = element_idx; + // Update or verify neighbor's adjacency using SLAM relation accessor + auto neighbor_adjacencies = ee_rel[neighbor]; + SLIC_ASSERT(neighbor_adjacencies[neighbor_face_idx] == INVALID_ELEMENT_INDEX || + neighbor_adjacencies[neighbor_face_idx] == element_idx); + neighbor_adjacencies[neighbor_face_idx] = element_idx; } } else @@ -822,17 +809,18 @@ void IAMesh::fixVertexNeighborhood(IndexType vertex_idx, ++num_incident_faces; // Create facet key for matching - const FacetKey face_key = createFacetKey(element_vertices, local_face_idx); + const FacetKey face_key = createFacetKey(element_idx, local_face_idx); // Try to find matching facet from another element if(auto match = facet_map.findAndRemove(face_key)) { - // Found the matching face - update both adjacencies + // Found the matching face - update both adjacencies using SLAM relation accessors SLIC_ASSERT_MSG(match->element_idx != element_idx, "Each face in the inserted star should be shared by two elements"); - ee_data[getFaceOffset(match->element_idx, match->face_idx)] = element_idx; - ee_data[getFaceOffset(element_idx, local_face_idx)] = match->element_idx; + auto match_element_neighbors = ee_rel[match->element_idx]; + match_element_neighbors[match->face_idx] = element_idx; + element_neighbors[local_face_idx] = match->element_idx; } else { From 1a07e07ea00ab8a3d26c4499b80fc71f8d3eafd5 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Fri, 8 May 2026 19:20:25 -0700 Subject: [PATCH 517/986] Fix for Delaunay with 0 or 1 initial point and for cases where we do not directly find a candidate --- src/axom/quest/Delaunay.hpp | 6 +++ src/axom/quest/ScatteredInterpolation.hpp | 14 +++++- .../quest/detail/DelaunayElementFinder.hpp | 11 +++++ src/axom/quest/detail/DelaunayImpl.hpp | 41 +++++++++++++++++ .../quest/detail/DelaunayPointLocation.hpp | 21 +++++++++ src/axom/quest/detail/DelaunayValidation.hpp | 2 +- src/axom/quest/tests/quest_delaunay.cpp | 44 +++++++++++++++++++ 7 files changed, 136 insertions(+), 3 deletions(-) diff --git a/src/axom/quest/Delaunay.hpp b/src/axom/quest/Delaunay.hpp index 677d08b300..501bd86a50 100644 --- a/src/axom/quest/Delaunay.hpp +++ b/src/axom/quest/Delaunay.hpp @@ -263,6 +263,12 @@ class Delaunay */ Delaunay() : m_has_boundary(false), m_insertion_validation_mode(InsertionValidationMode::None) { } + Delaunay(const Delaunay&) = delete; + Delaunay& operator=(const Delaunay&) = delete; + + Delaunay(Delaunay&& other) : Delaunay() { *this = std::move(other); } + Delaunay& operator=(Delaunay&& other); + /// \brief Returns statistics about insertion operations /// \return InsertionStats containing cavity size metrics InsertionStats getInsertionStats() const diff --git a/src/axom/quest/ScatteredInterpolation.hpp b/src/axom/quest/ScatteredInterpolation.hpp index 91adc6146b..0c661214f2 100644 --- a/src/axom/quest/ScatteredInterpolation.hpp +++ b/src/axom/quest/ScatteredInterpolation.hpp @@ -9,6 +9,7 @@ #include "axom/core.hpp" #include "axom/core/NumericLimits.hpp" +#include "axom/core/utilities/BitUtilities.hpp" #include "axom/slic.hpp" #include "axom/sidre.hpp" #include "axom/spin.hpp" @@ -314,6 +315,16 @@ class ScatteredInterpolation // and a quantized Morton index from a rectangular lattice over the bounding box const int npts = pts.size(); + if(npts <= 1) + { + axom::Array reordered(npts, npts); + for(int idx = 0; idx < npts; ++idx) + { + reordered[idx] = idx; + } + return reordered; + } + const int nlevels = axom::utilities::ceil(axom::utilities::log2(npts)); // Each point has a 50% chance of being at the max level; of the remaining points @@ -360,8 +371,7 @@ class ScatteredInterpolation return 0; } - // level in [1, used_bits] - const int level = RNG_BITS - __builtin_clzll(bits); + const int level = 32 - axom::utilities::countl_zero(static_cast(bits)); return axom::utilities::min(level, nlevels); }; diff --git a/src/axom/quest/detail/DelaunayElementFinder.hpp b/src/axom/quest/detail/DelaunayElementFinder.hpp index 3c81ec634d..c911d7cd77 100644 --- a/src/axom/quest/detail/DelaunayElementFinder.hpp +++ b/src/axom/quest/detail/DelaunayElementFinder.hpp @@ -215,6 +215,17 @@ class DelaunayElementFinder return flatIndex(cell); } + /// \brief Returns the largest useful bin-neighborhood radius for exhaustive local seeding. + inline int maxSearchRadius() const + { + int max_radius = 0; + for(int dim = 0; dim < DIM; ++dim) + { + max_radius = axom::utilities::max(max_radius, static_cast(m_bins.shape()[dim])); + } + return max_radius; + } + /// \brief Update a bin to reference a newly inserted vertex /// /// \param pt The position of the newly inserted vertex diff --git a/src/axom/quest/detail/DelaunayImpl.hpp b/src/axom/quest/detail/DelaunayImpl.hpp index edf0c5b773..8dd7ef6245 100644 --- a/src/axom/quest/detail/DelaunayImpl.hpp +++ b/src/axom/quest/detail/DelaunayImpl.hpp @@ -27,6 +27,47 @@ namespace axom namespace quest { +template +inline Delaunay& Delaunay::operator=(Delaunay&& other) +{ + if(this == &other) + { + return *this; + } + + const bool other_has_insertion_helper = other.m_insertion_helper != nullptr; + + m_mesh = std::move(other.m_mesh); + m_bounding_box = std::move(other.m_bounding_box); + m_has_boundary = other.m_has_boundary; + m_insertion_validation_mode = other.m_insertion_validation_mode; + m_deleted_elements = std::move(other.m_deleted_elements); + m_element_finder = std::move(other.m_element_finder); + m_next_regrid_vertex_count = other.m_next_regrid_vertex_count; + m_walk_visited = std::move(other.m_walk_visited); + m_total_removed_elements = other.m_total_removed_elements; + m_max_removed_elements = other.m_max_removed_elements; + m_num_insertions = other.m_num_insertions; + m_collect_location_stats = other.m_collect_location_stats; + m_num_walk_calls = other.m_num_walk_calls; + m_num_walk_found = other.m_num_walk_found; + m_num_walk_outside = other.m_num_walk_outside; + m_num_walk_failed = other.m_num_walk_failed; + m_total_walk_steps = other.m_total_walk_steps; + m_max_walk_steps = other.m_max_walk_steps; + m_candidate_elements_scratch = std::move(other.m_candidate_elements_scratch); + m_walked_elements_scratch = std::move(other.m_walked_elements_scratch); + m_walk_local_elements_scratch = std::move(other.m_walk_local_elements_scratch); + m_initial_vertices_scratch = std::move(other.m_initial_vertices_scratch); + m_insertion_helper = + other_has_insertion_helper ? std::make_unique(m_mesh) : nullptr; + + other.m_has_boundary = false; + other.m_insertion_helper.reset(); + + return *this; +} + template AXOM_QUEST_DELAUNAY_FORCE_INLINE bool Delaunay::isSearchableElement(IndexType element_idx) const { diff --git a/src/axom/quest/detail/DelaunayPointLocation.hpp b/src/axom/quest/detail/DelaunayPointLocation.hpp index ba69291fbe..019208aad5 100644 --- a/src/axom/quest/detail/DelaunayPointLocation.hpp +++ b/src/axom/quest/detail/DelaunayPointLocation.hpp @@ -295,6 +295,27 @@ AXOM_QUEST_DELAUNAY_FORCE_INLINE void Delaunay::getInitialCandidateElements /*search_radius=*/1, /*max_candidates=*/8); appendCandidateElementsFromVertices(candidate_elements, m_initial_vertices_scratch); + if(!candidate_elements.empty()) + { + return; + } + + const int max_search_radius = axom::utilities::max(1, m_element_finder.maxSearchRadius()); + for(int search_radius = 2; search_radius <= max_search_radius; + search_radius = axom::utilities::min(max_search_radius, 2 * search_radius)) + { + m_initial_vertices_scratch.clear(); + m_element_finder.getNearbyVertices(m_mesh, + query_pt, + m_initial_vertices_scratch, + search_radius, + /*max_candidates=*/8); + appendCandidateElementsFromVertices(candidate_elements, m_initial_vertices_scratch); + if(!candidate_elements.empty() || search_radius == max_search_radius) + { + break; + } + } } template diff --git a/src/axom/quest/detail/DelaunayValidation.hpp b/src/axom/quest/detail/DelaunayValidation.hpp index 9079369110..8885309830 100644 --- a/src/axom/quest/detail/DelaunayValidation.hpp +++ b/src/axom/quest/detail/DelaunayValidation.hpp @@ -355,7 +355,7 @@ inline bool Delaunay::isValid(bool verboseOutput) const } } - const int kUpper = (DIM == 2) ? 0 : res; + const int kUpper = (DIM == 2) ? 1 : res; const IndexType stride[3] = {1, res, (DIM == 2) ? 0 : res * res}; for(IndexType k = 0; k < kUpper; ++k) { diff --git a/src/axom/quest/tests/quest_delaunay.cpp b/src/axom/quest/tests/quest_delaunay.cpp index 0680c50233..4a3fb1990b 100644 --- a/src/axom/quest/tests/quest_delaunay.cpp +++ b/src/axom/quest/tests/quest_delaunay.cpp @@ -9,6 +9,7 @@ #include "axom/quest/Delaunay.hpp" #include "axom/slic.hpp" +#include #include #include @@ -248,6 +249,49 @@ TEST(quest_delaunay, query_location_regular_grid_2d) } } +TEST(quest_delaunay, query_location_boundary_ring_2d) +{ + using PointType = typename DelaunayType<2>::PointType; + using BoundingBox = typename DelaunayType<2>::BoundingBox; + + DelaunayType<2> dt; + dt.initializeBoundary(BoundingBox(PointType {-1.1, -1.1}, PointType {1.1, 1.1})); + + constexpr int NPTS = 625; + std::vector points; + points.reserve(NPTS); + for(int i = 0; i < NPTS; ++i) + { + const double theta = 2. * M_PI * static_cast(i) / static_cast(NPTS); + const double radius = 1. + 0.01 * std::sin(17. * theta) + 0.005 * std::cos(31. * theta); + points.push_back(PointType {radius * std::cos(theta), radius * std::sin(theta)}); + } + + insertPoints(dt, points); + dt.removeBoundary(); + + EXPECT_NE(DelaunayType<2>::INVALID_INDEX, dt.findContainingElement(PointType {0., 0.}, false)); +} + +TEST(quest_delaunay, move_rebinds_insertion_helper_2d) +{ + using PointType = typename DelaunayType<2>::PointType; + using BoundingBox = typename DelaunayType<2>::BoundingBox; + + std::vector points {PointType {0.1, 0.1}, + PointType {0.8, 0.1}, + PointType {0.2, 0.8}, + PointType {0.7, 0.7}}; + + DelaunayType<2> original; + original.initializeBoundary(BoundingBox(PointType {0., 0.}, PointType {1., 1.})); + insertPoints(original, std::vector(points.begin(), points.end() - 1)); + + DelaunayType<2> moved(std::move(original)); + moved.insertPoint(points.back()); + expectValidDelaunay(moved, points); +} + TEST(quest_delaunay, cospherical_cube_3d) { using PointType = typename DelaunayType<3>::PointType; From e2117838ced9f7edbfded403dbd10b5c168ec6e1 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Mon, 11 May 2026 13:29:43 -0700 Subject: [PATCH 518/986] Bugfix: Adds missing slam header This was caught by our installation tests. --- src/axom/slam/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/axom/slam/CMakeLists.txt b/src/axom/slam/CMakeLists.txt index c84b3a1f55..ca94e5ef97 100644 --- a/src/axom/slam/CMakeLists.txt +++ b/src/axom/slam/CMakeLists.txt @@ -70,6 +70,7 @@ set(slam_headers # Topological mesh headers mesh_struct/IA.hpp mesh_struct/IA_impl.hpp + mesh_struct/detail/FacetPairingMap.hpp ) set(slam_sources From 1403986643a88d1f0752522f43ad98dda29d4e11 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 16 Jun 2026 18:52:05 -0700 Subject: [PATCH 519/986] slam: Clarify docs for IAMesh's FacetPairingMap The FacetPairingMap operates on facets of the link of an apex vertex, i.e. the boundary of the simplices (triangles in 2D, tetrahedra in 3D) incident in the apex vertex. So, in 2D, the keys of this map are vertices of the edges incident in the apex vertex, and in 3D the keys are edges if the triangles incident in the apex vertex. --- src/axom/slam/mesh_struct/IA.hpp | 13 +- src/axom/slam/mesh_struct/IA_impl.hpp | 103 ++++----- .../mesh_struct/detail/FacetPairingMap.hpp | 197 +++++++++++------- .../tests/slam_detail_FacetPairingMap.cpp | 40 ++-- 4 files changed, 208 insertions(+), 145 deletions(-) diff --git a/src/axom/slam/mesh_struct/IA.hpp b/src/axom/slam/mesh_struct/IA.hpp index c401b74b7f..4566a8a974 100644 --- a/src/axom/slam/mesh_struct/IA.hpp +++ b/src/axom/slam/mesh_struct/IA.hpp @@ -283,12 +283,16 @@ class IAMesh /** * \brief Fix the element adjacency relation in the neighborhood of a vertex * - * \details Given a vertex index and a list of all the elements incident in - * that vertex, fix the element->element relation data. + * \details Given the apex vertex and the list of star elements incident to it + * (e.g. the elements created when inserting the apex), recover the + * element->element adjacencies internal to that star. * Sometimes when modifying the mesh, the mesh becomes non-manifold. * Adding elements may result in incorrect element->element data. + * + * \param apex The vertex whose star is being repaired + * \param star_elements The elements incident to \a apex */ - void fixVertexNeighborhood(IndexType vertex_idx, const std::vector& new_elements); + void fixVertexNeighborhood(IndexType apex, const std::vector& star_elements); /** * \brief Return a valid element index @@ -415,7 +419,8 @@ class IAMesh IndexType element_i, IndexType side_i); - /// \brief Create a facet key for matching faces during connectivity repair + /// \brief Create the link-face key for an incident face of a star element + /// (the face with the apex removed) during connectivity repair typename detail::FacetPairingMap::KeyType createFacetKey(IndexType element_idx, int face_idx) const; diff --git a/src/axom/slam/mesh_struct/IA_impl.hpp b/src/axom/slam/mesh_struct/IA_impl.hpp index af37c3435c..0126b0cb1a 100644 --- a/src/axom/slam/mesh_struct/IA_impl.hpp +++ b/src/axom/slam/mesh_struct/IA_impl.hpp @@ -631,14 +631,17 @@ typename IAMesh::IndexType IAMesh::reuseElement(In } /** - * \brief Create a facet key for a given face of an element. + * \brief Create the link-face key for an incident face of a star element. * - * In 2D (triangles): Returns the single non-shared vertex - * In 3D (tetrahedra): Returns the two non-shared vertices (will be auto-sorted by FacetKey) + * An incident face contains the apex vertex; removing the apex leaves a face of its link: + * - 2D (triangles): the single non-apex (link) vertex of the incident edge. + * - 3D (tetrahedra): the two non-apex (link) vertices of the incident triangle, + * i.e. the endpoints of the link edge (auto-sorted by FacetKey). * - * \param element_idx Index of the element - * \param face_idx Local face index (1..VERTS_PER_ELEM-1, face 0 is boundary) - * \return FacetKey identifying this face + * \param element_idx Index of the star element + * \param face_idx Local face index (1..VERTS_PER_ELEM-1; local face 0 is the link + * face opposite the apex and is not keyed here) + * \return The link-face key identifying this incident face */ template typename detail::FacetPairingMap::IndexType>::KeyType @@ -651,31 +654,31 @@ IAMesh::createFacetKey(IndexType element_idx, int face_idx) const if constexpr(TDIM == 2) { - // 2D: face is identified by the single non-shared vertex + // 2D: key is the single non-apex (link) vertex of the incident edge SLIC_ASSERT(face_idx == 1 || face_idx == 2); return KeyType(face_idx == 1 ? element_vertices[1] : element_vertices[0]); } else { - // 3D: face is identified by the two non-shared vertices + // 3D: key is the two non-apex (link) vertices of the incident triangle SLIC_ASSERT(face_idx >= 1 && face_idx <= 3); - IndexType key0, key1; + IndexType v0, v1; switch(face_idx) { case 1: - key0 = element_vertices[1]; - key1 = element_vertices[2]; + v0 = element_vertices[1]; + v1 = element_vertices[2]; break; case 2: - key0 = element_vertices[2]; - key1 = element_vertices[0]; + v0 = element_vertices[2]; + v1 = element_vertices[0]; break; default: // face 3 - key0 = element_vertices[0]; - key1 = element_vertices[1]; + v0 = element_vertices[0]; + v1 = element_vertices[1]; break; } - return KeyType(key0, key1); // Auto-sorted by FacetKey constructor + return KeyType(v0, v1); // Auto-sorted by FacetKey constructor } } @@ -747,28 +750,33 @@ int IAMesh::findNeighborFaceIndex(IndexType neighbor_idx, } } -//============================================================================== -// fixVertexNeighborhood - Refactored to use FacetPairingMap -//============================================================================== +//================================================================================= +// fixVertexNeighborhood - pairs the interior faces of the star via FacetPairingMap +//================================================================================= template -void IAMesh::fixVertexNeighborhood(IndexType vertex_idx, - const std::vector& new_elements) +void IAMesh::fixVertexNeighborhood(IndexType apex, + const std::vector& star_elements) { using FacetMap = detail::FacetPairingMap; using FacetKey = typename FacetMap::KeyType; using FacetData = typename FacetMap::DataType; - // Prepare hash table for expected facet count + // Prepare the link-face pairing table for the expected number of incident faces FacetMap facet_map; - facet_map.prepareForInsertions(new_elements.size()); + facet_map.prepareForInsertions(star_elements.size()); // Statistics tracking (for validation) - int num_boundary_faces = 0; - int num_incident_faces = 0; - - // Process all faces in the new elements - for(IndexType element_idx : new_elements) + int num_link_faces = 0; // local face 0 of each element: the link face (opposite apex) + int num_incident_faces = 0; // the TDIM faces of each element that contain apex + + // Each star element has TDIM+1 faces. + // In the IA convention, local face i is opposite local vertex i-1 and `apex` is the last local vertex, so: + // - local face 0 is opposite `apex`: it is this element's link face, and its + // neighbor lies OUTSIDE the star, so we repair that one adjacency directly. + // - the other TDIM faces contain `apex`: with `apex` removed each is a face of + // the link, and we pair it against the other star element that shares it. + for(IndexType element_idx : star_elements) { // Get element vertices and adjacencies using SLAM relation accessors const auto element_vertices = ev_rel[element_idx]; @@ -780,14 +788,13 @@ void IAMesh::fixVertexNeighborhood(IndexType vertex_idx, ModularFacetIndex mod_face(local_face_idx); const int vertex_position = mod_face - 1; - // Check if this face contains the vertex we're fixing - if(element_vertices[vertex_position] == vertex_idx) + // The face opposite `apex` (local face 0) is the link face + if(element_vertices[vertex_position] == apex) { - // This is a BOUNDARY face of the vertex star - // (face 0, which is opposite the last vertex) - ++num_boundary_faces; + // LINK face of this star element (opposite apex). + // Its neighbor lies outside the star, so repair that single adjacency directly. + ++num_link_faces; - // Update neighbor's adjacency to point back to this element const IndexType neighbor = element_neighbors[local_face_idx]; if(neighbor != INVALID_ELEMENT_INDEX) { @@ -805,28 +812,28 @@ void IAMesh::fixVertexNeighborhood(IndexType vertex_idx, } else { - // This is an INCIDENT face (touches vertex_idx but doesn't have it opposite) + // INCIDENT face (contains apex). Its key is the corresponding link face. ++num_incident_faces; - // Create facet key for matching - const FacetKey face_key = createFacetKey(element_idx, local_face_idx); + // Build the link-face key for matching + const FacetKey link_face_key = createFacetKey(element_idx, local_face_idx); - // Try to find matching facet from another element - if(auto match = facet_map.findAndRemove(face_key)) + // Try to match the other star element sharing this link face + if(auto match = facet_map.findAndRemove(link_face_key)) { - // Found the matching face - update both adjacencies using SLAM relation accessors + // Second sighting: wire the two star elements adjacent across this link face SLIC_ASSERT_MSG(match->element_idx != element_idx, - "Each face in the inserted star should be shared by two elements"); + "Each interior face of the star should be shared by two elements"); auto match_element_neighbors = ee_rel[match->element_idx]; - match_element_neighbors[match->face_idx] = element_idx; + match_element_neighbors[match->local_face] = element_idx; element_neighbors[local_face_idx] = match->element_idx; } else { - // First time seeing this facet - store it for later matching - FacetData face_data(element_idx, local_face_idx); - facet_map.insert(face_key, face_data); + // First sighting: insert the star element/face that owns this link face + FacetData parked_slot(element_idx, local_face_idx); + facet_map.insert(link_face_key, parked_slot); } } } @@ -834,10 +841,10 @@ void IAMesh::fixVertexNeighborhood(IndexType vertex_idx, // Validate star topology AXOM_UNUSED_VAR(num_incident_faces); - AXOM_UNUSED_VAR(num_boundary_faces); - SLIC_ASSERT(num_incident_faces == static_cast(new_elements.size()) * TDIM); + AXOM_UNUSED_VAR(num_link_faces); + SLIC_ASSERT(num_incident_faces == static_cast(star_elements.size()) * TDIM); SLIC_ASSERT_MSG(facet_map.allFacetsPaired(), - "All faces in the inserted star should be paired exactly once"); + "Every interior face of the star should be paired exactly twice"); } // Remove all the invalid entries in the IA structure diff --git a/src/axom/slam/mesh_struct/detail/FacetPairingMap.hpp b/src/axom/slam/mesh_struct/detail/FacetPairingMap.hpp index 35a72d0857..8a99bc3f8a 100644 --- a/src/axom/slam/mesh_struct/detail/FacetPairingMap.hpp +++ b/src/axom/slam/mesh_struct/detail/FacetPairingMap.hpp @@ -10,51 +10,89 @@ /** * \file FacetPairingMap.hpp * - * \brief Specialized hash table for matching pairs of facets during mesh connectivity repair. + * \brief Pairs the interior faces of a vertex star during IA connectivity repair. * - * This hash table is used by IAMesh::fixVertexNeighborhood() to efficiently match - * facets between elements in a vertex star. It uses Robin Hood open addressing with - * generational marking to provide O(1) expected performance without allocations. + * This is used by IAMesh::fixVertexNeighborhood() after a vertex \a apex is inserted + * and its star -- the new elements incident to \a apex -- is retriangulated. + * The job is to recover the element-element adjacencies that are \a internal + * to that star. It is a transient pairing table, not a persistent lookup container. * - * ## Algorithm: Robin Hood Open Addressing with Generational Marking + * ## Why the key is a face of the link + * + * Recall that the \a star of \a apex is the collection of elements (triangles in 2D, tetrahedra in 3D) + * incident to \a apex, and the \a link of \a apex is the boundary of the star: + * a closed polyline in 2D (or open if \a apex is on the domain boundary) + * and a triangulated sphere in 3D (or a disk if \a apex is on the domain boundary). + * + * Each star element splits its faces into two kinds. + * In the IA convention, local face \a i is opposite local vertex \a i-1, + * and \a apex is stored at the last local vertex position, + * so the face opposite \a apex is local face 0: + * - Local face 0 does not contain \a apex. This is the element's contribution + * to the LINK of \a apex (an edge in 2D, a triangle in 3D). Its neighbor lies + * outside the star, so it is repaired directly by fixVertexNeighborhood() and never enters this pairer. + * - The other \a TDIM faces are all incident in \a apex and are paired here. + * + * Because every incident face contains \a apex, the apex carries no + * discriminating information. Removing it leaves a face of the link (our hash Key): + * - 2D: an incident face is the edge {apex, a}; the key is the single link vertex. + * - 3D: an incident face is the triangle {apex, a, b}; the key is the link + * edge {a, b}, stored sorted so that both incident tetrahedra produce the same key. + * + * Two star elements are adjacent across an incident face if and only if they + * share that link face, so matching keys is equivalent to reconstructing the + * star's internal adjacency. The apex is the implicit extra vertex of every key. + * + * ## Algorithm: linear-probing open addressing with generational marking * * **Generational Marking:** Each entry stores a generation counter. Entries with - * generation != current_generation are treated as empty without actually clearing them. - * This avoids O(n) clear operations between uses. + * generation != current_generation are treated as empty without actually clearing + * them. This makes prepareForInsertions() an O(1) "clear" (bump the generation) + * rather than an O(table) reset between uses. * - * **Generation Wraparound:** On 32-bit wraparound (every ~4 billion calls), we reset - * all entry generations to 0 and restart at 1. This is the only O(n) operation in - * the table's lifetime. + * **Generation Wraparound:** On 32-bit wraparound (every ~4 billion + * prepareForInsertions() calls), we reset all entry generations to 0 and restart + * at 1. This is the only O(table) operation in the table's lifetime. * * **Tombstone Markers:** Deleted entries are marked with a special TOMBSTONE value * to preserve probe chains. Tombstones can be reused for new insertions. * - * **Thread-Local Storage:** Uses static thread_local storage to amortize allocation - * cost across many operations. This provides zero-allocation operation after the - * first resize. + * **Thread-Local Storage:** Uses static thread_local storage so the backing array + * is reused across calls. The table only ever grows: prepareForInsertions() grows + * it to fit the largest star seen, so after a large pairing pass the table stays + * oversized for the lifetime of the thread. + * + * ## Performance characteristics * - * ## Performance Characteristics + * - **Lookup / insert:** O(1) expected. + * - **Space:** cold, the table is sized to ~(2*TDIM)x the facet count, i.e. about + * 12-37% load for typical star sizes. After a large pairing pass it remains at + * that high-water mark, so the effective load factor for subsequent small stars is much lower. + * - **Allocations:** zero after the high-water mark is reached (thread-local reuse). * - * - **Lookup:** O(1) expected - * - **Insert:** O(1) expected - * - **Space:** 4x-8x oversizing for ~12.5-25% load factor - * - **Allocations:** Zero after first use (thread-local reuse) + * \note This is NOT a general-purpose facet map. The reduced key (a face of the link) + * is valid because every key within a single pairing pass shares the same apex. + * The global manifold check, IAMesh::isConforming(), has no fixed apex to factor out + * and instead keys on the full sorted vertex tuple. * - * ## Usage Pattern + * \note Host-only by construction (thread_local + std::vector + std::optional). + * This is a serial, construction-time helper, not a portable (e.g. device-capable) container. + * + * ## Usage pattern * * ```cpp * FacetPairingMap map; * map.prepareForInsertions(expected_facet_count); * - * for (each facet in new elements): - * if (auto match = map.findAndRemove(facet_key)) - * // Found matching facet - update both adjacencies - * updateAdjacencies(facet, match.value()); + * for (each incident face of each star element): + * if (auto match = map.findAndRemove(link_face_key)) + * // Second sighting: wire the two star elements adjacent across this link face + * updateAdjacencies(current_slot, match.value()); * else - * // First time seeing this facet - store it - * map.insert(facet_key, facet_data); + * // First sighting: park which star element/face owns this link face + * map.insert(link_face_key, current_slot); * - * assert(map.allFacetsPaired()); // Validate all facets matched + * assert(map.allFacetsPaired()); // every link face matched exactly twice * ``` */ @@ -74,67 +112,69 @@ namespace detail { /** - * \brief Facet key for 2D (edge) or 3D (triangle) face matching. + * \brief A face of the link of the inserted vertex (the apex) * - * In 2D (triangles): key0 is the single non-shared vertex, key1 is unused - * In 3D (tetrahedra): (key0, key1) are the two non-shared vertices in sorted order + * - In 2D (triangle star): \a v0 is the single link vertex + * - In 3D (tetrahedron star): (v0, v1) are the two endpoints of a link edge, stored in sorted order. * - * The key is normalized so that equivalent facets from different elements - * produce identical keys for matching. + * The sorted ordering in 3D ensures that the same link edge produces an identical key + * for both incident tetrahedra sharing in the star of \a apex. */ template struct FacetKey { static constexpr IndexType INVALID = static_cast(-1); - IndexType key0 {INVALID}; ///< First key component (only component in 2D) - IndexType key1 {INVALID}; ///< Second key component (unused in 2D) + IndexType v0 {INVALID}; ///< Link vertex (2D) or smaller link-edge endpoint (3D) + IndexType v1 {INVALID}; ///< Unused in 2D; larger link-edge endpoint (3D) /// Default constructor - creates invalid key FacetKey() = default; /// Construct from key components (automatically sorted in 3D) - /// \param k0 First key component - /// \param k1 Second key component (unused in 2D, sorted in 3D) - FacetKey(IndexType k0, IndexType k1 = INVALID) : key0(k0), key1(k1) + /// \param a First link vertex / link-edge endpoint + /// \param b Second link-edge endpoint (unused in 2D, sorted in 3D) + FacetKey(IndexType a, IndexType b = INVALID) : v0(a), v1(b) { - // In 3D, ensure consistent ordering: key0 <= key1 - // This allows facets from different elements to match + // In 3D, ensure consistent ordering: v0 <= v1 + // This allows the same link edge to match from either incident tetrahedron. if constexpr(TDIM == 3) { - if(k1 < k0) + if(b < a) { - axom::utilities::swap(key0, key1); + axom::utilities::swap(v0, v1); } } } /// Equality comparison - bool operator==(const FacetKey& other) const { return key0 == other.key0 && key1 == other.key1; } + bool operator==(const FacetKey& other) const { return v0 == other.v0 && v1 == other.v1; } /// Inequality comparison bool operator!=(const FacetKey& other) const { return !(*this == other); } }; /** - * \brief Facet data stored in the hash table during face matching. + * \brief Identifies which star element generated a given link face, and where. * - * Associates a facet key with the element that owns it and the local - * face index within that element. + * On the first sighting of a link face, fixVertexNeighborhood() stores the star + * element that owns it together with the local face slot it occupies in that element. + * On the second sighting this is returned so the caller can wire the two + * star elements that are adjacent across the link face. */ template struct FacetData { static constexpr IndexType INVALID = static_cast(-1); - IndexType element_idx {INVALID}; ///< Element owning this facet - IndexType face_idx {INVALID}; ///< Local face index (0..VERTS_PER_ELEM-1) + IndexType element_idx {INVALID}; ///< Star element owning this link face + IndexType local_face {INVALID}; ///< Local face slot in that element (0..VERTS_PER_ELEM-1) /// Default constructor FacetData() = default; - /// Construct from element and face indices - FacetData(IndexType elem, IndexType face) : element_idx(elem), face_idx(face) { } + /// Construct from element and local face slot + FacetData(IndexType elem, IndexType face) : element_idx(elem), local_face(face) { } }; /** @@ -158,7 +198,9 @@ class FacetPairingMap using DataType = FacetData; static constexpr IndexType INVALID = static_cast(-1); - static constexpr IndexType EMPTY_SLOT = INVALID; + // Emptiness is encoded by the per-entry generation counter (see Entry below), + // not by a sentinel key, so there is no EMPTY_SLOT. TOMBSTONE_SLOT marks a + // deleted entry within an otherwise live probe chain. static constexpr IndexType TOMBSTONE_SLOT = INVALID - 1; /// Golden ratio constant for Knuth's multiplicative hashing @@ -180,8 +222,8 @@ class FacetPairingMap static thread_local std::vector s_table; static thread_local unsigned int s_generation; - std::size_t m_mask {0}; ///< Table size - 1 (for fast modulo via bitwise AND) - int m_pending_count {0}; ///< Number of unpaired facets currently in table + std::size_t m_mask {0}; ///< Table size - 1 (for fast modulo via bitwise AND) + int m_unpaired_count {0}; ///< Number of unpaired facets currently in table public: /// Default constructor @@ -203,10 +245,11 @@ class FacetPairingMap */ void prepareForInsertions(std::size_t expected_facet_count) { - // Heuristic sizing to maintain low load factor: - // - 2D: ~2 incident faces per element, 4x oversizing = 50% load - // - 3D: ~3 incident faces per element, 8/3x oversizing ≈ 37.5% load - // These values are tuned for performance based on empirical testing. + // Cold sizing targets a low load factor: ~(2*TDIM)x the facet count + // (2D: ~2 incident faces/elem; 3D: ~3 incident faces/elem). The next + // power-of-two round-up below lowers the realized load further. + // The table never shrinks, so after a large pairing pass, + // subsequent small stars run at a much lower load factor. const std::size_t target_slots = axom::utilities::max(8, (TDIM == 2 ? 4 : 8) * expected_facet_count); @@ -224,7 +267,7 @@ class FacetPairingMap } m_mask = s_table.size() - 1; - m_pending_count = 0; + m_unpaired_count = 0; // Advance generation counter (handles wraparound) advanceGeneration(); @@ -270,7 +313,7 @@ class FacetPairingMap { DataType result = entry.data; entry.data.element_idx = TOMBSTONE_SLOT; // Mark as deleted - --m_pending_count; + --m_unpaired_count; return result; } @@ -308,7 +351,7 @@ class FacetPairingMap target.key = key; target.data = data; target.generation = s_generation; - ++m_pending_count; + ++m_unpaired_count; return; } @@ -333,19 +376,22 @@ class FacetPairingMap } } - /// \brief Returns the number of unpaired facets currently in the table - /// \return Number of facets waiting for a match - int pendingCount() const { return m_pending_count; } + /// \brief Returns the number of link faces seen an odd number of times so far + /// \return Number of link faces still waiting for their second sighting + int pendingCount() const { return m_unpaired_count; } /** - * \brief Validates that all facets were successfully paired. + * \brief Checks that every link face was paired exactly twice. * - * Should be called after all insertions complete. A non-zero pending count - * indicates a topological error (non-manifold vertex or broken adjacency). + * Should be called after all insertions complete. For an interior apex this is + * the manifold condition: the link is closed (a loop in 2D, a sphere in 3D), + * so every link face is interior and is shared by exactly two star elements. + * A non-zero count indicates a link face seen only once, i.e. a link with boundary + * or a non-manifold/broken star. * - * \return true if all facets paired successfully (pending count is zero) + * \return true if all link faces paired (unpaired count is zero) */ - bool allFacetsPaired() const { return m_pending_count == 0; } + bool allFacetsPaired() const { return m_unpaired_count == 0; } /// \brief Returns the current generation counter value (for testing) unsigned int currentGeneration() const { return s_generation; } @@ -358,23 +404,28 @@ class FacetPairingMap private: /** - * \brief Compute hash for a facet key using Knuth's multiplicative method. + * \brief Compute hash for a link-face key using Knuth's multiplicative method. * * Uses the golden ratio constant (phi * 2^64) which provides excellent - * bit distribution. In 3D, the two key components are mixed together. + * bit distribution. In 3D, the two link-edge endpoints are mixed together. * - * \param key The facet key to hash + * \param key The link-face key to hash * \return Hash value + * + * \note In 2D the single link vertex is returned unmixed; the table relies on + * linear probing and power-of-two masking to spread sequential vertex ids. + * With spatially sorted (e.g. BRIO) insertion these ids are not random and can clump, + * so applying the same multiplicative mix in 2D may be worth measuring. */ std::size_t computeHash(const KeyType& key) const { - std::size_t seed = static_cast(key.key0); + std::size_t seed = static_cast(key.v0); if constexpr(TDIM == 3) { - // Mix in second key component using bit shifting and XOR - // This formula provides good avalanche properties - seed ^= static_cast(key.key1) + HASH_MULTIPLIER + (seed << 6) + (seed >> 2); + // Mix in the second link-edge endpoint using bit shifting and XOR. + // This formula provides good avalanche properties. + seed ^= static_cast(key.v1) + HASH_MULTIPLIER + (seed << 6) + (seed >> 2); } return seed; diff --git a/src/axom/slam/tests/slam_detail_FacetPairingMap.cpp b/src/axom/slam/tests/slam_detail_FacetPairingMap.cpp index 02e6688c0e..4a0935f93b 100644 --- a/src/axom/slam/tests/slam_detail_FacetPairingMap.cpp +++ b/src/axom/slam/tests/slam_detail_FacetPairingMap.cpp @@ -23,18 +23,18 @@ TEST(slam_detail_FacetKey, construction_2d) // Default construction KeyType key1; - EXPECT_EQ(key1.key0, KeyType::INVALID); - EXPECT_EQ(key1.key1, KeyType::INVALID); + EXPECT_EQ(key1.v0, KeyType::INVALID); + EXPECT_EQ(key1.v1, KeyType::INVALID); // Construction with single key (2D) KeyType key2(42); - EXPECT_EQ(key2.key0, 42); - EXPECT_EQ(key2.key1, KeyType::INVALID); + EXPECT_EQ(key2.v0, 42); + EXPECT_EQ(key2.v1, KeyType::INVALID); - // key1 parameter should be ignored in 2D + // The second component is ignored in 2D (only the single link vertex matters) KeyType key3(42, 100); - EXPECT_EQ(key3.key0, 42); - // key1 is present but not used for matching in 2D + EXPECT_EQ(key3.v0, 42); + // v1 is present but not used for matching in 2D } TEST(slam_detail_FacetKey, construction_3d) @@ -43,18 +43,18 @@ TEST(slam_detail_FacetKey, construction_3d) // Default construction KeyType key1; - EXPECT_EQ(key1.key0, KeyType::INVALID); - EXPECT_EQ(key1.key1, KeyType::INVALID); + EXPECT_EQ(key1.v0, KeyType::INVALID); + EXPECT_EQ(key1.v1, KeyType::INVALID); // Construction with sorted keys KeyType key2(10, 20); - EXPECT_EQ(key2.key0, 10); - EXPECT_EQ(key2.key1, 20); + EXPECT_EQ(key2.v0, 10); + EXPECT_EQ(key2.v1, 20); // Construction with unsorted keys (should auto-sort) KeyType key3(20, 10); - EXPECT_EQ(key3.key0, 10); // Sorted - EXPECT_EQ(key3.key1, 20); // Sorted + EXPECT_EQ(key3.v0, 10); // Sorted + EXPECT_EQ(key3.v1, 20); // Sorted } TEST(slam_detail_FacetKey, equality_2d) @@ -94,12 +94,12 @@ TEST(slam_detail_FacetData, construction) // Default construction DataType data1; EXPECT_EQ(data1.element_idx, DataType::INVALID); - EXPECT_EQ(data1.face_idx, DataType::INVALID); + EXPECT_EQ(data1.local_face, DataType::INVALID); // Construction with values DataType data2(100, 2); EXPECT_EQ(data2.element_idx, 100); - EXPECT_EQ(data2.face_idx, 2); + EXPECT_EQ(data2.local_face, 2); } //------------------------------------------------------------------------------ @@ -129,7 +129,7 @@ TEST(slam_detail_FacetPairingMap, basic_2d_insert_and_match) ASSERT_TRUE(match.has_value()); EXPECT_EQ(match->element_idx, 100); - EXPECT_EQ(match->face_idx, 1); + EXPECT_EQ(match->local_face, 1); EXPECT_EQ(map.pendingCount(), 0); EXPECT_TRUE(map.allFacetsPaired()); } @@ -161,7 +161,7 @@ TEST(slam_detail_FacetPairingMap, multiple_2d_facets) ASSERT_TRUE(match.has_value()); EXPECT_EQ(match->element_idx, i); - EXPECT_EQ(match->face_idx, 0); + EXPECT_EQ(match->local_face, 0); } EXPECT_EQ(map.pendingCount(), 0); @@ -216,7 +216,7 @@ TEST(slam_detail_FacetPairingMap, basic_3d_insert_and_match) ASSERT_TRUE(match.has_value()); EXPECT_EQ(match->element_idx, 100); - EXPECT_EQ(match->face_idx, 1); + EXPECT_EQ(match->local_face, 1); EXPECT_EQ(map.pendingCount(), 0); } @@ -248,7 +248,7 @@ TEST(slam_detail_FacetPairingMap, multiple_3d_facets) ASSERT_TRUE(match.has_value()) << "Failed to find key (" << i << ", " << (i + 1000) << ")"; EXPECT_EQ(match->element_idx, i); - EXPECT_EQ(match->face_idx, 2); + EXPECT_EQ(match->local_face, 2); } EXPECT_EQ(map.pendingCount(), 0); @@ -411,7 +411,7 @@ TEST(slam_detail_FacetPairingMap, multiple_prepare_cycles) auto match = map.findAndRemove(key); ASSERT_TRUE(match.has_value()); EXPECT_EQ(match->element_idx, i); - EXPECT_EQ(match->face_idx, cycle); + EXPECT_EQ(match->local_face, cycle); } EXPECT_EQ(map.pendingCount(), 0); From 2e9461d23b07353b67559027b64076f7051ee4a2 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 16 Jun 2026 19:18:18 -0700 Subject: [PATCH 520/986] slam: Renames key/value types for FacetPairingMap To better reflect that the map is keyed on a facet of the vertex's link, and value for the map is a (face of a) simplex in the star of that vertex. --- src/axom/slam/mesh_struct/IA_impl.hpp | 14 ++--- .../mesh_struct/detail/FacetPairingMap.hpp | 26 ++++----- .../tests/slam_detail_FacetPairingMap.cpp | 54 +++++++++---------- 3 files changed, 47 insertions(+), 47 deletions(-) diff --git a/src/axom/slam/mesh_struct/IA_impl.hpp b/src/axom/slam/mesh_struct/IA_impl.hpp index 0126b0cb1a..5cf17c5ccd 100644 --- a/src/axom/slam/mesh_struct/IA_impl.hpp +++ b/src/axom/slam/mesh_struct/IA_impl.hpp @@ -636,7 +636,7 @@ typename IAMesh::IndexType IAMesh::reuseElement(In * An incident face contains the apex vertex; removing the apex leaves a face of its link: * - 2D (triangles): the single non-apex (link) vertex of the incident edge. * - 3D (tetrahedra): the two non-apex (link) vertices of the incident triangle, - * i.e. the endpoints of the link edge (auto-sorted by FacetKey). + * i.e. the endpoints of the link edge (auto-sorted by LinkFace). * * \param element_idx Index of the star element * \param face_idx Local face index (1..VERTS_PER_ELEM-1; local face 0 is the link @@ -678,7 +678,7 @@ IAMesh::createFacetKey(IndexType element_idx, int face_idx) const v1 = element_vertices[1]; break; } - return KeyType(v0, v1); // Auto-sorted by FacetKey constructor + return KeyType(v0, v1); // Auto-sorted by LinkFace constructor } } @@ -759,8 +759,8 @@ void IAMesh::fixVertexNeighborhood(IndexType apex, const std::vector& star_elements) { using FacetMap = detail::FacetPairingMap; - using FacetKey = typename FacetMap::KeyType; - using FacetData = typename FacetMap::DataType; + using LinkFaceKey = typename FacetMap::KeyType; + using StarSlot = typename FacetMap::DataType; // Prepare the link-face pairing table for the expected number of incident faces FacetMap facet_map; @@ -816,10 +816,10 @@ void IAMesh::fixVertexNeighborhood(IndexType apex, ++num_incident_faces; // Build the link-face key for matching - const FacetKey link_face_key = createFacetKey(element_idx, local_face_idx); + const LinkFaceKey link_face_key = createFacetKey(element_idx, local_face_idx); // Try to match the other star element sharing this link face - if(auto match = facet_map.findAndRemove(link_face_key)) + if(auto match = facet_map.findAndExtract(link_face_key)) { // Second sighting: wire the two star elements adjacent across this link face SLIC_ASSERT_MSG(match->element_idx != element_idx, @@ -832,7 +832,7 @@ void IAMesh::fixVertexNeighborhood(IndexType apex, else { // First sighting: insert the star element/face that owns this link face - FacetData parked_slot(element_idx, local_face_idx); + StarSlot parked_slot(element_idx, local_face_idx); facet_map.insert(link_face_key, parked_slot); } } diff --git a/src/axom/slam/mesh_struct/detail/FacetPairingMap.hpp b/src/axom/slam/mesh_struct/detail/FacetPairingMap.hpp index 8a99bc3f8a..22a7d36215 100644 --- a/src/axom/slam/mesh_struct/detail/FacetPairingMap.hpp +++ b/src/axom/slam/mesh_struct/detail/FacetPairingMap.hpp @@ -85,7 +85,7 @@ * map.prepareForInsertions(expected_facet_count); * * for (each incident face of each star element): - * if (auto match = map.findAndRemove(link_face_key)) + * if (auto match = map.findAndExtract(link_face_key)) * // Second sighting: wire the two star elements adjacent across this link face * updateAdjacencies(current_slot, match.value()); * else @@ -121,7 +121,7 @@ namespace detail * for both incident tetrahedra sharing in the star of \a apex. */ template -struct FacetKey +struct LinkFace { static constexpr IndexType INVALID = static_cast(-1); @@ -129,12 +129,12 @@ struct FacetKey IndexType v1 {INVALID}; ///< Unused in 2D; larger link-edge endpoint (3D) /// Default constructor - creates invalid key - FacetKey() = default; + LinkFace() = default; /// Construct from key components (automatically sorted in 3D) /// \param a First link vertex / link-edge endpoint /// \param b Second link-edge endpoint (unused in 2D, sorted in 3D) - FacetKey(IndexType a, IndexType b = INVALID) : v0(a), v1(b) + LinkFace(IndexType a, IndexType b = INVALID) : v0(a), v1(b) { // In 3D, ensure consistent ordering: v0 <= v1 // This allows the same link edge to match from either incident tetrahedron. @@ -148,10 +148,10 @@ struct FacetKey } /// Equality comparison - bool operator==(const FacetKey& other) const { return v0 == other.v0 && v1 == other.v1; } + bool operator==(const LinkFace& other) const { return v0 == other.v0 && v1 == other.v1; } /// Inequality comparison - bool operator!=(const FacetKey& other) const { return !(*this == other); } + bool operator!=(const LinkFace& other) const { return !(*this == other); } }; /** @@ -163,7 +163,7 @@ struct FacetKey * star elements that are adjacent across the link face. */ template -struct FacetData +struct StarFacetSlot { static constexpr IndexType INVALID = static_cast(-1); @@ -171,10 +171,10 @@ struct FacetData IndexType local_face {INVALID}; ///< Local face slot in that element (0..VERTS_PER_ELEM-1) /// Default constructor - FacetData() = default; + StarFacetSlot() = default; /// Construct from element and local face slot - FacetData(IndexType elem, IndexType face) : element_idx(elem), local_face(face) { } + StarFacetSlot(IndexType elem, IndexType face) : element_idx(elem), local_face(face) { } }; /** @@ -194,8 +194,8 @@ template class FacetPairingMap { public: - using KeyType = FacetKey; - using DataType = FacetData; + using KeyType = LinkFace; + using DataType = StarFacetSlot; static constexpr IndexType INVALID = static_cast(-1); // Emptiness is encoded by the per-entry generation counter (see Entry below), @@ -285,7 +285,7 @@ class FacetPairingMap * \note This uses linear probing with generational marking. Empty slots * (including stale generations) terminate the search. */ - std::optional findAndRemove(const KeyType& key) + std::optional findAndExtract(const KeyType& key) { const std::size_t hash = computeHash(key); std::size_t slot = hash & m_mask; @@ -325,7 +325,7 @@ class FacetPairingMap /** * \brief Insert a new facet into the table. * - * Should only be called after findAndRemove() returns empty for this key. + * Should only be called after findAndExtract() returns empty for this key. * Calling insert() for an already-present key is an error (assertion). * * \param key The facet key diff --git a/src/axom/slam/tests/slam_detail_FacetPairingMap.cpp b/src/axom/slam/tests/slam_detail_FacetPairingMap.cpp index 4a0935f93b..2ff4790046 100644 --- a/src/axom/slam/tests/slam_detail_FacetPairingMap.cpp +++ b/src/axom/slam/tests/slam_detail_FacetPairingMap.cpp @@ -14,12 +14,12 @@ using namespace axom; using namespace axom::slam; //------------------------------------------------------------------------------ -// FacetKey Tests +// LinkFace Tests //------------------------------------------------------------------------------ -TEST(slam_detail_FacetKey, construction_2d) +TEST(slam_detail_LinkFace, construction_2d) { - using KeyType = axom::slam::detail::FacetKey<2, int>; + using KeyType = axom::slam::detail::LinkFace<2, int>; // Default construction KeyType key1; @@ -37,9 +37,9 @@ TEST(slam_detail_FacetKey, construction_2d) // v1 is present but not used for matching in 2D } -TEST(slam_detail_FacetKey, construction_3d) +TEST(slam_detail_LinkFace, construction_3d) { - using KeyType = axom::slam::detail::FacetKey<3, int>; + using KeyType = axom::slam::detail::LinkFace<3, int>; // Default construction KeyType key1; @@ -57,9 +57,9 @@ TEST(slam_detail_FacetKey, construction_3d) EXPECT_EQ(key3.v1, 20); // Sorted } -TEST(slam_detail_FacetKey, equality_2d) +TEST(slam_detail_LinkFace, equality_2d) { - using KeyType = axom::slam::detail::FacetKey<2, int>; + using KeyType = axom::slam::detail::LinkFace<2, int>; KeyType key1(42); KeyType key2(42); @@ -70,9 +70,9 @@ TEST(slam_detail_FacetKey, equality_2d) EXPECT_TRUE(key1 != key3); } -TEST(slam_detail_FacetKey, equality_3d_with_sorting) +TEST(slam_detail_LinkFace, equality_3d_with_sorting) { - using KeyType = axom::slam::detail::FacetKey<3, int>; + using KeyType = axom::slam::detail::LinkFace<3, int>; // Same keys in different order should match KeyType key1(10, 20); @@ -84,12 +84,12 @@ TEST(slam_detail_FacetKey, equality_3d_with_sorting) } //------------------------------------------------------------------------------ -// FacetData Tests +// StarFacetSlot Tests //------------------------------------------------------------------------------ -TEST(slam_detail_FacetData, construction) +TEST(slam_detail_StarFacetSlot, construction) { - using DataType = axom::slam::detail::FacetData; + using DataType = axom::slam::detail::StarFacetSlot; // Default construction DataType data1; @@ -125,7 +125,7 @@ TEST(slam_detail_FacetPairingMap, basic_2d_insert_and_match) // Match with same key KeyType key2(42); - auto match = map.findAndRemove(key2); + auto match = map.findAndExtract(key2); ASSERT_TRUE(match.has_value()); EXPECT_EQ(match->element_idx, 100); @@ -157,7 +157,7 @@ TEST(slam_detail_FacetPairingMap, multiple_2d_facets) for(int i = 0; i < 10; ++i) { KeyType key(i * 10); - auto match = map.findAndRemove(key); + auto match = map.findAndExtract(key); ASSERT_TRUE(match.has_value()); EXPECT_EQ(match->element_idx, i); @@ -184,7 +184,7 @@ TEST(slam_detail_FacetPairingMap, not_found_2d) // Try to find a different key KeyType key2(43); - auto match = map.findAndRemove(key2); + auto match = map.findAndExtract(key2); EXPECT_FALSE(match.has_value()); EXPECT_EQ(map.pendingCount(), 1); // Original still there @@ -212,7 +212,7 @@ TEST(slam_detail_FacetPairingMap, basic_3d_insert_and_match) // Match with equivalent key (different order) KeyType key2(20, 10); // Should match (10, 20) after sorting - auto match = map.findAndRemove(key2); + auto match = map.findAndExtract(key2); ASSERT_TRUE(match.has_value()); EXPECT_EQ(match->element_idx, 100); @@ -244,7 +244,7 @@ TEST(slam_detail_FacetPairingMap, multiple_3d_facets) { // Use reversed order to test sorting KeyType key(i + 1000, i); - auto match = map.findAndRemove(key); + auto match = map.findAndExtract(key); ASSERT_TRUE(match.has_value()) << "Failed to find key (" << i << ", " << (i + 1000) << ")"; EXPECT_EQ(match->element_idx, i); @@ -271,7 +271,7 @@ TEST(slam_detail_FacetPairingMap, 3d_key_ordering_invariant) // Should match with reversed order KeyType key2(10, 5); - auto match = map.findAndRemove(key2); + auto match = map.findAndExtract(key2); ASSERT_TRUE(match.has_value()); EXPECT_EQ(match->element_idx, 100); @@ -283,7 +283,7 @@ TEST(slam_detail_FacetPairingMap, 3d_key_ordering_invariant) // Match with normal order KeyType key4(10, 15); - auto match2 = map.findAndRemove(key4); + auto match2 = map.findAndExtract(key4); ASSERT_TRUE(match2.has_value()); EXPECT_EQ(match2->element_idx, 200); @@ -326,7 +326,7 @@ TEST(slam_detail_FacetPairingMap, collision_handling) for(int idx : indices) { - auto match = map.findAndRemove(keys[idx]); + auto match = map.findAndExtract(keys[idx]); ASSERT_TRUE(match.has_value()) << "Failed to find key at index " << idx; EXPECT_EQ(match->element_idx, idx); } @@ -351,7 +351,7 @@ TEST(slam_detail_FacetPairingMap, tombstone_reuse) map.insert(key1, data1); KeyType key2(i * 10); - auto match = map.findAndRemove(key2); + auto match = map.findAndExtract(key2); ASSERT_TRUE(match.has_value()); } @@ -371,7 +371,7 @@ TEST(slam_detail_FacetPairingMap, tombstone_reuse) for(int i = 0; i < 5; ++i) { KeyType key(i * 10 + 5); - auto match = map.findAndRemove(key); + auto match = map.findAndExtract(key); ASSERT_TRUE(match.has_value()); EXPECT_EQ(match->element_idx, i + 100); } @@ -408,7 +408,7 @@ TEST(slam_detail_FacetPairingMap, multiple_prepare_cycles) for(int i = 0; i < 5; ++i) { KeyType key(cycle * 100 + i); - auto match = map.findAndRemove(key); + auto match = map.findAndExtract(key); ASSERT_TRUE(match.has_value()); EXPECT_EQ(match->element_idx, i); EXPECT_EQ(match->local_face, cycle); @@ -439,7 +439,7 @@ TEST(slam_detail_FacetPairingMap, generation_isolation) // Try to find old key - should not be found KeyType key2(42); - auto match = map.findAndRemove(key2); + auto match = map.findAndExtract(key2); EXPECT_FALSE(match.has_value()); } @@ -497,7 +497,7 @@ TEST(slam_detail_FacetPairingMap, empty_map_operations) // Try to find in empty map KeyType key(42); - auto match = map.findAndRemove(key); + auto match = map.findAndExtract(key); EXPECT_FALSE(match.has_value()); EXPECT_EQ(map.pendingCount(), 0); @@ -519,7 +519,7 @@ TEST(slam_detail_FacetPairingMap, single_facet) EXPECT_EQ(map.pendingCount(), 1); - auto match = map.findAndRemove(key); + auto match = map.findAndExtract(key); ASSERT_TRUE(match.has_value()); EXPECT_EQ(map.pendingCount(), 0); } @@ -548,7 +548,7 @@ TEST(slam_detail_FacetPairingMap, large_vertex_ids) for(int i = 0; i < 10; ++i) { KeyType key(LARGE_ID + i, LARGE_ID + i + 1); - auto match = map.findAndRemove(key); + auto match = map.findAndExtract(key); ASSERT_TRUE(match.has_value()); } From 6debc9fb2dd54cc85681bc42051f8c58eb531efb Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 17 Jun 2026 14:40:19 -0700 Subject: [PATCH 521/986] Improves docs on Delaunay and its helpers --- src/axom/quest/Delaunay.hpp | 190 +++++++++++++----- .../quest_delaunay_circumsphere.cpp | 8 + .../quest/detail/DelaunayElementFinder.hpp | 56 ++++-- .../quest/detail/DelaunayInsertionHelper.hpp | 55 +++++ .../mesh_struct/detail/FacetPairingMap.hpp | 3 +- 5 files changed, 238 insertions(+), 74 deletions(-) diff --git a/src/axom/quest/Delaunay.hpp b/src/axom/quest/Delaunay.hpp index 501bd86a50..5b2c82cbe3 100644 --- a/src/axom/quest/Delaunay.hpp +++ b/src/axom/quest/Delaunay.hpp @@ -193,9 +193,11 @@ class Delaunay using InsertionHelper = detail::DelaunayInsertionHelper; - /// Number of adjacency layers to search around visited simplices when a directed walk fails. - /// If a walk cycles or reaches its step budget, we probe this many layers of neighbors - /// before giving up. Value of 2 balances coverage vs. cost for typical meshes. + /** + * Number of adjacency layers to search around visited simplices when a directed walk fails. + * If a walk cycles or reaches its step budget, we probe this many layers of neighbors + * before giving up. Value of 2 balances coverage vs. cost for typical meshes. + */ static constexpr int WALK_NEIGHBORHOOD_LAYERS = 2; /** @@ -269,21 +271,27 @@ class Delaunay Delaunay(Delaunay&& other) : Delaunay() { *this = std::move(other); } Delaunay& operator=(Delaunay&& other); - /// \brief Returns statistics about insertion operations - /// \return InsertionStats containing cavity size metrics + /** + * \brief Returns statistics about insertion operations + * \return InsertionStats containing cavity size metrics + */ InsertionStats getInsertionStats() const { return {m_num_insertions, m_total_removed_elements, m_max_removed_elements}; } - /// \brief Enable or disable collection of point location statistics - /// \param enabled If true, track walk performance metrics (adds minimal overhead) - /// \note Stats collection is disabled by default + /** + * \brief Enable or disable collection of point location statistics + * \param enabled If true, track walk performance metrics (adds minimal overhead) + * \note Stats collection is disabled by default + */ void setCollectPointLocationStats(bool enabled) { m_collect_location_stats = enabled; } - /// \brief Returns statistics about point location query performance - /// \return PointLocationStats containing walk performance metrics - /// \note Returns zeros if setCollectPointLocationStats(true) was not called + /** + * \brief Returns statistics about point location query performance + * \return PointLocationStats containing walk performance metrics + * \note Returns zeros if setCollectPointLocationStats(true) was not called + */ PointLocationStats getPointLocationStats() const { return {m_num_walk_calls, @@ -294,10 +302,12 @@ class Delaunay m_max_walk_steps}; } - /// \brief Controls the amount of validation performed around each point insertion - /// - /// \note This is intended for debugging. `InsertionValidationMode::Full` is a diagnostic mode - /// and should not be enabled in performance-sensitive runs. + /** + * \brief Controls the amount of validation performed around each point insertion + * + * \note This is intended for debugging. `InsertionValidationMode::Full` is a diagnostic mode + * and should not be enabled in performance-sensitive runs. + */ void setInsertionValidationMode(InsertionValidationMode mode) { m_insertion_validation_mode = mode; @@ -315,13 +325,16 @@ class Delaunay */ void initializeBoundary(const BoundingBox& bb); - /// \brief Reserve storage for an expected number of inserted points. - /// - /// Uses a dimension-specific heuristic for the total simplex count so large - /// bulk-builds can avoid repeated mesh-container reallocations. - /// \param num_points Expected number of points to be inserted - /// \note Based on Euler characteristic: 2D expects ~2n triangles for n points, - /// 3D expects ~6n tetrahedra. Heuristics account for bounding box and over-tessellation. + /** + * \brief Reserve storage for an expected number of inserted points. + * + * Uses a dimension-specific heuristic for the total simplex count so large + * bulk-builds can avoid repeated mesh-container reallocations. + * + * \param num_points Expected number of points to be inserted + * \note Based on Euler characteristic: 2D expects ~2n triangles for n points, + * 3D expects ~6n tetrahedra. Heuristics account for bounding box and over-tessellation. + */ void reserveForPointCount(IndexType num_points) { if(!m_has_boundary || num_points <= 0) @@ -366,9 +379,11 @@ class Delaunay */ void insertPoint(const PointType& new_pt); - /// \brief Retrieves the geometric element (triangle or tetrahedron) at the given index - /// \param element_index The index of the element to retrieve - /// \return Triangle (2D) or Tetrahedron (3D) with vertex coordinates + /** + * \brief Retrieves the geometric element (triangle or tetrahedron) at the given index + * \param element_index The index of the element to retrieve + * \return Triangle (2D) or Tetrahedron (3D) with vertex coordinates + */ ElementType getElement(int element_index) const { const auto verts = m_mesh.boundaryVertices(element_index); @@ -442,20 +457,24 @@ class Delaunay */ bool isConforming(bool verboseOutput = false) const; - /// \brief Returns true if an element is active and can participate in point-location queries - /// - /// \param element_idx The element index to check - /// \return true if element is valid (not a deleted/recycled slot) - /// \note During insertion, all active elements have valid vertices. After removeBoundary(), - /// the mesh is compacted so all element indices are valid. + /** + * \brief Returns true if an element is active and can participate in point-location queries + * + * \param element_idx The element index to check + * \return true if element is valid (not a deleted/recycled slot) + * \note During insertion, all active elements have valid vertices. After removeBoundary(), + * the mesh is compacted so all element indices are valid. + */ bool isSearchableElement(IndexType element_idx) const; - /// \brief Locate the element containing a query point using directed walk - /// - /// \param query_pt The point to locate - /// \param warnOnInvalid If true, log a warning if location fails - /// \return Element index containing the point, or INVALID_INDEX if not found - /// \note Uses grid-seeded directed walk for O(n^(1/DIM)) expected performance + /** + * \brief Locate the element containing a query point using directed walk + * + * \param query_pt The point to locate + * \param warnOnInvalid If true, log a warning if location fails + * \return Element index containing the point, or INVALID_INDEX if not found + * \note Uses grid-seeded directed walk for O(n^(1/DIM)) expected performance + */ IndexType findContainingElement(const PointType& query_pt, bool warnOnInvalid = true) const; /** @@ -468,16 +487,18 @@ class Delaunay */ BaryCoordType getBaryCoords(IndexType element_idx, const PointType& q_pt) const; - /// \brief Returns initial seed elements for cavity expansion based on point location - /// - /// If the query point lies on a face/edge of the containing element (indicated by a - /// barycentric coordinate near zero), the neighbor across that face is also included - /// as a seed. This ensures the cavity expansion starts from all elements that might - /// contain the point in their circumsphere. - /// - /// \param element_idx The element containing (or nearest to) the query point - /// \param bary_coord Barycentric coordinates of the query point in that element - /// \return Array of seed element indices (at least 1, possibly more if point is on boundary feature) + /** + * \brief Returns initial seed elements for cavity expansion based on point location + * + * If the query point lies on a face/edge of the containing element (indicated by a + * barycentric coordinate near zero), the neighbor across that face is also included + * as a seed. This ensures the cavity expansion starts from all elements that might + * contain the point in their circumsphere. + * + * \param element_idx The element containing (or nearest to) the query point + * \param bary_coord Barycentric coordinates of the query point in that element + * \return Array of seed element indices (at least 1, possibly more if point is on boundary feature) + */ IndexArray getSeedElements(IndexType element_idx, const BaryCoordType& bary_coord) const { IndexArray seed_elements; @@ -501,38 +522,104 @@ class Delaunay return seed_elements; } - /// \brief Classify a value as positive, negative, or zero within tolerance - /// \param value The value to classify - /// \param tolerance The tolerance for considering the value as zero - /// \return +1 if value > tolerance, -1 if value < -tolerance, 0 otherwise + /** + * \brief Classify a value as positive, negative, or zero within tolerance + * + * \param value The value to classify + * \param tolerance The tolerance for considering the value as zero + * \return +1 if value > tolerance, -1 if value < -tolerance, 0 otherwise + */ static int signWithTolerance(double value, double tolerance) { return value > tolerance ? 1 : (value < -tolerance ? -1 : 0); } + /** + * \brief Builds the circumsphere (center and squared radius) of a mesh element. + * + * \param mesh The triangulation containing the element + * \param element_idx The element whose circumsphere is computed + * \return The precomputed circumsphere for repeated in-sphere distance tests + */ static CircumsphereEval evaluateCircumsphereOnMesh(const IAMeshType& mesh, IndexType element_idx); + /** + * \brief Scale-aware tolerance for comparing a squared distance against a + * circumsphere's squared radius, used to classify on-sphere cases robustly. + * + * \param sphere The circumsphere being tested against + * \param x The query point + * \param distance_sq The squared distance from \a x to the sphere center + * \return The tolerance (in squared-distance units) for the on-sphere band + */ static double sphereSquaredDistanceTolerance(const CircumsphereEval& sphere, const PointType& x, double distance_sq); + /** + * \brief Classifies the query point against an element's circumsphere via the + * in-sphere determinant: ON_POSITIVE_SIDE (outside), ON_NEGATIVE_SIDE (inside), + * or ON_BOUNDARY (on the sphere, within tolerance). + * + * \param mesh The triangulation containing the element + * \param q The query point + * \param element_idx The element whose circumsphere is tested + */ static int inSphereOrientationOnMesh(const IAMeshType& mesh, const PointType& q, IndexType element_idx); + /** + * \brief Returns the raw (unclassified) in-sphere determinant of \a q against + * the circumsphere of \a element_idx. The sign indicates inside/outside. + * The caller applies its own tolerance (see inSphereOrientationOnMesh()). + * + * \param mesh The triangulation containing the element + * \param q The query point + * \param element_idx The element whose circumsphere is tested + */ static double inSphereDeterminantOnMesh(const IAMeshType& mesh, const PointType& q, IndexType element_idx); + /** + * \brief Returns a human-readable name for a primal::OrientationResult value + * (for diagnostics and warning messages). + * + * \param result An orientation result code (ON_POSITIVE_SIDE/ON_BOUNDARY/ON_NEGATIVE_SIDE) + */ static const char* orientationResultName(int result); + /** + * \brief Convenience predicate: true if \a q is inside element \a element_idx's + * circumsphere (and, when \a includeBoundary is set, on it within tolerance). + * + * \param mesh The triangulation containing the element + * \param q The query point + * \param element_idx The element whose circumsphere is tested + * \param includeBoundary If true, on-sphere points count as contained + */ static bool isPointInSphereOnMesh(const IAMeshType& mesh, const PointType& q, IndexType element_idx, bool includeBoundary); + /** + * \brief Classifies an orientation determinant against a tolerance into a + * primal::OrientationResult (ON_POSITIVE_SIDE/ON_BOUNDARY/ON_NEGATIVE_SIDE). + * + * \param det The raw orientation determinant + * \param tol The scale-aware tolerance for the ON_BOUNDARY band + */ static int classifyOrientationDeterminant(double det, double tol); + /** + * \brief Computes the signed orientation determinant of an element together + * with its scale-aware tolerance and classification. + * + * \param element_idx The element to evaluate + * \return An OrientationEval bundling the determinant, tolerance, and class + */ OrientationEval evaluateElementOrientationDeterminant(IndexType element_idx) const; private: @@ -628,6 +715,7 @@ class Delaunay /** * \brief Helper function to fill the array with the initial mesh. + * * \details create a rectangle for 2D, cube for 3D, and fill the array with the mesh data. */ void generateInitialMesh(std::vector& points, diff --git a/src/axom/quest/benchmarks/quest_delaunay_circumsphere.cpp b/src/axom/quest/benchmarks/quest_delaunay_circumsphere.cpp index dace130735..cf2493f40d 100644 --- a/src/axom/quest/benchmarks/quest_delaunay_circumsphere.cpp +++ b/src/axom/quest/benchmarks/quest_delaunay_circumsphere.cpp @@ -58,6 +58,14 @@ double unitInterval(std::uint64_t bits) return static_cast(bits & 0xFFFFFFFFu) / static_cast(0x100000000ULL); } +// Advances a 64-bit linear congruential generator (LCG): +// x_{n+1} = (a * x_n + c) mod 2^64 +// with Knuth's multiplier a = 6364136223846793005 and increment c = 1442695040888963407. +// The modulus is 2^64 via unsigned wraparound. +// By the Hull-Dobell theorem this generator has full period 2^64 because: +// c is odd (hence coprime to the modulus), +// (a - 1) is divisible by 4 (the modulus's only prime factor, 2, with the extra factor required for 4 | m). +// We use it here to generate reproducible pseudo-random sample geometry for the benchmark. std::uint64_t stepLcg(std::uint64_t state) { return state * 6364136223846793005ULL + 1442695040888963407ULL; diff --git a/src/axom/quest/detail/DelaunayElementFinder.hpp b/src/axom/quest/detail/DelaunayElementFinder.hpp index c911d7cd77..d874cf9aab 100644 --- a/src/axom/quest/detail/DelaunayElementFinder.hpp +++ b/src/axom/quest/detail/DelaunayElementFinder.hpp @@ -38,6 +38,12 @@ namespace detail * The grid adapts to point set size: resolution is O(n^(1/DIM)) to maintain * constant expected bin occupancy. This provides O(1) expected distance (in hops) * from grid cell to query point's containing simplex. + * + * \tparam DIM The spatial dimension (2 or 3) + * \tparam PointType The point type used for query coordinates, e.g. primal::Point + * \tparam IAMeshType The topological mesh data structure type (slam::IAMesh) being queried + * \tparam BoundingBox The bounding-box type spanning the triangulation, e.g. primal::BoundingBox + * \tparam IndexType The integer type indexing vertices, elements, and lattice bins */ template class DelaunayElementFinder @@ -48,11 +54,13 @@ class DelaunayElementFinder explicit DelaunayElementFinder() = default; - /// \brief Rebuild the spatial bin structure based on current vertex positions - /// - /// \param mesh The current Delaunay mesh - /// \param bb The bounding box of the triangulation - /// \note Grid resolution adapts to vertex count: ~n^(1/DIM) / 4 bins per dimension + /** + * \brief Rebuild the spatial bin structure based on current vertex positions + * + * \param mesh The current Delaunay mesh + * \param bb The bounding box of the triangulation + * \note Grid resolution adapts to vertex count: ~n^(1/DIM) / 4 bins per dimension + */ void recomputeGrid(const IAMeshType& mesh, const BoundingBox& bb) { const auto& verts = mesh.vertices(); @@ -100,13 +108,15 @@ class DelaunayElementFinder } } - /// \brief Find vertices in bins near a query point, sorted by distance - /// - /// \param mesh The current Delaunay mesh - /// \param pt The query point - /// \param[out] nearby_vertices Output array of vertex indices sorted by distance to pt - /// \param search_radius Number of bin layers to search (1 = immediate neighbors, 2 = two layers, etc.) - /// \param max_candidates Maximum number of vertices to return + /** + * \brief Find vertices in bins near a query point, sorted by distance + * + * \param mesh The current Delaunay mesh + * \param pt The query point + * \param[out] nearby_vertices Output array of vertex indices sorted by distance to pt + * \param search_radius Number of bin layers to search (1 = immediate neighbors, 2 = two layers, etc.) + * \param max_candidates Maximum number of vertices to return + */ inline void getNearbyVertices(const IAMeshType& mesh, const PointType& pt, std::vector& nearby_vertices, @@ -205,10 +215,12 @@ class DelaunayElementFinder } } - /// \brief Get the vertex stored in the bin containing a query point - /// - /// \param pt The query point - /// \return Vertex index of a representative vertex in that bin (or INVALID_INDEX if bin is empty) + /** + * \brief Get the vertex stored in the bin containing a query point + * + * \param pt The query point + * \return Vertex index of a representative vertex in that bin (or INVALID_INDEX if bin is empty) + */ inline IndexType getNearbyVertex(const PointType& pt) const { const auto cell = m_lattice.gridCell(pt); @@ -226,11 +238,13 @@ class DelaunayElementFinder return max_radius; } - /// \brief Update a bin to reference a newly inserted vertex - /// - /// \param pt The position of the newly inserted vertex - /// \param vertex_id The index of the newly inserted vertex - /// \note Called after each successful point insertion + /** + * \brief Update a bin to reference a newly inserted vertex + * + * \param pt The position of the newly inserted vertex + * \param vertex_id The index of the newly inserted vertex + * \note Called after each successful point insertion + */ inline void updateBin(const PointType& pt, IndexType vertex_id) { const auto cell = m_lattice.gridCell(pt); diff --git a/src/axom/quest/detail/DelaunayInsertionHelper.hpp b/src/axom/quest/detail/DelaunayInsertionHelper.hpp index 4e6011e39f..526e24bcea 100644 --- a/src/axom/quest/detail/DelaunayInsertionHelper.hpp +++ b/src/axom/quest/detail/DelaunayInsertionHelper.hpp @@ -47,6 +47,13 @@ namespace detail * - boundary_facets: Faces between cavity and non-cavity elements * - inserted_elems: New simplices created by connecting new point to boundary * - containing_element, containing_bary, seed_elements_debug: For diagnostics + * + * \tparam DIM The spatial dimension (2 for triangles, 3 for tetrahedra) + * \tparam PointType The point type used for the new (query) point, e.g. primal::Point + * \tparam BaryCoordType The barycentric-coordinate type used during point location + * \tparam IndexType The integer type indexing vertices and elements of the mesh + * \tparam IndexArray The container type used to pass a set of seed elements + * \tparam IAMeshType The topological mesh data structure type (slam::IAMesh) being modified */ template class DelaunayInsertionHelper @@ -56,14 +63,28 @@ class DelaunayInsertionHelper static constexpr int VERTS_PER_FACET = VERT_PER_ELEMENT - 1; static constexpr IndexType INVALID_INDEX = IndexType {-1}; + /** + * \brief A facet on the boundary of the cavity, paired with its adjacent non-cavity element + * + * Used to re-stitch the triangulation after the cavity is removed. + */ struct BoundaryFacet { std::array vertices {}; IndexType neighbor {INVALID_INDEX}; }; + /** + * \brief Construct the helper bound to the mesh it will modify in place. + * + * \param mesh The IA mesh whose cavity will be carved and re-triangulated + */ explicit DelaunayInsertionHelper(IAMeshType& mesh) : m_mesh(mesh) { } + /** + * \brief Clears all per-insertion scratch state so the helper can be reused + * for the next point without reallocating its internal buffers. + */ void reset() { for(const IndexType element_idx : cavity_elems) @@ -82,6 +103,20 @@ class DelaunayInsertionHelper m_stack.clear(); } + /** + * \brief Computes the Bowyer-Watson cavity: the set of elements whose circumspheres + * contain the query point, found by a flood fill outward from the seed elements across shared facets. + * + * Populates \a cavity_elems (the elements to delete) and their \a boundary_facets + * + * \tparam CircumspherePredicate Callable `(IndexType elem) -> bool` returning + * true when the query point lies inside element \a elem's circumsphere + * + * \param query_pt The point being inserted + * \param seed_elements One or more elements known to contain \a query_pt in + * their circumsphere, used to seed the flood fill + * \param isPointInCircumsphere The in-circumsphere predicate (see tparam) + */ template void findCavityElements(const PointType& query_pt, const IndexArray& seed_elements, @@ -162,6 +197,13 @@ class DelaunayInsertionHelper SLIC_ASSERT(!boundary_facets.empty()); } + /** + * \brief Removes the cavity elements from the mesh, releasing their slots to + * the deleted-element pool for reuse by the new simplices. + * + * \tparam DeletedElementPool A pool type exposing `release(IndexType)` + * \param deleted_elements The pool that receives the freed element slots + */ template void createCavity(DeletedElementPool& deleted_elements) { @@ -172,6 +214,14 @@ class DelaunayInsertionHelper } } + /** + * \brief Retriangulates the cavity by connecting the new point to each + * boundary facet (forming the "Delaunay ball"), reusing freed slots first. + * + * \tparam DeletedElementPool A pool type exposing reusable element slots + * \param new_pt_i The vertex index of the just-inserted point + * \param deleted_elements The pool of element slots freed by createCavity() + */ template void delaunayBall(IndexType new_pt_i, DeletedElementPool& deleted_elements) { @@ -234,6 +284,11 @@ class DelaunayInsertionHelper int numRemovedElements() const { return static_cast(cavity_elems.size()); } + /** + * \brief Returns true if \a element_idx is a member of the current cavity. + * + * \param element_idx The element to test for cavity membership + */ bool containsCavityElement(IndexType element_idx) const { return element_idx >= 0 && static_cast(element_idx) < m_cavity_membership.size() && diff --git a/src/axom/slam/mesh_struct/detail/FacetPairingMap.hpp b/src/axom/slam/mesh_struct/detail/FacetPairingMap.hpp index 22a7d36215..76102d6a58 100644 --- a/src/axom/slam/mesh_struct/detail/FacetPairingMap.hpp +++ b/src/axom/slam/mesh_struct/detail/FacetPairingMap.hpp @@ -235,7 +235,6 @@ class FacetPairingMap * This method sizes the table appropriately and advances the generation counter. * It should be called before each batch of insertions. * - * The table is sized to maintain a low load factor (~12.5-25%) for O(1) performance. * In 2D, each element contributes 2 incident faces (face 0 is on boundary). * In 3D, each element contributes 3 incident faces (face 0 is on boundary). * @@ -248,7 +247,7 @@ class FacetPairingMap // Cold sizing targets a low load factor: ~(2*TDIM)x the facet count // (2D: ~2 incident faces/elem; 3D: ~3 incident faces/elem). The next // power-of-two round-up below lowers the realized load further. - // The table never shrinks, so after a large pairing pass, + // The table never shrinks, so after a large pairing pass, // subsequent small stars run at a much lower load factor. const std::size_t target_slots = axom::utilities::max(8, (TDIM == 2 ? 4 : 8) * expected_facet_count); From 47ed41549cec83dbda93fec0456b8b9a15821094 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 17 Jun 2026 15:00:29 -0700 Subject: [PATCH 522/986] Improves documentation for Delaunay tolerance factors --- src/axom/primal/operators/in_sphere.hpp | 8 +++++ .../quest/detail/DelaunayPointLocation.hpp | 8 ++++- src/axom/quest/detail/DelaunayValidation.hpp | 34 +++++++++++++++++-- 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/src/axom/primal/operators/in_sphere.hpp b/src/axom/primal/operators/in_sphere.hpp index f1fe94f8cc..99b695526d 100644 --- a/src/axom/primal/operators/in_sphere.hpp +++ b/src/axom/primal/operators/in_sphere.hpp @@ -76,6 +76,14 @@ inline double in_sphere_determinant(const Point& q, const Tetrahedron inline int in_sphere_orientation(const Point& q, diff --git a/src/axom/quest/detail/DelaunayPointLocation.hpp b/src/axom/quest/detail/DelaunayPointLocation.hpp index 019208aad5..a1eb882a49 100644 --- a/src/axom/quest/detail/DelaunayPointLocation.hpp +++ b/src/axom/quest/detail/DelaunayPointLocation.hpp @@ -86,7 +86,13 @@ inline double Delaunay::rawBarycentricDeterminantTolerance(IndexType elemen scale = axom::utilities::max(scale, diff.norm()); } - const double k = 64.; + // Tolerance for treating a raw barycentric determinant as zero + // (the query point lies on a facet of this element). + // A barycentric numerator is a (DIM)-dimensional determinant of coordinate differences, + // so its round-off scales like (machine epsilon) x (length)^DIM. + // The factor 64 matches getBoundaryCoordinateTolerance(), which is also related to the facet/boundary decision. + // Empirical safety margin, validated on the grid/cospherical stress tests, not a proven bound. + constexpr double k = 64.; if constexpr(DIM == 2) { return k * std::numeric_limits::epsilon() * scale * scale; diff --git a/src/axom/quest/detail/DelaunayValidation.hpp b/src/axom/quest/detail/DelaunayValidation.hpp index 8885309830..45916870bb 100644 --- a/src/axom/quest/detail/DelaunayValidation.hpp +++ b/src/axom/quest/detail/DelaunayValidation.hpp @@ -61,7 +61,15 @@ inline double Delaunay::getBoundaryCoordinateTolerance() const max_extent = axom::utilities::max(max_extent, max_pt[dim] - min_pt[dim]); } - return 64. * std::numeric_limits::epsilon() * max_extent; + // Absolute tolerance for treating a coordinate as lying on the bounding box. + // Geometric predicates accumulate floating-point round-off proportional to the + // magnitude of the coordinates involved: an error of order (machine epsilon) x (coordinate magnitude). + // We use the largest box extent as that magnitude and multiply by a conservative safety factor. + // The factor 64 (= 2^6) was chosen empirically -- it comfortably dominates the observed round-off on the 2D/3D + // grid and cospherical stress tests without being so large that it merges genuinely distinct points. + // Treat it as a tuned safety margin, not an exact bound. + constexpr double SAFETY_FACTOR = 64.; + return SAFETY_FACTOR * std::numeric_limits::epsilon() * max_extent; } template @@ -108,7 +116,22 @@ template inline double Delaunay::getElementMeasureTolerance() const { const double scale = axom::utilities::max(1., m_bounding_box.range().norm()); - return 256. * std::numeric_limits::epsilon() * std::pow(scale, static_cast(DIM)); + + // Tolerance for deciding whether an element's signed area/volume is effectively zero. + // A d-dimensional measure is a determinant of d coordinate differences, + // so its round-off scales like (machine epsilon) x (length)^d. + // The leading 256 (= 2^8) is a conservative empirical safety margin validated on grid/cospherical tests. + // It is larger than the boundary-coordinate factor because measures compound error across DIM coordinate differences. + // Treat this as a tuned margin rather than a proven bound. + constexpr double SAFETY_FACTOR = 256.; + if constexpr(DIM == 2) + { + return SAFETY_FACTOR * std::numeric_limits::epsilon() * scale * scale; + } + else + { + return SAFETY_FACTOR * std::numeric_limits::epsilon() * scale * scale * scale; + } } template @@ -313,7 +336,12 @@ inline bool Delaunay::isValid(bool verboseOutput) const scale = axom::utilities::max(scale, axom::utilities::abs(x[dim])); } - return 256. * std::numeric_limits::epsilon() * scale; + // Tolerance band for classifying a point as on the circumsphere when comparing (signed) distance to the radius. + // Round-off scales with the largest magnitude among the radius, center, and query coordinates. + // The 256 safety factor matches getElementMeasureTolerance(), which is also related to near-zero geometric quantities. + // Treat this as an empirical bound rather than a proven bound. + constexpr double SAFETY_FACTOR = 256.; + return SAFETY_FACTOR * std::numeric_limits::epsilon() * scale; }; { From 42d7837efcb9628b3f2f8b8e20876b0ea7e47aea Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 17 Jun 2026 15:03:23 -0700 Subject: [PATCH 523/986] Adds comment about failed Delaunay insertion case --- src/axom/quest/detail/DelaunayImpl.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/axom/quest/detail/DelaunayImpl.hpp b/src/axom/quest/detail/DelaunayImpl.hpp index 8dd7ef6245..63f960674c 100644 --- a/src/axom/quest/detail/DelaunayImpl.hpp +++ b/src/axom/quest/detail/DelaunayImpl.hpp @@ -341,6 +341,10 @@ inline void Delaunay::insertPoint(const PointType& new_pt) IndexType element_i = findContainingElement(new_pt); + // No-op (with a warning) if point location fails: if no containing element is found, + // the point cannot be inserted, so we leave the triangulation unchanged and return. + // This can happen for points outside the current convex hull or in near-degenerate + // configurations where the walk does not converge. if(element_i == INVALID_INDEX) { SLIC_WARNING( From 77ff18106fb8634f5bb0b4a91e5fe67a9fc92887 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 17 Jun 2026 15:15:11 -0700 Subject: [PATCH 524/986] Adds comments about using custom random number generator (vs. axom::random_real) --- src/axom/quest/ScatteredInterpolation.hpp | 5 +++++ src/axom/quest/examples/scattered_interpolation.cpp | 3 +++ 2 files changed, 8 insertions(+) diff --git a/src/axom/quest/ScatteredInterpolation.hpp b/src/axom/quest/ScatteredInterpolation.hpp index 0c661214f2..f396609a01 100644 --- a/src/axom/quest/ScatteredInterpolation.hpp +++ b/src/axom/quest/ScatteredInterpolation.hpp @@ -338,6 +338,11 @@ class ScatteredInterpolation // distribution using a single 64-bit random integer per point: // - Take the top `nlevels` bits. // - The BRIO level is the position of the highest set bit (1..nlevels), or 0 if all are 0. + // + // This single-integer sampler is intentionally preferred over repeated `random_real()` here: + // it is faster at large N and draws the same geometric level distribution. + // The "highest set bit of a random integer -> geometric level" idiom is reusable + // and could be promoted to axom::utilities (alongside the bit-twiddling helpers) std::mt19937_64 mt; std::uint64_t seed = 0; if(::getScatteredInterpSeed(seed)) diff --git a/src/axom/quest/examples/scattered_interpolation.cpp b/src/axom/quest/examples/scattered_interpolation.cpp index bf160ad5a1..c38f7931db 100644 --- a/src/axom/quest/examples/scattered_interpolation.cpp +++ b/src/axom/quest/examples/scattered_interpolation.cpp @@ -595,6 +595,9 @@ axom::Array> generatePts(int numPts, BoundingBox bbox {PointType(bb_min.data()), PointType(bb_max.data())}; + // This example uses a directly-seedable std::mt19937_64 (rather than axom::utilities::random_real) + // so that the generated point set is exactly reproducible. The AXOM_SCATTERED_INTERP_SEED environment variable + // can be used to pin the seed for repeatable timing/comparison runs, falling back to a nondeterministic seed otherwise. std::mt19937_64 mt; if(const char* env = std::getenv("AXOM_SCATTERED_INTERP_SEED")) { From 4dc441c0e5803f5ff412ae8a467e2e6fb972db8d Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 17 Jun 2026 16:29:07 -0700 Subject: [PATCH 525/986] Bugfix: A moved-from axom::Array should remain valid We were previously setting an INVALID_ALLOCATOR_ID, which made the array invalid. A moved-from Array now has has 0 and the same allocator it had before. Adds tests that show that we can push to and access data from a moved-from Array. --- src/axom/core/Array.hpp | 11 +++++++++-- src/axom/core/tests/core_array.hpp | 10 ++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/axom/core/Array.hpp b/src/axom/core/Array.hpp index b103ffbc72..09597099f8 100644 --- a/src/axom/core/Array.hpp +++ b/src/axom/core/Array.hpp @@ -136,6 +136,11 @@ struct DefaultStoragePolicy * its memory at allocation time and we use axom's memory_management * and allocator ID abstractions rather than std::allocator. * + * Move semantics follow standard container conventions: moved-from Arrays are + * left in a valid-but-unspecified state and may be safely reused + * (e.g. assigned to, cleared, or appended to). + * The allocator id of a moved-from Array remains valid. + * * Array always retains exclusive ownership of its data and is responsible for * freeing its memory. * @@ -293,6 +298,8 @@ class Array : public ArrayBase>, pro /*! * \brief Move constructor for an Array instance + * + * \note The moved-from Array is left in a valid-but-unspecified state and may be reused. */ Array(Array&& other) noexcept; @@ -369,6 +376,8 @@ class Array : public ArrayBase>, pro /*! * \brief Move assignment operator for Array + * + * \note The moved-from Array is left in a valid-but-unspecified state and may be reused. */ Array& operator=(Array&& other) noexcept { @@ -390,7 +399,6 @@ class Array : public ArrayBase>, pro other.m_num_elements = 0; other.m_capacity = 0; other.m_resize_ratio = DEFAULT_RESIZE_RATIO; - other.m_allocator_id = INVALID_ALLOCATOR_ID; } return *this; @@ -1262,7 +1270,6 @@ Array::Array(Array&& other) noexcept other.m_num_elements = 0; other.m_capacity = 0; other.m_resize_ratio = DEFAULT_RESIZE_RATIO; - other.m_allocator_id = INVALID_ALLOCATOR_ID; } //------------------------------------------------------------------------------ diff --git a/src/axom/core/tests/core_array.hpp b/src/axom/core/tests/core_array.hpp index 60712a01b6..84f1520c6e 100644 --- a/src/axom/core/tests/core_array.hpp +++ b/src/axom/core/tests/core_array.hpp @@ -1557,6 +1557,11 @@ TEST(core_array, check_move_copy) EXPECT_EQ(v_int_copy_assign.data(), nullptr); EXPECT_EQ(v_int_copy_ctor.data(), nullptr); + /* Moved-from arrays are valid and should be reusable */ + v_int_copy_assign.push_back(MAGIC_INT); + EXPECT_EQ(v_int_copy_assign.size(), 1); + EXPECT_EQ(v_int_copy_assign[0], MAGIC_INT); + /* Check copy and move semantics for array of doubles */ axom::Array v_double(size, capacity); v_double.fill(MAGIC_DOUBLE); @@ -1574,6 +1579,11 @@ TEST(core_array, check_move_copy) EXPECT_EQ(v_double, v_double_move_ctor); EXPECT_EQ(v_double_copy_assign.data(), nullptr); EXPECT_EQ(v_double_copy_ctor.data(), nullptr); + + /* Moved-from arrays are valid and should be reusable */ + v_double_copy_assign.push_back(MAGIC_DOUBLE); + EXPECT_EQ(v_double_copy_assign.size(), 1); + EXPECT_EQ(v_double_copy_assign[0], MAGIC_DOUBLE); } } From 7b60fed658135e35e79006f3bb37d0867193c254 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 17 Jun 2026 16:32:02 -0700 Subject: [PATCH 526/986] Improves test names and comments about some regression tests Also checks that a moved-from Delaunay complex remains valid. --- src/axom/quest/tests/quest_delaunay.cpp | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/axom/quest/tests/quest_delaunay.cpp b/src/axom/quest/tests/quest_delaunay.cpp index 4a3fb1990b..c25240b2c9 100644 --- a/src/axom/quest/tests/quest_delaunay.cpp +++ b/src/axom/quest/tests/quest_delaunay.cpp @@ -69,8 +69,14 @@ void expectValidDelaunayWithBoundary(const DelaunayType& dt) } // namespace -TEST(quest_delaunay, local_bad_geometry_pool_2d) +TEST(quest_delaunay, regression_clustered_collinear_points_2d) { + // Regression test: a tightly clustered, nearly-collinear pool of points + // that previously triggered point-location / predicate failures + // (points getting "stuck" on a facet, producing a non-conforming mesh). + // Inserts all but the last point, validates, then inserts the point that exposed the bug. + // Uses Local insertion validation to catch cavity/ball invariant violations as they happen. + using PointType = typename DelaunayType<2>::PointType; using BoundingBox = typename DelaunayType<2>::BoundingBox; using ValidationMode = typename DelaunayType<2>::InsertionValidationMode; @@ -105,8 +111,12 @@ TEST(quest_delaunay, local_bad_geometry_pool_2d) expectValidDelaunayWithBoundary(dt); } -TEST(quest_delaunay, local_bad_geometry_pool_3d) +TEST(quest_delaunay, regression_clustered_coplanar_points_3d) { + // Regression test (3D analog of the 2D case above): a tightly clustered, + // nearly co-planar collections of tetrahedralization points that previously caused + // point insertion to fail near-degenerate predicate decisions. + using PointType = typename DelaunayType<3>::PointType; using BoundingBox = typename DelaunayType<3>::BoundingBox; @@ -290,6 +300,14 @@ TEST(quest_delaunay, move_rebinds_insertion_helper_2d) DelaunayType<2> moved(std::move(original)); moved.insertPoint(points.back()); expectValidDelaunay(moved, points); + + // The moved-from object is in a valid-but-unspecified state. + // We don't assert a particular topology on it (the move contract doesn't guarantee one), + // but it must remain safe to reuse: re-initializing and building a fresh triangulation + // in `original` should work and produce a valid Delaunay complex. + original.initializeBoundary(BoundingBox(PointType {0., 0.}, PointType {1., 1.})); + insertPoints(original, points); + expectValidDelaunay(original, points); } TEST(quest_delaunay, cospherical_cube_3d) From e1c1ea73ee550ac4451d27f511887c05c5cd0054 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 17 Jun 2026 17:00:12 -0700 Subject: [PATCH 527/986] Improves discussion about tolerances/scaling factors for orientation/in_sphere predicates And adds some tests demonstrating where the absolute tolerances can go wrong. --- .../detail/predicate_determinants.hpp | 13 +++ src/axom/primal/operators/in_sphere.hpp | 21 ++-- src/axom/primal/operators/orientation.hpp | 3 + src/axom/primal/tests/primal_in_sphere.cpp | 74 +++++++++++++++ src/axom/primal/tests/primal_orientation.cpp | 95 +++++++++++++++++++ 5 files changed, 199 insertions(+), 7 deletions(-) diff --git a/src/axom/primal/operators/detail/predicate_determinants.hpp b/src/axom/primal/operators/detail/predicate_determinants.hpp index e499997d2a..2ad6479726 100644 --- a/src/axom/primal/operators/detail/predicate_determinants.hpp +++ b/src/axom/primal/operators/detail/predicate_determinants.hpp @@ -12,6 +12,19 @@ * These routines centralize the core determinant computations used by primal * predicates (e.g. orientation and in-sphere). They return the raw determinant * values without interpreting tolerances or mapping to OrientationResult. + * + * \note **Precision / robustness.** These determinants are evaluated in ordinary + * IEEE double-precision floating point. They are fast and accurate away from + * degeneracy, but they are NOT exact sign oracles: for inputs that are exactly + * or nearly degenerate (collinear points for orientation; co-circular / + * co-spherical points for in-sphere), accumulated round-off can make the + * computed sign unreliable, and no fixed tolerance fully recovers the exact + * sign. Callers needing robust behavior on (near-)degenerate configurations + * should classify with a scale-aware tolerance (see the *_orientation wrappers + * and quest::Delaunay's tolerance helpers) rather than trusting the bare sign. + * Exact / adaptive-precision predicates (e.g. Shewchuk-style) would remove this + * limitation and are a possible future enhancement; see the primal tests + * characterizing where the double-precision sign is and is not reliable. */ #ifndef AXOM_PRIMAL_PREDICATE_DETERMINANTS_HPP_ diff --git a/src/axom/primal/operators/in_sphere.hpp b/src/axom/primal/operators/in_sphere.hpp index 99b695526d..192575aace 100644 --- a/src/axom/primal/operators/in_sphere.hpp +++ b/src/axom/primal/operators/in_sphere.hpp @@ -12,6 +12,11 @@ * * This is a well known computational geometry primitive. For reference, * see Section 3.1.6.4 in "Real-time collision detection" by C. Ericson. + * + * \note These routines use double-precision determinants and are not exact sign + * oracles near degeneracy (e.g. nearly co-circular/co-spherical inputs). + * See detail/predicate_determinants.hpp for the precision/robustness discussion + * and in_sphere_orientation() for the tolerance (EPS) scaling caveat. */ #ifndef AXOM_PRIMAL_IN_SPHERE_H_ @@ -77,13 +82,15 @@ inline double in_sphere_determinant(const Point& q, const Tetrahedron inline int in_sphere_orientation(const Point& q, diff --git a/src/axom/primal/operators/orientation.hpp b/src/axom/primal/operators/orientation.hpp index 7897b2ae2d..eb4cdc8913 100644 --- a/src/axom/primal/operators/orientation.hpp +++ b/src/axom/primal/operators/orientation.hpp @@ -10,6 +10,9 @@ * \brief Consists of a set of templated (overloaded) routines used to calculate * the orientation of a given point to another geometric entity. * + * \note These routines use double-precision determinants and are not exact sign + * oracles near degeneracy (e.g. nearly collinear/coplanar inputs). + * See detail/predicate_determinants.hpp for the precision/robustness discussion. */ #ifndef AXOM_PRIMAL_ORIENTATION_HPP_ diff --git a/src/axom/primal/tests/primal_in_sphere.cpp b/src/axom/primal/tests/primal_in_sphere.cpp index 74b9f6ae84..e554094142 100644 --- a/src/axom/primal/tests/primal_in_sphere.cpp +++ b/src/axom/primal/tests/primal_in_sphere.cpp @@ -257,6 +257,80 @@ TEST(primal_in_sphere, bounding_box_in_sphere) } } +//------------------------------------------------------------------------------ +// Characterizes the double-precision in_sphere predicate near degeneracy. +// +// Documents where the bare double-precision sign is and is not reliable (see the +// precision note in detail/predicate_determinants.hpp) and the EPS-scaling caveat +// on in_sphere_orientation(). Descriptive: pins current behavior so a future +// exact/adaptive predicate can be validated against the same cases. +//------------------------------------------------------------------------------ +TEST(primal_in_sphere, degeneracy_characterization_2d) +{ + using PointType = primal::Point; + + // Four points exactly on the unit circle are co-circular: the in_sphere + // determinant of the 4th against the triangle of the first three is (near) + // zero, and with an adequate tolerance it classifies as ON_BOUNDARY. + { + PointType p0 {1., 0.}, p1 {0., 1.}, p2 {-1., 0.}; + PointType on_circle {0., -1.}; + const double det = primal::in_sphere_determinant(on_circle, p0, p1, p2); + EXPECT_NEAR(det, 0., 1e-12); + EXPECT_EQ(primal::in_sphere_orientation(on_circle, p0, p1, p2, 1e-8), primal::ON_BOUNDARY); + + // includeBoundary controls whether an on-circle point counts as "inside". + EXPECT_TRUE(primal::in_sphere(on_circle, p0, p1, p2, 1e-8, /*includeBoundary=*/true)); + EXPECT_FALSE(primal::in_sphere(on_circle, p0, p1, p2, 1e-8, /*includeBoundary=*/false)); + } + + // Clearly interior and clearly exterior points get opposite definite signs. + { + PointType p0 {1., 0.}, p1 {0., 1.}, p2 {-1., 0.}; + EXPECT_EQ(primal::in_sphere_orientation(PointType {0., 0.}, p0, p1, p2), + primal::ON_NEGATIVE_SIDE); // center is inside + EXPECT_EQ(primal::in_sphere_orientation(PointType {5., 5.}, p0, p1, p2), + primal::ON_POSITIVE_SIDE); // far away is outside + } +} + +TEST(primal_in_sphere, degeneracy_characterization_3d) +{ + using PointType = primal::Point; + + // Points on the unit sphere are co-spherical: the in_sphere determinant of a + // 5th on-sphere point against the tetrahedron of four others is (near) zero. + { + PointType p0 {1., 0., 0.}, p1 {-1., 0., 0.}, p2 {0., 1., 0.}, p3 {0., 0., 1.}; + PointType on_sphere {0., -1., 0.}; + const double det = primal::in_sphere_determinant(on_sphere, p0, p1, p2, p3); + EXPECT_NEAR(det, 0., 1e-12); + EXPECT_EQ(primal::in_sphere_orientation(on_sphere, p0, p1, p2, p3, 1e-8), primal::ON_BOUNDARY); + + EXPECT_TRUE(primal::in_sphere(on_sphere, p0, p1, p2, p3, 1e-8, /*includeBoundary=*/true)); + EXPECT_FALSE(primal::in_sphere(on_sphere, p0, p1, p2, p3, 1e-8, /*includeBoundary=*/false)); + } + + // The in_sphere determinant is not normalized. Its matrix mixes three linear + // coordinate columns with a squared-norm column, so it has degree (DIM+2) = 5 + // in the coordinates: uniformly scaling all points by S multiplies it by ~S^5. + // A co-spherical configuration stays (near) zero under scaling, but a strictly + // inside/outside determinant grows like S^5 -- which is why a fixed absolute + // EPS does not suffice for large coordinates (see in_sphere_orientation()). + { + PointType q {0., 0., 0.}; // center: strictly inside (definite, nonzero sign) + PointType p0 {1., 0., 0.}, p1 {-1., 0., 0.}, p2 {0., 1., 0.}, p3 {0., 0., 1.}; + const double small_det = primal::in_sphere_determinant(q, p0, p1, p2, p3); + ASSERT_NE(small_det, 0.); + + const double S = 100.; // S^5 = 1e10, kept modest to stay well within double range + auto sc = [S](const PointType& p) { return PointType {S * p[0], S * p[1], S * p[2]}; }; + const double big_det = primal::in_sphere_determinant(sc(q), sc(p0), sc(p1), sc(p2), sc(p3)); + + EXPECT_NEAR(big_det, small_det * S * S * S * S * S, axom::utilities::abs(big_det) * 1e-9); + } +} + //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ diff --git a/src/axom/primal/tests/primal_orientation.cpp b/src/axom/primal/tests/primal_orientation.cpp index 861d4398cf..d1709d97c6 100644 --- a/src/axom/primal/tests/primal_orientation.cpp +++ b/src/axom/primal/tests/primal_orientation.cpp @@ -210,6 +210,101 @@ TEST(primal_orientation, determinant_helpers) 0.); } +//------------------------------------------------------------------------------ +// Characterizes the double-precision orientation predicate near degeneracy. +// +// These tests document where the bare double-precision sign is and is not +// reliable (see the precision note in detail/predicate_determinants.hpp). +// They intentionally pin the current behavior so a future exact adaptive +// predicate can be swapped in and validated against the same cases. +//------------------------------------------------------------------------------ +TEST(primal_orientation, degeneracy_characterization_2d) +{ + namespace primal = axom::primal; + using Point2 = primal::Point; + using Segment = primal::Segment; + + // Exactly collinear points: the orientation determinant is exactly zero, + // so the classifier reports ON_BOUNDARY for any non-negative tolerance. + { + Point2 a {0., 0.}, b {1., 1.}, c {2., 2.}; // all on the line y = x + const double det = primal::orientation_determinant(a, b, c); + EXPECT_EQ(det, 0.); + Segment seg2(a, b); + EXPECT_EQ(primal::orientation(c, seg2), primal::ON_BOUNDARY); + } + + // Clearly non-degenerate points classify with a definite (non-boundary) sign. + { + Point2 a {0., 0.}, b {1., 0.}; + Segment seg2(a, b); + EXPECT_NE(primal::orientation(Point2 {0.5, 0.5}, seg2), primal::ON_BOUNDARY); + EXPECT_NE(primal::orientation(Point2 {0.5, -0.5}, seg2), primal::ON_BOUNDARY); + // and the two sides receive opposite classifications + EXPECT_NE(primal::orientation(Point2 {0.5, 0.5}, seg2), + primal::orientation(Point2 {0.5, -0.5}, seg2)); + } + + // The determinant is not normalized: for a fixed shape, translating the points + // far from the origin and scaling them up inflates the determinant magnitude. + // This is why a fixed absolute EPS is inadequate for large coordinates + // and why callers (e.g. quest::Delaunay) use a scale-aware tolerance. + { + Point2 a {0., 0.}, b {1., 0.}, c {0.5, 1.}; + const double small_det = primal::orientation_determinant(a, b, c); + + const double S = 1e6; + Point2 A {S * a[0], S * a[1]}, B {S * b[0], S * b[1]}, C {S * c[0], S * c[1]}; + const double big_det = primal::orientation_determinant(A, B, C); + + // 2D orientation determinant has degree 2 in the coordinates, + // so scaling by S multiplies it by ~S^2. + EXPECT_GT(axom::utilities::abs(big_det), axom::utilities::abs(small_det)); + EXPECT_NEAR(big_det, small_det * S * S, axom::utilities::abs(big_det) * 1e-9); + } +} + +TEST(primal_orientation, degeneracy_characterization_3d) +{ + namespace primal = axom::primal; + using Point3 = primal::Point; + using Tri = primal::Triangle; + + // Exactly coplanar query point: determinant is exactly zero -> ON_BOUNDARY. + { + Tri tri(Point3 {0., 0., 0.}, Point3 {1., 0., 0.}, Point3 {0., 1., 0.}); // z = 0 plane + Point3 coplanar {0.25, 0.25, 0.}; + const double det = primal::orientation_determinant(coplanar, tri[0], tri[1], tri[2]); + EXPECT_EQ(det, 0.); + EXPECT_EQ(primal::orientation(coplanar, tri), primal::ON_BOUNDARY); + } + + // Points clearly above / below the plane get opposite, non-boundary signs. + { + Tri tri(Point3 {0., 0., 0.}, Point3 {1., 0., 0.}, Point3 {0., 1., 0.}); + const int above = primal::orientation(Point3 {0.25, 0.25, 1.}, tri); + const int below = primal::orientation(Point3 {0.25, 0.25, -1.}, tri); + EXPECT_NE(above, primal::ON_BOUNDARY); + EXPECT_NE(below, primal::ON_BOUNDARY); + EXPECT_NE(above, below); + } + + // Degree-3 scaling of the 3D orientation determinant: scaling coordinates by S + // multiplies the determinant by ~S^3 + { + Point3 q {0.25, 0.25, 0.5}, p0 {0., 0., 0.}, p1 {1., 0., 0.}, p2 {0., 1., 0.}; + const double small_det = primal::orientation_determinant(q, p0, p1, p2); + ASSERT_NE(small_det, 0.); + + const double S = 1.0e4; + auto scaled = [S](const Point3& p) { return Point3 {S * p[0], S * p[1], S * p[2]}; }; + const double big_det = + primal::orientation_determinant(scaled(q), scaled(p0), scaled(p1), scaled(p2)); + + EXPECT_NEAR(big_det, small_det * S * S * S, axom::utilities::abs(big_det) * 1e-9); + } +} + //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ From f7ae36ab9a5cf364cbf8ee22c4da6118eb505b7e Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 17 Jun 2026 17:09:27 -0700 Subject: [PATCH 528/986] Updates RELEASE-NOTES --- RELEASE-NOTES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 8b41964149..5ad996c74a 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -59,6 +59,7 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ - Primal: Improves reproducibility of 3D GWN methods by removing some sources of randomness - Core: ArrayView assigments/copies now copy the stride - Core: Array construction from strided ArrayView now correctly copies the strided elements +- Core: A moved-from Array is valid, e.g. it can be pushed to - Core: Improved `axom::FlatMap` insertion performance by fusing duplicate-key lookup with empty-slot probing. - Core: Updated DeviceHash to use 64-bit hash results and improved coverage for integer and floating-point hashing. - Python: Improves lifetime handling for python wrapped sidre entities, including support for external views into numpy arrays. From e96610983d0b5a520f97782883cedb280af18438 Mon Sep 17 00:00:00 2001 From: Jason Burmark Date: Wed, 24 Jun 2026 10:31:30 -0700 Subject: [PATCH 529/986] Remove Nodes even sooner The Request object holds onto the packed data so we don't need to hold onto the original node while the isends are processing. --- .../detail/DistributedClosestPointImpl.hpp | 84 +++++++++---------- 1 file changed, 40 insertions(+), 44 deletions(-) diff --git a/src/axom/quest/detail/DistributedClosestPointImpl.hpp b/src/axom/quest/detail/DistributedClosestPointImpl.hpp index bd520d1d60..97e98d837d 100644 --- a/src/axom/quest/detail/DistributedClosestPointImpl.hpp +++ b/src/axom/quest/detail/DistributedClosestPointImpl.hpp @@ -478,15 +478,14 @@ class DistributedClosestPointImpl } /// Wait for some non-blocking sends (if any) to finish. - void check_send_requests(std::list>>& isendRequests, + void check_send_requests(std::list& isendRequests, bool atLeastOne) const { std::vector reqs; reqs.reserve(isendRequests.size()); for(auto const& isr : isendRequests) { - reqs.push_back(isr.first.m_request); + reqs.push_back(isr.m_request); } int inCount = static_cast(reqs.size()); @@ -745,48 +744,47 @@ class DistributedClosestPointExec : public DistributedClosestPointImpl { SLIC_ASSERT_MSG(m_bvh, "BVH tree must be initialized before calling 'computeClosestPoints"); - std::unique_ptr xferNodePtr = std::make_unique(); - - // create conduit Node containing data that has to xfer between ranks. - // The node will be mostly empty if there are no domains on this rank - node_copy_query_to_xfer(queryMesh, *xferNodePtr, topologyName); - (*xferNodePtr)["homeRank"] = m_rank; + // arbitrary tags for send/recv xferNodes. + const int tag = 987342; - BoxType myQueryBb = computeMeshBoundingBox(*xferNodePtr); - put_bounding_box_to_conduit_node(myQueryBb, xferNodePtr->fetch("aabb")); - BoxArray allQueryBbs; - gatherBoundingBoxes(myQueryBb, allQueryBbs); + int remainingRecvs = 0; - computeLocalClosestPoints(*xferNodePtr); + std::list isendRequests; - const auto& myObjectBb = m_objectPartitionBbs[m_rank]; - int remainingRecvs = 0; - for(int r = 0; r < m_nranks; ++r) { - if(r != m_rank) + // create conduit Node containing data that has to xfer between ranks. + // The node will be mostly empty if there are no domains on this rank + conduit::Node xferNode; + node_copy_query_to_xfer(queryMesh, xferNode, topologyName); + xferNode["homeRank"] = m_rank; + + BoxType myQueryBb = computeMeshBoundingBox(xferNode); + put_bounding_box_to_conduit_node(myQueryBb, xferNode.fetch("aabb")); + BoxArray allQueryBbs; + gatherBoundingBoxes(myQueryBb, allQueryBbs); + + computeLocalClosestPoints(xferNode); + + const auto& myObjectBb = m_objectPartitionBbs[m_rank]; + for(int r = 0; r < m_nranks; ++r) { - const auto& otherQueryBb = allQueryBbs[r]; - double sqDistance = axom::primal::squared_distance(otherQueryBb, myObjectBb); - if(sqDistance <= m_sqDistanceThreshold) + if(r != m_rank) { - ++remainingRecvs; + const auto& otherQueryBb = allQueryBbs[r]; + double sqDistance = axom::primal::squared_distance(otherQueryBb, myObjectBb); + if(sqDistance <= m_sqDistanceThreshold) + { + ++remainingRecvs; + } } } - } - // arbitrary tags for send/recv xferNodes. - const int tag = 987342; - - std::list>> isendRequests; - - { /* Send local query mesh to next rank with close-enough object partition, if any. Increase remainingRecvs, because this data will come back. */ - int firstRecipForMyQuery = next_recipient(*xferNodePtr); + int firstRecipForMyQuery = next_recipient(xferNode); if(m_nranks == 1) { SLIC_ASSERT(firstRecipForMyQuery == -1); @@ -795,15 +793,13 @@ class DistributedClosestPointExec : public DistributedClosestPointImpl if(firstRecipForMyQuery == -1) { // No need to send anywhere. Put computed data back into queryMesh. - node_copy_xfer_to_query(*xferNodePtr, queryMesh, topologyName); - // Free xferNode memory - xferNodePtr.reset(); + node_copy_xfer_to_query(xferNode, queryMesh, topologyName); } else { - isendRequests.emplace_back(conduit::relay::mpi::Request(), std::move(xferNodePtr)); + isendRequests.emplace_back(conduit::relay::mpi::Request()); auto& req = isendRequests.back(); - relay::mpi::isend_using_schema(*req.second, firstRecipForMyQuery, tag, m_mpiComm, &req.first); + relay::mpi::isend_using_schema(xferNode, firstRecipForMyQuery, tag, m_mpiComm, &req.first); ++remainingRecvs; } } @@ -814,25 +810,25 @@ class DistributedClosestPointExec : public DistributedClosestPointImpl fmt::format("======= {} receives remaining =======", remainingRecvs)); // Receive the next xferNode - std::unique_ptr recvXferNodePtr = std::make_unique(); - conduit::relay::mpi::recv_using_schema(*recvXferNodePtr, MPI_ANY_SOURCE, tag, m_mpiComm); + conduit::Node recvXferNode; + conduit::relay::mpi::recv_using_schema(recvXferNode, MPI_ANY_SOURCE, tag, m_mpiComm); - const int homeRank = recvXferNodePtr->fetch_existing("homeRank").as_int(); + const int homeRank = recvXferNode.fetch_existing("homeRank").as_int(); --remainingRecvs; if(homeRank == m_rank) { - node_copy_xfer_to_query(*recvXferNodePtr, queryMesh, topologyName); + node_copy_xfer_to_query(recvXferNode, queryMesh, topologyName); } else { - computeLocalClosestPoints(*recvXferNodePtr); + computeLocalClosestPoints(recvXferNode); - int nextRecipient = next_recipient(*recvXferNodePtr); + int nextRecipient = next_recipient(recvXferNode); SLIC_ASSERT(nextRecipient != -1); - isendRequests.emplace_back(conduit::relay::mpi::Request(), std::move(recvXferNodePtr)); + isendRequests.emplace_back(conduit::relay::mpi::Request()); auto& isendRequest = isendRequests.back(); - relay::mpi::isend_using_schema(*isendRequest.second, nextRecipient, tag, m_mpiComm, &isendRequest.first); + relay::mpi::isend_using_schema(recvXferNode, nextRecipient, tag, m_mpiComm, &isendRequest); // Check non-blocking sends to free memory. check_send_requests(isendRequests, false); From 8735137b2379587188fa7018185eaad5b967d079 Mon Sep 17 00:00:00 2001 From: Jason Burmark Date: Wed, 24 Jun 2026 10:36:49 -0700 Subject: [PATCH 530/986] Fix indenting --- .../detail/DistributedClosestPointImpl.hpp | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/axom/quest/detail/DistributedClosestPointImpl.hpp b/src/axom/quest/detail/DistributedClosestPointImpl.hpp index 1c130551fb..0888d384f7 100644 --- a/src/axom/quest/detail/DistributedClosestPointImpl.hpp +++ b/src/axom/quest/detail/DistributedClosestPointImpl.hpp @@ -760,24 +760,24 @@ class DistributedClosestPointExec : public DistributedClosestPointImpl BoxType myQueryBb = computeMeshBoundingBox(xferNode); put_bounding_box_to_conduit_node(myQueryBb, xferNode.fetch("aabb")); - BoxArray allQueryBbs; - gatherBoundingBoxes(myQueryBb, allQueryBbs); + BoxArray allQueryBbs; + gatherBoundingBoxes(myQueryBb, allQueryBbs); computeLocalClosestPoints(xferNode); - const auto& myObjectBb = m_objectPartitionBbs[m_rank]; - for(int r = 0; r < m_nranks; ++r) - { - if(r != m_rank) + const auto& myObjectBb = m_objectPartitionBbs[m_rank]; + for(int r = 0; r < m_nranks; ++r) { - const auto& otherQueryBb = allQueryBbs[r]; - double sqDistance = axom::primal::squared_distance(otherQueryBb, myObjectBb); - if(sqDistance <= m_sqDistanceThreshold) + if(r != m_rank) { - ++remainingRecvs; + const auto& otherQueryBb = allQueryBbs[r]; + double sqDistance = axom::primal::squared_distance(otherQueryBb, myObjectBb); + if(sqDistance <= m_sqDistanceThreshold) + { + ++remainingRecvs; + } } } - } /* Send local query mesh to next rank with close-enough object From 75f6ca067665cbf6b2904e59bafa178c7bb46217 Mon Sep 17 00:00:00 2001 From: Jason Burmark Date: Wed, 24 Jun 2026 10:36:54 -0700 Subject: [PATCH 531/986] fix compile --- src/axom/quest/detail/DistributedClosestPointImpl.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/axom/quest/detail/DistributedClosestPointImpl.hpp b/src/axom/quest/detail/DistributedClosestPointImpl.hpp index 0888d384f7..b17b7c0416 100644 --- a/src/axom/quest/detail/DistributedClosestPointImpl.hpp +++ b/src/axom/quest/detail/DistributedClosestPointImpl.hpp @@ -28,6 +28,7 @@ #include #include #include +#include #ifndef AXOM_USE_MPI #error This file requires Axom to be configured with MPI @@ -799,7 +800,7 @@ class DistributedClosestPointExec : public DistributedClosestPointImpl { isendRequests.emplace_back(conduit::relay::mpi::Request()); auto& req = isendRequests.back(); - relay::mpi::isend_using_schema(xferNode, firstRecipForMyQuery, tag, m_mpiComm, &req.first); + relay::mpi::isend_using_schema(xferNode, firstRecipForMyQuery, tag, m_mpiComm, &req); ++remainingRecvs; } } From f8a8ff8abb581189e05a5133466c4a31eaf98d8d Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 24 Jun 2026 10:49:41 -0700 Subject: [PATCH 532/986] Doc fix --- src/axom/bump/docs/sphinx/bump_views.rst | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/axom/bump/docs/sphinx/bump_views.rst b/src/axom/bump/docs/sphinx/bump_views.rst index 7affff0113..ae17537fdb 100644 --- a/src/axom/bump/docs/sphinx/bump_views.rst +++ b/src/axom/bump/docs/sphinx/bump_views.rst @@ -165,12 +165,11 @@ Matsets The BUMP component provides material views to wrap Blueprint matsets behind an interface that supports queries of the matset data without having to care much about its internal representation. -Blueprint describes 4 flavors of matset, each with a different representation. In practice, -Blueprint supports 4 flavors of matset, unibuffer element-dominant, multibuffer element-dominant, -and multibuffer material-dominant. The ``axom::bump::views::UnibufferMaterialView`` class wraps -unibuffer element-dominant matsets, which consist of several arrays that define materials for -each zone in the associated topology. The view's methods allow algorithms to query the list of -materials for each zone. +Blueprint describes 3 flavors of matset, each with a different representation: unibuffer +element-dominant, multibuffer element-dominant, and multibuffer material-dominant. The +``axom::bump::views::UnibufferMaterialView`` class wraps unibuffer element-dominant matsets, +which consist of several arrays that define materials for each zone in the associated topology. +The view's methods allow algorithms to query the list of materials for each zone. .. literalinclude:: ../../tests/bump_views.cpp :start-after: _bump_views_matsetview_begin From bdcb27b6d18f7c4e0edbb9a154a1ed563e122b0e Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 24 Jun 2026 10:49:55 -0700 Subject: [PATCH 533/986] Verify materials are in the same order. --- src/axom/bump/views/dispatch_material_field.hpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/axom/bump/views/dispatch_material_field.hpp b/src/axom/bump/views/dispatch_material_field.hpp index f2d806c7e1..a8cf8c3563 100644 --- a/src/axom/bump/views/dispatch_material_field.hpp +++ b/src/axom/bump/views/dispatch_material_field.hpp @@ -17,6 +17,19 @@ namespace views { namespace detail { +inline void verifyMatchingMaterialOrder(const conduit::Node &mat_values, + const conduit::Node &field_values) +{ + SLIC_ERROR_IF(mat_values.number_of_children() != field_values.number_of_children(), + "The matset volume_fractions and field matset_values have different numbers of materials."); + + for(conduit::index_t i = 0; i < mat_values.number_of_children(); i++) + { + SLIC_ERROR_IF(mat_values[i].name() != field_values[i].name(), + "The matset volume_fractions and field matset_values do not have matching material order."); + } +} + /*! * \brief Dispatch a unibuffer matset_values field. * @@ -55,7 +68,7 @@ bool dispatch_multibuffer_field(const conduit::Node &n_field, FuncType &&func) bool rv = false; detail::verifyMixedField(n_field); const conduit::Node &matset_values = n_field["matset_values"]; - SLIC_ERROR_IF(matset_values.number_of_children() <= 1, "Missing fields in matset_values."); + SLIC_ERROR_IF(matset_values.number_of_children() < 1, "Missing fields in matset_values."); // NOTE: For now support float, double types. axom::bump::views::floatNodeToArrayView(matset_values[0], [&](auto firstValuesView) { using FieldT = typename decltype(firstValuesView)::value_type; @@ -120,6 +133,7 @@ bool dispatch_material_element_dominant_field(const conduit::Node &matset, { verify(matset, "matset"); detail::verifyMixedField(n_field); + detail::verifyMatchingMaterialOrder(matset["volume_fractions"], n_field["matset_values"]); auto handleMatset = [&](auto matsetView) { using MatsetView = decltype(matsetView); detail::dispatch_multibuffer_field(n_field, [&](auto mixedFieldView) { @@ -149,6 +163,7 @@ bool dispatch_material_material_dominant_field(const conduit::Node &matset, { verify(matset, "matset"); detail::verifyMixedField(n_field); + detail::verifyMatchingMaterialOrder(matset["volume_fractions"], n_field["matset_values"]); auto handleMatset = [&](auto matsetView) { using MatsetView = decltype(matsetView); detail::dispatch_multibuffer_field(n_field, [&](auto mixedFieldView) { From 30fea652df36d93313ddab2d846b803f88b745f9 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 24 Jun 2026 11:11:25 -0700 Subject: [PATCH 534/986] Switch from size_t to axom::IndexType and static_assert. --- src/axom/bump/views/MaterialView.hpp | 3 ++ src/axom/bump/views/MixedFieldView.hpp | 3 ++ src/axom/bump/views/dispatch_material.hpp | 29 ++++++++++++++----- .../bump/views/dispatch_material_field.hpp | 12 +++++--- 4 files changed, 35 insertions(+), 12 deletions(-) diff --git a/src/axom/bump/views/MaterialView.hpp b/src/axom/bump/views/MaterialView.hpp index dd3c74ee1d..cd80f87557 100644 --- a/src/axom/bump/views/MaterialView.hpp +++ b/src/axom/bump/views/MaterialView.hpp @@ -91,6 +91,7 @@ template class UnibufferMaterialView { public: + static_assert(MAXMATERIALS > 0, "MAXMATERIALS must be greater than 0."); using MaterialID = IndexT; using ZoneIndex = IndexT; using IndexType = IndexT; @@ -334,6 +335,7 @@ template class ElementDominantMaterialView { public: + static_assert(MAXMATERIALS > 0, "MAXMATERIALS must be greater than 0."); using MaterialID = IndexT; using ZoneIndex = IndexT; using IndexType = IndexT; @@ -624,6 +626,7 @@ template class MaterialDominantMaterialView { public: + static_assert(MAXMATERIALS > 0, "MAXMATERIALS must be greater than 0."); using MaterialID = IndexT; using ZoneIndex = IndexT; using IndexType = IndexT; diff --git a/src/axom/bump/views/MixedFieldView.hpp b/src/axom/bump/views/MixedFieldView.hpp index 7e5f499b4a..17b152f771 100644 --- a/src/axom/bump/views/MixedFieldView.hpp +++ b/src/axom/bump/views/MixedFieldView.hpp @@ -31,6 +31,7 @@ struct MixedFieldTraits template struct MixedFieldTraits, FieldT> { + static_assert(MAXMATERIALS > 0, "MAXMATERIALS must be greater than 0."); using MatsetView = UnibufferMaterialView; using ValueView = axom::ArrayView; @@ -63,6 +64,7 @@ struct MixedFieldTraits, Fie template struct MixedFieldTraits, FieldT> { + static_assert(MAXMATERIALS > 0, "MAXMATERIALS must be greater than 0."); using MatsetView = ElementDominantMaterialView; using ValueView = axom::ArrayView; @@ -97,6 +99,7 @@ struct MixedFieldTraits struct MixedFieldTraits, FieldT> { + static_assert(MAXMATERIALS > 0, "MAXMATERIALS must be greater than 0."); using MatsetView = MaterialDominantMaterialView; using ValueView = axom::ArrayView; diff --git a/src/axom/bump/views/dispatch_material.hpp b/src/axom/bump/views/dispatch_material.hpp index 5a16ea64c4..c8187c6366 100644 --- a/src/axom/bump/views/dispatch_material.hpp +++ b/src/axom/bump/views/dispatch_material.hpp @@ -21,6 +21,11 @@ namespace views { namespace detail { +template +constexpr void verifyPositiveMaxMaterials() +{ + static_assert(MAXMATERIALS > 0, "MAXMATERIALS must be greater than 0."); +} inline void verifyMixedField(const conduit::Node &n_field) { @@ -40,11 +45,12 @@ inline void verifyMixedField(const conduit::Node &n_field) * * \return true if the dispatch worked, false otherwise. */ -template +template bool dispatch_material_unibuffer_with_values(const conduit::Node &matset, const conduit::Node &values, FuncType &&func) { + detail::verifyPositiveMaxMaterials(); bool retval = false; verify(matset, "matset"); if(conduit::blueprint::mesh::matset::is_uni_buffer(matset)) @@ -106,11 +112,12 @@ IntElement getMaterialID(const conduit::Node &matset, * * \return true if the dispatch worked, false otherwise. */ -template +template bool dispatch_material_element_dominant_with_values(const conduit::Node &matset, const conduit::Node &values_object, FuncType &&func) { + detail::verifyPositiveMaxMaterials(); bool retval = false; verify(matset, "matset"); if(conduit::blueprint::mesh::matset::is_multi_buffer(matset) && @@ -160,11 +167,12 @@ bool dispatch_material_element_dominant_with_values(const conduit::Node &matset, * * \return true if the dispatch worked, false otherwise. */ -template +template bool dispatch_material_material_dominant_with_values(const conduit::Node &matset, const conduit::Node &values_object, FuncType &&func) { + detail::verifyPositiveMaxMaterials(); bool retval = false; verify(matset, "matset"); if(conduit::blueprint::mesh::matset::is_multi_buffer(matset) && @@ -222,9 +230,10 @@ bool dispatch_material_material_dominant_with_values(const conduit::Node &matset /*! * \brief Make a unibuffer matset view from a Conduit node. */ -template +template struct make_unibuffer_matset { + static_assert(MAXMATERIALS > 0, "MAXMATERIALS must be greater than 0."); using MatsetView = UnibufferMaterialView; /*! @@ -259,9 +268,10 @@ struct make_unibuffer_matset * * \return true if the dispatch worked, false otherwise. */ -template +template bool dispatch_material_unibuffer(const conduit::Node &matset, FuncType &&func) { + detail::verifyPositiveMaxMaterials(); verify(matset, "matset"); return detail::dispatch_material_unibuffer_with_values( matset, @@ -280,9 +290,10 @@ bool dispatch_material_unibuffer(const conduit::Node &matset, FuncType &&func) * * \return true if the dispatch worked, false otherwise. */ -template +template bool dispatch_material_element_dominant(const conduit::Node &matset, FuncType &&func) { + detail::verifyPositiveMaxMaterials(); verify(matset, "matset"); return detail::dispatch_material_element_dominant_with_values(matset, matset["volume_fractions"], @@ -299,9 +310,10 @@ bool dispatch_material_element_dominant(const conduit::Node &matset, FuncType && * * \return true if the dispatch worked, false otherwise. */ -template +template bool dispatch_material_material_dominant(const conduit::Node &matset, FuncType &&func) { + detail::verifyPositiveMaxMaterials(); verify(matset, "matset"); return detail::dispatch_material_material_dominant_with_values( matset, @@ -319,9 +331,10 @@ bool dispatch_material_material_dominant(const conduit::Node &matset, FuncType & * * \return true if the dispatch worked, false otherwise. */ -template +template bool dispatch_material(const conduit::Node &matset, FuncType &&func) { + detail::verifyPositiveMaxMaterials(); bool retval = dispatch_material_unibuffer(matset, std::forward(func)); // Multibuffer diff --git a/src/axom/bump/views/dispatch_material_field.hpp b/src/axom/bump/views/dispatch_material_field.hpp index a8cf8c3563..b7ef0cc68c 100644 --- a/src/axom/bump/views/dispatch_material_field.hpp +++ b/src/axom/bump/views/dispatch_material_field.hpp @@ -96,11 +96,12 @@ bool dispatch_multibuffer_field(const conduit::Node &n_field, FuncType &&func) * \param n_field The node that contains the values to be used as volume fractions / field. * \param func The function/lambda that will operate on the matset and mixed field views. */ -template +template bool dispatch_material_unibuffer_field(const conduit::Node &matset, const conduit::Node &n_field, FuncType &&func) { + detail::verifyPositiveMaxMaterials(); verify(matset, "matset"); detail::verifyMixedField(n_field); @@ -126,11 +127,12 @@ bool dispatch_material_unibuffer_field(const conduit::Node &matset, * \param n_field The node that contains the values to be used as volume fractions / field. * \param func The function/lambda that will operate on the matset and mixed field views. */ -template +template bool dispatch_material_element_dominant_field(const conduit::Node &matset, const conduit::Node &n_field, FuncType &&func) { + detail::verifyPositiveMaxMaterials(); verify(matset, "matset"); detail::verifyMixedField(n_field); detail::verifyMatchingMaterialOrder(matset["volume_fractions"], n_field["matset_values"]); @@ -156,11 +158,12 @@ bool dispatch_material_element_dominant_field(const conduit::Node &matset, * \param n_field The node that contains the values to be used as volume fractions / field. * \param func The function/lambda that will operate on the matset and mixed field views. */ -template +template bool dispatch_material_material_dominant_field(const conduit::Node &matset, const conduit::Node &n_field, FuncType &&func) { + detail::verifyPositiveMaxMaterials(); verify(matset, "matset"); detail::verifyMixedField(n_field); detail::verifyMatchingMaterialOrder(matset["volume_fractions"], n_field["matset_values"]); @@ -187,9 +190,10 @@ bool dispatch_material_material_dominant_field(const conduit::Node &matset, * \param n_field The node that contains the values to be used as volume fractions / field. * \param func The function/lambda that will operate on the matset and mixed field views. */ -template +template bool dispatch_material_field(const conduit::Node &matset, const conduit::Node &n_field, FuncType &&func) { + detail::verifyPositiveMaxMaterials(); bool retval = dispatch_material_unibuffer_field(matset, n_field, From c26998ac588e90fc031406d388e80025f5796652 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 24 Jun 2026 11:12:12 -0700 Subject: [PATCH 535/986] make style --- src/axom/bump/views/dispatch_material_field.hpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/axom/bump/views/dispatch_material_field.hpp b/src/axom/bump/views/dispatch_material_field.hpp index b7ef0cc68c..3cabd66750 100644 --- a/src/axom/bump/views/dispatch_material_field.hpp +++ b/src/axom/bump/views/dispatch_material_field.hpp @@ -20,13 +20,15 @@ namespace detail inline void verifyMatchingMaterialOrder(const conduit::Node &mat_values, const conduit::Node &field_values) { - SLIC_ERROR_IF(mat_values.number_of_children() != field_values.number_of_children(), - "The matset volume_fractions and field matset_values have different numbers of materials."); + SLIC_ERROR_IF( + mat_values.number_of_children() != field_values.number_of_children(), + "The matset volume_fractions and field matset_values have different numbers of materials."); for(conduit::index_t i = 0; i < mat_values.number_of_children(); i++) { - SLIC_ERROR_IF(mat_values[i].name() != field_values[i].name(), - "The matset volume_fractions and field matset_values do not have matching material order."); + SLIC_ERROR_IF( + mat_values[i].name() != field_values[i].name(), + "The matset volume_fractions and field matset_values do not have matching material order."); } } From bafa1970a0b328a0b960d044658caf92b5030850 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 24 Jun 2026 11:46:44 -0700 Subject: [PATCH 536/986] Incorporate feedback. --- scripts/spack/packages/axom/package.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/scripts/spack/packages/axom/package.py b/scripts/spack/packages/axom/package.py index 6d6593f6f0..a0f54db43c 100644 --- a/scripts/spack/packages/axom/package.py +++ b/scripts/spack/packages/axom/package.py @@ -155,11 +155,10 @@ class Axom(CachedCMakePackage, CudaPackage, ROCmPackage): variant("lua", default=True, description="Build with Lua") variant("mfem", default=False, description="Build with mfem") variant("opencascade", default=False, description="Build with opencascade") + variant("raja", default=True, description="Build with raja") variant("scr", default=False, description="Build with SCR") variant("umpire", default=True, description="Build with umpire") - variant("raja", default=True, description="Build with raja") - varmsg = "Build development tools (such as Sphinx, Doxygen, etc...)" variant("devtools", default=False, description=varmsg) @@ -230,7 +229,7 @@ class Axom(CachedCMakePackage, CudaPackage, ROCmPackage): depends_on("caliper", when="+caliper") with when("+profiling"): depends_on("adiak") - depends_on("caliper+adiak~papi") + depends_on("caliper+adiak") depends_on("caliper+cuda", when="+cuda") depends_on("caliper~cuda", when="~cuda") From 993710ab33c3b61c39493e0020feb625a26a58bb Mon Sep 17 00:00:00 2001 From: Rich Hornung Date: Thu, 25 Jun 2026 08:43:31 -0700 Subject: [PATCH 537/986] clang-format --- src/axom/quest/detail/DistributedClosestPointImpl.hpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/axom/quest/detail/DistributedClosestPointImpl.hpp b/src/axom/quest/detail/DistributedClosestPointImpl.hpp index b17b7c0416..abe5d516b4 100644 --- a/src/axom/quest/detail/DistributedClosestPointImpl.hpp +++ b/src/axom/quest/detail/DistributedClosestPointImpl.hpp @@ -479,8 +479,7 @@ class DistributedClosestPointImpl } /// Wait for some non-blocking sends (if any) to finish. - void check_send_requests(std::list& isendRequests, - bool atLeastOne) const + void check_send_requests(std::list& isendRequests, bool atLeastOne) const { std::vector reqs; reqs.reserve(isendRequests.size()); @@ -753,8 +752,8 @@ class DistributedClosestPointExec : public DistributedClosestPointImpl std::list isendRequests; { - // create conduit Node containing data that has to xfer between ranks. - // The node will be mostly empty if there are no domains on this rank + // create conduit Node containing data that has to xfer between ranks. + // The node will be mostly empty if there are no domains on this rank conduit::Node xferNode; node_copy_query_to_xfer(queryMesh, xferNode, topologyName); xferNode["homeRank"] = m_rank; From 5bbec91dccb454d821293a9bfb7a57ae3131d745 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 25 Jun 2026 12:03:27 -0700 Subject: [PATCH 538/986] 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 b0a56090423e45196e30e882aa816c215780311a Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 25 Jun 2026 15:48:24 -0700 Subject: [PATCH 539/986] Fix axom::copy + Umpire race --- RELEASE-NOTES.md | 1 + src/axom/core/memory_management.hpp | 34 +++++++++- src/axom/core/tests/CMakeLists.txt | 12 ++++ src/axom/core/tests/core_copy_openmp_race.cpp | 62 +++++++++++++++++++ 4 files changed, 106 insertions(+), 3 deletions(-) create mode 100644 src/axom/core/tests/core_copy_openmp_race.cpp diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 5ad996c74a..e0db007a58 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -62,6 +62,7 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ - Core: A moved-from Array is valid, e.g. it can be pushed to - Core: Improved `axom::FlatMap` insertion performance by fusing duplicate-key lookup with empty-slot probing. - Core: Updated DeviceHash to use 64-bit hash results and improved coverage for integer and floating-point hashing. +- Core: Avoids a first-use race in `axom::copy()` when multiple OpenMP threads concurrently trigger Umpire fallback host-copy initialization. - Python: Improves lifetime handling for python wrapped sidre entities, including support for external views into numpy arrays. ## [Version 0.14.0] - Release date 2026-03-31 diff --git a/src/axom/core/memory_management.hpp b/src/axom/core/memory_management.hpp index 3fb93ef238..7d7f017c01 100644 --- a/src/axom/core/memory_management.hpp +++ b/src/axom/core/memory_management.hpp @@ -27,11 +27,39 @@ #include #include #include +#include #include #include namespace axom { +#ifdef AXOM_USE_UMPIRE +namespace detail +{ +struct UmpireCopyContext +{ + umpire::strategy::AllocationStrategy* hostStrategy {nullptr}; + umpire::op::MemoryOperationRegistry* operationRegistry {nullptr}; +}; + +inline const UmpireCopyContext& getUmpireCopyContext() noexcept +{ + static std::once_flag once; + static UmpireCopyContext context {}; + + // Resolve Umpire's fallback HOST path once so the first threaded axom::copy() + // cannot race through lazy resource creation. + std::call_once(once, []() { + auto& rm = umpire::ResourceManager::getInstance(); + context.hostStrategy = rm.getAllocator("HOST").getAllocationStrategy(); + context.operationRegistry = &umpire::op::MemoryOperationRegistry::getInstance(); + }); + + return context; +} +} // namespace detail +#endif + // To co-exist with Umpire allocator ids, use negative values here. constexpr int INVALID_ALLOCATOR_ID = -1; //!< Place holder for no/unknown allocator constexpr int MALLOC_ALLOCATOR_ID = -3; //!< Refers to MemorySpace::Malloc @@ -462,11 +490,11 @@ inline T* reallocate(T* pointer, std::size_t n, int allocID) noexcept inline void copy(void* dst, const void* src, std::size_t numbytes) noexcept { #ifdef AXOM_USE_UMPIRE + const auto& copyContext = detail::getUmpireCopyContext(); umpire::ResourceManager& rm = umpire::ResourceManager::getInstance(); - umpire::op::MemoryOperationRegistry& op_registry = - umpire::op::MemoryOperationRegistry::getInstance(); + umpire::op::MemoryOperationRegistry& op_registry = *copyContext.operationRegistry; - auto dstStrategy = rm.getAllocator("HOST").getAllocationStrategy(); + auto dstStrategy = copyContext.hostStrategy; auto srcStrategy = dstStrategy; using AllocationRecord = umpire::util::AllocationRecord; diff --git a/src/axom/core/tests/CMakeLists.txt b/src/axom/core/tests/CMakeLists.txt index 7322f589f6..ba627f713e 100644 --- a/src/axom/core/tests/CMakeLists.txt +++ b/src/axom/core/tests/CMakeLists.txt @@ -153,6 +153,18 @@ if (AXOM_ENABLE_OPENMP AND RAJA_FOUND) COMMAND core_openmp_tests --gtest_filter=${test_suite}* NUM_OMP_THREADS ${AXOM_TEST_NUM_OMP_THREADS} ) endforeach() + + if (UMPIRE_FOUND) + axom_add_executable(NAME core_copy_openmp_race_test + SOURCES core_copy_openmp_race.cpp + OUTPUT_DIR ${TEST_OUTPUT_DIRECTORY} + DEPENDS_ON ${core_test_depends} + FOLDER axom/core/tests ) + + axom_add_test( NAME core_copy_openmp_race + COMMAND core_copy_openmp_race_test + NUM_OMP_THREADS ${AXOM_TEST_NUM_OMP_THREADS} ) + endif() endif() #------------------------------------------------------------------------------ diff --git a/src/axom/core/tests/core_copy_openmp_race.cpp b/src/axom/core/tests/core_copy_openmp_race.cpp new file mode 100644 index 0000000000..497394b880 --- /dev/null +++ b/src/axom/core/tests/core_copy_openmp_race.cpp @@ -0,0 +1,62 @@ +// 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/execution/for_all.hpp" +#include "axom/core/memory_management.hpp" + +#include + +namespace +{ +template +struct CopySlices +{ + static void run(int* dst, const int* src, axom::IndexType nslices, axom::IndexType sliceSize) + { + axom::for_all(nslices, [=](axom::IndexType slice) { + axom::copy(dst + slice * sliceSize, src + slice * sliceSize, sliceSize * sizeof(int)); + }); + } +}; +} // namespace + +TEST(core_copy_openmp_race, first_use_inside_for_all) +{ + constexpr axom::IndexType nslices = 128; + constexpr axom::IndexType sliceSize = 64; + constexpr axom::IndexType size = nslices * sliceSize; + + auto* src = static_cast(std::malloc(size * sizeof(int))); + auto* dst = static_cast(std::malloc(size * sizeof(int))); + + ASSERT_NE(src, nullptr); + ASSERT_NE(dst, nullptr); + + for(axom::IndexType i = 0; i < size; ++i) + { + src[i] = static_cast(i); + dst[i] = -1; + } + + CopySlices::run(dst, src, nslices, sliceSize); + + for(axom::IndexType i = 0; i < size; ++i) + { + EXPECT_EQ(dst[i], src[i]); + } + + std::free(dst); + std::free(src); +} + +int main(int argc, char** argv) +{ + ::testing::InitGoogleTest(&argc, argv); + ::testing::FLAGS_gtest_death_test_style = "threadsafe"; + return RUN_ALL_TESTS(); +} From 8f4dd6e0e286fc255257969b7b9c3580e230b9a3 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 25 Jun 2026 16:02:16 -0700 Subject: [PATCH 540/986] Use axom::Array instead of malloc/free. --- src/axom/core/tests/core_copy_openmp_race.cpp | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/axom/core/tests/core_copy_openmp_race.cpp b/src/axom/core/tests/core_copy_openmp_race.cpp index 497394b880..943b4d1370 100644 --- a/src/axom/core/tests/core_copy_openmp_race.cpp +++ b/src/axom/core/tests/core_copy_openmp_race.cpp @@ -6,11 +6,10 @@ #include "gtest/gtest.h" +#include "axom/core/Array.hpp" #include "axom/core/execution/for_all.hpp" #include "axom/core/memory_management.hpp" -#include - namespace { template @@ -31,11 +30,8 @@ TEST(core_copy_openmp_race, first_use_inside_for_all) constexpr axom::IndexType sliceSize = 64; constexpr axom::IndexType size = nslices * sliceSize; - auto* src = static_cast(std::malloc(size * sizeof(int))); - auto* dst = static_cast(std::malloc(size * sizeof(int))); - - ASSERT_NE(src, nullptr); - ASSERT_NE(dst, nullptr); + axom::Array src(size); + axom::Array dst(size); for(axom::IndexType i = 0; i < size; ++i) { @@ -43,15 +39,12 @@ TEST(core_copy_openmp_race, first_use_inside_for_all) dst[i] = -1; } - CopySlices::run(dst, src, nslices, sliceSize); + CopySlices::run(dst.data(), src.data(), nslices, sliceSize); for(axom::IndexType i = 0; i < size; ++i) { EXPECT_EQ(dst[i], src[i]); } - - std::free(dst); - std::free(src); } int main(int argc, char** argv) From 45dc46573a1709d4b0c0bda5712f8ca91c018d68 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 25 Jun 2026 18:22:35 -0700 Subject: [PATCH 541/986] 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 a07e6c4236759a25f568d052434325a9308af422 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 25 Jun 2026 18:33:55 -0700 Subject: [PATCH 542/986] Added some comments. --- src/axom/core/memory_management.hpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/axom/core/memory_management.hpp b/src/axom/core/memory_management.hpp index 7d7f017c01..c382c337b0 100644 --- a/src/axom/core/memory_management.hpp +++ b/src/axom/core/memory_management.hpp @@ -36,12 +36,22 @@ namespace axom #ifdef AXOM_USE_UMPIRE namespace detail { +/*! + * \brief Cache for Umpire data used in axom::copy. + */ struct UmpireCopyContext { umpire::strategy::AllocationStrategy* hostStrategy {nullptr}; umpire::op::MemoryOperationRegistry* operationRegistry {nullptr}; }; +/*! + * \brief Gets a reference to an UmpireCopyContext object, initializing it on demand. + * The static UmpireCopyContext is initialized via std::call_once so multiple + * threads can call this function and only initialize the object once. + * + * \return A reference to the cached UmpireCopyContext. + */ inline const UmpireCopyContext& getUmpireCopyContext() noexcept { static std::once_flag once; From e4a834ecbca5958d07e6fc327e680b7d9cb611d0 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Fri, 26 Jun 2026 11:22:29 -0700 Subject: [PATCH 543/986] Represent QuadratureFunctions as mcarray. --- RELEASE-NOTES.md | 1 + .../sidre/core/MFEMSidreDataCollection.cpp | 92 ++++++++++++------- .../sidre/core/MFEMSidreDataCollection.hpp | 17 ++++ .../sidre/tests/sidre_mfem_datacollection.cpp | 47 ++++++++++ 4 files changed, 126 insertions(+), 31 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 5a7d2e09b9..5730089e41 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -66,6 +66,7 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ - Core: Improved `axom::FlatMap` insertion performance by fusing duplicate-key lookup with empty-slot probing. - Core: Updated DeviceHash to use 64-bit hash results and improved coverage for integer and floating-point hashing. - Python: Improves lifetime handling for python wrapped sidre entities, including support for external views into numpy arrays. +- Sidre: Vector-valued MFEM `QuadratureFunction` fields exported through `MFEMSidreDataCollection` now use Blueprint mcarray component storage under `values`, instead of a single scalar array. ## [Version 0.14.0] - Release date 2026-03-31 diff --git a/src/axom/sidre/core/MFEMSidreDataCollection.cpp b/src/axom/sidre/core/MFEMSidreDataCollection.cpp index d877a3c3c6..8094738324 100644 --- a/src/axom/sidre/core/MFEMSidreDataCollection.cpp +++ b/src/axom/sidre/core/MFEMSidreDataCollection.cpp @@ -1241,10 +1241,13 @@ void MFEMSidreDataCollection::addScalarBasedField(const std::string& field_name, } // private method -void MFEMSidreDataCollection::addVectorBasedGridFunction(const std::string& field_name, - GridFunction* gf, - const std::string& buffer_name, - IndexType offset) +void MFEMSidreDataCollection::addVectorBasedField(const std::string& field_name, + mfem::Vector* field, + const std::string& buffer_name, + IndexType offset, + int vdim, + int ndof, + Ordering::Type ordering) { sidre::Group* grp = m_bp_grp->getGroup("fields/" + field_name); SLIC_ASSERT_MSG(grp != nullptr, "field " << field_name << " does not exist"); @@ -1252,33 +1255,18 @@ void MFEMSidreDataCollection::addVectorBasedGridFunction(const std::string& fiel const int FLD_SZ = 20; char fidxName[FLD_SZ]; - int vdim = gf->FESpace()->GetVDim(); - int ndof = gf->FESpace()->GetNDofs(); - Ordering::Type ordering = gf->FESpace()->GetOrdering(); - - if(gf->GetData() == nullptr) + if(field->GetData() == nullptr) { AllocNamedBuffer(buffer_name, offset + vdim * ndof); - // gf->data is set below. + // field->data is set below. } - /* - * Mesh blueprint for a vector-based grid function is of the form - * /fields/field_name/basis - * -- string value is GridFunction's FEC::Name - * /fields/field_name/values/x0 - * /fields/field_name/values/x1 - * ... - * /fields/field_name/values/xn - * -- each coordinate is an array of size ndof - */ - // Get/create the Group "values". sidre::Group* vg = alloc_group(grp, "values"); - // Create the Views "x0", "x1", etc inside the "values" Group, vg. + // Create the Views "x0", "x1", etc inside the "values" Group. // If we have a named buffer for field_name, attach it to the Views; - // otherwise set the Views to use gf->GetData() as external data. + // otherwise set the Views to use field->GetData() as external data. sidre::DataType dtype = sidre::DataType::c_double(ndof); const int entry_stride = (ordering == Ordering::byNODES ? 1 : vdim); const int vdim_stride = (ordering == Ordering::byNODES ? ndof : 1); @@ -1301,7 +1289,7 @@ void MFEMSidreDataCollection::addVectorBasedGridFunction(const std::string& fiel dtype.set_offset(dtype.offset() + dtype.element_bytes() * vdim_stride); } - gf->NewDataAndSize(bv->getData() + offset, vdim * ndof); + field->NewDataAndSize(bv->getData() + offset, vdim * ndof); } else { @@ -1309,7 +1297,7 @@ void MFEMSidreDataCollection::addVectorBasedGridFunction(const std::string& fiel { std::snprintf(fidxName, FLD_SZ, "x%d", d); sidre::View* xv = alloc_view(vg, fidxName, dtype); - xv->setExternalDataPtr(gf->GetData()); + xv->setExternalDataPtr(field->GetData()); dtype.set_offset(dtype.offset() + dtype.element_bytes() * vdim_stride); } } @@ -1322,13 +1310,38 @@ void MFEMSidreDataCollection::addVectorBasedGridFunction(const std::string& fiel SLIC_ASSERT_MSG( (ndof > 0 && xv->isApplied()) || (ndof == 0 && xv->isEmpty() && xv->isDescribed()), "invalid View state"); - SLIC_ASSERT_MSG(ndof == 0 || xv->getData() == gf->GetData() + d * vdim_stride, - "View data is different from GridFunction data"); - SLIC_ASSERT_MSG(xv->getNumElements() == ndof, "View size is different from GridFunction size"); + SLIC_ASSERT_MSG(ndof == 0 || xv->getData() == field->GetData() + d * vdim_stride, + "View data is different from field data"); + SLIC_ASSERT_MSG(xv->getNumElements() == ndof, "View size is different from field size"); } #endif } +// private method +void MFEMSidreDataCollection::addVectorBasedGridFunction(const std::string& field_name, + GridFunction* gf, + const std::string& buffer_name, + IndexType offset) +{ + /* + * Mesh blueprint for a vector-based grid function is of the form + * /fields/field_name/basis + * -- string value is GridFunction's FEC::Name + * /fields/field_name/values/x0 + * /fields/field_name/values/x1 + * ... + * /fields/field_name/values/xn + * -- each coordinate is an array of size ndof + */ + addVectorBasedField(field_name, + gf, + buffer_name, + offset, + gf->FESpace()->GetVDim(), + gf->FESpace()->GetNDofs(), + gf->FESpace()->GetOrdering()); +} + // private method // Should only be called on mpi rank 0 ( or if serial problem ). void MFEMSidreDataCollection::RegisterFieldInBPIndex(const std::string& field_name, @@ -1507,7 +1520,10 @@ void MFEMSidreDataCollection::RegisterQField(const std::string& field_name, // A QField has the following schema: // /fields// // /fields//topology = "mesh" - // /fields//values = array of size QuadratureFunction::Size() + // /fields//values = scalar array of size + // QuadratureFunction::Size() + // or mcarray components of size + // QuadratureSpace::GetSize() // /fields//basis = string that encodes the QF // integration order and vector dimension // "QF_Default_[ORDER]_[VDIM]" @@ -1523,8 +1539,22 @@ void MFEMSidreDataCollection::RegisterQField(const std::string& field_name, // This is always 'mesh' v = alloc_view(grp, "topology")->setString("mesh"); - // Set the View "/fields//values" - addScalarBasedField(field_name, qf, buffer_name, offset, qf->Size()); + if(qf->GetVDim() == 1) + { + // Set the View "/fields//values" + addScalarBasedField(field_name, qf, buffer_name, offset, qf->Size()); + } + else + { + // Set the Group "/fields//values" + addVectorBasedField(field_name, + qf, + buffer_name, + offset, + qf->GetVDim(), + qf->GetSpace()->GetSize(), + Ordering::byVDIM); + } // Register field_name in the blueprint_index group. if(myid == 0) diff --git a/src/axom/sidre/core/MFEMSidreDataCollection.hpp b/src/axom/sidre/core/MFEMSidreDataCollection.hpp index 6126a01ec2..12f02946c2 100644 --- a/src/axom/sidre/core/MFEMSidreDataCollection.hpp +++ b/src/axom/sidre/core/MFEMSidreDataCollection.hpp @@ -644,6 +644,23 @@ class MFEMSidreDataCollection : public mfem::DataCollection IndexType offset, const int num_dofs); + /** + * \brief A private helper function to set up the views associated with the + data of a vector valued grid *or* quadrature function in the blueprint style. + * \pre field is not null + * \note This function is expected to be called by RegisterField() or RegisterQField() + * \note Handles cases where hierarchy is already set up, + * where the data was allocated by this data collection + * and where the field data is external to Sidre + */ + void addVectorBasedField(const std::string& field_name, + mfem::Vector* field, + const std::string& buffer_name, + IndexType offset, + int vdim, + int ndof, + mfem::Ordering::Type ordering); + /** * \brief A private helper function to set up the views associated with the data of a vector valued grid function in the blueprint style. diff --git a/src/axom/sidre/tests/sidre_mfem_datacollection.cpp b/src/axom/sidre/tests/sidre_mfem_datacollection.cpp index 8409b36bcc..08de7eb7d3 100644 --- a/src/axom/sidre/tests/sidre_mfem_datacollection.cpp +++ b/src/axom/sidre/tests/sidre_mfem_datacollection.cpp @@ -17,6 +17,8 @@ #include "mfem.hpp" #include "gtest/gtest.h" +#include "conduit_blueprint.hpp" + #ifdef AXOM_USE_MPI #include "mpi.h" #endif @@ -28,6 +30,11 @@ constexpr double EPSILON = 1.0e-6; std::string testName() { return ::testing::UnitTest::GetInstance()->current_test_info()->name(); } +void checkMcarrayFieldValues(axom::sidre::Group* bp_grp, + const std::string& field_name, + int vdim, + int ndof); + TEST(sidre_datacollection, dc_alloc_no_mesh) { MFEMSidreDataCollection sdc(testName()); @@ -415,6 +422,13 @@ TEST(sidre_datacollection, dc_reload_qf) sdc_writer.RegisterQField("qs", &qs); sdc_writer.RegisterQField("qv", &qv); + auto* writer_bp_grp = sdc_writer.GetBPGroup(); + ASSERT_NE(writer_bp_grp, nullptr); + EXPECT_TRUE(writer_bp_grp->hasView("fields/qs/values")); + EXPECT_FALSE(writer_bp_grp->hasGroup("fields/qs/values")); + EXPECT_FALSE(writer_bp_grp->hasView("fields/qv/values")); + checkMcarrayFieldValues(writer_bp_grp, "qv", qv_vdim, qspace.GetSize()); + // The data needs to be instantiated before we save it off for(int i = 0; i < Nq; ++i) { @@ -444,6 +458,13 @@ TEST(sidre_datacollection, dc_reload_qf) mfem::QuadratureFunction* reader_qs = sdc_reader.GetQField("qs"); mfem::QuadratureFunction* reader_qv = sdc_reader.GetQField("qv"); + auto* reader_bp_grp = sdc_reader.GetBPGroup(); + ASSERT_NE(reader_bp_grp, nullptr); + EXPECT_TRUE(reader_bp_grp->hasView("fields/qs/values")); + EXPECT_FALSE(reader_bp_grp->hasGroup("fields/qs/values")); + EXPECT_FALSE(reader_bp_grp->hasView("fields/qv/values")); + checkMcarrayFieldValues(reader_bp_grp, "qv", qv_vdim, qspace.GetSize()); + // order_qs should also equal order_qv in this trivial case EXPECT_EQ(reader_qs->GetSpace()->GetOrder(), intOrder); EXPECT_EQ(reader_qv->GetSpace()->GetOrder(), intOrder); @@ -478,6 +499,32 @@ void checkReferentialEquality(axom::sidre::Group* grp, } } +void checkMcarrayFieldValues(axom::sidre::Group* bp_grp, + const std::string& field_name, + int vdim, + int ndof) +{ + ASSERT_TRUE(bp_grp->hasGroup("fields/" + field_name + "/values")); + auto* values_grp = bp_grp->getGroup("fields/" + field_name + "/values"); + ASSERT_NE(values_grp, nullptr); + EXPECT_EQ(values_grp->getNumViews(), vdim); + + for(int d = 0; d < vdim; ++d) + { + const std::string view_name = "x" + std::to_string(d); + ASSERT_TRUE(values_grp->hasView(view_name)); + auto* component_view = values_grp->getView(view_name); + ASSERT_NE(component_view, nullptr); + EXPECT_EQ(component_view->getNumElements(), ndof); + EXPECT_EQ(component_view->getStride(), static_cast(vdim)); + } + + conduit::Node values_node; + conduit::Node info; + EXPECT_TRUE(values_grp->createNativeLayout(values_node)); + EXPECT_TRUE(conduit::blueprint::verify("mcarray", values_node, info)) << info.to_string(); +} + TEST(sidre_datacollection, create_matset) { // 1D mesh divided into 10 segments From 933bc2d8f5ba432a45d6d3c8d3e7f3b1bf6bf6e6 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Fri, 26 Jun 2026 11:46:29 -0700 Subject: [PATCH 544/986] Moved code into addVectorBasedQuadratureFunction method. --- .../sidre/core/MFEMSidreDataCollection.cpp | 37 +++++++++++++++---- .../sidre/core/MFEMSidreDataCollection.hpp | 14 +++++++ 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/src/axom/sidre/core/MFEMSidreDataCollection.cpp b/src/axom/sidre/core/MFEMSidreDataCollection.cpp index 8094738324..32bc95f2a1 100644 --- a/src/axom/sidre/core/MFEMSidreDataCollection.cpp +++ b/src/axom/sidre/core/MFEMSidreDataCollection.cpp @@ -1342,6 +1342,35 @@ void MFEMSidreDataCollection::addVectorBasedGridFunction(const std::string& fiel gf->FESpace()->GetOrdering()); } +// private method +void MFEMSidreDataCollection::addVectorBasedQuadratureFunction( + const std::string& field_name, + mfem::QuadratureFunction* qf, + const std::string& buffer_name, + IndexType offset) +{ + /* + * Mesh blueprint for a vector-based quadrature function is of the form + * /fields/field_name/basis + * -- string value encodes the QuadratureSpace order and vdim + * /fields/field_name/values/x0 + * /fields/field_name/values/x1 + * ... + * /fields/field_name/values/xn + * -- each component is an array of size QuadratureSpace::GetSize() + * + * MFEM stores QuadratureFunction vectors in byVDIM order, i.e. one tuple per + * quadrature point with component data interleaved in memory. + */ + addVectorBasedField(field_name, + qf, + buffer_name, + offset, + qf->GetVDim(), + qf->GetSpace()->GetSize(), + Ordering::byVDIM); +} + // private method // Should only be called on mpi rank 0 ( or if serial problem ). void MFEMSidreDataCollection::RegisterFieldInBPIndex(const std::string& field_name, @@ -1547,13 +1576,7 @@ void MFEMSidreDataCollection::RegisterQField(const std::string& field_name, else { // Set the Group "/fields//values" - addVectorBasedField(field_name, - qf, - buffer_name, - offset, - qf->GetVDim(), - qf->GetSpace()->GetSize(), - Ordering::byVDIM); + addVectorBasedQuadratureFunction(field_name, qf, buffer_name, offset); } // Register field_name in the blueprint_index group. diff --git a/src/axom/sidre/core/MFEMSidreDataCollection.hpp b/src/axom/sidre/core/MFEMSidreDataCollection.hpp index 12f02946c2..50cb2cb9b1 100644 --- a/src/axom/sidre/core/MFEMSidreDataCollection.hpp +++ b/src/axom/sidre/core/MFEMSidreDataCollection.hpp @@ -675,6 +675,20 @@ class MFEMSidreDataCollection : public mfem::DataCollection const std::string& buffer_name, IndexType offset); + /** + * \brief A private helper function to set up the views associated with the + data of a vector valued quadrature function in the blueprint style. + * \pre qf is not null + * \note This function is expected to be called by RegisterQField() + * \note QuadratureFunction data is always interpreted with MFEM's byVDIM + * layout, with one mcarray component per vector component and one entry + * per quadrature tuple in the associated QuadratureSpace. + */ + void addVectorBasedQuadratureFunction(const std::string& field_name, + mfem::QuadratureFunction* qf, + const std::string& buffer_name, + IndexType offset); + /** @brief A private helper function to set up the Views associated with attribute field named @a field_name */ void addIntegerAttributeField(const std::string& field_name, bool is_bdry); From dd9131aa26270baaa359626795ebb37495ad559b Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Fri, 26 Jun 2026 11:47:24 -0700 Subject: [PATCH 545/986] Added test for HO QuadratureFunction. --- .../sidre/tests/sidre_mfem_datacollection.cpp | 106 ++++++++++++++++-- 1 file changed, 94 insertions(+), 12 deletions(-) diff --git a/src/axom/sidre/tests/sidre_mfem_datacollection.cpp b/src/axom/sidre/tests/sidre_mfem_datacollection.cpp index 08de7eb7d3..8b6804aeb6 100644 --- a/src/axom/sidre/tests/sidre_mfem_datacollection.cpp +++ b/src/axom/sidre/tests/sidre_mfem_datacollection.cpp @@ -35,6 +35,85 @@ void checkMcarrayFieldValues(axom::sidre::Group* bp_grp, int vdim, int ndof); +double quadratureValueFor(int element, int qpt, int component) +{ + return 1000. * element + 10. * qpt + 0.5 * component + 0.25; +} + +void setQuadratureFunctionValues(mfem::QuadratureFunction& qf) +{ + auto* qspace = qf.GetSpace(); + ASSERT_NE(qspace, nullptr); + + for(int el = 0; el < qspace->GetNE(); ++el) + { + const auto& ir = qspace->GetIntRule(el); + for(int qpt = 0; qpt < ir.Size(); ++qpt) + { + mfem::Vector values; + qf.GetValues(el, qpt, values); + ASSERT_EQ(values.Size(), qf.GetVDim()); + + for(int comp = 0; comp < qf.GetVDim(); ++comp) + { + values(comp) = quadratureValueFor(el, qpt, comp); + } + } + } +} + +void checkQuadratureFunctionValues(const mfem::QuadratureFunction& qf) +{ + auto* qspace = qf.GetSpace(); + ASSERT_NE(qspace, nullptr); + + for(int el = 0; el < qspace->GetNE(); ++el) + { + const auto& ir = qspace->GetIntRule(el); + for(int qpt = 0; qpt < ir.Size(); ++qpt) + { + mfem::Vector values; + qf.GetValues(el, qpt, values); + ASSERT_EQ(values.Size(), qf.GetVDim()); + + for(int comp = 0; comp < qf.GetVDim(); ++comp) + { + EXPECT_DOUBLE_EQ(values(comp), quadratureValueFor(el, qpt, comp)); + } + } + } +} + +void checkQuadratureFunctionMcarrayData(axom::sidre::Group* bp_grp, + const std::string& field_name, + const mfem::QuadratureSpaceBase& qspace, + int vdim) +{ + auto* values_grp = bp_grp->getGroup("fields/" + field_name + "/values"); + ASSERT_NE(values_grp, nullptr); + + for(int el = 0; el < qspace.GetNE(); ++el) + { + const int offset = qspace.Offset(el); + const auto& ir = qspace.GetIntRule(el); + for(int qpt = 0; qpt < ir.Size(); ++qpt) + { + for(int comp = 0; comp < vdim; ++comp) + { + const std::string view_name = "x" + std::to_string(comp); + auto* component_view = values_grp->getView(view_name); + ASSERT_NE(component_view, nullptr); + const double* component_data = component_view->getData(); + ASSERT_NE(component_data, nullptr); + const auto stride = + static_cast(component_view->getStride()); + EXPECT_DOUBLE_EQ(component_data[(offset + qpt) * stride], + quadratureValueFor(el, qpt, comp)); + } + } + } +} + TEST(sidre_datacollection, dc_alloc_no_mesh) { MFEMSidreDataCollection sdc(testName()); @@ -389,10 +468,8 @@ TEST(sidre_datacollection, dc_reload_mesh) TEST(sidre_datacollection, dc_reload_qf) { - //Set up a small mesh and a couple of grid function on that mesh + // Set up a high-order quadrature space so each element has multiple quadrature points. auto mesh = mfem::Mesh::MakeCartesian2D(2, 3, mfem::Element::QUADRILATERAL, 0, 2., 3.); - mfem::LinearFECollection fec; - mfem::FiniteElementSpace fes(&mesh, &fec); const int intOrder = 3; const int qs_vdim = 1; @@ -407,8 +484,6 @@ TEST(sidre_datacollection, dc_reload_qf) mfem::QuadratureFunction qv(&qspace, qv_vdim); qv.NewDataAndSize(nullptr, qv_vdim * qspace.GetSize()); - int Nq = qs.Size(); - // The mesh and field(s) must be owned by Sidre to properly manage data in case of // a simulated restart (save -> load) const bool owns_mesh_data = true; @@ -428,14 +503,14 @@ TEST(sidre_datacollection, dc_reload_qf) EXPECT_FALSE(writer_bp_grp->hasGroup("fields/qs/values")); EXPECT_FALSE(writer_bp_grp->hasView("fields/qv/values")); checkMcarrayFieldValues(writer_bp_grp, "qv", qv_vdim, qspace.GetSize()); + EXPECT_GT(qspace.GetIntRule(0).Size(), 1); - // The data needs to be instantiated before we save it off - for(int i = 0; i < Nq; ++i) - { - qs(i) = double(i); - qv(2 * i + 0) = double(i); - qv(2 * i + 1) = double(Nq - i - 1); - } + // Fill by element/quadrature point/component so we test high-order layout explicitly. + setQuadratureFunctionValues(qs); + setQuadratureFunctionValues(qv); + checkQuadratureFunctionValues(qs); + checkQuadratureFunctionValues(qv); + checkQuadratureFunctionMcarrayData(writer_bp_grp, "qv", qspace, qv_vdim); sdc_writer.SetCycle(5); sdc_writer.SetTime(8.0); @@ -464,6 +539,10 @@ TEST(sidre_datacollection, dc_reload_qf) EXPECT_FALSE(reader_bp_grp->hasGroup("fields/qs/values")); EXPECT_FALSE(reader_bp_grp->hasView("fields/qv/values")); checkMcarrayFieldValues(reader_bp_grp, "qv", qv_vdim, qspace.GetSize()); + checkQuadratureFunctionMcarrayData(reader_bp_grp, + "qv", + *reader_qv->GetSpace(), + reader_qv->GetVDim()); // order_qs should also equal order_qv in this trivial case EXPECT_EQ(reader_qs->GetSpace()->GetOrder(), intOrder); @@ -472,6 +551,9 @@ TEST(sidre_datacollection, dc_reload_qf) EXPECT_EQ(reader_qs->GetVDim(), qs_vdim); EXPECT_EQ(reader_qv->GetVDim(), qv_vdim); + checkQuadratureFunctionValues(*reader_qs); + checkQuadratureFunctionValues(*reader_qv); + *(reader_qs) -= qs; *(reader_qv) -= qv; From e264caf30fac351e4a3cf76e6e1e8443f2caa847 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Fri, 26 Jun 2026 11:47:51 -0700 Subject: [PATCH 546/986] make style --- src/axom/sidre/core/MFEMSidreDataCollection.cpp | 9 ++++----- src/axom/sidre/tests/sidre_mfem_datacollection.cpp | 11 +++-------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/axom/sidre/core/MFEMSidreDataCollection.cpp b/src/axom/sidre/core/MFEMSidreDataCollection.cpp index 32bc95f2a1..18b1f95449 100644 --- a/src/axom/sidre/core/MFEMSidreDataCollection.cpp +++ b/src/axom/sidre/core/MFEMSidreDataCollection.cpp @@ -1343,11 +1343,10 @@ void MFEMSidreDataCollection::addVectorBasedGridFunction(const std::string& fiel } // private method -void MFEMSidreDataCollection::addVectorBasedQuadratureFunction( - const std::string& field_name, - mfem::QuadratureFunction* qf, - const std::string& buffer_name, - IndexType offset) +void MFEMSidreDataCollection::addVectorBasedQuadratureFunction(const std::string& field_name, + mfem::QuadratureFunction* qf, + const std::string& buffer_name, + IndexType offset) { /* * Mesh blueprint for a vector-based quadrature function is of the form diff --git a/src/axom/sidre/tests/sidre_mfem_datacollection.cpp b/src/axom/sidre/tests/sidre_mfem_datacollection.cpp index 8b6804aeb6..d82bef19ff 100644 --- a/src/axom/sidre/tests/sidre_mfem_datacollection.cpp +++ b/src/axom/sidre/tests/sidre_mfem_datacollection.cpp @@ -105,10 +105,8 @@ void checkQuadratureFunctionMcarrayData(axom::sidre::Group* bp_grp, ASSERT_NE(component_view, nullptr); const double* component_data = component_view->getData(); ASSERT_NE(component_data, nullptr); - const auto stride = - static_cast(component_view->getStride()); - EXPECT_DOUBLE_EQ(component_data[(offset + qpt) * stride], - quadratureValueFor(el, qpt, comp)); + const auto stride = static_cast(component_view->getStride()); + EXPECT_DOUBLE_EQ(component_data[(offset + qpt) * stride], quadratureValueFor(el, qpt, comp)); } } } @@ -539,10 +537,7 @@ TEST(sidre_datacollection, dc_reload_qf) EXPECT_FALSE(reader_bp_grp->hasGroup("fields/qs/values")); EXPECT_FALSE(reader_bp_grp->hasView("fields/qv/values")); checkMcarrayFieldValues(reader_bp_grp, "qv", qv_vdim, qspace.GetSize()); - checkQuadratureFunctionMcarrayData(reader_bp_grp, - "qv", - *reader_qv->GetSpace(), - reader_qv->GetVDim()); + checkQuadratureFunctionMcarrayData(reader_bp_grp, "qv", *reader_qv->GetSpace(), reader_qv->GetVDim()); // order_qs should also equal order_qv in this trivial case EXPECT_EQ(reader_qs->GetSpace()->GetOrder(), intOrder); From c9ece910855f61506a7cd3623a7f2fa4fe3505e0 Mon Sep 17 00:00:00 2001 From: Chris White Date: Mon, 29 Jun 2026 15:35:11 -0700 Subject: [PATCH 547/986] fix issue created by last PR related to get<>() in container and proxy --- src/axom/inlet/Container.hpp | 6 ++-- src/axom/inlet/Proxy.hpp | 47 +++++++++++++++++++++++++-- src/axom/inlet/tests/inlet_object.cpp | 45 +++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 6 deletions(-) diff --git a/src/axom/inlet/Container.hpp b/src/axom/inlet/Container.hpp index 4f2936687a..b8a6c6342f 100644 --- a/src/axom/inlet/Container.hpp +++ b/src/axom/inlet/Container.hpp @@ -827,9 +827,9 @@ class Container : public Verifiable ******************************************************************************* */ template - typename std::enable_if::value && !detail::is_inlet_array::value && - !detail::is_inlet_dict::value && !detail::is_std_vector::value && - !detail::is_variant_value::value && !detail::is_std_variant::value, + typename std::enable_if::value && + !detail::is_inlet_array::value && !detail::is_inlet_dict::value && + !detail::is_std_vector::value && !detail::is_variant_value::value, T>::type get(const std::string& name = "") const { diff --git a/src/axom/inlet/Proxy.hpp b/src/axom/inlet/Proxy.hpp index 50e234dad8..1b02022750 100644 --- a/src/axom/inlet/Proxy.hpp +++ b/src/axom/inlet/Proxy.hpp @@ -16,6 +16,7 @@ #define INLET_PROXY_HPP #include +#include #include "axom/inlet/Field.hpp" #include "axom/inlet/Container.hpp" @@ -24,6 +25,22 @@ namespace axom { namespace inlet { + +namespace detail +{ +template +struct has_ProxyFromInlet_specialization : std::false_type +{ }; + +template +struct has_ProxyFromInlet_specialization< + T, + typename std::enable_if< + std::is_same&>()(std::declval()))>::value>::type> + : std::true_type +{ }; +} // namespace detail + /*! ******************************************************************************* * \class Proxy @@ -42,7 +59,7 @@ class Proxy /*! ******************************************************************************* * \brief Constructs a proxy view onto a container - * + * * \param [in] container The container to construct a proxy into ******************************************************************************* */ @@ -51,7 +68,7 @@ class Proxy /*! ******************************************************************************* * \brief Constructs a proxy view onto a field - * + * * \param [in] field The field to construct a proxy into ******************************************************************************* */ @@ -139,6 +156,28 @@ class Proxy */ std::string name() const; + /*! + ******************************************************************************* + * \brief Returns a user-defined type from the proxy + * + * \tparam T The type of the object to retrieve + * \return The retrieved object + * \pre The Proxy must refer to a container object + ******************************************************************************* + */ + template + typename std::enable_if::value && !detail::is_std_function::value && + detail::has_ProxyFromInlet_specialization::value, + T>::type + get() const + { + SLIC_ASSERT_MSG(m_container != nullptr, + "[Inlet] Tried to read a user-defined type from a Proxy " + "containing a single field or function"); + FromInlet from_inlet; + return from_inlet(*this); + } + /*! ******************************************************************************* * \brief Returns a user-defined type from the proxy @@ -149,7 +188,9 @@ class Proxy ******************************************************************************* */ template - typename std::enable_if::value && !detail::is_std_function::value, T>::type + typename std::enable_if::value && !detail::is_std_function::value && + !detail::has_ProxyFromInlet_specialization::value, + T>::type get() const { SLIC_ASSERT_MSG(m_container != nullptr, diff --git a/src/axom/inlet/tests/inlet_object.cpp b/src/axom/inlet/tests/inlet_object.cpp index 2cab056a4f..07793abede 100644 --- a/src/axom/inlet/tests/inlet_object.cpp +++ b/src/axom/inlet/tests/inlet_object.cpp @@ -5,6 +5,7 @@ // SPDX-License-Identifier: (BSD-3-Clause) #include "axom/core/Path.hpp" +#include "axom/fmt.hpp" #include "axom/slic/core/SimpleLogger.hpp" #include "axom/sidre.hpp" @@ -86,6 +87,36 @@ struct FromInlet Box operator()(const axom::inlet::Container& base) { return {base["width"], base["height"]}; } }; +template <> +struct FromInlet +{ + Shape operator()(const axom::inlet::Container& base) { return (*this)(axom::inlet::Proxy(base)); } + + Shape operator()(const axom::inlet::Proxy& base) + { + const std::string kind = base["kind"]; + if(kind == "circle") + { + return Circle {base["radius"]}; + } + if(kind == "box") + { + return Box {base["width"], base["height"]}; + } + + SLIC_ERROR(axom::fmt::format("Unknown shape discriminator '{}'", kind)); + return Box {0.0, 0.0}; + } +}; + +void defineShapeSchema(axom::inlet::Container& shape) +{ + shape.addString("kind").required().validValues({"circle", "box"}); + shape.addDouble("radius").required(false); + shape.addDouble("width").required(false); + shape.addDouble("height").required(false); +} + void defineShapeSchema(axom::inlet::VariantStructCollection& shapes) { shapes.addAlternative("circle", [](axom::inlet::Container& circle) { @@ -238,6 +269,20 @@ TYPED_TEST(inlet_object, variant_array_of_struct_unknown_discriminator_fails) EXPECT_FALSE(inlet.verify()); } +TYPED_TEST(inlet_object, variant_struct_by_value) +{ + std::string testString = "shape = { kind = \"box\"; width = 3.0; height = 4.0 }"; + Inlet inlet = createBasicInlet(testString); + + auto& shape = inlet.addStruct("shape"); + defineShapeSchema(shape); + + EXPECT_TRUE(inlet.verify()); + + EXPECT_EQ(inlet.get("shape"), Shape(Box {3.0, 4.0})); + EXPECT_EQ(inlet["shape"].get(), Shape(Box {3.0, 4.0})); +} + TYPED_TEST(inlet_object, simple_array_of_struct_implicit_idx) { std::string testString = From ad332e2a7a672832b8b05d5e48edd756f5f67fbc Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 29 Jun 2026 18:42:26 -0700 Subject: [PATCH 548/986] First changes to support checksums. --- src/axom/core/utilities/Checksum.hpp | 80 +++++++++++++++++++++++++++ src/axom/sidre/core/ConduitMemory.cpp | 70 +++++++++++++++++++++++ src/axom/sidre/core/ConduitMemory.hpp | 10 ++++ src/axom/sidre/core/Group.cpp | 18 ++++++ src/axom/sidre/core/Group.hpp | 9 +++ src/axom/sidre/core/View.cpp | 15 +++++ src/axom/sidre/core/View.hpp | 8 +++ 7 files changed, 210 insertions(+) create mode 100644 src/axom/core/utilities/Checksum.hpp diff --git a/src/axom/core/utilities/Checksum.hpp b/src/axom/core/utilities/Checksum.hpp new file mode 100644 index 0000000000..dec5df6735 --- /dev/null +++ b/src/axom/core/utilities/Checksum.hpp @@ -0,0 +1,80 @@ +// 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_UTILITIES_CHECKSUM_HPP_ +#define AXOM_UTILITIES_CHECKSUM_HPP_ + +#include "axom/config.hpp" // for compile-time definitions +#include "axom/core/ArrayView.hpp" + +namespace axom +{ +namespace utilities +{ +// Checksum types +using CheckSum = long double; +using ScaleFactor = double; + +namespace detail +{ +/*! + * \brief Calculate and return checksum for data arrays. + * + * \param view The view that contains the data. + * + * \note Adapted from RAJAPerf at https://github.com/LLNL/RAJAPerf/blob/cda42470851fff2b7c8e6a9b5b11ab83f33a5a07/src/common/DataUtils.cpp#L598-L622 + * + * \return A CheckSum value for the array view. + */ +template +inline CheckSum calculateChecksum(axom::ArrayView view) +{ + CheckSum tchk = 0.0; + CheckSum ckahan = 0.0; + const auto len = view.size(); + for (axom::IndexType j = 0; j < len; ++j) { + const auto value = static_cast(view[j]); + CheckSum x = (std::abs(std::sin(j+1.0))+0.5) * value; + CheckSum y = x - ckahan; + volatile CheckSum t = tchk + y; + volatile CheckSum z = t - tchk; + ckahan = z - y; + tchk = t; + } + return tchk; +} +} // namespace detail + +/*! + * \brief Calculate and return checksum. + * + * \param value The value we want to checksum. + * + * \return A CheckSum value for the array view. + */ +template +inline CheckSum checksum(T value, const ScaleFactor scaleFactor = 1.) +{ + ArrayView view(&value, 1); + return detail::calculateChecksum(view) * scaleFactor; +} + +/*! + * \brief Calculate and return checksum for an array view. + * + * \param view The view that contains the data we want to checksum. + * + * \return A CheckSum value for the array view. + */ +template +inline CheckSum checksum(axom::ArrayView view, const ScaleFactor scaleFactor = 1.) +{ + return detail::calculateChecksum(view) * scaleFactor; +} + +} // namespace utilities +} // namespace axom + +#endif // AXOM_UTILITIES_CHECKSUM_HPP_ diff --git a/src/axom/sidre/core/ConduitMemory.cpp b/src/axom/sidre/core/ConduitMemory.cpp index 141bcbb9ff..3bc73b056b 100644 --- a/src/axom/sidre/core/ConduitMemory.cpp +++ b/src/axom/sidre/core/ConduitMemory.cpp @@ -285,5 +285,75 @@ const ConduitMemory& ConduitMemory::instanceForConduitId(conduit::index_t condui return *it->second; } +axom::utilities::CheckSum checksum(const conduit::Node &n) +{ + std::string name(n.name()); + axom::ArrayView view(name.data(), name.size()); + auto cs = axom::utilities::checksum(view); + + if(n.number_of_children() > 0) + { + for(conduit::index_t i = 0; i < n.number_of_children(); i++) + { + cs += checksum(n[i]); + } + } + else + { + // NOTE: this assumes contiguous data + if(n.dtype().is_string() || n.dtype().is_int8()) + { + axom::ArrayView view(static_cast(n.data_ptr()), n.dtype().number_of_elements()); + return cs += axom::utilities::checksum(view); + } + else if(n.dtype().is_int16()) + { + axom::ArrayView view(static_cast(n.data_ptr()), n.dtype().number_of_elements()); + return cs += axom::utilities::checksum(view); + } + else if(n.dtype().is_int32()) + { + axom::ArrayView view(static_cast(n.data_ptr()), n.dtype().number_of_elements()); + return cs += axom::utilities::checksum(view); + } + else if(n.dtype().is_int64()) + { + axom::ArrayView view(static_cast(n.data_ptr()), n.dtype().number_of_elements()); + return cs += axom::utilities::checksum(view); + } + else if(n.dtype().is_uint8()) + { + axom::ArrayView view(static_cast(n.data_ptr()), n.dtype().number_of_elements()); + return cs += axom::utilities::checksum(view); + } + else if(n.dtype().is_uint16()) + { + axom::ArrayView view(static_cast(n.data_ptr()), n.dtype().number_of_elements()); + return cs += axom::utilities::checksum(view); + } + else if(n.dtype().is_uint32()) + { + axom::ArrayView view(static_cast(n.data_ptr()), n.dtype().number_of_elements()); + return cs += axom::utilities::checksum(view); + } + else if(n.dtype().is_uint64()) + { + axom::ArrayView view(static_cast(n.data_ptr()), n.dtype().number_of_elements()); + return cs += axom::utilities::checksum(view); + } + else if(n.dtype().is_float32()) + { + axom::ArrayView view(static_cast(n.data_ptr()), n.dtype().number_of_elements()); + return cs += axom::utilities::checksum(view); + } + else if(n.dtype().is_float64()) + { + axom::ArrayView view(static_cast(n.data_ptr()), n.dtype().number_of_elements()); + return cs += axom::utilities::checksum(view); + } + } + return cs; +} + } // end namespace sidre } // end namespace axom diff --git a/src/axom/sidre/core/ConduitMemory.hpp b/src/axom/sidre/core/ConduitMemory.hpp index 9dde67378b..bcfed92a10 100644 --- a/src/axom/sidre/core/ConduitMemory.hpp +++ b/src/axom/sidre/core/ConduitMemory.hpp @@ -25,6 +25,7 @@ #include "axom/config.hpp" #include "axom/core/memory_management.hpp" #include "axom/core/utilities/Utilities.hpp" +#include "axom/core/utilities/Checksum.hpp" #include "conduit_node.hpp" #include "conduit_utils.hpp" @@ -161,6 +162,15 @@ struct ConduitMemory void privateRegisterAllocator(); }; +/*! + * \brief Checksum the structure and contents of a Conduit node. + * + * \param n The node being checksummed. + * + * \return A checksum of the node. + */ +axom::utilities::CheckSum checksum(const conduit::Node &n); + } /* end namespace sidre */ } /* end namespace axom */ diff --git a/src/axom/sidre/core/Group.cpp b/src/axom/sidre/core/Group.cpp index 1805561ad8..c84d5d07dc 100644 --- a/src/axom/sidre/core/Group.cpp +++ b/src/axom/sidre/core/Group.cpp @@ -2879,6 +2879,24 @@ bool Group::importConduitTreeExternal(conduit::Node& node, bool preserve_content return success; } +axom::utilities::CheckSum Group::checksum() const +{ + // Checksum the name + axom::ArrayView nameView(m_name.data(), m_name.size()); + auto cs = axom::utilities::checksum(nameView); + + // Add the checksums of the views and groups. + for(const auto& view : this->views()) + { + cs += view.checksum(); + } + for(const auto& group : this->groups()) + { + cs += group.checksum(); + } + return cs; +} + /* ************************************************************************* * diff --git a/src/axom/sidre/core/Group.hpp b/src/axom/sidre/core/Group.hpp index 038d284d1b..21c2599691 100644 --- a/src/axom/sidre/core/Group.hpp +++ b/src/axom/sidre/core/Group.hpp @@ -24,6 +24,7 @@ #include "axom/core/Macros.hpp" #include "axom/core/MapCollection.hpp" #include "axom/core/Types.hpp" +#include "axom/core/utilities/Checksum.hpp" #include "axom/slic.hpp" #include "axom/export/sidre.h" @@ -1848,6 +1849,14 @@ class Group */ bool importConduitTreeExternal(conduit::Node& node, bool preserve_contents = false); + /*! + * \brief Traverse the group and all of its descendents and compute a checksum + * of the structure as well as the contents of the views. + * + * \return A CheckSum of the group. + */ + axom::utilities::CheckSum checksum() const; + private: DISABLE_DEFAULT_CTOR(Group); DISABLE_COPY_AND_ASSIGNMENT(Group); diff --git a/src/axom/sidre/core/View.cpp b/src/axom/sidre/core/View.cpp index 4911ac1c37..2a490d0589 100644 --- a/src/axom/sidre/core/View.cpp +++ b/src/axom/sidre/core/View.cpp @@ -16,6 +16,7 @@ #include "axom/core/execution/execution_space.hpp" #include "axom/core/Macros.hpp" +#include "axom/core/utilities/Checksum.hpp" #include "axom/sidre/core/ConduitMemory.hpp" namespace axom @@ -2070,5 +2071,19 @@ int View::getValidAllocatorId(int allocId) return axom::INVALID_ALLOCATOR_ID; } +axom::utilities::CheckSum View::checksum() const +{ + // Checksum the name + axom::ArrayView nameView(m_name.data(), m_name.size()); + auto cs = axom::utilities::checksum(nameView); + + // Checksum the data in the view's node (including any attributes, etc) + conduit::Node tmp; + createNativeLayout(tmp); + cs += axom::sidre::checksum(tmp); + + return cs; +} + } /* end namespace sidre */ } /* end namespace axom */ diff --git a/src/axom/sidre/core/View.hpp b/src/axom/sidre/core/View.hpp index 1e1893b060..11e961c6d3 100644 --- a/src/axom/sidre/core/View.hpp +++ b/src/axom/sidre/core/View.hpp @@ -28,6 +28,7 @@ #include "axom/core/Array.hpp" #include "axom/core/Macros.hpp" #include "axom/core/Types.hpp" +#include "axom/core/utilities/Checksum.hpp" #include "axom/slic.hpp" // Sidre headers @@ -1366,6 +1367,13 @@ class View ///@} + /*! + * \brief Compute a checksum for the view. + * + * \return A CheckSum of the view. + */ + axom::utilities::CheckSum checksum() const; + private: DISABLE_DEFAULT_CTOR(View); DISABLE_MOVE_AND_ASSIGNMENT(View); From d5ebb9f07918fb14bc7ccd6638ac3e901a060128 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 30 Jun 2026 12:39:06 -0700 Subject: [PATCH 549/986] Change to conduit::DataArray in conduit checksum --- src/axom/core/utilities/Checksum.hpp | 15 +++------ src/axom/sidre/core/ConduitMemory.cpp | 46 +++++++++++++++------------ 2 files changed, 31 insertions(+), 30 deletions(-) diff --git a/src/axom/core/utilities/Checksum.hpp b/src/axom/core/utilities/Checksum.hpp index dec5df6735..3374e9effe 100644 --- a/src/axom/core/utilities/Checksum.hpp +++ b/src/axom/core/utilities/Checksum.hpp @@ -17,8 +17,6 @@ namespace utilities using CheckSum = long double; using ScaleFactor = double; -namespace detail -{ /*! * \brief Calculate and return checksum for data arrays. * @@ -28,14 +26,13 @@ namespace detail * * \return A CheckSum value for the array view. */ -template -inline CheckSum calculateChecksum(axom::ArrayView view) +template +inline CheckSum calculateChecksum(DataGetter data, axom::IndexType len) { CheckSum tchk = 0.0; CheckSum ckahan = 0.0; - const auto len = view.size(); for (axom::IndexType j = 0; j < len; ++j) { - const auto value = static_cast(view[j]); + const auto value = data(j); CheckSum x = (std::abs(std::sin(j+1.0))+0.5) * value; CheckSum y = x - ckahan; volatile CheckSum t = tchk + y; @@ -45,7 +42,6 @@ inline CheckSum calculateChecksum(axom::ArrayView view) } return tchk; } -} // namespace detail /*! * \brief Calculate and return checksum. @@ -57,8 +53,7 @@ inline CheckSum calculateChecksum(axom::ArrayView view) template inline CheckSum checksum(T value, const ScaleFactor scaleFactor = 1.) { - ArrayView view(&value, 1); - return detail::calculateChecksum(view) * scaleFactor; + return calculateChecksum([=](axom::IndexType) { return static_cast(value); }, 1) * scaleFactor; } /*! @@ -71,7 +66,7 @@ inline CheckSum checksum(T value, const ScaleFactor scaleFactor = 1.) template inline CheckSum checksum(axom::ArrayView view, const ScaleFactor scaleFactor = 1.) { - return detail::calculateChecksum(view) * scaleFactor; + return calculateChecksum([=](axom::IndexType i) { return static_cast(view[i]); }, view.size()) * scaleFactor; } } // namespace utilities diff --git a/src/axom/sidre/core/ConduitMemory.cpp b/src/axom/sidre/core/ConduitMemory.cpp index 3bc73b056b..a7d8cfd1d3 100644 --- a/src/axom/sidre/core/ConduitMemory.cpp +++ b/src/axom/sidre/core/ConduitMemory.cpp @@ -285,6 +285,13 @@ const ConduitMemory& ConduitMemory::instanceForConduitId(conduit::index_t condui return *it->second; } +/// Operate on conduit::DataArray so we can handle strided data. +template +axom::utilities::CheckSum checksumArray(const conduit::DataArray &arr, const axom::utilities::ScaleFactor scaleFactor = 1.) +{ + return axom::utilities::calculateChecksum([=](axom::IndexType i) { return static_cast(arr[i]); }, arr.number_of_elements()) * scaleFactor; +} + axom::utilities::CheckSum checksum(const conduit::Node &n) { std::string name(n.name()); @@ -301,55 +308,54 @@ axom::utilities::CheckSum checksum(const conduit::Node &n) else { // NOTE: this assumes contiguous data - if(n.dtype().is_string() || n.dtype().is_int8()) + if(n.dtype().is_string()) { - axom::ArrayView view(static_cast(n.data_ptr()), n.dtype().number_of_elements()); + axom::ArrayView view(static_cast(n.data_ptr()), n.dtype().number_of_elements()); return cs += axom::utilities::checksum(view); } + else if(n.dtype().is_int8()) + { + return cs += checksumArray(n.as_int8_array()); + } else if(n.dtype().is_int16()) { - axom::ArrayView view(static_cast(n.data_ptr()), n.dtype().number_of_elements()); - return cs += axom::utilities::checksum(view); + return cs += checksumArray(n.as_int16_array()); } else if(n.dtype().is_int32()) { - axom::ArrayView view(static_cast(n.data_ptr()), n.dtype().number_of_elements()); - return cs += axom::utilities::checksum(view); + return cs += checksumArray(n.as_int32_array()); } else if(n.dtype().is_int64()) { - axom::ArrayView view(static_cast(n.data_ptr()), n.dtype().number_of_elements()); - return cs += axom::utilities::checksum(view); + return cs += checksumArray(n.as_int64_array()); } else if(n.dtype().is_uint8()) { - axom::ArrayView view(static_cast(n.data_ptr()), n.dtype().number_of_elements()); - return cs += axom::utilities::checksum(view); + return cs += checksumArray(n.as_uint8_array()); } else if(n.dtype().is_uint16()) { - axom::ArrayView view(static_cast(n.data_ptr()), n.dtype().number_of_elements()); - return cs += axom::utilities::checksum(view); + return cs += checksumArray(n.as_uint16_array()); } else if(n.dtype().is_uint32()) { - axom::ArrayView view(static_cast(n.data_ptr()), n.dtype().number_of_elements()); - return cs += axom::utilities::checksum(view); + return cs += checksumArray(n.as_uint32_array()); } else if(n.dtype().is_uint64()) { - axom::ArrayView view(static_cast(n.data_ptr()), n.dtype().number_of_elements()); - return cs += axom::utilities::checksum(view); + return cs += checksumArray(n.as_uint64_array()); + } + else if(n.dtype().is_index_t()) + { + return cs += checksumArray(n.as_index_t_array()); } else if(n.dtype().is_float32()) { - axom::ArrayView view(static_cast(n.data_ptr()), n.dtype().number_of_elements()); - return cs += axom::utilities::checksum(view); + return cs += checksumArray(n.as_float32_array()); } else if(n.dtype().is_float64()) { - axom::ArrayView view(static_cast(n.data_ptr()), n.dtype().number_of_elements()); - return cs += axom::utilities::checksum(view); + return cs += checksumArray(n.as_float64_array()); } } return cs; From 5755dbfbedbbf2d068c98c2133742d5c1261a72c Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 30 Jun 2026 13:11:21 -0700 Subject: [PATCH 550/986] Adjusted sidre::View::checksum implementation. Added some tests. --- src/axom/core/tests/CMakeLists.txt | 1 + src/axom/core/tests/core_checksum.hpp | 45 +++++++ src/axom/core/tests/core_serial_main.cpp | 1 + src/axom/core/utilities/Checksum.hpp | 6 +- src/axom/sidre/core/ConduitMemory.cpp | 16 ++- src/axom/sidre/core/ConduitMemory.hpp | 5 +- src/axom/sidre/core/View.cpp | 6 +- src/axom/sidre/tests/CMakeLists.txt | 1 + src/axom/sidre/tests/sidre_checksum.cpp | 164 +++++++++++++++++++++++ 9 files changed, 232 insertions(+), 13 deletions(-) create mode 100644 src/axom/core/tests/core_checksum.hpp create mode 100644 src/axom/sidre/tests/sidre_checksum.cpp diff --git a/src/axom/core/tests/CMakeLists.txt b/src/axom/core/tests/CMakeLists.txt index ba627f713e..9d4e3bea7a 100644 --- a/src/axom/core/tests/CMakeLists.txt +++ b/src/axom/core/tests/CMakeLists.txt @@ -22,6 +22,7 @@ set(core_serial_tests core_array_for_all.hpp core_utilities.hpp core_bit_utilities.hpp + core_checksum.hpp core_device_hash.hpp core_execution_for_all.hpp core_execution_scans.hpp diff --git a/src/axom/core/tests/core_checksum.hpp b/src/axom/core/tests/core_checksum.hpp new file mode 100644 index 0000000000..1f7fbf8295 --- /dev/null +++ b/src/axom/core/tests/core_checksum.hpp @@ -0,0 +1,45 @@ +// 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/ArrayView.hpp" +#include "axom/core/utilities/Checksum.hpp" + +TEST(core_checksum, scalar_matches_singleton_view_and_scale_factor) +{ + const double value = 3.25; + axom::ArrayView singletonView(&value, 1); + + const auto scalarChecksum = axom::utilities::checksum(value); + + EXPECT_EQ(scalarChecksum, axom::utilities::checksum(singletonView)); + EXPECT_EQ(scalarChecksum * 2.5L, axom::utilities::checksum(value, 2.5)); +} + +TEST(core_checksum, empty_view_is_zero) +{ + axom::ArrayView emptyView(nullptr, 0); + + EXPECT_EQ(0.0L, axom::utilities::checksum(emptyView)); +} + +TEST(core_checksum, array_order_and_values_change_checksum) +{ + const double ordered[] = {1.0, 2.0, 3.0, 4.0}; + const double reordered[] = {4.0, 3.0, 2.0, 1.0}; + const double modified[] = {1.0, 2.0, 3.0, 5.0}; + + const auto orderedChecksum = + axom::utilities::checksum(axom::ArrayView(ordered, 4)); + + EXPECT_NE( + orderedChecksum, + axom::utilities::checksum(axom::ArrayView(reordered, 4))); + EXPECT_NE( + orderedChecksum, + axom::utilities::checksum(axom::ArrayView(modified, 4))); +} diff --git a/src/axom/core/tests/core_serial_main.cpp b/src/axom/core/tests/core_serial_main.cpp index 940f036140..e0419fd544 100644 --- a/src/axom/core/tests/core_serial_main.cpp +++ b/src/axom/core/tests/core_serial_main.cpp @@ -14,6 +14,7 @@ #include "core_array_mapping.hpp" #include "core_utilities.hpp" #include "core_bit_utilities.hpp" +#include "core_checksum.hpp" #include "core_device_hash.hpp" #include "core_execution_for_all.hpp" #include "core_execution_scans.hpp" diff --git a/src/axom/core/utilities/Checksum.hpp b/src/axom/core/utilities/Checksum.hpp index 3374e9effe..a5335caeb9 100644 --- a/src/axom/core/utilities/Checksum.hpp +++ b/src/axom/core/utilities/Checksum.hpp @@ -6,6 +6,8 @@ #ifndef AXOM_UTILITIES_CHECKSUM_HPP_ #define AXOM_UTILITIES_CHECKSUM_HPP_ +#include + #include "axom/config.hpp" // for compile-time definitions #include "axom/core/ArrayView.hpp" @@ -51,7 +53,7 @@ inline CheckSum calculateChecksum(DataGetter data, axom::IndexType len) * \return A CheckSum value for the array view. */ template -inline CheckSum checksum(T value, const ScaleFactor scaleFactor = 1.) +inline CheckSum checksum(T value, const ScaleFactor scaleFactor = ScaleFactor{1}) { return calculateChecksum([=](axom::IndexType) { return static_cast(value); }, 1) * scaleFactor; } @@ -64,7 +66,7 @@ inline CheckSum checksum(T value, const ScaleFactor scaleFactor = 1.) * \return A CheckSum value for the array view. */ template -inline CheckSum checksum(axom::ArrayView view, const ScaleFactor scaleFactor = 1.) +inline CheckSum checksum(axom::ArrayView view, const ScaleFactor scaleFactor = ScaleFactor{1}) { return calculateChecksum([=](axom::IndexType i) { return static_cast(view[i]); }, view.size()) * scaleFactor; } diff --git a/src/axom/sidre/core/ConduitMemory.cpp b/src/axom/sidre/core/ConduitMemory.cpp index a7d8cfd1d3..693f4197d1 100644 --- a/src/axom/sidre/core/ConduitMemory.cpp +++ b/src/axom/sidre/core/ConduitMemory.cpp @@ -287,22 +287,26 @@ const ConduitMemory& ConduitMemory::instanceForConduitId(conduit::index_t condui /// Operate on conduit::DataArray so we can handle strided data. template -axom::utilities::CheckSum checksumArray(const conduit::DataArray &arr, const axom::utilities::ScaleFactor scaleFactor = 1.) +axom::utilities::CheckSum checksumArray(const conduit::DataArray &arr, const axom::utilities::ScaleFactor scaleFactor = axom::utilities::ScaleFactor{1}) { return axom::utilities::calculateChecksum([=](axom::IndexType i) { return static_cast(arr[i]); }, arr.number_of_elements()) * scaleFactor; } -axom::utilities::CheckSum checksum(const conduit::Node &n) +axom::utilities::CheckSum checksum(const conduit::Node& n, bool include_name) { - std::string name(n.name()); - axom::ArrayView view(name.data(), name.size()); - auto cs = axom::utilities::checksum(view); + auto cs = axom::utilities::CheckSum {0}; + if(include_name) + { + std::string name(n.name()); + axom::ArrayView view(name.data(), name.size()); + cs = axom::utilities::checksum(view); + } if(n.number_of_children() > 0) { for(conduit::index_t i = 0; i < n.number_of_children(); i++) { - cs += checksum(n[i]); + cs += checksum(n[i], true); } } else diff --git a/src/axom/sidre/core/ConduitMemory.hpp b/src/axom/sidre/core/ConduitMemory.hpp index bcfed92a10..30237eca8f 100644 --- a/src/axom/sidre/core/ConduitMemory.hpp +++ b/src/axom/sidre/core/ConduitMemory.hpp @@ -166,10 +166,13 @@ struct ConduitMemory * \brief Checksum the structure and contents of a Conduit node. * * \param n The node being checksummed. + * \param include_name If true, include this node's own name in the checksum. + * Child node names are always included during recursive traversal. * * \return A checksum of the node. */ -axom::utilities::CheckSum checksum(const conduit::Node &n); +axom::utilities::CheckSum checksum(const conduit::Node& n, + bool include_name = true); } /* end namespace sidre */ } /* end namespace axom */ diff --git a/src/axom/sidre/core/View.cpp b/src/axom/sidre/core/View.cpp index 2a490d0589..306daec6f8 100644 --- a/src/axom/sidre/core/View.cpp +++ b/src/axom/sidre/core/View.cpp @@ -2077,10 +2077,8 @@ axom::utilities::CheckSum View::checksum() const axom::ArrayView nameView(m_name.data(), m_name.size()); auto cs = axom::utilities::checksum(nameView); - // Checksum the data in the view's node (including any attributes, etc) - conduit::Node tmp; - createNativeLayout(tmp); - cs += axom::sidre::checksum(tmp); + // Checksum the view contents without double-counting the view name. + cs += axom::sidre::checksum(m_node, false); return cs; } diff --git a/src/axom/sidre/tests/CMakeLists.txt b/src/axom/sidre/tests/CMakeLists.txt index ebd5af7ce2..8b34e2648b 100644 --- a/src/axom/sidre/tests/CMakeLists.txt +++ b/src/axom/sidre/tests/CMakeLists.txt @@ -21,6 +21,7 @@ set(gtest_sidre_tests sidre_external.cpp sidre_group.cpp sidre_opaque.cpp + sidre_checksum.cpp sidre_view.cpp sidre_native_layout.cpp sidre_attribute.cpp diff --git a/src/axom/sidre/tests/sidre_checksum.cpp b/src/axom/sidre/tests/sidre_checksum.cpp new file mode 100644 index 0000000000..9cdd2c5b56 --- /dev/null +++ b/src/axom/sidre/tests/sidre_checksum.cpp @@ -0,0 +1,164 @@ +// 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/sidre.hpp" +#include "axom/sidre/core/ConduitMemory.hpp" + +#include "conduit_node.hpp" + +#include + +namespace +{ +void populateConduitNode(conduit::Node& node, + const std::string& nestedName = "nested", + const char* message = "alpha", + int count = 42, + double arrayTail = 3.75, + bool addExtraChild = false) +{ + const double values[] = {1.5, -2.25, arrayTail}; + + node["message"].set(message); + node["count"].set(count); + node["values"].set(values, 3); + node[nestedName + "/pi"].set(3.14159); + + if(addExtraChild) + { + node["extra"].set(-7); + } +} +} // namespace + +TEST(sidre_checksum, conduit_checksum_handles_strided_numeric_arrays) +{ + double contiguousValues[] = {1.0, 2.0, 3.0}; + double interleavedValues[] = {1.0, 99.0, 2.0, 99.0, 3.0, 99.0}; + + conduit::Node contiguous; + contiguous.set_external(contiguousValues, 3); + + conduit::Node strided; + strided.set_external( + conduit::DataType::float64(3, 0, 2 * sizeof(double)), + interleavedValues); + + EXPECT_EQ(axom::sidre::checksum(contiguous), axom::sidre::checksum(strided)); +} + +TEST(sidre_checksum, conduit_checksum_changes_for_tree_structure_and_leaf_data) +{ + conduit::Node baseline; + populateConduitNode(baseline); + const auto checksum = axom::sidre::checksum(baseline); + + conduit::Node stringChanged; + populateConduitNode(stringChanged, "nested", "beta"); + EXPECT_NE(checksum, axom::sidre::checksum(stringChanged)); + + conduit::Node scalarChanged; + populateConduitNode(scalarChanged, "nested", "alpha", 7); + EXPECT_NE(checksum, axom::sidre::checksum(scalarChanged)); + + conduit::Node arrayChanged; + populateConduitNode(arrayChanged, "nested", "alpha", 42, 9.25); + EXPECT_NE(checksum, axom::sidre::checksum(arrayChanged)); + + conduit::Node renamedChild; + populateConduitNode(renamedChild, "renamed_nested"); + EXPECT_NE(checksum, axom::sidre::checksum(renamedChild)); + + conduit::Node addedChild; + populateConduitNode(addedChild, "nested", "alpha", 42, 3.75, true); + EXPECT_NE(checksum, axom::sidre::checksum(addedChild)); +} + +TEST(sidre_checksum, view_checksum_changes_on_rename_and_data_mutation) +{ + axom::sidre::DataStore datastore; + axom::sidre::Group* root = datastore.getRoot(); + axom::sidre::View* view = + root->createViewAndAllocate("values", axom::sidre::INT_ID, 4); + + int* data = view->getData(); + data[0] = 2; + data[1] = 4; + data[2] = 6; + data[3] = 8; + + const auto originalChecksum = view->checksum(); + + data[2] += 5; + const auto dataChecksum = view->checksum(); + EXPECT_NE(originalChecksum, dataChecksum); + + ASSERT_TRUE(view->rename("renamed_values")); + EXPECT_NE(dataChecksum, view->checksum()); +} + +TEST(sidre_checksum, view_checksum_matches_unnamed_native_layout_path) +{ + axom::sidre::DataStore datastore; + axom::sidre::View* view = + datastore.getRoot()->createViewAndAllocate("values", axom::sidre::INT_ID, 3); + + int* data = view->getData(); + data[0] = 5; + data[1] = 8; + data[2] = 13; + + conduit::Node nativeLayout; + view->createNativeLayout(nativeLayout); + + axom::ArrayView nameView(view->getName().data(), view->getName().size()); + const auto oldPathChecksum = + axom::utilities::checksum(nameView) + axom::sidre::checksum(nativeLayout); + + EXPECT_EQ(oldPathChecksum, view->checksum()); +} + +TEST(sidre_checksum, group_checksum_changes_on_add_remove_rename_and_descendant_data) +{ + axom::sidre::DataStore datastore; + axom::sidre::Group* group = datastore.getRoot()->createGroup("fields"); + + const auto emptyChecksum = group->checksum(); + + group->createViewScalar("flag", 7); + const auto viewAddedChecksum = group->checksum(); + EXPECT_NE(emptyChecksum, viewAddedChecksum); + + group->destroyView("flag"); + EXPECT_EQ(emptyChecksum, group->checksum()); + + axom::sidre::Group* child = group->createGroup("child"); + const auto childAddedChecksum = group->checksum(); + EXPECT_NE(emptyChecksum, childAddedChecksum); + + ASSERT_TRUE(child->rename("child_renamed")); + const auto childRenamedChecksum = group->checksum(); + EXPECT_NE(childAddedChecksum, childRenamedChecksum); + + axom::sidre::View* values = + child->createViewAndAllocate("values", axom::sidre::INT_ID, 4); + int* data = values->getData(); + data[0] = 1; + data[1] = 3; + data[2] = 5; + data[3] = 7; + + const auto beforeMutationChecksum = group->checksum(); + data[1] += 10; + const auto afterMutationChecksum = group->checksum(); + EXPECT_NE(beforeMutationChecksum, afterMutationChecksum); + + group->destroyGroupAndData("child_renamed"); + EXPECT_NE(afterMutationChecksum, group->checksum()); + EXPECT_EQ(emptyChecksum, group->checksum()); +} From 9f339e767548e758f1a6a458cb8b79474e15b5c0 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 30 Jun 2026 15:03:21 -0700 Subject: [PATCH 551/986] Allow empty object/list for Conduit checksum. --- src/axom/sidre/core/ConduitMemory.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/axom/sidre/core/ConduitMemory.cpp b/src/axom/sidre/core/ConduitMemory.cpp index 693f4197d1..7d02647edb 100644 --- a/src/axom/sidre/core/ConduitMemory.cpp +++ b/src/axom/sidre/core/ConduitMemory.cpp @@ -5,7 +5,6 @@ // SPDX-License-Identifier: (BSD-3-Clause) #include "axom/sidre/core/ConduitMemory.hpp" - namespace axom { namespace sidre @@ -361,6 +360,10 @@ axom::utilities::CheckSum checksum(const conduit::Node& n, bool include_name) { return cs += checksumArray(n.as_float64_array()); } + else if(n.dtype().is_empty() || n.dtype().is_object() || n.dtype().is_list()) + { + return cs; + } } return cs; } From e70f89a2697ef9f6c8951ef1733168ada4a0a0ce Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 30 Jun 2026 15:03:43 -0700 Subject: [PATCH 552/986] Enhance tests. --- src/axom/sidre/tests/sidre_checksum.cpp | 72 +++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/src/axom/sidre/tests/sidre_checksum.cpp b/src/axom/sidre/tests/sidre_checksum.cpp index 9cdd2c5b56..dee8407116 100644 --- a/src/axom/sidre/tests/sidre_checksum.cpp +++ b/src/axom/sidre/tests/sidre_checksum.cpp @@ -52,6 +52,28 @@ TEST(sidre_checksum, conduit_checksum_handles_strided_numeric_arrays) EXPECT_EQ(axom::sidre::checksum(contiguous), axom::sidre::checksum(strided)); } +TEST(sidre_checksum, conduit_checksum_handles_strided_numeric_array_trees) +{ + int contiguousIds[] = {10, 20, 30}; + int interleavedIds[] = {10, -1, 20, -1, 30, -1}; + double contiguousValues[] = {1.5, 2.5, 3.5}; + double interleavedValues[] = {1.5, -1.0, 2.5, -1.0, 3.5, -1.0}; + + conduit::Node contiguous; + contiguous["fields/ids"].set_external(contiguousIds, 3); + contiguous["fields/values"].set_external(contiguousValues, 3); + + conduit::Node strided; + strided["fields/ids"].set_external( + conduit::DataType::c_int(3, 0, 2 * sizeof(int)), + interleavedIds); + strided["fields/values"].set_external( + conduit::DataType::float64(3, 0, 2 * sizeof(double)), + interleavedValues); + + EXPECT_EQ(axom::sidre::checksum(contiguous), axom::sidre::checksum(strided)); +} + TEST(sidre_checksum, conduit_checksum_changes_for_tree_structure_and_leaf_data) { conduit::Node baseline; @@ -79,6 +101,56 @@ TEST(sidre_checksum, conduit_checksum_changes_for_tree_structure_and_leaf_data) EXPECT_NE(checksum, axom::sidre::checksum(addedChild)); } +TEST(sidre_checksum, conduit_checksum_supports_empty_object_and_list_nodes) +{ + conduit::Node emptyObject; + emptyObject.set(conduit::DataType::object()); + + conduit::Node emptyList; + emptyList.set(conduit::DataType::list()); + + EXPECT_EQ(0.0L, axom::sidre::checksum(emptyObject, false)); + EXPECT_EQ(0.0L, axom::sidre::checksum(emptyList, false)); + EXPECT_EQ(axom::sidre::checksum(emptyObject, false), + axom::sidre::checksum(emptyList, false)); + + conduit::Node namedEmptyObject; + namedEmptyObject["empty_object"].set(conduit::DataType::object()); + + conduit::Node namedEmptyList; + namedEmptyList["empty_list"].set(conduit::DataType::list()); + + EXPECT_NE(axom::sidre::checksum(namedEmptyObject["empty_object"]), + axom::sidre::checksum(namedEmptyList["empty_list"])); +} + +TEST(sidre_checksum, sidre_view_and_group_checksum_support_strided_external_data) +{ + axom::sidre::DataStore datastore; + axom::sidre::Group* group = datastore.getRoot()->createGroup("fields"); + + int interleavedData[] = {11, -1, 22, -1, 33, -1}; + int contiguousData[] = {11, 22, 33}; + + axom::sidre::View* stridedView = group->createView("strided"); + stridedView->setExternalDataPtr(interleavedData); + stridedView->apply(conduit::DataType::c_int(3, 0, 2 * sizeof(int))); + + conduit::Node contiguousNode; + contiguousNode.set_external(contiguousData, 3); + + axom::ArrayView nameView(stridedView->getName().data(), + stridedView->getName().size()); + const auto expectedViewChecksum = + axom::utilities::checksum(nameView) + axom::sidre::checksum(contiguousNode); + + EXPECT_EQ(expectedViewChecksum, stridedView->checksum()); + + const auto groupChecksumBefore = group->checksum(); + interleavedData[2] += 7; + EXPECT_NE(groupChecksumBefore, group->checksum()); +} + TEST(sidre_checksum, view_checksum_changes_on_rename_and_data_mutation) { axom::sidre::DataStore datastore; From 7a424abdd9bbf69e24b790f17897017026955b0e Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 30 Jun 2026 15:06:11 -0700 Subject: [PATCH 553/986] Added release notes. --- RELEASE-NOTES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index f4790caab6..2d0481a94d 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -44,6 +44,7 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ - Primal: Adds a `primal::BezierTriangle` class - Inlet: Added the ability to have collections (array and dictionary) with variant values. - Inlet: Added the ability to have collections (array and dictionary) with variant user defined structures. +- Core/Sidre: Added `axom::sidre::Group::checksum()` and `axom::sidre::View::checksum()` methods. ### Removed - Bump: Removed `axom::bump::views::MultiBufferMaterialView`, which was a view type for an obsolete flavor of Blueprint matset. From dbdb0c3a27835e8d457e635f34e0ce873adf7fb7 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 30 Jun 2026 16:00:56 -0700 Subject: [PATCH 554/986] Include view attributes in checksum, improve tests --- src/axom/core/tests/core_checksum.hpp | 16 ++++ src/axom/sidre/core/ConduitMemory.cpp | 29 ++++++- src/axom/sidre/core/View.cpp | 22 ++++++ src/axom/sidre/tests/sidre_checksum.cpp | 100 +++++++++++++++++++++++- 4 files changed, 160 insertions(+), 7 deletions(-) diff --git a/src/axom/core/tests/core_checksum.hpp b/src/axom/core/tests/core_checksum.hpp index 1f7fbf8295..be09c647fa 100644 --- a/src/axom/core/tests/core_checksum.hpp +++ b/src/axom/core/tests/core_checksum.hpp @@ -9,6 +9,9 @@ #include "axom/core/ArrayView.hpp" #include "axom/core/utilities/Checksum.hpp" +#include +#include + TEST(core_checksum, scalar_matches_singleton_view_and_scale_factor) { const double value = 3.25; @@ -43,3 +46,16 @@ TEST(core_checksum, array_order_and_values_change_checksum) orderedChecksum, axom::utilities::checksum(axom::ArrayView(modified, 4))); } + +TEST(core_checksum, exceptional_floating_point_values_are_stable) +{ + const double positiveZero = 0.0; + const double negativeZero = -0.0; + const double infinity = std::numeric_limits::infinity(); + const double nanValue = std::numeric_limits::quiet_NaN(); + + EXPECT_EQ(axom::utilities::checksum(positiveZero), + axom::utilities::checksum(negativeZero)); + EXPECT_TRUE(std::isinf(axom::utilities::checksum(infinity))); + EXPECT_TRUE(std::isnan(axom::utilities::checksum(nanValue))); +} diff --git a/src/axom/sidre/core/ConduitMemory.cpp b/src/axom/sidre/core/ConduitMemory.cpp index 7d02647edb..5e67b5a579 100644 --- a/src/axom/sidre/core/ConduitMemory.cpp +++ b/src/axom/sidre/core/ConduitMemory.cpp @@ -9,6 +9,17 @@ namespace axom { namespace sidre { +namespace +{ +axom::utilities::CheckSum checksumNodeMetadata(const conduit::Node& n) +{ + auto cs = axom::utilities::CheckSum {0}; + cs += axom::utilities::checksum(static_cast(n.dtype().id()), 2.0); + cs += axom::utilities::checksum(n.number_of_children(), 3.0); + cs += axom::utilities::checksum(n.dtype().number_of_elements(), 5.0); + return cs; +} +} // namespace std::map> ConduitMemory::s_axomToInstance; std::map> ConduitMemory::s_conduitToInstance; @@ -301,16 +312,28 @@ axom::utilities::CheckSum checksum(const conduit::Node& n, bool include_name) cs = axom::utilities::checksum(view); } + cs += checksumNodeMetadata(n); + if(n.number_of_children() > 0) { - for(conduit::index_t i = 0; i < n.number_of_children(); i++) + if(n.dtype().is_list()) + { + cs += axom::utilities::calculateChecksum( + [&](axom::IndexType i) -> axom::utilities::CheckSum { + return checksum(n[static_cast(i)], true); + }, + n.number_of_children()); + } + else { - cs += checksum(n[i], true); + for(conduit::index_t i = 0; i < n.number_of_children(); i++) + { + cs += checksum(n[i], true); + } } } else { - // NOTE: this assumes contiguous data if(n.dtype().is_string()) { axom::ArrayView view(static_cast(n.data_ptr()), n.dtype().number_of_elements()); diff --git a/src/axom/sidre/core/View.cpp b/src/axom/sidre/core/View.cpp index 306daec6f8..99c39a7cdf 100644 --- a/src/axom/sidre/core/View.cpp +++ b/src/axom/sidre/core/View.cpp @@ -23,6 +23,18 @@ namespace axom { namespace sidre { +namespace +{ +axom::utilities::CheckSum checksumNamedNode(const std::string& name, + const conduit::Node& node) +{ + axom::ArrayView nameView(name.data(), name.size()); + auto cs = axom::utilities::checksum(nameView); + cs += axom::sidre::checksum(node, false); + return cs; +} +} // namespace + /* ************************************************************************* * @@ -2079,6 +2091,16 @@ axom::utilities::CheckSum View::checksum() const // Checksum the view contents without double-counting the view name. cs += axom::sidre::checksum(m_node, false); + + for(IndexType attrIdx = getFirstValidAttrValueIndex(); attrIdx != InvalidIndex; + attrIdx = getNextValidAttrValueIndex(attrIdx)) + { + const Attribute* attr = getAttribute(attrIdx); + if(attr != nullptr) + { + cs += checksumNamedNode(attr->getName(), getAttributeNodeRef(attr)); + } + } return cs; } diff --git a/src/axom/sidre/tests/sidre_checksum.cpp b/src/axom/sidre/tests/sidre_checksum.cpp index dee8407116..3f23f8fcbd 100644 --- a/src/axom/sidre/tests/sidre_checksum.cpp +++ b/src/axom/sidre/tests/sidre_checksum.cpp @@ -109,9 +109,9 @@ TEST(sidre_checksum, conduit_checksum_supports_empty_object_and_list_nodes) conduit::Node emptyList; emptyList.set(conduit::DataType::list()); - EXPECT_EQ(0.0L, axom::sidre::checksum(emptyObject, false)); - EXPECT_EQ(0.0L, axom::sidre::checksum(emptyList, false)); - EXPECT_EQ(axom::sidre::checksum(emptyObject, false), + EXPECT_NE(0.0L, axom::sidre::checksum(emptyObject, false)); + EXPECT_NE(0.0L, axom::sidre::checksum(emptyList, false)); + EXPECT_NE(axom::sidre::checksum(emptyObject, false), axom::sidre::checksum(emptyList, false)); conduit::Node namedEmptyObject; @@ -124,6 +124,44 @@ TEST(sidre_checksum, conduit_checksum_supports_empty_object_and_list_nodes) axom::sidre::checksum(namedEmptyList["empty_list"])); } +TEST(sidre_checksum, conduit_checksum_distinguishes_schema_only_changes) +{ + conduit::Node twoElementArray; + const int twoValues[] = {5, 0}; + twoElementArray.set(twoValues, 2); + + conduit::Node oneElementArray; + const int oneValue[] = {5}; + oneElementArray.set(oneValue, 1); + + conduit::Node emptyIntArray; + emptyIntArray.set(conduit::DataType::c_int(0)); + + conduit::Node emptyFloatArray; + emptyFloatArray.set(conduit::DataType::float64(0)); + + EXPECT_NE(axom::sidre::checksum(oneElementArray, false), + axom::sidre::checksum(twoElementArray, false)); + EXPECT_NE(axom::sidre::checksum(emptyIntArray, false), + axom::sidre::checksum(emptyFloatArray, false)); +} + +TEST(sidre_checksum, conduit_checksum_is_order_sensitive_for_lists) +{ + conduit::Node ordered; + ordered.set(conduit::DataType::list()); + ordered.append().set(1); + ordered.append().set(2); + + conduit::Node reordered; + reordered.set(conduit::DataType::list()); + reordered.append().set(2); + reordered.append().set(1); + + EXPECT_NE(axom::sidre::checksum(ordered, false), + axom::sidre::checksum(reordered, false)); +} + TEST(sidre_checksum, sidre_view_and_group_checksum_support_strided_external_data) { axom::sidre::DataStore datastore; @@ -174,9 +212,60 @@ TEST(sidre_checksum, view_checksum_changes_on_rename_and_data_mutation) EXPECT_NE(dataChecksum, view->checksum()); } -TEST(sidre_checksum, view_checksum_matches_unnamed_native_layout_path) +TEST(sidre_checksum, view_and_group_checksum_change_on_attribute_mutation) +{ + axom::sidre::DataStore datastore; + auto* category = datastore.createAttributeString("category", "default"); + auto* priority = datastore.createAttributeScalar("priority", 1); + + axom::sidre::Group* group = datastore.getRoot()->createGroup("fields"); + axom::sidre::View* view = group->createViewScalar("value", 9); + + const auto baseChecksum = view->checksum(); + const auto baseGroupChecksum = group->checksum(); + + ASSERT_TRUE(view->setAttributeString(category, "alpha")); + const auto stringAttrChecksum = view->checksum(); + EXPECT_NE(baseChecksum, stringAttrChecksum); + EXPECT_NE(baseGroupChecksum, group->checksum()); + + ASSERT_TRUE(view->setAttributeScalar(priority, 3)); + const auto scalarAttrChecksum = view->checksum(); + EXPECT_NE(stringAttrChecksum, scalarAttrChecksum); + + ASSERT_TRUE(view->setAttributeString(category, "beta")); + const auto mutatedAttrChecksum = view->checksum(); + EXPECT_NE(scalarAttrChecksum, mutatedAttrChecksum); + + ASSERT_TRUE(view->setAttributeToDefault(category)); + EXPECT_NE(mutatedAttrChecksum, view->checksum()); + + ASSERT_TRUE(view->setAttributeToDefault(priority)); + EXPECT_EQ(baseChecksum, view->checksum()); + EXPECT_EQ(baseGroupChecksum, group->checksum()); +} + +TEST(sidre_checksum, view_checksum_distinguishes_array_extent_changes) +{ + axom::sidre::DataStore oneStore; + axom::sidre::View* oneElementView = + oneStore.getRoot()->createViewAndAllocate("value", axom::sidre::INT_ID, 1); + oneElementView->getData()[0] = 5; + + axom::sidre::DataStore twoStore; + axom::sidre::View* twoElementView = + twoStore.getRoot()->createViewAndAllocate("value", axom::sidre::INT_ID, 2); + int* twoElementData = twoElementView->getData(); + twoElementData[0] = 5; + twoElementData[1] = 0; + + EXPECT_NE(oneElementView->checksum(), twoElementView->checksum()); +} + +TEST(sidre_checksum, view_checksum_extends_unnamed_native_layout_path_with_attributes) { axom::sidre::DataStore datastore; + auto* tag = datastore.createAttributeString("tag", "default"); axom::sidre::View* view = datastore.getRoot()->createViewAndAllocate("values", axom::sidre::INT_ID, 3); @@ -193,6 +282,9 @@ TEST(sidre_checksum, view_checksum_matches_unnamed_native_layout_path) axom::utilities::checksum(nameView) + axom::sidre::checksum(nativeLayout); EXPECT_EQ(oldPathChecksum, view->checksum()); + + ASSERT_TRUE(view->setAttributeString(tag, "alpha")); + EXPECT_NE(oldPathChecksum, view->checksum()); } TEST(sidre_checksum, group_checksum_changes_on_add_remove_rename_and_descendant_data) From 88c979334bd4f224dbb369b3269b2b161f9e5b4a Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 30 Jun 2026 16:22:46 -0700 Subject: [PATCH 555/986] Expose the checksum method in Python sidre bindings. --- src/axom/sidre/nanobind_sidre.cpp | 4 ++++ src/axom/sidre/tests/sidre_group_Py.py | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/axom/sidre/nanobind_sidre.cpp b/src/axom/sidre/nanobind_sidre.cpp index c7838966a5..4481384861 100644 --- a/src/axom/sidre/nanobind_sidre.cpp +++ b/src/axom/sidre/nanobind_sidre.cpp @@ -796,6 +796,7 @@ NB_MODULE(pysidre, m_sidre) .def("getPathName", &View::getPathName, "Return the full path of the View object, including its name.") + .def("checksum", &View::checksum, "Return a checksum for the View's name, metadata, and data.") .def("getOwningGroup", nb::overload_cast<>(&View::getOwningGroup), nb::rv_policy::reference_internal, @@ -1162,6 +1163,9 @@ NB_MODULE(pysidre, m_sidre) .def("getName", &Group::getName, "Return const reference to name of Group object.") .def("getPath", &Group::getPath, "Return path of Group object, not including its name.") .def("getPathName", &Group::getPathName, "Return full path of Group object, including its name.") + .def("checksum", + &Group::checksum, + "Return a checksum for the Group's name, child structure, and descendant view data.") .def("getParent", nb::overload_cast<>(&Group::getParent, nb::const_), nb::rv_policy::reference_internal, diff --git a/src/axom/sidre/tests/sidre_group_Py.py b/src/axom/sidre/tests/sidre_group_Py.py index 18a68df4f8..313ef4a6dc 100644 --- a/src/axom/sidre/tests/sidre_group_Py.py +++ b/src/axom/sidre/tests/sidre_group_Py.py @@ -218,6 +218,29 @@ def test_get_view(): assert view2 == None +def test_group_and_view_checksum(): + ds = pysidre.DataStore() + root = ds.getRoot() + group = root.createGroup("checksum_group") + view = group.createViewAndAllocate("values", pysidre.TypeID.INT32_ID, 4) + + data = view.getDataArray() + data[:] = np.array([1, 2, 3, 4], dtype=np.int32) + + view_checksum = float(view.checksum()) + group_checksum = float(group.checksum()) + + data[2] = 9 + mutated_view_checksum = float(view.checksum()) + mutated_group_checksum = float(group.checksum()) + + assert mutated_view_checksum != view_checksum + assert mutated_group_checksum != group_checksum + + group.createGroup("child") + assert float(group.checksum()) != mutated_group_checksum + + #------------------------------------------------------------------------------ # createView, hasView(), getView(), destroyView() with path strings #------------------------------------------------------------------------------ From 8cad2a049d40bb878caac00f1739e97e6f08b6c4 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 30 Jun 2026 16:25:24 -0700 Subject: [PATCH 556/986] make style --- src/axom/core/tests/core_checksum.hpp | 16 +++++--------- src/axom/core/utilities/Checksum.hpp | 16 ++++++++------ src/axom/sidre/core/ConduitMemory.cpp | 12 ++++++++--- src/axom/sidre/core/ConduitMemory.hpp | 3 +-- src/axom/sidre/core/View.cpp | 5 ++--- src/axom/sidre/tests/sidre_checksum.cpp | 28 ++++++++----------------- 6 files changed, 36 insertions(+), 44 deletions(-) diff --git a/src/axom/core/tests/core_checksum.hpp b/src/axom/core/tests/core_checksum.hpp index be09c647fa..285dba09d3 100644 --- a/src/axom/core/tests/core_checksum.hpp +++ b/src/axom/core/tests/core_checksum.hpp @@ -36,15 +36,10 @@ TEST(core_checksum, array_order_and_values_change_checksum) const double reordered[] = {4.0, 3.0, 2.0, 1.0}; const double modified[] = {1.0, 2.0, 3.0, 5.0}; - const auto orderedChecksum = - axom::utilities::checksum(axom::ArrayView(ordered, 4)); - - EXPECT_NE( - orderedChecksum, - axom::utilities::checksum(axom::ArrayView(reordered, 4))); - EXPECT_NE( - orderedChecksum, - axom::utilities::checksum(axom::ArrayView(modified, 4))); + const auto orderedChecksum = axom::utilities::checksum(axom::ArrayView(ordered, 4)); + + EXPECT_NE(orderedChecksum, axom::utilities::checksum(axom::ArrayView(reordered, 4))); + EXPECT_NE(orderedChecksum, axom::utilities::checksum(axom::ArrayView(modified, 4))); } TEST(core_checksum, exceptional_floating_point_values_are_stable) @@ -54,8 +49,7 @@ TEST(core_checksum, exceptional_floating_point_values_are_stable) const double infinity = std::numeric_limits::infinity(); const double nanValue = std::numeric_limits::quiet_NaN(); - EXPECT_EQ(axom::utilities::checksum(positiveZero), - axom::utilities::checksum(negativeZero)); + EXPECT_EQ(axom::utilities::checksum(positiveZero), axom::utilities::checksum(negativeZero)); EXPECT_TRUE(std::isinf(axom::utilities::checksum(infinity))); EXPECT_TRUE(std::isnan(axom::utilities::checksum(nanValue))); } diff --git a/src/axom/core/utilities/Checksum.hpp b/src/axom/core/utilities/Checksum.hpp index a5335caeb9..0a3c06b2a9 100644 --- a/src/axom/core/utilities/Checksum.hpp +++ b/src/axom/core/utilities/Checksum.hpp @@ -33,9 +33,10 @@ inline CheckSum calculateChecksum(DataGetter data, axom::IndexType len) { CheckSum tchk = 0.0; CheckSum ckahan = 0.0; - for (axom::IndexType j = 0; j < len; ++j) { + for(axom::IndexType j = 0; j < len; ++j) + { const auto value = data(j); - CheckSum x = (std::abs(std::sin(j+1.0))+0.5) * value; + CheckSum x = (std::abs(std::sin(j + 1.0)) + 0.5) * value; CheckSum y = x - ckahan; volatile CheckSum t = tchk + y; volatile CheckSum z = t - tchk; @@ -53,9 +54,10 @@ inline CheckSum calculateChecksum(DataGetter data, axom::IndexType len) * \return A CheckSum value for the array view. */ template -inline CheckSum checksum(T value, const ScaleFactor scaleFactor = ScaleFactor{1}) +inline CheckSum checksum(T value, const ScaleFactor scaleFactor = ScaleFactor {1}) { - return calculateChecksum([=](axom::IndexType) { return static_cast(value); }, 1) * scaleFactor; + return calculateChecksum([=](axom::IndexType) { return static_cast(value); }, 1) * + scaleFactor; } /*! @@ -66,9 +68,11 @@ inline CheckSum checksum(T value, const ScaleFactor scaleFactor = ScaleFactor{1} * \return A CheckSum value for the array view. */ template -inline CheckSum checksum(axom::ArrayView view, const ScaleFactor scaleFactor = ScaleFactor{1}) +inline CheckSum checksum(axom::ArrayView view, const ScaleFactor scaleFactor = ScaleFactor {1}) { - return calculateChecksum([=](axom::IndexType i) { return static_cast(view[i]); }, view.size()) * scaleFactor; + return calculateChecksum([=](axom::IndexType i) { return static_cast(view[i]); }, + view.size()) * + scaleFactor; } } // namespace utilities diff --git a/src/axom/sidre/core/ConduitMemory.cpp b/src/axom/sidre/core/ConduitMemory.cpp index 5e67b5a579..f006b102c2 100644 --- a/src/axom/sidre/core/ConduitMemory.cpp +++ b/src/axom/sidre/core/ConduitMemory.cpp @@ -297,9 +297,14 @@ const ConduitMemory& ConduitMemory::instanceForConduitId(conduit::index_t condui /// Operate on conduit::DataArray so we can handle strided data. template -axom::utilities::CheckSum checksumArray(const conduit::DataArray &arr, const axom::utilities::ScaleFactor scaleFactor = axom::utilities::ScaleFactor{1}) +axom::utilities::CheckSum checksumArray( + const conduit::DataArray& arr, + const axom::utilities::ScaleFactor scaleFactor = axom::utilities::ScaleFactor {1}) { - return axom::utilities::calculateChecksum([=](axom::IndexType i) { return static_cast(arr[i]); }, arr.number_of_elements()) * scaleFactor; + return axom::utilities::calculateChecksum( + [=](axom::IndexType i) { return static_cast(arr[i]); }, + arr.number_of_elements()) * + scaleFactor; } axom::utilities::CheckSum checksum(const conduit::Node& n, bool include_name) @@ -336,7 +341,8 @@ axom::utilities::CheckSum checksum(const conduit::Node& n, bool include_name) { if(n.dtype().is_string()) { - axom::ArrayView view(static_cast(n.data_ptr()), n.dtype().number_of_elements()); + axom::ArrayView view(static_cast(n.data_ptr()), + n.dtype().number_of_elements()); return cs += axom::utilities::checksum(view); } else if(n.dtype().is_int8()) diff --git a/src/axom/sidre/core/ConduitMemory.hpp b/src/axom/sidre/core/ConduitMemory.hpp index 30237eca8f..a560f7bc54 100644 --- a/src/axom/sidre/core/ConduitMemory.hpp +++ b/src/axom/sidre/core/ConduitMemory.hpp @@ -171,8 +171,7 @@ struct ConduitMemory * * \return A checksum of the node. */ -axom::utilities::CheckSum checksum(const conduit::Node& n, - bool include_name = true); +axom::utilities::CheckSum checksum(const conduit::Node& n, bool include_name = true); } /* end namespace sidre */ } /* end namespace axom */ diff --git a/src/axom/sidre/core/View.cpp b/src/axom/sidre/core/View.cpp index 99c39a7cdf..0b44db7e01 100644 --- a/src/axom/sidre/core/View.cpp +++ b/src/axom/sidre/core/View.cpp @@ -25,8 +25,7 @@ namespace sidre { namespace { -axom::utilities::CheckSum checksumNamedNode(const std::string& name, - const conduit::Node& node) +axom::utilities::CheckSum checksumNamedNode(const std::string& name, const conduit::Node& node) { axom::ArrayView nameView(name.data(), name.size()); auto cs = axom::utilities::checksum(nameView); @@ -2101,7 +2100,7 @@ axom::utilities::CheckSum View::checksum() const cs += checksumNamedNode(attr->getName(), getAttributeNodeRef(attr)); } } - + return cs; } diff --git a/src/axom/sidre/tests/sidre_checksum.cpp b/src/axom/sidre/tests/sidre_checksum.cpp index 3f23f8fcbd..a414a91b10 100644 --- a/src/axom/sidre/tests/sidre_checksum.cpp +++ b/src/axom/sidre/tests/sidre_checksum.cpp @@ -45,9 +45,7 @@ TEST(sidre_checksum, conduit_checksum_handles_strided_numeric_arrays) contiguous.set_external(contiguousValues, 3); conduit::Node strided; - strided.set_external( - conduit::DataType::float64(3, 0, 2 * sizeof(double)), - interleavedValues); + strided.set_external(conduit::DataType::float64(3, 0, 2 * sizeof(double)), interleavedValues); EXPECT_EQ(axom::sidre::checksum(contiguous), axom::sidre::checksum(strided)); } @@ -64,12 +62,9 @@ TEST(sidre_checksum, conduit_checksum_handles_strided_numeric_array_trees) contiguous["fields/values"].set_external(contiguousValues, 3); conduit::Node strided; - strided["fields/ids"].set_external( - conduit::DataType::c_int(3, 0, 2 * sizeof(int)), - interleavedIds); - strided["fields/values"].set_external( - conduit::DataType::float64(3, 0, 2 * sizeof(double)), - interleavedValues); + strided["fields/ids"].set_external(conduit::DataType::c_int(3, 0, 2 * sizeof(int)), interleavedIds); + strided["fields/values"].set_external(conduit::DataType::float64(3, 0, 2 * sizeof(double)), + interleavedValues); EXPECT_EQ(axom::sidre::checksum(contiguous), axom::sidre::checksum(strided)); } @@ -111,8 +106,7 @@ TEST(sidre_checksum, conduit_checksum_supports_empty_object_and_list_nodes) EXPECT_NE(0.0L, axom::sidre::checksum(emptyObject, false)); EXPECT_NE(0.0L, axom::sidre::checksum(emptyList, false)); - EXPECT_NE(axom::sidre::checksum(emptyObject, false), - axom::sidre::checksum(emptyList, false)); + EXPECT_NE(axom::sidre::checksum(emptyObject, false), axom::sidre::checksum(emptyList, false)); conduit::Node namedEmptyObject; namedEmptyObject["empty_object"].set(conduit::DataType::object()); @@ -158,8 +152,7 @@ TEST(sidre_checksum, conduit_checksum_is_order_sensitive_for_lists) reordered.append().set(2); reordered.append().set(1); - EXPECT_NE(axom::sidre::checksum(ordered, false), - axom::sidre::checksum(reordered, false)); + EXPECT_NE(axom::sidre::checksum(ordered, false), axom::sidre::checksum(reordered, false)); } TEST(sidre_checksum, sidre_view_and_group_checksum_support_strided_external_data) @@ -177,8 +170,7 @@ TEST(sidre_checksum, sidre_view_and_group_checksum_support_strided_external_data conduit::Node contiguousNode; contiguousNode.set_external(contiguousData, 3); - axom::ArrayView nameView(stridedView->getName().data(), - stridedView->getName().size()); + axom::ArrayView nameView(stridedView->getName().data(), stridedView->getName().size()); const auto expectedViewChecksum = axom::utilities::checksum(nameView) + axom::sidre::checksum(contiguousNode); @@ -193,8 +185,7 @@ TEST(sidre_checksum, view_checksum_changes_on_rename_and_data_mutation) { axom::sidre::DataStore datastore; axom::sidre::Group* root = datastore.getRoot(); - axom::sidre::View* view = - root->createViewAndAllocate("values", axom::sidre::INT_ID, 4); + axom::sidre::View* view = root->createViewAndAllocate("values", axom::sidre::INT_ID, 4); int* data = view->getData(); data[0] = 2; @@ -309,8 +300,7 @@ TEST(sidre_checksum, group_checksum_changes_on_add_remove_rename_and_descendant_ const auto childRenamedChecksum = group->checksum(); EXPECT_NE(childAddedChecksum, childRenamedChecksum); - axom::sidre::View* values = - child->createViewAndAllocate("values", axom::sidre::INT_ID, 4); + axom::sidre::View* values = child->createViewAndAllocate("values", axom::sidre::INT_ID, 4); int* data = values->getData(); data[0] = 1; data[1] = 3; From 662a4933dbd3361550e22f1ab773f1cbdcc15275 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 30 Jun 2026 17:22:54 -0700 Subject: [PATCH 557/986] Added a Group::checksum() overload that can output a tree of checksums. --- src/axom/sidre/core/Group.cpp | 28 +++++++++++++ src/axom/sidre/core/Group.hpp | 54 +++++++++++++++++++++++++ src/axom/sidre/tests/sidre_checksum.cpp | 46 +++++++++++++++++++++ 3 files changed, 128 insertions(+) diff --git a/src/axom/sidre/core/Group.cpp b/src/axom/sidre/core/Group.cpp index c84d5d07dc..af0891e7f8 100644 --- a/src/axom/sidre/core/Group.cpp +++ b/src/axom/sidre/core/Group.cpp @@ -2897,6 +2897,34 @@ axom::utilities::CheckSum Group::checksum() const return cs; } +void Group::checksum(conduit::Node& n_checksum) const +{ + // Always emit a fresh snapshot so callers can safely reuse the same node. + n_checksum.reset(); + n_checksum.set(conduit::DataType::object()); + n_checksum["checksum"] = static_cast(checksum()); + + // Add the checksums of the views and groups. + if(getNumViews() > 0) + { + conduit::Node& n_views = n_checksum["views"]; + for(const auto& view : this->views()) + { + conduit::Node& n_view = n_views[view.getName()]; + n_view["checksum"] = static_cast(view.checksum()); + } + } + if(getNumGroups() > 0) + { + conduit::Node& n_groups = n_checksum["groups"]; + for(const auto& group : this->groups()) + { + conduit::Node& n_group = n_groups[group.getName()]; + group.checksum(n_group); + } + } +} + /* ************************************************************************* * diff --git a/src/axom/sidre/core/Group.hpp b/src/axom/sidre/core/Group.hpp index 21c2599691..c5f7e5bc22 100644 --- a/src/axom/sidre/core/Group.hpp +++ b/src/axom/sidre/core/Group.hpp @@ -1857,6 +1857,60 @@ class Group */ axom::utilities::CheckSum checksum() const; + /*! + * \brief Store checksum metadata for this group subtree in a Conduit node. + * + * The output node is overwritten with an object that mirrors the nesting of + * the current group's direct child groups and views. The current group itself + * is represented by the output node, not by an additional wrapper keyed by + * `getName()`. Each group object stores its aggregate checksum in a + * `checksum` field, optional child-group metadata in + * `groups/`, and optional direct-view metadata in + * `views/`. Each view entry stores its checksum in a + * `checksum` field. + * + * For a group tree such as: + * + * \code + * { + * "group0": + * { + * "group1": + * { + * "view1": [1, 2, 3] + * }, + * "view2": [4, 5, 6] + * } + * } + * \endcode + * + * Calling `group0->checksum(out)` emits metadata for `group0` itself: + * + * \code + * { + * "checksum": , + * "groups": + * { + * "group1": + * { + * "checksum": , + * "views": + * { + * "view1": { "checksum": } + * } + * } + * }, + * "views": + * { + * "view2": { "checksum": } + * } + * } + * \endcode + * + * \param n_checksum The output node that receives the checksum metadata. + */ + void checksum(conduit::Node& n_checksum) const; + private: DISABLE_DEFAULT_CTOR(Group); DISABLE_COPY_AND_ASSIGNMENT(Group); diff --git a/src/axom/sidre/tests/sidre_checksum.cpp b/src/axom/sidre/tests/sidre_checksum.cpp index a414a91b10..ea903d8711 100644 --- a/src/axom/sidre/tests/sidre_checksum.cpp +++ b/src/axom/sidre/tests/sidre_checksum.cpp @@ -316,3 +316,49 @@ TEST(sidre_checksum, group_checksum_changes_on_add_remove_rename_and_descendant_ EXPECT_NE(afterMutationChecksum, group->checksum()); EXPECT_EQ(emptyChecksum, group->checksum()); } + +TEST(sidre_checksum, group_checksum_metadata_mirrors_structure_and_replaces_stale_entries) +{ + axom::sidre::DataStore datastore; + axom::sidre::Group* root = datastore.getRoot(); + axom::sidre::Group* group = root->createGroup("group0"); + axom::sidre::Group* child = group->createGroup("group1"); + axom::sidre::View* childView = child->createViewAndAllocate("view1", axom::sidre::INT_ID, 3); + axom::sidre::View* directView = group->createViewAndAllocate("view2", axom::sidre::INT_ID, 3); + + int* childData = childView->getData(); + childData[0] = 1; + childData[1] = 2; + childData[2] = 3; + + int* directData = directView->getData(); + directData[0] = 4; + directData[1] = 5; + directData[2] = 6; + + conduit::Node metadata; + group->checksum(metadata); + + EXPECT_DOUBLE_EQ(static_cast(group->checksum()), metadata["checksum"].to_double()); + ASSERT_TRUE(metadata.has_path("groups/group1/checksum")); + ASSERT_TRUE(metadata.has_path("groups/group1/views/view1/checksum")); + ASSERT_TRUE(metadata.has_path("views/view2/checksum")); + EXPECT_DOUBLE_EQ(static_cast(child->checksum()), + metadata["groups/group1/checksum"].to_double()); + EXPECT_DOUBLE_EQ(static_cast(childView->checksum()), + metadata["groups/group1/views/view1/checksum"].to_double()); + EXPECT_DOUBLE_EQ(static_cast(directView->checksum()), + metadata["views/view2/checksum"].to_double()); + + group->destroyGroupAndData("group1"); + group->destroyViewAndData("view2"); + group->createViewScalar("status", 99); + + group->checksum(metadata); + + EXPECT_DOUBLE_EQ(static_cast(group->checksum()), metadata["checksum"].to_double()); + EXPECT_FALSE(metadata.has_path("groups/group1")); + EXPECT_FALSE(metadata.has_path("views/view2")); + ASSERT_TRUE(metadata.has_path("views/status/checksum")); + EXPECT_FALSE(metadata.has_child("groups")); +} From bc02eba00a5a687fec8056add6ac75143eb039e4 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 30 Jun 2026 17:26:52 -0700 Subject: [PATCH 558/986] Changed release notes --- RELEASE-NOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 2d0481a94d..01e5d99105 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -44,7 +44,7 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ - Primal: Adds a `primal::BezierTriangle` class - Inlet: Added the ability to have collections (array and dictionary) with variant values. - Inlet: Added the ability to have collections (array and dictionary) with variant user defined structures. -- Core/Sidre: Added `axom::sidre::Group::checksum()` and `axom::sidre::View::checksum()` methods. +- Sidre: Added `axom::sidre::View::checksum()` and `axom::sidre::Group::checksum()` methods that return checksum values. A `Group::checksum(conduit::Node&)` overload emits diffable checksum metadata for group/view subtrees. ### Removed - Bump: Removed `axom::bump::views::MultiBufferMaterialView`, which was a view type for an obsolete flavor of Blueprint matset. From 318ac25896deb397040523dfff24ca19111ab6a0 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 30 Jun 2026 17:38:02 -0700 Subject: [PATCH 559/986] Make axom_execution_policies function and use it in various CMakeLists.txt files --- RELEASE-NOTES.md | 1 + src/axom/core/examples/CMakeLists.txt | 7 +--- .../concentric_circles/CMakeLists.txt | 7 +--- .../mir/examples/heavily_mixed/CMakeLists.txt | 7 +--- .../examples/tutorial_simple/CMakeLists.txt | 7 +--- src/axom/primal/examples/CMakeLists.txt | 5 +-- src/axom/quest/examples/CMakeLists.txt | 37 +++---------------- src/axom/quest/tests/CMakeLists.txt | 17 +-------- src/cmake/AxomMacros.cmake | 17 +++++++++ 9 files changed, 30 insertions(+), 75 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index f4790caab6..771ba76e0c 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -53,6 +53,7 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ ### Changed - Updates CMake code check targets to only use checked in files (via `git ls-files`, when available) +- CMake: Reuses `axom_execution_policies()` in more example, test, and tool CMake logic across Core, Primal, Mir, Quest, and `mesh_tester`. - Core: Optimization for axom::Array indirection -- since the stride is always 1, we can remove the runtime multiplication - Python: Removes build and test dependencies from `run_python_with_axom.sh` wrapper script diff --git a/src/axom/core/examples/CMakeLists.txt b/src/axom/core/examples/CMakeLists.txt index b992677d93..4e2a0c220d 100644 --- a/src/axom/core/examples/CMakeLists.txt +++ b/src/axom/core/examples/CMakeLists.txt @@ -72,12 +72,7 @@ axom_add_executable( if(AXOM_ENABLE_TESTS) # Run the core array performance example on N ranks for each enabled policy - set(_policies "seq") - if(RAJA_FOUND) - blt_list_append(TO _policies ELEMENTS "omp" IF AXOM_ENABLE_OPENMP) - blt_list_append(TO _policies ELEMENTS "cuda" IF AXOM_ENABLE_CUDA) - blt_list_append(TO _policies ELEMENTS "hip" IF AXOM_ENABLE_HIP) - endif() + axom_execution_policies(_policies) foreach(_pol ${_policies}) # sets the number of threads for the omp policy; leave empty for non-omp policies set(_num_threads) diff --git a/src/axom/mir/examples/concentric_circles/CMakeLists.txt b/src/axom/mir/examples/concentric_circles/CMakeLists.txt index f941e72364..cd58e54764 100644 --- a/src/axom/mir/examples/concentric_circles/CMakeLists.txt +++ b/src/axom/mir/examples/concentric_circles/CMakeLists.txt @@ -32,12 +32,7 @@ if(AXOM_ENABLE_MPI AND CONDUIT_RELAY_MPI_ENABLED) endif() if(AXOM_ENABLE_TESTS) - set (_policies "seq") - if(RAJA_FOUND AND UMPIRE_FOUND) - blt_list_append(TO _policies ELEMENTS "omp" IF AXOM_ENABLE_OPENMP) - blt_list_append(TO _policies ELEMENTS "cuda" IF AXOM_ENABLE_CUDA) - blt_list_append(TO _policies ELEMENTS "hip" IF AXOM_ENABLE_HIP) - endif() + axom_execution_policies(_policies) foreach(_policy ${_policies}) # sets the number of threads for the omp policy; leave empty for non-omp policies diff --git a/src/axom/mir/examples/heavily_mixed/CMakeLists.txt b/src/axom/mir/examples/heavily_mixed/CMakeLists.txt index 5c3f757018..321e91b900 100644 --- a/src/axom/mir/examples/heavily_mixed/CMakeLists.txt +++ b/src/axom/mir/examples/heavily_mixed/CMakeLists.txt @@ -21,12 +21,7 @@ axom_add_executable( ) if(AXOM_ENABLE_TESTS) - set (_policies "seq") - if(RAJA_FOUND AND UMPIRE_FOUND) - blt_list_append(TO _policies ELEMENTS "omp" IF AXOM_ENABLE_OPENMP) - blt_list_append(TO _policies ELEMENTS "cuda" IF AXOM_ENABLE_CUDA) - blt_list_append(TO _policies ELEMENTS "hip" IF AXOM_ENABLE_HIP) - endif() + axom_execution_policies(_policies) foreach(_policy ${_policies}) set(_num_threads) diff --git a/src/axom/mir/examples/tutorial_simple/CMakeLists.txt b/src/axom/mir/examples/tutorial_simple/CMakeLists.txt index 37be285aea..a56be26cc3 100644 --- a/src/axom/mir/examples/tutorial_simple/CMakeLists.txt +++ b/src/axom/mir/examples/tutorial_simple/CMakeLists.txt @@ -41,12 +41,7 @@ axom_add_executable( if(AXOM_ENABLE_TESTS) set(_test_numbers 1 2 3 4 5) - set (_policies "seq") - if(RAJA_FOUND AND UMPIRE_FOUND) - blt_list_append(TO _policies ELEMENTS "omp" IF AXOM_ENABLE_OPENMP) - blt_list_append(TO _policies ELEMENTS "cuda" IF AXOM_ENABLE_CUDA) - blt_list_append(TO _policies ELEMENTS "hip" IF AXOM_ENABLE_HIP) - endif() + axom_execution_policies(_policies) foreach(_policy ${_policies}) set(_num_threads) diff --git a/src/axom/primal/examples/CMakeLists.txt b/src/axom/primal/examples/CMakeLists.txt index 62e021762b..9598849cd1 100644 --- a/src/axom/primal/examples/CMakeLists.txt +++ b/src/axom/primal/examples/CMakeLists.txt @@ -46,10 +46,7 @@ if (RAJA_FOUND AND UMPIRE_FOUND) # Run the hex_tet_volume_ex example with different raja policies # to check for completion - set (_policies "seq") - blt_list_append(TO _policies ELEMENTS "omp" IF AXOM_ENABLE_OPENMP) - blt_list_append(TO _policies ELEMENTS "cuda" IF AXOM_ENABLE_CUDA) - blt_list_append(TO _policies ELEMENTS "hip" IF AXOM_ENABLE_HIP) + axom_execution_policies(_policies) foreach(_policy ${_policies}) # sets the number of threads for the omp policy; leave empty for non-omp policies diff --git a/src/axom/quest/examples/CMakeLists.txt b/src/axom/quest/examples/CMakeLists.txt index c76d278a23..8f43a50a48 100644 --- a/src/axom/quest/examples/CMakeLists.txt +++ b/src/axom/quest/examples/CMakeLists.txt @@ -52,12 +52,7 @@ if(AXOM_ENABLE_TESTS AND AXOM_DATA_DIR) set(input_file "${AXOM_DATA_DIR}/quest/unit_cube.stl") - set (_policies "seq") - if(RAJA_FOUND) - blt_list_append(TO _policies ELEMENTS "omp" IF AXOM_ENABLE_OPENMP) - blt_list_append(TO _policies ELEMENTS "cuda" IF AXOM_ENABLE_CUDA) - blt_list_append(TO _policies ELEMENTS "hip" IF AXOM_ENABLE_HIP) - endif() + axom_execution_policies(_policies) foreach(_policy ${_policies}) # sets the number of threads for the omp policy; leave empty for non-omp policies @@ -109,12 +104,7 @@ if (AXOM_ENABLE_MPI AND CONDUIT_FOUND AND UMPIRE_FOUND) set(_methods "bvh" "implicit") - set (_policies "seq") - if(RAJA_FOUND) - blt_list_append(TO _policies ELEMENTS "omp" IF AXOM_ENABLE_OPENMP) - blt_list_append(TO _policies ELEMENTS "cuda" IF AXOM_ENABLE_CUDA) - blt_list_append(TO _policies ELEMENTS "hip" IF AXOM_ENABLE_HIP) - endif() + axom_execution_policies(_policies) foreach(_method ${_methods}) foreach(_policy ${_policies}) @@ -240,14 +230,7 @@ if(AXOM_ENABLE_MPI AND MFEM_FOUND AND MFEM_USE_MPI # Tests 2D c2c contour if(C2C_FOUND) - set(_policies "seq") - if(RAJA_FOUND) - blt_list_append(TO _policies ELEMENTS "omp" IF AXOM_ENABLE_OPENMP) - if(UMPIRE_FOUND) - blt_list_append(TO _policies ELEMENTS "cuda" IF AXOM_ENABLE_CUDA) - blt_list_append(TO _policies ELEMENTS "hip" IF AXOM_ENABLE_HIP) - endif() - endif() + axom_execution_policies(_policies) foreach(_policy ${_policies}) # sets the number of threads for the omp policy; leave empty for non-omp policies @@ -360,12 +343,7 @@ if(AXOM_ENABLE_MPI AND AXOM_ENABLE_SIDRE AND HDF5_FOUND) set(_nranks 3) # Run the distributed closest point example on N ranks for each enabled policy - set(_policies "seq") - if(RAJA_FOUND) - blt_list_append(TO _policies ELEMENTS "cuda" IF AXOM_ENABLE_CUDA) - blt_list_append(TO _policies ELEMENTS "hip" IF AXOM_ENABLE_HIP) - blt_list_append(TO _policies ELEMENTS "omp" IF AXOM_ENABLE_OPENMP) - endif() + axom_execution_policies(_policies) # Non-zero empty-rank probability tests domain underloading case set(_meshes "mdmesh.2x1" "mdmesh.2x3" "mdmesh.2x2x1" "mdmeshg.2x2x1") @@ -447,12 +425,7 @@ if(CONDUIT_FOUND) endif() # Run the marching cubes example on N ranks for each enabled policy - set(_policies "seq") - if(RAJA_FOUND) - blt_list_append(TO _policies ELEMENTS "omp" IF AXOM_ENABLE_OPENMP) - blt_list_append(TO _policies ELEMENTS "cuda" IF AXOM_ENABLE_CUDA) - blt_list_append(TO _policies ELEMENTS "hip" IF AXOM_ENABLE_HIP) - endif() + axom_execution_policies(_policies) # Non-zero empty-rank probability tests domain underloading case set(_meshes "mdmesh.2x1" "mdmesh.2x3" "mdmesh.2x2x1" "mdmeshg.2x2x1") diff --git a/src/axom/quest/tests/CMakeLists.txt b/src/axom/quest/tests/CMakeLists.txt index 98eaace8ac..7117ab56a5 100644 --- a/src/axom/quest/tests/CMakeLists.txt +++ b/src/axom/quest/tests/CMakeLists.txt @@ -199,15 +199,7 @@ if(ENABLE_MPI AND MFEM_FOUND AND MFEM_USE_MPI endforeach() # Invoke the intersection shaper test program as many tests that run a subset of the tests. - set(_policies "seq") - if(RAJA_FOUND) - blt_list_append(TO _policies ELEMENTS "omp" IF AXOM_ENABLE_OPENMP) - if(AXOM_USE_UMPIRE) - blt_list_append(TO _policies ELEMENTS "cuda" IF AXOM_ENABLE_CUDA) - blt_list_append(TO _policies ELEMENTS "hip" IF AXOM_ENABLE_HIP) - endif() - endif() - + axom_execution_policies(_policies) foreach(_pol ${_policies}) # sets the number of threads set(_num_threads) @@ -268,12 +260,7 @@ if(AXOM_ENABLE_KLEE AND AXOM_ENABLE_SIDRE AND CONDUIT_FOUND AND RAJA_FOUND AND A set(_nranks 1) # Run the geometry clipping test on with each enabled policy - set(_policies "seq") - if(RAJA_FOUND) - blt_list_append(TO _policies ELEMENTS "omp" IF AXOM_ENABLE_OPENMP) - blt_list_append(TO _policies ELEMENTS "cuda" IF AXOM_ENABLE_CUDA) - blt_list_append(TO _policies ELEMENTS "hip" IF AXOM_ENABLE_HIP) - endif() + axom_execution_policies(_policies) set(_testgeoms "plane" "tet" "hex" "sphere" "tetmesh" "cupmesh" "sor" "cyl" "cone" "plane,hex,tetmesh") set(_meshTypes "bpSidre" "bpConduit") diff --git a/src/cmake/AxomMacros.cmake b/src/cmake/AxomMacros.cmake index 02a0cd6d0e..a34510172f 100644 --- a/src/cmake/AxomMacros.cmake +++ b/src/cmake/AxomMacros.cmake @@ -687,3 +687,20 @@ macro(axom_force_release_for_target tgt) endif() target_compile_definitions(${tgt} PRIVATE NDEBUG) endmacro(axom_force_release_for_target) + +##------------------------------------------------------------------------------ +## axom_execution_policies +## +## This function returns the list of valid execution policies for the build. +##------------------------------------------------------------------------------ +function(axom_execution_policies out_var) + set(_policies "seq") + if(RAJA_FOUND) + blt_list_append(TO _policies ELEMENTS "omp" IF AXOM_ENABLE_OPENMP) + if(AXOM_USE_UMPIRE) + blt_list_append(TO _policies ELEMENTS "cuda" IF AXOM_ENABLE_CUDA) + blt_list_append(TO _policies ELEMENTS "hip" IF AXOM_ENABLE_HIP) + endif() + endif() + set(${out_var} "${_policies}" PARENT_SCOPE) +endfunction() From e475bb33428c45edd270c994a0d1681650710411 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 30 Jun 2026 17:39:14 -0700 Subject: [PATCH 560/986] Convert mesh_tester to newer style of indicating policies: seq,omp,cuda,hip. We make the non-RAJA case go through seq. --- src/tools/CMakeLists.txt | 7 +- src/tools/mesh_tester.cpp | 246 ++++++++++++-------------------------- 2 files changed, 78 insertions(+), 175 deletions(-) diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index a7be4a3af1..2089b0a21e 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -75,16 +75,13 @@ if(AXOM_ENABLE_QUEST) if(AXOM_ENABLE_TESTS AND AXOM_DATA_DIR AND RAJA_FOUND AND UMPIRE_FOUND) # Run the mesh_tester with the plane_simp_problems.stl example and - # different spatial indexes, raja policies + # different spatial indexes and execution policies set(plane_data_dir "${AXOM_DATA_DIR}/quest/plane_simp_problems.stl") set(_methods "bvh" "implicit" "uniform") - set (_policies "raja_seq") - blt_list_append(TO _policies ELEMENTS "raja_omp" IF AXOM_ENABLE_OPENMP) - blt_list_append(TO _policies ELEMENTS "raja_cuda" IF AXOM_ENABLE_CUDA) - blt_list_append(TO _policies ELEMENTS "raja_hip" IF AXOM_ENABLE_HIP) + axom_execution_policies(_policies) foreach(_method ${_methods}) foreach(_policy ${_policies}) diff --git a/src/tools/mesh_tester.cpp b/src/tools/mesh_tester.cpp index 976b34d940..a5d0c1b418 100644 --- a/src/tools/mesh_tester.cpp +++ b/src/tools/mesh_tester.cpp @@ -80,59 +80,18 @@ using SpatialBoundingBox = primal::BoundingBox; using UniformGrid3 = spin::UniformGrid; using Vector3 = primal::Vector; using Segment3 = primal::Segment; - -enum RuntimePolicy -{ - seq = 0, - raja_seq = 1, - raja_omp = 2, - raja_cuda = 3, - raja_hip = 4 -}; - -template <> -struct axom::fmt::formatter : axom::fmt::formatter -{ - template - auto format(const RuntimePolicy& policy, FormatContext& ctx) const - { - std::string name = "unknown"; - switch(policy) - { - case seq: - name = "seq"; - break; - case raja_seq: - name = "raja_seq"; - break; - case raja_omp: - name = "raja_omp"; - break; - case raja_cuda: - name = "raja_cuda"; - break; - case raja_hip: - name = "raja_hip"; - break; - default: - name = "unknown"; - break; - } - return axom::fmt::formatter::format(name, ctx); - } -}; +using RuntimePolicy = axom::runtime_policy::Policy; struct Input { static const std::set s_validMethods; static const std::set s_validFormats; - static const std::map s_validPolicies; std::string stlInput {""}; std::string fileOutput {""}; std::string fileFormat {"vtk"}; std::string method {"uniform"}; - RuntimePolicy policy {seq}; + RuntimePolicy policy {RuntimePolicy::seq}; std::string annotationMode {"none"}; int resolution {0}; @@ -166,22 +125,6 @@ const std::set Input::s_validFormats({ "stl", "vtk" }); - -const std::map Input::s_validPolicies({ - {"seq", seq} - #ifdef AXOM_USE_RAJA - , {"raja_seq", raja_seq} - #if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) - , {"raja_omp", raja_omp} - #endif - #if defined(AXOM_RUNTIME_POLICY_USE_CUDA) - , {"raja_cuda", raja_cuda} - #endif - #if defined(AXOM_RUNTIME_POLICY_USE_HIP) - , {"raja_hip", raja_hip} - #endif - #endif -}); // clang-format on void Input::parse(int argc, char** argv, axom::CLI::App& app) @@ -207,25 +150,21 @@ void Input::parse(int argc, char** argv, axom::CLI::App& app) ->capture_default_str(); std::stringstream pol_sstr; - pol_sstr << "With '-m bvh' or '-m naive', set runtime policy. \n" - << "Set to 'seq' or 0 to use the sequential algorithm " - << "(w/o RAJA)."; -#ifdef AXOM_USE_RAJA - pol_sstr << "\nSet to 'raja_seq' or 1 to use the RAJA sequential policy."; - #if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) - pol_sstr << "\nSet to 'raja_omp' or 2 to use the RAJA OpenMP policy."; - #endif - #if defined(AXOM_RUNTIME_POLICY_USE_CUDA) - pol_sstr << "\nSet to 'raja_cuda' or 3 to use the RAJA CUDA policy."; - #endif - #if defined(AXOM_RUNTIME_POLICY_USE_HIP) - pol_sstr << "\nSet to 'raja_hip' or 4 to use the RAJA HIP policy."; - #endif + pol_sstr << "Set runtime policy for the selected method."; + pol_sstr << "\nSet to 'seq' or 0 to use the sequential policy."; +#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) + pol_sstr << "\nSet to 'omp' or 1 to use the OpenMP policy."; +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) + pol_sstr << "\nSet to 'cuda' or 2 to use the CUDA policy."; +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_HIP) + pol_sstr << "\nSet to 'hip' or 3 to use the HIP policy."; #endif app.add_option("-p, --policy", policy, pol_sstr.str()) ->capture_default_str() - ->transform(axom::CLI::CheckedTransformer(Input::s_validPolicies)); + ->transform(axom::CLI::CheckedTransformer(axom::runtime_policy::s_nameToPolicy)); app.add_option("-f, --format", fileFormat, "Output file format, one of: 'stl', 'vtk'") ->capture_default_str() @@ -284,20 +223,24 @@ void Input::parse(int argc, char** argv, axom::CLI::App& app) else { return "";} }()); - const std::string policy_str = (method == "naive" || method == "bvh") - ? axom::fmt::format("\n policy = {} {}", static_cast(policy), + const std::string policy_str = axom::fmt::format("\n policy = {} {}", + axom::runtime_policy::policyToName(policy), [this]() -> std::string { switch(this->policy) { - case seq: return " (use sequential policy)"; - case raja_omp: return " (use RAJA OpenMP policy)"; - case raja_cuda: return " (use RAJA CUDA policy)"; - case raja_hip: return " (use RAJA HIP policy)"; - case raja_seq: return " (use RAJA sequential policy)"; + case RuntimePolicy::seq: return " (use sequential policy)"; +#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) + case RuntimePolicy::omp: return " (use OpenMP policy)"; +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) + case RuntimePolicy::cuda: return " (use CUDA policy)"; +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_HIP) + case RuntimePolicy::hip: return " (use HIP policy)"; +#endif default: return ""; } - }()) - : ""; + }()); // clang-format on #ifdef AXOM_USE_CALIPER @@ -455,7 +398,6 @@ std::vector> naiveIntersectionAlgorithm(mint::Mesh* surface_ return retval; } -#if defined(AXOM_USE_RAJA) template std::vector> naiveIntersectionAlgorithm(mint::Mesh* surface_mesh, std::vector& degenerate, @@ -523,7 +465,7 @@ std::vector> naiveIntersectionAlgorithm(mint::Mesh* surface_ auto intersections_v = intersections_d.view(); auto counter_v = on_device ? counter_d.view() : counter_h.view(); - // RAJA loop to populate with intersections + // Populate the intersections using Axom execution policies. axom::for_all( col_range, row_range, @@ -551,7 +493,6 @@ std::vector> naiveIntersectionAlgorithm(mint::Mesh* surface_ return retval; } -#endif void announceMeshProblems(int triangleCount, int intersectPairCount, int degenerateCount) { @@ -753,38 +694,33 @@ int main(int argc, char** argv) { switch(params.policy) { - case seq: - collisions = - naiveIntersectionAlgorithm(surface_mesh, degenerate, params.intersectionThreshold); - break; -#if defined(AXOM_USE_RAJA) - case raja_seq: + case RuntimePolicy::seq: collisions = naiveIntersectionAlgorithm(surface_mesh, degenerate, params.intersectionThreshold); break; - #if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) - case raja_omp: +#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) + case RuntimePolicy::omp: collisions = naiveIntersectionAlgorithm(surface_mesh, degenerate, params.intersectionThreshold); break; - #endif - #if defined(AXOM_RUNTIME_POLICY_USE_CUDA) - case raja_cuda: +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) + case RuntimePolicy::cuda: collisions = naiveIntersectionAlgorithm(surface_mesh, degenerate, params.intersectionThreshold); break; - #endif - #if defined(AXOM_RUNTIME_POLICY_USE_HIP) - case raja_hip: +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_HIP) + case RuntimePolicy::hip: collisions = naiveIntersectionAlgorithm(surface_mesh, degenerate, params.intersectionThreshold); break; - #endif -#endif // AXOM_USE_RAJA && AXOM_USE_UMPIRE +#endif default: - SLIC_ERROR("Unhandled runtime policy case " << params.policy); + SLIC_ERROR("Unhandled runtime policy case " + << axom::runtime_policy::policyToName(params.policy)); break; } } // end of if method == 'naive' @@ -792,49 +728,39 @@ int main(int argc, char** argv) { switch(params.policy) { - case seq: -#ifdef AXOM_USE_RAJA - SLIC_INFO("BVH was compiled with RAJA - seq and raja_seq execution will be equivalent"); -#endif + case RuntimePolicy::seq: quest::findTriMeshIntersectionsBVH(surface_mesh, collisions, degenerate, params.intersectionThreshold); break; -#if defined(AXOM_USE_RAJA) - case raja_seq: - quest::findTriMeshIntersectionsBVH(surface_mesh, - collisions, - degenerate, - params.intersectionThreshold); - break; - #if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) - case raja_omp: +#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) + case RuntimePolicy::omp: quest::findTriMeshIntersectionsBVH(surface_mesh, collisions, degenerate, params.intersectionThreshold); break; - #endif - #if defined(AXOM_RUNTIME_POLICY_USE_CUDA) - case raja_cuda: +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) + case RuntimePolicy::cuda: quest::findTriMeshIntersectionsBVH(surface_mesh, collisions, degenerate, params.intersectionThreshold); break; - #endif - #if defined(AXOM_RUNTIME_POLICY_USE_HIP) - case raja_hip: +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_HIP) + case RuntimePolicy::hip: quest::findTriMeshIntersectionsBVH(surface_mesh, collisions, degenerate, params.intersectionThreshold); break; - #endif -#endif // AXOM_USE_RAJA && AXOM_USE_UMPIRE +#endif default: - SLIC_ERROR("Unhandled runtime policy case " << params.policy); + SLIC_ERROR("Unhandled runtime policy case " + << axom::runtime_policy::policyToName(params.policy)); break; } } // end of if method == 'bvh' @@ -842,55 +768,43 @@ int main(int argc, char** argv) { switch(params.policy) { - case seq: -#ifdef AXOM_USE_RAJA - SLIC_INFO( - "ImplicitGrid was compiled with RAJA - seq and raja_seq execution will be equivalent"); -#endif - quest::findTriMeshIntersectionsImplicitGrid(surface_mesh, - collisions, - degenerate, - params.resolution, - params.intersectionThreshold); - break; -#ifdef AXOM_USE_RAJA - case raja_seq: + case RuntimePolicy::seq: quest::findTriMeshIntersectionsImplicitGrid(surface_mesh, collisions, degenerate, params.resolution, params.intersectionThreshold); break; - #if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) - case raja_omp: +#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) + case RuntimePolicy::omp: quest::findTriMeshIntersectionsImplicitGrid(surface_mesh, collisions, degenerate, params.resolution, params.intersectionThreshold); break; - #endif - #if defined(AXOM_RUNTIME_POLICY_USE_CUDA) - case raja_cuda: +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) + case RuntimePolicy::cuda: quest::findTriMeshIntersectionsImplicitGrid(surface_mesh, collisions, degenerate, params.resolution, params.intersectionThreshold); break; - #endif - #if defined(AXOM_RUNTIME_POLICY_USE_HIP) - case raja_hip: +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_HIP) + case RuntimePolicy::hip: quest::findTriMeshIntersectionsImplicitGrid(surface_mesh, collisions, degenerate, params.resolution, params.intersectionThreshold); break; - #endif -#endif // AXOM_USE_RAJA +#endif default: - SLIC_ERROR("Unhandled runtime policy case " << params.policy); + SLIC_ERROR("Unhandled runtime policy case " + << axom::runtime_policy::policyToName(params.policy)); break; } } @@ -898,54 +812,46 @@ int main(int argc, char** argv) { switch(params.policy) { - case seq: + case RuntimePolicy::seq: // _check_repair_intersections_start // Use a uniform grid spatial index - quest::findTriMeshIntersections(surface_mesh, - collisions, - degenerate, - params.resolution, - params.intersectionThreshold); - // _check_repair_intersections_end - break; -#ifdef AXOM_USE_RAJA - case raja_seq: quest::findTriMeshIntersectionsUniformGrid(surface_mesh, collisions, degenerate, params.resolution, params.intersectionThreshold); + // _check_repair_intersections_end break; - #if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) - case raja_omp: +#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) + case RuntimePolicy::omp: quest::findTriMeshIntersectionsUniformGrid(surface_mesh, collisions, degenerate, params.resolution, params.intersectionThreshold); break; - #endif - #if defined(AXOM_RUNTIME_POLICY_USE_CUDA) - case raja_cuda: +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) + case RuntimePolicy::cuda: quest::findTriMeshIntersectionsUniformGrid(surface_mesh, collisions, degenerate, params.resolution, params.intersectionThreshold); break; - #endif - #if defined(AXOM_RUNTIME_POLICY_USE_HIP) - case raja_hip: +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_HIP) + case RuntimePolicy::hip: quest::findTriMeshIntersectionsUniformGrid(surface_mesh, collisions, degenerate, params.resolution, params.intersectionThreshold); break; - #endif -#endif // AXOM_USE_RAJA +#endif default: - SLIC_ERROR("Unhandled runtime policy case " << params.policy); + SLIC_ERROR("Unhandled runtime policy case " + << axom::runtime_policy::policyToName(params.policy)); break; } } From d5bfd62f9b0f015b14ba5c4d8f836f720b7dc229 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 30 Jun 2026 17:44:02 -0700 Subject: [PATCH 561/986] Added a cast --- src/axom/core/utilities/Checksum.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/axom/core/utilities/Checksum.hpp b/src/axom/core/utilities/Checksum.hpp index 0a3c06b2a9..dfb45f136b 100644 --- a/src/axom/core/utilities/Checksum.hpp +++ b/src/axom/core/utilities/Checksum.hpp @@ -35,7 +35,7 @@ inline CheckSum calculateChecksum(DataGetter data, axom::IndexType len) CheckSum ckahan = 0.0; for(axom::IndexType j = 0; j < len; ++j) { - const auto value = data(j); + const auto value = static_cast(data(j)); CheckSum x = (std::abs(std::sin(j + 1.0)) + 0.5) * value; CheckSum y = x - ckahan; volatile CheckSum t = tchk + y; From 7e6c36e4952e1846484181cd39f7985bd76f2f1a Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 30 Jun 2026 17:49:48 -0700 Subject: [PATCH 562/986] Make single return --- src/axom/sidre/core/ConduitMemory.cpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/axom/sidre/core/ConduitMemory.cpp b/src/axom/sidre/core/ConduitMemory.cpp index f006b102c2..ade761f429 100644 --- a/src/axom/sidre/core/ConduitMemory.cpp +++ b/src/axom/sidre/core/ConduitMemory.cpp @@ -343,55 +343,55 @@ axom::utilities::CheckSum checksum(const conduit::Node& n, bool include_name) { axom::ArrayView view(static_cast(n.data_ptr()), n.dtype().number_of_elements()); - return cs += axom::utilities::checksum(view); + cs += axom::utilities::checksum(view); } else if(n.dtype().is_int8()) { - return cs += checksumArray(n.as_int8_array()); + cs += checksumArray(n.as_int8_array()); } else if(n.dtype().is_int16()) { - return cs += checksumArray(n.as_int16_array()); + cs += checksumArray(n.as_int16_array()); } else if(n.dtype().is_int32()) { - return cs += checksumArray(n.as_int32_array()); + cs += checksumArray(n.as_int32_array()); } else if(n.dtype().is_int64()) { - return cs += checksumArray(n.as_int64_array()); + cs += checksumArray(n.as_int64_array()); } else if(n.dtype().is_uint8()) { - return cs += checksumArray(n.as_uint8_array()); + cs += checksumArray(n.as_uint8_array()); } else if(n.dtype().is_uint16()) { - return cs += checksumArray(n.as_uint16_array()); + cs += checksumArray(n.as_uint16_array()); } else if(n.dtype().is_uint32()) { - return cs += checksumArray(n.as_uint32_array()); + cs += checksumArray(n.as_uint32_array()); } else if(n.dtype().is_uint64()) { - return cs += checksumArray(n.as_uint64_array()); + cs += checksumArray(n.as_uint64_array()); } else if(n.dtype().is_index_t()) { - return cs += checksumArray(n.as_index_t_array()); + cs += checksumArray(n.as_index_t_array()); } else if(n.dtype().is_float32()) { - return cs += checksumArray(n.as_float32_array()); + cs += checksumArray(n.as_float32_array()); } else if(n.dtype().is_float64()) { - return cs += checksumArray(n.as_float64_array()); + cs += checksumArray(n.as_float64_array()); } else if(n.dtype().is_empty() || n.dtype().is_object() || n.dtype().is_list()) { - return cs; + // no-op } } return cs; From 5ac7f6c145118107bb55f1f619941c1af9054e11 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 30 Jun 2026 18:13:57 -0700 Subject: [PATCH 563/986] Adjust release notes --- RELEASE-NOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 771ba76e0c..fdc9db3b5d 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -53,7 +53,7 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ ### Changed - Updates CMake code check targets to only use checked in files (via `git ls-files`, when available) -- CMake: Reuses `axom_execution_policies()` in more example, test, and tool CMake logic across Core, Primal, Mir, Quest, and `mesh_tester`. +- CMake: Reuses `axom_execution_policies()` to simplify Axom build logic - Core: Optimization for axom::Array indirection -- since the stride is always 1, we can remove the runtime multiplication - Python: Removes build and test dependencies from `run_python_with_axom.sh` wrapper script From 42b81aeca889127e91c4523bd47129492c2fc9b1 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 30 Jun 2026 18:18:42 -0700 Subject: [PATCH 564/986] Fix a sidre Python test --- src/axom/sidre/nanobind_sidre.cpp | 10 +++++++++- src/axom/sidre/tests/sidre_group_Py.py | 11 +++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/axom/sidre/nanobind_sidre.cpp b/src/axom/sidre/nanobind_sidre.cpp index 4481384861..b20ebd8f76 100644 --- a/src/axom/sidre/nanobind_sidre.cpp +++ b/src/axom/sidre/nanobind_sidre.cpp @@ -1164,8 +1164,16 @@ NB_MODULE(pysidre, m_sidre) .def("getPath", &Group::getPath, "Return path of Group object, not including its name.") .def("getPathName", &Group::getPathName, "Return full path of Group object, including its name.") .def("checksum", - &Group::checksum, + nb::overload_cast<>(&Group::checksum, nb::const_), "Return a checksum for the Group's name, child structure, and descendant view data.") + .def( + "checksum", + [](const Group& self, nb::object& o) { + conduit::Node& cpp_node = nbObjectToNode(o); + self.checksum(cpp_node); + }, + "Populate a Conduit node with checksum metadata for this Group hierarchy.", + nb::arg("n_checksum")) .def("getParent", nb::overload_cast<>(&Group::getParent, nb::const_), nb::rv_policy::reference_internal, diff --git a/src/axom/sidre/tests/sidre_group_Py.py b/src/axom/sidre/tests/sidre_group_Py.py index 313ef4a6dc..a11e8f4e53 100644 --- a/src/axom/sidre/tests/sidre_group_Py.py +++ b/src/axom/sidre/tests/sidre_group_Py.py @@ -6,6 +6,7 @@ import pysidre import numpy as np +from conduit import Node if pysidre.AXOM_USE_HDF5: NPROTOCOLS = 3 @@ -240,6 +241,16 @@ def test_group_and_view_checksum(): group.createGroup("child") assert float(group.checksum()) != mutated_group_checksum + metadata = Node() + group.checksum(metadata) + + assert metadata.has_child("checksum") + assert metadata.has_child("views") + assert metadata.has_path("views/values/checksum") + assert metadata.has_child("groups") + assert metadata.has_path("groups/child/checksum") + assert float(metadata["checksum"]) == float(group.checksum()) + #------------------------------------------------------------------------------ # createView, hasView(), getView(), destroyView() with path strings From 9c459c0a9128843038bf5c26f67cfa39785079b2 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 30 Jun 2026 18:40:27 -0700 Subject: [PATCH 565/986] More consistent policy use --- .../tutorial_simple/mir_tutorial_simple.cpp | 24 ++++++++---------- .../quest/examples/quest_bvh_two_pass.cpp | 25 +++++++++---------- 2 files changed, 22 insertions(+), 27 deletions(-) diff --git a/src/axom/mir/examples/tutorial_simple/mir_tutorial_simple.cpp b/src/axom/mir/examples/tutorial_simple/mir_tutorial_simple.cpp index 6b2f257382..a2011d0778 100644 --- a/src/axom/mir/examples/tutorial_simple/mir_tutorial_simple.cpp +++ b/src/axom/mir/examples/tutorial_simple/mir_tutorial_simple.cpp @@ -80,17 +80,15 @@ struct Input std::stringstream pol_sstr; pol_sstr << "Set MIR runtime policy."; -#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) pol_sstr << "\nSet to 'seq' or 0 to use the RAJA sequential policy."; - #ifdef AXOM_USE_OPENMP +#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) pol_sstr << "\nSet to 'omp' or 1 to use the RAJA OpenMP policy."; - #endif - #ifdef AXOM_USE_CUDA +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) pol_sstr << "\nSet to 'cuda' or 2 to use the RAJA CUDA policy."; - #endif - #ifdef AXOM_USE_HIP +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_HIP) pol_sstr << "\nSet to 'hip' or 3 to use the RAJA HIP policy."; - #endif #endif m_app.add_option("-p, --policy", m_policy, pol_sstr.str()) ->capture_default_str() @@ -214,25 +212,23 @@ int main(int argc, char **argv) { retval = runMIR_seq(mesh, options, resultMesh); } -#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) - #if defined(AXOM_USE_OPENMP) +#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) else if(params.m_policy == RuntimePolicy::omp) { retval = runMIR_omp(mesh, options, resultMesh); } - #endif - #if defined(AXOM_USE_CUDA) +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) else if(params.m_policy == RuntimePolicy::cuda) { retval = runMIR_cuda(mesh, options, resultMesh); } - #endif - #if defined(AXOM_USE_HIP) +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_HIP) else if(params.m_policy == RuntimePolicy::hip) { retval = runMIR_hip(mesh, options, resultMesh); } - #endif #endif else { diff --git a/src/axom/quest/examples/quest_bvh_two_pass.cpp b/src/axom/quest/examples/quest_bvh_two_pass.cpp index a569bfe827..f06cbd4713 100644 --- a/src/axom/quest/examples/quest_bvh_two_pass.cpp +++ b/src/axom/quest/examples/quest_bvh_two_pass.cpp @@ -15,6 +15,7 @@ // Axom includes #include "axom/core.hpp" +#include "axom/core/execution/runtime_policy.hpp" #include "axom/mint.hpp" #include "axom/primal.hpp" #include "axom/spin.hpp" @@ -41,13 +42,13 @@ enum class ExecPolicy }; const std::map validExecPolicies {{"seq", ExecPolicy::CPU}, -#ifdef AXOM_USE_OPENMP +#ifdef AXOM_RUNTIME_POLICY_USE_OPENMP {"omp", ExecPolicy::OpenMP}, #endif -#ifdef AXOM_USE_CUDA +#ifdef AXOM_RUNTIME_POLICY_USE_CUDA {"cuda", ExecPolicy::CUDA} #endif -#ifdef AXOM_USE_HIP +#ifdef AXOM_RUNTIME_POLICY_USE_HIP {"hip", ExecPolicy::HIP} #endif }; @@ -328,13 +329,13 @@ struct Arguments std::string pol_info = "Sets execution space of the BVH two-pass example.\n"; pol_info += "Set to \'seq\' to use sequential execution policy."; -#ifdef AXOM_USE_OPENMP +#ifdef AXOM_RUNTIME_POLICY_USE_OPENMP pol_info += "\nSet to \'omp\' to use an OpenMP execution policy."; #endif -#ifdef AXOM_USE_CUDA +#ifdef AXOM_RUNTIME_POLICY_USE_CUDA pol_info += "\nSet to \'cuda\' to use a CUDA GPU execution policy."; #endif -#ifdef AXOM_USE_HIP +#ifdef AXOM_RUNTIME_POLICY_USE_HIP pol_info += "\nSet to \'hip\' to use a HIP GPU execution policy."; #endif app.add_option("-e, --exec_space", this->exec_space, pol_info) @@ -397,8 +398,7 @@ int main(int argc, char** argv) firstPair, secondPair); break; -#if defined(AXOM_USE_RAJA) - #if defined(AXOM_USE_OPENMP) +#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) case ExecPolicy::OpenMP: find_collisions_broadphase(surface_mesh.get(), candFirstPair, candSecondPair); find_collisions_narrowphase(surface_mesh.get(), @@ -407,8 +407,8 @@ int main(int argc, char** argv) firstPair, secondPair); break; - #endif - #if defined(AXOM_USE_UMPIRE) && defined(AXOM_USE_CUDA) +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) case ExecPolicy::CUDA: find_collisions_broadphase>(surface_mesh.get(), candFirstPair, candSecondPair); find_collisions_narrowphase>(surface_mesh.get(), @@ -417,8 +417,8 @@ int main(int argc, char** argv) firstPair, secondPair); break; - #endif - #if defined(AXOM_USE_UMPIRE) && defined(AXOM_USE_HIP) +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_HIP) case ExecPolicy::HIP: find_collisions_broadphase>(surface_mesh.get(), candFirstPair, candSecondPair); find_collisions_narrowphase>(surface_mesh.get(), @@ -427,7 +427,6 @@ int main(int argc, char** argv) firstPair, secondPair); break; - #endif #endif default: SLIC_ERROR("Unsupported execution space."); From 870409a8af0cc17e75babd77fd6092f88f64aeab Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 30 Jun 2026 18:47:18 -0700 Subject: [PATCH 566/986] policy and style --- .../examples/concentric_circles/MIRApplication.cpp | 12 +++++------- .../mir/examples/heavily_mixed/HMApplication.cpp | 12 +++++------- src/axom/quest/tests/quest_mesh_clipper.cpp | 12 +++++------- 3 files changed, 15 insertions(+), 21 deletions(-) diff --git a/src/axom/mir/examples/concentric_circles/MIRApplication.cpp b/src/axom/mir/examples/concentric_circles/MIRApplication.cpp index 0921c89809..8f89c15b03 100644 --- a/src/axom/mir/examples/concentric_circles/MIRApplication.cpp +++ b/src/axom/mir/examples/concentric_circles/MIRApplication.cpp @@ -69,17 +69,15 @@ int MIRApplication::initialize(int argc, char **argv) std::stringstream pol_sstr; pol_sstr << "Set MIR runtime policy method."; -#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) pol_sstr << "\nSet to 'seq' or 0 to use the RAJA sequential policy."; - #ifdef AXOM_USE_OPENMP +#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) pol_sstr << "\nSet to 'omp' or 1 to use the RAJA OpenMP policy."; - #endif - #ifdef AXOM_USE_CUDA +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) pol_sstr << "\nSet to 'cuda' or 2 to use the RAJA CUDA policy."; - #endif - #ifdef AXOM_USE_HIP +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_HIP) pol_sstr << "\nSet to 'hip' or 3 to use the RAJA HIP policy."; - #endif #endif app.add_option("-p, --policy", policy, pol_sstr.str()) ->capture_default_str() diff --git a/src/axom/mir/examples/heavily_mixed/HMApplication.cpp b/src/axom/mir/examples/heavily_mixed/HMApplication.cpp index d4d505b8b5..6ad94f41fe 100644 --- a/src/axom/mir/examples/heavily_mixed/HMApplication.cpp +++ b/src/axom/mir/examples/heavily_mixed/HMApplication.cpp @@ -256,17 +256,15 @@ int HMApplication::initialize(int argc, char **argv) std::stringstream pol_sstr; pol_sstr << "Set MIR runtime policy method."; -#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) pol_sstr << "\nSet to 'seq' or 0 to use the RAJA sequential policy."; - #ifdef AXOM_USE_OPENMP +#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) pol_sstr << "\nSet to 'omp' or 1 to use the RAJA OpenMP policy."; - #endif - #ifdef AXOM_USE_CUDA +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) pol_sstr << "\nSet to 'cuda' or 2 to use the RAJA CUDA policy."; - #endif - #ifdef AXOM_USE_HIP +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_HIP) pol_sstr << "\nSet to 'hip' or 3 to use the RAJA HIP policy."; - #endif #endif app.add_option("-p, --policy", m_policy, pol_sstr.str()) ->capture_default_str() diff --git a/src/axom/quest/tests/quest_mesh_clipper.cpp b/src/axom/quest/tests/quest_mesh_clipper.cpp index a75be27c15..b988990569 100644 --- a/src/axom/quest/tests/quest_mesh_clipper.cpp +++ b/src/axom/quest/tests/quest_mesh_clipper.cpp @@ -247,16 +247,14 @@ struct Input std::stringstream pol_sstr; pol_sstr << "Set runtime policy for intersection-based sampling method."; pol_sstr << "\nSet to 'seq' or 0 to use the sequential policy."; -#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) - #ifdef AXOM_USE_OPENMP +#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) pol_sstr << "\nSet to 'omp' or 1 to use the RAJA OpenMP policy."; - #endif - #ifdef AXOM_USE_CUDA +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) pol_sstr << "\nSet to 'cuda' or 2 to use the RAJA CUDA policy."; - #endif - #ifdef AXOM_USE_HIP +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_HIP) pol_sstr << "\nSet to 'hip' or 3 to use the RAJA HIP policy."; - #endif #endif intersection_options->add_option("-p, --policy", policy, pol_sstr.str()) From fccdc61e1f664f44879e8ea3a728bb9c0cba4774 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 30 Jun 2026 18:56:10 -0700 Subject: [PATCH 567/986] Changed some ifdef to if defined - codex got a bit overeager. --- src/axom/core/examples/core_array_perf.cpp | 6 ++-- src/axom/core/examples/core_flatmap_perf.cpp | 6 ++-- src/axom/primal/tests/primal_clip_perf.cpp | 6 ++-- src/axom/quest/DistributedClosestPoint.cpp | 12 +++---- src/axom/quest/MeshClipper.cpp | 6 ++-- .../detail/MarchingCubesSingleDomain.cpp | 6 ++-- src/axom/quest/detail/clipping/SORClipper.cpp | 6 ++-- .../quest/examples/quest_bvh_two_pass.cpp | 12 +++---- .../examples/quest_candidates_example.cpp | 36 +++++++++---------- .../examples/quest_winding_number_2d.cpp | 2 +- .../examples/quest_winding_number_3d.cpp | 2 +- 11 files changed, 50 insertions(+), 50 deletions(-) diff --git a/src/axom/core/examples/core_array_perf.cpp b/src/axom/core/examples/core_array_perf.cpp index 68bd7d846f..4ed0ba1dbe 100644 --- a/src/axom/core/examples/core_array_perf.cpp +++ b/src/axom/core/examples/core_array_perf.cpp @@ -814,19 +814,19 @@ int main(int argc, char** argv) { runTest(); } -#ifdef AXOM_RUNTIME_POLICY_USE_OPENMP +#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) else if(params.runtimePolicy == RuntimePolicy::omp) { runTest(); } #endif -#ifdef AXOM_RUNTIME_POLICY_USE_CUDA +#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) else if(params.runtimePolicy == RuntimePolicy::cuda) { runTest>(); } #endif -#ifdef AXOM_RUNTIME_POLICY_USE_HIP +#if defined(AXOM_RUNTIME_POLICY_USE_HIP) else if(params.runtimePolicy == RuntimePolicy::hip) { runTest>(); diff --git a/src/axom/core/examples/core_flatmap_perf.cpp b/src/axom/core/examples/core_flatmap_perf.cpp index f6779dabad..0c27de0905 100644 --- a/src/axom/core/examples/core_flatmap_perf.cpp +++ b/src/axom/core/examples/core_flatmap_perf.cpp @@ -177,19 +177,19 @@ int main(int argc, char** argv) { test_flatmap_init_and_query(params.num_elems, params.rep_count); } -#ifdef AXOM_RUNTIME_POLICY_USE_OPENMP +#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) else if(params.runtime_policy == RuntimePolicy::omp) { test_flatmap_init_and_query(params.num_elems, params.rep_count); } #endif -#ifdef AXOM_RUNTIME_POLICY_USE_CUDA +#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) else if(params.runtime_policy == RuntimePolicy::cuda) { test_flatmap_init_and_query, int>(params.num_elems, params.rep_count); } #endif -#ifdef AXOM_RUNTIME_POLICY_USE_HIP +#if defined(AXOM_RUNTIME_POLICY_USE_HIP) else if(params.runtime_policy == RuntimePolicy::hip) { test_flatmap_init_and_query, int>(params.num_elems, params.rep_count); diff --git a/src/axom/primal/tests/primal_clip_perf.cpp b/src/axom/primal/tests/primal_clip_perf.cpp index 2681190cf3..a4b883452a 100644 --- a/src/axom/primal/tests/primal_clip_perf.cpp +++ b/src/axom/primal/tests/primal_clip_perf.cpp @@ -94,19 +94,19 @@ void time_repeat_clips_all(const Primal3D::TetrahedronType &a, { time_repeat_clips(a, b, count, caseName); -#ifdef AXOM_RUNTIME_POLICY_USE_OPENMP +#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) { time_repeat_clips(a, b, count, caseName); } #endif -#ifdef AXOM_RUNTIME_POLICY_USE_CUDA +#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) { time_repeat_clips>(a, b, count, caseName); } #endif -#ifdef AXOM_RUNTIME_POLICY_USE_HIP +#if defined(AXOM_RUNTIME_POLICY_USE_HIP) { time_repeat_clips>(a, b, count, caseName); } diff --git a/src/axom/quest/DistributedClosestPoint.cpp b/src/axom/quest/DistributedClosestPoint.cpp index caa00be645..d6d1b84869 100644 --- a/src/axom/quest/DistributedClosestPoint.cpp +++ b/src/axom/quest/DistributedClosestPoint.cpp @@ -61,21 +61,21 @@ void DistributedClosestPoint::setDefaultAllocatorID() defaultAllocatorID = axom::execution_space::allocatorID(); break; -#ifdef AXOM_RUNTIME_POLICY_USE_OPENMP +#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) case RuntimePolicy::omp: defaultAllocatorID = axom::execution_space::allocatorID(); break; #endif #ifdef __CUDACC__ - #ifdef AXOM_RUNTIME_POLICY_USE_CUDA + #if defined(AXOM_RUNTIME_POLICY_USE_CUDA) case RuntimePolicy::cuda: defaultAllocatorID = axom::execution_space>::allocatorID(); break; #endif #endif -#ifdef AXOM_RUNTIME_POLICY_USE_HIP +#if defined(AXOM_RUNTIME_POLICY_USE_HIP) case RuntimePolicy::hip: defaultAllocatorID = axom::execution_space>::allocatorID(); break; @@ -229,21 +229,21 @@ void DistributedClosestPoint::allocateQueryInstance() : allocateQueryInstance<3, axom::SEQ_EXEC>(); break; -#ifdef AXOM_RUNTIME_POLICY_USE_OPENMP +#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) case RuntimePolicy::omp: m_dimension == 2 ? allocateQueryInstance<2, axom::OMP_EXEC>() : allocateQueryInstance<3, axom::OMP_EXEC>(); break; #endif -#ifdef AXOM_RUNTIME_POLICY_USE_CUDA +#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) case RuntimePolicy::cuda: m_dimension == 2 ? allocateQueryInstance<2, axom::CUDA_EXEC<256>>() : allocateQueryInstance<3, axom::CUDA_EXEC<256>>(); break; #endif -#ifdef AXOM_RUNTIME_POLICY_USE_HIP +#if defined(AXOM_RUNTIME_POLICY_USE_HIP) case RuntimePolicy::hip: m_dimension == 2 ? allocateQueryInstance<2, axom::HIP_EXEC<256>>() : allocateQueryInstance<3, axom::HIP_EXEC<256>>(); diff --git a/src/axom/quest/MeshClipper.cpp b/src/axom/quest/MeshClipper.cpp index c36f76eb72..003c0154ba 100644 --- a/src/axom/quest/MeshClipper.cpp +++ b/src/axom/quest/MeshClipper.cpp @@ -217,19 +217,19 @@ std::unique_ptr MeshClipper::newImpl() { impl.reset(new detail::MeshClipperImpl(*this)); } -#ifdef AXOM_RUNTIME_POLICY_USE_OPENMP +#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) else if(runtimePolicy == RuntimePolicy::omp) { impl.reset(new detail::MeshClipperImpl(*this)); } #endif -#ifdef AXOM_RUNTIME_POLICY_USE_CUDA +#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) else if(runtimePolicy == RuntimePolicy::cuda) { impl.reset(new detail::MeshClipperImpl>(*this)); } #endif -#ifdef AXOM_RUNTIME_POLICY_USE_HIP +#if defined(AXOM_RUNTIME_POLICY_USE_HIP) else if(runtimePolicy == RuntimePolicy::hip) { impl.reset(new detail::MeshClipperImpl>(*this)); diff --git a/src/axom/quest/detail/MarchingCubesSingleDomain.cpp b/src/axom/quest/detail/MarchingCubesSingleDomain.cpp index 43b41c98ba..b550a84f73 100644 --- a/src/axom/quest/detail/MarchingCubesSingleDomain.cpp +++ b/src/axom/quest/detail/MarchingCubesSingleDomain.cpp @@ -107,7 +107,7 @@ std::unique_ptr MarchingCubesSingleDomain:: m_mc.m_scannedFlags, m_mc.m_facetIncrs)); } -#ifdef AXOM_RUNTIME_POLICY_USE_OPENMP +#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) else if(m_runtimePolicy == MarchingCubes::RuntimePolicy::omp) { impl = m_ndim == 2 @@ -125,7 +125,7 @@ std::unique_ptr MarchingCubesSingleDomain:: m_mc.m_facetIncrs)); } #endif -#ifdef AXOM_RUNTIME_POLICY_USE_CUDA +#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) else if(m_runtimePolicy == MarchingCubes::RuntimePolicy::cuda) { impl = m_ndim == 2 @@ -143,7 +143,7 @@ std::unique_ptr MarchingCubesSingleDomain:: m_mc.m_facetIncrs)); } #endif -#ifdef AXOM_RUNTIME_POLICY_USE_HIP +#if defined(AXOM_RUNTIME_POLICY_USE_HIP) else if(m_runtimePolicy == MarchingCubes::RuntimePolicy::hip) { impl = m_ndim == 2 diff --git a/src/axom/quest/detail/clipping/SORClipper.cpp b/src/axom/quest/detail/clipping/SORClipper.cpp index f531170a9c..bafcbe8b66 100644 --- a/src/axom/quest/detail/clipping/SORClipper.cpp +++ b/src/axom/quest/detail/clipping/SORClipper.cpp @@ -143,19 +143,19 @@ void SORClipper::accumulateData(axom::ArrayView a, { accumulateDataImpl(a, b, scale); } -#ifdef AXOM_RUNTIME_POLICY_USE_OPENMP +#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) else if(runtimePolicy == RuntimePolicy::omp) { accumulateDataImpl(a, b, scale); } #endif -#ifdef AXOM_RUNTIME_POLICY_USE_CUDA +#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) else if(runtimePolicy == RuntimePolicy::cuda) { accumulateDataImpl>(a, b, scale); } #endif -#ifdef AXOM_RUNTIME_POLICY_USE_HIP +#if defined(AXOM_RUNTIME_POLICY_USE_HIP) else if(runtimePolicy == RuntimePolicy::hip) { accumulateDataImpl>(a, b, scale); diff --git a/src/axom/quest/examples/quest_bvh_two_pass.cpp b/src/axom/quest/examples/quest_bvh_two_pass.cpp index f06cbd4713..00e973983c 100644 --- a/src/axom/quest/examples/quest_bvh_two_pass.cpp +++ b/src/axom/quest/examples/quest_bvh_two_pass.cpp @@ -42,13 +42,13 @@ enum class ExecPolicy }; const std::map validExecPolicies {{"seq", ExecPolicy::CPU}, -#ifdef AXOM_RUNTIME_POLICY_USE_OPENMP +#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) {"omp", ExecPolicy::OpenMP}, #endif -#ifdef AXOM_RUNTIME_POLICY_USE_CUDA +#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) {"cuda", ExecPolicy::CUDA} #endif -#ifdef AXOM_RUNTIME_POLICY_USE_HIP +#if defined(AXOM_RUNTIME_POLICY_USE_HIP) {"hip", ExecPolicy::HIP} #endif }; @@ -329,13 +329,13 @@ struct Arguments std::string pol_info = "Sets execution space of the BVH two-pass example.\n"; pol_info += "Set to \'seq\' to use sequential execution policy."; -#ifdef AXOM_RUNTIME_POLICY_USE_OPENMP +#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) pol_info += "\nSet to \'omp\' to use an OpenMP execution policy."; #endif -#ifdef AXOM_RUNTIME_POLICY_USE_CUDA +#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) pol_info += "\nSet to \'cuda\' to use a CUDA GPU execution policy."; #endif -#ifdef AXOM_RUNTIME_POLICY_USE_HIP +#if defined(AXOM_RUNTIME_POLICY_USE_HIP) pol_info += "\nSet to \'hip\' to use a HIP GPU execution policy."; #endif app.add_option("-e, --exec_space", this->exec_space, pol_info) diff --git a/src/axom/quest/examples/quest_candidates_example.cpp b/src/axom/quest/examples/quest_candidates_example.cpp index 239fd188ee..d8876cf974 100644 --- a/src/axom/quest/examples/quest_candidates_example.cpp +++ b/src/axom/quest/examples/quest_candidates_example.cpp @@ -149,13 +149,13 @@ void Input::parse(int argc, char** argv, axom::CLI::App& app) ->description( "Execution policy." "\nSet to 'seq' or 0 to use the RAJA sequential policy." -#ifdef AXOM_RUNTIME_POLICY_USE_OPENMP +#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) "\nSet to 'omp' or 1 to use the RAJA openmp policy." #endif -#ifdef AXOM_RUNTIME_POLICY_USE_CUDA +#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) "\nSet to 'cuda' or 2 to use the RAJA cuda policy." #endif -#ifdef AXOM_RUNTIME_POLICY_USE_HIP +#if defined(AXOM_RUNTIME_POLICY_USE_HIP) "\nSet to 'hip' or 3 to use the RAJA hip policy." #endif ) @@ -201,17 +201,17 @@ void Input::parse(int argc, char** argv, axom::CLI::App& app) method == "bvh" ? "Bounding Volume Hierarchy (BVH)" : "Implicit Grid", method == "bvh" ? "Not Applicable" : std::to_string(resolution), policy == -#ifdef AXOM_RUNTIME_POLICY_USE_OPENMP +#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) RuntimePolicy::omp ? "omp" : policy == #endif -#ifdef AXOM_RUNTIME_POLICY_USE_CUDA +#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) RuntimePolicy::cuda ? "cuda" : policy == #endif -#ifdef AXOM_RUNTIME_POLICY_USE_HIP +#if defined(AXOM_RUNTIME_POLICY_USE_HIP) RuntimePolicy::hip ? "hip" : policy == @@ -779,21 +779,21 @@ int main(int argc, char** argv) switch(params.policy) { -#ifdef AXOM_RUNTIME_POLICY_USE_OPENMP +#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) case RuntimePolicy::omp: insert_mesh = loadBlueprintHexMesh(params.mesh_file_first, MeshLoadPolicy::OneDomainPerRankOrReplicated, params.isVerbose()); break; #endif -#ifdef AXOM_RUNTIME_POLICY_USE_CUDA +#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) case RuntimePolicy::cuda: insert_mesh = loadBlueprintHexMesh(params.mesh_file_first, MeshLoadPolicy::OneDomainPerRankOrReplicated, params.isVerbose()); break; #endif -#ifdef AXOM_RUNTIME_POLICY_USE_HIP +#if defined(AXOM_RUNTIME_POLICY_USE_HIP) case RuntimePolicy::hip: insert_mesh = loadBlueprintHexMesh(params.mesh_file_first, MeshLoadPolicy::OneDomainPerRankOrReplicated, @@ -814,21 +814,21 @@ int main(int argc, char** argv) switch(params.policy) { -#ifdef AXOM_RUNTIME_POLICY_USE_OPENMP +#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) case RuntimePolicy::omp: query_mesh = loadBlueprintHexMesh(params.mesh_file_second, MeshLoadPolicy::Replicated, params.isVerbose()); break; #endif -#ifdef AXOM_RUNTIME_POLICY_USE_CUDA +#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) case RuntimePolicy::cuda: query_mesh = loadBlueprintHexMesh(params.mesh_file_second, MeshLoadPolicy::Replicated, params.isVerbose()); break; #endif -#ifdef AXOM_RUNTIME_POLICY_USE_HIP +#if defined(AXOM_RUNTIME_POLICY_USE_HIP) case RuntimePolicy::hip: query_mesh = loadBlueprintHexMesh(params.mesh_file_second, MeshLoadPolicy::Replicated, @@ -853,17 +853,17 @@ int main(int argc, char** argv) { switch(params.policy) { -#ifdef AXOM_RUNTIME_POLICY_USE_OPENMP +#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) case RuntimePolicy::omp: candidatePairs = findCandidatesBVH(insert_mesh, query_mesh); break; #endif -#ifdef AXOM_RUNTIME_POLICY_USE_CUDA +#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) case RuntimePolicy::cuda: candidatePairs = findCandidatesBVH(insert_mesh, query_mesh); break; #endif -#ifdef AXOM_RUNTIME_POLICY_USE_HIP +#if defined(AXOM_RUNTIME_POLICY_USE_HIP) case RuntimePolicy::hip: candidatePairs = findCandidatesBVH(insert_mesh, query_mesh); break; @@ -878,17 +878,17 @@ int main(int argc, char** argv) { switch(params.policy) { -#ifdef AXOM_RUNTIME_POLICY_USE_OPENMP +#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) case RuntimePolicy::omp: candidatePairs = findCandidatesImplicit(insert_mesh, query_mesh, params.resolution); break; #endif -#ifdef AXOM_RUNTIME_POLICY_USE_CUDA +#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) case RuntimePolicy::cuda: candidatePairs = findCandidatesImplicit(insert_mesh, query_mesh, params.resolution); break; #endif -#ifdef AXOM_RUNTIME_POLICY_USE_HIP +#if defined(AXOM_RUNTIME_POLICY_USE_HIP) case RuntimePolicy::hip: candidatePairs = findCandidatesImplicit(insert_mesh, query_mesh, params.resolution); break; diff --git a/src/axom/quest/examples/quest_winding_number_2d.cpp b/src/axom/quest/examples/quest_winding_number_2d.cpp index efbd49ae0c..d3f5380d05 100644 --- a/src/axom/quest/examples/quest_winding_number_2d.cpp +++ b/src/axom/quest/examples/quest_winding_number_2d.cpp @@ -353,7 +353,7 @@ class Input std::stringstream pol_sstr; pol_sstr << "Set runtime policy method."; pol_sstr << "\nSet to 'seq' or 0 to use the RAJA sequential policy."; -#ifdef AXOM_RUNTIME_POLICY_USE_OPENMP +#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) pol_sstr << "\nSet to 'omp' or 1 to use the RAJA OpenMP policy."; #endif diff --git a/src/axom/quest/examples/quest_winding_number_3d.cpp b/src/axom/quest/examples/quest_winding_number_3d.cpp index a89008f197..863e53fd2a 100644 --- a/src/axom/quest/examples/quest_winding_number_3d.cpp +++ b/src/axom/quest/examples/quest_winding_number_3d.cpp @@ -167,7 +167,7 @@ class Input std::stringstream pol_sstr; pol_sstr << "Set runtime policy method."; pol_sstr << "\nSet to 'seq' or 0 to use the RAJA sequential policy."; -#ifdef AXOM_RUNTIME_POLICY_USE_OPENMP +#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) pol_sstr << "\nSet to 'omp' or 1 to use the RAJA OpenMP policy."; #endif From cca711c7846941aaef09978f17ebed68d5b32772 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 1 Jul 2026 10:17:25 -0700 Subject: [PATCH 568/986] Removed axom_execution_policies function and define a global AXOM_EXECUTION_POLICIES list that we can use everywhere. --- src/CMakeLists.txt | 10 ++++++++ src/axom/core/examples/CMakeLists.txt | 3 +-- .../concentric_circles/CMakeLists.txt | 4 +--- .../mir/examples/heavily_mixed/CMakeLists.txt | 4 +--- .../examples/tutorial_simple/CMakeLists.txt | 3 +-- src/axom/primal/examples/CMakeLists.txt | 4 +--- src/axom/quest/examples/CMakeLists.txt | 23 ++++--------------- src/axom/quest/tests/CMakeLists.txt | 7 ++---- src/cmake/AxomMacros.cmake | 17 -------------- src/tools/CMakeLists.txt | 4 +--- 10 files changed, 23 insertions(+), 56 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 08eabf44f7..118e617b0b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -132,6 +132,16 @@ if(AXOM_ENABLE_CUDA AND ${CMAKE_VERSION} VERSION_LESS 3.18.0) message(FATAL_ERROR "Axom requires CMake version 3.18.0+ when CUDA is enabled.") endif() +# Build the execution policy list once so all subdirectories can reuse it. +set(AXOM_EXECUTION_POLICIES "seq") +if(RAJA_FOUND) + blt_list_append(TO AXOM_EXECUTION_POLICIES ELEMENTS "omp" IF AXOM_ENABLE_OPENMP) + if(AXOM_USE_UMPIRE) + blt_list_append(TO AXOM_EXECUTION_POLICIES ELEMENTS "cuda" IF AXOM_ENABLE_CUDA) + blt_list_append(TO AXOM_EXECUTION_POLICIES ELEMENTS "hip" IF AXOM_ENABLE_HIP) + endif() +endif() + axom_add_code_checks() #------------------------------------------------------------------------------ diff --git a/src/axom/core/examples/CMakeLists.txt b/src/axom/core/examples/CMakeLists.txt index 4e2a0c220d..2bfd40eb63 100644 --- a/src/axom/core/examples/CMakeLists.txt +++ b/src/axom/core/examples/CMakeLists.txt @@ -72,8 +72,7 @@ axom_add_executable( if(AXOM_ENABLE_TESTS) # Run the core array performance example on N ranks for each enabled policy - axom_execution_policies(_policies) - foreach(_pol ${_policies}) + foreach(_pol ${AXOM_EXECUTION_POLICIES}) # sets the number of threads for the omp policy; leave empty for non-omp policies set(_num_threads) if(_pol STREQUAL "omp") diff --git a/src/axom/mir/examples/concentric_circles/CMakeLists.txt b/src/axom/mir/examples/concentric_circles/CMakeLists.txt index cd58e54764..df0d6805ca 100644 --- a/src/axom/mir/examples/concentric_circles/CMakeLists.txt +++ b/src/axom/mir/examples/concentric_circles/CMakeLists.txt @@ -32,9 +32,7 @@ if(AXOM_ENABLE_MPI AND CONDUIT_RELAY_MPI_ENABLED) endif() if(AXOM_ENABLE_TESTS) - axom_execution_policies(_policies) - - foreach(_policy ${_policies}) + foreach(_policy ${AXOM_EXECUTION_POLICIES}) # sets the number of threads for the omp policy; leave empty for non-omp policies set(_num_threads) if(_policy STREQUAL "omp") diff --git a/src/axom/mir/examples/heavily_mixed/CMakeLists.txt b/src/axom/mir/examples/heavily_mixed/CMakeLists.txt index 321e91b900..244ddcc00c 100644 --- a/src/axom/mir/examples/heavily_mixed/CMakeLists.txt +++ b/src/axom/mir/examples/heavily_mixed/CMakeLists.txt @@ -21,9 +21,7 @@ axom_add_executable( ) if(AXOM_ENABLE_TESTS) - axom_execution_policies(_policies) - - foreach(_policy ${_policies}) + foreach(_policy ${AXOM_EXECUTION_POLICIES}) set(_num_threads) if(_policy STREQUAL "omp") set(_num_threads ${AXOM_TEST_NUM_OMP_THREADS}) diff --git a/src/axom/mir/examples/tutorial_simple/CMakeLists.txt b/src/axom/mir/examples/tutorial_simple/CMakeLists.txt index a56be26cc3..709e212ba8 100644 --- a/src/axom/mir/examples/tutorial_simple/CMakeLists.txt +++ b/src/axom/mir/examples/tutorial_simple/CMakeLists.txt @@ -41,9 +41,8 @@ axom_add_executable( if(AXOM_ENABLE_TESTS) set(_test_numbers 1 2 3 4 5) - axom_execution_policies(_policies) - foreach(_policy ${_policies}) + foreach(_policy ${AXOM_EXECUTION_POLICIES}) set(_num_threads) if(_policy STREQUAL "omp") set(_num_threads ${AXOM_TEST_NUM_OMP_THREADS}) diff --git a/src/axom/primal/examples/CMakeLists.txt b/src/axom/primal/examples/CMakeLists.txt index 9598849cd1..74d4eda668 100644 --- a/src/axom/primal/examples/CMakeLists.txt +++ b/src/axom/primal/examples/CMakeLists.txt @@ -46,9 +46,7 @@ if (RAJA_FOUND AND UMPIRE_FOUND) # Run the hex_tet_volume_ex example with different raja policies # to check for completion - axom_execution_policies(_policies) - - foreach(_policy ${_policies}) + foreach(_policy ${AXOM_EXECUTION_POLICIES}) # sets the number of threads for the omp policy; leave empty for non-omp policies set(_num_threads) if(_policy STREQUAL "omp") diff --git a/src/axom/quest/examples/CMakeLists.txt b/src/axom/quest/examples/CMakeLists.txt index 8f43a50a48..bb88387758 100644 --- a/src/axom/quest/examples/CMakeLists.txt +++ b/src/axom/quest/examples/CMakeLists.txt @@ -52,9 +52,7 @@ if(AXOM_ENABLE_TESTS AND AXOM_DATA_DIR) set(input_file "${AXOM_DATA_DIR}/quest/unit_cube.stl") - axom_execution_policies(_policies) - - foreach(_policy ${_policies}) + foreach(_policy ${AXOM_EXECUTION_POLICIES}) # sets the number of threads for the omp policy; leave empty for non-omp policies set(_num_threads) if(_policy STREQUAL "omp") @@ -104,10 +102,8 @@ if (AXOM_ENABLE_MPI AND CONDUIT_FOUND AND UMPIRE_FOUND) set(_methods "bvh" "implicit") - axom_execution_policies(_policies) - foreach(_method ${_methods}) - foreach(_policy ${_policies}) + foreach(_policy ${AXOM_EXECUTION_POLICIES}) # sets the number of threads for the omp policy; leave empty for non-omp policies set(_num_threads) if(_policy STREQUAL "omp") @@ -230,9 +226,7 @@ if(AXOM_ENABLE_MPI AND MFEM_FOUND AND MFEM_USE_MPI # Tests 2D c2c contour if(C2C_FOUND) - axom_execution_policies(_policies) - - foreach(_policy ${_policies}) + foreach(_policy ${AXOM_EXECUTION_POLICIES}) # sets the number of threads for the omp policy; leave empty for non-omp policies set(_num_threads) if(_policy STREQUAL "omp") @@ -256,7 +250,6 @@ if(AXOM_ENABLE_MPI AND MFEM_FOUND AND MFEM_USE_MPI PASS_REGULAR_EXPRESSION "Volume of material 'steel' is 1.57") endforeach() - unset(_policies) endif() # Test 2D MFEM mesh (linearize contours) @@ -343,15 +336,13 @@ if(AXOM_ENABLE_MPI AND AXOM_ENABLE_SIDRE AND HDF5_FOUND) set(_nranks 3) # Run the distributed closest point example on N ranks for each enabled policy - axom_execution_policies(_policies) - # Non-zero empty-rank probability tests domain underloading case set(_meshes "mdmesh.2x1" "mdmesh.2x3" "mdmesh.2x2x1" "mdmeshg.2x2x1") # The mdmesh.* files were generated by these commands: # src/tools/gen-multidom-structured-mesh.py -ml=0,0 -mu=2,2 -ms=100,100 -dc=2,1 -o mdmesh.2x1 # src/tools/gen-multidom-structured-mesh.py -ml=0,0 -mu=2,2 -ms=100,100 -dc=2,3 -o mdmesh.2x3 # src/tools/gen-multidom-structured-mesh.py -ml=0,0,0 -mu=2,2,2 -ms=20,20,15 -dc=2,2,1 -o mdmeshg.2x2x1 --strided - foreach(_pol ${_policies}) + foreach(_pol ${AXOM_EXECUTION_POLICIES}) # sets the number of threads for the omp policy; leave empty for non-omp policies set(_num_threads) if(_pol STREQUAL "omp") @@ -394,7 +385,6 @@ if(AXOM_ENABLE_MPI AND AXOM_ENABLE_SIDRE AND HDF5_FOUND) unset(optional_dependency) unset(_nranks) - unset(_policies) unset(_test) endif() endif() @@ -425,8 +415,6 @@ if(CONDUIT_FOUND) endif() # Run the marching cubes example on N ranks for each enabled policy - axom_execution_policies(_policies) - # Non-zero empty-rank probability tests domain underloading case set(_meshes "mdmesh.2x1" "mdmesh.2x3" "mdmesh.2x2x1" "mdmeshg.2x2x1") # The amc.* files were generated by these commands: @@ -434,7 +422,7 @@ if(CONDUIT_FOUND) # src/tools/gen-multidom-structured-mesh.py -ml=0,0 -mu=2,2 -ms=100,100 -dc=2,3 -o mdmesh.2x3 # src/tools/gen-multidom-structured-mesh.py -ml=0,0,0 -mu=2,2,2 -ms=20,20,15 -dc=2,2,1 -o mdmesh.2x2x1 # src/tools/gen-multidom-structured-mesh.py -ml=0,0,0 -mu=2,2,2 -ms=20,20,15 -dc=2,2,1 -o mdmeshg.2x2x1 --strided - foreach(_pol ${_policies}) + foreach(_pol ${AXOM_EXECUTION_POLICIES}) # sets the number of threads for the omp policy; leave empty for non-omp policies set(_num_threads) if(_pol STREQUAL "omp") @@ -477,7 +465,6 @@ if(CONDUIT_FOUND) endforeach() unset(_nranks) - unset(_policies) unset(_test) endif() diff --git a/src/axom/quest/tests/CMakeLists.txt b/src/axom/quest/tests/CMakeLists.txt index 7117ab56a5..15cdf4b608 100644 --- a/src/axom/quest/tests/CMakeLists.txt +++ b/src/axom/quest/tests/CMakeLists.txt @@ -199,8 +199,7 @@ if(ENABLE_MPI AND MFEM_FOUND AND MFEM_USE_MPI endforeach() # Invoke the intersection shaper test program as many tests that run a subset of the tests. - axom_execution_policies(_policies) - foreach(_pol ${_policies}) + foreach(_pol ${AXOM_EXECUTION_POLICIES}) # sets the number of threads set(_num_threads) if(_pol STREQUAL "omp") @@ -260,12 +259,10 @@ if(AXOM_ENABLE_KLEE AND AXOM_ENABLE_SIDRE AND CONDUIT_FOUND AND RAJA_FOUND AND A set(_nranks 1) # Run the geometry clipping test on with each enabled policy - axom_execution_policies(_policies) - set(_testgeoms "plane" "tet" "hex" "sphere" "tetmesh" "cupmesh" "sor" "cyl" "cone" "plane,hex,tetmesh") set(_meshTypes "bpSidre" "bpConduit") foreach(_meshType ${_meshTypes}) - foreach(_policy ${_policies}) + foreach(_policy ${AXOM_EXECUTION_POLICIES}) # sets the number of threads for the omp policy; leave empty for non-omp policies set(_num_threads) if(_policy STREQUAL "omp") diff --git a/src/cmake/AxomMacros.cmake b/src/cmake/AxomMacros.cmake index a34510172f..02a0cd6d0e 100644 --- a/src/cmake/AxomMacros.cmake +++ b/src/cmake/AxomMacros.cmake @@ -687,20 +687,3 @@ macro(axom_force_release_for_target tgt) endif() target_compile_definitions(${tgt} PRIVATE NDEBUG) endmacro(axom_force_release_for_target) - -##------------------------------------------------------------------------------ -## axom_execution_policies -## -## This function returns the list of valid execution policies for the build. -##------------------------------------------------------------------------------ -function(axom_execution_policies out_var) - set(_policies "seq") - if(RAJA_FOUND) - blt_list_append(TO _policies ELEMENTS "omp" IF AXOM_ENABLE_OPENMP) - if(AXOM_USE_UMPIRE) - blt_list_append(TO _policies ELEMENTS "cuda" IF AXOM_ENABLE_CUDA) - blt_list_append(TO _policies ELEMENTS "hip" IF AXOM_ENABLE_HIP) - endif() - endif() - set(${out_var} "${_policies}" PARENT_SCOPE) -endfunction() diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index 2089b0a21e..9f3f442dc6 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -81,10 +81,8 @@ if(AXOM_ENABLE_QUEST) set(_methods "bvh" "implicit" "uniform") - axom_execution_policies(_policies) - foreach(_method ${_methods}) - foreach(_policy ${_policies}) + foreach(_policy ${AXOM_EXECUTION_POLICIES}) set(_testname "mesh_tester_${_method}_${_policy}") axom_add_test( From d5ebf151a47266d3b4add6711f8990a9c1f8cda2 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 1 Jul 2026 10:44:13 -0700 Subject: [PATCH 569/986] Changed release notes. --- RELEASE-NOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index fdc9db3b5d..bda70dd0b1 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -53,7 +53,7 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ ### Changed - Updates CMake code check targets to only use checked in files (via `git ls-files`, when available) -- CMake: Reuses `axom_execution_policies()` to simplify Axom build logic +- CMake: Simplified execution policy logic through use of `AXOM_EXECUTION_POLICIES` variable. - Core: Optimization for axom::Array indirection -- since the stride is always 1, we can remove the runtime multiplication - Python: Removes build and test dependencies from `run_python_with_axom.sh` wrapper script From 96096691da3b273aceeac539031527869ab710d1 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 1 Jul 2026 10:45:52 -0700 Subject: [PATCH 570/986] Fixed CMake build so a header gets installed. --- src/axom/core/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/axom/core/CMakeLists.txt b/src/axom/core/CMakeLists.txt index fd2832829d..017ed9675f 100644 --- a/src/axom/core/CMakeLists.txt +++ b/src/axom/core/CMakeLists.txt @@ -25,6 +25,7 @@ set(core_headers utilities/About.hpp utilities/Annotations.hpp utilities/BitUtilities.hpp + utilities/Checksum.hpp utilities/CommandLineUtilities.hpp utilities/FileUtilities.hpp utilities/RAII.hpp From c682300e54ec3e481f00bb30eda6abf976b3cf5f Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 1 Jul 2026 11:47:20 -0700 Subject: [PATCH 571/986] Move some logic to CMakeBasics.cmake --- src/CMakeLists.txt | 10 ---------- src/cmake/CMakeBasics.cmake | 12 ++++++++++++ 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 118e617b0b..08eabf44f7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -132,16 +132,6 @@ if(AXOM_ENABLE_CUDA AND ${CMAKE_VERSION} VERSION_LESS 3.18.0) message(FATAL_ERROR "Axom requires CMake version 3.18.0+ when CUDA is enabled.") endif() -# Build the execution policy list once so all subdirectories can reuse it. -set(AXOM_EXECUTION_POLICIES "seq") -if(RAJA_FOUND) - blt_list_append(TO AXOM_EXECUTION_POLICIES ELEMENTS "omp" IF AXOM_ENABLE_OPENMP) - if(AXOM_USE_UMPIRE) - blt_list_append(TO AXOM_EXECUTION_POLICIES ELEMENTS "cuda" IF AXOM_ENABLE_CUDA) - blt_list_append(TO AXOM_EXECUTION_POLICIES ELEMENTS "hip" IF AXOM_ENABLE_HIP) - endif() -endif() - axom_add_code_checks() #------------------------------------------------------------------------------ diff --git a/src/cmake/CMakeBasics.cmake b/src/cmake/CMakeBasics.cmake index a97fbf573b..833893f13f 100644 --- a/src/cmake/CMakeBasics.cmake +++ b/src/cmake/CMakeBasics.cmake @@ -228,6 +228,18 @@ if(COMPILER_FAMILY_IS_MSVC) set( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /bigobj" ) endif() +#------------------------------------------------------------------------------ +# Setup execution policies variable (so all subdirectories can reuse it) +#------------------------------------------------------------------------------ +set(AXOM_EXECUTION_POLICIES "seq") +if(RAJA_FOUND) + blt_list_append(TO AXOM_EXECUTION_POLICIES ELEMENTS "omp" IF AXOM_ENABLE_OPENMP) + if(AXOM_USE_UMPIRE) + blt_list_append(TO AXOM_EXECUTION_POLICIES ELEMENTS "cuda" IF AXOM_ENABLE_CUDA) + blt_list_append(TO AXOM_EXECUTION_POLICIES ELEMENTS "hip" IF AXOM_ENABLE_HIP) + endif() +endif() + #------------------------------------------------------------------------------ # Configure our CTest Dashboard Driver Script #------------------------------------------------------------------------------ From f2d2ba512233d3aab1c76f4b77364b1b151ead53 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 1 Jul 2026 13:51:41 -0700 Subject: [PATCH 572/986] Added comments --- src/axom/core/utilities/Checksum.hpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/axom/core/utilities/Checksum.hpp b/src/axom/core/utilities/Checksum.hpp index dfb45f136b..1e246041e4 100644 --- a/src/axom/core/utilities/Checksum.hpp +++ b/src/axom/core/utilities/Checksum.hpp @@ -22,6 +22,10 @@ using ScaleFactor = double; /*! * \brief Calculate and return checksum for data arrays. * + * \tparam DataGetter A callable type that retrieves an element in a sequence of data via operator(). + * The operator takes an index in the range [0,len). The returned value must be + * castable to CheckSum. + * * \param view The view that contains the data. * * \note Adapted from RAJAPerf at https://github.com/LLNL/RAJAPerf/blob/cda42470851fff2b7c8e6a9b5b11ab83f33a5a07/src/common/DataUtils.cpp#L598-L622 @@ -50,7 +54,8 @@ inline CheckSum calculateChecksum(DataGetter data, axom::IndexType len) * \brief Calculate and return checksum. * * \param value The value we want to checksum. - * + * \param scaleFactor An optional scale factor that acts as a weight on a returned checksum value. + * When adding multiple checksums together, different weights can be passed. * \return A CheckSum value for the array view. */ template @@ -64,6 +69,8 @@ inline CheckSum checksum(T value, const ScaleFactor scaleFactor = ScaleFactor {1 * \brief Calculate and return checksum for an array view. * * \param view The view that contains the data we want to checksum. + * \param scaleFactor An optional scale factor that acts as a weight on a returned checksum value. + * When adding multiple checksums together, different weights can be passed. * * \return A CheckSum value for the array view. */ From 7a9d04f5840d9fd6f67a9999a9fe61ef4028967d Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 1 Jul 2026 14:02:05 -0700 Subject: [PATCH 573/986] Addressed performance concern in the conduit::Node Group::checksum. --- src/axom/sidre/core/Group.cpp | 19 +++++++--- src/axom/sidre/core/Group.hpp | 4 ++- src/axom/sidre/tests/sidre_checksum.cpp | 47 +++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 5 deletions(-) diff --git a/src/axom/sidre/core/Group.cpp b/src/axom/sidre/core/Group.cpp index af0891e7f8..7c0e2e14f5 100644 --- a/src/axom/sidre/core/Group.cpp +++ b/src/axom/sidre/core/Group.cpp @@ -2897,12 +2897,16 @@ axom::utilities::CheckSum Group::checksum() const return cs; } -void Group::checksum(conduit::Node& n_checksum) const +axom::utilities::CheckSum Group::checksum(conduit::Node& n_checksum) const { // Always emit a fresh snapshot so callers can safely reuse the same node. n_checksum.reset(); n_checksum.set(conduit::DataType::object()); - n_checksum["checksum"] = static_cast(checksum()); + conduit::Node &groupCS = n_checksum["checksum"]; + + // Checksum the name + axom::ArrayView nameView(m_name.data(), m_name.size()); + auto cs = axom::utilities::checksum(nameView); // Add the checksums of the views and groups. if(getNumViews() > 0) @@ -2911,7 +2915,9 @@ void Group::checksum(conduit::Node& n_checksum) const for(const auto& view : this->views()) { conduit::Node& n_view = n_views[view.getName()]; - n_view["checksum"] = static_cast(view.checksum()); + const auto vcs = view.checksum(); + cs += vcs; + n_view["checksum"] = static_cast(vcs); } } if(getNumGroups() > 0) @@ -2920,9 +2926,14 @@ void Group::checksum(conduit::Node& n_checksum) const for(const auto& group : this->groups()) { conduit::Node& n_group = n_groups[group.getName()]; - group.checksum(n_group); + cs += group.checksum(n_group); } } + + // Set the overall checksum for the group based on the combined checksums. + groupCS.set(static_cast(cs)); + + return cs; } /* diff --git a/src/axom/sidre/core/Group.hpp b/src/axom/sidre/core/Group.hpp index c5f7e5bc22..2e21ad3c8c 100644 --- a/src/axom/sidre/core/Group.hpp +++ b/src/axom/sidre/core/Group.hpp @@ -1908,8 +1908,10 @@ class Group * \endcode * * \param n_checksum The output node that receives the checksum metadata. + * + * \return A CheckSum of the group. */ - void checksum(conduit::Node& n_checksum) const; + axom::utilities::CheckSum checksum(conduit::Node& n_checksum) const; private: DISABLE_DEFAULT_CTOR(Group); diff --git a/src/axom/sidre/tests/sidre_checksum.cpp b/src/axom/sidre/tests/sidre_checksum.cpp index ea903d8711..a45ef4ac57 100644 --- a/src/axom/sidre/tests/sidre_checksum.cpp +++ b/src/axom/sidre/tests/sidre_checksum.cpp @@ -11,6 +11,10 @@ #include "conduit_node.hpp" +#include +#include +#include +#include #include namespace @@ -362,3 +366,46 @@ TEST(sidre_checksum, group_checksum_metadata_mirrors_structure_and_replaces_stal ASSERT_TRUE(metadata.has_path("views/status/checksum")); EXPECT_FALSE(metadata.has_child("groups")); } + +TEST(sidre_checksum, group_checksum_metadata_matches_nonconduit_checksum_with_tolerance) +{ + axom::sidre::DataStore datastore; + axom::sidre::Group* root = datastore.getRoot(); + axom::sidre::Group* group = root->createGroup("group0"); + axom::sidre::Group* child = group->createGroup("group1"); + axom::sidre::Group* grandchild = child->createGroup("group2"); + axom::sidre::View* rootView = group->createViewAndAllocate("root_values", axom::sidre::INT_ID, 3); + axom::sidre::View* childView = + child->createViewAndAllocate("child_values", axom::sidre::DOUBLE_ID, 2); + axom::sidre::View* grandchildView = + grandchild->createViewAndAllocate("grandchild_values", axom::sidre::INT64_ID, 4); + + int* rootData = rootView->getData(); + rootData[0] = 2; + rootData[1] = 3; + rootData[2] = 5; + + double* childData = childView->getData(); + childData[0] = 1.25; + childData[1] = -4.5; + + std::int64_t* grandchildData = grandchildView->getData(); + grandchildData[0] = 8; + grandchildData[1] = 13; + grandchildData[2] = 21; + grandchildData[3] = 34; + + const auto checksum = group->checksum(); + + conduit::Node metadata; + const auto metadataChecksum = group->checksum(metadata); + + EXPECT_EQ(checksum, metadataChecksum); + + const double serializedChecksum = metadata["checksum"].to_double(); + const double expectedChecksum = static_cast(checksum); + const double tolerance = + std::numeric_limits::epsilon() * std::max(1.0, std::abs(expectedChecksum)) * 8.0; + + EXPECT_NEAR(expectedChecksum, serializedChecksum, tolerance); +} From 3fb0d5ca50f8fab1d0e854efb68b2229ea308239 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 1 Jul 2026 14:45:56 -0700 Subject: [PATCH 574/986] Comments, checksum changes, and make view attributes optional in checksum. --- src/axom/core/utilities/Checksum.hpp | 4 +-- src/axom/sidre/core/ConduitMemory.cpp | 48 ++++++++++++++++---------- src/axom/sidre/core/ConduitMemory.hpp | 15 ++++++++ src/axom/sidre/core/Group.cpp | 12 +++---- src/axom/sidre/core/Group.hpp | 7 ++-- src/axom/sidre/core/View.cpp | 15 ++++---- src/axom/sidre/core/View.hpp | 4 ++- src/axom/sidre/nanobind_sidre.cpp | 17 +++++---- src/axom/sidre/tests/sidre_group_Py.py | 14 ++++++++ 9 files changed, 94 insertions(+), 42 deletions(-) diff --git a/src/axom/core/utilities/Checksum.hpp b/src/axom/core/utilities/Checksum.hpp index 1e246041e4..259f87ea7d 100644 --- a/src/axom/core/utilities/Checksum.hpp +++ b/src/axom/core/utilities/Checksum.hpp @@ -62,7 +62,7 @@ template inline CheckSum checksum(T value, const ScaleFactor scaleFactor = ScaleFactor {1}) { return calculateChecksum([=](axom::IndexType) { return static_cast(value); }, 1) * - scaleFactor; + static_cast(scaleFactor); } /*! @@ -79,7 +79,7 @@ inline CheckSum checksum(axom::ArrayView view, const ScaleFactor scaleFactor { return calculateChecksum([=](axom::IndexType i) { return static_cast(view[i]); }, view.size()) * - scaleFactor; + static_cast(scaleFactor); } } // namespace utilities diff --git a/src/axom/sidre/core/ConduitMemory.cpp b/src/axom/sidre/core/ConduitMemory.cpp index ade761f429..5fcf05b6d1 100644 --- a/src/axom/sidre/core/ConduitMemory.cpp +++ b/src/axom/sidre/core/ConduitMemory.cpp @@ -9,17 +9,6 @@ namespace axom { namespace sidre { -namespace -{ -axom::utilities::CheckSum checksumNodeMetadata(const conduit::Node& n) -{ - auto cs = axom::utilities::CheckSum {0}; - cs += axom::utilities::checksum(static_cast(n.dtype().id()), 2.0); - cs += axom::utilities::checksum(n.number_of_children(), 3.0); - cs += axom::utilities::checksum(n.dtype().number_of_elements(), 5.0); - return cs; -} -} // namespace std::map> ConduitMemory::s_axomToInstance; std::map> ConduitMemory::s_conduitToInstance; @@ -295,19 +284,29 @@ const ConduitMemory& ConduitMemory::instanceForConduitId(conduit::index_t condui return *it->second; } +namespace +{ +axom::utilities::CheckSum checksumNodeMetadata(const conduit::Node& n) +{ + auto cs = axom::utilities::CheckSum {0}; + // Give each metadata component different coefficients so they contribute differently + cs += axom::utilities::checksum(static_cast(n.dtype().id()), 2.0); + cs += axom::utilities::checksum(n.number_of_children(), 3.0); + cs += axom::utilities::checksum(n.dtype().number_of_elements(), 5.0); + return cs; +} + /// Operate on conduit::DataArray so we can handle strided data. template -axom::utilities::CheckSum checksumArray( - const conduit::DataArray& arr, - const axom::utilities::ScaleFactor scaleFactor = axom::utilities::ScaleFactor {1}) +axom::utilities::CheckSum checksumArray(const conduit::DataArray& arr) { return axom::utilities::calculateChecksum( [=](axom::IndexType i) { return static_cast(arr[i]); }, - arr.number_of_elements()) * - scaleFactor; + arr.number_of_elements()); } -axom::utilities::CheckSum checksum(const conduit::Node& n, bool include_name) +/// Compute a checksum on the conduit tree \a n. +axom::utilities::CheckSum checksumImpl(const conduit::Node& n, bool include_name) { auto cs = axom::utilities::CheckSum {0}; if(include_name) @@ -325,7 +324,7 @@ axom::utilities::CheckSum checksum(const conduit::Node& n, bool include_name) { cs += axom::utilities::calculateChecksum( [&](axom::IndexType i) -> axom::utilities::CheckSum { - return checksum(n[static_cast(i)], true); + return checksumImpl(n[static_cast(i)], true); }, n.number_of_children()); } @@ -333,7 +332,7 @@ axom::utilities::CheckSum checksum(const conduit::Node& n, bool include_name) { for(conduit::index_t i = 0; i < n.number_of_children(); i++) { - cs += checksum(n[i], true); + cs += checksumImpl(n[i], true); } } } @@ -396,6 +395,17 @@ axom::utilities::CheckSum checksum(const conduit::Node& n, bool include_name) } return cs; } +} // end namespace + +axom::utilities::CheckSum checksum(const conduit::Node& n, axom::utilities::ScaleFactor scaleFactor, bool include_name) +{ + return checksumImpl(n, include_name) * static_cast(scaleFactor); +} + +axom::utilities::CheckSum checksum(const conduit::Node& n, bool include_name) +{ + return checksumImpl(n, include_name); +} } // end namespace sidre } // end namespace axom diff --git a/src/axom/sidre/core/ConduitMemory.hpp b/src/axom/sidre/core/ConduitMemory.hpp index a560f7bc54..08ed2f4791 100644 --- a/src/axom/sidre/core/ConduitMemory.hpp +++ b/src/axom/sidre/core/ConduitMemory.hpp @@ -173,6 +173,21 @@ struct ConduitMemory */ axom::utilities::CheckSum checksum(const conduit::Node& n, bool include_name = true); +/*! + * \brief Checksum the structure and contents of a Conduit node and scale the result. + * + * \param n The node being checksummed. + * \param scaleFactor An optional scale factor that acts as a weight on a returned checksum value. + * When adding multiple checksums together, different weights can be passed. + * \param include_name If true, include this node's own name in the checksum. + * Child node names are always included during recursive traversal. + * + * \return A checksum of the node. + */ +axom::utilities::CheckSum checksum(const conduit::Node& n, + axom::utilities::ScaleFactor scaleFactor, + bool include_name); + } /* end namespace sidre */ } /* end namespace axom */ diff --git a/src/axom/sidre/core/Group.cpp b/src/axom/sidre/core/Group.cpp index 7c0e2e14f5..53b60ecae0 100644 --- a/src/axom/sidre/core/Group.cpp +++ b/src/axom/sidre/core/Group.cpp @@ -2879,7 +2879,7 @@ bool Group::importConduitTreeExternal(conduit::Node& node, bool preserve_content return success; } -axom::utilities::CheckSum Group::checksum() const +axom::utilities::CheckSum Group::checksum(bool includeAttributes) const { // Checksum the name axom::ArrayView nameView(m_name.data(), m_name.size()); @@ -2888,16 +2888,16 @@ axom::utilities::CheckSum Group::checksum() const // Add the checksums of the views and groups. for(const auto& view : this->views()) { - cs += view.checksum(); + cs += view.checksum(includeAttributes); } for(const auto& group : this->groups()) { - cs += group.checksum(); + cs += group.checksum(includeAttributes); } return cs; } -axom::utilities::CheckSum Group::checksum(conduit::Node& n_checksum) const +axom::utilities::CheckSum Group::checksum(conduit::Node& n_checksum, bool includeAttributes) const { // Always emit a fresh snapshot so callers can safely reuse the same node. n_checksum.reset(); @@ -2915,7 +2915,7 @@ axom::utilities::CheckSum Group::checksum(conduit::Node& n_checksum) const for(const auto& view : this->views()) { conduit::Node& n_view = n_views[view.getName()]; - const auto vcs = view.checksum(); + const auto vcs = view.checksum(includeAttributes); cs += vcs; n_view["checksum"] = static_cast(vcs); } @@ -2926,7 +2926,7 @@ axom::utilities::CheckSum Group::checksum(conduit::Node& n_checksum) const for(const auto& group : this->groups()) { conduit::Node& n_group = n_groups[group.getName()]; - cs += group.checksum(n_group); + cs += group.checksum(n_group, includeAttributes); } } diff --git a/src/axom/sidre/core/Group.hpp b/src/axom/sidre/core/Group.hpp index 2e21ad3c8c..95151d671e 100644 --- a/src/axom/sidre/core/Group.hpp +++ b/src/axom/sidre/core/Group.hpp @@ -1853,9 +1853,11 @@ class Group * \brief Traverse the group and all of its descendents and compute a checksum * of the structure as well as the contents of the views. * + * \param includeAttributes Whether to include view attributes in the checksum. + * * \return A CheckSum of the group. */ - axom::utilities::CheckSum checksum() const; + axom::utilities::CheckSum checksum(bool includeAttributes = true) const; /*! * \brief Store checksum metadata for this group subtree in a Conduit node. @@ -1908,10 +1910,11 @@ class Group * \endcode * * \param n_checksum The output node that receives the checksum metadata. + * \param includeAttributes Whether to include view attributes in the checksum. * * \return A CheckSum of the group. */ - axom::utilities::CheckSum checksum(conduit::Node& n_checksum) const; + axom::utilities::CheckSum checksum(conduit::Node& n_checksum, bool includeAttributes = true) const; private: DISABLE_DEFAULT_CTOR(Group); diff --git a/src/axom/sidre/core/View.cpp b/src/axom/sidre/core/View.cpp index 0b44db7e01..b60d45dc1d 100644 --- a/src/axom/sidre/core/View.cpp +++ b/src/axom/sidre/core/View.cpp @@ -2082,7 +2082,7 @@ int View::getValidAllocatorId(int allocId) return axom::INVALID_ALLOCATOR_ID; } -axom::utilities::CheckSum View::checksum() const +axom::utilities::CheckSum View::checksum(bool includeAttributes) const { // Checksum the name axom::ArrayView nameView(m_name.data(), m_name.size()); @@ -2091,13 +2091,16 @@ axom::utilities::CheckSum View::checksum() const // Checksum the view contents without double-counting the view name. cs += axom::sidre::checksum(m_node, false); - for(IndexType attrIdx = getFirstValidAttrValueIndex(); attrIdx != InvalidIndex; - attrIdx = getNextValidAttrValueIndex(attrIdx)) + if(includeAttributes) { - const Attribute* attr = getAttribute(attrIdx); - if(attr != nullptr) + for(IndexType attrIdx = getFirstValidAttrValueIndex(); attrIdx != InvalidIndex; + attrIdx = getNextValidAttrValueIndex(attrIdx)) { - cs += checksumNamedNode(attr->getName(), getAttributeNodeRef(attr)); + const Attribute* attr = getAttribute(attrIdx); + if(attr != nullptr) + { + cs += checksumNamedNode(attr->getName(), getAttributeNodeRef(attr)); + } } } diff --git a/src/axom/sidre/core/View.hpp b/src/axom/sidre/core/View.hpp index 11e961c6d3..58dab11460 100644 --- a/src/axom/sidre/core/View.hpp +++ b/src/axom/sidre/core/View.hpp @@ -1370,9 +1370,11 @@ class View /*! * \brief Compute a checksum for the view. * + * \param includeAttributes Whether to include attributes in the checksum. + * * \return A CheckSum of the view. */ - axom::utilities::CheckSum checksum() const; + axom::utilities::CheckSum checksum(bool includeAttributes = true) const; private: DISABLE_DEFAULT_CTOR(View); diff --git a/src/axom/sidre/nanobind_sidre.cpp b/src/axom/sidre/nanobind_sidre.cpp index b20ebd8f76..98d3ed8309 100644 --- a/src/axom/sidre/nanobind_sidre.cpp +++ b/src/axom/sidre/nanobind_sidre.cpp @@ -796,7 +796,10 @@ NB_MODULE(pysidre, m_sidre) .def("getPathName", &View::getPathName, "Return the full path of the View object, including its name.") - .def("checksum", &View::checksum, "Return a checksum for the View's name, metadata, and data.") + .def("checksum", + &View::checksum, + "Return a checksum for the View's name, metadata, and data.", + nb::arg("includeAttributes") = true) .def("getOwningGroup", nb::overload_cast<>(&View::getOwningGroup), nb::rv_policy::reference_internal, @@ -1164,16 +1167,18 @@ NB_MODULE(pysidre, m_sidre) .def("getPath", &Group::getPath, "Return path of Group object, not including its name.") .def("getPathName", &Group::getPathName, "Return full path of Group object, including its name.") .def("checksum", - nb::overload_cast<>(&Group::checksum, nb::const_), - "Return a checksum for the Group's name, child structure, and descendant view data.") + nb::overload_cast(&Group::checksum, nb::const_), + "Return a checksum for the Group's name, child structure, and descendant view data.", + nb::arg("includeAttributes") = true) .def( "checksum", - [](const Group& self, nb::object& o) { + [](const Group& self, nb::object& o, bool includeAttributes) { conduit::Node& cpp_node = nbObjectToNode(o); - self.checksum(cpp_node); + self.checksum(cpp_node, includeAttributes); }, "Populate a Conduit node with checksum metadata for this Group hierarchy.", - nb::arg("n_checksum")) + nb::arg("n_checksum"), + nb::arg("includeAttributes") = true) .def("getParent", nb::overload_cast<>(&Group::getParent, nb::const_), nb::rv_policy::reference_internal, diff --git a/src/axom/sidre/tests/sidre_group_Py.py b/src/axom/sidre/tests/sidre_group_Py.py index a11e8f4e53..9fbf58335d 100644 --- a/src/axom/sidre/tests/sidre_group_Py.py +++ b/src/axom/sidre/tests/sidre_group_Py.py @@ -251,6 +251,20 @@ def test_group_and_view_checksum(): assert metadata.has_path("groups/child/checksum") assert float(metadata["checksum"]) == float(group.checksum()) + ds.createAttributeString("units", "none") + attrless_view_checksum = float(view.checksum(False)) + attrless_group_checksum = float(group.checksum(False)) + + assert view.setAttributeString("units", "counts") + assert float(view.checksum()) != mutated_view_checksum + assert float(group.checksum()) != mutated_group_checksum + assert float(view.checksum(False)) == attrless_view_checksum + assert float(group.checksum(False)) == attrless_group_checksum + + metadata_without_attributes = Node() + group.checksum(metadata_without_attributes, False) + assert float(metadata_without_attributes["checksum"]) == float(group.checksum(False)) + #------------------------------------------------------------------------------ # createView, hasView(), getView(), destroyView() with path strings From b745f6ab842d7b063455f73a0cfc35b1984ac5b8 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 1 Jul 2026 14:54:41 -0700 Subject: [PATCH 575/986] Check that 2 separate Nodes checksum the same when they contain the same structure/data. --- src/axom/sidre/tests/sidre_checksum.cpp | 26 +++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/axom/sidre/tests/sidre_checksum.cpp b/src/axom/sidre/tests/sidre_checksum.cpp index a45ef4ac57..6a969a3d46 100644 --- a/src/axom/sidre/tests/sidre_checksum.cpp +++ b/src/axom/sidre/tests/sidre_checksum.cpp @@ -100,6 +100,32 @@ TEST(sidre_checksum, conduit_checksum_changes_for_tree_structure_and_leaf_data) EXPECT_NE(checksum, axom::sidre::checksum(addedChild)); } +TEST(sidre_checksum, conduit_checksum_matches_for_equivalent_hierarchical_nodes) +{ + int firstIds[] = {10, 20, 30}; + int secondIds[] = {10, 20, 30}; + double firstValues[] = {1.5, -2.25, 3.75}; + double secondValues[] = {1.5, -2.25, 3.75}; + + conduit::Node first; + first["fields/ids"].set_external(firstIds, 3); + first["fields/values"].set_external(firstValues, 3); + first["state/message"].set("alpha"); + first["state/count"].set(42); + first["nested/pi"].set(3.14159); + + conduit::Node second; + second["fields/ids"].set_external(secondIds, 3); + second["fields/values"].set_external(secondValues, 3); + second["state/message"].set("alpha"); + second["state/count"].set(42); + second["nested/pi"].set(3.14159); + + EXPECT_NE(firstIds, secondIds); + EXPECT_NE(firstValues, secondValues); + EXPECT_EQ(axom::sidre::checksum(first), axom::sidre::checksum(second)); +} + TEST(sidre_checksum, conduit_checksum_supports_empty_object_and_list_nodes) { conduit::Node emptyObject; From c10dc4e4b268969629a9ee1cbb7737b5042826b8 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 1 Jul 2026 14:57:35 -0700 Subject: [PATCH 576/986] Test view.checksum(true) != view.checksum(false) when the view has attributes. --- src/axom/sidre/tests/sidre_checksum.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/axom/sidre/tests/sidre_checksum.cpp b/src/axom/sidre/tests/sidre_checksum.cpp index 6a969a3d46..bfb3e35aaa 100644 --- a/src/axom/sidre/tests/sidre_checksum.cpp +++ b/src/axom/sidre/tests/sidre_checksum.cpp @@ -248,11 +248,15 @@ TEST(sidre_checksum, view_and_group_checksum_change_on_attribute_mutation) ASSERT_TRUE(view->setAttributeString(category, "alpha")); const auto stringAttrChecksum = view->checksum(); EXPECT_NE(baseChecksum, stringAttrChecksum); + EXPECT_NE(view->checksum(true), view->checksum(false)); + EXPECT_EQ(baseChecksum, view->checksum(false)); EXPECT_NE(baseGroupChecksum, group->checksum()); ASSERT_TRUE(view->setAttributeScalar(priority, 3)); const auto scalarAttrChecksum = view->checksum(); EXPECT_NE(stringAttrChecksum, scalarAttrChecksum); + EXPECT_NE(view->checksum(true), view->checksum(false)); + EXPECT_EQ(baseChecksum, view->checksum(false)); ASSERT_TRUE(view->setAttributeString(category, "beta")); const auto mutatedAttrChecksum = view->checksum(); From 83363177256f495a588b219df54fba5745c2c5ed Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 1 Jul 2026 15:08:43 -0700 Subject: [PATCH 577/986] make style --- src/axom/sidre/core/ConduitMemory.cpp | 10 ++++++---- src/axom/sidre/core/Group.cpp | 2 +- src/axom/sidre/core/Group.hpp | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/axom/sidre/core/ConduitMemory.cpp b/src/axom/sidre/core/ConduitMemory.cpp index 5fcf05b6d1..9ed3f08177 100644 --- a/src/axom/sidre/core/ConduitMemory.cpp +++ b/src/axom/sidre/core/ConduitMemory.cpp @@ -301,8 +301,8 @@ template axom::utilities::CheckSum checksumArray(const conduit::DataArray& arr) { return axom::utilities::calculateChecksum( - [=](axom::IndexType i) { return static_cast(arr[i]); }, - arr.number_of_elements()); + [=](axom::IndexType i) { return static_cast(arr[i]); }, + arr.number_of_elements()); } /// Compute a checksum on the conduit tree \a n. @@ -395,9 +395,11 @@ axom::utilities::CheckSum checksumImpl(const conduit::Node& n, bool include_name } return cs; } -} // end namespace +} // end namespace -axom::utilities::CheckSum checksum(const conduit::Node& n, axom::utilities::ScaleFactor scaleFactor, bool include_name) +axom::utilities::CheckSum checksum(const conduit::Node& n, + axom::utilities::ScaleFactor scaleFactor, + bool include_name) { return checksumImpl(n, include_name) * static_cast(scaleFactor); } diff --git a/src/axom/sidre/core/Group.cpp b/src/axom/sidre/core/Group.cpp index 53b60ecae0..c9e35ac0f5 100644 --- a/src/axom/sidre/core/Group.cpp +++ b/src/axom/sidre/core/Group.cpp @@ -2902,7 +2902,7 @@ axom::utilities::CheckSum Group::checksum(conduit::Node& n_checksum, bool includ // Always emit a fresh snapshot so callers can safely reuse the same node. n_checksum.reset(); n_checksum.set(conduit::DataType::object()); - conduit::Node &groupCS = n_checksum["checksum"]; + conduit::Node& groupCS = n_checksum["checksum"]; // Checksum the name axom::ArrayView nameView(m_name.data(), m_name.size()); diff --git a/src/axom/sidre/core/Group.hpp b/src/axom/sidre/core/Group.hpp index 95151d671e..13e8ab8874 100644 --- a/src/axom/sidre/core/Group.hpp +++ b/src/axom/sidre/core/Group.hpp @@ -1914,7 +1914,7 @@ class Group * * \return A CheckSum of the group. */ - axom::utilities::CheckSum checksum(conduit::Node& n_checksum, bool includeAttributes = true) const; + axom::utilities::CheckSum checksum(conduit::Node& n_checksum, bool includeAttributes = true) const; private: DISABLE_DEFAULT_CTOR(Group); From a19d20b576b9a73c30afe3e1fbe6ae12d2d5b97b Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 17 Jun 2026 22:15:24 -0700 Subject: [PATCH 578/986] Removes some hand-rolled metaprogramming and idioms that are no longer needed * Replace bespoke void_t with std::void_t * Removes `NotImplementedYet` struct * Removes util::TypeToString -- it only supported int and double --- src/axom/slam/FieldRegistry.hpp | 6 ++-- src/axom/slam/Set.hpp | 3 -- src/axom/slam/Utilities.hpp | 34 ++----------------- src/axom/slam/policies/PolicyTraits.hpp | 34 ++++--------------- src/axom/slam/tests/slam_map_BivariateMap.cpp | 8 ++--- src/axom/slam/tests/slam_map_Map.cpp | 7 ++-- src/axom/slam/tests/slam_map_SubMap.cpp | 4 +-- 7 files changed, 20 insertions(+), 76 deletions(-) diff --git a/src/axom/slam/FieldRegistry.hpp b/src/axom/slam/FieldRegistry.hpp index c4639716d3..47d7e94530 100644 --- a/src/axom/slam/FieldRegistry.hpp +++ b/src/axom/slam/FieldRegistry.hpp @@ -14,6 +14,7 @@ #include "axom/slam/Map.hpp" #include +#include namespace axom { @@ -106,10 +107,7 @@ class FieldRegistry } private: - std::string dataTypeString() const - { - return axom::slam::util::TypeToString::to_string(); - } + std::string dataTypeString() const { return typeid(DataType).name(); } inline void verifyFieldsKey(const KeyType& AXOM_DEBUG_PARAM(key)) const { diff --git a/src/axom/slam/Set.hpp b/src/axom/slam/Set.hpp index e8670794fc..de566320d4 100644 --- a/src/axom/slam/Set.hpp +++ b/src/axom/slam/Set.hpp @@ -129,9 +129,6 @@ class Set * with some value for not containing the element */ virtual bool contains(const SetElement & elt) const = 0; - - //Possible other useful functions - void reset(size_type) { throw NotImplementedException(); } #endif private: diff --git a/src/axom/slam/Utilities.hpp b/src/axom/slam/Utilities.hpp index a6f9994e55..284636fb4c 100644 --- a/src/axom/slam/Utilities.hpp +++ b/src/axom/slam/Utilities.hpp @@ -14,46 +14,19 @@ #include "axom/core.hpp" #include "axom/fmt.hpp" -#include #include -namespace axom -{ -namespace slam +namespace axom::slam { using DefaultPositionType = axom::IndexType; using DefaultElementType = axom::IndexType; -class NotImplementedException -{ }; - namespace util { -/** \brief A helper class to print the name of a few types */ -template -struct TypeToString -{ - static std::string to_string() { return ""; } -}; - -/** \brief A helper class to print the name of integers as 'int' */ -template <> -struct TypeToString -{ - static std::string to_string() { return "int"; } -}; - -/** \brief A helper class to print the name of doubles as 'double' */ -template <> -struct TypeToString -{ - static std::string to_string() { return "double"; } -}; - /** * \brief A simple 3D point class similar to primal's point class, * with some basic Point/Vector functionalities - * + * * \note This is needed for internal testing in slam (which does not depend on primal) */ template @@ -160,8 +133,7 @@ T distance(const Point3& pt1, const Point3& pt2) } } // end namespace util -} // end namespace slam -} // end namespace axom +} // end namespace axom::slam /// Overload to format an axom::slam::util::Point3 using fmt template diff --git a/src/axom/slam/policies/PolicyTraits.hpp b/src/axom/slam/policies/PolicyTraits.hpp index 0f44a40252..501800f490 100644 --- a/src/axom/slam/policies/PolicyTraits.hpp +++ b/src/axom/slam/policies/PolicyTraits.hpp @@ -18,13 +18,10 @@ #include "axom/slam/policies/SizePolicies.hpp" #include "axom/slam/policies/StridePolicies.hpp" +#include #include -namespace axom -{ -namespace slam -{ -namespace policies +namespace axom::slam::policies { /** * \brief Definition of a type trait to adapt a StridePolicy into a SizePolicy @@ -97,24 +94,10 @@ struct EmptySetTraits> } }; -} // end namespace policies +} // end namespace axom::slam::policies -namespace traits -{ -// Implementation of void_t (from C++17) with bug fix for -// earlier versions of gcc. Credit: https://stackoverflow.com/a/35754473 -namespace void_details -{ -template -struct make_void +namespace axom::slam::traits { - using type = void; -}; -} // namespace void_details - -template -using void_t = typename void_details::make_void::type; - ///\name has_relation_ptr traits class ///@{ @@ -123,7 +106,7 @@ struct has_relation_ptr : std::false_type { }; template -struct has_relation_ptr().getRelation())>> : std::true_type +struct has_relation_ptr().getRelation())>> : std::true_type { }; ///@} @@ -136,14 +119,11 @@ struct indices_use_indirection : std::true_type { }; template -struct indices_use_indirection> : std::false_type +struct indices_use_indirection> : std::false_type { }; ///@} -} // end namespace traits - -} // end namespace slam -} // end namespace axom +} // end namespace axom::slam::traits #endif // SLAM_POLICY_TRAITS_H_ diff --git a/src/axom/slam/tests/slam_map_BivariateMap.cpp b/src/axom/slam/tests/slam_map_BivariateMap.cpp index 22e79f0344..df7ef4030b 100644 --- a/src/axom/slam/tests/slam_map_BivariateMap.cpp +++ b/src/axom/slam/tests/slam_map_BivariateMap.cpp @@ -89,7 +89,7 @@ void constructAndTestCartesianMap(int stride) EXPECT_EQ(s.size(), MAX_SET_SIZE1 * MAX_SET_SIZE2); EXPECT_TRUE(s.isValid()); - SLIC_INFO("Creating " << slam::util::TypeToString::to_string() << " map on the set "); + SLIC_INFO("Creating map on the set "); BMapType m(&s, static_cast(0), stride); @@ -217,7 +217,7 @@ void constructAndTestRelationSetMap(int stride) EXPECT_EQ(indice_size, s.totalSize()); EXPECT_TRUE(s.isValid(true)); - SLIC_INFO("Creating " << slam::util::TypeToString::to_string() << " map on the set "); + SLIC_INFO("Creating map on the set "); MapType m(&s, (T)0, stride); @@ -338,7 +338,7 @@ void constructAndTestBivariateMapIterator(int stride) EXPECT_EQ(s.size(), MAX_SET_SIZE1 * MAX_SET_SIZE2); EXPECT_TRUE(s.isValid()); - SLIC_INFO("Creating " << slam::util::TypeToString::to_string() << " map on the set "); + SLIC_INFO("Creating map on the set "); MapType m(&s, 0.0, stride); EXPECT_TRUE(m.isValid()); EXPECT_EQ(s.size(), m.totalSize()); @@ -458,7 +458,7 @@ void testScopedCopyBehavior(int stride) EXPECT_EQ(s.size(), MAX_SET_SIZE1 * MAX_SET_SIZE2); EXPECT_TRUE(s.isValid()); - SLIC_INFO("Creating " << slam::util::TypeToString::to_string() << " map on the set "); + SLIC_INFO("Creating map on the set "); BMapType m; { diff --git a/src/axom/slam/tests/slam_map_Map.cpp b/src/axom/slam/tests/slam_map_Map.cpp index 466919816e..37e57a6fd3 100644 --- a/src/axom/slam/tests/slam_map_Map.cpp +++ b/src/axom/slam/tests/slam_map_Map.cpp @@ -60,7 +60,7 @@ bool constructAndTestMap() EXPECT_EQ(s.size(), MAX_SET_SIZE); EXPECT_TRUE(s.isValid()); - SLIC_INFO("Creating " << slam::util::TypeToString::to_string() << " map on the set "); + SLIC_INFO("Creating map on the set "); slam::Map m(&s); EXPECT_TRUE(m.isValid()); @@ -155,8 +155,7 @@ void constructAndTestMapWithStride(int stride) EXPECT_EQ(s.size(), MAX_SET_SIZE); EXPECT_TRUE(s.isValid()); - SLIC_INFO("\nCreating " << slam::util::TypeToString::to_string() << " map with stride " - << stride << " on the set "); + SLIC_INFO("\nCreating map with stride " << stride << " on the set "); using MapType = slam::Map, StrideType>; MapType m(&s, 0, stride); @@ -211,7 +210,7 @@ TEST(slam_map, iterate) SetType s(MAX_SET_SIZE); EXPECT_TRUE(s.isValid()); - SLIC_INFO("Creating '" << slam::util::TypeToString::to_string() << "' map on the set "); + SLIC_INFO("Creating map on the set "); RealMap m(&s); EXPECT_TRUE(m.isValid()); diff --git a/src/axom/slam/tests/slam_map_SubMap.cpp b/src/axom/slam/tests/slam_map_SubMap.cpp index 995efbf015..2b02b09a8f 100644 --- a/src/axom/slam/tests/slam_map_SubMap.cpp +++ b/src/axom/slam/tests/slam_map_SubMap.cpp @@ -61,9 +61,7 @@ struct MapForTest , s(OrderedSetType::SetBuilder().size(size).data(&set_data)) , m(&s) { - SLIC_INFO("Initializing set of size " << s.size() << " and '" - << slam::util::TypeToString::to_string() - << "' map on the set "); + SLIC_INFO("Initializing set of size " << s.size() << " and map on the set "); for(auto i : s.positions()) { From 0a9cf0e67234315cd637b2d1b3d19d3d5eb86038 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 17 Jun 2026 22:34:24 -0700 Subject: [PATCH 579/986] slam: Convert SFINAE to if constexpr in IndirectionPolicies --- .../slam/policies/IndirectionPolicies.hpp | 88 ++++++++----------- 1 file changed, 39 insertions(+), 49 deletions(-) diff --git a/src/axom/slam/policies/IndirectionPolicies.hpp b/src/axom/slam/policies/IndirectionPolicies.hpp index 72ca958a5c..36110bf754 100644 --- a/src/axom/slam/policies/IndirectionPolicies.hpp +++ b/src/axom/slam/policies/IndirectionPolicies.hpp @@ -38,11 +38,7 @@ #include "axom/core/NumericLimits.hpp" #include "axom/slic/interface/slic.hpp" -namespace axom -{ -namespace slam -{ -namespace policies +namespace axom::slam::policies { namespace detail { @@ -81,68 +77,64 @@ struct IndexedIndirection : public BasePolicy bool verboseOutput = false) const; template - AXOM_HOST_DEVICE static inline std::enable_if_t getIndirection( - IndirectionRefType buf, - PositionType pos = 0) - { - return &buf[pos]; - } - - template - AXOM_HOST_DEVICE static inline std::enable_if_t getConstIndirection( - IndirectionConstRefType buf, - PositionType pos = 0) - { - return &buf[pos]; - } - - template - AXOM_HOST_DEVICE static inline std::enable_if_t getIndirection( - IndirectionRefType buf, - PositionType pos = 0) + AXOM_HOST_DEVICE static inline ResultPtr getIndirection(IndirectionRefType buf, PositionType pos = 0) { + if constexpr(DeviceEnable) + { + return &buf[pos]; + } + else + { #ifdef AXOM_DEVICE_CODE - AXOM_UNUSED_VAR(buf); - AXOM_UNUSED_VAR(pos); - SLIC_ASSERT_MSG( - false, - BasePolicy::Name << " -- Attempting to indirect on an unsupported indirection policy."); + AXOM_UNUSED_VAR(buf); + AXOM_UNUSED_VAR(pos); + SLIC_ASSERT_MSG( + false, + BasePolicy::Name << " -- Attempting to indirect on an unsupported indirection policy."); // Disable no-return warnings from device code #if defined(__CUDA_ARCH__) - __trap(); + __trap(); #elif defined(__HIP_DEVICE_COMPILE__) - abort(); + abort(); #endif - return nullptr; + return nullptr; #else - // Always return a value. - return &buf[pos]; + // Always return a value. + return &buf[pos]; #endif + } } template - AXOM_HOST_DEVICE static inline std::enable_if_t - getConstIndirection(IndirectionConstRefType buf, PositionType pos = 0) + AXOM_HOST_DEVICE static inline ConstResultPtr getConstIndirection(IndirectionConstRefType buf, + PositionType pos = 0) { + if constexpr(DeviceEnable) + { + return &buf[pos]; + } + else + { #ifdef AXOM_DEVICE_CODE - AXOM_UNUSED_VAR(buf); - AXOM_UNUSED_VAR(pos); - SLIC_ASSERT_MSG( - false, - BasePolicy::Name << " -- Attempting to indirect on an unsupported indirection policy."); + AXOM_UNUSED_VAR(buf); + AXOM_UNUSED_VAR(pos); + SLIC_ASSERT_MSG( + false, + BasePolicy::Name << " -- Attempting to indirect on an unsupported indirection policy."); // Disable no-return warnings from device code #if defined(__CUDA_ARCH__) - __trap(); + __trap(); #elif defined(__HIP_DEVICE_COMPILE__) - abort(); + abort(); #endif - return nullptr; + return nullptr; #else - // Always return a value. - return &buf[pos]; + // Always return a value. + return &buf[pos]; #endif + } } AXOM_HOST_DEVICE inline ConstIndirectionResult indirection(PositionType pos) const @@ -464,8 +456,6 @@ using ArrayViewIndirection = /// \} -} // end namespace policies -} // end namespace slam -} // end namespace axom +} // end namespace axom::slam::policies #endif // SLAM_POLICIES_INDIRECTION_H_ From 894a535000993a6a3266bcd275dff5038ae9d9bd Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 17 Jun 2026 22:49:56 -0700 Subject: [PATCH 580/986] slam: Inlines static nullset ... and removes OrderedSet.cpp since it had no other contents --- src/axom/slam/CMakeLists.txt | 1 - src/axom/slam/OrderedSet.cpp | 24 ------------------- src/axom/slam/policies/SubsettingPolicies.hpp | 15 ++++-------- 3 files changed, 4 insertions(+), 36 deletions(-) delete mode 100644 src/axom/slam/OrderedSet.cpp diff --git a/src/axom/slam/CMakeLists.txt b/src/axom/slam/CMakeLists.txt index ca94e5ef97..3e191f6f3b 100644 --- a/src/axom/slam/CMakeLists.txt +++ b/src/axom/slam/CMakeLists.txt @@ -76,7 +76,6 @@ set(slam_headers set(slam_sources # SRM Set sources BitSet.cpp - OrderedSet.cpp # SRM Relation sources diff --git a/src/axom/slam/OrderedSet.cpp b/src/axom/slam/OrderedSet.cpp deleted file mode 100644 index db3b1f19dc..0000000000 --- a/src/axom/slam/OrderedSet.cpp +++ /dev/null @@ -1,24 +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) - -/** - * \file OrderedSet.cpp - */ - -#include "OrderedSet.hpp" - -namespace axom -{ -namespace slam -{ -namespace policies -{ -const NullSet<> NoSubset::s_nullSet; -NullSet<> VirtualParentSubset::s_nullSet; - -} // namespace policies -} // namespace slam -} // namespace axom diff --git a/src/axom/slam/policies/SubsettingPolicies.hpp b/src/axom/slam/policies/SubsettingPolicies.hpp index ec012dc9ad..f85520a9b7 100644 --- a/src/axom/slam/policies/SubsettingPolicies.hpp +++ b/src/axom/slam/policies/SubsettingPolicies.hpp @@ -27,15 +27,10 @@ #include "axom/core/Macros.hpp" #include "axom/slam/NullSet.hpp" -#include "axom/export/slam.h" #include -namespace axom -{ -namespace slam -{ -namespace policies +namespace axom::slam::policies { /** * \name OrderedSet_Subsetting_Policies @@ -46,7 +41,7 @@ namespace policies struct NoSubset { - AXOM_SLAM_EXPORT static const NullSet<> s_nullSet; + inline static const NullSet<> s_nullSet {}; using ParentSetType = const Set<>; AXOM_HOST_DEVICE NoSubset() { } @@ -69,7 +64,7 @@ struct NoSubset struct VirtualParentSubset { - AXOM_SLAM_EXPORT static NullSet<> s_nullSet; + inline static NullSet<> s_nullSet {}; using ParentSetType = Set<>; @@ -203,8 +198,6 @@ struct ConcreteParentSubset /// \} -} // end namespace policies -} // end namespace slam -} // end namespace axom +} // end namespace axom::slam::policies #endif // SLAM_POLICIES_SUBSET_H_ From 1807e14a1d730e944f2375b96e45a9c33d01b5ce Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 17 Jun 2026 23:18:30 -0700 Subject: [PATCH 581/986] slam: Consolidates OffsetPolicy, StridePolicy and SizePolicy into ValuePolicy There was a lot of shared logic for the Runtime- and Compiletime policies associates with size, offset and stride. It is now shared. --- src/axom/slam/CMakeLists.txt | 1 + src/axom/slam/DynamicSet.hpp | 4 +- src/axom/slam/policies/OffsetPolicies.hpp | 68 ++++----- src/axom/slam/policies/SizePolicies.hpp | 86 ++++------- src/axom/slam/policies/StridePolicies.hpp | 78 +++++----- src/axom/slam/policies/ValuePolicies.hpp | 168 ++++++++++++++++++++++ 6 files changed, 258 insertions(+), 147 deletions(-) create mode 100644 src/axom/slam/policies/ValuePolicies.hpp diff --git a/src/axom/slam/CMakeLists.txt b/src/axom/slam/CMakeLists.txt index 3e191f6f3b..105a2bdc95 100644 --- a/src/axom/slam/CMakeLists.txt +++ b/src/axom/slam/CMakeLists.txt @@ -31,6 +31,7 @@ set(slam_headers #SRM policies policies/CardinalityPolicies.hpp + policies/ValuePolicies.hpp policies/SizePolicies.hpp policies/OffsetPolicies.hpp policies/StridePolicies.hpp diff --git a/src/axom/slam/DynamicSet.hpp b/src/axom/slam/DynamicSet.hpp index baa13030e3..cd6a805ea9 100644 --- a/src/axom/slam/DynamicSet.hpp +++ b/src/axom/slam/DynamicSet.hpp @@ -409,7 +409,7 @@ class DynamicSet : public Set, SizePolicy IndexType insert(ElementType val) { m_data.push_back(val); - SizePolicy::m_sz = m_data.size(); + SizePolicy::size() = m_data.size(); return size() - 1; }; @@ -431,7 +431,7 @@ class DynamicSet : public Set, SizePolicy */ void reset(PositionType sz) { - SizePolicy::m_sz = sz; + SizePolicy::size() = sz; fill_array_default(sz); } diff --git a/src/axom/slam/policies/OffsetPolicies.hpp b/src/axom/slam/policies/OffsetPolicies.hpp index bbd968c98f..0691e4122f 100644 --- a/src/axom/slam/policies/OffsetPolicies.hpp +++ b/src/axom/slam/policies/OffsetPolicies.hpp @@ -19,18 +19,19 @@ * * isValid() : bool -- indicates whether the Offset policy of the set is * valid [optional] * * operator(): IntType -- alternate accessor for the offset value + * + * \note The Runtime/CompileTime storage, constructors and validity checking + * are provided by the unified RuntimeValue/CompileTimeValue core in ValuePolicies.hpp. + * The policies below add only the named `offset()` accessor and the DEFAULT_VALUE member. */ #ifndef SLAM_POLICIES_OFFSET_H_ #define SLAM_POLICIES_OFFSET_H_ #include "axom/core/Macros.hpp" +#include "axom/slam/policies/ValuePolicies.hpp" -namespace axom -{ -namespace slam -{ -namespace policies +namespace axom::slam::policies { /** * \name OrderedSet_Offset_Policies @@ -39,67 +40,46 @@ namespace policies /// \{ -/** - * \brief A policy class for the offset in a set. The offset can be set at - * runtime. - */ +/// \brief A policy class for the offset in a set. The offset can be set at runtime. template -struct RuntimeOffset +struct RuntimeOffset : RuntimeValue> { +private: + using BaseType = RuntimeValue>; + public: static const IntType DEFAULT_VALUE; - AXOM_HOST_DEVICE RuntimeOffset(IntType off = DEFAULT_VALUE) : m_off(off) { } - - AXOM_HOST_DEVICE inline IntType offset() const { return m_off; } - AXOM_HOST_DEVICE inline IntType& offset() { return m_off; } - - inline IntType operator()() const { return offset(); } - inline IntType& operator()() { return offset(); } - - inline bool isValid(bool) const { return true; } + using BaseType::BaseType; -private: - IntType m_off; + AXOM_HOST_DEVICE inline IntType offset() const { return this->value(); } + AXOM_HOST_DEVICE inline IntType& offset() { return this->value(); } }; template -const IntType RuntimeOffset::DEFAULT_VALUE = IntType {}; +const IntType RuntimeOffset::DEFAULT_VALUE = OffsetTag::defaultValue(); -/** - * \brief A policy class for a compile-time known set offset - */ +/// \brief A policy class for a compile-time known set offset template -struct CompileTimeOffset +struct CompileTimeOffset : CompileTimeValue> { +private: + using BaseType = CompileTimeValue>; + +public: static constexpr IntType DEFAULT_VALUE = INT_VAL; - AXOM_HOST_DEVICE CompileTimeOffset(IntType val = DEFAULT_VALUE) - { - AXOM_UNUSED_VAR(val); - SLIC_ASSERT_MSG(val == INT_VAL, - "slam::CompileTimeOffset -- tried to initialize a compile time " - << "offset with value (" << val << " ) that differs from " - << "the template parameter of " << INT_VAL << "."); - } + using BaseType::BaseType; AXOM_HOST_DEVICE inline IntType offset() const { return INT_VAL; } - - inline IntType operator()() const { return offset(); } - - inline bool isValid(bool) const { return true; } }; -/** - * \brief A policy class for when we have no offset - */ +/// \brief A policy class for when we have no offset template using ZeroOffset = CompileTimeOffset; /// \} -} // end namespace policies -} // end namespace slam -} // end namespace axom +} // end namespace axom::slam::policies #endif // SLAM_POLICIES_OFFSET_H_ diff --git a/src/axom/slam/policies/SizePolicies.hpp b/src/axom/slam/policies/SizePolicies.hpp index f3a353ad48..aad459b65e 100644 --- a/src/axom/slam/policies/SizePolicies.hpp +++ b/src/axom/slam/policies/SizePolicies.hpp @@ -19,6 +19,10 @@ * valid * * [optional] * * operator(): IntType -- alternate accessor for the size value + * + * \note The Runtime/CompileTime storage, constructors and validity checking + * are provided by the unified RuntimeValue/CompileTimeValue core in ValuePolicies.hpp. + * The scalar policies below add only the named `size()` accessor, `empty()`, and the DEFAULT_VALUE member. */ #ifndef SLAM_POLICIES_SIZE_H_ @@ -26,12 +30,9 @@ #include "axom/core/Macros.hpp" #include "axom/slic.hpp" +#include "axom/slam/policies/ValuePolicies.hpp" -namespace axom -{ -namespace slam -{ -namespace policies +namespace axom::slam::policies { /** * \name OrderedSet_Size_Policies @@ -40,41 +41,28 @@ namespace policies /// \{ -/** - * \brief A policy class for the size of a set whose value can be set at - * runtime. - */ +/// \brief A policy class for the size of a set whose value can be set at runtime template -struct RuntimeSize +struct RuntimeSize : RuntimeValue> { +private: + using BaseType = RuntimeValue>; + public: static const IntType DEFAULT_VALUE; - AXOM_HOST_DEVICE RuntimeSize(IntType sz = DEFAULT_VALUE) : m_sz(sz) { } + using BaseType::BaseType; - AXOM_HOST_DEVICE inline IntType size() const { return m_sz; } - AXOM_HOST_DEVICE inline IntType& size() { return m_sz; } + AXOM_HOST_DEVICE inline IntType size() const { return this->value(); } + AXOM_HOST_DEVICE inline IntType& size() { return this->value(); } - inline IntType operator()() const { return size(); } - inline IntType& operator()() { return size(); } - - AXOM_HOST_DEVICE inline bool empty() const { return m_sz == IntType(); } - inline bool isValid(bool) const - { - // We do not (currently) allow negatively sized sets - return m_sz >= IntType(); - } - -protected: - IntType m_sz; + AXOM_HOST_DEVICE inline bool empty() const { return this->m_value == IntType(); } }; template -const IntType RuntimeSize::DEFAULT_VALUE = IntType {}; +const IntType RuntimeSize::DEFAULT_VALUE = SizeTag::defaultValue(); -/** - * \brief A policy class for the size of a set that can be modified at runtime - */ +/// \brief A policy class for the size of a set that can be modified at runtime template struct DynamicRuntimeSize : public RuntimeSize { @@ -87,7 +75,7 @@ struct DynamicRuntimeSize : public RuntimeSize { if(s >= 0) { - RuntimeSize::m_sz = s; + RuntimeSize::m_value = s; } } @@ -95,7 +83,7 @@ struct DynamicRuntimeSize : public RuntimeSize { if(s >= 0) { - RuntimeSize::m_sz += s; + RuntimeSize::m_value += s; } } @@ -103,43 +91,29 @@ struct DynamicRuntimeSize : public RuntimeSize { if(s >= 0) { - RuntimeSize::m_sz -= s; + RuntimeSize::m_value -= s; } } }; -/** - * \brief A policy class for a compile-time known set size - */ +/// \brief A policy class for a compile-time known set size template -struct CompileTimeSize +struct CompileTimeSize : CompileTimeValue> { +private: + using BaseType = CompileTimeValue>; + +public: static const IntType DEFAULT_VALUE = INT_VAL; - AXOM_HOST_DEVICE CompileTimeSize(IntType val = INT_VAL) - { - AXOM_UNUSED_VAR(val); - SLIC_ASSERT_MSG(val == INT_VAL, - "slam::CompileTimeSize -- tried to initialize a compile time size " - << "policy with value (" << val << " ) that differs from the " - << "template parameter of " << INT_VAL << "."); - } + using BaseType::BaseType; AXOM_HOST_DEVICE inline IntType size() const { return INT_VAL; } - inline IntType operator()() const { return size(); } - AXOM_HOST_DEVICE inline bool empty() const { return INT_VAL == IntType {}; } - inline bool isValid(bool) const - { - // We do not (currently) allow negatively sized sets - return INT_VAL >= IntType {}; - } }; -/** - * \brief A policy class for an empty set (no size) - */ +/// \brief A policy class for an empty set (no size) template struct ZeroSize { @@ -164,8 +138,6 @@ const IntType ZeroSize::DEFAULT_VALUE = IntType {}; /// \} -} // end namespace policies -} // end namespace slam -} // end namespace axom +} // end namespace axom::slam::policies #endif // SLAM_POLICIES_SIZE_H_ diff --git a/src/axom/slam/policies/StridePolicies.hpp b/src/axom/slam/policies/StridePolicies.hpp index 0afa9c8736..8795a9dc75 100644 --- a/src/axom/slam/policies/StridePolicies.hpp +++ b/src/axom/slam/policies/StridePolicies.hpp @@ -9,7 +9,7 @@ * * \brief Stride policies for SLAM * - * Stride policies are meant to represent the fixed distance between consecutive + * Stride policies are meant to represent the fixed distance between consecutive * elements of an OrderedSet * A valid stride policy must support the following interface: * * [required] @@ -21,18 +21,21 @@ * * operator(): IntType -- alternate accessor for the stride value * * \note All non-zero stride values are valid. + * + * \note The single-stride Runtime/CompileTime storage, constructors and validity checking + * are provided by the unified RuntimeValue/CompileTimeValue core in ValuePolicies.hpp. + * The scalar policies below add only the stride-specific surface (named `stride()`/`shape()` accessors, + * the dimensional typedefs, DEFAULT_VALUE/IS_COMPILE_TIME). + * MultiDimStride is a separate, inherently multi-dimensional policy and is unaffected. */ #ifndef SLAM_POLICIES_STRIDE_H_ #define SLAM_POLICIES_STRIDE_H_ #include "axom/core/Macros.hpp" +#include "axom/slam/policies/ValuePolicies.hpp" -namespace axom -{ -namespace slam -{ -namespace policies +namespace axom::slam::policies { /** * \name OrderedSet_Stride_Policies @@ -46,49 +49,47 @@ namespace policies * When using this class, the stride can be set at runtime. */ template -struct RuntimeStride +struct RuntimeStride : RuntimeValue> { +private: + using BaseType = RuntimeValue>; + public: - static const IntType DEFAULT_VALUE = IntType(1); + static const IntType DEFAULT_VALUE; static const bool IS_COMPILE_TIME = false; constexpr static int NumDims = 1; using IndexType = IntType; using ShapeType = IntType; - static constexpr IntType DefaultSize() { return DEFAULT_VALUE; } + static constexpr IntType DefaultSize() { return StrideTag::defaultValue(); } - AXOM_HOST_DEVICE RuntimeStride(IntType stride = DEFAULT_VALUE) : m_stride(stride) { } + using BaseType::BaseType; /// \brief Returns the stride between consecutive elements. - AXOM_HOST_DEVICE inline IntType stride() const { return m_stride; } + AXOM_HOST_DEVICE inline IntType stride() const { return this->value(); } + AXOM_HOST_DEVICE inline IntType& stride() { return this->value(); } + /*! * \brief Returns the shape of the inner data for a given stride. * This only has meaning when used with Map-based types. */ - AXOM_HOST_DEVICE inline IntType shape() const { return m_stride; } - AXOM_HOST_DEVICE inline IntType& stride() { return m_stride; } + AXOM_HOST_DEVICE inline IntType shape() const { return this->value(); } - void setStride(IntType str) { m_stride = str; } - - inline IntType operator()() const { return stride(); } - inline IntType& operator()() { return stride(); } - - /** All non-zero strides are valid */ - inline bool isValid(bool) const { return (m_stride != 0); } - - //inline bool hasStride() const { return m_stride != IntType(); } - -private: - IntType m_stride; + void setStride(IntType str) { this->m_value = str; } }; -/** - * \brief A policy class for a compile-time known stride - */ +template +const IntType RuntimeStride::DEFAULT_VALUE = StrideTag::defaultValue(); + +/// \brief A policy class for a compile-time known stride template -struct CompileTimeStride +struct CompileTimeStride : CompileTimeValue> { +private: + using BaseType = CompileTimeValue>; + +public: static const IntType DEFAULT_VALUE = INT_VAL; static const bool IS_COMPILE_TIME = true; constexpr static int NumDims = 1; @@ -98,11 +99,10 @@ struct CompileTimeStride static constexpr IntType DefaultSize() { return DEFAULT_VALUE; } - AXOM_HOST_DEVICE CompileTimeStride(IntType val = DEFAULT_VALUE) { setStride(val); } + using BaseType::BaseType; AXOM_HOST_DEVICE inline IntType stride() const { return INT_VAL; } AXOM_HOST_DEVICE inline IntType shape() const { return INT_VAL; } - inline IntType operator()() const { return stride(); } AXOM_HOST_DEVICE void setStride(IntType AXOM_DEBUG_PARAM(val)) { @@ -111,21 +111,13 @@ struct CompileTimeStride << " with value (" << val << " ) that differs from the template" << " parameter of " << INT_VAL << "."); } - - /** All non-zero strides are valid */ - inline bool isValid(bool) const { return (INT_VAL != 0); } }; -/** - * \brief A policy class for a set with stride one (i.e. the default stride) - */ +/// \brief A policy class for a set with stride one (i.e. the default stride) template using StrideOne = CompileTimeStride; -/** - * \brief A policy class for a set with multi-dimensional stride. Assumed - * layout is row-major. - */ +/// \brief A policy class for a set with multi-dimensional stride. Assumed layout is row-major. template struct MultiDimStride { @@ -173,8 +165,6 @@ struct MultiDimStride /// \} -} // end namespace policies -} // end namespace slam -} // end namespace axom +} // end namespace axom::slam::policies #endif // SLAM_POLICIES_STRIDE_H_ diff --git a/src/axom/slam/policies/ValuePolicies.hpp b/src/axom/slam/policies/ValuePolicies.hpp new file mode 100644 index 0000000000..e097700b2f --- /dev/null +++ b/src/axom/slam/policies/ValuePolicies.hpp @@ -0,0 +1,168 @@ +// 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) + +/** + * \file ValuePolicies.hpp + * + * \brief Unified storage core for SLAM's scalar value policies. + * + * Slam's Size, Stride and Offset policies each historically defined + * a near-identical `Runtime*` / `CompileTime*` pair, with the same: + * (a) single-integer storage + * (b) defaulted/asserting constructors, and + * (c) `operator()` accessors, + * and differing only in + * (a) the name of the named accessor (`size()` / `stride()` / `offset()`), + * (b) the validity predicate, and + * (c) the default value. + * + * This file factors that shared substrate into one `RuntimeValue` / `CompileTimeValue` family. + * + * A `Tag` type supplies the policy-specific knobs as static members: + * - `static constexpr IntType defaultValue();` -- the DEFAULT_VALUE + * - `static constexpr bool isValidValue(IntType);` -- validity predicate + * - `static constexpr const char* name();` -- used in assertion text + * + * The named accessors (`size()`, `stride()`, `offset()`) are *not* provided here; + * they live in the thin leaf policies in SizePolicies.hpp / StridePolicies.hpp / OffsetPolicies.hpp + * as one-line forwarders to `value()`, which keeps every existing call site spelling + * and signature unchanged while removing the storage/ctor/validity duplication. + * + * \note Multi-dimensional and dynamically-resizable policies (MultiDimStride, + * DynamicRuntimeSize) and the always-zero policies (ZeroSize) are not part of + * this scalar substrate and remain defined alongside their families. + */ + +#ifndef SLAM_POLICIES_VALUE_H_ +#define SLAM_POLICIES_VALUE_H_ + +#include "axom/core/Macros.hpp" +#include "axom/slic.hpp" + +namespace axom::slam::policies +{ +/// \name Value policy tags +/// \brief Tag types selecting the named-accessor family and the policy knobs +/// (default value, validity predicate) for the unified value-policy core. +/// \{ + +/*! + * \brief Tag for set-size value policies. + * \note Sizes may not be negative; the default size is zero. + */ +template +struct SizeTag +{ + AXOM_HOST_DEVICE static constexpr IntType defaultValue() { return IntType {}; } + AXOM_HOST_DEVICE static constexpr bool isValidValue(IntType v) { return v >= IntType {}; } + static constexpr const char* name() { return "slam::Size"; } +}; + +/*! + * \brief Tag for set-stride value policies. + * \note All non-zero strides are valid; the default stride is one. + */ +template +struct StrideTag +{ + AXOM_HOST_DEVICE static constexpr IntType defaultValue() { return IntType(1); } + AXOM_HOST_DEVICE static constexpr bool isValidValue(IntType v) { return v != IntType {}; } + static constexpr const char* name() { return "slam::Stride"; } +}; + +/*! + * \brief Tag for set-offset value policies. + * \note Every offset is valid; the default offset is zero. + */ +template +struct OffsetTag +{ + AXOM_HOST_DEVICE static constexpr IntType defaultValue() { return IntType {}; } + AXOM_HOST_DEVICE static constexpr bool isValidValue(IntType) { return true; } + static constexpr const char* name() { return "slam::Offset"; } +}; + +/// \} + +/*! + * \class RuntimeValue + * + * \brief Shared storage core for a runtime-settable scalar value policy. + * + * Stores a single \a IntType whose default is supplied by \a Tag. + * Provides the generic `value()` accessors (const and mutable), `operator()`, + * and an `isValid()` delegating to the tag's predicate. + * + * Leaf policies derive from this and add their named accessor (`size()` / `stride()` / `offset()`). + * + * \tparam Tag a value-policy tag (SizeTag / StrideTag / OffsetTag) + * carrying the IntType, default value and validity predicate. + */ +template +struct RuntimeValue +{ +public: + using TagType = Tag; + + AXOM_HOST_DEVICE RuntimeValue(decltype(Tag::defaultValue()) val = Tag::defaultValue()) + : m_value(val) + { } + + AXOM_HOST_DEVICE inline auto value() const { return m_value; } + AXOM_HOST_DEVICE inline auto& value() { return m_value; } + + inline auto operator()() const { return value(); } + inline auto& operator()() { return value(); } + + inline bool isValid(bool) const { return Tag::isValidValue(m_value); } + +protected: + decltype(Tag::defaultValue()) m_value; +}; + +/*! + * \class CompileTimeValue + * + * \brief Shared core for a compile-time-known scalar value policy. + * + * The value \a V is fixed at compile time. + * The (defaulted) constructor argument exists only to satisfy + * the uniform policy-construction interface and is asserted to match \a V. + * + * Provides the generic `value()` accessor, `operator()`, and an `isValid()` delegating to the tag's predicate. + * Leaf policies derive from this and add their named accessor. + * + * \tparam V the compile-time value (its type is the policy's IntType). + * \tparam Tag a value-policy tag carrying the default value and validity predicate. + */ +template +struct CompileTimeValue +{ +public: + using TagType = Tag; + using IntType = decltype(V); + + static constexpr IntType VALUE = V; + + AXOM_HOST_DEVICE CompileTimeValue(IntType val = V) + { + AXOM_UNUSED_VAR(val); + SLIC_ASSERT_MSG(val == V, + Tag::name() << " -- tried to initialize a compile-time value policy with " + << "value (" << val << ") that differs from the template " + << "parameter of " << V << "."); + } + + AXOM_HOST_DEVICE inline IntType value() const { return V; } + + inline IntType operator()() const { return value(); } + + inline bool isValid(bool) const { return Tag::isValidValue(V); } +}; + +} // end namespace axom::slam::policies + +#endif // SLAM_POLICIES_VALUE_H_ From 46a01378f1ddcf01ccf018a07c4ea790f9f14a34 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 18 Jun 2026 10:53:22 -0700 Subject: [PATCH 582/986] Tags several slam functions with [[nodiscard]] This should avoid ignoring values that were computed. Misc: Fixes some formatting issues with doxygen comments. --- src/axom/slam/BitSet.hpp | 43 +++++------- src/axom/slam/BivariateMap.hpp | 73 ++++++-------------- src/axom/slam/BivariateSet.hpp | 29 +++----- src/axom/slam/DynamicMap.hpp | 8 +-- src/axom/slam/DynamicSet.hpp | 13 ++-- src/axom/slam/Map.hpp | 51 +++++--------- src/axom/slam/MapBase.hpp | 4 +- src/axom/slam/OrderedSet.hpp | 23 +++--- src/axom/slam/ProductSet.hpp | 43 +++++------- src/axom/slam/Relation.hpp | 10 ++- src/axom/slam/RelationSet.hpp | 27 +++----- src/axom/slam/Set.hpp | 37 ++++------ src/axom/slam/StaticRelation.hpp | 2 +- src/axom/slam/SubMap.hpp | 30 +++----- src/axom/slam/tests/slam_set_DynamicSet.cpp | 2 +- src/axom/slam/tests/slam_set_PositionSet.cpp | 2 +- src/axom/slam/tests/slam_set_RangeSet.cpp | 4 +- 17 files changed, 148 insertions(+), 253 deletions(-) diff --git a/src/axom/slam/BitSet.hpp b/src/axom/slam/BitSet.hpp index c33ac6fc64..13ef45e1aa 100644 --- a/src/axom/slam/BitSet.hpp +++ b/src/axom/slam/BitSet.hpp @@ -88,16 +88,12 @@ BitSet operator-(const BitSet& lhs, const BitSet& rhs); * * This class supports bitwise manipulation operations (e.g. set intersection, * union and difference) on an ordered set of bits. The class has a similar - * interface to std::bitset and boost::dynamic_bitset, but with the following - * differences: + * interface to std::bitset and boost::dynamic_bitset, but with the following differences: * - The size of the bitset is supplied at runtime in the class constructor. - * However, BitSet does not currently support changing the size after - * construction. + * However, BitSet does not currently support changing the size after construction. * - We do not support the random access operation ( operator[](index) ). - * The value of individual bits can be checked using the test function - * (e.g. bset.test(i) ) - * - There is no support for directly initializing the bits in the bitset - * (e.g. via strings). + * The value of individual bits can be checked using the test function (e.g. bset.test(i) ) + * - There is no support for directly initializing the bits in the bitset (e.g. via strings). * * The individual bits in the bitset are packed into a contiguous array of * Words (an unsigned integer type). Many bitset operations such as count() @@ -221,20 +217,18 @@ class BitSet /** * \brief Finds the index of the first bit that is set in the bitset * - * \return The index of the first set bit, - * or BitSet::npos if no bits are set + * \return The index of the first set bit, or BitSet::npos if no bits are set */ - Index find_first() const; + [[nodiscard]] Index find_first() const; /** * \brief Finds the index of the next set bit in the bitset after \a idx * * \param idx The starting index - * \return The index of the first set bit after index \a idx - * or BitSet::npos if none can be found + * \return The index of the first set bit after index \a idx or BitSet::npos if none can be found * \note Will also return BitSet::npos if \a idx is BitSet::npos */ - Index find_next(Index idx) const; + [[nodiscard]] Index find_next(Index idx) const; /// @} @@ -243,10 +237,10 @@ class BitSet /// @{ /** \brief Returns the cardinality of the bitset */ - AXOM_HOST_DEVICE int size() const { return m_numBits; } + [[nodiscard]] AXOM_HOST_DEVICE int size() const { return m_numBits; } /** \brief Returns the number of bits that are set */ - int count() const; + [[nodiscard]] int count() const; /** \brief Clears all bits in the bitset */ void clear(); @@ -263,10 +257,9 @@ class BitSet * \return True if the bitset is valid, false otherwise * * A bitset is valid if it has sufficient storage for size() bits. - * If we have storage for more than size() bits, none of these additional - * bits are set. + * If we have storage for more than size() bits, none of these additional bits are set. */ - bool isValid() const; + [[nodiscard]] bool isValid() const; /// @} @@ -340,8 +333,7 @@ class BitSet * \brief Gets the index of the word containing bit at index \a idx * * \param idx The index of the word containing the desired bit - * \param checkIndexValid Option to enable bounds checking to ensure - * that \a idx is within range [0, size() ) + * \param checkIndexValid Option to enable bounds checking to ensure that \a idx is within range [0, size() ) */ AXOM_HOST_DEVICE Word& getWord(Index idx, bool checkIndexValid = true) @@ -372,8 +364,7 @@ class BitSet } /** - * \brief Returns a bitmask for the desired bit within the word - * containing \a idx + * \brief Returns a bitmask for the desired bit within the word containing \a idx * * \param idx The index of the desired bit */ @@ -407,11 +398,9 @@ class BitSet } /** - * \brief Predicate to determine if we need special processing - * for the final word of the bitset + * \brief Predicate to determine if we need special processing for the final word of the bitset * - * The last word is full when the bitset has exactly - * m_words * BitsPerWord bits + * The last word is full when the bitset has exactly m_words * BitsPerWord bits */ bool isLastWordFull() const { diff --git a/src/axom/slam/BivariateMap.hpp b/src/axom/slam/BivariateMap.hpp index e800901439..34eec57d09 100644 --- a/src/axom/slam/BivariateMap.hpp +++ b/src/axom/slam/BivariateMap.hpp @@ -237,8 +237,7 @@ class BivariateMap : public policies::MapInterfaceth component of the ith * element, where `setIndex = i * numComp() + j`. @@ -299,8 +297,7 @@ class BivariateMap : public policies::MapInterfacegetElements(s1); } - /** - * \brief Search for the FlatIndex of an element given its DenseIndex in the - * BivariateSet. - */ + /// \brief Search for the FlatIndex of an element given its DenseIndex in the BivariateSet. AXOM_HOST_DEVICE inline SetPosition flatIndex(SetPosition s1, SetPosition s2) const { return set()->findElementFlatIndex(s1, s2); @@ -503,7 +497,7 @@ class BivariateMap : public policies::MapInterfaceisValid(verboseOutput) && m_map.isValid(verboseOutput); } @@ -578,8 +572,7 @@ typename BivariateMap::NullBivariateSetType c /** * \class BivariateMapIterator - * \brief An iterator type for a BivariateMap, iterating via its - * ElementFlatIndex. + * \brief An iterator type for a BivariateMap, iterating via its ElementFlatIndex. * * This iterator class iterates over all elements in the associated map. */ @@ -606,18 +599,14 @@ class BivariateMap::FlatIterator static constexpr PositionType INVALID_POS = -2; public: - /** - * \brief Construct a new BivariateMap Iterator given an ElementFlatIndex - */ + /// \brief Construct a new BivariateMap Iterator given an ElementFlatIndex AXOM_HOST_DEVICE FlatIterator(BivariateMapPtr sMap, PositionType pos) : IterBase(pos) , m_map(sMap) , m_bsetIterator(m_map->set(), pos / m_map->numComp()) { } - /** - * \brief Returns the current map element pointed to by the iterator. - */ + /// \brief Returns the current map element pointed to by the iterator. AXOM_SUPPRESS_HD_WARN AXOM_HOST_DEVICE DataRefType operator*() const { @@ -626,21 +615,16 @@ class BivariateMap::FlatIterator AXOM_HOST_DEVICE pointer operator->() const { return &(*this); } - /** - * \brief return the current iterator's first index into the BivariateSet - */ + /// \brief return the current iterator's first index into the BivariateSet PositionType firstIndex() const { return m_bsetIterator.firstIndex(); } - /** - * \brief return the current iterator's second index (DenseIndex) - * into the BivariateSet - */ + /// \brief return the current iterator's second index (DenseIndex) into the BivariateSet PositionType secondIndex() const { return m_bsetIterator.secondIndex(); } /// \brief return the current iterator's component index PositionType compIndex() const { return this->m_pos % numComp(); } - /** \brief Returns the number of components per element in the map. */ + /// \brief Returns the number of components per element in the map. AXOM_SUPPRESS_HD_WARN AXOM_HOST_DEVICE PositionType numComp() const { return m_map->numComp(); } @@ -663,12 +647,10 @@ class BivariateMap::FlatIterator /** * \class BivariateMap::RangeIterator * - * \brief An iterator type for a BivariateMap, iterating over elements in an - * associated BivariateSet. + * \brief An iterator type for a BivariateMap, iterating over elements in an associated BivariateSet. * * Unlike the FlatIterator, which iterates over all map elements, the - * RangeIterator may point to a range of elements in the case of non-unit - * stride. + * RangeIterator may point to a range of elements in the case of non-unit stride. */ template template @@ -694,9 +676,7 @@ class BivariateMap::RangeIterator static constexpr PositionType INVALID_POS = -2; public: - /*! - * \brief Construct a new BivariateMap Iterator given an ElementFlatIndex - */ + /// \brief Construct a new BivariateMap Iterator given an ElementFlatIndex AXOM_HOST_DEVICE RangeIterator(BivariateMapPtr sMap, PositionType pos) : IterBase(pos) , m_map(sMap) @@ -704,14 +684,12 @@ class BivariateMap::RangeIterator , m_bsetIterator(m_map->set(), pos) { } - /*! - * \brief Returns the range of elements pointed to by this iterator. - */ + /// \brief Returns the range of elements pointed to by this iterator. AXOM_HOST_DEVICE reference operator*() const { return *m_mapIterator; } AXOM_HOST_DEVICE pointer operator->() const { return m_mapIterator.operator->(); } - /*! + /** * \brief Returns the iterator's value at the given component index. * * \pre `sizeof(compIdx) == StridePolicy::NumDims` @@ -724,10 +702,10 @@ class BivariateMap::RangeIterator return value(comp_idx...); } - /** \brief Returns the first component value after n increments. */ + /// \brief Returns the first component value after n increments. DataRefType operator[](PositionType n) const { return *(this->operator+(n)); } - /*! + /** * \brief Return the value at the iterator's position for a given component * index. Same as operator() */ @@ -737,23 +715,16 @@ class BivariateMap::RangeIterator return m_mapIterator(comp...); } - /** - * \brief return the current iterator's first index into the BivariateSet - */ + /// \brief return the current iterator's first index into the BivariateSet PositionType firstIndex() const { return m_bsetIterator.firstIndex(); } - /** - * \brief return the current iterator's second index (DenseIndex) - * into the BivariateSet - */ + /// \brief return the current iterator's second index (DenseIndex) into the BivariateSet PositionType secondIndex() const { return m_bsetIterator.secondIndex(); } - /** - * \brief Return the current iterator's flat bivariate index. - */ + /// \brief Return the current iterator's flat bivariate index. AXOM_HOST_DEVICE PositionType flatIndex() const { return m_mapIterator.flatIndex(); } - /** \brief Returns the number of components per element in the map. */ + /// \brief Returns the number of components per element in the map. PositionType numComp() const { return m_map->numComp(); } protected: diff --git a/src/axom/slam/BivariateSet.hpp b/src/axom/slam/BivariateSet.hpp index 0ce218ff7e..0f77a60a5d 100644 --- a/src/axom/slam/BivariateSet.hpp +++ b/src/axom/slam/BivariateSet.hpp @@ -37,8 +37,7 @@ struct BivariateSetIterator; * * \brief Abstract class that models a set whose elements are indexed by two * indices. Each element in a BivariateSet is equivalent to an ordered - * pair containing a row and column index, similar to indexing in a - * matrix. + * pair containing a row and column index, similar to indexing in a matrix. * * \detail BivariateSet models a subset of the Cartesian product of its two * sets. Elements of a BivariateSet can be represented as an ordered @@ -191,11 +190,9 @@ class BivariateSet * \return A range set of the positions in the second set */ AXOM_HOST_DEVICE virtual RangeSetType elementRangeSet(PositionType pos1) const = 0; - /** - * \brief Size of the BivariateSet, which is the number of non-zero entries - * in the BivariateSet. - */ - AXOM_HOST_DEVICE virtual PositionType size() const = 0; + + /// \brief The number of non-zero entries in the BivariateSet. + [[nodiscard]] AXOM_HOST_DEVICE virtual PositionType size() const = 0; /** * \brief Number of elements of the BivariateSet whose first index is \a pos @@ -205,14 +202,14 @@ class BivariateSet virtual PositionType size(PositionType pos1) const = 0; //size of a row /** \brief Size of the first set. */ - AXOM_HOST_DEVICE inline PositionType firstSetSize() const + [[nodiscard]] AXOM_HOST_DEVICE inline PositionType firstSetSize() const { return getSize(m_set1); } /** \brief Size of the second set. */ AXOM_SUPPRESS_HD_WARN - AXOM_HOST_DEVICE inline PositionType secondSetSize() const + [[nodiscard]] AXOM_HOST_DEVICE inline PositionType secondSetSize() const { return getSize(m_set2); } @@ -224,7 +221,7 @@ class BivariateSet const SecondSetType* getSecondSet() const { return m_set2; } /** \brief Returns the element at the given FlatIndex \a pos */ - AXOM_HOST_DEVICE virtual ElementType at(PositionType pos) const = 0; + [[nodiscard]] AXOM_HOST_DEVICE virtual ElementType at(PositionType pos) const = 0; /** * \brief A set of elements with the given first set index. @@ -235,19 +232,13 @@ class BivariateSet */ virtual SubsetType getElements(PositionType s1) const = 0; - /*! - * \brief Return an iterator to the first pair of set elements in the - * relation. - */ + /// \brief Return an iterator to the first pair of set elements in the relation. IteratorType begin() const { return IteratorType(this, 0); } - /*! - * \brief Return an iterator to one past the last pair of set elements in the - * relation. - */ + /// \brief Return an iterator to one past the last pair of set elements in the relation. IteratorType end() const { return IteratorType(this, size()); } - virtual bool isValid(bool verboseOutput = false) const; + [[nodiscard]] virtual bool isValid(bool verboseOutput = false) const; private: virtual void verifyPosition(PositionType s1, PositionType s2) const = 0; diff --git a/src/axom/slam/DynamicMap.hpp b/src/axom/slam/DynamicMap.hpp index ea5673fed6..89efabafee 100644 --- a/src/axom/slam/DynamicMap.hpp +++ b/src/axom/slam/DynamicMap.hpp @@ -25,8 +25,7 @@ namespace slam * \class DynamicMap * \brief A slam map class that supports adding and removing entries. * - * \detail An entry in the map is considered valid if - * its corresponding set's entry is valid + * \detail An entry in the map is considered valid if its corresponding set's entry is valid */ template class DynamicMap @@ -112,8 +111,7 @@ class DynamicMap /** * \brief Return the number of valid entries * - * An entry at a given index is considered valid if corresponding - * set element is valid. + * An entry at a given index is considered valid if corresponding set element is valid. */ SetPosition numberOfValidEntries() const { @@ -131,7 +129,7 @@ class DynamicMap } /** \brief Predicate to check if this DynamicMap instance is valid */ - bool isValid(bool verboseOutput = false) const; + [[nodiscard]] bool isValid(bool verboseOutput = false) const; /// @} diff --git a/src/axom/slam/DynamicSet.hpp b/src/axom/slam/DynamicSet.hpp index cd6a805ea9..c011b06575 100644 --- a/src/axom/slam/DynamicSet.hpp +++ b/src/axom/slam/DynamicSet.hpp @@ -250,7 +250,7 @@ class DynamicSet : public Set, SizePolicy * * \pre pos must be between 0 and size() */ - ElementType at(PositionType pos) const { return operator[](pos); }; + [[nodiscard]] ElementType at(PositionType pos) const { return operator[](pos); }; /** * \brief Access the element at position \a pos @@ -328,14 +328,13 @@ class DynamicSet : public Set, SizePolicy /** * \brief Returns the number of possible elements in the set * - * \note Not all elements are necessarily valid since some elements - * could have been deleted + * \note Not all elements are necessarily valid since some elements could have been deleted * \sa numberOfValidEntries(), isValidEntry() */ AXOM_HOST_DEVICE inline PositionType size() const { return SizePolicy::size(); }; /// \brief Uses \a SizePolicy::empty() to determine if the set is empty - AXOM_HOST_DEVICE bool empty() const { return SizePolicy::empty(); }; + [[nodiscard]] AXOM_HOST_DEVICE bool empty() const { return SizePolicy::empty(); }; /// \brief Returns a positionset over the set elements PositionSet positions() const { return PositionSet(size()); } @@ -346,7 +345,7 @@ class DynamicSet : public Set, SizePolicy * \details This is an O(n) operation, because the class makes no assumption * that data was not changed by the user */ - PositionType numberOfValidEntries() const + [[nodiscard]] PositionType numberOfValidEntries() const { PositionType nvalid = 0; @@ -373,7 +372,7 @@ class DynamicSet : public Set, SizePolicy * The entry is valid when 0 <= i < size() and the value at index * \a i is not marked as \a INVALID_ENTRY */ - inline bool isValidEntry(IndexType i) const + [[nodiscard]] inline bool isValidEntry(IndexType i) const { return i >= 0 && i < SizePolicy::size() && m_data[i] != INVALID_ENTRY; }; @@ -384,7 +383,7 @@ class DynamicSet : public Set, SizePolicy * A DynamicSet is valid if each of its policies claim it to be valid. * This includes its \a SizePolicy, \a OffsetPolicy and \a StridePolicy */ - bool isValid(bool verboseOutput = false) const + [[nodiscard]] bool isValid(bool verboseOutput = false) const { bool bValid = SizePolicy::isValid(verboseOutput); return bValid; diff --git a/src/axom/slam/Map.hpp b/src/axom/slam/Map.hpp index 76971c3c2e..2471d2466e 100644 --- a/src/axom/slam/Map.hpp +++ b/src/axom/slam/Map.hpp @@ -350,7 +350,7 @@ class Map : public StrPol, public policies::MapInterface::isEmpty(m_set.get()) ? static_cast(m_set.get()->size()) @@ -361,11 +361,10 @@ class Map : public StrPol, public policies::MapInterface::emptySet()) { } - /** \brief Provide the Set to be used by the Map */ + /// \brief Provide the Set to be used by the Map MapBuilder& set(const SetType* set) { m_set = set; return *this; } - /** \brief Set the stride of the Map using StridePolicy */ + /// \brief Set the stride of the Map using StridePolicy MapBuilder& stride(SetPosition str) { m_stride = StridePolicyType(str); return *this; } - /** \brief Set the pointer to the array of data the Map will contain - * (makes a copy of the array currently) - */ + /// \brief Set the pointer to the array of data the Map will contain MapBuilder& data(DataType* bufPtr) { m_data_ptr = bufPtr; @@ -484,9 +480,7 @@ class Map : public StrPol, public policies::MapInterface() const { return &(*this); } @@ -511,17 +505,14 @@ class Map : public StrPol, public policies::MapInterfaceth component values of the iterator's - * current element, use `iter(j)`. + * Each increment operation advances the iterator to the next set element. + * To access the jth component values of the iterator's current element, use `iter(j)`. * \warning Note the difference between the subscript operator ( `iter[off]` ) - * and the parenthesis operator ( `iter(j)` ). \n + * and the parenthesis operator ( `iter(j)` ). * `iter[off]` returns the value of the first component of the - * element at offset \a `off` from the currently pointed to - * element.\n + * element at offset \a `off` from the currently pointed to element. * And `iter(j)` returns the value of the jth component of - * the currently pointed to element (where 0 <= j < numComp()).\n + * the currently pointed to element (where 0 <= j < numComp()). * For example: `iter[off]` is the same as `(iter+off)(0)` */ template @@ -572,9 +563,7 @@ class Map : public StrPol, public policies::MapInterface() const { return &m_currRange; } @@ -619,11 +608,11 @@ class Map : public StrPol, public policies::MapInterfacem_pos; } - /** \brief Returns the number of components per element in the Map. */ + /// \brief Returns the number of components per element in the Map. PositionType numComp() const { return m_map->stride(); } protected: - /** Implementation of advance() as required by IteratorBase */ + /// Implementation of advance() as required by IteratorBase AXOM_HOST_DEVICE void advance(PositionType n) { this->m_pos += n; @@ -657,9 +646,7 @@ class Map : public StrPol, public policies::MapInterface= 0 && pos < size(); } + [[nodiscard]] inline bool isValidIndex(PositionType pos) const + { + return pos >= 0 && pos < size(); + } /** * \brief returns a PositionSet over the set's positions diff --git a/src/axom/slam/ProductSet.hpp b/src/axom/slam/ProductSet.hpp index f7779c5552..c6571cfbe0 100644 --- a/src/axom/slam/ProductSet.hpp +++ b/src/axom/slam/ProductSet.hpp @@ -29,9 +29,8 @@ namespace slam /** * \class ProductSet * - * \brief Models a set whose element is the Cartesian product of two sets. The - * number of elements in this set is the product of the sizes of the two - * input sets. + * \brief Models a set whose element is the Cartesian product of two sets. + * The number of elements in this set is the product of the sizes of the two input sets. * * Users should refer to the BivariateSet documentation for descriptions * of the different indexing names (SparseIndex, DenseIndex, FlatIndex). @@ -171,8 +170,7 @@ class ProductSet final : public policies::BivariateSetInterfacesecondSetSize()); } - AXOM_HOST_DEVICE ElementType at(PositionType pos) const { return pos % this->secondSetSize(); } + [[nodiscard]] AXOM_HOST_DEVICE ElementType at(PositionType pos) const + { + return pos % this->secondSetSize(); + } AXOM_HOST_DEVICE PositionType size() const { @@ -228,16 +228,10 @@ class ProductSet final : public policies::BivariateSetInterfacesecondSetSize(); } - /*! - * \brief Return an iterator to the first pair of set elements in the - * relation. - */ + /// \brief Return an iterator to the first pair of set elements in the relation. IteratorType begin() const { return IteratorType(this, 0); } - /*! - * \brief Return an iterator to one past the last pair of set elements in the - * relation. - */ + /// \brief Return an iterator to one past the last pair of set elements in the relation. IteratorType end() const { return IteratorType(this, size()); } AXOM_HOST_DEVICE RangeSetType elementRangeSet(PositionType pos1) const @@ -246,23 +240,26 @@ class ProductSet final : public policies::BivariateSetInterfacefirstSetSize(); PositionType size2 = this->secondSetSize(); return s1 >= 0 && s1 < size1 && s2 >= 0 && s2 < size2; } - bool isValid(bool verboseOutput = false) const { return BaseType::isValid(verboseOutput); } + [[nodiscard]] bool isValid(bool verboseOutput = false) const + { + return BaseType::isValid(verboseOutput); + } private: - /** \brief verify the FlatIndex \a pos is within the valid range. */ + /// \brief verify the FlatIndex \a pos is within the valid range. void verifyPosition(PositionType pos) const { //from RangeSet, overloading to avoid warning in compiler verifyPositionImpl(pos); } - /** \brief implementation for verifyPosition */ + /// \brief implementation for verifyPosition inline void verifyPositionImpl(PositionType AXOM_DEBUG_PARAM(pos)) const { //from RangeSet, overloading to avoid warning in compiler SLIC_ASSERT_MSG(pos >= 0 && pos < size(), @@ -270,15 +267,13 @@ class ProductSet final : public policies::BivariateSetInterface NullSet Relation::s_nullSet; diff --git a/src/axom/slam/RelationSet.hpp b/src/axom/slam/RelationSet.hpp index 8d2b74c448..98d64f8992 100644 --- a/src/axom/slam/RelationSet.hpp +++ b/src/axom/slam/RelationSet.hpp @@ -119,8 +119,7 @@ class RelationSet final : public policies::BivariateSetInterfacesize(pos); } - /*! - * \brief Return an iterator to the first pair of set elements in the - * relation. - */ + /// \brief Return an iterator to the first pair of set elements in the relation. IteratorType begin() const { return IteratorType(this, 0); } - /*! - * \brief Return an iterator to one past the last pair of set elements in the - * relation. - */ + /// \brief Return an iterator to one past the last pair of set elements in the relation. IteratorType end() const { return IteratorType(this, totalSize()); } - bool isValid(bool verboseOutput = false) const + [[nodiscard]] bool isValid(bool verboseOutput = false) const { if(m_relation == nullptr) { @@ -271,14 +262,14 @@ class RelationSet final : public policies::BivariateSetInterfacerelationData().size()); } private: //range check only - bool isValidIndex(PositionType s1, PositionType s2) const + [[nodiscard]] bool isValidIndex(PositionType s1, PositionType s2) const { return s1 >= 0 && s1 < m_relation->fromSet()->size() && s2 >= 0 && s2 < m_relation->size(s1); } diff --git a/src/axom/slam/Set.hpp b/src/axom/slam/Set.hpp index de566320d4..d9b387c6c0 100644 --- a/src/axom/slam/Set.hpp +++ b/src/axom/slam/Set.hpp @@ -64,8 +64,7 @@ namespace slam *
    *
  1. Implicit indexes -- all we need here is a size operator *
  2. Sliced indices -- here we need the dimension and the striding - *
  3. Explicit indices -- for a subset, we need the indices with respect to - * some other indexing scheme + *
  4. Explicit indices -- for a subset, we need the indices with respect to some other indexing scheme *
* * The interface is for constant access to the elements. @@ -85,57 +84,49 @@ class Set * \brief Random access to the entities of the set * \param The index of the desired element * \return The value of the element at the given position - * \pre The position must be less than the number of elements in the set ( - * size() ) - * \note Concrete realizations of Set also support subscript operator -- - * operator[]. - * \note How are we planning to handle indexes that are out or range - *(accidentally)? + * \pre The position must be less than the number of elements in the set ( size() ) + * \note Concrete realizations of Set also support subscript operator operator[]. + * \note How are we planning to handle indexes that are out or range (accidentally)? * Are we planning to handle indexes that are intentionally out of range * (e.g. to indicate a problem, or a missing element etc..)? */ - virtual ElementType at(PositionType) const = 0; + [[nodiscard]] virtual ElementType at(PositionType) const = 0; /** * \brief Get the number of entities in the set * \return The number of entities in the set. */ - AXOM_HOST_DEVICE virtual PositionType size() const = 0; + [[nodiscard]] AXOM_HOST_DEVICE virtual PositionType size() const = 0; /** * \brief Determines if the Set is a Subset of another set. * \return true if the set is a subset of another set, otherwise false. */ - virtual bool isSubset() const = 0; + [[nodiscard]] virtual bool isSubset() const = 0; /** * \brief Checks whether the set is valid. * \return true if the underlying indices are valid, false otherwise. */ - virtual bool isValid(bool verboseOutput = false) const = 0; + [[nodiscard]] virtual bool isValid(bool verboseOutput = false) const = 0; /** - * \brief Checks if there are any elements in the set -- equivalent to: - * set.size() == 0 + * \brief Checks if there are any elements in the set -- equivalent to: set.size() == 0 */ - AXOM_HOST_DEVICE virtual bool empty() const = 0; + [[nodiscard]] AXOM_HOST_DEVICE virtual bool empty() const = 0; #if 0 /** * \brief Returns true if the set contains the given element. * - * Alternatively, we can return the position in the set containing the - * element, + * Alternatively, we can return the position in the set containing the element, * with some value for not containing the element */ virtual bool contains(const SetElement & elt) const = 0; #endif private: - /** - * \brief Utility function to verify that the given SetPosition is in a valid - * range. - */ + /// \brief Utility function to verify that the given SetPosition is in a valid range. virtual void verifyPosition(PositionType) const = 0; }; @@ -170,9 +161,7 @@ inline bool operator==(const Set& set1, const Set& set2) } return true; } -/** - * \brief Set inequality operator - */ +/// \brief Set inequality operator template inline bool operator!=(const Set& set1, const Set& set2) { diff --git a/src/axom/slam/StaticRelation.hpp b/src/axom/slam/StaticRelation.hpp index e6d8b8c247..737c5eb05b 100644 --- a/src/axom/slam/StaticRelation.hpp +++ b/src/axom/slam/StaticRelation.hpp @@ -174,7 +174,7 @@ class StaticRelation : public /*Relation,*/ RelationCardinalityPolicy .data(m_relationIndices.ptr()); } - bool isValid(bool verboseOutput = false) const; + [[nodiscard]] bool isValid(bool verboseOutput = false) const; RelationIterator begin(SetPosition fromSetInd) { return (*this)[fromSetInd].begin(); } diff --git a/src/axom/slam/SubMap.hpp b/src/axom/slam/SubMap.hpp index c4cd3a384d..2828d693ad 100644 --- a/src/axom/slam/SubMap.hpp +++ b/src/axom/slam/SubMap.hpp @@ -33,16 +33,13 @@ namespace slam * \brief The SubMap class provides an API to easily traverse a subset of a Map. * * A SubMap is defined by a subset of the indices into a Map, which we refer to - * as its SuperMap (of type SuperMapType). The indices are expressed as - * ElementFlatIndex.\n + * as its SuperMap (of type SuperMapType). The indices are expressed as ElementFlatIndex.\n * Please see BivariateMap for an explanation of the various indexing schemes. * - * SubMap is used by BivariateMap to return a set of values mapped to each item - * in its first set. + * SubMap is used by BivariateMap to return a set of values mapped to each item in its first set. * * \tparam SuperMapType the type of SuperMap - * \tparam SetType defines the indices int the super map. - * SetType cannot be abstract. + * \tparam SetType defines the indices int the super map. It cannot be abstract. * * \warning SubMap constructor can take a const Map pointer or a non-const Map * pointer. A non-const value access function in SubMap will fail if the @@ -131,8 +128,7 @@ class SubMap : public policies::MapInterface AXOM_HOST_DEVICE inline SetPosition componentOffset(ComponentIndex componentIndex) const @@ -471,10 +462,7 @@ class SubMap::RangeIterator /// \brief Returns the set element mapped by this iterator. SetElement index() const { return m_submap.index(this->m_pos); } - /*! - * \brief Returns the flat index in the original map pointed to by this - * iterator. - */ + /// \brief Returns the flat index in the original map pointed to by thisiterator. SetPosition flatIndex() const { return m_mapIter.flatIndex(); } /// \brief Returns the index into the submap pointed to by this iterator. diff --git a/src/axom/slam/tests/slam_set_DynamicSet.cpp b/src/axom/slam/tests/slam_set_DynamicSet.cpp index 7b86b54a9a..083a11e113 100644 --- a/src/axom/slam/tests/slam_set_DynamicSet.cpp +++ b/src/axom/slam/tests/slam_set_DynamicSet.cpp @@ -93,7 +93,7 @@ TEST(slam_set_dynamicset, out_of_bounds_at) // NOTE: AXOM_DEBUG is disabled in release mode, // so this test will only fail in debug mode - EXPECT_DEATH_IF_SUPPORTED(s.at(MAX_SET_SIZE), ""); + EXPECT_DEATH_IF_SUPPORTED((void)s.at(MAX_SET_SIZE), ""); #else SLIC_INFO("Skipped assertion failure check in release mode."); #endif diff --git a/src/axom/slam/tests/slam_set_PositionSet.cpp b/src/axom/slam/tests/slam_set_PositionSet.cpp index fb5e928afc..912a409b65 100644 --- a/src/axom/slam/tests/slam_set_PositionSet.cpp +++ b/src/axom/slam/tests/slam_set_PositionSet.cpp @@ -204,7 +204,7 @@ TEST(slam_set_positionset, out_of_bounds_at) // add this line to avoid a warning in the output about thread safety ::testing::FLAGS_gtest_death_test_style = "threadsafe"; - EXPECT_DEATH_IF_SUPPORTED(s.at(MAX_SET_SIZE), ""); + EXPECT_DEATH_IF_SUPPORTED((void)s.at(MAX_SET_SIZE), ""); #else SLIC_INFO("Skipped assertion failure check in release mode."); #endif diff --git a/src/axom/slam/tests/slam_set_RangeSet.cpp b/src/axom/slam/tests/slam_set_RangeSet.cpp index 0228d914b6..dfdff7ec96 100644 --- a/src/axom/slam/tests/slam_set_RangeSet.cpp +++ b/src/axom/slam/tests/slam_set_RangeSet.cpp @@ -296,8 +296,8 @@ TEST(slam_range_set, out_of_range) #ifdef AXOM_DEBUG // NOTE: AXOM_DEBUG is disabled in release mode, // so this test will only fail in debug mode - EXPECT_DEATH_IF_SUPPORTED(s.at(upperIndex), ""); - EXPECT_DEATH_IF_SUPPORTED(s.at(MAX_SIZE), ""); + EXPECT_DEATH_IF_SUPPORTED((void)s.at(upperIndex), ""); + EXPECT_DEATH_IF_SUPPORTED((void)s.at(MAX_SIZE), ""); #else SLIC_INFO("Skipped assertion failure check in release mode."); #endif From 6278869943b6e643112394e2c753dd814dce75af Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 18 Jun 2026 13:13:41 -0700 Subject: [PATCH 583/986] slam: Marks compile-time scalars and ModInt as constexpr Uses a new ConstexprAssert construct along with a SLAM_CONSTEXPR_ASSSERT macro instead of SLIC_ASSERT for the constexpr functions. Misc: Marks `axom::utilities::processAbort()` as `[[noreturn]]` to allow the slam::ConstexprAssert to be `[[noreturn]]`. --- src/axom/core/utilities/Utilities.cpp | 2 +- src/axom/core/utilities/Utilities.hpp | 2 +- src/axom/slam/CMakeLists.txt | 1 + src/axom/slam/ModularInt.hpp | 84 +++++------- src/axom/slam/policies/ConstexprAssert.hpp | 80 +++++++++++ src/axom/slam/policies/OffsetPolicies.hpp | 6 +- src/axom/slam/policies/SizePolicies.hpp | 13 +- src/axom/slam/policies/StridePolicies.hpp | 25 ++-- src/axom/slam/policies/ValuePolicies.hpp | 26 ++-- src/axom/slam/tests/CMakeLists.txt | 3 +- src/axom/slam/tests/slam_static_asserts.cpp | 141 ++++++++++++++++++++ 11 files changed, 296 insertions(+), 87 deletions(-) create mode 100644 src/axom/slam/policies/ConstexprAssert.hpp create mode 100644 src/axom/slam/tests/slam_static_asserts.cpp diff --git a/src/axom/core/utilities/Utilities.cpp b/src/axom/core/utilities/Utilities.cpp index 6d3dadb70e..c197f257d9 100644 --- a/src/axom/core/utilities/Utilities.cpp +++ b/src/axom/core/utilities/Utilities.cpp @@ -24,7 +24,7 @@ namespace axom { namespace utilities { -void processAbort() +[[noreturn]] void processAbort() { #ifndef AXOM_USE_MPI abort(); diff --git a/src/axom/core/utilities/Utilities.hpp b/src/axom/core/utilities/Utilities.hpp index 4cd2801f54..f4cb946923 100644 --- a/src/axom/core/utilities/Utilities.hpp +++ b/src/axom/core/utilities/Utilities.hpp @@ -33,7 +33,7 @@ namespace utilities /*! * \brief Gracefully aborts the application */ -void processAbort(); +[[noreturn]] void processAbort(); /*! * \brief Returns the absolute value of x. diff --git a/src/axom/slam/CMakeLists.txt b/src/axom/slam/CMakeLists.txt index 105a2bdc95..9862e333e0 100644 --- a/src/axom/slam/CMakeLists.txt +++ b/src/axom/slam/CMakeLists.txt @@ -31,6 +31,7 @@ set(slam_headers #SRM policies policies/CardinalityPolicies.hpp + policies/ConstexprAssert.hpp policies/ValuePolicies.hpp policies/SizePolicies.hpp policies/OffsetPolicies.hpp diff --git a/src/axom/slam/ModularInt.hpp b/src/axom/slam/ModularInt.hpp index a7d3eaaf4e..704cb51254 100644 --- a/src/axom/slam/ModularInt.hpp +++ b/src/axom/slam/ModularInt.hpp @@ -19,6 +19,7 @@ #include "axom/slic/interface/slic.hpp" #include "axom/slam/policies/SizePolicies.hpp" +#include "axom/slam/policies/ConstexprAssert.hpp" namespace axom { @@ -40,11 +41,11 @@ template > class ModularInt : private SizePolicy { public: - ModularInt(int val = 0, int modulusVal = SizePolicy::DEFAULT_VALUE) + constexpr ModularInt(int val = 0, int modulusVal = SizePolicy::DEFAULT_VALUE) : SizePolicy(modulusVal) , m_val(val) { - SLIC_ASSERT(modulus() != 0); + SLAM_CONSTEXPR_ASSERT(modulus() != 0); normalize(); } @@ -53,13 +54,12 @@ class ModularInt : private SizePolicy * * \param mi other ModularInt */ - ModularInt(const ModularInt& mi) : SizePolicy(mi), m_val(mi.m_val) + constexpr ModularInt(const ModularInt& mi) : SizePolicy(mi), m_val(mi.m_val) { - SLIC_ASSERT(modulus() != 0); + SLAM_CONSTEXPR_ASSERT(modulus() != 0); - // For efficiency, we are assuming that argument mi is consistent - // (and avoiding normalization). This assumption is tested in debug - // builds... + // For efficiency, we are assuming that argument mi is consistent and avoid normalization. + // This assumption is tested in debug builds... //normalize(); verifyValue(); } @@ -69,10 +69,9 @@ class ModularInt : private SizePolicy * * \param mi other ModularInt * \return A reference to the constructed object - * \note This operator only modifies the value of the local instance. - * It does not modify the \a modulus() + * \note This operator only modifies the value of the local instance, but not modify the \a modulus() */ - ModularInt& operator=(const ModularInt& mi) + constexpr ModularInt& operator=(const ModularInt& mi) { if(&mi != this) { @@ -85,9 +84,9 @@ class ModularInt : private SizePolicy /** * \brief Implicit cast of a ModularInt to an int */ - operator int() const { return m_val; } + constexpr operator int() const { return m_val; } - int modulus() const { return SizePolicy::size(); } + constexpr int modulus() const { return SizePolicy::size(); } /// \name ModularInt arithmetic operations /// @@ -97,14 +96,14 @@ class ModularInt : private SizePolicy /// @{ /** Pre-increment operator */ - ModularInt& operator++() + constexpr ModularInt& operator++() { add(1); return *this; } /** Post-increment operator */ - const ModularInt operator++(int) + constexpr const ModularInt operator++(int) { ModularInt tmp(m_val, modulus()); add(1); @@ -112,14 +111,14 @@ class ModularInt : private SizePolicy } /** Pre-decrement operator */ - ModularInt& operator--() + constexpr ModularInt& operator--() { subtract(1); return *this; } /** Post-decrement operator */ - const ModularInt operator--(int) + constexpr const ModularInt operator--(int) { ModularInt tmp(m_val, modulus()); subtract(1); @@ -127,21 +126,21 @@ class ModularInt : private SizePolicy } /** \brief Addition assignment operator */ - ModularInt& operator+=(int val) + constexpr ModularInt& operator+=(int val) { add(val); return *this; } /** \brief Subtraction assignment operator */ - ModularInt& operator-=(int val) + constexpr ModularInt& operator-=(int val) { subtract(val); return *this; } /** \brief Multiplication assignment operator */ - ModularInt& operator*=(int val) + constexpr ModularInt& operator*=(int val) { multiply(val); return *this; @@ -151,8 +150,7 @@ class ModularInt : private SizePolicy /// \name ModularInt equality operations /// - /// \note Equality operations allow the operands to have - /// different \a SizePolicy types + /// \note Equality operations allow the operands to have different \a SizePolicy types /// @{ /** @@ -160,23 +158,21 @@ class ModularInt : private SizePolicy * * \param mi Other ModularInt * \note This function supports ModularInts with different SizePolicies - * \return True when both ModularInts have the same modulus() - * and the same value, false otherwise. + * \return True when both ModularInts have the same modulus() and the same value, false otherwise. */ template - bool operator==(const ModularInt& mi) const + constexpr bool operator==(const ModularInt& mi) const { return (this->modulus() == mi.modulus()) && (m_val == static_cast(mi)); } /** * \brief Inequality comparison operator - * \return True if the two ModularInts are not equal - * (as defined by operator==() ) + * \return True if the two ModularInts are not equal (as defined by operator==() ) * \sa operator==() */ template - bool operator!=(const ModularInt& mi) const + constexpr bool operator!=(const ModularInt& mi) const { return !operator==(mi); } @@ -184,26 +180,24 @@ class ModularInt : private SizePolicy /// @} private: - void add(int val) + constexpr void add(int val) { m_val += val; normalize(); } - void subtract(int val) + constexpr void subtract(int val) { m_val -= val; normalize(); } - void multiply(int val) + constexpr void multiply(int val) { m_val *= val; normalize(); } - /** - * Normalizes to invariant for ModularInt. Namely: 0 <= m_val < size() - */ - void normalize() + /// Normalizes to invariant for ModularInt. Namely: 0 <= m_val < size() + constexpr void normalize() { const int sz = modulus(); @@ -228,8 +222,7 @@ class ModularInt : private SizePolicy } #else // MODINT_MODLESS - // this version assumes that we are usually only adding - // small offsets to avoid the div + // this version assumes that we are usually only adding small offsets to avoid the div // if(m_val >= 0) while(m_val >= sz) m_val -= sz; // else @@ -239,19 +232,14 @@ class ModularInt : private SizePolicy verifyValue(); } - void verifyValue() - { - SLIC_ASSERT_MSG( - m_val >= 0 && m_val < modulus(), - "ModularInt: Value must be between 0 and " << modulus() << " but value was " << m_val << "."); - } + constexpr void verifyValue() { SLAM_CONSTEXPR_ASSERT(m_val >= 0 && m_val < modulus()); } private: int m_val; }; template -ModularInt operator+(const ModularInt& zn, const int n) +constexpr ModularInt operator+(const ModularInt& zn, const int n) { ModularInt tmp(zn); tmp += n; @@ -259,7 +247,7 @@ ModularInt operator+(const ModularInt& zn, const int n) } template -ModularInt operator+(const int n, const ModularInt& zn) +constexpr ModularInt operator+(const int n, const ModularInt& zn) { ModularInt tmp(zn); tmp += n; @@ -267,7 +255,7 @@ ModularInt operator+(const int n, const ModularInt& zn) } template -ModularInt operator-(const ModularInt& zn, const int n) +constexpr ModularInt operator-(const ModularInt& zn, const int n) { ModularInt tmp(zn); tmp -= n; @@ -275,7 +263,7 @@ ModularInt operator-(const ModularInt& zn, const int n) } template -ModularInt operator-(const int n, const ModularInt& zn) +constexpr ModularInt operator-(const int n, const ModularInt& zn) { ModularInt tmp(zn); tmp -= n; @@ -283,7 +271,7 @@ ModularInt operator-(const int n, const ModularInt& zn) } template -ModularInt operator*(const ModularInt& zn, const int n) +constexpr ModularInt operator*(const ModularInt& zn, const int n) { ModularInt tmp(zn); tmp *= n; @@ -291,7 +279,7 @@ ModularInt operator*(const ModularInt& zn, const int n) } template -ModularInt operator*(const int n, const ModularInt& zn) +constexpr ModularInt operator*(const int n, const ModularInt& zn) { ModularInt tmp(zn); tmp *= n; diff --git a/src/axom/slam/policies/ConstexprAssert.hpp b/src/axom/slam/policies/ConstexprAssert.hpp new file mode 100644 index 0000000000..dd55642bd7 --- /dev/null +++ b/src/axom/slam/policies/ConstexprAssert.hpp @@ -0,0 +1,80 @@ +// 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) + +/** + * \file ConstexprAssert.hpp + * + * \brief A constexpr-friendly assertion for slam's compile-time arithmetic core. + * + * slam's debug assertion (SLIC_ASSERT) expands, on the host debug path, + * to a block containing a `std::ostringstream` (a non-literal type) + * whih is ill-formed before C++23 -- so SLIC_ASSERT cannot appear in a function + * that must also be usable in a constant expression. + * + * SLAM_CONSTEXPR_ASSERT bridges the gap: + * - When the condition holds, it is a true no-op and is fully usable inside a + * constant expression. + * - At run time with AXOM_DEBUG enabled, a violated condition logs via SLIC + * and aborts, matching SLIC_ASSERT's runtime behavior. + * - During constant evaluation, a violated condition calls a non-constexpr + * function, which is a hard compile error pointing at the offending constant + * - When AXOM_DEBUG is off, it collapses to a no-op. + */ + +#ifndef SLAM_POLICIES_CONSTEXPR_ASSERT_H_ +#define SLAM_POLICIES_CONSTEXPR_ASSERT_H_ + +#include "axom/config.hpp" +#include "axom/core/Macros.hpp" + +#if defined(AXOM_DEBUG) && !defined(AXOM_DEVICE_CODE) + #include "axom/slic/interface/slic_macros.hpp" + #include "axom/core/utilities/Utilities.hpp" +#endif + +namespace axom::slam::detail +{ +#if defined(AXOM_DEBUG) && !defined(AXOM_DEVICE_CODE) + +/*! + * \brief Runtime handler for a failed constexpr assert (host debug only). + * + * Not marked constexpr: reaching this during constant evaluation is the + * mechanism by which a compile-time invariant violation becomes a hard error. + */ +[[noreturn]] inline void constexprAssertFail(const char* expr, const char* /*file*/, int /*line*/) +{ + SLIC_ERROR("Failed constexpr assert: " << expr); + // SLIC_ERROR may not abort if abort-on-error is disabled + axom::utilities::processAbort(); +} + +/*! + * \brief constexpr-safe assertion. + * \param cond the invariant that must hold + * \param expr stringized form of \a cond (for diagnostics) + */ +constexpr void constexprAssert(bool cond, const char* expr, const char* file, int line) +{ + cond ? void(0) : constexprAssertFail(expr, file, line); +} + +#else // release, or device code: no-op + +constexpr void constexprAssert(bool, const char*, const char*, int) { } + +#endif + +} // namespace axom::slam::detail + +/*! + * \def SLAM_CONSTEXPR_ASSERT(EXP) + * \brief Assert \a EXP in a way that is valid inside constexpr functions. + */ +#define SLAM_CONSTEXPR_ASSERT(EXP) \ + ::axom::slam::detail::constexprAssert((EXP), #EXP, __FILE__, __LINE__) + +#endif // SLAM_POLICIES_CONSTEXPR_ASSERT_H_ diff --git a/src/axom/slam/policies/OffsetPolicies.hpp b/src/axom/slam/policies/OffsetPolicies.hpp index 0691e4122f..50040e827c 100644 --- a/src/axom/slam/policies/OffsetPolicies.hpp +++ b/src/axom/slam/policies/OffsetPolicies.hpp @@ -52,8 +52,8 @@ struct RuntimeOffset : RuntimeValue> using BaseType::BaseType; - AXOM_HOST_DEVICE inline IntType offset() const { return this->value(); } - AXOM_HOST_DEVICE inline IntType& offset() { return this->value(); } + AXOM_HOST_DEVICE constexpr IntType offset() const { return this->value(); } + AXOM_HOST_DEVICE constexpr IntType& offset() { return this->value(); } }; template @@ -71,7 +71,7 @@ struct CompileTimeOffset : CompileTimeValue> using BaseType::BaseType; - AXOM_HOST_DEVICE inline IntType offset() const { return INT_VAL; } + AXOM_HOST_DEVICE constexpr IntType offset() const { return INT_VAL; } }; /// \brief A policy class for when we have no offset diff --git a/src/axom/slam/policies/SizePolicies.hpp b/src/axom/slam/policies/SizePolicies.hpp index aad459b65e..8f46087f06 100644 --- a/src/axom/slam/policies/SizePolicies.hpp +++ b/src/axom/slam/policies/SizePolicies.hpp @@ -15,8 +15,7 @@ * * DEFAULT_VALUE is a public static constant of type IntType * * size() : IntType -- returns the underlying integer size * * empty() : bool -- returns whether the size is zero - * * isValid() : bool -- indicates whether the Size policy of the set is - * valid + * * isValid() : bool -- indicates whether the Size policy of the set is valid * * [optional] * * operator(): IntType -- alternate accessor for the size value * @@ -53,10 +52,10 @@ struct RuntimeSize : RuntimeValue> using BaseType::BaseType; - AXOM_HOST_DEVICE inline IntType size() const { return this->value(); } - AXOM_HOST_DEVICE inline IntType& size() { return this->value(); } + AXOM_HOST_DEVICE constexpr IntType size() const { return this->value(); } + AXOM_HOST_DEVICE constexpr IntType& size() { return this->value(); } - AXOM_HOST_DEVICE inline bool empty() const { return this->m_value == IntType(); } + AXOM_HOST_DEVICE constexpr bool empty() const { return this->m_value == IntType(); } }; template @@ -108,9 +107,9 @@ struct CompileTimeSize : CompileTimeValue> using BaseType::BaseType; - AXOM_HOST_DEVICE inline IntType size() const { return INT_VAL; } + AXOM_HOST_DEVICE constexpr IntType size() const { return INT_VAL; } - AXOM_HOST_DEVICE inline bool empty() const { return INT_VAL == IntType {}; } + AXOM_HOST_DEVICE constexpr bool empty() const { return INT_VAL == IntType {}; } }; /// \brief A policy class for an empty set (no size) diff --git a/src/axom/slam/policies/StridePolicies.hpp b/src/axom/slam/policies/StridePolicies.hpp index 8795a9dc75..c07b0b28bc 100644 --- a/src/axom/slam/policies/StridePolicies.hpp +++ b/src/axom/slam/policies/StridePolicies.hpp @@ -12,13 +12,13 @@ * Stride policies are meant to represent the fixed distance between consecutive * elements of an OrderedSet * A valid stride policy must support the following interface: - * * [required] - * * DEFAULT_VALUE is a public static const IntType - * * IS_COMPILE_TIME is a public static const bool - * * stride() : IntType -- returns the stride - * * isValid() : bool -- indicates whether the Stride policy of the set is valid - * * [optional] - * * operator(): IntType -- alternate accessor for the stride value + * [required] + * - DEFAULT_VALUE is a public static const IntType + * - IS_COMPILE_TIME is a public static const bool + * - stride() : IntType -- returns the stride + * - isValid() : bool -- indicates whether the Stride policy of the set is valid + * [optional] + * - operator(): IntType -- alternate accessor for the stride value * * \note All non-zero stride values are valid. * @@ -33,6 +33,7 @@ #define SLAM_POLICIES_STRIDE_H_ #include "axom/core/Macros.hpp" +#include "axom/core/StackArray.hpp" #include "axom/slam/policies/ValuePolicies.hpp" namespace axom::slam::policies @@ -67,14 +68,14 @@ struct RuntimeStride : RuntimeValue> using BaseType::BaseType; /// \brief Returns the stride between consecutive elements. - AXOM_HOST_DEVICE inline IntType stride() const { return this->value(); } - AXOM_HOST_DEVICE inline IntType& stride() { return this->value(); } + AXOM_HOST_DEVICE constexpr IntType stride() const { return this->value(); } + AXOM_HOST_DEVICE constexpr IntType& stride() { return this->value(); } /*! * \brief Returns the shape of the inner data for a given stride. * This only has meaning when used with Map-based types. */ - AXOM_HOST_DEVICE inline IntType shape() const { return this->value(); } + AXOM_HOST_DEVICE constexpr IntType shape() const { return this->value(); } void setStride(IntType str) { this->m_value = str; } }; @@ -101,8 +102,8 @@ struct CompileTimeStride : CompileTimeValue> using BaseType::BaseType; - AXOM_HOST_DEVICE inline IntType stride() const { return INT_VAL; } - AXOM_HOST_DEVICE inline IntType shape() const { return INT_VAL; } + AXOM_HOST_DEVICE constexpr IntType stride() const { return INT_VAL; } + AXOM_HOST_DEVICE constexpr IntType shape() const { return INT_VAL; } AXOM_HOST_DEVICE void setStride(IntType AXOM_DEBUG_PARAM(val)) { diff --git a/src/axom/slam/policies/ValuePolicies.hpp b/src/axom/slam/policies/ValuePolicies.hpp index e097700b2f..5f6148b0ec 100644 --- a/src/axom/slam/policies/ValuePolicies.hpp +++ b/src/axom/slam/policies/ValuePolicies.hpp @@ -41,6 +41,7 @@ #include "axom/core/Macros.hpp" #include "axom/slic.hpp" +#include "axom/slam/policies/ConstexprAssert.hpp" namespace axom::slam::policies { @@ -107,17 +108,17 @@ struct RuntimeValue public: using TagType = Tag; - AXOM_HOST_DEVICE RuntimeValue(decltype(Tag::defaultValue()) val = Tag::defaultValue()) + AXOM_HOST_DEVICE constexpr RuntimeValue(decltype(Tag::defaultValue()) val = Tag::defaultValue()) : m_value(val) { } - AXOM_HOST_DEVICE inline auto value() const { return m_value; } - AXOM_HOST_DEVICE inline auto& value() { return m_value; } + AXOM_HOST_DEVICE constexpr auto value() const { return m_value; } + AXOM_HOST_DEVICE constexpr auto& value() { return m_value; } - inline auto operator()() const { return value(); } - inline auto& operator()() { return value(); } + constexpr auto operator()() const { return value(); } + constexpr auto& operator()() { return value(); } - inline bool isValid(bool) const { return Tag::isValidValue(m_value); } + constexpr bool isValid(bool) const { return Tag::isValidValue(m_value); } protected: decltype(Tag::defaultValue()) m_value; @@ -147,20 +148,17 @@ struct CompileTimeValue static constexpr IntType VALUE = V; - AXOM_HOST_DEVICE CompileTimeValue(IntType val = V) + AXOM_HOST_DEVICE constexpr CompileTimeValue(IntType val = V) { AXOM_UNUSED_VAR(val); - SLIC_ASSERT_MSG(val == V, - Tag::name() << " -- tried to initialize a compile-time value policy with " - << "value (" << val << ") that differs from the template " - << "parameter of " << V << "."); + SLAM_CONSTEXPR_ASSERT(val == V); } - AXOM_HOST_DEVICE inline IntType value() const { return V; } + AXOM_HOST_DEVICE constexpr IntType value() const { return V; } - inline IntType operator()() const { return value(); } + constexpr IntType operator()() const { return value(); } - inline bool isValid(bool) const { return Tag::isValidValue(V); } + constexpr bool isValid(bool) const { return Tag::isValidValue(V); } }; } // end namespace axom::slam::policies diff --git a/src/axom/slam/tests/CMakeLists.txt b/src/axom/slam/tests/CMakeLists.txt index b4b514c62a..faef2a0d01 100644 --- a/src/axom/slam/tests/CMakeLists.txt +++ b/src/axom/slam/tests/CMakeLists.txt @@ -40,7 +40,8 @@ set(gtest_slam_tests # aux tests slam_ModularInt.cpp - + slam_static_asserts.cpp + #mesh structure test slam_IA.cpp slam_detail_FacetPairingMap.cpp diff --git a/src/axom/slam/tests/slam_static_asserts.cpp b/src/axom/slam/tests/slam_static_asserts.cpp new file mode 100644 index 0000000000..244f28d442 --- /dev/null +++ b/src/axom/slam/tests/slam_static_asserts.cpp @@ -0,0 +1,141 @@ +// 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) + +/** + * \file slam_static_asserts.cpp + * + * \brief Compile-time conformance harness for slam's constexpr arithmetic core. + * + * The tests in this file exercise the compile time set/value asserts + * for sizes, offsets, strides, modular wraparound, and policy composition, so the checks cost + * nothing at run time and run on every backend on every build. + * + * The single gtest case below exists only so the file participates in the test runner. + * If this file compiles, the invariants it encodes hold. + */ + +#include "gtest/gtest.h" + +#include "axom/slam/ModularInt.hpp" +#include "axom/slam/policies/SizePolicies.hpp" +#include "axom/slam/policies/StridePolicies.hpp" +#include "axom/slam/policies/OffsetPolicies.hpp" +#include "axom/slam/policies/ValuePolicies.hpp" + +namespace +{ +namespace slam = axom::slam; +namespace policies = axom::slam::policies; + +//------------------------------------------------------------------------------ +// Value policies: compile-time-known values report their value, validity, +// and (for size) emptiness without any runtime state. +//------------------------------------------------------------------------------ + +// --- Size --- +using Size0 = policies::CompileTimeSize; +using Size5 = policies::CompileTimeSize; + +static_assert(Size5().size() == 5, "CompileTimeSize reports its size"); +static_assert(Size0().size() == 0, "CompileTimeSize of zero reports zero"); +static_assert(!Size5().empty(), "non-zero CompileTimeSize is not empty"); +static_assert(Size0().empty(), "zero CompileTimeSize is empty"); +static_assert(Size5().isValid(false), "non-negative size is valid"); +static_assert(Size5().DEFAULT_VALUE == 5, "DEFAULT_VALUE matches the NTTP"); + +// --- Offset --- +using Off0 = policies::CompileTimeOffset; +using Off3 = policies::CompileTimeOffset; + +static_assert(Off3().offset() == 3, "CompileTimeOffset reports its offset"); +static_assert(Off0().offset() == 0, "ZeroOffset-equivalent reports zero"); +static_assert(policies::ZeroOffset().offset() == 0, "ZeroOffset is zero"); +static_assert(Off3().isValid(false), "all offsets are valid"); + +// --- Stride --- +using Stride1 = policies::CompileTimeStride; +using Stride4 = policies::CompileTimeStride; + +static_assert(Stride4().stride() == 4, "CompileTimeStride reports its stride"); +static_assert(Stride4().shape() == 4, "CompileTimeStride shape equals stride"); +static_assert(policies::StrideOne().stride() == 1, "StrideOne is one"); +static_assert(Stride4().isValid(false), "non-zero stride is valid"); +static_assert(Stride1::IS_COMPILE_TIME, "CompileTimeStride is compile-time"); +static_assert(Stride1::NumDims == 1, "scalar stride is one-dimensional"); + +//------------------------------------------------------------------------------ +// The unified core: RuntimeValue / CompileTimeValue behave through the tags. +//------------------------------------------------------------------------------ +static_assert(policies::CompileTimeValue<7, policies::SizeTag>().value() == 7, + "CompileTimeValue forwards its NTTP"); +static_assert(policies::SizeTag::isValidValue(0), "size 0 is valid"); +static_assert(!policies::SizeTag::isValidValue(-1), "negative size is invalid"); +static_assert(!policies::StrideTag::isValidValue(0), "stride 0 is invalid"); +static_assert(policies::StrideTag::isValidValue(-2), "negative stride is valid"); +static_assert(policies::OffsetTag::isValidValue(-5), "any offset is valid"); +static_assert(policies::StrideTag::defaultValue() == 1, "default stride is one"); +static_assert(policies::SizeTag::defaultValue() == 0, "default size is zero"); + +//------------------------------------------------------------------------------ +// Offset / stride composition: the flat index of element i in a strided, +// offset range is offset + i * stride. Verify the policy values compose. +//------------------------------------------------------------------------------ +constexpr int flatIndex(int offset, int stride, int i) { return offset + i * stride; } + +static_assert(flatIndex(Off3().offset(), Stride4().stride(), 0) == 3, + "element 0 lands at the offset"); +static_assert(flatIndex(Off3().offset(), Stride4().stride(), 2) == 11, + "offset 3 + 2*stride 4 == 11"); +static_assert(flatIndex(policies::ZeroOffset().offset(), policies::StrideOne().stride(), 9) == + 9, + "unit stride, zero offset is the identity index"); + +//------------------------------------------------------------------------------ +// ModularInt: cyclic arithmetic is fully constexpr-evaluable. +//------------------------------------------------------------------------------ +using Mod5 = slam::ModularInt>; + +static_assert(int(Mod5(0)) == 0, "0 mod 5 == 0"); +static_assert(int(Mod5(5)) == 0, "5 mod 5 == 0 (wraparound)"); +static_assert(int(Mod5(7)) == 2, "7 mod 5 == 2"); +static_assert(int(Mod5(-1)) == 4, "-1 mod 5 normalizes to 4"); +static_assert(int(Mod5(-5)) == 0, "-5 mod 5 == 0"); +static_assert(int(Mod5(13)) == 3, "13 mod 5 == 3"); + +// arithmetic operators +static_assert(int(Mod5(3) + 4) == 2, "(3+4) mod 5 == 2"); +static_assert(int(Mod5(4) + 1) == 0, "(4+1) mod 5 wraps to 0"); +static_assert(int(4 + Mod5(3)) == 2, "int + ModularInt commutes"); +static_assert(int(Mod5(1) - 3) == 3, "(1-3) mod 5 == 3"); +static_assert(int(Mod5(2) * 4) == 3, "(2*4) mod 5 == 3"); + +// pre/post increment in a constexpr lambda +constexpr int incTwice(int start) +{ + Mod5 m(start); + ++m; + ++m; + return int(m); +} +static_assert(incTwice(4) == 1, "++ twice from 4 mod 5 == 1"); + +// equality across the modulus +static_assert(Mod5(2) == Mod5(7), "2 and 7 are equal mod 5"); +static_assert(Mod5(2) != Mod5(3), "2 and 3 differ mod 5"); + +} // anonymous namespace + +//------------------------------------------------------------------------------ +// Checks that a constexpr value survives to run time. +//------------------------------------------------------------------------------ +TEST(slam_static_asserts, compile_time_value_holds) +{ + constexpr int wrapped = int(Mod5(7)); + EXPECT_EQ(wrapped, 2); + + constexpr int idx = flatIndex(Off3().offset(), Stride4().stride(), 2); + EXPECT_EQ(idx, 11); +} From 62b3d3363ba9b9897256ced2c9b85fd0fe3dd965 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 18 Jun 2026 14:56:04 -0700 Subject: [PATCH 584/986] slam: Adds slam::Optional and uses string_view for lookup functions into FieldRegistry slam::Optional is a lightweight device-capable analog of std::optional, but not a drop-in replacement. It will allow us to use optional types in device kernels. The FieldRegistry is host-only, so it can use std::optional. We updated the API to take string_view instead of std::string (sometimes refs to strings) to avoid unnecessary copies. --- src/axom/slam/CMakeLists.txt | 1 + src/axom/slam/FieldRegistry.hpp | 142 ++++++++++++++------ src/axom/slam/Optional.hpp | 86 ++++++++++++ src/axom/slam/docs/sphinx/index.rst | 1 + src/axom/slam/docs/sphinx/portability.rst | 57 ++++++++ src/axom/slam/tests/CMakeLists.txt | 1 + src/axom/slam/tests/slam_FieldRegistry.cpp | 121 +++++++++++++++++ src/axom/slam/tests/slam_static_asserts.cpp | 45 ++++++- 8 files changed, 404 insertions(+), 50 deletions(-) create mode 100644 src/axom/slam/Optional.hpp create mode 100644 src/axom/slam/docs/sphinx/portability.rst create mode 100644 src/axom/slam/tests/slam_FieldRegistry.cpp diff --git a/src/axom/slam/CMakeLists.txt b/src/axom/slam/CMakeLists.txt index 9862e333e0..1a6544d07d 100644 --- a/src/axom/slam/CMakeLists.txt +++ b/src/axom/slam/CMakeLists.txt @@ -28,6 +28,7 @@ set(slam_headers Utilities.hpp FieldRegistry.hpp ModularInt.hpp + Optional.hpp #SRM policies policies/CardinalityPolicies.hpp diff --git a/src/axom/slam/FieldRegistry.hpp b/src/axom/slam/FieldRegistry.hpp index 47d7e94530..9827fedc9b 100644 --- a/src/axom/slam/FieldRegistry.hpp +++ b/src/axom/slam/FieldRegistry.hpp @@ -8,23 +8,30 @@ #define SLAM_FIELD_REGISTRY_H_ #include "axom/slic.hpp" +#include "axom/fmt.hpp" #include "axom/slam/Utilities.hpp" #include "axom/slam/Set.hpp" #include "axom/slam/Map.hpp" -#include -#include +#include +#include +#include +#include +#include -namespace axom -{ -namespace slam +namespace axom::slam { /** * \brief Simple container for fields of type DataType w/ minimal error checking * * \note We are using concrete instances for int and double in the code below. * This should eventually be replaced with the sidre datastore. + * + * \note FieldRegistry is a host-only facility: it stores std::map tables + * keyed by std::string and its find APIs return std::optional. The + * three lookup tables use transparent comparison (std::less<>) so callers may + * query with a std::string_view without constructing a temporary std::string. */ template class FieldRegistry @@ -36,92 +43,140 @@ class FieldRegistry using MapType = slam::Map; using BufferType = typename MapType::OrderedMap; - using DataVecMap = std::map; - using DataBufferMap = std::map; - using DataAttrMap = std::map; + // Transparent comparator (std::less<>) enables heterogeneous lookup: a + // std::string_view (or const char*) can be used as a key without allocating. + using DataVecMap = std::map>; + using DataBufferMap = std::map>; + using DataAttrMap = std::map>; public: - bool hasField(const KeyType& key) const { return m_maps.find(key) != m_maps.end(); } + [[nodiscard]] bool hasField(std::string_view key) const + { + return m_maps.find(key) != m_maps.end(); + } - MapType& addField(KeyType key, const SetType* theSet) { return m_maps[key] = MapType(theSet); } + MapType& addField(KeyType key, const SetType* theSet) + { + return m_maps[std::move(key)] = MapType(theSet); + } MapType& addNamelessField(const SetType* theSet) { static int cnt = 0; - std::stringstream key; - - key << "__field_" << cnt++; - return m_maps[key.str()] = MapType(theSet); + return m_maps[axom::fmt::format("__field_{}", cnt++)] = MapType(theSet); } - MapType& getField(KeyType key) + MapType& getField(std::string_view key) { verifyFieldsKey(key); - return m_maps[key]; + return m_maps.find(key)->second; } - const MapType& getField(KeyType key) const + const MapType& getField(std::string_view key) const { verifyFieldsKey(key); - return m_maps[key]; + return m_maps.find(key)->second; } - bool hasBuffer(const KeyType& key) const { return m_buff.find(key) != m_buff.end(); } + /*! + * \brief Find a field by name without inserting or asserting. + * \return an optional referencing the field if present, else empty. + */ + [[nodiscard]] std::optional> findField(std::string_view key) + { + auto it = m_maps.find(key); + return it != m_maps.end() ? std::optional>(it->second) + : std::nullopt; + } - BufferType& addBuffer(KeyType key, int size = 0) { return m_buff[key] = BufferType(size); } + [[nodiscard]] std::optional> findField(std::string_view key) const + { + auto it = m_maps.find(key); + return it != m_maps.end() ? std::optional>(it->second) + : std::nullopt; + } + + [[nodiscard]] bool hasBuffer(std::string_view key) const + { + return m_buff.find(key) != m_buff.end(); + } + + BufferType& addBuffer(KeyType key, int size = 0) + { + return m_buff[std::move(key)] = BufferType(size); + } BufferType& addNamelessBuffer(int size = 0) { static int cnt = 0; - std::stringstream key; - - key << "__buffer_" << cnt++; - return m_buff[key.str()] = BufferType(size); + return m_buff[axom::fmt::format("__buffer_{}", cnt++)] = BufferType(size); } - BufferType& getBuffer(KeyType key) + BufferType& getBuffer(std::string_view key) { verifyBufferKey(key); - return m_buff[key]; + return m_buff.find(key)->second; } - const BufferType& getBuffer(KeyType key) const + const BufferType& getBuffer(std::string_view key) const { verifyBufferKey(key); - return m_buff[key]; + return m_buff.find(key)->second; } - bool hasScalar(const KeyType& key) const { return m_scal.find(key) != m_scal.end(); } + /*! + * \brief Find a buffer by name without inserting or asserting. + * \return an optional referencing the buffer if present, else empty. + */ + [[nodiscard]] std::optional> findBuffer(std::string_view key) + { + auto it = m_buff.find(key); + return it != m_buff.end() ? std::optional>(it->second) + : std::nullopt; + } - DataType& addScalar(KeyType key, DataType val) { return m_scal[key] = val; } + [[nodiscard]] bool hasScalar(std::string_view key) const + { + return m_scal.find(key) != m_scal.end(); + } + + DataType& addScalar(KeyType key, DataType val) { return m_scal[std::move(key)] = val; } - DataType& getScalar(KeyType key) + DataType& getScalar(std::string_view key) { verifyScalarKey(key); - return m_scal[key]; + return m_scal.find(key)->second; } - const DataType& getScalar(KeyType key) const + const DataType& getScalar(std::string_view key) const { verifyScalarKey(key); - return m_scal[key]; + return m_scal.find(key)->second; } -private: - std::string dataTypeString() const { return typeid(DataType).name(); } + /*! + * \brief Find a scalar by name without inserting or asserting. + * \return an engaged optional with the scalar's value if present, else empty. + */ + [[nodiscard]] std::optional findScalar(std::string_view key) const + { + auto it = m_scal.find(key); + return it != m_scal.end() ? std::optional(it->second) : std::nullopt; + } - inline void verifyFieldsKey(const KeyType& AXOM_DEBUG_PARAM(key)) const +private: + inline void verifyFieldsKey(std::string_view AXOM_DEBUG_PARAM(key)) const { - SLIC_ASSERT_MSG(hasField(key), "Didn't find " << dataTypeString() << " field named " << key); + SLIC_ASSERT_MSG(hasField(key), "Didn't find field named " << key); } - inline void verifyBufferKey(const KeyType& AXOM_DEBUG_PARAM(key)) const + inline void verifyBufferKey(std::string_view AXOM_DEBUG_PARAM(key)) const { - SLIC_ASSERT_MSG(hasBuffer(key), "Didn't find " << dataTypeString() << " buffer named " << key); + SLIC_ASSERT_MSG(hasBuffer(key), "Didn't find buffer named " << key); } - inline void verifyScalarKey(const KeyType& AXOM_DEBUG_PARAM(key)) const + inline void verifyScalarKey(std::string_view AXOM_DEBUG_PARAM(key)) const { - SLIC_ASSERT_MSG(hasScalar(key), "Didn't find " << dataTypeString() << " scalar named " << key); + SLIC_ASSERT_MSG(hasScalar(key), "Didn't find scalar named " << key); } private: @@ -130,7 +185,6 @@ class FieldRegistry DataAttrMap m_scal; }; -} // end namespace slam -} // end namespace axom +} // end namespace axom::slam #endif // SLAM_FIELD_REGISTRY_H_ diff --git a/src/axom/slam/Optional.hpp b/src/axom/slam/Optional.hpp new file mode 100644 index 0000000000..d6204c4658 --- /dev/null +++ b/src/axom/slam/Optional.hpp @@ -0,0 +1,86 @@ +// 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) + +/** + * \file Optional.hpp + * + * \brief A minimal, host-device "maybe a value" type for slam. + * + * std::optional is a host-only facility in slam's portability model + * and is not guaranteed to be available/usable in device code + * across all of slam's backends (SEQ/OMP/CUDA/HIP). + * + * slam::Optional is a trivially-structured aggregate of {engaged flag, storage} + * that is AXOM_HOST_DEVICE throughout and has no throwing value() accessor + * (querying an unengaged Optional in a kernel cannot throw, + * so the contract is "check has_value() first"; in debug host builds a violation asserts). + * + * It has sufficient functionality for a kernel-side optional (i.e to check if something is found) + * but is not a drop-in std::optional -- it does not have: exceptions, monadic and_then/transforms, + * or in-place construction machinery. + */ + +#ifndef SLAM_OPTIONAL_H_ +#define SLAM_OPTIONAL_H_ + +#include "axom/core/Macros.hpp" +#include "axom/slam/policies/ConstexprAssert.hpp" + +#include + +namespace axom::slam +{ +/*! + * \class Optional + * \brief Host-device "maybe a value of type T". + * + * \tparam T the (literal/trivially-copyable) value type. Intended for the + * small value and index types that flow through slam's device find APIs. + */ +template +struct Optional +{ + T m_value {}; + bool m_engaged {false}; + + /// \brief Construct a disengaged Optional (no value). + AXOM_HOST_DEVICE constexpr Optional() = default; + + /// \brief Construct an engaged Optional holding \a value. + AXOM_HOST_DEVICE constexpr Optional(const T& value) : m_value(value), m_engaged(true) { } + + /// \brief Whether this Optional holds a value. + AXOM_HOST_DEVICE constexpr bool has_value() const { return m_engaged; } + + /// \brief Whether this Optional holds a value (bool conversion). + AXOM_HOST_DEVICE constexpr explicit operator bool() const { return m_engaged; } + + /*! + * \brief Access the contained value. + * \pre has_value() is true. There is no throwing accessor -- in host debug builds, + * a disengaged access asserts, and during constant evaluation it is a compile error. + */ + AXOM_HOST_DEVICE constexpr const T& operator*() const + { + SLAM_CONSTEXPR_ASSERT(m_engaged); + return m_value; + } + AXOM_HOST_DEVICE constexpr T& operator*() + { + SLAM_CONSTEXPR_ASSERT(m_engaged); + return m_value; + } + + /// \brief Return the contained value if engaged, otherwise \a fallback. + AXOM_HOST_DEVICE constexpr T value_or(const T& fallback) const + { + return m_engaged ? m_value : fallback; + } +}; + +} // end namespace axom::slam + +#endif // SLAM_OPTIONAL_H_ diff --git a/src/axom/slam/docs/sphinx/index.rst b/src/axom/slam/docs/sphinx/index.rst index f9d181fd4e..7245e02285 100644 --- a/src/axom/slam/docs/sphinx/index.rst +++ b/src/axom/slam/docs/sphinx/index.rst @@ -96,3 +96,4 @@ Current limitations first_example core_concepts implementation_details + portability diff --git a/src/axom/slam/docs/sphinx/portability.rst b/src/axom/slam/docs/sphinx/portability.rst new file mode 100644 index 0000000000..1e55a9be0e --- /dev/null +++ b/src/axom/slam/docs/sphinx/portability.rst @@ -0,0 +1,57 @@ +.. ## 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) + +.. _portability-label: + +Host/device portability tiers +============================== + +Slam's types are designed to run both on the host and inside GPU kernels. + +Not every C++ facility is safe in device code across all of Slam's backends +(sequential, OpenMP, CUDA, HIP), so Slam categorizes the constructs it uses into +three portability tiers. Compile-time features generate no device code +and are therefore unconditionally kernel-safe. + +.. list-table:: Slam's portability tiers + :widths: 8 62 30 + :header-rows: 1 + + * - Tier + - Contents + - Allowed where + * - A + - All compile-time language features (concepts, ``if constexpr``, + class template argument deduction (CTAD), non-type template parameters, + fold expressions, type traits, ``constexpr`` evaluation); + Axom host-device types (``StackArray``, ``ArrayView``, ``NumericLimits``, ``utilities::*``); + and Slam's own host-device types, including ``slam::Optional``. + - everywhere, including kernels + * - B + - ``std::optional``, ``std::string_view``, ``std::variant``, + ``std::ranges`` views/algorithms, ``std::vector``, ``std::map``, + exceptions, and iostreams. + - host only: builders, registries, ``isValid(verbose)``, and I/O + * - C + - Virtual functions on host-device types; standard containers held inside + view types; throwing accessors on host-device paths. + - nowhere (existing instances are migration targets) + +Why not a device ``std::optional``? +----------------------------------- + +``libcu++`` and ``libhipcxx`` provide device-capable ``tuple``/``optional``/ ``variant``/``span``, +but we cannot depend on them for our non-GPU sequential and OpenMP builds. +Instead, Slam uses small internal host-device types in the spirit of ``axom::StackArray``. + +:cpp:class:`axom::slam::Optional` is the one such type Slam adds for this purpose. +It is a trivially-copyable aggregate of an engaged flag and storage, +is ``AXOM_HOST_DEVICE`` throughout, and has no throwing ``value()`` accessor. +The contract is to check ``has_value()`` (or use ``value_or``) before dereferencing. + +The host-side counterpart is unchanged: host-only registries and find APIs +(for example :cpp:class:`axom::slam::FieldRegistry`) return ``std::optional``. + diff --git a/src/axom/slam/tests/CMakeLists.txt b/src/axom/slam/tests/CMakeLists.txt index faef2a0d01..b80ad1b126 100644 --- a/src/axom/slam/tests/CMakeLists.txt +++ b/src/axom/slam/tests/CMakeLists.txt @@ -39,6 +39,7 @@ set(gtest_slam_tests slam_AccessingRelationDataInMap.cpp # aux tests + slam_FieldRegistry.cpp slam_ModularInt.cpp slam_static_asserts.cpp diff --git a/src/axom/slam/tests/slam_FieldRegistry.cpp b/src/axom/slam/tests/slam_FieldRegistry.cpp new file mode 100644 index 0000000000..21ce5985c0 --- /dev/null +++ b/src/axom/slam/tests/slam_FieldRegistry.cpp @@ -0,0 +1,121 @@ +// 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) + +/** + * \file slam_FieldRegistry.cpp + * + * \brief Unit tests for slam::FieldRegistry, covering the std::optional find APIs + * and transparent (string_view) heterogeneous lookup + */ + +#include "gtest/gtest.h" + +#include "axom/slam/RangeSet.hpp" +#include "axom/slam/FieldRegistry.hpp" + +#include +#include +#include + +namespace +{ +namespace slam = axom::slam; + +using SetType = slam::PositionSet<>; +using ScalarRegistry = slam::FieldRegistry; +using IndexRegistry = slam::FieldRegistry; + +} // anonymous namespace + +TEST(slam_FieldRegistry, scalar_add_get_has) +{ + ScalarRegistry reg; + EXPECT_FALSE(reg.hasScalar("gravity")); + + reg.addScalar("gravity", 9.81); + EXPECT_TRUE(reg.hasScalar("gravity")); + EXPECT_DOUBLE_EQ(reg.getScalar("gravity"), 9.81); + + // getScalar returns a mutable reference + reg.getScalar("gravity") = 9.80665; + EXPECT_DOUBLE_EQ(reg.getScalar("gravity"), 9.80665); +} + +TEST(slam_FieldRegistry, find_scalar_optional) +{ + ScalarRegistry reg; + reg.addScalar("dt", 0.5); + + std::optional hit = reg.findScalar("dt"); + ASSERT_TRUE(hit.has_value()); + EXPECT_DOUBLE_EQ(*hit, 0.5); + + std::optional miss = reg.findScalar("missing"); + EXPECT_FALSE(miss.has_value()); + + // find does not insert: a missing query leaves the registry unchanged. + EXPECT_FALSE(reg.hasScalar("missing")); +} + +TEST(slam_FieldRegistry, find_field_optional) +{ + SetType s(10); + ScalarRegistry reg; + reg.addField("temperature", &s); + + auto hit = reg.findField("temperature"); + ASSERT_TRUE(hit.has_value()); + EXPECT_EQ(hit->get().size(), 10); + + auto miss = reg.findField("pressure"); + EXPECT_FALSE(miss.has_value()); + EXPECT_FALSE(reg.hasField("pressure")); +} + +TEST(slam_FieldRegistry, find_buffer_optional) +{ + IndexRegistry reg; + reg.addBuffer("indices", 7); + + auto hit = reg.findBuffer("indices"); + ASSERT_TRUE(hit.has_value()); + + auto miss = reg.findBuffer("absent"); + EXPECT_FALSE(miss.has_value()); +} + +TEST(slam_FieldRegistry, heterogeneous_lookup_no_allocation) +{ + ScalarRegistry reg; + reg.addScalar("energy", 1.0); + + // Query with a string_view and a const char* -- transparent comparison means + // neither constructs a temporary std::string key. + std::string_view sv = "energy"; + EXPECT_TRUE(reg.hasScalar(sv)); + EXPECT_TRUE(reg.hasScalar("energy")); + EXPECT_DOUBLE_EQ(reg.getScalar(sv), 1.0); + + std::optional hit = reg.findScalar(sv); + ASSERT_TRUE(hit.has_value()); + EXPECT_DOUBLE_EQ(*hit, 1.0); +} + +TEST(slam_FieldRegistry, nameless_keys_are_unique) +{ + SetType s(3); + ScalarRegistry reg; + + auto& f0 = reg.addNamelessField(&s); + auto& f1 = reg.addNamelessField(&s); + // Distinct fields were created (distinct storage). + EXPECT_NE(&f0, &f1); + + IndexRegistry ireg; + auto& b0 = ireg.addNamelessBuffer(2); + auto& b1 = ireg.addNamelessBuffer(2); + EXPECT_NE(&b0, &b1); +} diff --git a/src/axom/slam/tests/slam_static_asserts.cpp b/src/axom/slam/tests/slam_static_asserts.cpp index 244f28d442..10a9ba25f7 100644 --- a/src/axom/slam/tests/slam_static_asserts.cpp +++ b/src/axom/slam/tests/slam_static_asserts.cpp @@ -7,19 +7,19 @@ /** * \file slam_static_asserts.cpp * - * \brief Compile-time conformance harness for slam's constexpr arithmetic core. + * \brief Compile-time conformance harness for slam's constexpr arithmetic * * The tests in this file exercise the compile time set/value asserts * for sizes, offsets, strides, modular wraparound, and policy composition, so the checks cost * nothing at run time and run on every backend on every build. - * - * The single gtest case below exists only so the file participates in the test runner. - * If this file compiles, the invariants it encodes hold. + * + * Many of the checks in this file are static_asserts. If the file compiles, its invariants hold. */ #include "gtest/gtest.h" #include "axom/slam/ModularInt.hpp" +#include "axom/slam/Optional.hpp" #include "axom/slam/policies/SizePolicies.hpp" #include "axom/slam/policies/StridePolicies.hpp" #include "axom/slam/policies/OffsetPolicies.hpp" @@ -126,16 +126,49 @@ static_assert(incTwice(4) == 1, "++ twice from 4 mod 5 == 1"); static_assert(Mod5(2) == Mod5(7), "2 and 7 are equal mod 5"); static_assert(Mod5(2) != Mod5(3), "2 and 3 differ mod 5"); +//------------------------------------------------------------------------------ +// slam::Optional: a device-safe "maybe a value", fully constexpr. +//------------------------------------------------------------------------------ +static_assert(!slam::Optional().has_value(), "default Optional is disengaged"); +static_assert(slam::Optional(42).has_value(), "value-constructed Optional is engaged"); +static_assert(*slam::Optional(42) == 42, "engaged Optional yields its value"); +static_assert(slam::Optional(42).value_or(-1) == 42, "value_or returns the value when engaged"); +static_assert(slam::Optional().value_or(-1) == -1, "value_or returns fallback when empty"); +static_assert(static_cast(slam::Optional(0)), "engaged-with-zero is still engaged"); +static_assert(!static_cast(slam::Optional()), "disengaged converts to false"); +// Trivial copyability allows it to be captured on device +static_assert(std::is_trivially_copyable_v>, + "slam::Optional is trivially copyable (device-capturable)"); + } // anonymous namespace -//------------------------------------------------------------------------------ -// Checks that a constexpr value survives to run time. //------------------------------------------------------------------------------ TEST(slam_static_asserts, compile_time_value_holds) { + // Checks that a constexpr value survives to run time. + constexpr int wrapped = int(Mod5(7)); EXPECT_EQ(wrapped, 2); constexpr int idx = flatIndex(Off3().offset(), Stride4().stride(), 2); EXPECT_EQ(idx, 11); } + +TEST(slam_optional, engaged_and_disengaged) +{ + // Checks runtime API of slam::Optional + + axom::slam::Optional empty; + EXPECT_FALSE(empty.has_value()); + EXPECT_FALSE(static_cast(empty)); + EXPECT_DOUBLE_EQ(empty.value_or(2.5), 2.5); + + axom::slam::Optional full(3.25); + EXPECT_TRUE(full.has_value()); + EXPECT_TRUE(static_cast(full)); + EXPECT_DOUBLE_EQ(*full, 3.25); + EXPECT_DOUBLE_EQ(full.value_or(2.5), 3.25); + + *full = 9.0; + EXPECT_DOUBLE_EQ(*full, 9.0); +} From 0a95e362b70f34bb5aa5af5c7081e83d65dafca0 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 18 Jun 2026 15:51:48 -0700 Subject: [PATCH 585/986] slam: Adds some make_*_set and make_*_relation helpers to generate sets and relations Initial step to make it easier to use slam via helper functions that can deduce some of the underlying types. --- src/axom/slam/CMakeLists.txt | 2 + src/axom/slam/RelationBuilders.hpp | 220 ++++++++++++++++++++++ src/axom/slam/SetBuilders.hpp | 141 ++++++++++++++ src/axom/slam/examples/UserDocs.cpp | 23 +-- src/axom/slam/tests/CMakeLists.txt | 1 + src/axom/slam/tests/slam_make_helpers.cpp | 189 +++++++++++++++++++ 6 files changed, 565 insertions(+), 11 deletions(-) create mode 100644 src/axom/slam/RelationBuilders.hpp create mode 100644 src/axom/slam/SetBuilders.hpp create mode 100644 src/axom/slam/tests/slam_make_helpers.cpp diff --git a/src/axom/slam/CMakeLists.txt b/src/axom/slam/CMakeLists.txt index 1a6544d07d..00ae2e1ca1 100644 --- a/src/axom/slam/CMakeLists.txt +++ b/src/axom/slam/CMakeLists.txt @@ -53,6 +53,7 @@ set(slam_headers ProductSet.hpp DynamicSet.hpp Set.hpp + SetBuilders.hpp BivariateSet.hpp BitSet.hpp RelationSet.hpp @@ -60,6 +61,7 @@ set(slam_headers # SRM Relation headers Relation.hpp StaticRelation.hpp + RelationBuilders.hpp DynamicVariableRelation.hpp DynamicConstantRelation.hpp diff --git a/src/axom/slam/RelationBuilders.hpp b/src/axom/slam/RelationBuilders.hpp new file mode 100644 index 0000000000..c6499ef8fe --- /dev/null +++ b/src/axom/slam/RelationBuilders.hpp @@ -0,0 +1,220 @@ +// 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) + +/** + * \file RelationBuilders.hpp + * + * \brief Free-function "make" helpers that construct SLAM relations while + * deducing the from/to set types and the policy stack. + * + * A static, variable-cardinality (CSR-style) relation is configured by + * a cardinality policy, an indirection policy, and the from/to set types, + * then built through a chained RelationBuilder over begins/indices SetBuilders: + * + * \code + * using Rel = slam::StaticRelation, + * STLIndirection, FromSet, ToSet>; + * Rel r(Rel::RelationBuilder() + * .fromSet(&from).toSet(&to) + * .begins (Rel::RelationBuilder::BeginsSetBuilder ().size(off.size()).data(&off)) + * .indices(Rel::RelationBuilder::IndicesSetBuilder().size(idx.size()).data(&idx))); + * \endcode + * + * make_variable_relation collapses that to one call, deducing FromSet and ToSet from the set pointers: + * + * \code + * auto r = slam::make_variable_relation(&from, &to, offsets, indices); + * \endcode + * + * \note See SetBuilders.hpp for why these are free functions rather than class-template-argument + * deduction guides (the builder argument is a non-deduced nested-name context). + */ + +#ifndef SLAM_RELATION_BUILDERS_H_ +#define SLAM_RELATION_BUILDERS_H_ + +#include "axom/slam/StaticRelation.hpp" +#include "axom/slam/policies/CardinalityPolicies.hpp" +#include "axom/slam/policies/IndirectionPolicies.hpp" + +#include "axom/core/ArrayView.hpp" + +#include + +namespace axom::slam +{ +/// \name Relation construction helpers +/// \{ + +/*! + * \brief Make a static, variable-cardinality (CSR) relation + * from \a fromSet to \a toSet, backed by std::vector storage for its begins and indices. + * + * The from/to set types are deduced from the pointers. + The begins offsets and flat indices are taken from \a begins and \a indices + * (which must outlive the relation). The relation uses STL-vector indirection. + * + * \param fromSet pointer to the from-set (must outlive the relation) + * \param toSet pointer to the to-set (must outlive the relation) + * \param begins the per-from-element begin offsets (size == fromSet->size()+1) + * \param indices the flat to-set indices + * \return a StaticRelation with VariableCardinality and STLVector indirection + * + * \pre begins.size() == fromSet->size() + 1 + */ +template +auto make_variable_relation(FromSet* fromSet, + ToSet* toSet, + std::vector& begins, + std::vector& indices) +{ + using BeginsIndirection = policies::STLVectorIndirection; + using IndicesIndirection = policies::STLVectorIndirection; + using Cardinality = policies::VariableCardinality; + using RelationType = + StaticRelation; + using Builder = typename RelationType::RelationBuilder; + + return RelationType( + Builder() + .fromSet(fromSet) + .toSet(toSet) + .begins( + typename Builder::BeginsSetBuilder().size(static_cast(begins.size())).data(&begins)) + .indices( + typename Builder::IndicesSetBuilder().size(static_cast(indices.size())).data(&indices))); +} + +/*! + * \brief Make a static, variable-cardinality (CSR) relation backed by C array storage. + * + * \param fromSet pointer to the from-set (must outlive the relation) + * \param toSet pointer to the to-set (must outlive the relation) + * \param begins pointer to begin offsets (size == fromSet->size()+1; must outlive the relation) + * \param beginsSize number of begin offsets + * \param indices pointer to flat indices (must outlive the relation) + * \param indicesSize number of indices + */ +template +auto make_variable_relation(FromSet* fromSet, + ToSet* toSet, + PosType* begins, + PosType beginsSize, + ElemType* indices, + PosType indicesSize) +{ + using BeginsIndirection = policies::CArrayIndirection; + using IndicesIndirection = policies::CArrayIndirection; + using Cardinality = policies::VariableCardinality; + using RelationType = + StaticRelation; + using Builder = typename RelationType::RelationBuilder; + + return RelationType( + Builder() + .fromSet(fromSet) + .toSet(toSet) + .begins(typename Builder::BeginsSetBuilder().size(beginsSize).data(begins)) + .indices(typename Builder::IndicesSetBuilder().size(indicesSize).data(indices))); +} + +/*! + * \brief Make a static, variable-cardinality (CSR) relation backed by ArrayView storage. + * + * \param fromSet pointer to the from-set (must outlive the relation) + * \param toSet pointer to the to-set (must outlive the relation) + * \param begins array view of begin offsets (size == fromSet->size()+1) + * \param indices array view of flat indices + */ +template +auto make_variable_relation(FromSet* fromSet, + ToSet* toSet, + axom::ArrayView begins, + axom::ArrayView indices) +{ + using BeginsIndirection = policies::ArrayViewIndirection; + using IndicesIndirection = policies::ArrayViewIndirection; + using Cardinality = policies::VariableCardinality; + using RelationType = + StaticRelation; + using Builder = typename RelationType::RelationBuilder; + + return RelationType( + Builder() + .fromSet(fromSet) + .toSet(toSet) + .begins( + typename Builder::BeginsSetBuilder().size(static_cast(begins.size())).data(begins)) + .indices( + typename Builder::IndicesSetBuilder().size(static_cast(indices.size())).data(indices))); +} + +/*! + * \brief Make a static, constant-cardinality relation with a runtime stride, backed by std::vector indices. + * + * \param fromSet pointer to the from-set (must outlive the relation) + * \param toSet pointer to the to-set (must outlive the relation) + * \param stride number of to-set elements per from-set element + * \param indices flat indices (size == fromSet->size() * stride; must outlive the relation) + */ +template +auto make_constant_relation(FromSet* fromSet, ToSet* toSet, PosType stride, std::vector& indices) +{ + using IndicesIndirection = policies::STLVectorIndirection; + using CTy = policies::ConstantCardinality>; + using RelationType = StaticRelation; + using Builder = typename RelationType::RelationBuilder; + + auto begins_builder = typename Builder::BeginsSetBuilder().stride(stride); + return RelationType(Builder() + .fromSet(fromSet) + .toSet(toSet) + .begins(begins_builder) + .indices(typename Builder::IndicesSetBuilder() + .size(static_cast(indices.size())) + .data(&indices))); +} + +/*! + * \brief Make a static, constant-cardinality relation with a compile-time stride, backed by C array indices. + * + * \tparam STRIDE number of to-set elements per from-set element + */ +template +auto make_constant_relation_ct(FromSet* fromSet, ToSet* toSet, ElemType* indices, PosType indicesSize) +{ + using IndicesIndirection = policies::CArrayIndirection; + using StridePolicy = policies::CompileTimeStride(STRIDE)>; + using CTy = policies::ConstantCardinality; + using RelationType = StaticRelation; + using Builder = typename RelationType::RelationBuilder; + + return RelationType(Builder().fromSet(fromSet).toSet(toSet).indices( + typename Builder::IndicesSetBuilder().size(indicesSize).data(indices))); +} + +/// \} + +} // end namespace axom::slam + +#endif // SLAM_RELATION_BUILDERS_H_ diff --git a/src/axom/slam/SetBuilders.hpp b/src/axom/slam/SetBuilders.hpp new file mode 100644 index 0000000000..1964c6e74c --- /dev/null +++ b/src/axom/slam/SetBuilders.hpp @@ -0,0 +1,141 @@ +// 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) + +/** + * \file SetBuilders.hpp + * + * \brief Free-function "make" helpers that construct SLAM sets from a buffer or + * a range while deducing the full policy stack. + * + * SLAM's sets are configured by a long list of orthogonal policy template + * parameters. Spelling the full stack at every construction site is verbose: + * + * \code + * using Set = slam::ArrayViewIndirectionSet; + * Set s(Set::SetBuilder().size(v.size()).data(v)); + * \endcode + * + * The helpers here collapse that to a single call that deduces the element type + * from the buffer: + * + * \code + * auto s = slam::make_array_view_set(v); // -> ArrayViewIndirectionSet<.., double> + * \endcode + * + * \note On CTAD vs. helpers. A class-template-argument deduction guide cannot + * recover a set's policy stack from a SetBuilder argument: a guide parameter of + * the form `typename OrderedSet::SetBuilder` is a non-deduced context + * (the template arguments appear only as a nested-name-specifier), + * so `OrderedSet s(builder)` can never deduce. Deduction only works from a + * directly-named argument type such as `axom::ArrayView`. These free + * functions are therefore the portable way to get stack-deducing construction in C++17. + */ + +#ifndef SLAM_SET_BUILDERS_H_ +#define SLAM_SET_BUILDERS_H_ + +#include "axom/core/memory_management.hpp" + +#include "axom/slam/Utilities.hpp" +#include "axom/slam/RangeSet.hpp" +#include "axom/slam/IndirectionSet.hpp" + +#include +#include + +namespace axom::slam +{ +namespace detail +{ +template +struct type_identity +{ + using type = T; +}; + +template +using type_identity_t = typename type_identity::type; +} // namespace detail + +/// \name Set construction helpers +/// \brief Construct a SLAM set while deducing its policy stack from the buffer +/// or range. \a PosType defaults to slam's default position type and may be +/// supplied explicitly as the leading template argument. +/// \{ + +/*! + * \brief Make a contiguous range set \f$[0, size)\f$. + * \param size the number of elements + * \return a RangeSet + */ +template +RangeSet make_range_set(detail::type_identity_t size) +{ + return RangeSet(size); +} + +/*! + * \brief Make a contiguous range set \f$[lower, upper)\f$. + * \param lower the first element of the range + * \param upper one past the last element of the range + * \return a RangeSet + */ +template +RangeSet make_range_set(detail::type_identity_t lower, + detail::type_identity_t upper) +{ + return RangeSet(lower, upper); +} + +/*! + * \brief Make a set whose elements indirect through an axom::ArrayView. + * + * The element type is deduced from \a view; the set is device-capable + * (ArrayView indirection is host-device). The set's size matches the view. + * + * \param view the backing array view + * \return an ArrayViewIndirectionSet + */ +template +ArrayViewIndirectionSet make_array_view_set(axom::ArrayView view) +{ + using SetType = ArrayViewIndirectionSet; + return SetType(typename SetType::SetBuilder().size(static_cast(view.size())).data(view)); +} + +/*! + * \brief Make a set whose elements indirect through an std::vector. + * + * The element type is deduced from \a vec. The set's size matches the vector. + * \param vec the backing vector (must outlive the set) + * \return a VectorIndirectionSet + */ +template +VectorIndirectionSet make_vector_set(std::vector& vec) +{ + using SetType = VectorIndirectionSet; + return SetType(typename SetType::SetBuilder().size(static_cast(vec.size())).data(&vec)); +} + +/*! + * \brief Make a set whose elements indirect through a C array. + * + * \param data pointer to the backing buffer (must outlive the set) + * \param size the number of elements + * \return a CArrayIndirectionSet + */ +template +CArrayIndirectionSet make_c_array_set(T* data, detail::type_identity_t size) +{ + using SetType = CArrayIndirectionSet; + return SetType(typename SetType::SetBuilder().size(size).data(data)); +} + +/// \} + +} // end namespace axom::slam + +#endif // SLAM_SET_BUILDERS_H_ diff --git a/src/axom/slam/examples/UserDocs.cpp b/src/axom/slam/examples/UserDocs.cpp index 44ab36d3c2..e80e5b43ed 100644 --- a/src/axom/slam/examples/UserDocs.cpp +++ b/src/axom/slam/examples/UserDocs.cpp @@ -26,6 +26,8 @@ #include "axom/slam.hpp" // _quadmesh_example_import_header_end +#include "axom/slam/RelationBuilders.hpp" + #include #include #include @@ -176,23 +178,22 @@ struct SimpleQuadMesh { // _quadmesh_example_construct_bdry_relation_start // construct boundary relation from elements to vertices - using RelationBuilder = ElemToVertRelation::RelationBuilder; - bdry = RelationBuilder().fromSet(&elems).toSet(&verts).indices( - RelationBuilder::IndicesSetBuilder().size(static_cast(evInds.size())).data(evInds.data())); + bdry = slam::make_constant_relation_ct(&elems, + &verts, + evInds.data(), + static_cast(evInds.size())); // _quadmesh_example_construct_bdry_relation_end } { // _quadmesh_example_construct_cobdry_relation_start // construct coboundary relation from vertices to elements - using RelationBuilder = VertToElemRelation::RelationBuilder; - cobdry = RelationBuilder() - .fromSet(&verts) - .toSet(&elems) - .begins(RelationBuilder::BeginsSetBuilder().size(verts.size()).data(veBegins.data())) - .indices(RelationBuilder::IndicesSetBuilder() - .size(static_cast(veInds.size())) - .data(veInds.data())); + cobdry = slam::make_variable_relation(&verts, + &elems, + veBegins.data(), + static_cast(veBegins.size()), + veInds.data(), + static_cast(veInds.size())); // _quadmesh_example_construct_cobdry_relation_end } diff --git a/src/axom/slam/tests/CMakeLists.txt b/src/axom/slam/tests/CMakeLists.txt index b80ad1b126..95e7cf320d 100644 --- a/src/axom/slam/tests/CMakeLists.txt +++ b/src/axom/slam/tests/CMakeLists.txt @@ -40,6 +40,7 @@ set(gtest_slam_tests # aux tests slam_FieldRegistry.cpp + slam_make_helpers.cpp slam_ModularInt.cpp slam_static_asserts.cpp diff --git a/src/axom/slam/tests/slam_make_helpers.cpp b/src/axom/slam/tests/slam_make_helpers.cpp new file mode 100644 index 0000000000..7389983dcb --- /dev/null +++ b/src/axom/slam/tests/slam_make_helpers.cpp @@ -0,0 +1,189 @@ +// 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) + +/** + * \file slam_make_helpers.cpp + * + * \brief Unit tests for the make* set/relation construction helpers, + * which deduce a SLAM type's full policy stack from a buffer or range. + */ + +#include "gtest/gtest.h" + +#include "axom/core/Array.hpp" +#include "axom/slam/SetBuilders.hpp" +#include "axom/slam/RelationBuilders.hpp" + +#include +#include + +namespace +{ +namespace slam = axom::slam; +using Pos = slam::DefaultPositionType; +} // anonymous namespace + +TEST(slam_make_helpers, make_range_set_size) +{ + auto s = slam::make_range_set(5); + EXPECT_EQ(s.size(), 5); + EXPECT_EQ(s[0], 0); + EXPECT_EQ(s[4], 4); + + static_assert(std::is_same_v, + "make_range_set uses DefaultPositionType by default"); + static_assert(std::is_same_v, + "make_range_set uses DefaultElementType by default"); + + // The deduced type is the blessed RangeSet alias. + static_assert(std::is_same_v>, + "make_range_set(size) yields RangeSet"); +} + +TEST(slam_make_helpers, make_range_set_bounds) +{ + auto s = slam::make_range_set(3, 8); + EXPECT_EQ(s.size(), 5); + EXPECT_EQ(s[0], 3); + EXPECT_EQ(s[4], 7); + + static_assert(std::is_same_v, + "make_range_set(lower, upper) uses DefaultPositionType by default"); +} + +TEST(slam_make_helpers, make_range_set_explicit_position_type) +{ + auto s = slam::make_range_set(5); + static_assert(std::is_same_v, + "explicit PosType flows through make_range_set"); + EXPECT_EQ(s.size(), 5); +} + +TEST(slam_make_helpers, make_array_view_set_deduces_element_type) +{ + axom::Array data {10., 20., 30., 40.}; + axom::ArrayView view = data.view(); + + auto s = slam::make_array_view_set(view); + + // Element type double was deduced from the view. + static_assert(std::is_same_v>, + "make_array_view_set deduces ArrayViewIndirectionSet<.., double>"); + static_assert(std::is_same_v, + "make_array_view_set uses DefaultPositionType by default"); + + ASSERT_EQ(s.size(), 4); + EXPECT_TRUE(s.isValid()); + EXPECT_DOUBLE_EQ(s[0], 10.0); + EXPECT_DOUBLE_EQ(s[3], 40.0); +} + +TEST(slam_make_helpers, make_vector_set_deduces_element_type) +{ + std::vector vec {7, 8, 9}; + auto s = slam::make_vector_set(vec); + + static_assert(std::is_same_v>, + "make_vector_set deduces VectorIndirectionSet<.., int>"); + static_assert(std::is_same_v, + "make_vector_set uses DefaultPositionType by default"); + + ASSERT_EQ(s.size(), 3); + EXPECT_TRUE(s.isValid()); + EXPECT_EQ(s[0], 7); + EXPECT_EQ(s[2], 9); +} + +TEST(slam_make_helpers, make_carray_set) +{ + int data[3] = {2, 4, 6}; + auto s = slam::make_c_array_set(data, 3); + + static_assert(std::is_same_v>, + "make_c_array_set deduces CArrayIndirectionSet<.., int>"); + static_assert(std::is_same_v, + "make_c_array_set uses DefaultPositionType by default"); + + ASSERT_EQ(s.size(), 3); + EXPECT_TRUE(s.isValid()); + EXPECT_EQ(s[1], 4); +} + +TEST(slam_make_helpers, make_array_view_set_explicit_position_type) +{ + axom::Array data {1.5f, 2.5f}; + auto view = data.view(); + + // Position type can be supplied explicitly as the leading template argument. + auto s = slam::make_array_view_set(view); + static_assert(std::is_same_v>, + "explicit PosType flows through"); + EXPECT_EQ(s.size(), 2); +} + +TEST(slam_make_helpers, make_variable_relation) +{ + // from-set of 3 elements, to-set of 5 elements. + auto fromSet = slam::make_range_set(3); + auto toSet = slam::make_range_set(5); + + // CSR layout: element 0 -> {1,2}, element 1 -> {3}, element 2 -> {0,4} + std::vector begins {0, 2, 3, 5}; // size == fromSet.size() + 1 + std::vector indices {1, 2, 3, 0, 4}; + + auto rel = slam::make_variable_relation(&fromSet, &toSet, begins, indices); + + EXPECT_TRUE(rel.isValid()); + EXPECT_EQ(rel.size(0), 2); + EXPECT_EQ(rel.size(1), 1); + EXPECT_EQ(rel.size(2), 2); + + // Spot-check the related indices for from-element 2. + auto rel2 = rel[2]; + ASSERT_EQ(rel2.size(), 2); + EXPECT_EQ(rel2[0], 0); + EXPECT_EQ(rel2[1], 4); +} + +TEST(slam_make_helpers, make_constant_relation_runtime_stride) +{ + auto fromSet = slam::make_range_set(3); + auto toSet = slam::make_range_set(5); + + //from { 0 -> {1,2}; 1 -> {3,4}; from 2 -> {0,2} } + std::vector indices {1, 2, 3, 4, 0, 2}; + + auto rel = slam::make_constant_relation(&fromSet, &toSet, Pos {2}, indices); + + EXPECT_TRUE(rel.isValid()); + EXPECT_EQ(rel.size(0), 2); + EXPECT_EQ(rel.size(1), 2); + EXPECT_EQ(rel.size(2), 2); + + auto r1 = rel[1]; + ASSERT_EQ(r1.size(), 2); + EXPECT_EQ(r1[0], 3); + EXPECT_EQ(r1[1], 4); +} + +TEST(slam_make_helpers, make_constant_relation_compile_time_stride) +{ + auto fromSet = slam::make_range_set(2); + auto toSet = slam::make_range_set(5); + + Pos indices[4] = {0, 4, 1, 3}; + + auto rel = slam::make_constant_relation_ct<2>(&fromSet, &toSet, indices, Pos {4}); + + EXPECT_TRUE(rel.isValid()); + EXPECT_EQ(rel.size(0), 2); + EXPECT_EQ(rel.size(1), 2); + + auto r0 = rel[0]; + ASSERT_EQ(r0.size(), 2); + EXPECT_EQ(r0[0], 0); + EXPECT_EQ(r0[1], 4); +} From 8418564665aa77a1d396343bd743949bd8917bc7 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 18 Jun 2026 23:39:02 -0700 Subject: [PATCH 586/986] Moves the constexpr assert MACRO to core Also moves `axom::processAbort()` to its own file. We originally experimented with it in slam. --- src/axom/core/CMakeLists.txt | 2 + src/axom/core/Macros.hpp | 9 ++ src/axom/core/tests/CMakeLists.txt | 1 + src/axom/core/tests/core_constexpr_assert.hpp | 45 ++++++++ src/axom/core/tests/core_serial_main.cpp | 1 + src/axom/core/utilities/Abort.hpp | 30 ++++++ src/axom/core/utilities/ConstexprAssert.hpp | 102 ++++++++++++++++++ src/axom/core/utilities/Utilities.hpp | 6 +- src/axom/slam/CMakeLists.txt | 1 - src/axom/slam/ModularInt.hpp | 8 +- src/axom/slam/Optional.hpp | 5 +- src/axom/slam/policies/ConstexprAssert.hpp | 80 -------------- src/axom/slam/policies/ValuePolicies.hpp | 3 +- 13 files changed, 198 insertions(+), 95 deletions(-) create mode 100644 src/axom/core/tests/core_constexpr_assert.hpp create mode 100644 src/axom/core/utilities/Abort.hpp create mode 100644 src/axom/core/utilities/ConstexprAssert.hpp delete mode 100644 src/axom/slam/policies/ConstexprAssert.hpp diff --git a/src/axom/core/CMakeLists.txt b/src/axom/core/CMakeLists.txt index fd2832829d..7257760f3d 100644 --- a/src/axom/core/CMakeLists.txt +++ b/src/axom/core/CMakeLists.txt @@ -24,8 +24,10 @@ set(core_headers ## utilities utilities/About.hpp utilities/Annotations.hpp + utilities/Abort.hpp utilities/BitUtilities.hpp utilities/CommandLineUtilities.hpp + utilities/ConstexprAssert.hpp utilities/FileUtilities.hpp utilities/RAII.hpp utilities/Sorting.hpp diff --git a/src/axom/core/Macros.hpp b/src/axom/core/Macros.hpp index 53cfa646e1..c6e94138b7 100644 --- a/src/axom/core/Macros.hpp +++ b/src/axom/core/Macros.hpp @@ -416,4 +416,13 @@ template \ void GTEST_TEST_CLASS_NAME_(CaseName, TestName)::TestBody() +// Provides the definition for `axom::detail::constexprAssert` used by AXOM_CONSTEXPR_ASSERT. +#include "axom/core/utilities/ConstexprAssert.hpp" + +/*! + * \def AXOM_CONSTEXPR_ASSERT(EXP) + * \brief Assert \a EXP in a way that is valid inside constexpr functions. + */ +#define AXOM_CONSTEXPR_ASSERT(EXP) ::axom::detail::constexprAssert((EXP), #EXP, __FILE__, __LINE__) + #endif // AXOM_MACROS_HPP_ diff --git a/src/axom/core/tests/CMakeLists.txt b/src/axom/core/tests/CMakeLists.txt index ba627f713e..9307fe905d 100644 --- a/src/axom/core/tests/CMakeLists.txt +++ b/src/axom/core/tests/CMakeLists.txt @@ -22,6 +22,7 @@ set(core_serial_tests core_array_for_all.hpp core_utilities.hpp core_bit_utilities.hpp + core_constexpr_assert.hpp core_device_hash.hpp core_execution_for_all.hpp core_execution_scans.hpp diff --git a/src/axom/core/tests/core_constexpr_assert.hpp b/src/axom/core/tests/core_constexpr_assert.hpp new file mode 100644 index 0000000000..439cbbd3ee --- /dev/null +++ b/src/axom/core/tests/core_constexpr_assert.hpp @@ -0,0 +1,45 @@ +// 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_CORE_TESTS_CORE_CONSTEXPR_ASSERT_HPP_ +#define AXOM_CORE_TESTS_CORE_CONSTEXPR_ASSERT_HPP_ + +#include "gtest/gtest.h" + +#include "axom/core/Macros.hpp" + +namespace +{ +constexpr int checked_add(int a, int b) +{ + AXOM_CONSTEXPR_ASSERT(a >= 0); + AXOM_CONSTEXPR_ASSERT(b >= 0); + return a + b; +} + +static_assert(checked_add(1, 2) == 3, "AXOM_CONSTEXPR_ASSERT works in constant evaluation"); +} // namespace + +TEST(core_constexpr_assert, usable_in_constexpr) +{ + constexpr int v = checked_add(4, 5); + EXPECT_EQ(v, 9); +} + +TEST(core_constexpr_assert, runtime_true_noop) +{ + AXOM_CONSTEXPR_ASSERT(true); + SUCCEED(); +} + +#if defined(AXOM_DEBUG) && !defined(AXOM_DEVICE_CODE) +TEST(core_constexpr_assert, runtime_false_death) +{ + EXPECT_DEATH_IF_SUPPORTED([]() { AXOM_CONSTEXPR_ASSERT(false); }(), ".*"); +} +#endif + +#endif // AXOM_CORE_TESTS_CORE_CONSTEXPR_ASSERT_HPP_ diff --git a/src/axom/core/tests/core_serial_main.cpp b/src/axom/core/tests/core_serial_main.cpp index 940f036140..af7d5e17f0 100644 --- a/src/axom/core/tests/core_serial_main.cpp +++ b/src/axom/core/tests/core_serial_main.cpp @@ -14,6 +14,7 @@ #include "core_array_mapping.hpp" #include "core_utilities.hpp" #include "core_bit_utilities.hpp" +#include "core_constexpr_assert.hpp" #include "core_device_hash.hpp" #include "core_execution_for_all.hpp" #include "core_execution_scans.hpp" diff --git a/src/axom/core/utilities/Abort.hpp b/src/axom/core/utilities/Abort.hpp new file mode 100644 index 0000000000..6f07e80d2b --- /dev/null +++ b/src/axom/core/utilities/Abort.hpp @@ -0,0 +1,30 @@ +// 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) + +/*! + * \file Abort.hpp + * + * \brief Declarations for process-abort utilities. + * + * \note This header intentionally keeps dependencies minimal and is safe to include + * in low-level facilities that cannot depend on higher-level utilities headers. + */ + +#ifndef AXOM_CORE_UTILITIES_ABORT_HPP_ +#define AXOM_CORE_UTILITIES_ABORT_HPP_ + +#include "axom/config.hpp" + +namespace axom::utilities +{ +/*! + * \brief Gracefully aborts the application + */ +[[noreturn]] void processAbort(); +} // namespace axom::utilities + +#endif // AXOM_CORE_UTILITIES_ABORT_HPP_ + diff --git a/src/axom/core/utilities/ConstexprAssert.hpp b/src/axom/core/utilities/ConstexprAssert.hpp new file mode 100644 index 0000000000..b13c19c50c --- /dev/null +++ b/src/axom/core/utilities/ConstexprAssert.hpp @@ -0,0 +1,102 @@ +// 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) + +/*! + * \file ConstexprAssert.hpp + * + * \brief A constexpr-friendly assertion for host/device code. + * + * This header provides the low-level implementation routine + * `axom::detail::constexprAssert(bool, const char*, const char*, int)`. + * + * Most Axom code should use the convenience macro `AXOM_CONSTEXPR_ASSERT(EXP)` + * (defined in `axom/core/Macros.hpp`), which forwards expression text and source location + * to `axom::detail::constexprAssert(...)`. + * + * \note Rationale: Some debug assertion mechanisms use non-literal types (e.g. iostreams) + * and therefore cannot appear inside functions that must be usable in constant expressions under C++17. + * This facility provides an assertion-like hook that is valid in constexpr-capable code and remains + * device-compilable across Axom's supported backends. + * + * Semantics: + * - If the condition is false during constant evaluation, compilation fails. + * - If the condition is false at run time and AXOM_DEBUG is enabled (host code), the process aborts. + * - In non-debug runtime builds, it is a no-op. + * - In device compilation, it is a no-op (kernels cannot throw/abort portably). + */ + +#ifndef AXOM_CORE_UTILITIES_CONSTEXPR_ASSERT_HPP_ +#define AXOM_CORE_UTILITIES_CONSTEXPR_ASSERT_HPP_ + +#include "axom/config.hpp" +#include "axom/core/utilities/Abort.hpp" + +// This header is included by `axom/core/Macros.hpp`, so redefine the necessary macro(s) or spell them out +// +// The following is equivalent to AXOM_HOST_DEVICE +#if defined(__CUDACC__) || defined(__HIPCC__) + #define AXOM_DETAIL_CONSTEXPR_ASSERT_HOST_DEVICE __host__ __device__ +#else + #define AXOM_DETAIL_CONSTEXPR_ASSERT_HOST_DEVICE +#endif + +#if !defined(__CUDA_ARCH__) && !defined(__HIP_DEVICE_COMPILE__) + #include +#endif + +namespace axom::detail +{ +// This conditional is equivalent to !defined(AXOM_DEVICE_CODE) +#if !defined(__CUDA_ARCH__) && !defined(__HIP_DEVICE_COMPILE__) + +[[noreturn]] inline void constexprAssertFail(const char* /*expr*/, const char* /*file*/, int /*line*/) +{ + #if defined(AXOM_DEBUG) + // Provide a debugger-friendly trap site in debug builds. + assert(false && "Failed AXOM_CONSTEXPR_ASSERT"); + #endif + axom::utilities::processAbort(); +} + +AXOM_DETAIL_CONSTEXPR_ASSERT_HOST_DEVICE constexpr void constexprAssert(bool cond, + const char* expr, + const char* file, + int line) +{ + if(!cond) + { + #if defined(__clang__) || defined(__GNUC__) + if(__builtin_is_constant_evaluated()) + { + // Not constexpr: reaching this in constant evaluation is a hard error. + constexprAssertFail(expr, file, line); + } + #endif + + #if defined(AXOM_DEBUG) + constexprAssertFail(expr, file, line); + #else + static_cast(expr); + static_cast(file); + static_cast(line); + #endif + } +} + +#else // device compilation: no-op + +AXOM_DETAIL_CONSTEXPR_ASSERT_HOST_DEVICE constexpr void constexprAssert(bool, + const char*, + const char*, + int) +{ } + +#endif +} // namespace axom::detail + +#undef AXOM_DETAIL_CONSTEXPR_ASSERT_HOST_DEVICE + +#endif // AXOM_CORE_UTILITIES_CONSTEXPR_ASSERT_HPP_ diff --git a/src/axom/core/utilities/Utilities.hpp b/src/axom/core/utilities/Utilities.hpp index f4cb946923..cf58b68c1f 100644 --- a/src/axom/core/utilities/Utilities.hpp +++ b/src/axom/core/utilities/Utilities.hpp @@ -18,6 +18,7 @@ #include "axom/config.hpp" // for compile-time definitions #include "axom/core/Types.hpp" #include "axom/core/Macros.hpp" // for AXOM_STATIC_ASSERT +#include "axom/core/utilities/Abort.hpp" #include // for assert() #include // for log2() @@ -30,11 +31,6 @@ namespace axom { namespace utilities { -/*! - * \brief Gracefully aborts the application - */ -[[noreturn]] void processAbort(); - /*! * \brief Returns the absolute value of x. * \accelerated diff --git a/src/axom/slam/CMakeLists.txt b/src/axom/slam/CMakeLists.txt index 00ae2e1ca1..0de87c0d2c 100644 --- a/src/axom/slam/CMakeLists.txt +++ b/src/axom/slam/CMakeLists.txt @@ -32,7 +32,6 @@ set(slam_headers #SRM policies policies/CardinalityPolicies.hpp - policies/ConstexprAssert.hpp policies/ValuePolicies.hpp policies/SizePolicies.hpp policies/OffsetPolicies.hpp diff --git a/src/axom/slam/ModularInt.hpp b/src/axom/slam/ModularInt.hpp index 704cb51254..4251abcc3e 100644 --- a/src/axom/slam/ModularInt.hpp +++ b/src/axom/slam/ModularInt.hpp @@ -19,7 +19,7 @@ #include "axom/slic/interface/slic.hpp" #include "axom/slam/policies/SizePolicies.hpp" -#include "axom/slam/policies/ConstexprAssert.hpp" +#include "axom/core/Macros.hpp" namespace axom { @@ -45,7 +45,7 @@ class ModularInt : private SizePolicy : SizePolicy(modulusVal) , m_val(val) { - SLAM_CONSTEXPR_ASSERT(modulus() != 0); + AXOM_CONSTEXPR_ASSERT(modulus() != 0); normalize(); } @@ -56,7 +56,7 @@ class ModularInt : private SizePolicy */ constexpr ModularInt(const ModularInt& mi) : SizePolicy(mi), m_val(mi.m_val) { - SLAM_CONSTEXPR_ASSERT(modulus() != 0); + AXOM_CONSTEXPR_ASSERT(modulus() != 0); // For efficiency, we are assuming that argument mi is consistent and avoid normalization. // This assumption is tested in debug builds... @@ -232,7 +232,7 @@ class ModularInt : private SizePolicy verifyValue(); } - constexpr void verifyValue() { SLAM_CONSTEXPR_ASSERT(m_val >= 0 && m_val < modulus()); } + constexpr void verifyValue() { AXOM_CONSTEXPR_ASSERT(m_val >= 0 && m_val < modulus()); } private: int m_val; diff --git a/src/axom/slam/Optional.hpp b/src/axom/slam/Optional.hpp index d6204c4658..f7b729cc64 100644 --- a/src/axom/slam/Optional.hpp +++ b/src/axom/slam/Optional.hpp @@ -27,7 +27,6 @@ #define SLAM_OPTIONAL_H_ #include "axom/core/Macros.hpp" -#include "axom/slam/policies/ConstexprAssert.hpp" #include @@ -65,12 +64,12 @@ struct Optional */ AXOM_HOST_DEVICE constexpr const T& operator*() const { - SLAM_CONSTEXPR_ASSERT(m_engaged); + AXOM_CONSTEXPR_ASSERT(m_engaged); return m_value; } AXOM_HOST_DEVICE constexpr T& operator*() { - SLAM_CONSTEXPR_ASSERT(m_engaged); + AXOM_CONSTEXPR_ASSERT(m_engaged); return m_value; } diff --git a/src/axom/slam/policies/ConstexprAssert.hpp b/src/axom/slam/policies/ConstexprAssert.hpp deleted file mode 100644 index dd55642bd7..0000000000 --- a/src/axom/slam/policies/ConstexprAssert.hpp +++ /dev/null @@ -1,80 +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) - -/** - * \file ConstexprAssert.hpp - * - * \brief A constexpr-friendly assertion for slam's compile-time arithmetic core. - * - * slam's debug assertion (SLIC_ASSERT) expands, on the host debug path, - * to a block containing a `std::ostringstream` (a non-literal type) - * whih is ill-formed before C++23 -- so SLIC_ASSERT cannot appear in a function - * that must also be usable in a constant expression. - * - * SLAM_CONSTEXPR_ASSERT bridges the gap: - * - When the condition holds, it is a true no-op and is fully usable inside a - * constant expression. - * - At run time with AXOM_DEBUG enabled, a violated condition logs via SLIC - * and aborts, matching SLIC_ASSERT's runtime behavior. - * - During constant evaluation, a violated condition calls a non-constexpr - * function, which is a hard compile error pointing at the offending constant - * - When AXOM_DEBUG is off, it collapses to a no-op. - */ - -#ifndef SLAM_POLICIES_CONSTEXPR_ASSERT_H_ -#define SLAM_POLICIES_CONSTEXPR_ASSERT_H_ - -#include "axom/config.hpp" -#include "axom/core/Macros.hpp" - -#if defined(AXOM_DEBUG) && !defined(AXOM_DEVICE_CODE) - #include "axom/slic/interface/slic_macros.hpp" - #include "axom/core/utilities/Utilities.hpp" -#endif - -namespace axom::slam::detail -{ -#if defined(AXOM_DEBUG) && !defined(AXOM_DEVICE_CODE) - -/*! - * \brief Runtime handler for a failed constexpr assert (host debug only). - * - * Not marked constexpr: reaching this during constant evaluation is the - * mechanism by which a compile-time invariant violation becomes a hard error. - */ -[[noreturn]] inline void constexprAssertFail(const char* expr, const char* /*file*/, int /*line*/) -{ - SLIC_ERROR("Failed constexpr assert: " << expr); - // SLIC_ERROR may not abort if abort-on-error is disabled - axom::utilities::processAbort(); -} - -/*! - * \brief constexpr-safe assertion. - * \param cond the invariant that must hold - * \param expr stringized form of \a cond (for diagnostics) - */ -constexpr void constexprAssert(bool cond, const char* expr, const char* file, int line) -{ - cond ? void(0) : constexprAssertFail(expr, file, line); -} - -#else // release, or device code: no-op - -constexpr void constexprAssert(bool, const char*, const char*, int) { } - -#endif - -} // namespace axom::slam::detail - -/*! - * \def SLAM_CONSTEXPR_ASSERT(EXP) - * \brief Assert \a EXP in a way that is valid inside constexpr functions. - */ -#define SLAM_CONSTEXPR_ASSERT(EXP) \ - ::axom::slam::detail::constexprAssert((EXP), #EXP, __FILE__, __LINE__) - -#endif // SLAM_POLICIES_CONSTEXPR_ASSERT_H_ diff --git a/src/axom/slam/policies/ValuePolicies.hpp b/src/axom/slam/policies/ValuePolicies.hpp index 5f6148b0ec..ffc22395e7 100644 --- a/src/axom/slam/policies/ValuePolicies.hpp +++ b/src/axom/slam/policies/ValuePolicies.hpp @@ -41,7 +41,6 @@ #include "axom/core/Macros.hpp" #include "axom/slic.hpp" -#include "axom/slam/policies/ConstexprAssert.hpp" namespace axom::slam::policies { @@ -151,7 +150,7 @@ struct CompileTimeValue AXOM_HOST_DEVICE constexpr CompileTimeValue(IntType val = V) { AXOM_UNUSED_VAR(val); - SLAM_CONSTEXPR_ASSERT(val == V); + AXOM_CONSTEXPR_ASSERT(val == V); } AXOM_HOST_DEVICE constexpr IntType value() const { return V; } From 198b77628431decbefeb98f9813483205b482e72 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Fri, 19 Jun 2026 14:36:29 -0700 Subject: [PATCH 587/986] Adds axom::Optional for a device-based analog of std::optional Note: This is not a drop-in replacement, but allows an analog for an optional type within kernels. This was originally experimented with in slam. --- src/axom/core/CMakeLists.txt | 11 ++--- src/axom/{slam => core}/Optional.hpp | 47 +++++++++++---------- src/axom/core/tests/CMakeLists.txt | 1 + src/axom/core/tests/core_optional.hpp | 45 ++++++++++++++++++++ src/axom/core/tests/core_serial_main.cpp | 1 + src/axom/slam/CMakeLists.txt | 1 - src/axom/slam/docs/sphinx/portability.rst | 8 ++-- src/axom/slam/tests/slam_static_asserts.cpp | 41 +++++------------- 8 files changed, 93 insertions(+), 62 deletions(-) rename src/axom/{slam => core}/Optional.hpp (51%) create mode 100644 src/axom/core/tests/core_optional.hpp diff --git a/src/axom/core/CMakeLists.txt b/src/axom/core/CMakeLists.txt index 7257760f3d..0d394089a0 100644 --- a/src/axom/core/CMakeLists.txt +++ b/src/axom/core/CMakeLists.txt @@ -62,7 +62,10 @@ set(core_headers ArrayBase.hpp ArrayIteratorBase.hpp ArrayView.hpp - MDMapping.hpp + DeviceHash.hpp + FlatMap.hpp + FlatMapView.hpp + FlatMapUtil.hpp IndexedCollection.hpp ItemCollection.hpp IteratorBase.hpp @@ -70,12 +73,10 @@ set(core_headers Macros.hpp Map.hpp MapCollection.hpp - FlatMap.hpp - FlatMapView.hpp - FlatMapUtil.hpp - DeviceHash.hpp + MDMapping.hpp NumericArray.hpp NumericLimits.hpp + Optional.hpp Path.hpp RangeAdapter.hpp StackArray.hpp diff --git a/src/axom/slam/Optional.hpp b/src/axom/core/Optional.hpp similarity index 51% rename from src/axom/slam/Optional.hpp rename to src/axom/core/Optional.hpp index f7b729cc64..9f8a577a04 100644 --- a/src/axom/slam/Optional.hpp +++ b/src/axom/core/Optional.hpp @@ -4,44 +4,46 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -/** +/*! * \file Optional.hpp * - * \brief A minimal, host-device "maybe a value" type for slam. + * \brief A minimal, host/device "maybe a value" type. + * + * `std::optional` is a host-only facility in Axom's portability model + * and is not guaranteed to be available/usable in device code across all backends (SEQ/OMP/CUDA/HIP). * - * std::optional is a host-only facility in slam's portability model - * and is not guaranteed to be available/usable in device code - * across all of slam's backends (SEQ/OMP/CUDA/HIP). + * `axom::Optional` is a trivially-structured aggregate of `{storage, engaged flag}` + * that is `AXOM_HOST_DEVICE` throughout and has no throwing `value()` accessor. * - * slam::Optional is a trivially-structured aggregate of {engaged flag, storage} - * that is AXOM_HOST_DEVICE throughout and has no throwing value() accessor - * (querying an unengaged Optional in a kernel cannot throw, - * so the contract is "check has_value() first"; in debug host builds a violation asserts). + * The contract is to check `has_value()` before accessing the value. + * In host debug builds, a disengaged dereference asserts, and during constant evaluation it is a compile error. * - * It has sufficient functionality for a kernel-side optional (i.e to check if something is found) - * but is not a drop-in std::optional -- it does not have: exceptions, monadic and_then/transforms, - * or in-place construction machinery. + * This type is intentionally small and is not a drop-in replacement for `std::optional`. + * Specifically, it does not provide exceptions, monadic combinators, or in-place construction. */ -#ifndef SLAM_OPTIONAL_H_ -#define SLAM_OPTIONAL_H_ +#ifndef AXOM_OPTIONAL_HPP_ +#define AXOM_OPTIONAL_HPP_ #include "axom/core/Macros.hpp" #include -namespace axom::slam +namespace axom { /*! * \class Optional - * \brief Host-device "maybe a value of type T". + * \brief Host/device "maybe a value of type T". * - * \tparam T the (literal/trivially-copyable) value type. Intended for the - * small value and index types that flow through slam's device find APIs. + * \tparam T The value type. Intended for small, trivially-copyable types + * that are safe to capture and use in device kernels. */ template struct Optional { + static_assert(std::is_trivially_copyable_v, + "axom::Optional is intended for trivially-copyable value types"); + T m_value {}; bool m_engaged {false}; @@ -59,8 +61,9 @@ struct Optional /*! * \brief Access the contained value. - * \pre has_value() is true. There is no throwing accessor -- in host debug builds, - * a disengaged access asserts, and during constant evaluation it is a compile error. + * \pre has_value() is true. There is no throwing accessor. + * In host debug builds, a disengaged access asserts, + * and during constant evaluation it is a compile error. */ AXOM_HOST_DEVICE constexpr const T& operator*() const { @@ -80,6 +83,6 @@ struct Optional } }; -} // end namespace axom::slam +} // namespace axom -#endif // SLAM_OPTIONAL_H_ +#endif // AXOM_OPTIONAL_HPP_ diff --git a/src/axom/core/tests/CMakeLists.txt b/src/axom/core/tests/CMakeLists.txt index 9307fe905d..f009b7328a 100644 --- a/src/axom/core/tests/CMakeLists.txt +++ b/src/axom/core/tests/CMakeLists.txt @@ -23,6 +23,7 @@ set(core_serial_tests core_utilities.hpp core_bit_utilities.hpp core_constexpr_assert.hpp + core_optional.hpp core_device_hash.hpp core_execution_for_all.hpp core_execution_scans.hpp diff --git a/src/axom/core/tests/core_optional.hpp b/src/axom/core/tests/core_optional.hpp new file mode 100644 index 0000000000..e560cf0f01 --- /dev/null +++ b/src/axom/core/tests/core_optional.hpp @@ -0,0 +1,45 @@ +// 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_CORE_TESTS_CORE_OPTIONAL_HPP_ +#define AXOM_CORE_TESTS_CORE_OPTIONAL_HPP_ + +#include "gtest/gtest.h" + +#include "axom/core/Optional.hpp" + +namespace +{ +static_assert(!axom::Optional().has_value(), "default Optional is disengaged"); +static_assert(axom::Optional(42).has_value(), "value-constructed Optional is engaged"); +static_assert(*axom::Optional(42) == 42, "engaged Optional yields its value"); +static_assert(axom::Optional(42).value_or(-1) == 42, "value_or returns the value when engaged"); +static_assert(axom::Optional().value_or(-1) == -1, "value_or returns fallback when empty"); +static_assert(static_cast(axom::Optional(0)), "engaged-with-zero is still engaged"); +static_assert(!static_cast(axom::Optional()), "disengaged converts to false"); + +static_assert(std::is_trivially_copyable_v>, + "axom::Optional is trivially copyable (device-capturable)"); +} // namespace + +TEST(core_optional, engaged_and_disengaged) +{ + axom::Optional empty; + EXPECT_FALSE(empty.has_value()); + EXPECT_FALSE(static_cast(empty)); + EXPECT_DOUBLE_EQ(empty.value_or(2.5), 2.5); + + axom::Optional full(3.25); + EXPECT_TRUE(full.has_value()); + EXPECT_TRUE(static_cast(full)); + EXPECT_DOUBLE_EQ(*full, 3.25); + EXPECT_DOUBLE_EQ(full.value_or(2.5), 3.25); + + *full = 9.0; + EXPECT_DOUBLE_EQ(*full, 9.0); +} + +#endif // AXOM_CORE_TESTS_CORE_OPTIONAL_HPP_ diff --git a/src/axom/core/tests/core_serial_main.cpp b/src/axom/core/tests/core_serial_main.cpp index af7d5e17f0..3cb13e2878 100644 --- a/src/axom/core/tests/core_serial_main.cpp +++ b/src/axom/core/tests/core_serial_main.cpp @@ -15,6 +15,7 @@ #include "core_utilities.hpp" #include "core_bit_utilities.hpp" #include "core_constexpr_assert.hpp" +#include "core_optional.hpp" #include "core_device_hash.hpp" #include "core_execution_for_all.hpp" #include "core_execution_scans.hpp" diff --git a/src/axom/slam/CMakeLists.txt b/src/axom/slam/CMakeLists.txt index 0de87c0d2c..7ac0a7dc9d 100644 --- a/src/axom/slam/CMakeLists.txt +++ b/src/axom/slam/CMakeLists.txt @@ -28,7 +28,6 @@ set(slam_headers Utilities.hpp FieldRegistry.hpp ModularInt.hpp - Optional.hpp #SRM policies policies/CardinalityPolicies.hpp diff --git a/src/axom/slam/docs/sphinx/portability.rst b/src/axom/slam/docs/sphinx/portability.rst index 1e55a9be0e..5a726c3edb 100644 --- a/src/axom/slam/docs/sphinx/portability.rst +++ b/src/axom/slam/docs/sphinx/portability.rst @@ -26,9 +26,9 @@ and are therefore unconditionally kernel-safe. * - A - All compile-time language features (concepts, ``if constexpr``, class template argument deduction (CTAD), non-type template parameters, - fold expressions, type traits, ``constexpr`` evaluation); - Axom host-device types (``StackArray``, ``ArrayView``, ``NumericLimits``, ``utilities::*``); - and Slam's own host-device types, including ``slam::Optional``. + fold expressions, type traits, ``constexpr`` evaluation) + as well as Axom host-device types (``StackArray``, ``ArrayView``, + ``NumericLimits``, ``Optional``, ``utilities::*``). - everywhere, including kernels * - B - ``std::optional``, ``std::string_view``, ``std::variant``, @@ -47,7 +47,7 @@ Why not a device ``std::optional``? but we cannot depend on them for our non-GPU sequential and OpenMP builds. Instead, Slam uses small internal host-device types in the spirit of ``axom::StackArray``. -:cpp:class:`axom::slam::Optional` is the one such type Slam adds for this purpose. +:cpp:class:`axom::Optional` is the one such type Axom provides for this purpose. It is a trivially-copyable aggregate of an engaged flag and storage, is ``AXOM_HOST_DEVICE`` throughout, and has no throwing ``value()`` accessor. The contract is to check ``has_value()`` (or use ``value_or``) before dereferencing. diff --git a/src/axom/slam/tests/slam_static_asserts.cpp b/src/axom/slam/tests/slam_static_asserts.cpp index 10a9ba25f7..bb3ecc8035 100644 --- a/src/axom/slam/tests/slam_static_asserts.cpp +++ b/src/axom/slam/tests/slam_static_asserts.cpp @@ -18,8 +18,8 @@ #include "gtest/gtest.h" +#include "axom/core/Optional.hpp" #include "axom/slam/ModularInt.hpp" -#include "axom/slam/Optional.hpp" #include "axom/slam/policies/SizePolicies.hpp" #include "axom/slam/policies/StridePolicies.hpp" #include "axom/slam/policies/OffsetPolicies.hpp" @@ -127,18 +127,18 @@ static_assert(Mod5(2) == Mod5(7), "2 and 7 are equal mod 5"); static_assert(Mod5(2) != Mod5(3), "2 and 3 differ mod 5"); //------------------------------------------------------------------------------ -// slam::Optional: a device-safe "maybe a value", fully constexpr. +// axom::Optional: a device-safe "maybe a value", fully constexpr. //------------------------------------------------------------------------------ -static_assert(!slam::Optional().has_value(), "default Optional is disengaged"); -static_assert(slam::Optional(42).has_value(), "value-constructed Optional is engaged"); -static_assert(*slam::Optional(42) == 42, "engaged Optional yields its value"); -static_assert(slam::Optional(42).value_or(-1) == 42, "value_or returns the value when engaged"); -static_assert(slam::Optional().value_or(-1) == -1, "value_or returns fallback when empty"); -static_assert(static_cast(slam::Optional(0)), "engaged-with-zero is still engaged"); -static_assert(!static_cast(slam::Optional()), "disengaged converts to false"); +static_assert(!axom::Optional().has_value(), "default Optional is disengaged"); +static_assert(axom::Optional(42).has_value(), "value-constructed Optional is engaged"); +static_assert(*axom::Optional(42) == 42, "engaged Optional yields its value"); +static_assert(axom::Optional(42).value_or(-1) == 42, "value_or returns the value when engaged"); +static_assert(axom::Optional().value_or(-1) == -1, "value_or returns fallback when empty"); +static_assert(static_cast(axom::Optional(0)), "engaged-with-zero is still engaged"); +static_assert(!static_cast(axom::Optional()), "disengaged converts to false"); // Trivial copyability allows it to be captured on device -static_assert(std::is_trivially_copyable_v>, - "slam::Optional is trivially copyable (device-capturable)"); +static_assert(std::is_trivially_copyable_v>, + "axom::Optional is trivially copyable (device-capturable)"); } // anonymous namespace @@ -153,22 +153,3 @@ TEST(slam_static_asserts, compile_time_value_holds) constexpr int idx = flatIndex(Off3().offset(), Stride4().stride(), 2); EXPECT_EQ(idx, 11); } - -TEST(slam_optional, engaged_and_disengaged) -{ - // Checks runtime API of slam::Optional - - axom::slam::Optional empty; - EXPECT_FALSE(empty.has_value()); - EXPECT_FALSE(static_cast(empty)); - EXPECT_DOUBLE_EQ(empty.value_or(2.5), 2.5); - - axom::slam::Optional full(3.25); - EXPECT_TRUE(full.has_value()); - EXPECT_TRUE(static_cast(full)); - EXPECT_DOUBLE_EQ(*full, 3.25); - EXPECT_DOUBLE_EQ(full.value_or(2.5), 3.25); - - *full = 9.0; - EXPECT_DOUBLE_EQ(*full, 9.0); -} From 3c322631b6b5637a9490d672f4712b64d4bb2957 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Fri, 19 Jun 2026 14:54:55 -0700 Subject: [PATCH 588/986] slam: Fix the ElementType of our make_*_relation helpers The relation entries are Position types from the FromSet --- src/axom/slam/RelationBuilders.hpp | 14 +++++++------- src/axom/slam/tests/slam_make_helpers.cpp | 22 ++++++++++++++++++++++ 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/axom/slam/RelationBuilders.hpp b/src/axom/slam/RelationBuilders.hpp index c6499ef8fe..797a8e9414 100644 --- a/src/axom/slam/RelationBuilders.hpp +++ b/src/axom/slam/RelationBuilders.hpp @@ -54,8 +54,8 @@ namespace axom::slam * \brief Make a static, variable-cardinality (CSR) relation * from \a fromSet to \a toSet, backed by std::vector storage for its begins and indices. * - * The from/to set types are deduced from the pointers. - The begins offsets and flat indices are taken from \a begins and \a indices + * The from/to set types are deduced from the pointers. + * The begins offsets and flat indices (to-set positions) are taken from \a begins and \a indices * (which must outlive the relation). The relation uses STL-vector indirection. * * \param fromSet pointer to the from-set (must outlive the relation) @@ -69,7 +69,7 @@ namespace axom::slam template + typename ElemType = typename ToSet::PositionType> auto make_variable_relation(FromSet* fromSet, ToSet* toSet, std::vector& begins, @@ -105,7 +105,7 @@ auto make_variable_relation(FromSet* fromSet, template + typename ElemType = typename ToSet::PositionType> auto make_variable_relation(FromSet* fromSet, ToSet* toSet, PosType* begins, @@ -139,7 +139,7 @@ auto make_variable_relation(FromSet* fromSet, template + typename ElemType = typename ToSet::PositionType> auto make_variable_relation(FromSet* fromSet, ToSet* toSet, axom::ArrayView begins, @@ -173,7 +173,7 @@ auto make_variable_relation(FromSet* fromSet, template + typename ElemType = typename ToSet::PositionType> auto make_constant_relation(FromSet* fromSet, ToSet* toSet, PosType stride, std::vector& indices) { using IndicesIndirection = policies::STLVectorIndirection; @@ -200,7 +200,7 @@ template + typename ElemType = typename ToSet::PositionType> auto make_constant_relation_ct(FromSet* fromSet, ToSet* toSet, ElemType* indices, PosType indicesSize) { using IndicesIndirection = policies::CArrayIndirection; diff --git a/src/axom/slam/tests/slam_make_helpers.cpp b/src/axom/slam/tests/slam_make_helpers.cpp index 7389983dcb..e17a57da9b 100644 --- a/src/axom/slam/tests/slam_make_helpers.cpp +++ b/src/axom/slam/tests/slam_make_helpers.cpp @@ -148,6 +148,28 @@ TEST(slam_make_helpers, make_variable_relation) EXPECT_EQ(rel2[1], 4); } +TEST(slam_make_helpers, make_variable_relation_indices_are_to_set_positions) +{ + // toSet has element type != position type; relation indices should still be positions. + auto fromSet = slam::make_range_set(2); + + axom::Array values {10., 20., 30.}; + auto toSet = slam::make_array_view_set(values.view()); // ArrayViewIndirectionSet + + std::vector begins {0, 2, 3}; + std::vector indices {0, 2, 1}; // positions into toSet + + auto rel = slam::make_variable_relation(&fromSet, &toSet, begins, indices); + + static_assert(std::is_same_v, + "relation element type defaults to ToSet::PositionType"); + + auto r0 = rel[0]; + ASSERT_EQ(r0.size(), 2); + EXPECT_DOUBLE_EQ(toSet[r0[0]], 10.0); + EXPECT_DOUBLE_EQ(toSet[r0[1]], 30.0); +} + TEST(slam_make_helpers, make_constant_relation_runtime_stride) { auto fromSet = slam::make_range_set(3); From bcc1ac218d92d3e8e01c64cd862a6954edf1f724 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Fri, 19 Jun 2026 15:09:58 -0700 Subject: [PATCH 589/986] slam: Improves make_*_{set,relation} helper functions Adds missing helpers for axom::Array, and for references to arrays, vectors, etc.. Incorporates into a slam example. --- src/axom/slam/RelationBuilders.hpp | 329 ++++++++++++++++++++++ src/axom/slam/SetBuilders.hpp | 18 +- src/axom/slam/examples/HandleMesh.cpp | 6 +- src/axom/slam/tests/slam_make_helpers.cpp | 45 +++ 4 files changed, 393 insertions(+), 5 deletions(-) diff --git a/src/axom/slam/RelationBuilders.hpp b/src/axom/slam/RelationBuilders.hpp index 797a8e9414..d51007fc8e 100644 --- a/src/axom/slam/RelationBuilders.hpp +++ b/src/axom/slam/RelationBuilders.hpp @@ -92,6 +92,21 @@ auto make_variable_relation(FromSet* fromSet, typename Builder::IndicesSetBuilder().size(static_cast(indices.size())).data(&indices))); } +/*! + * \brief Reference overload for make_variable_relation (std::vector-backed). + */ +template +auto make_variable_relation(FromSet& fromSet, + ToSet& toSet, + std::vector& begins, + std::vector& indices) +{ + return make_variable_relation(&fromSet, &toSet, begins, indices); +} + /*! * \brief Make a static, variable-cardinality (CSR) relation backed by C array storage. * @@ -128,6 +143,23 @@ auto make_variable_relation(FromSet* fromSet, .indices(typename Builder::IndicesSetBuilder().size(indicesSize).data(indices))); } +/*! + * \brief Reference overload for make_variable_relation (C-array-backed). + */ +template +auto make_variable_relation(FromSet& fromSet, + ToSet& toSet, + PosType* begins, + PosType beginsSize, + ElemType* indices, + PosType indicesSize) +{ + return make_variable_relation(&fromSet, &toSet, begins, beginsSize, indices, indicesSize); +} + /*! * \brief Make a static, variable-cardinality (CSR) relation backed by ArrayView storage. * @@ -162,6 +194,70 @@ auto make_variable_relation(FromSet* fromSet, typename Builder::IndicesSetBuilder().size(static_cast(indices.size())).data(indices))); } +/*! + * \brief Reference overload for make_variable_relation (ArrayView-backed). + */ +template +auto make_variable_relation(FromSet& fromSet, + ToSet& toSet, + axom::ArrayView begins, + axom::ArrayView indices) +{ + return make_variable_relation(&fromSet, &toSet, begins, indices); +} + +/*! + * \brief Make a static, variable-cardinality (CSR) relation backed by axom::Array storage. + * + * \param fromSet pointer to the from-set (must outlive the relation) + * \param toSet pointer to the to-set (must outlive the relation) + * \param begins array of begin offsets (size == fromSet->size()+1; must outlive the relation) + * \param indices array of flat indices (to-set positions; must outlive the relation) + */ +template +auto make_variable_relation(FromSet* fromSet, + ToSet* toSet, + axom::Array& begins, + axom::Array& indices) +{ + using BeginsIndirection = policies::ArrayIndirection; + using IndicesIndirection = policies::ArrayIndirection; + using Cardinality = policies::VariableCardinality; + using RelationType = + StaticRelation; + using Builder = typename RelationType::RelationBuilder; + + return RelationType( + Builder() + .fromSet(fromSet) + .toSet(toSet) + .begins( + typename Builder::BeginsSetBuilder().size(static_cast(begins.size())).data(&begins)) + .indices( + typename Builder::IndicesSetBuilder().size(static_cast(indices.size())).data(&indices))); +} + +/*! + * \brief Reference overload for make_variable_relation (axom::Array-backed). + */ +template +auto make_variable_relation(FromSet& fromSet, + ToSet& toSet, + axom::Array& begins, + axom::Array& indices) +{ + return make_variable_relation(&fromSet, &toSet, begins, indices); +} + /*! * \brief Make a static, constant-cardinality relation with a runtime stride, backed by std::vector indices. * @@ -191,6 +287,139 @@ auto make_constant_relation(FromSet* fromSet, ToSet* toSet, PosType stride, std: .data(&indices))); } +/*! + * \brief Reference overload for make_constant_relation (std::vector-backed). + */ +template +auto make_constant_relation(FromSet& fromSet, ToSet& toSet, PosType stride, std::vector& indices) +{ + return make_constant_relation(&fromSet, &toSet, stride, indices); +} + +/*! + * \brief Make a static, constant-cardinality relation with a runtime stride, backed by C array indices. + */ +template +auto make_constant_relation(FromSet* fromSet, + ToSet* toSet, + PosType stride, + ElemType* indices, + PosType indicesSize) +{ + using IndicesIndirection = policies::CArrayIndirection; + using CTy = policies::ConstantCardinality>; + using RelationType = StaticRelation; + using Builder = typename RelationType::RelationBuilder; + + auto begins_builder = typename Builder::BeginsSetBuilder().stride(stride); + return RelationType( + Builder() + .fromSet(fromSet) + .toSet(toSet) + .begins(begins_builder) + .indices(typename Builder::IndicesSetBuilder().size(indicesSize).data(indices))); +} + +/*! + * \brief Reference overload for make_constant_relation (C-array-backed). + */ +template +auto make_constant_relation(FromSet& fromSet, + ToSet& toSet, + PosType stride, + ElemType* indices, + PosType indicesSize) +{ + return make_constant_relation(&fromSet, &toSet, stride, indices, indicesSize); +} + +/*! + * \brief Make a static, constant-cardinality relation with a runtime stride, backed by ArrayView indices. + */ +template +auto make_constant_relation(FromSet* fromSet, + ToSet* toSet, + PosType stride, + axom::ArrayView indices) +{ + using IndicesIndirection = policies::ArrayViewIndirection; + using CTy = policies::ConstantCardinality>; + using RelationType = StaticRelation; + using Builder = typename RelationType::RelationBuilder; + + auto begins_builder = typename Builder::BeginsSetBuilder().stride(stride); + return RelationType( + Builder() + .fromSet(fromSet) + .toSet(toSet) + .begins(begins_builder) + .indices( + typename Builder::IndicesSetBuilder().size(static_cast(indices.size())).data(indices))); +} + +/*! + * \brief Reference overload for make_constant_relation (ArrayView-backed). + */ +template +auto make_constant_relation(FromSet& fromSet, + ToSet& toSet, + PosType stride, + axom::ArrayView indices) +{ + return make_constant_relation(&fromSet, &toSet, stride, indices); +} + +/*! + * \brief Make a static, constant-cardinality relation with a runtime stride, backed by axom::Array indices. + */ +template +auto make_constant_relation(FromSet* fromSet, ToSet* toSet, PosType stride, axom::Array& indices) +{ + using IndicesIndirection = policies::ArrayIndirection; + using CTy = policies::ConstantCardinality>; + using RelationType = StaticRelation; + using Builder = typename RelationType::RelationBuilder; + + auto begins_builder = typename Builder::BeginsSetBuilder().stride(stride); + return RelationType(Builder() + .fromSet(fromSet) + .toSet(toSet) + .begins(begins_builder) + .indices(typename Builder::IndicesSetBuilder() + .size(static_cast(indices.size())) + .data(&indices))); +} + +/*! + * \brief Reference overload for make_constant_relation (axom::Array-backed). + */ +template +auto make_constant_relation(FromSet& fromSet, ToSet& toSet, PosType stride, axom::Array& indices) +{ + return make_constant_relation(&fromSet, &toSet, stride, indices); +} + /*! * \brief Make a static, constant-cardinality relation with a compile-time stride, backed by C array indices. * @@ -213,6 +442,106 @@ auto make_constant_relation_ct(FromSet* fromSet, ToSet* toSet, ElemType* indices typename Builder::IndicesSetBuilder().size(indicesSize).data(indices))); } +/*! + * \brief Reference overload for make_constant_relation_ct (C-array-backed). + */ +template +auto make_constant_relation_ct(FromSet& fromSet, ToSet& toSet, ElemType* indices, PosType indicesSize) +{ + return make_constant_relation_ct(&fromSet, &toSet, indices, indicesSize); +} + +/*! + * \brief Make a static, constant-cardinality relation with a compile-time stride, backed by std::vector indices. + */ +template +auto make_constant_relation_ct(FromSet* fromSet, ToSet* toSet, std::vector& indices) +{ + return make_constant_relation_ct(fromSet, + toSet, + indices.data(), + static_cast(indices.size())); +} + +/*! + * \brief Reference overload for make_constant_relation_ct (std::vector-backed). + */ +template +auto make_constant_relation_ct(FromSet& fromSet, ToSet& toSet, std::vector& indices) +{ + return make_constant_relation_ct(&fromSet, &toSet, indices); +} + +/*! + * \brief Make a static, constant-cardinality relation with a compile-time stride, backed by ArrayView indices. + */ +template +auto make_constant_relation_ct(FromSet* fromSet, ToSet* toSet, axom::ArrayView indices) +{ + return make_constant_relation_ct(fromSet, + toSet, + indices.data(), + static_cast(indices.size())); +} + +/*! + * \brief Reference overload for make_constant_relation_ct (ArrayView-backed). + */ +template +auto make_constant_relation_ct(FromSet& fromSet, ToSet& toSet, axom::ArrayView indices) +{ + return make_constant_relation_ct(&fromSet, &toSet, indices); +} + +/*! + * \brief Make a static, constant-cardinality relation with a compile-time stride, backed by axom::Array indices. + */ +template +auto make_constant_relation_ct(FromSet* fromSet, ToSet* toSet, axom::Array& indices) +{ + return make_constant_relation_ct(fromSet, + toSet, + indices.data(), + static_cast(indices.size())); +} + +/*! + * \brief Reference overload for make_constant_relation_ct (axom::Array-backed). + */ +template +auto make_constant_relation_ct(FromSet& fromSet, ToSet& toSet, axom::Array& indices) +{ + return make_constant_relation_ct(&fromSet, &toSet, indices); +} + /// \} } // end namespace axom::slam diff --git a/src/axom/slam/SetBuilders.hpp b/src/axom/slam/SetBuilders.hpp index 1964c6e74c..0eb21d3152 100644 --- a/src/axom/slam/SetBuilders.hpp +++ b/src/axom/slam/SetBuilders.hpp @@ -37,7 +37,7 @@ #ifndef SLAM_SET_BUILDERS_H_ #define SLAM_SET_BUILDERS_H_ -#include "axom/core/memory_management.hpp" +#include "axom/core/Array.hpp" #include "axom/slam/Utilities.hpp" #include "axom/slam/RangeSet.hpp" @@ -120,6 +120,22 @@ VectorIndirectionSet make_vector_set(std::vector& vec) return SetType(typename SetType::SetBuilder().size(static_cast(vec.size())).data(&vec)); } +/*! + * \brief Make a set whose elements indirect through an axom::Array. + * + * The element type is deduced from \a arr; the set is device-capable + * (axom::Array indirection is host-device). The set's size matches the array. + * + * \param arr the backing array (must outlive the set) + * \return an ArrayIndirectionSet + */ +template +ArrayIndirectionSet make_array_set(axom::Array& arr) +{ + using SetType = ArrayIndirectionSet; + return SetType(typename SetType::SetBuilder().size(static_cast(arr.size())).data(&arr)); +} + /*! * \brief Make a set whose elements indirect through a C array. * diff --git a/src/axom/slam/examples/HandleMesh.cpp b/src/axom/slam/examples/HandleMesh.cpp index 6ca033f284..728eaad89f 100644 --- a/src/axom/slam/examples/HandleMesh.cpp +++ b/src/axom/slam/examples/HandleMesh.cpp @@ -69,10 +69,8 @@ int main(int, char**) const int sz = 5; HandleSet::IndirectionBufferType vecHandle(sz); - // Create a set of handles - HandleSet hSet = HandleSet::SetBuilder() // - .size(sz) // - .data(&vecHandle); + // Create a set of handles (helper deduces the set policy stack) + HandleSet hSet = slam::make_vector_set(vecHandle); // Add handles with (somewhat) arbitrary IDs to the set for(auto i : hSet.positions()) diff --git a/src/axom/slam/tests/slam_make_helpers.cpp b/src/axom/slam/tests/slam_make_helpers.cpp index e17a57da9b..12bb009abd 100644 --- a/src/axom/slam/tests/slam_make_helpers.cpp +++ b/src/axom/slam/tests/slam_make_helpers.cpp @@ -97,6 +97,22 @@ TEST(slam_make_helpers, make_vector_set_deduces_element_type) EXPECT_EQ(s[2], 9); } +TEST(slam_make_helpers, make_array_set_deduces_element_type) +{ + axom::Array data {1.0, 2.0, 3.0}; + auto s = slam::make_array_set(data); + + static_assert(std::is_same_v>, + "make_array_set deduces ArrayIndirectionSet<.., double>"); + static_assert(std::is_same_v, + "make_array_set uses DefaultPositionType by default"); + + ASSERT_EQ(s.size(), 3); + EXPECT_TRUE(s.isValid()); + EXPECT_DOUBLE_EQ(s[0], 1.0); + EXPECT_DOUBLE_EQ(s[2], 3.0); +} + TEST(slam_make_helpers, make_carray_set) { int data[3] = {2, 4, 6}; @@ -148,6 +164,22 @@ TEST(slam_make_helpers, make_variable_relation) EXPECT_EQ(rel2[1], 4); } +TEST(slam_make_helpers, make_variable_relation_axom_array_buffers) +{ + auto fromSet = slam::make_range_set(3); + auto toSet = slam::make_range_set(5); + + axom::Array begins {0, 2, 3, 5}; + axom::Array indices {1, 2, 3, 0, 4}; + + auto rel = slam::make_variable_relation(&fromSet, &toSet, begins, indices); + + EXPECT_TRUE(rel.isValid()); + EXPECT_EQ(rel.size(0), 2); + EXPECT_EQ(rel.size(1), 1); + EXPECT_EQ(rel.size(2), 2); +} + TEST(slam_make_helpers, make_variable_relation_indices_are_to_set_positions) { // toSet has element type != position type; relation indices should still be positions. @@ -191,6 +223,19 @@ TEST(slam_make_helpers, make_constant_relation_runtime_stride) EXPECT_EQ(r1[1], 4); } +TEST(slam_make_helpers, make_constant_relation_runtime_stride_array_view) +{ + auto fromSet = slam::make_range_set(2); + auto toSet = slam::make_range_set(5); + + axom::Array indices {0, 4, 1, 3}; + auto rel = slam::make_constant_relation(&fromSet, &toSet, Pos {2}, indices.view()); + + EXPECT_TRUE(rel.isValid()); + EXPECT_EQ(rel.size(0), 2); + EXPECT_EQ(rel.size(1), 2); +} + TEST(slam_make_helpers, make_constant_relation_compile_time_stride) { auto fromSet = slam::make_range_set(2); From b63ee4f861df3b1db9c5ce417d490bf3940563ca Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Fri, 19 Jun 2026 15:22:05 -0700 Subject: [PATCH 590/986] slam: Renames set helpers based on category, e.g. make_indirection_set() --- src/axom/slam/SetBuilders.hpp | 18 +++++++-------- src/axom/slam/examples/HandleMesh.cpp | 2 +- src/axom/slam/tests/slam_make_helpers.cpp | 28 +++++++++++------------ 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/axom/slam/SetBuilders.hpp b/src/axom/slam/SetBuilders.hpp index 0eb21d3152..44302336ec 100644 --- a/src/axom/slam/SetBuilders.hpp +++ b/src/axom/slam/SetBuilders.hpp @@ -22,7 +22,7 @@ * from the buffer: * * \code - * auto s = slam::make_array_view_set(v); // -> ArrayViewIndirectionSet<.., double> + * auto s = slam::make_indirection_set(v); // -> ArrayViewIndirectionSet<.., double> * \endcode * * \note On CTAD vs. helpers. A class-template-argument deduction guide cannot @@ -91,7 +91,7 @@ RangeSet make_range_set(detail::type_identity_t lowe } /*! - * \brief Make a set whose elements indirect through an axom::ArrayView. + * \brief Make an indirection set whose elements indirect through an axom::ArrayView. * * The element type is deduced from \a view; the set is device-capable * (ArrayView indirection is host-device). The set's size matches the view. @@ -100,28 +100,28 @@ RangeSet make_range_set(detail::type_identity_t lowe * \return an ArrayViewIndirectionSet */ template -ArrayViewIndirectionSet make_array_view_set(axom::ArrayView view) +ArrayViewIndirectionSet make_indirection_set(axom::ArrayView view) { using SetType = ArrayViewIndirectionSet; return SetType(typename SetType::SetBuilder().size(static_cast(view.size())).data(view)); } /*! - * \brief Make a set whose elements indirect through an std::vector. + * \brief Make an indirection set whose elements indirect through an std::vector. * * The element type is deduced from \a vec. The set's size matches the vector. * \param vec the backing vector (must outlive the set) * \return a VectorIndirectionSet */ template -VectorIndirectionSet make_vector_set(std::vector& vec) +VectorIndirectionSet make_indirection_set(std::vector& vec) { using SetType = VectorIndirectionSet; return SetType(typename SetType::SetBuilder().size(static_cast(vec.size())).data(&vec)); } /*! - * \brief Make a set whose elements indirect through an axom::Array. + * \brief Make an indirection set whose elements indirect through an axom::Array. * * The element type is deduced from \a arr; the set is device-capable * (axom::Array indirection is host-device). The set's size matches the array. @@ -130,21 +130,21 @@ VectorIndirectionSet make_vector_set(std::vector& vec) * \return an ArrayIndirectionSet */ template -ArrayIndirectionSet make_array_set(axom::Array& arr) +ArrayIndirectionSet make_indirection_set(axom::Array& arr) { using SetType = ArrayIndirectionSet; return SetType(typename SetType::SetBuilder().size(static_cast(arr.size())).data(&arr)); } /*! - * \brief Make a set whose elements indirect through a C array. + * \brief Make an indirection set whose elements indirect through a C array. * * \param data pointer to the backing buffer (must outlive the set) * \param size the number of elements * \return a CArrayIndirectionSet */ template -CArrayIndirectionSet make_c_array_set(T* data, detail::type_identity_t size) +CArrayIndirectionSet make_indirection_set(T* data, detail::type_identity_t size) { using SetType = CArrayIndirectionSet; return SetType(typename SetType::SetBuilder().size(size).data(data)); diff --git a/src/axom/slam/examples/HandleMesh.cpp b/src/axom/slam/examples/HandleMesh.cpp index 728eaad89f..ef8e93c729 100644 --- a/src/axom/slam/examples/HandleMesh.cpp +++ b/src/axom/slam/examples/HandleMesh.cpp @@ -70,7 +70,7 @@ int main(int, char**) HandleSet::IndirectionBufferType vecHandle(sz); // Create a set of handles (helper deduces the set policy stack) - HandleSet hSet = slam::make_vector_set(vecHandle); + HandleSet hSet = slam::make_indirection_set(vecHandle); // Add handles with (somewhat) arbitrary IDs to the set for(auto i : hSet.positions()) diff --git a/src/axom/slam/tests/slam_make_helpers.cpp b/src/axom/slam/tests/slam_make_helpers.cpp index 12bb009abd..8d42496246 100644 --- a/src/axom/slam/tests/slam_make_helpers.cpp +++ b/src/axom/slam/tests/slam_make_helpers.cpp @@ -67,13 +67,13 @@ TEST(slam_make_helpers, make_array_view_set_deduces_element_type) axom::Array data {10., 20., 30., 40.}; axom::ArrayView view = data.view(); - auto s = slam::make_array_view_set(view); + auto s = slam::make_indirection_set(view); // Element type double was deduced from the view. static_assert(std::is_same_v>, - "make_array_view_set deduces ArrayViewIndirectionSet<.., double>"); + "make_indirection_set(ArrayView) deduces ArrayViewIndirectionSet<.., double>"); static_assert(std::is_same_v, - "make_array_view_set uses DefaultPositionType by default"); + "make_indirection_set(ArrayView) uses DefaultPositionType by default"); ASSERT_EQ(s.size(), 4); EXPECT_TRUE(s.isValid()); @@ -84,12 +84,12 @@ TEST(slam_make_helpers, make_array_view_set_deduces_element_type) TEST(slam_make_helpers, make_vector_set_deduces_element_type) { std::vector vec {7, 8, 9}; - auto s = slam::make_vector_set(vec); + auto s = slam::make_indirection_set(vec); static_assert(std::is_same_v>, - "make_vector_set deduces VectorIndirectionSet<.., int>"); + "make_indirection_set(vector) deduces VectorIndirectionSet<.., int>"); static_assert(std::is_same_v, - "make_vector_set uses DefaultPositionType by default"); + "make_indirection_set(vector) uses DefaultPositionType by default"); ASSERT_EQ(s.size(), 3); EXPECT_TRUE(s.isValid()); @@ -100,12 +100,12 @@ TEST(slam_make_helpers, make_vector_set_deduces_element_type) TEST(slam_make_helpers, make_array_set_deduces_element_type) { axom::Array data {1.0, 2.0, 3.0}; - auto s = slam::make_array_set(data); + auto s = slam::make_indirection_set(data); static_assert(std::is_same_v>, - "make_array_set deduces ArrayIndirectionSet<.., double>"); + "make_indirection_set(Array) deduces ArrayIndirectionSet<.., double>"); static_assert(std::is_same_v, - "make_array_set uses DefaultPositionType by default"); + "make_indirection_set(Array) uses DefaultPositionType by default"); ASSERT_EQ(s.size(), 3); EXPECT_TRUE(s.isValid()); @@ -116,12 +116,12 @@ TEST(slam_make_helpers, make_array_set_deduces_element_type) TEST(slam_make_helpers, make_carray_set) { int data[3] = {2, 4, 6}; - auto s = slam::make_c_array_set(data, 3); + auto s = slam::make_indirection_set(data, 3); static_assert(std::is_same_v>, - "make_c_array_set deduces CArrayIndirectionSet<.., int>"); + "make_indirection_set(C array) deduces CArrayIndirectionSet<.., int>"); static_assert(std::is_same_v, - "make_c_array_set uses DefaultPositionType by default"); + "make_indirection_set(C array) uses DefaultPositionType by default"); ASSERT_EQ(s.size(), 3); EXPECT_TRUE(s.isValid()); @@ -134,7 +134,7 @@ TEST(slam_make_helpers, make_array_view_set_explicit_position_type) auto view = data.view(); // Position type can be supplied explicitly as the leading template argument. - auto s = slam::make_array_view_set(view); + auto s = slam::make_indirection_set(view); static_assert(std::is_same_v>, "explicit PosType flows through"); EXPECT_EQ(s.size(), 2); @@ -186,7 +186,7 @@ TEST(slam_make_helpers, make_variable_relation_indices_are_to_set_positions) auto fromSet = slam::make_range_set(2); axom::Array values {10., 20., 30.}; - auto toSet = slam::make_array_view_set(values.view()); // ArrayViewIndirectionSet + auto toSet = slam::make_indirection_set(values.view()); // ArrayViewIndirectionSet std::vector begins {0, 2, 3}; std::vector indices {0, 2, 1}; // positions into toSet From f496fdf978060e1c7ae9308708461cbe674fb0f8 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Fri, 19 Jun 2026 16:41:39 -0700 Subject: [PATCH 591/986] slam: Adds make_map helper functions --- src/axom/slam/CMakeLists.txt | 1 + src/axom/slam/Map.hpp | 18 +++++ src/axom/slam/MapBuilders.hpp | 116 +++++++++++++++++++++++++++ src/axom/slam/RelationBuilders.hpp | 12 ++- src/axom/slam/tests/slam_map_Map.cpp | 71 ++++++++++++++++ 5 files changed, 214 insertions(+), 4 deletions(-) create mode 100644 src/axom/slam/MapBuilders.hpp diff --git a/src/axom/slam/CMakeLists.txt b/src/axom/slam/CMakeLists.txt index 7ac0a7dc9d..71d1da71b2 100644 --- a/src/axom/slam/CMakeLists.txt +++ b/src/axom/slam/CMakeLists.txt @@ -65,6 +65,7 @@ set(slam_headers # SRM Map headers Map.hpp + MapBuilders.hpp MapBase.hpp BivariateMap.hpp SubMap.hpp diff --git a/src/axom/slam/Map.hpp b/src/axom/slam/Map.hpp index 2471d2466e..11e0f077d9 100644 --- a/src/axom/slam/Map.hpp +++ b/src/axom/slam/Map.hpp @@ -167,6 +167,24 @@ class Map : public StrPol, public policies::MapInterface {}); + } + /// \overload template +axom::IndexType map_storage_size(const SetType* set, PosType stride) +{ + const axom::IndexType sz = set ? static_cast(set->size()) : axom::IndexType {0}; + return sz * static_cast(stride); +} +} // namespace detail + +/*! + * \brief Make a strided SLAM map backed by ArrayView storage. + * + * \param set pointer to the map's set (must outlive the map) + * \param stride runtime stride (#values per set element) + * \param data backing storage as an ArrayView (must outlive the map) + */ +template +auto make_map(SetType* set, PosType stride, axom::ArrayView data) +{ + using Indirection = policies::ArrayViewIndirection; + using Stride = policies::RuntimeStride; + using MapType = Map; + return MapType(set, data, stride); +} + +/*! + * \brief Make a stride-one SLAM map backed by ArrayView storage. + */ +template +auto make_map(SetType* set, axom::ArrayView data) +{ + using Indirection = policies::ArrayViewIndirection; + using Stride = policies::StrideOne; + using MapType = Map; + return MapType(set, data); +} + +/*! + * \brief Make a strided SLAM map backed by a raw pointer buffer. + * + * This overload wraps the buffer as an ArrayView with length `set->size() * stride` + * and returns an ArrayView-backed map. + */ +template +auto make_map(SetType* set, PosType stride, T* data) +{ + const auto n = detail::map_storage_size(set, stride); + return make_map(set, stride, axom::ArrayView(data, n)); +} + +/*! + * \brief Make a stride-one SLAM map backed by a raw pointer buffer. + */ +template +auto make_map(SetType* set, T* data) +{ + const auto n = detail::map_storage_size(set, PosType {1}); + return make_map(set, axom::ArrayView(data, n)); +} + +/*! + * \brief Make a compile-time strided SLAM map backed by ArrayView storage. + * + * \tparam STRIDE number of values per set element + */ +template +auto make_map_ct(SetType* set, axom::ArrayView data) +{ + using Indirection = policies::ArrayViewIndirection; + using Stride = policies::CompileTimeStride(STRIDE)>; + using MapType = Map; + return MapType(set, data); +} + +/*! + * \brief Make a compile-time strided SLAM map backed by a raw pointer buffer. + * + * This overload wraps the buffer as an ArrayView with length `set->size() * STRIDE` + * and returns an ArrayView-backed map. + */ +template +auto make_map_ct(SetType* set, T* data) +{ + const PosType stride = static_cast(STRIDE); + const auto n = detail::map_storage_size(set, stride); + return make_map_ct(set, axom::ArrayView(data, n)); +} + +} // namespace axom::slam + +#endif // SLAM_MAP_BUILDERS_HPP_ diff --git a/src/axom/slam/RelationBuilders.hpp b/src/axom/slam/RelationBuilders.hpp index d51007fc8e..7a2a1f7069 100644 --- a/src/axom/slam/RelationBuilders.hpp +++ b/src/axom/slam/RelationBuilders.hpp @@ -494,10 +494,14 @@ template auto make_constant_relation_ct(FromSet* fromSet, ToSet* toSet, axom::ArrayView indices) { - return make_constant_relation_ct(fromSet, - toSet, - indices.data(), - static_cast(indices.size())); + using IndicesIndirection = policies::ArrayViewIndirection; + using StridePolicy = policies::CompileTimeStride(STRIDE)>; + using CTy = policies::ConstantCardinality; + using RelationType = StaticRelation; + using Builder = typename RelationType::RelationBuilder; + + return RelationType(Builder().fromSet(fromSet).toSet(toSet).indices( + typename Builder::IndicesSetBuilder().size(static_cast(indices.size())).data(indices))); } /*! diff --git a/src/axom/slam/tests/slam_map_Map.cpp b/src/axom/slam/tests/slam_map_Map.cpp index 37e57a6fd3..c5a839f0f2 100644 --- a/src/axom/slam/tests/slam_map_Map.cpp +++ b/src/axom/slam/tests/slam_map_Map.cpp @@ -11,11 +11,13 @@ */ #include +#include #include "gtest/gtest.h" #include "axom/core/execution/runtime_policy.hpp" #include "axom/slic.hpp" #include "axom/slam.hpp" +#include "axom/slam/MapBuilders.hpp" namespace { @@ -145,6 +147,75 @@ TEST(slam_map, map_builder) } } +TEST(slam_map, make_map_array_view_storage) +{ + SetType s(MAX_SET_SIZE); + BaseSet* s_ptr = &s; + + double data[MAX_SET_SIZE] = {}; + for(SetPosition i = 0; i < MAX_SET_SIZE; ++i) + { + data[i] = 0.25 * i; + } + + auto m = slam::make_map(s_ptr, data); + using Indirection = policies::ArrayViewIndirection; + using Stride = policies::StrideOne; + static_assert(std::is_same_v>, + "make_map(set, ptr) yields an ArrayView-backed stride-one Map"); + + EXPECT_TRUE(m.isValid()); + EXPECT_EQ(m.size(), s.size()); + EXPECT_EQ(m.stride(), 1); + for(SetPosition i = 0; i < MAX_SET_SIZE; ++i) + { + EXPECT_DOUBLE_EQ(m(i), data[i]); + } +} + +TEST(slam_map, make_map_runtime_stride_array_view_storage) +{ + SetType s(3); + BaseSet* s_ptr = &s; + + constexpr SetPosition stride = 2; + double data[3 * stride] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; + + auto m = slam::make_map(s_ptr, stride, data); + using Indirection = policies::ArrayViewIndirection; + using Stride = policies::RuntimeStride; + static_assert(std::is_same_v>, + "make_map(set, stride, ptr) yields an ArrayView-backed runtime-stride Map"); + + EXPECT_TRUE(m.isValid()); + EXPECT_EQ(m.size(), s.size()); + EXPECT_EQ(m.stride(), stride); + EXPECT_DOUBLE_EQ(m(0, 0), 1.0); + EXPECT_DOUBLE_EQ(m(0, 1), 2.0); + EXPECT_DOUBLE_EQ(m(2, 0), 5.0); + EXPECT_DOUBLE_EQ(m(2, 1), 6.0); +} + +TEST(slam_map, make_map_compile_time_stride_array_view_storage) +{ + SetType s(2); + BaseSet* s_ptr = &s; + + double data[4] = {10.0, 11.0, 20.0, 21.0}; + + auto m = slam::make_map_ct<2>(s_ptr, data); + using Indirection = policies::ArrayViewIndirection; + using Stride = policies::CompileTimeStride; + static_assert(std::is_same_v>, + "make_map_ct yields an ArrayView-backed compile-time-stride Map"); + + EXPECT_TRUE(m.isValid()); + EXPECT_EQ(m.size(), s.size()); + EXPECT_EQ(m.stride(), 2); + EXPECT_DOUBLE_EQ(m(1, 0), 20.0); + EXPECT_DOUBLE_EQ(m(1, 1), 21.0); +} + template void constructAndTestMapWithStride(int stride) { From 74e8b1dc383a8b90f21b9cbca59db571f6a01de3 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Fri, 19 Jun 2026 17:07:31 -0700 Subject: [PATCH 592/986] slam: Extends support in FieldRegistry to data backed by axom::Array and axom::ArrayView --- src/axom/slam/FieldRegistry.hpp | 402 ++++++++++++++++++++- src/axom/slam/MapBuilders.hpp | 12 +- src/axom/slam/tests/slam_FieldRegistry.cpp | 51 +++ 3 files changed, 449 insertions(+), 16 deletions(-) diff --git a/src/axom/slam/FieldRegistry.hpp b/src/axom/slam/FieldRegistry.hpp index 9827fedc9b..5a7a265ec4 100644 --- a/src/axom/slam/FieldRegistry.hpp +++ b/src/axom/slam/FieldRegistry.hpp @@ -13,12 +13,14 @@ #include "axom/slam/Utilities.hpp" #include "axom/slam/Set.hpp" #include "axom/slam/Map.hpp" +#include "axom/slam/MapBuilders.hpp" #include #include #include #include #include +#include namespace axom::slam { @@ -32,46 +34,120 @@ namespace axom::slam * keyed by std::string and its find APIs return std::optional. The * three lookup tables use transparent comparison (std::less<>) so callers may * query with a std::string_view without constructing a temporary std::string. + * + * \note FieldRegistry supports two storage modes: + * - owning fields/buffers stored as `slam::Map` / `std::vector` + * - view-backed fields/buffers stored as `slam::Map` / `axom::ArrayView` + * (useful when generating SLAM objects from externally-owned C arrays) */ template class FieldRegistry { public: + /// \brief The set's position type. using PositionType = typename SetType::PositionType; + + /// \brief Data type for scalars, buffers, and map values stored in this registry. using DataType = TheDataType; + + /// \brief Key type used for registry entries. using KeyType = std::string; + + /// \brief Owning field type (`slam::Map`) stored by this registry. using MapType = slam::Map; + + /// \brief Owning buffer type used by `MapType`. using BufferType = typename MapType::OrderedMap; + /*! + * \brief View-backed field type stored by this registry. + * + * This is the return type of `slam::make_map(set, axom::ArrayView{...})` + * and uses `policies::ArrayViewIndirection`. + */ + using ViewMapType = decltype(slam::make_map(std::declval(), + std::declval>())); + + /// \brief View-backed buffer type stored by this registry. + using ViewBufferType = axom::ArrayView; + // Transparent comparator (std::less<>) enables heterogeneous lookup: a // std::string_view (or const char*) can be used as a key without allocating. using DataVecMap = std::map>; + using DataViewMap = std::map>; using DataBufferMap = std::map>; + using DataViewBufferMap = std::map>; using DataAttrMap = std::map>; public: + /// \name Owning Fields + /// @{ + + /*! + * \brief Returns true if an owning field with the given key exists. + * + * This function performs heterogeneous lookup, so callers may pass a + * `std::string_view` (or `const char*`) without allocating. + */ [[nodiscard]] bool hasField(std::string_view key) const { return m_maps.find(key) != m_maps.end(); } + /*! + * \brief Adds (or replaces) an owning field with the given key. + * + * \param key The field name. + * \param theSet Pointer to the set associated with the field (must outlive the map). + * \return A mutable reference to the stored field. + * + * \note If an entry with the same key already exists, it is overwritten. + */ MapType& addField(KeyType key, const SetType* theSet) { - return m_maps[std::move(key)] = MapType(theSet); + auto [it, _] = m_maps.insert_or_assign(std::move(key), MapType(theSet)); + return it->second; } + /*! + * \brief Adds a new owning field using an auto-generated unique key. + * + * \param theSet Pointer to the set associated with the field (must outlive the map). + * \return A mutable reference to the stored field. + * + * \note The generated key is unique within this translation unit and template instantiation, + * but this API is not intended to be used concurrently from multiple threads. + */ MapType& addNamelessField(const SetType* theSet) { static int cnt = 0; - return m_maps[axom::fmt::format("__field_{}", cnt++)] = MapType(theSet); + return addField(axom::fmt::format("__field_{}", cnt++), theSet); } + /*! + * \brief Returns a mutable reference to the owning field for \a key. + * + * \param key Field name. + * \return A mutable reference to the stored field. + * + * \pre `hasField(key)` is true. + * \note In debug builds, this asserts if the key is missing. + */ MapType& getField(std::string_view key) { verifyFieldsKey(key); return m_maps.find(key)->second; } + /*! + * \brief Returns a const reference to the owning field for \a key. + * + * \param key Field name. + * \return A const reference to the stored field. + * + * \pre `hasField(key)` is true. + * \note In debug builds, this asserts if the key is missing. + */ const MapType& getField(std::string_view key) const { verifyFieldsKey(key); @@ -79,8 +155,12 @@ class FieldRegistry } /*! - * \brief Find a field by name without inserting or asserting. - * \return an optional referencing the field if present, else empty. + * \brief Finds an owning field by name without inserting or asserting. + * + * \param key Field name. + * \return An optional referencing the field if present, else empty. + * + * \note This function never inserts a new entry. */ [[nodiscard]] std::optional> findField(std::string_view key) { @@ -89,6 +169,14 @@ class FieldRegistry : std::nullopt; } + /*! + * \brief Finds an owning field by name (const overload). + * + * \param key Field name. + * \return An optional referencing the field if present, else empty. + * + * \note This function never inserts a new entry. + */ [[nodiscard]] std::optional> findField(std::string_view key) const { auto it = m_maps.find(key); @@ -96,27 +184,165 @@ class FieldRegistry : std::nullopt; } + /// @} + + /// \name View-backed Fields + /// @{ + + /// \brief Returns true if a view-backed field with the given key exists. + [[nodiscard]] bool hasFieldView(std::string_view key) const + { + return m_view_maps.find(key) != m_view_maps.end(); + } + + /*! + * \brief Adds (or replaces) a view-backed field from an `axom::ArrayView`. + * + * \param key The field name. + * \param theSet Pointer to the set associated with the field (must outlive the map). + * \param data View of externally-owned storage (must outlive the map). + * \return A mutable reference to the stored field. + * + * \note If an entry with the same key already exists, it is overwritten. + */ + ViewMapType& addFieldView(KeyType key, const SetType* theSet, axom::ArrayView data) + { + auto [it, _] = m_view_maps.insert_or_assign(std::move(key), slam::make_map(theSet, data)); + return it->second; + } + + /*! + * \brief Adds (or replaces) a view-backed field from a raw pointer buffer. + * + * \param key The field name. + * \param theSet Pointer to the set associated with the field (must outlive the map). + * \param data Pointer to externally-owned storage (must outlive the map). + * The helper wraps this as an `axom::ArrayView` sized to `theSet->size()`. + * \return A mutable reference to the stored field. + * + * \note If an entry with the same key already exists, it is overwritten. + */ + ViewMapType& addFieldView(KeyType key, const SetType* theSet, DataType* data) + { + auto [it, _] = m_view_maps.insert_or_assign(std::move(key), slam::make_map(theSet, data)); + return it->second; + } + + /*! + * \brief Adds a new view-backed field using an auto-generated unique key. + * + * \param theSet Pointer to the set associated with the field (must outlive the map). + * \param data View of externally-owned storage (must outlive the map). + * \return A mutable reference to the stored field. + */ + ViewMapType& addNamelessFieldView(const SetType* theSet, axom::ArrayView data) + { + static int cnt = 0; + return addFieldView(axom::fmt::format("__field_view_{}", cnt++), theSet, data); + } + + /*! + * \brief Returns a mutable reference to the view-backed field for \a key. + * + * \pre `hasFieldView(key)` is true. + */ + ViewMapType& getFieldView(std::string_view key) + { + verifyFieldViewKey(key); + return m_view_maps.find(key)->second; + } + + /*! + * \brief Returns a const reference to the view-backed field for \a key. + * + * \pre `hasFieldView(key)` is true. + */ + const ViewMapType& getFieldView(std::string_view key) const + { + verifyFieldViewKey(key); + return m_view_maps.find(key)->second; + } + + /*! + * \brief Finds a view-backed field by name without inserting or asserting. + * + * \return An optional referencing the field if present, else empty. + */ + [[nodiscard]] std::optional> findFieldView(std::string_view key) + { + auto it = m_view_maps.find(key); + return it != m_view_maps.end() ? std::optional>(it->second) + : std::nullopt; + } + + /*! + * \brief Finds a view-backed field by name (const overload). + * + * \return An optional referencing the field if present, else empty. + */ + [[nodiscard]] std::optional> findFieldView( + std::string_view key) const + { + auto it = m_view_maps.find(key); + return it != m_view_maps.end() + ? std::optional>(it->second) + : std::nullopt; + } + + /// @} + + /// \name Owning Buffers + /// @{ + + /// \brief Returns true if an owning buffer with the given key exists. [[nodiscard]] bool hasBuffer(std::string_view key) const { return m_buff.find(key) != m_buff.end(); } + /*! + * \brief Adds (or replaces) an owning buffer with the given key. + * + * \param key Buffer name. + * \param size Initial buffer size. + * \return A mutable reference to the stored buffer. + * + * \note If an entry with the same key already exists, it is overwritten. + */ BufferType& addBuffer(KeyType key, int size = 0) { - return m_buff[std::move(key)] = BufferType(size); + auto [it, _] = m_buff.insert_or_assign(std::move(key), BufferType(size)); + return it->second; } + /*! + * \brief Adds a new owning buffer using an auto-generated unique key. + * + * \param size Initial buffer size. + * \return A mutable reference to the stored buffer. + */ BufferType& addNamelessBuffer(int size = 0) { static int cnt = 0; - return m_buff[axom::fmt::format("__buffer_{}", cnt++)] = BufferType(size); + return addBuffer(axom::fmt::format("__buffer_{}", cnt++), size); } + /*! + * \brief Returns a mutable reference to the owning buffer for \a key. + * + * \pre `hasBuffer(key)` is true. + */ BufferType& getBuffer(std::string_view key) { verifyBufferKey(key); return m_buff.find(key)->second; } + + /*! + * \brief Returns a const reference to the owning buffer for \a key. + * + * \pre `hasBuffer(key)` is true. + */ const BufferType& getBuffer(std::string_view key) const { verifyBufferKey(key); @@ -124,8 +350,9 @@ class FieldRegistry } /*! - * \brief Find a buffer by name without inserting or asserting. - * \return an optional referencing the buffer if present, else empty. + * \brief Finds an owning buffer by name without inserting or asserting. + * + * \return An optional referencing the buffer if present, else empty. */ [[nodiscard]] std::optional> findBuffer(std::string_view key) { @@ -134,19 +361,158 @@ class FieldRegistry : std::nullopt; } + /*! + * \brief Finds an owning buffer by name (const overload). + * + * \return An optional referencing the buffer if present, else empty. + */ + [[nodiscard]] std::optional> findBuffer( + std::string_view key) const + { + auto it = m_buff.find(key); + return it != m_buff.end() ? std::optional>(it->second) + : std::nullopt; + } + + /// @} + + /// \name View-backed Buffers + /// @{ + + /// \brief Returns true if a view-backed buffer with the given key exists. + [[nodiscard]] bool hasBufferView(std::string_view key) const + { + return m_view_buff.find(key) != m_view_buff.end(); + } + + /*! + * \brief Adds (or replaces) a view-backed buffer from an `axom::ArrayView`. + * + * \param key Buffer name. + * \param view View of externally-owned storage (must outlive the registry entry). + * \return A mutable reference to the stored view. + * + * \note If an entry with the same key already exists, it is overwritten. + */ + ViewBufferType& addBufferView(KeyType key, ViewBufferType view) + { + auto [it, _] = m_view_buff.insert_or_assign(std::move(key), view); + return it->second; + } + + /*! + * \brief Adds (or replaces) a view-backed buffer from a raw pointer and size. + * + * \param key Buffer name. + * \param data Pointer to externally-owned storage (must outlive the registry entry). + * \param size Number of elements. + * \return A mutable reference to the stored view. + */ + ViewBufferType& addBufferView(KeyType key, DataType* data, PositionType size) + { + return addBufferView(std::move(key), + axom::ArrayView(data, static_cast(size))); + } + + /*! + * \brief Adds a new view-backed buffer using an auto-generated unique key. + * + * \param view View of externally-owned storage (must outlive the registry entry). + * \return A mutable reference to the stored view. + */ + ViewBufferType& addNamelessBufferView(ViewBufferType view) + { + static int cnt = 0; + return addBufferView(axom::fmt::format("__buffer_view_{}", cnt++), view); + } + + /*! + * \brief Returns a mutable reference to the view-backed buffer for \a key. + * + * \pre `hasBufferView(key)` is true. + */ + ViewBufferType& getBufferView(std::string_view key) + { + verifyBufferViewKey(key); + return m_view_buff.find(key)->second; + } + + /*! + * \brief Returns a const reference to the view-backed buffer for \a key. + * + * \pre `hasBufferView(key)` is true. + */ + const ViewBufferType& getBufferView(std::string_view key) const + { + verifyBufferViewKey(key); + return m_view_buff.find(key)->second; + } + + /*! + * \brief Finds a view-backed buffer by name without inserting or asserting. + * + * \return An optional referencing the buffer if present, else empty. + */ + [[nodiscard]] std::optional> findBufferView(std::string_view key) + { + auto it = m_view_buff.find(key); + return it != m_view_buff.end() + ? std::optional>(it->second) + : std::nullopt; + } + + /*! + * \brief Finds a view-backed buffer by name (const overload). + * + * \return An optional referencing the buffer if present, else empty. + */ + [[nodiscard]] std::optional> findBufferView( + std::string_view key) const + { + auto it = m_view_buff.find(key); + return it != m_view_buff.end() + ? std::optional>(it->second) + : std::nullopt; + } + + /// @} + + /// \name Scalars + /// @{ + + /// \brief Returns true if a scalar with the given key exists. [[nodiscard]] bool hasScalar(std::string_view key) const { return m_scal.find(key) != m_scal.end(); } + /*! + * \brief Adds (or replaces) a scalar value with the given key. + * + * \param key Scalar name. + * \param val Scalar value. + * \return A mutable reference to the stored value. + * + * \note If an entry with the same key already exists, it is overwritten. + */ DataType& addScalar(KeyType key, DataType val) { return m_scal[std::move(key)] = val; } + /*! + * \brief Returns a mutable reference to the scalar for \a key. + * + * \pre `hasScalar(key)` is true. + */ DataType& getScalar(std::string_view key) { verifyScalarKey(key); return m_scal.find(key)->second; } + /*! + * \brief Returns a const reference to the scalar for \a key. + * + * \pre `hasScalar(key)` is true. + */ const DataType& getScalar(std::string_view key) const { verifyScalarKey(key); @@ -154,8 +520,10 @@ class FieldRegistry } /*! - * \brief Find a scalar by name without inserting or asserting. - * \return an engaged optional with the scalar's value if present, else empty. + * \brief Finds a scalar by name without inserting or asserting. + * + * \param key Scalar name. + * \return An engaged optional with the scalar's value if present, else empty. */ [[nodiscard]] std::optional findScalar(std::string_view key) const { @@ -163,17 +531,29 @@ class FieldRegistry return it != m_scal.end() ? std::optional(it->second) : std::nullopt; } + /// @} + private: inline void verifyFieldsKey(std::string_view AXOM_DEBUG_PARAM(key)) const { SLIC_ASSERT_MSG(hasField(key), "Didn't find field named " << key); } + inline void verifyFieldViewKey(std::string_view AXOM_DEBUG_PARAM(key)) const + { + SLIC_ASSERT_MSG(hasFieldView(key), "Didn't find view field named " << key); + } + inline void verifyBufferKey(std::string_view AXOM_DEBUG_PARAM(key)) const { SLIC_ASSERT_MSG(hasBuffer(key), "Didn't find buffer named " << key); } + inline void verifyBufferViewKey(std::string_view AXOM_DEBUG_PARAM(key)) const + { + SLIC_ASSERT_MSG(hasBufferView(key), "Didn't find view buffer named " << key); + } + inline void verifyScalarKey(std::string_view AXOM_DEBUG_PARAM(key)) const { SLIC_ASSERT_MSG(hasScalar(key), "Didn't find scalar named " << key); @@ -181,7 +561,9 @@ class FieldRegistry private: DataVecMap m_maps; + DataViewMap m_view_maps; DataBufferMap m_buff; + DataViewBufferMap m_view_buff; DataAttrMap m_scal; }; diff --git a/src/axom/slam/MapBuilders.hpp b/src/axom/slam/MapBuilders.hpp index 2be3668443..4280c3821e 100644 --- a/src/axom/slam/MapBuilders.hpp +++ b/src/axom/slam/MapBuilders.hpp @@ -40,7 +40,7 @@ axom::IndexType map_storage_size(const SetType* set, PosType stride) * \param data backing storage as an ArrayView (must outlive the map) */ template -auto make_map(SetType* set, PosType stride, axom::ArrayView data) +auto make_map(const SetType* set, PosType stride, axom::ArrayView data) { using Indirection = policies::ArrayViewIndirection; using Stride = policies::RuntimeStride; @@ -52,7 +52,7 @@ auto make_map(SetType* set, PosType stride, axom::ArrayView data) * \brief Make a stride-one SLAM map backed by ArrayView storage. */ template -auto make_map(SetType* set, axom::ArrayView data) +auto make_map(const SetType* set, axom::ArrayView data) { using Indirection = policies::ArrayViewIndirection; using Stride = policies::StrideOne; @@ -67,7 +67,7 @@ auto make_map(SetType* set, axom::ArrayView data) * and returns an ArrayView-backed map. */ template -auto make_map(SetType* set, PosType stride, T* data) +auto make_map(const SetType* set, PosType stride, T* data) { const auto n = detail::map_storage_size(set, stride); return make_map(set, stride, axom::ArrayView(data, n)); @@ -77,7 +77,7 @@ auto make_map(SetType* set, PosType stride, T* data) * \brief Make a stride-one SLAM map backed by a raw pointer buffer. */ template -auto make_map(SetType* set, T* data) +auto make_map(const SetType* set, T* data) { const auto n = detail::map_storage_size(set, PosType {1}); return make_map(set, axom::ArrayView(data, n)); @@ -89,7 +89,7 @@ auto make_map(SetType* set, T* data) * \tparam STRIDE number of values per set element */ template -auto make_map_ct(SetType* set, axom::ArrayView data) +auto make_map_ct(const SetType* set, axom::ArrayView data) { using Indirection = policies::ArrayViewIndirection; using Stride = policies::CompileTimeStride(STRIDE)>; @@ -104,7 +104,7 @@ auto make_map_ct(SetType* set, axom::ArrayView data) * and returns an ArrayView-backed map. */ template -auto make_map_ct(SetType* set, T* data) +auto make_map_ct(const SetType* set, T* data) { const PosType stride = static_cast(STRIDE); const auto n = detail::map_storage_size(set, stride); diff --git a/src/axom/slam/tests/slam_FieldRegistry.cpp b/src/axom/slam/tests/slam_FieldRegistry.cpp index 21ce5985c0..58bc86ed33 100644 --- a/src/axom/slam/tests/slam_FieldRegistry.cpp +++ b/src/axom/slam/tests/slam_FieldRegistry.cpp @@ -19,6 +19,7 @@ #include #include #include +#include namespace { @@ -119,3 +120,53 @@ TEST(slam_FieldRegistry, nameless_keys_are_unique) auto& b1 = ireg.addNamelessBuffer(2); EXPECT_NE(&b0, &b1); } + +TEST(slam_FieldRegistry, view_buffer_add_get_find) +{ + IndexRegistry reg; + + int data[4] = {10, 20, 30, 40}; + reg.addBufferView("view", data, SetType::PositionType {4}); + + EXPECT_TRUE(reg.hasBufferView("view")); + EXPECT_EQ(reg.getBufferView("view")[2], 30); + + auto hit = reg.findBufferView("view"); + ASSERT_TRUE(hit.has_value()); + EXPECT_EQ(hit->get()[3], 40); + + const IndexRegistry& creg = reg; + auto chit = creg.findBufferView("view"); + ASSERT_TRUE(chit.has_value()); + EXPECT_EQ(chit->get()[0], 10); +} + +TEST(slam_FieldRegistry, view_field_is_non_owning) +{ + SetType s(5); + ScalarRegistry reg; + + double data[5] = {1., 2., 3., 4., 5.}; + auto& field = reg.addFieldView("temp", &s, data); + + static_assert( + std::is_same_v::IndirectionPolicy::IndirectionBufferType, + axom::ArrayView>, + "view fields use ArrayView-backed indirection"); + + EXPECT_TRUE(reg.hasFieldView("temp")); + EXPECT_DOUBLE_EQ(reg.getFieldView("temp")[1], 2.0); + + // Mutating through the map updates the backing buffer. + reg.getFieldView("temp")[2] = 42.0; + EXPECT_DOUBLE_EQ(data[2], 42.0); + + // Mutating the backing buffer is visible through the map. + data[0] = -3.0; + EXPECT_DOUBLE_EQ(reg.getFieldView("temp")[0], -3.0); + + const ScalarRegistry& creg = reg; + auto hit = creg.findFieldView("temp"); + ASSERT_TRUE(hit.has_value()); + EXPECT_EQ(hit->get().size(), 5); +} From 1837fc50bec1dacffa1a27f36a5b258810ee427d Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Fri, 19 Jun 2026 18:05:00 -0700 Subject: [PATCH 593/986] slam: Adds Optional-returning API to several slam Set classes --- src/axom/slam/BivariateSet.hpp | 74 ++++++++++++++--- src/axom/slam/DynamicSet.hpp | 15 ++++ src/axom/slam/ProductSet.hpp | 39 +++++++++ src/axom/slam/RelationSet.hpp | 39 +++++++++ src/axom/slam/tests/slam_set_BivariateSet.cpp | 79 +++++++++++++++++++ src/axom/slam/tests/slam_set_DynamicSet.cpp | 15 ++++ 6 files changed, 249 insertions(+), 12 deletions(-) diff --git a/src/axom/slam/BivariateSet.hpp b/src/axom/slam/BivariateSet.hpp index 0f77a60a5d..dfb50afce7 100644 --- a/src/axom/slam/BivariateSet.hpp +++ b/src/axom/slam/BivariateSet.hpp @@ -22,6 +22,8 @@ #include "axom/slam/RangeSet.hpp" #include "axom/slam/policies/PolicyTraits.hpp" +#include "axom/core/Optional.hpp" + #include #include @@ -100,7 +102,7 @@ class BivariateSet using IteratorType = BivariateSetIterator; public: - static const PositionType INVALID_POS = PositionType(-1); + static constexpr PositionType INVALID_POS = PositionType(-1); static const NullSetType s_nullSet; public: @@ -139,10 +141,26 @@ class BivariateSet */ virtual PositionType findElementIndex(PositionType pos1, PositionType pos2) const = 0; - /** - * \brief Search for the FlatIndex of the element given its DenseIndex. - * - * \param pos1 The first set position. + /*! + * \brief Finds the SparseIndex of the element given its DenseIndex. + * + * \return An engaged `axom::Optional` containing the SparseIndex if the element exists, + * or an empty `axom::Optional` if the element does not exist. + * + * \note This is a convenience wrapper around `findElementIndex(...)` that avoids + * sentinel checks against `INVALID_POS`. + */ + [[nodiscard]] axom::Optional findElementIndexOptional(PositionType pos1, + PositionType pos2) const + { + const auto idx = findElementIndex(pos1, pos2); + return idx != INVALID_POS ? axom::Optional(idx) : axom::Optional {}; + } + + /** + * \brief Search for the FlatIndex of the element given its DenseIndex. + * + * \param pos1 The first set position. * \param pos2 The second set position. * * \return The element's FlatIndex @@ -151,9 +169,26 @@ class BivariateSet AXOM_HOST_DEVICE virtual PositionType findElementFlatIndex(PositionType pos1, PositionType pos2) const = 0; - /** - * \brief Searches for the first existing element given the row index (first - * set position). + /*! + * \brief Finds the FlatIndex of the element given its DenseIndex. + * + * \return An engaged `axom::Optional` containing the FlatIndex if the element exists, + * or an empty `axom::Optional` if the element does not exist. + * + * \note This is a convenience wrapper around `findElementFlatIndex(...)` that avoids + * sentinel checks against `INVALID_POS`. + */ + [[nodiscard]] AXOM_HOST_DEVICE axom::Optional findElementFlatIndexOptional( + PositionType pos1, + PositionType pos2) const + { + const auto idx = findElementFlatIndex(pos1, pos2); + return idx != INVALID_POS ? axom::Optional(idx) : axom::Optional {}; + } + + /** + * \brief Searches for the first existing element given the row index (first + * set position). * * \param pos1 The first set position. * @@ -162,10 +197,25 @@ class BivariateSet */ virtual PositionType findElementFlatIndex(PositionType pos1) const = 0; - /** - * \brief Given the flat index, return the associated from-set index in the - * relation pair. - * + /*! + * \brief Finds the FlatIndex of the first existing element in a row. + * + * \return An engaged `axom::Optional` containing the FlatIndex if the row contains any elements, + * or an empty `axom::Optional` if the row is empty. + * + * \note This is a convenience wrapper around `findElementFlatIndex(pos1)` that avoids + * sentinel checks against `INVALID_POS`. + */ + [[nodiscard]] axom::Optional findElementFlatIndexOptional(PositionType pos1) const + { + const auto idx = findElementFlatIndex(pos1); + return idx != INVALID_POS ? axom::Optional(idx) : axom::Optional {}; + } + + /** + * \brief Given the flat index, return the associated from-set index in the + * relation pair. + * * \param flatIndex The FlatIndex of the from-set/to-set pair. * * \return pos1 The from-set index. diff --git a/src/axom/slam/DynamicSet.hpp b/src/axom/slam/DynamicSet.hpp index c011b06575..775747fdab 100644 --- a/src/axom/slam/DynamicSet.hpp +++ b/src/axom/slam/DynamicSet.hpp @@ -16,6 +16,7 @@ #include "axom/config.hpp" #include "axom/core/IteratorBase.hpp" +#include "axom/core/Optional.hpp" #include "axom/slam/OrderedSet.hpp" #include "axom/slam/RangeSet.hpp" @@ -300,6 +301,20 @@ class DynamicSet : public Set, SizePolicy return INVALID_ENTRY; }; + /*! + * \brief Given a value, find the index of the first entry containing it. + * + * \return An engaged `axom::Optional` with the index of the first element with + * value \a e, or an empty `axom::Optional` if none can be found. + * \note This is an O(n) operation in the size of the set. + */ + [[nodiscard]] axom::Optional findIndexOptional(ElementType e) const + { + const IndexType idx = findIndex(e); + const IndexType invalid = static_cast(INVALID_ENTRY); + return idx != invalid ? axom::Optional(idx) : axom::Optional {}; + } + /** * \brief Checks whether an element exists within the DynamicSet * diff --git a/src/axom/slam/ProductSet.hpp b/src/axom/slam/ProductSet.hpp index c6571cfbe0..dce12dbbdc 100644 --- a/src/axom/slam/ProductSet.hpp +++ b/src/axom/slam/ProductSet.hpp @@ -137,6 +137,19 @@ class ProductSet final : public policies::BivariateSetInterface findElementIndexOptional(PositionType pos1, + PositionType pos2) const + { + const auto idx = findElementIndex(pos1, pos2); + return idx != BaseType::INVALID_POS ? axom::Optional(idx) + : axom::Optional {}; + } + /** * \brief Returns an element's FlatIndex given its DenseIndex. Since * ProductSet is the full Cartesian product of the two sets, an @@ -158,6 +171,20 @@ class ProductSet final : public policies::BivariateSetInterface findElementFlatIndexOptional( + PositionType pos1, + PositionType pos2) const + { + const auto idx = findElementFlatIndex(pos1, pos2); + return idx != BaseType::INVALID_POS ? axom::Optional(idx) + : axom::Optional {}; + } + /** * \brief Returns the FlatIndex of the first element in the specified row. * This is equal to `pos1*secondSetSize()`. @@ -169,6 +196,18 @@ class ProductSet final : public policies::BivariateSetInterface findElementFlatIndexOptional(PositionType pos1) const + { + const auto idx = findElementFlatIndex(pos1); + return idx != BaseType::INVALID_POS ? axom::Optional(idx) + : axom::Optional {}; + } + /** * \brief Given the flat index, return the associated to-set index in the relation pair. * diff --git a/src/axom/slam/RelationSet.hpp b/src/axom/slam/RelationSet.hpp index 98d64f8992..388556d58d 100644 --- a/src/axom/slam/RelationSet.hpp +++ b/src/axom/slam/RelationSet.hpp @@ -117,6 +117,19 @@ class RelationSet final : public policies::BivariateSetInterface findElementIndexOptional(PositionType pos1, + PositionType pos2) const + { + const auto idx = findElementIndex(pos1, pos2); + return idx != BaseType::INVALID_POS ? axom::Optional(idx) + : axom::Optional {}; + } + /** * \brief Search for the FlatIndex of the element given its DenseIndex. * \warning This function can be slow, since a linear search is performed on the row each time. @@ -140,6 +153,20 @@ class RelationSet final : public policies::BivariateSetInterface findElementFlatIndexOptional( + PositionType s1, + PositionType s2) const + { + const auto idx = findElementFlatIndex(s1, s2); + return idx != BaseType::INVALID_POS ? axom::Optional(idx) + : axom::Optional {}; + } + /** * \brief Given the from-set index pos1, return the FlatIndex of the first * existing to-set element in the relation pair, or `INVALID_POS` if @@ -162,6 +189,18 @@ class RelationSet final : public policies::BivariateSetInterface findElementFlatIndexOptional(PositionType pos1) const + { + const auto idx = findElementFlatIndex(pos1); + return idx != BaseType::INVALID_POS ? axom::Optional(idx) + : axom::Optional {}; + } + /** * \brief Given the flat index, return the associated to-set index in the relation pair. * diff --git a/src/axom/slam/tests/slam_set_BivariateSet.cpp b/src/axom/slam/tests/slam_set_BivariateSet.cpp index abb1961eaf..caa3774553 100644 --- a/src/axom/slam/tests/slam_set_BivariateSet.cpp +++ b/src/axom/slam/tests/slam_set_BivariateSet.cpp @@ -412,6 +412,19 @@ void bSetTraverseTest(slam::BivariateSet* bset, bool shouldCheckMod) EXPECT_EQ(sparseIdx, bset->findElementIndex(idx, innerIdx)); EXPECT_EQ(flatIdx, bset->findElementFlatIndex(idx, innerIdx)); + + { + auto sparse_opt = bset->findElementIndexOptional(idx, innerIdx); + ASSERT_TRUE(sparse_opt.has_value()); + EXPECT_EQ(sparseIdx, *sparse_opt); + } + + { + auto flat_opt = bset->findElementFlatIndexOptional(idx, innerIdx); + ASSERT_TRUE(flat_opt.has_value()); + EXPECT_EQ(flatIdx, *flat_opt); + } + EXPECT_EQ(idx, bset->flatToFirstIndex(flatIdx)); EXPECT_EQ(innerIdx, bset->flatToSecondIndex(flatIdx)); @@ -435,6 +448,14 @@ void bSetTraverseTest(slam::BivariateSet* bset, bool shouldCheckMod) { EXPECT_EQ(flatIndex, bsetElem.flatIndex()); EXPECT_EQ(flatIndex, bset->findElementFlatIndex(bsetElem.firstIndex(), bsetElem.secondIndex())); + + { + auto flat_opt = + bset->findElementFlatIndexOptional(bsetElem.firstIndex(), bsetElem.secondIndex()); + ASSERT_TRUE(flat_opt.has_value()); + EXPECT_EQ(flatIndex, *flat_opt); + } + EXPECT_EQ(bsetElem.firstIndex(), bset->flatToFirstIndex(flatIndex)); EXPECT_EQ(bsetElem.secondIndex(), bset->flatToSecondIndex(flatIndex)); @@ -463,6 +484,14 @@ void bSetTraverseTest(slam::BivariateSet* bset, bool shouldCheckMod) EXPECT_EQ(flatIndex, bsetElem.flatIndex()); EXPECT_EQ(flatIndex, derivedSet->findElementFlatIndex(bsetElem.firstIndex(), bsetElem.secondIndex())); + + { + auto flat_opt = + derivedSet->findElementFlatIndexOptional(bsetElem.firstIndex(), bsetElem.secondIndex()); + ASSERT_TRUE(flat_opt.has_value()); + EXPECT_EQ(flatIndex, *flat_opt); + } + EXPECT_EQ(bsetElem.firstIndex(), derivedSet->flatToFirstIndex(flatIndex)); EXPECT_EQ(bsetElem.secondIndex(), derivedSet->flatToSecondIndex(flatIndex)); @@ -476,6 +505,35 @@ void bSetTraverseTest(slam::BivariateSet* bset, bool shouldCheckMod) } } +template +std::pair find_missing_mod_pair( + const SetType& bset) +{ + using PositionType = typename SetType::PositionType; + const auto* firstSet = bset.getFirstSet(); + const auto* secondSet = bset.getSecondSet(); + + for(PositionType i = 0; i < bset.firstSetSize(); ++i) + { + const auto outer = firstSet->at(i); + if(outer == 0) + { + continue; + } + + for(PositionType j = 0; j < bset.secondSetSize(); ++j) + { + const auto inner = secondSet->at(j); + if(!modCheck(outer, inner)) + { + return {i, j}; + } + } + } + + return {SetType::INVALID_POS, SetType::INVALID_POS}; +} + TYPED_TEST(BivariateSetTester, traverse) { using S1 = typename TestFixture::FirstSetType; @@ -508,6 +566,27 @@ TYPED_TEST(BivariateSetTester, traverse) } } +TYPED_TEST(BivariateSetTester, optional_find_returns_empty_for_missing_relation_entries) +{ + using S1 = typename TestFixture::FirstSetType; + using S2 = typename TestFixture::SecondSetType; + using RType = typename TestFixture::RelationType; + using RSet = slam::RelationSet; + + RSet rset = RSet(&this->modRelation); + ASSERT_TRUE(rset.isValid(true)); + + const auto [i, j] = find_missing_mod_pair(rset); + ASSERT_NE(i, RSet::INVALID_POS); + ASSERT_NE(j, RSet::INVALID_POS); + + auto sparse_opt = rset.findElementIndexOptional(i, j); + EXPECT_FALSE(sparse_opt.has_value()); + + auto flat_opt = rset.findElementFlatIndexOptional(i, j); + EXPECT_FALSE(flat_opt.has_value()); +} + //----------------------------------------------------------------------------- int main(int argc, char* argv[]) { diff --git a/src/axom/slam/tests/slam_set_DynamicSet.cpp b/src/axom/slam/tests/slam_set_DynamicSet.cpp index 083a11e113..01d4182a84 100644 --- a/src/axom/slam/tests/slam_set_DynamicSet.cpp +++ b/src/axom/slam/tests/slam_set_DynamicSet.cpp @@ -244,6 +244,21 @@ TEST(slam_set_dynamicset, find_index) } } +TEST(slam_set_dynamicset, findIndexOptional) +{ + SetType s(MAX_SET_SIZE); + + for(SetPosition i = 0; i < MAX_SET_SIZE; i++) + { + auto opt = s.findIndexOptional(i); + ASSERT_TRUE(opt.has_value()); + EXPECT_EQ(*opt, i); + } + + auto miss = s.findIndexOptional(MAX_SET_SIZE + 1); + EXPECT_FALSE(miss.has_value()); +} + TEST(slam_set_dynamicset, iterator) { SetType s(MAX_SET_SIZE); From 1eaedda7820959aea7042022bc3433009bd2931f Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Fri, 19 Jun 2026 18:13:44 -0700 Subject: [PATCH 594/986] slam: Cleanup -- removes unused function in Value policy tag --- src/axom/core/utilities/Abort.hpp | 1 - src/axom/slam/policies/ValuePolicies.hpp | 5 ----- 2 files changed, 6 deletions(-) diff --git a/src/axom/core/utilities/Abort.hpp b/src/axom/core/utilities/Abort.hpp index 6f07e80d2b..b3790a5732 100644 --- a/src/axom/core/utilities/Abort.hpp +++ b/src/axom/core/utilities/Abort.hpp @@ -27,4 +27,3 @@ namespace axom::utilities } // namespace axom::utilities #endif // AXOM_CORE_UTILITIES_ABORT_HPP_ - diff --git a/src/axom/slam/policies/ValuePolicies.hpp b/src/axom/slam/policies/ValuePolicies.hpp index ffc22395e7..f7acafac93 100644 --- a/src/axom/slam/policies/ValuePolicies.hpp +++ b/src/axom/slam/policies/ValuePolicies.hpp @@ -24,7 +24,6 @@ * A `Tag` type supplies the policy-specific knobs as static members: * - `static constexpr IntType defaultValue();` -- the DEFAULT_VALUE * - `static constexpr bool isValidValue(IntType);` -- validity predicate - * - `static constexpr const char* name();` -- used in assertion text * * The named accessors (`size()`, `stride()`, `offset()`) are *not* provided here; * they live in the thin leaf policies in SizePolicies.hpp / StridePolicies.hpp / OffsetPolicies.hpp @@ -40,7 +39,6 @@ #define SLAM_POLICIES_VALUE_H_ #include "axom/core/Macros.hpp" -#include "axom/slic.hpp" namespace axom::slam::policies { @@ -58,7 +56,6 @@ struct SizeTag { AXOM_HOST_DEVICE static constexpr IntType defaultValue() { return IntType {}; } AXOM_HOST_DEVICE static constexpr bool isValidValue(IntType v) { return v >= IntType {}; } - static constexpr const char* name() { return "slam::Size"; } }; /*! @@ -70,7 +67,6 @@ struct StrideTag { AXOM_HOST_DEVICE static constexpr IntType defaultValue() { return IntType(1); } AXOM_HOST_DEVICE static constexpr bool isValidValue(IntType v) { return v != IntType {}; } - static constexpr const char* name() { return "slam::Stride"; } }; /*! @@ -82,7 +78,6 @@ struct OffsetTag { AXOM_HOST_DEVICE static constexpr IntType defaultValue() { return IntType {}; } AXOM_HOST_DEVICE static constexpr bool isValidValue(IntType) { return true; } - static constexpr const char* name() { return "slam::Offset"; } }; /// \} From d1515aaaa17aa1bc54d7c80161334d0107301905 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Sun, 21 Jun 2026 12:11:30 -0700 Subject: [PATCH 595/986] Improves compiler checks for constant time checks in ConstexprAssert --- src/axom/core/utilities/ConstexprAssert.hpp | 29 +++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/axom/core/utilities/ConstexprAssert.hpp b/src/axom/core/utilities/ConstexprAssert.hpp index b13c19c50c..44faac1030 100644 --- a/src/axom/core/utilities/ConstexprAssert.hpp +++ b/src/axom/core/utilities/ConstexprAssert.hpp @@ -45,6 +45,29 @@ #if !defined(__CUDA_ARCH__) && !defined(__HIP_DEVICE_COMPILE__) #include + #include +#endif + +// Detect if we are in a constant-evaluation context: +// - std::is_constant_evaluated() is a C++20 library function. +// - __builtin_is_constant_evaluated() is a compiler builtin available in C++17 +// - otherwise, AXOM_DETAIL_HAS_IS_CONSTANT_EVALUATED is 0 +#if defined(__cpp_lib_is_constant_evaluated) + #define AXOM_DETAIL_IS_CONSTANT_EVALUATED() (std::is_constant_evaluated()) + #define AXOM_DETAIL_HAS_IS_CONSTANT_EVALUATED 1 +#elif defined(__has_builtin) + #if __has_builtin(__builtin_is_constant_evaluated) + #define AXOM_DETAIL_IS_CONSTANT_EVALUATED() (__builtin_is_constant_evaluated()) + #define AXOM_DETAIL_HAS_IS_CONSTANT_EVALUATED 1 + #endif +#elif defined(_MSC_VER) && _MSC_VER >= 1926 + // MSVC provides the builtin but historically did not implement __has_builtin. + #define AXOM_DETAIL_IS_CONSTANT_EVALUATED() (__builtin_is_constant_evaluated()) + #define AXOM_DETAIL_HAS_IS_CONSTANT_EVALUATED 1 +#endif + +#if !defined(AXOM_DETAIL_HAS_IS_CONSTANT_EVALUATED) + #define AXOM_DETAIL_HAS_IS_CONSTANT_EVALUATED 0 #endif namespace axom::detail @@ -68,8 +91,8 @@ AXOM_DETAIL_CONSTEXPR_ASSERT_HOST_DEVICE constexpr void constexprAssert(bool con { if(!cond) { - #if defined(__clang__) || defined(__GNUC__) - if(__builtin_is_constant_evaluated()) + #if AXOM_DETAIL_HAS_IS_CONSTANT_EVALUATED + if(AXOM_DETAIL_IS_CONSTANT_EVALUATED()) { // Not constexpr: reaching this in constant evaluation is a hard error. constexprAssertFail(expr, file, line); @@ -98,5 +121,7 @@ AXOM_DETAIL_CONSTEXPR_ASSERT_HOST_DEVICE constexpr void constexprAssert(bool, } // namespace axom::detail #undef AXOM_DETAIL_CONSTEXPR_ASSERT_HOST_DEVICE +#undef AXOM_DETAIL_IS_CONSTANT_EVALUATED +#undef AXOM_DETAIL_HAS_IS_CONSTANT_EVALUATED #endif // AXOM_CORE_UTILITIES_CONSTEXPR_ASSERT_HPP_ From aa171eec3c3aa07eee8f92dcb858e7b8acc9223a Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Sun, 21 Jun 2026 15:12:54 -0700 Subject: [PATCH 596/986] Adds configuration test for the AXOM_CONSTEXPR_ASSERT macro --- src/axom/core/tests/CMakeLists.txt | 39 ++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/axom/core/tests/CMakeLists.txt b/src/axom/core/tests/CMakeLists.txt index f009b7328a..f35b7b4823 100644 --- a/src/axom/core/tests/CMakeLists.txt +++ b/src/axom/core/tests/CMakeLists.txt @@ -248,3 +248,42 @@ if (ENABLE_BENCHMARKS) NUM_OMP_THREADS ${_num_threads}) endforeach() endif() + +#------------------------------------------------------------------------------ +# Negative compile-time test for AXOM_CONSTEXPR_ASSERT +# +# The runtime behavior of AXOM_CONSTEXPR_ASSERT is covered by unit tests. +# The following checks its compile-time invariants. +#------------------------------------------------------------------------------ +include(CheckCXXSourceCompiles) + +# Probe needs the generated axom/config.hpp and the in-source headers. +get_property(_axom_config_dir TARGET core PROPERTY BINARY_DIR) +set(CMAKE_REQUIRED_INCLUDES + "${PROJECT_SOURCE_DIR}" + "${PROJECT_BINARY_DIR}/include") +# Compile the probe with AXOM_DEBUG off, which isolates the constant-evaluation path +set(CMAKE_REQUIRED_DEFINITIONS "-UAXOM_DEBUG" "-DNDEBUG") + +# A static_assert forces constant evaluation of a violating AXOM_CONSTEXPR_ASSERT. +# This source is expected NOT to compile. +check_cxx_source_compiles([=[ +#include "axom/core/Macros.hpp" +constexpr int checked(int x) { AXOM_CONSTEXPR_ASSERT(x > 0); return x; } +static_assert(checked(-1) == -1, "must fail: x>0 violated during constant evaluation"); +int main() { return 0; } +]=] AXOM_CONSTEXPR_ASSERT_VIOLATION_COMPILES) + +unset(CMAKE_REQUIRED_INCLUDES) +unset(CMAKE_REQUIRED_DEFINITIONS) + +if(AXOM_CONSTEXPR_ASSERT_VIOLATION_COMPILES) + message(FATAL_ERROR + "AXOM_CONSTEXPR_ASSERT regression: a compile-time invariant violation " + "compiled successfully. The constant-evaluation detection in " + "axom/core/utilities/ConstexprAssert.hpp is not firing on this compiler " + "(${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}).") +else() + message(STATUS + "AXOM_CONSTEXPR_ASSERT compile-time guarantee verified on ${CMAKE_CXX_COMPILER_ID}") +endif() From 15c1dcc6b5ec5c60f324f1dbc047e6dde4755c81 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Sun, 21 Jun 2026 15:18:59 -0700 Subject: [PATCH 597/986] slam: Adds missing tests for slam::make_constant_relation --- src/axom/slam/tests/slam_make_helpers.cpp | 48 +++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/axom/slam/tests/slam_make_helpers.cpp b/src/axom/slam/tests/slam_make_helpers.cpp index 8d42496246..341f07b5b2 100644 --- a/src/axom/slam/tests/slam_make_helpers.cpp +++ b/src/axom/slam/tests/slam_make_helpers.cpp @@ -254,3 +254,51 @@ TEST(slam_make_helpers, make_constant_relation_compile_time_stride) EXPECT_EQ(r0[0], 0); EXPECT_EQ(r0[1], 4); } + +TEST(slam_make_helpers, make_constant_relation_runtime_stride_carray) +{ + // tests make_constant_relation helper function for C-style arrays + + auto fromSet = slam::make_range_set(3); + auto toSet = slam::make_range_set(5); + + // from { 0 -> {1,2}; 1 -> {3,4}; 2 -> {0,2} } + Pos indices[6] = {1, 2, 3, 4, 0, 2}; + + // C-array backing: raw pointer + element count. + auto rel = slam::make_constant_relation(&fromSet, &toSet, Pos {2}, indices, Pos {6}); + + EXPECT_TRUE(rel.isValid()); + EXPECT_EQ(rel.size(0), 2); + EXPECT_EQ(rel.size(1), 2); + EXPECT_EQ(rel.size(2), 2); + + auto r2 = rel[2]; + ASSERT_EQ(r2.size(), 2); + EXPECT_EQ(r2[0], 0); + EXPECT_EQ(r2[1], 2); +} + +TEST(slam_make_helpers, make_constant_relation_runtime_stride_axom_array) +{ + // tests make_constant_relation helper function for axom::Array + + auto fromSet = slam::make_range_set(3); + auto toSet = slam::make_range_set(5); + + // from { 0 -> {1,2}; 1 -> {3,4}; 2 -> {0,2} } + axom::Array indices {1, 2, 3, 4, 0, 2}; + + // axom::Array backing (by reference, distinct from the ArrayView overload). + auto rel = slam::make_constant_relation(&fromSet, &toSet, Pos {2}, indices); + + EXPECT_TRUE(rel.isValid()); + EXPECT_EQ(rel.size(0), 2); + EXPECT_EQ(rel.size(1), 2); + EXPECT_EQ(rel.size(2), 2); + + auto r0 = rel[0]; + ASSERT_EQ(r0.size(), 2); + EXPECT_EQ(r0[0], 1); + EXPECT_EQ(r0[1], 2); +} From 7f4baa76657602c58d868a733536646938eebc69 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Sun, 21 Jun 2026 18:00:00 -0700 Subject: [PATCH 598/986] Moves type_identify to core component Was originally developed in slam, but is a more general utility. --- src/axom/core/Types.hpp | 18 ++++++++++++++++++ src/axom/slam/SetBuilders.hpp | 21 +++++---------------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/axom/core/Types.hpp b/src/axom/core/Types.hpp index a5ee4d18e7..45320e288c 100644 --- a/src/axom/core/Types.hpp +++ b/src/axom/core/Types.hpp @@ -68,6 +68,24 @@ using IndexType = std::int32_t; static constexpr IndexType InvalidIndex = -1; +/*! + * \brief Maps a type to itself in a non-deduced context. + * + * This is an Axom-local equivalent of C++20's std::type_identity for C++17. + * Its primary use is to suppress template argument deduction for a function parameter. + * A parameter declared as type_identity_t does not participate in deducing T, + * so T is taken from the explicit template argument or from another parameter, + * and the argument here is implicitly converted. + */ +template +struct type_identity +{ + using type = T; +}; + +template +using type_identity_t = typename type_identity::type; + #ifdef AXOM_USE_MPI // Note: MSVC complains about uninitialized static const integer class members, diff --git a/src/axom/slam/SetBuilders.hpp b/src/axom/slam/SetBuilders.hpp index 44302336ec..5d0a325ee8 100644 --- a/src/axom/slam/SetBuilders.hpp +++ b/src/axom/slam/SetBuilders.hpp @@ -38,6 +38,7 @@ #define SLAM_SET_BUILDERS_H_ #include "axom/core/Array.hpp" +#include "axom/core/Types.hpp" #include "axom/slam/Utilities.hpp" #include "axom/slam/RangeSet.hpp" @@ -48,18 +49,6 @@ namespace axom::slam { -namespace detail -{ -template -struct type_identity -{ - using type = T; -}; - -template -using type_identity_t = typename type_identity::type; -} // namespace detail - /// \name Set construction helpers /// \brief Construct a SLAM set while deducing its policy stack from the buffer /// or range. \a PosType defaults to slam's default position type and may be @@ -72,7 +61,7 @@ using type_identity_t = typename type_identity::type; * \return a RangeSet */ template -RangeSet make_range_set(detail::type_identity_t size) +RangeSet make_range_set(axom::type_identity_t size) { return RangeSet(size); } @@ -84,8 +73,8 @@ RangeSet make_range_set(detail::type_identity_t size * \return a RangeSet */ template -RangeSet make_range_set(detail::type_identity_t lower, - detail::type_identity_t upper) +RangeSet make_range_set(axom::type_identity_t lower, + axom::type_identity_t upper) { return RangeSet(lower, upper); } @@ -144,7 +133,7 @@ ArrayIndirectionSet make_indirection_set(axom::Array */ template -CArrayIndirectionSet make_indirection_set(T* data, detail::type_identity_t size) +CArrayIndirectionSet make_indirection_set(T* data, axom::type_identity_t size) { using SetType = CArrayIndirectionSet; return SetType(typename SetType::SetBuilder().size(size).data(data)); From debfc91464b413d39cfc1cea45c47c08c8c963e6 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Sun, 21 Jun 2026 23:06:37 -0700 Subject: [PATCH 599/986] slam: Bugfix for ProductSet::findElementFlatIndex when second set is empty --- src/axom/slam/ProductSet.hpp | 10 ++++++- src/axom/slam/tests/slam_set_BivariateSet.cpp | 27 +++++++++++++++++-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/axom/slam/ProductSet.hpp b/src/axom/slam/ProductSet.hpp index dce12dbbdc..4df3635b28 100644 --- a/src/axom/slam/ProductSet.hpp +++ b/src/axom/slam/ProductSet.hpp @@ -8,7 +8,6 @@ * \file ProductSet.hpp * * \brief Basic API for a SLAM Cartesian product set - * */ #ifndef SLAM_PRODUCT_SET_H_ @@ -193,6 +192,15 @@ class ProductSet final : public policies::BivariateSetInterface= 0 && pos1 < this->firstSetSize(), + "SLAM::ProductSet -- requested out-of-range first-set position " + << pos1 << ", but set only has " << this->firstSetSize() << " rows."); + + if(this->secondSetSize() == 0) + { + return BaseType::INVALID_POS; + } + return findElementFlatIndex(pos1, 0); } diff --git a/src/axom/slam/tests/slam_set_BivariateSet.cpp b/src/axom/slam/tests/slam_set_BivariateSet.cpp index caa3774553..25bda6fbbe 100644 --- a/src/axom/slam/tests/slam_set_BivariateSet.cpp +++ b/src/axom/slam/tests/slam_set_BivariateSet.cpp @@ -8,8 +8,7 @@ * \file slam_set_BivariateSet.cpp * * This file tests BivariateSet, ProductSet and RelationSet within Slam. - * It uses a templated test fixture to test many different types of - * bivariate sets + * It uses a templated test fixture to test many different types of bivariate sets */ #include "gtest/gtest.h" @@ -566,6 +565,30 @@ TYPED_TEST(BivariateSetTester, traverse) } } +TEST(slam_bivariate_set, product_set_empty_second_set_has_no_first_flat_index) +{ + using SetType = slam::PositionSet<>; + using ProductSetType = slam::ProductSet; + + SetType firstSet(3); + SetType secondSet(0); + ProductSetType productSet(&firstSet, &secondSet); + + ASSERT_TRUE(productSet.isValid(true)); + EXPECT_EQ(productSet.firstSetSize(), 3); + EXPECT_EQ(productSet.secondSetSize(), 0); + EXPECT_EQ(productSet.size(), 0); + EXPECT_EQ(productSet.size(0), 0); + EXPECT_EQ(productSet.getElements(0).size(), 0); + + EXPECT_EQ(productSet.findElementFlatIndex(0), ProductSetType::INVALID_POS); + EXPECT_FALSE(productSet.findElementFlatIndexOptional(0).has_value()); + + const slam::BivariateSet* baseSet = &productSet; + EXPECT_EQ(baseSet->findElementFlatIndex(0), ProductSetType::INVALID_POS); + EXPECT_FALSE(baseSet->findElementFlatIndexOptional(0).has_value()); +} + TYPED_TEST(BivariateSetTester, optional_find_returns_empty_for_missing_relation_entries) { using S1 = typename TestFixture::FirstSetType; From e71a6dc27e5129dfe421218593ec1047e9dd8d0a Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Sun, 21 Jun 2026 23:26:46 -0700 Subject: [PATCH 600/986] slam: Fixes types for make_*_relation for std vector and axom Array We were previously silently converting to the raw pointer versions. --- src/axom/slam/RelationBuilders.hpp | 24 ++++++++----- src/axom/slam/tests/slam_make_helpers.cpp | 44 +++++++++++++++++++++++ 2 files changed, 60 insertions(+), 8 deletions(-) diff --git a/src/axom/slam/RelationBuilders.hpp b/src/axom/slam/RelationBuilders.hpp index 7a2a1f7069..bea68f37a0 100644 --- a/src/axom/slam/RelationBuilders.hpp +++ b/src/axom/slam/RelationBuilders.hpp @@ -465,10 +465,14 @@ template auto make_constant_relation_ct(FromSet* fromSet, ToSet* toSet, std::vector& indices) { - return make_constant_relation_ct(fromSet, - toSet, - indices.data(), - static_cast(indices.size())); + using IndicesIndirection = policies::STLVectorIndirection; + using StridePolicy = policies::CompileTimeStride(STRIDE)>; + using CTy = policies::ConstantCardinality; + using RelationType = StaticRelation; + using Builder = typename RelationType::RelationBuilder; + + return RelationType(Builder().fromSet(fromSet).toSet(toSet).indices( + typename Builder::IndicesSetBuilder().size(static_cast(indices.size())).data(&indices))); } /*! @@ -527,10 +531,14 @@ template auto make_constant_relation_ct(FromSet* fromSet, ToSet* toSet, axom::Array& indices) { - return make_constant_relation_ct(fromSet, - toSet, - indices.data(), - static_cast(indices.size())); + using IndicesIndirection = policies::ArrayIndirection; + using StridePolicy = policies::CompileTimeStride(STRIDE)>; + using CTy = policies::ConstantCardinality; + using RelationType = StaticRelation; + using Builder = typename RelationType::RelationBuilder; + + return RelationType(Builder().fromSet(fromSet).toSet(toSet).indices( + typename Builder::IndicesSetBuilder().size(static_cast(indices.size())).data(&indices))); } /*! diff --git a/src/axom/slam/tests/slam_make_helpers.cpp b/src/axom/slam/tests/slam_make_helpers.cpp index 341f07b5b2..a4e765f3f2 100644 --- a/src/axom/slam/tests/slam_make_helpers.cpp +++ b/src/axom/slam/tests/slam_make_helpers.cpp @@ -255,6 +255,50 @@ TEST(slam_make_helpers, make_constant_relation_compile_time_stride) EXPECT_EQ(r0[1], 4); } +TEST(slam_make_helpers, make_constant_relation_compile_time_stride_vector_backed) +{ + auto fromSet = slam::make_range_set(2); + auto toSet = slam::make_range_set(5); + + std::vector indices {0, 4, 1, 3}; + auto rel = slam::make_constant_relation_ct<2>(&fromSet, &toSet, indices); + + static_assert(std::is_same_v>, + "make_constant_relation_ct(vector) keeps STLVectorIndirection backing"); + + EXPECT_TRUE(rel.isValid()); + EXPECT_EQ(rel.size(0), 2); + EXPECT_EQ(rel.size(1), 2); + + auto r1 = rel[1]; + ASSERT_EQ(r1.size(), 2); + EXPECT_EQ(r1[0], 1); + EXPECT_EQ(r1[1], 3); +} + +TEST(slam_make_helpers, make_constant_relation_compile_time_stride_axom_array_backed) +{ + auto fromSet = slam::make_range_set(2); + auto toSet = slam::make_range_set(5); + + axom::Array indices {0, 4, 1, 3}; + auto rel = slam::make_constant_relation_ct<2>(&fromSet, &toSet, indices); + + static_assert(std::is_same_v>, + "make_constant_relation_ct(axom::Array) keeps ArrayIndirection backing"); + + EXPECT_TRUE(rel.isValid()); + EXPECT_EQ(rel.size(0), 2); + EXPECT_EQ(rel.size(1), 2); + + auto r0 = rel[0]; + ASSERT_EQ(r0.size(), 2); + EXPECT_EQ(r0[0], 0); + EXPECT_EQ(r0[1], 4); +} + TEST(slam_make_helpers, make_constant_relation_runtime_stride_carray) { // tests make_constant_relation helper function for C-style arrays From 3b8a06958e5275d5cb5fcd1952b725b1268d4444 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Sun, 21 Jun 2026 23:46:22 -0700 Subject: [PATCH 601/986] slam: Adds tests for const element type in ArrayViewIndirectionPolicy --- src/axom/slam/tests/slam_FieldRegistry.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/axom/slam/tests/slam_FieldRegistry.cpp b/src/axom/slam/tests/slam_FieldRegistry.cpp index 58bc86ed33..89b7e2cf15 100644 --- a/src/axom/slam/tests/slam_FieldRegistry.cpp +++ b/src/axom/slam/tests/slam_FieldRegistry.cpp @@ -20,6 +20,7 @@ #include #include #include +#include namespace { @@ -153,6 +154,16 @@ TEST(slam_FieldRegistry, view_field_is_non_owning) std::is_same_v::IndirectionPolicy::IndirectionBufferType, axom::ArrayView>, "view fields use ArrayView-backed indirection"); + using ViewFieldType = std::remove_reference_t; + static_assert(std::is_same_v, + "const ArrayView-backed fields preserve mutable view semantics"); + + using ConstViewIndirection = + slam::policies::ArrayViewIndirection; + static_assert(std::is_same_v, + "ArrayView exposes immutable values"); + static_assert(std::is_same_v, + "const ArrayView fields expose immutable values"); EXPECT_TRUE(reg.hasFieldView("temp")); EXPECT_DOUBLE_EQ(reg.getFieldView("temp")[1], 2.0); @@ -166,7 +177,11 @@ TEST(slam_FieldRegistry, view_field_is_non_owning) EXPECT_DOUBLE_EQ(reg.getFieldView("temp")[0], -3.0); const ScalarRegistry& creg = reg; + const auto& constField = creg.getFieldView("temp"); + EXPECT_DOUBLE_EQ(constField[0], -3.0); + auto hit = creg.findFieldView("temp"); ASSERT_TRUE(hit.has_value()); EXPECT_EQ(hit->get().size(), 5); + EXPECT_DOUBLE_EQ(hit->get()[2], 42.0); } From e225ac11cceb09e8aaa670f00860c209a398f574 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Mon, 22 Jun 2026 01:10:49 -0700 Subject: [PATCH 602/986] slam: Adds missing size parameter to relation builders Helps with c-style array backed sets. --- src/axom/slam/OrderedSet.hpp | 8 ++++ src/axom/slam/RelationBuilders.hpp | 8 ++-- .../slam/policies/IndirectionPolicies.hpp | 9 +++- src/axom/slam/tests/slam_make_helpers.cpp | 41 +++++++++++++++++++ 4 files changed, 60 insertions(+), 6 deletions(-) diff --git a/src/axom/slam/OrderedSet.hpp b/src/axom/slam/OrderedSet.hpp index a51948c26f..38fd2c1a50 100644 --- a/src/axom/slam/OrderedSet.hpp +++ b/src/axom/slam/OrderedSet.hpp @@ -229,6 +229,14 @@ struct OrderedSet : public policies::SetInterface::value, + "This indirection policy does not support sized data binding."); + m_data = IndirectionPolicyType(bufPtr, bufferSize); + return *this; + } + SetBuilder& parent(ParentSetType* parSet) { m_parent = SubsettingPolicyType(parSet); diff --git a/src/axom/slam/RelationBuilders.hpp b/src/axom/slam/RelationBuilders.hpp index bea68f37a0..018fee110b 100644 --- a/src/axom/slam/RelationBuilders.hpp +++ b/src/axom/slam/RelationBuilders.hpp @@ -139,8 +139,8 @@ auto make_variable_relation(FromSet* fromSet, Builder() .fromSet(fromSet) .toSet(toSet) - .begins(typename Builder::BeginsSetBuilder().size(beginsSize).data(begins)) - .indices(typename Builder::IndicesSetBuilder().size(indicesSize).data(indices))); + .begins(typename Builder::BeginsSetBuilder().size(beginsSize).data(begins, beginsSize)) + .indices(typename Builder::IndicesSetBuilder().size(indicesSize).data(indices, indicesSize))); } /*! @@ -323,7 +323,7 @@ auto make_constant_relation(FromSet* fromSet, .fromSet(fromSet) .toSet(toSet) .begins(begins_builder) - .indices(typename Builder::IndicesSetBuilder().size(indicesSize).data(indices))); + .indices(typename Builder::IndicesSetBuilder().size(indicesSize).data(indices, indicesSize))); } /*! @@ -439,7 +439,7 @@ auto make_constant_relation_ct(FromSet* fromSet, ToSet* toSet, ElemType* indices using Builder = typename RelationType::RelationBuilder; return RelationType(Builder().fromSet(fromSet).toSet(toSet).indices( - typename Builder::IndicesSetBuilder().size(indicesSize).data(indices))); + typename Builder::IndicesSetBuilder().size(indicesSize).data(indices, indicesSize))); } /*! diff --git a/src/axom/slam/policies/IndirectionPolicies.hpp b/src/axom/slam/policies/IndirectionPolicies.hpp index 36110bf754..aaced1fa4c 100644 --- a/src/axom/slam/policies/IndirectionPolicies.hpp +++ b/src/axom/slam/policies/IndirectionPolicies.hpp @@ -289,17 +289,22 @@ struct CArrayIndirectionBase static constexpr bool IsMutableBuffer = false; static constexpr const char* Name = "SLAM::CArrayIndirection"; - AXOM_HOST_DEVICE CArrayIndirectionBase(IndirectionPtrType buf = nullptr) : m_arrBuf(buf) { } + AXOM_HOST_DEVICE CArrayIndirectionBase(IndirectionPtrType buf = nullptr, + PositionType size = axom::numeric_limits::max()) + : m_arrBuf(buf) + , m_arrSize(size) + { } AXOM_HOST_DEVICE IndirectionBufferType data() const { return m_arrBuf; } AXOM_HOST_DEVICE IndirectionBufferType& ptr() { return m_arrBuf; } bool hasIndirection() const { return m_arrBuf != nullptr; } - constexpr PositionType size() const { return axom::numeric_limits::max(); } + AXOM_HOST_DEVICE PositionType size() const { return m_arrSize; } private: IndirectionBufferType m_arrBuf; + PositionType m_arrSize; }; /** diff --git a/src/axom/slam/tests/slam_make_helpers.cpp b/src/axom/slam/tests/slam_make_helpers.cpp index a4e765f3f2..6a4c15ec2b 100644 --- a/src/axom/slam/tests/slam_make_helpers.cpp +++ b/src/axom/slam/tests/slam_make_helpers.cpp @@ -164,6 +164,47 @@ TEST(slam_make_helpers, make_variable_relation) EXPECT_EQ(rel2[1], 4); } +TEST(slam_make_helpers, make_variable_relation_carray_buffers) +{ + auto fromSet = slam::make_range_set(3); + auto toSet = slam::make_range_set(5); + + Pos begins[4] = {0, 2, 3, 5}; + Pos indices[5] = {1, 2, 3, 0, 4}; + + auto rel = slam::make_variable_relation(&fromSet, &toSet, begins, Pos {4}, indices, Pos {5}); + + static_assert(std::is_same_v>, + "raw-pointer begins remain C-array backed"); + static_assert(std::is_same_v>, + "raw-pointer indices remain C-array backed"); + + EXPECT_TRUE(rel.isValid()); + EXPECT_EQ(rel.size(0), 2); + EXPECT_EQ(rel.size(1), 1); + EXPECT_EQ(rel.size(2), 2); + + auto rel0 = rel[0]; + ASSERT_EQ(rel0.size(), 2); + EXPECT_EQ(rel0[0], 1); + EXPECT_EQ(rel0[1], 2); +} + +TEST(slam_make_helpers, make_variable_relation_carray_rejects_short_begins_size) +{ + auto fromSet = slam::make_range_set(3); + auto toSet = slam::make_range_set(5); + + Pos begins[4] = {0, 2, 3, 3}; + Pos indices[3] = {1, 2, 3}; + + auto rel = slam::make_variable_relation(&fromSet, &toSet, begins, Pos {3}, indices, Pos {3}); + + EXPECT_FALSE(rel.isValid()); +} + TEST(slam_make_helpers, make_variable_relation_axom_array_buffers) { auto fromSet = slam::make_range_set(3); From 6a0769fe9c3b8a6d4960f1d98a1962b11b5a7ced Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Mon, 22 Jun 2026 01:43:15 -0700 Subject: [PATCH 603/986] slam: Simplifies field storage in FieldRegistry ... and consolidates backing types. --- src/axom/slam/FieldRegistry.hpp | 157 ++++++++++++++++----- src/axom/slam/tests/slam_FieldRegistry.cpp | 36 ++++- 2 files changed, 154 insertions(+), 39 deletions(-) diff --git a/src/axom/slam/FieldRegistry.hpp b/src/axom/slam/FieldRegistry.hpp index 5a7a265ec4..7f2deea4cb 100644 --- a/src/axom/slam/FieldRegistry.hpp +++ b/src/axom/slam/FieldRegistry.hpp @@ -21,6 +21,7 @@ #include #include #include +#include namespace axom::slam { @@ -32,12 +33,12 @@ namespace axom::slam * * \note FieldRegistry is a host-only facility: it stores std::map tables * keyed by std::string and its find APIs return std::optional. The - * three lookup tables use transparent comparison (std::less<>) so callers may + * lookup tables use transparent comparison (std::less<>) so callers may * query with a std::string_view without constructing a temporary std::string. * - * \note FieldRegistry supports two storage modes: - * - owning fields/buffers stored as `slam::Map` / `std::vector` - * - view-backed fields/buffers stored as `slam::Map` / `axom::ArrayView` + * \note FieldRegistry supports two field storage modes under a single field keyspace: + * - owning fields stored as `slam::Map` + * - view-backed fields stored as `slam::Map` over `axom::ArrayView` * (useful when generating SLAM objects from externally-owned C arrays) */ template @@ -71,20 +72,22 @@ class FieldRegistry /// \brief View-backed buffer type stored by this registry. using ViewBufferType = axom::ArrayView; + /// \brief Storage for one named field, either owning or view-backed. + using FieldStorageType = std::variant; + // Transparent comparator (std::less<>) enables heterogeneous lookup: a // std::string_view (or const char*) can be used as a key without allocating. - using DataVecMap = std::map>; - using DataViewMap = std::map>; + using DataVecMap = std::map>; using DataBufferMap = std::map>; using DataViewBufferMap = std::map>; using DataAttrMap = std::map>; public: - /// \name Owning Fields + /// \name Fields /// @{ /*! - * \brief Returns true if an owning field with the given key exists. + * \brief Returns true if a field with the given key exists. * * This function performs heterogeneous lookup, so callers may pass a * `std::string_view` (or `const char*`) without allocating. @@ -105,8 +108,43 @@ class FieldRegistry */ MapType& addField(KeyType key, const SetType* theSet) { - auto [it, _] = m_maps.insert_or_assign(std::move(key), MapType(theSet)); - return it->second; + auto [it, _] = m_maps.insert_or_assign(std::move(key), FieldStorageType(MapType(theSet))); + return std::get(it->second); + } + + /*! + * \brief Adds (or replaces) a view-backed field from an `axom::ArrayView`. + * + * \param key The field name. + * \param theSet Pointer to the set associated with the field (must outlive the map). + * \param data View of externally-owned storage (must outlive the map). + * \return A mutable reference to the stored field. + * + * \note If an entry with the same key already exists, it is overwritten. + */ + ViewMapType& addField(KeyType key, const SetType* theSet, axom::ArrayView data) + { + auto [it, _] = + m_maps.insert_or_assign(std::move(key), FieldStorageType(slam::make_map(theSet, data))); + return std::get(it->second); + } + + /*! + * \brief Adds (or replaces) a view-backed field from a raw pointer buffer. + * + * \param key The field name. + * \param theSet Pointer to the set associated with the field (must outlive the map). + * \param data Pointer to externally-owned storage (must outlive the map). + * The helper wraps this as an `axom::ArrayView` sized to `theSet->size()`. + * \return A mutable reference to the stored field. + * + * \note If an entry with the same key already exists, it is overwritten. + */ + ViewMapType& addField(KeyType key, const SetType* theSet, DataType* data) + { + auto [it, _] = + m_maps.insert_or_assign(std::move(key), FieldStorageType(slam::make_map(theSet, data))); + return std::get(it->second); } /*! @@ -120,8 +158,19 @@ class FieldRegistry */ MapType& addNamelessField(const SetType* theSet) { - static int cnt = 0; - return addField(axom::fmt::format("__field_{}", cnt++), theSet); + return addField(makeNamelessFieldKey(), theSet); + } + + /*! + * \brief Adds a new view-backed field using an auto-generated unique key. + * + * \param theSet Pointer to the set associated with the field (must outlive the map). + * \param data View of externally-owned storage (must outlive the map). + * \return A mutable reference to the stored field. + */ + ViewMapType& addNamelessField(const SetType* theSet, axom::ArrayView data) + { + return addField(makeNamelessFieldKey(), theSet, data); } /*! @@ -130,13 +179,14 @@ class FieldRegistry * \param key Field name. * \return A mutable reference to the stored field. * - * \pre `hasField(key)` is true. + * \pre `findField(key)` has a value. * \note In debug builds, this asserts if the key is missing. + * \note For view-backed fields, use `getFieldView()`. */ MapType& getField(std::string_view key) { verifyFieldsKey(key); - return m_maps.find(key)->second; + return std::get(m_maps.find(key)->second); } /*! @@ -145,13 +195,14 @@ class FieldRegistry * \param key Field name. * \return A const reference to the stored field. * - * \pre `hasField(key)` is true. + * \pre `findField(key)` has a value. * \note In debug builds, this asserts if the key is missing. + * \note For view-backed fields, use `getFieldView()`. */ const MapType& getField(std::string_view key) const { verifyFieldsKey(key); - return m_maps.find(key)->second; + return std::get(m_maps.find(key)->second); } /*! @@ -165,8 +216,13 @@ class FieldRegistry [[nodiscard]] std::optional> findField(std::string_view key) { auto it = m_maps.find(key); - return it != m_maps.end() ? std::optional>(it->second) - : std::nullopt; + if(it == m_maps.end()) + { + return std::nullopt; + } + + auto* field = std::get_if(&it->second); + return field != nullptr ? std::optional>(*field) : std::nullopt; } /*! @@ -180,8 +236,14 @@ class FieldRegistry [[nodiscard]] std::optional> findField(std::string_view key) const { auto it = m_maps.find(key); - return it != m_maps.end() ? std::optional>(it->second) - : std::nullopt; + if(it == m_maps.end()) + { + return std::nullopt; + } + + const auto* field = std::get_if(&it->second); + return field != nullptr ? std::optional>(*field) + : std::nullopt; } /// @} @@ -192,7 +254,8 @@ class FieldRegistry /// \brief Returns true if a view-backed field with the given key exists. [[nodiscard]] bool hasFieldView(std::string_view key) const { - return m_view_maps.find(key) != m_view_maps.end(); + auto it = m_maps.find(key); + return it != m_maps.end() && std::holds_alternative(it->second); } /*! @@ -207,8 +270,7 @@ class FieldRegistry */ ViewMapType& addFieldView(KeyType key, const SetType* theSet, axom::ArrayView data) { - auto [it, _] = m_view_maps.insert_or_assign(std::move(key), slam::make_map(theSet, data)); - return it->second; + return addField(std::move(key), theSet, data); } /*! @@ -224,8 +286,7 @@ class FieldRegistry */ ViewMapType& addFieldView(KeyType key, const SetType* theSet, DataType* data) { - auto [it, _] = m_view_maps.insert_or_assign(std::move(key), slam::make_map(theSet, data)); - return it->second; + return addField(std::move(key), theSet, data); } /*! @@ -237,8 +298,7 @@ class FieldRegistry */ ViewMapType& addNamelessFieldView(const SetType* theSet, axom::ArrayView data) { - static int cnt = 0; - return addFieldView(axom::fmt::format("__field_view_{}", cnt++), theSet, data); + return addNamelessField(theSet, data); } /*! @@ -249,7 +309,7 @@ class FieldRegistry ViewMapType& getFieldView(std::string_view key) { verifyFieldViewKey(key); - return m_view_maps.find(key)->second; + return std::get(m_maps.find(key)->second); } /*! @@ -260,7 +320,7 @@ class FieldRegistry const ViewMapType& getFieldView(std::string_view key) const { verifyFieldViewKey(key); - return m_view_maps.find(key)->second; + return std::get(m_maps.find(key)->second); } /*! @@ -270,9 +330,15 @@ class FieldRegistry */ [[nodiscard]] std::optional> findFieldView(std::string_view key) { - auto it = m_view_maps.find(key); - return it != m_view_maps.end() ? std::optional>(it->second) - : std::nullopt; + auto it = m_maps.find(key); + if(it == m_maps.end()) + { + return std::nullopt; + } + + auto* field = std::get_if(&it->second); + return field != nullptr ? std::optional>(*field) + : std::nullopt; } /*! @@ -283,10 +349,15 @@ class FieldRegistry [[nodiscard]] std::optional> findFieldView( std::string_view key) const { - auto it = m_view_maps.find(key); - return it != m_view_maps.end() - ? std::optional>(it->second) - : std::nullopt; + auto it = m_maps.find(key); + if(it == m_maps.end()) + { + return std::nullopt; + } + + const auto* field = std::get_if(&it->second); + return field != nullptr ? std::optional>(*field) + : std::nullopt; } /// @} @@ -534,9 +605,20 @@ class FieldRegistry /// @} private: + static KeyType makeNamelessFieldKey() + { + static int cnt = 0; + return axom::fmt::format("__field_{}", cnt++); + } + inline void verifyFieldsKey(std::string_view AXOM_DEBUG_PARAM(key)) const { - SLIC_ASSERT_MSG(hasField(key), "Didn't find field named " << key); +#ifdef AXOM_DEBUG + auto it = m_maps.find(key); + SLIC_ASSERT_MSG(it != m_maps.end(), "Didn't find field named " << key); + SLIC_ASSERT_MSG(it == m_maps.end() || std::holds_alternative(it->second), + "Field named " << key << " is view-backed; use getFieldView()"); +#endif } inline void verifyFieldViewKey(std::string_view AXOM_DEBUG_PARAM(key)) const @@ -561,7 +643,6 @@ class FieldRegistry private: DataVecMap m_maps; - DataViewMap m_view_maps; DataBufferMap m_buff; DataViewBufferMap m_view_buff; DataAttrMap m_scal; diff --git a/src/axom/slam/tests/slam_FieldRegistry.cpp b/src/axom/slam/tests/slam_FieldRegistry.cpp index 89b7e2cf15..e1daf9daf6 100644 --- a/src/axom/slam/tests/slam_FieldRegistry.cpp +++ b/src/axom/slam/tests/slam_FieldRegistry.cpp @@ -148,7 +148,7 @@ TEST(slam_FieldRegistry, view_field_is_non_owning) ScalarRegistry reg; double data[5] = {1., 2., 3., 4., 5.}; - auto& field = reg.addFieldView("temp", &s, data); + auto& field = reg.addField("temp", &s, data); static_assert( std::is_same_v::IndirectionPolicy::IndirectionBufferType, @@ -165,6 +165,7 @@ TEST(slam_FieldRegistry, view_field_is_non_owning) static_assert(std::is_same_v, "const ArrayView fields expose immutable values"); + EXPECT_TRUE(reg.hasField("temp")); EXPECT_TRUE(reg.hasFieldView("temp")); EXPECT_DOUBLE_EQ(reg.getFieldView("temp")[1], 2.0); @@ -185,3 +186,36 @@ TEST(slam_FieldRegistry, view_field_is_non_owning) EXPECT_EQ(hit->get().size(), 5); EXPECT_DOUBLE_EQ(hit->get()[2], 42.0); } + +TEST(slam_FieldRegistry, field_storage_modes_share_keyspace) +{ + SetType s(3); + ScalarRegistry reg; + + double data[3] = {10., 20., 30.}; + auto& viewField = reg.addField("density", &s, data); + + EXPECT_TRUE(reg.hasField("density")); + EXPECT_TRUE(reg.hasFieldView("density")); + EXPECT_FALSE(reg.findField("density").has_value()); + ASSERT_TRUE(reg.findFieldView("density").has_value()); + + viewField[1] = 21.; + EXPECT_DOUBLE_EQ(data[1], 21.); + + auto& owningField = reg.addField("density", &s); + EXPECT_TRUE(reg.hasField("density")); + EXPECT_FALSE(reg.hasFieldView("density")); + EXPECT_FALSE(reg.findFieldView("density").has_value()); + ASSERT_TRUE(reg.findField("density").has_value()); + + owningField[1] = 99.; + EXPECT_DOUBLE_EQ(reg.getField("density")[1], 99.); + EXPECT_DOUBLE_EQ(data[1], 21.); + + auto& viewFieldAgain = reg.addFieldView("density", &s, data); + EXPECT_TRUE(reg.hasField("density")); + EXPECT_TRUE(reg.hasFieldView("density")); + EXPECT_FALSE(reg.findField("density").has_value()); + EXPECT_DOUBLE_EQ(viewFieldAgain[1], 21.); +} From 0c15bed77cf9b969f7bef1bce3372e63efcb6506 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Mon, 22 Jun 2026 02:10:53 -0700 Subject: [PATCH 604/986] slam: Fixes make_indirection_set for ArrayView --- src/axom/slam/SetBuilders.hpp | 22 +++++++++------ src/axom/slam/tests/slam_make_helpers.cpp | 34 +++++++++++++++++++++-- 2 files changed, 45 insertions(+), 11 deletions(-) diff --git a/src/axom/slam/SetBuilders.hpp b/src/axom/slam/SetBuilders.hpp index 5d0a325ee8..64db7285ce 100644 --- a/src/axom/slam/SetBuilders.hpp +++ b/src/axom/slam/SetBuilders.hpp @@ -22,7 +22,7 @@ * from the buffer: * * \code - * auto s = slam::make_indirection_set(v); // -> ArrayViewIndirectionSet<.., double> + * auto s = slam::make_indirection_set(view); // -> ArrayViewIndirectionSet<.., double> * \endcode * * \note On CTAD vs. helpers. A class-template-argument deduction guide cannot @@ -110,19 +110,23 @@ VectorIndirectionSet make_indirection_set(std::vector& vec) } /*! - * \brief Make an indirection set whose elements indirect through an axom::Array. + * \brief Make an indirection set whose elements indirect through an axom::Array's flat storage. * - * The element type is deduced from \a arr; the set is device-capable - * (axom::Array indirection is host-device). The set's size matches the array. + * The element type is deduced from \a arr; the set is device-capable because it + * stores an ArrayView over the array's flat storage. The set's size matches the array. * - * \param arr the backing array (must outlive the set) - * \return an ArrayIndirectionSet + * \param arr the backing array (its storage must outlive the set and not be reallocated) + * \return an ArrayViewIndirectionSet */ template -ArrayIndirectionSet make_indirection_set(axom::Array& arr) +ArrayViewIndirectionSet make_indirection_set(axom::Array& arr) { - using SetType = ArrayIndirectionSet; - return SetType(typename SetType::SetBuilder().size(static_cast(arr.size())).data(&arr)); + using SetType = ArrayViewIndirectionSet; + const axom::StackArray flatShape {arr.size()}; + const axom::StackArray flatStride {arr.minStride()}; + axom::ArrayView flatView(arr.data(), flatShape, flatStride); + return SetType( + typename SetType::SetBuilder().size(static_cast(flatView.size())).data(flatView)); } /*! diff --git a/src/axom/slam/tests/slam_make_helpers.cpp b/src/axom/slam/tests/slam_make_helpers.cpp index 6a4c15ec2b..ee3a8a4fbb 100644 --- a/src/axom/slam/tests/slam_make_helpers.cpp +++ b/src/axom/slam/tests/slam_make_helpers.cpp @@ -102,8 +102,8 @@ TEST(slam_make_helpers, make_array_set_deduces_element_type) axom::Array data {1.0, 2.0, 3.0}; auto s = slam::make_indirection_set(data); - static_assert(std::is_same_v>, - "make_indirection_set(Array) deduces ArrayIndirectionSet<.., double>"); + static_assert(std::is_same_v>, + "make_indirection_set(Array) deduces ArrayViewIndirectionSet<.., double>"); static_assert(std::is_same_v, "make_indirection_set(Array) uses DefaultPositionType by default"); @@ -111,6 +111,36 @@ TEST(slam_make_helpers, make_array_set_deduces_element_type) EXPECT_TRUE(s.isValid()); EXPECT_DOUBLE_EQ(s[0], 1.0); EXPECT_DOUBLE_EQ(s[2], 3.0); + + data[1] = 20.0; + EXPECT_DOUBLE_EQ(s[1], 20.0); + + s[2] = 30.0; + EXPECT_DOUBLE_EQ(data[2], 30.0); +} + +TEST(slam_make_helpers, make_multidimensional_array_set_uses_flat_storage) +{ + const axom::StackArray shape {2, 3}; + axom::Array data(shape); + + for(axom::IndexType i = 0; i < data.size(); ++i) + { + data.flatIndex(i) = static_cast(10 + i); + } + + auto s = slam::make_indirection_set(data); + + static_assert(std::is_same_v>, + "multidimensional Array helper returns flat ArrayViewIndirectionSet"); + + ASSERT_EQ(s.size(), 6); + EXPECT_TRUE(s.isValid()); + EXPECT_EQ(s[0], 10); + EXPECT_EQ(s[4], data.flatIndex(4)); + + s[5] = 42; + EXPECT_EQ(data.flatIndex(5), 42); } TEST(slam_make_helpers, make_carray_set) From fbeeab886bdbd213495d367d73f99079df3bc7c2 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 23 Jun 2026 18:40:17 -0700 Subject: [PATCH 605/986] slam: Improves FieldRegistry field/view access checks .. and adds tests Misc: Updates some comments --- src/axom/slam/FieldRegistry.hpp | 30 +++++++---- src/axom/slam/ModularInt.hpp | 2 +- src/axom/slam/SetBuilders.hpp | 7 +++ src/axom/slam/policies/StridePolicies.hpp | 2 +- src/axom/slam/tests/slam_FieldRegistry.cpp | 59 ++++++++++++++++++++-- 5 files changed, 85 insertions(+), 15 deletions(-) diff --git a/src/axom/slam/FieldRegistry.hpp b/src/axom/slam/FieldRegistry.hpp index 7f2deea4cb..9e7b12360e 100644 --- a/src/axom/slam/FieldRegistry.hpp +++ b/src/axom/slam/FieldRegistry.hpp @@ -179,9 +179,11 @@ class FieldRegistry * \param key Field name. * \return A mutable reference to the stored field. * - * \pre `findField(key)` has a value. - * \note In debug builds, this asserts if the key is missing. - * \note For view-backed fields, use `getFieldView()`. + * \pre `findField(key)` has a value (the key exists and is owning, not view-backed). + * \note In debug builds, this asserts if the key is missing or is view-backed. + * \note For view-backed fields, use `getFieldView()`. Requesting an owning field for a + * view-backed key is a precondition violation: it asserts in debug builds, and in release + * builds the underlying `std::get` throws `std::bad_variant_access`. */ MapType& getField(std::string_view key) { @@ -195,9 +197,11 @@ class FieldRegistry * \param key Field name. * \return A const reference to the stored field. * - * \pre `findField(key)` has a value. - * \note In debug builds, this asserts if the key is missing. - * \note For view-backed fields, use `getFieldView()`. + * \pre `findField(key)` has a value (the key exists and is owning, not view-backed). + * \note In debug builds, this asserts if the key is missing or is view-backed. + * \note For view-backed fields, use `getFieldView()`. Requesting an owning field for a + * view-backed key is a precondition violation: it asserts in debug builds, and in release + * builds the underlying `std::get` throws `std::bad_variant_access`. */ const MapType& getField(std::string_view key) const { @@ -304,7 +308,10 @@ class FieldRegistry /*! * \brief Returns a mutable reference to the view-backed field for \a key. * - * \pre `hasFieldView(key)` is true. + * \pre `hasFieldView(key)` is true (the key exists and is view-backed, not owning). + * \note For owning fields, use `getField()`. Requesting a view-backed field for an owning + * key is a precondition violation: it asserts in debug builds, and in release builds the + * underlying `std::get` throws `std::bad_variant_access`. */ ViewMapType& getFieldView(std::string_view key) { @@ -315,7 +322,10 @@ class FieldRegistry /*! * \brief Returns a const reference to the view-backed field for \a key. * - * \pre `hasFieldView(key)` is true. + * \pre `hasFieldView(key)` is true (the key exists and is view-backed, not owning). + * \note For owning fields, use `getField()`. Requesting a view-backed field for an owning + * key is a precondition violation: it asserts in debug builds, and in release builds the + * underlying `std::get` throws `std::bad_variant_access`. */ const ViewMapType& getFieldView(std::string_view key) const { @@ -616,8 +626,8 @@ class FieldRegistry #ifdef AXOM_DEBUG auto it = m_maps.find(key); SLIC_ASSERT_MSG(it != m_maps.end(), "Didn't find field named " << key); - SLIC_ASSERT_MSG(it == m_maps.end() || std::holds_alternative(it->second), - "Field named " << key << " is view-backed; use getFieldView()"); + SLIC_ASSERT_MSG(std::holds_alternative(it->second), + "Field named '" << key << "' is view-backed; use getFieldView()"); #endif } diff --git a/src/axom/slam/ModularInt.hpp b/src/axom/slam/ModularInt.hpp index 4251abcc3e..1cd0f0adc3 100644 --- a/src/axom/slam/ModularInt.hpp +++ b/src/axom/slam/ModularInt.hpp @@ -69,7 +69,7 @@ class ModularInt : private SizePolicy * * \param mi other ModularInt * \return A reference to the constructed object - * \note This operator only modifies the value of the local instance, but not modify the \a modulus() + * \note This operator only modifies the value of the local instance. It does not modify the \a modulus() */ constexpr ModularInt& operator=(const ModularInt& mi) { diff --git a/src/axom/slam/SetBuilders.hpp b/src/axom/slam/SetBuilders.hpp index 64db7285ce..a7bccc013f 100644 --- a/src/axom/slam/SetBuilders.hpp +++ b/src/axom/slam/SetBuilders.hpp @@ -115,6 +115,13 @@ VectorIndirectionSet make_indirection_set(std::vector& vec) * The element type is deduced from \a arr; the set is device-capable because it * stores an ArrayView over the array's flat storage. The set's size matches the array. * + * The set exposes the array in `flatIndex` order: element \c i resolves to + * `arr.data()[i * arr.minStride()]`, matching axom::Array's own flat-index contract + * (see axom::ArrayBase::flatIndex). The flat ArrayView is therefore valid for any layout + * axom::Array produces, including multidimensional row-major arrays. For the common 1D case + * (`axom::Array` / `DIM == 1`) the storage is always contiguous with unit stride, so the + * set indexes the buffer directly. + * * \param arr the backing array (its storage must outlive the set and not be reallocated) * \return an ArrayViewIndirectionSet */ diff --git a/src/axom/slam/policies/StridePolicies.hpp b/src/axom/slam/policies/StridePolicies.hpp index c07b0b28bc..5a99df072c 100644 --- a/src/axom/slam/policies/StridePolicies.hpp +++ b/src/axom/slam/policies/StridePolicies.hpp @@ -9,7 +9,7 @@ * * \brief Stride policies for SLAM * - * Stride policies are meant to represent the fixed distance between consecutive + * Stride policies are meant to represent the fixed distance between consecutive * elements of an OrderedSet * A valid stride policy must support the following interface: * [required] diff --git a/src/axom/slam/tests/slam_FieldRegistry.cpp b/src/axom/slam/tests/slam_FieldRegistry.cpp index e1daf9daf6..d4ea508bba 100644 --- a/src/axom/slam/tests/slam_FieldRegistry.cpp +++ b/src/axom/slam/tests/slam_FieldRegistry.cpp @@ -13,6 +13,7 @@ #include "gtest/gtest.h" +#include "axom/slic.hpp" #include "axom/slam/RangeSet.hpp" #include "axom/slam/FieldRegistry.hpp" @@ -21,6 +22,7 @@ #include #include #include +#include namespace { @@ -89,13 +91,16 @@ TEST(slam_FieldRegistry, find_buffer_optional) EXPECT_FALSE(miss.has_value()); } -TEST(slam_FieldRegistry, heterogeneous_lookup_no_allocation) +TEST(slam_FieldRegistry, heterogeneous_lookup_with_string_view_key) { ScalarRegistry reg; reg.addScalar("energy", 1.0); - // Query with a string_view and a const char* -- transparent comparison means - // neither constructs a temporary std::string key. + // The lookup tables use transparent comparison (std::less<>), so a std::string_view or a const char* + // resolves directly against the std::string keys. (A non-transparent std::map would require + // an implicit std::string_view -> std::string conversion, which is explicit and would not compile. + // So the fact that these calls compile and resolve is the guarantees that no temporary std::string key + // is being constructed for lookup.) std::string_view sv = "energy"; EXPECT_TRUE(reg.hasScalar(sv)); EXPECT_TRUE(reg.hasScalar("energy")); @@ -219,3 +224,51 @@ TEST(slam_FieldRegistry, field_storage_modes_share_keyspace) EXPECT_FALSE(reg.findField("density").has_value()); EXPECT_DOUBLE_EQ(viewFieldAgain[1], 21.); } + +TEST(slam_FieldRegistry, get_wrong_storage_mode_is_a_precondition_violation) +{ + // getField()/getFieldView() fetch a specific std::variant alternative. Asking for the + // wrong alternative for an existing key is a precondition violation: it asserts in debug + // builds (via the verify*Key helpers) and throws std::bad_variant_access in release builds. + SetType s(3); + ScalarRegistry reg; + + double data[3] = {10., 20., 30.}; + reg.addFieldView("density", &s, data); // view-backed + reg.addField("temperature", &s); // owning + +#ifdef AXOM_DEBUG + // NOTE: AXOM_DEBUG is disabled in release mode, so these checks are skipped there. + EXPECT_DEATH_IF_SUPPORTED(reg.getField("density"), ""); + EXPECT_DEATH_IF_SUPPORTED(reg.getFieldView("temperature"), ""); + + const ScalarRegistry& creg = reg; + EXPECT_DEATH_IF_SUPPORTED(creg.getField("density"), ""); + EXPECT_DEATH_IF_SUPPORTED(creg.getFieldView("temperature"), ""); +#else + SLIC_INFO("Skipped assertion failure check in release mode."); + // In release builds the underlying std::get throws on the wrong alternative. + EXPECT_THROW(reg.getField("density"), std::bad_variant_access); + EXPECT_THROW(reg.getFieldView("temperature"), std::bad_variant_access); +#endif + + // The safe, non-throwing discriminators agree on the storage mode in all build types. + EXPECT_FALSE(reg.findField("density").has_value()); + EXPECT_TRUE(reg.findFieldView("density").has_value()); + EXPECT_TRUE(reg.findField("temperature").has_value()); + EXPECT_FALSE(reg.findFieldView("temperature").has_value()); +} + +//---------------------------------------------------------------------- + +int main(int argc, char* argv[]) +{ + int result = 0; + + ::testing::InitGoogleTest(&argc, argv); + axom::slic::SimpleLogger logger; + + result = RUN_ALL_TESTS(); + + return result; +} From 32a0da07a9243be04b03fb1512c163c4bdab1ff0 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 23 Jun 2026 18:46:32 -0700 Subject: [PATCH 606/986] slam: Adds debug check for sizes in make_map helpers --- src/axom/slam/MapBuilders.hpp | 40 +++++++ src/axom/slam/tests/slam_make_helpers.cpp | 135 ++++++++++++++++++++++ 2 files changed, 175 insertions(+) diff --git a/src/axom/slam/MapBuilders.hpp b/src/axom/slam/MapBuilders.hpp index 4280c3821e..381c115c9e 100644 --- a/src/axom/slam/MapBuilders.hpp +++ b/src/axom/slam/MapBuilders.hpp @@ -15,6 +15,7 @@ #define SLAM_MAP_BUILDERS_HPP_ #include "axom/core/ArrayView.hpp" +#include "axom/slic.hpp" #include "axom/slam/Map.hpp" #include "axom/slam/policies/IndirectionPolicies.hpp" @@ -30,6 +31,33 @@ axom::IndexType map_storage_size(const SetType* set, PosType stride) const axom::IndexType sz = set ? static_cast(set->size()) : axom::IndexType {0}; return sz * static_cast(stride); } + +/*! + * \brief Debug-only check that an ArrayView backing a map is correctly sized. + * + * An ArrayView-backed map indexes through `pos * stride + offset`, so the backing + * storage must contain exactly `set->size() * stride` elements. Unlike the raw-pointer + * make_map overloads (which size the view themselves), the ArrayView overloads trust the + * caller's view length; an undersized view would index out of bounds. This asserts the + * invariant in debug builds and is a no-op in release builds. + */ +template +inline void check_map_view_size(const SetType* set, + PosType stride, + const axom::ArrayView& AXOM_DEBUG_PARAM(data)) +{ +#ifdef AXOM_DEBUG + const axom::IndexType expected = map_storage_size(set, stride); + SLIC_ASSERT_MSG(static_cast(data.size()) == expected, + "slam::make_map -- ArrayView backing storage has " + << data.size() << " elements, but the set (size " + << (set ? static_cast(set->size()) : axom::IndexType {0}) + << ") with stride " << stride << " requires exactly " << expected << "."); +#else + AXOM_UNUSED_VAR(set); + AXOM_UNUSED_VAR(stride); +#endif +} } // namespace detail /*! @@ -38,6 +66,9 @@ axom::IndexType map_storage_size(const SetType* set, PosType stride) * \param set pointer to the map's set (must outlive the map) * \param stride runtime stride (#values per set element) * \param data backing storage as an ArrayView (must outlive the map) + * + * \pre `data.size() == set->size() * stride`. The view must be sized to back every + * element of the set at the given stride; this is checked in debug builds. */ template auto make_map(const SetType* set, PosType stride, axom::ArrayView data) @@ -45,11 +76,15 @@ auto make_map(const SetType* set, PosType stride, axom::ArrayView data) using Indirection = policies::ArrayViewIndirection; using Stride = policies::RuntimeStride; using MapType = Map; + detail::check_map_view_size(set, stride, data); return MapType(set, data, stride); } /*! * \brief Make a stride-one SLAM map backed by ArrayView storage. + * + * \pre `data.size() == set->size()`. The view must be sized to back every element of the + * set; this is checked in debug builds. */ template auto make_map(const SetType* set, axom::ArrayView data) @@ -57,6 +92,7 @@ auto make_map(const SetType* set, axom::ArrayView data) using Indirection = policies::ArrayViewIndirection; using Stride = policies::StrideOne; using MapType = Map; + detail::check_map_view_size(set, PosType {1}, data); return MapType(set, data); } @@ -87,6 +123,9 @@ auto make_map(const SetType* set, T* data) * \brief Make a compile-time strided SLAM map backed by ArrayView storage. * * \tparam STRIDE number of values per set element + * + * \pre `data.size() == set->size() * STRIDE`. The view must be sized to back every element + * of the set at the compile-time stride; this is checked in debug builds. */ template auto make_map_ct(const SetType* set, axom::ArrayView data) @@ -94,6 +133,7 @@ auto make_map_ct(const SetType* set, axom::ArrayView data) using Indirection = policies::ArrayViewIndirection; using Stride = policies::CompileTimeStride(STRIDE)>; using MapType = Map; + detail::check_map_view_size(set, static_cast(STRIDE), data); return MapType(set, data); } diff --git a/src/axom/slam/tests/slam_make_helpers.cpp b/src/axom/slam/tests/slam_make_helpers.cpp index ee3a8a4fbb..98912f51aa 100644 --- a/src/axom/slam/tests/slam_make_helpers.cpp +++ b/src/axom/slam/tests/slam_make_helpers.cpp @@ -13,9 +13,13 @@ #include "gtest/gtest.h" +#include "axom/slic.hpp" #include "axom/core/Array.hpp" #include "axom/slam/SetBuilders.hpp" #include "axom/slam/RelationBuilders.hpp" +#include "axom/slam/MapBuilders.hpp" +#include "axom/slam/policies/IndirectionPolicies.hpp" +#include "axom/slam/policies/StridePolicies.hpp" #include #include @@ -417,3 +421,134 @@ TEST(slam_make_helpers, make_constant_relation_runtime_stride_axom_array) EXPECT_EQ(r0[0], 1); EXPECT_EQ(r0[1], 2); } + +//------------------------------------------------------------------------------ +// Test for make_map helpers +//------------------------------------------------------------------------------ + +TEST(slam_make_helpers, make_map_stride_one_array_view) +{ + auto set = slam::make_range_set(4); + using SetT = decltype(set); + + axom::Array data {10., 20., 30., 40.}; + auto m = slam::make_map(&set, data.view()); + + using Indirection = slam::policies::ArrayViewIndirection; + using Stride = slam::policies::StrideOne; + static_assert(std::is_same_v>, + "make_map(set, ArrayView) yields a stride-one ArrayView-backed Map"); + + EXPECT_TRUE(m.isValid()); + EXPECT_EQ(m.size(), set.size()); + EXPECT_EQ(m.stride(), 1); + EXPECT_DOUBLE_EQ(m[0], 10.); + EXPECT_DOUBLE_EQ(m[3], 40.); + + // The map is a non-owning view: writes round-trip through the backing storage. + m[2] = 33.; + EXPECT_DOUBLE_EQ(data[2], 33.); + data[1] = 22.; + EXPECT_DOUBLE_EQ(m[1], 22.); +} + +TEST(slam_make_helpers, make_map_runtime_stride_array_view) +{ + auto set = slam::make_range_set(2); + + // 2 elements, each with stride/numComp 3 => 6 backing values, laid out element-major: + // element 0 -> {1, 2, 3}, element 1 -> {4, 5, 6} + axom::Array data {1., 2., 3., 4., 5., 6.}; + auto m = slam::make_map(&set, Pos {3}, data.view()); + + EXPECT_TRUE(m.isValid()); + EXPECT_EQ(m.size(), 2); + EXPECT_EQ(m.stride(), 3); + + // operator()(elem, comp) reads flat index elem*stride + comp. + EXPECT_DOUBLE_EQ(m(0, 0), 1.); + EXPECT_DOUBLE_EQ(m(0, 2), 3.); + EXPECT_DOUBLE_EQ(m(1, 0), 4.); + EXPECT_DOUBLE_EQ(m(1, 2), 6.); + + // operator[] is the flat accessor over size()*numComp() values. + EXPECT_DOUBLE_EQ(m[4], 5.); +} + +TEST(slam_make_helpers, make_map_ct_compile_time_stride_array_view) +{ + auto set = slam::make_range_set(2); + using SetT = decltype(set); + + axom::Array data {10., 11., 20., 21.}; + auto m = slam::make_map_ct<2>(&set, data.view()); + + using Indirection = slam::policies::ArrayViewIndirection; + using Stride = slam::policies::CompileTimeStride; + static_assert(std::is_same_v>, + "make_map_ct yields an ArrayView-backed compile-time-stride Map"); + + EXPECT_TRUE(m.isValid()); + EXPECT_EQ(m.size(), 2); + EXPECT_EQ(m.stride(), 2); + EXPECT_DOUBLE_EQ(m(0, 0), 10.); + EXPECT_DOUBLE_EQ(m(1, 1), 21.); +} + +TEST(slam_make_helpers, make_map_raw_pointer_overloads_size_the_view) +{ + auto set = slam::make_range_set(3); + + // stride-one raw pointer overload: view sized to set->size(). + double one[3] = {7., 8., 9.}; + auto m1 = slam::make_map(&set, one); + EXPECT_TRUE(m1.isValid()); + EXPECT_EQ(m1.stride(), 1); + EXPECT_DOUBLE_EQ(m1[2], 9.); + + // strided raw pointer overload: view sized to set->size() * stride. + double strided[6] = {1., 2., 3., 4., 5., 6.}; + auto m2 = slam::make_map(&set, Pos {2}, strided); + EXPECT_TRUE(m2.isValid()); + EXPECT_EQ(m2.stride(), 2); + EXPECT_DOUBLE_EQ(m2(2, 1), 6.); + + // compile-time-stride raw pointer overload. + auto m3 = slam::make_map_ct<2>(&set, strided); + EXPECT_TRUE(m3.isValid()); + EXPECT_EQ(m3.stride(), 2); + EXPECT_DOUBLE_EQ(m3(0, 1), 2.); +} + +TEST(slam_make_helpers, make_map_undersized_view_is_a_precondition_violation) +{ + // The ArrayView-taking make_map overloads require data.size() == set->size() * stride. + // An undersized view is checked in debug builds (no-op in release). + auto set = slam::make_range_set(4); + +#ifdef AXOM_DEBUG + // NOTE: AXOM_DEBUG is disabled in release mode, so these checks are skipped there. + axom::Array tooSmall {1., 2., 3.}; // need 4 for stride one + EXPECT_DEATH_IF_SUPPORTED(slam::make_map(&set, tooSmall.view()), ""); + + axom::Array tooSmallStrided {1., 2., 3., 4., 5.}; // need 8 for stride two + EXPECT_DEATH_IF_SUPPORTED(slam::make_map(&set, Pos {2}, tooSmallStrided.view()), ""); + EXPECT_DEATH_IF_SUPPORTED(slam::make_map_ct<2>(&set, tooSmallStrided.view()), ""); +#else + SLIC_INFO("Skipped assertion failure check in release mode."); +#endif +} + +//---------------------------------------------------------------------- + +int main(int argc, char* argv[]) +{ + int result = 0; + + ::testing::InitGoogleTest(&argc, argv); + axom::slic::SimpleLogger logger; + + result = RUN_ALL_TESTS(); + + return result; +} From 90b690c0984b1eea1ff1e75439521c313a26f9e8 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 23 Jun 2026 20:15:42 -0700 Subject: [PATCH 607/986] Updates RELEASE-NOTES --- RELEASE-NOTES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index bda70dd0b1..16b88ea14f 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -44,6 +44,9 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ - Primal: Adds a `primal::BezierTriangle` class - Inlet: Added the ability to have collections (array and dictionary) with variant values. - Inlet: Added the ability to have collections (array and dictionary) with variant user defined structures. +- Core: Adds `axom::Optional` as a device capable analog for `std::optional` +- Core: Adds `AXOM_CONSTEXPR_ASSERT` macro for assertions that are usable within `constexpr` contexts +- Slam: Adds `make_*_set`, `make_*relation` and `make_map` helper functions for building sets, relations and maps ### Removed - Bump: Removed `axom::bump::views::MultiBufferMaterialView`, which was a view type for an obsolete flavor of Blueprint matset. From 7769c6b8cac6886d0e54accfc18bf7a483fe82ae Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Mon, 29 Jun 2026 11:45:30 -0700 Subject: [PATCH 608/986] Core: Moves implementation of processAbort to Abort.cpp --- src/axom/core/CMakeLists.txt | 1 + src/axom/core/utilities/Abort.cpp | 32 +++++++++++++++++++++++++++ src/axom/core/utilities/Utilities.cpp | 27 ++-------------------- 3 files changed, 35 insertions(+), 25 deletions(-) create mode 100644 src/axom/core/utilities/Abort.cpp diff --git a/src/axom/core/CMakeLists.txt b/src/axom/core/CMakeLists.txt index 0d394089a0..f2d1f1455e 100644 --- a/src/axom/core/CMakeLists.txt +++ b/src/axom/core/CMakeLists.txt @@ -104,6 +104,7 @@ set(core_headers ) set(core_sources + utilities/Abort.cpp utilities/Annotations.cpp utilities/FileUtilities.cpp utilities/StringUtilities.cpp diff --git a/src/axom/core/utilities/Abort.cpp b/src/axom/core/utilities/Abort.cpp new file mode 100644 index 0000000000..8d88d3658b --- /dev/null +++ b/src/axom/core/utilities/Abort.cpp @@ -0,0 +1,32 @@ +// 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" +#include "axom/core/utilities/Abort.hpp" + +#ifdef AXOM_USE_MPI + #include +#endif + +#include + +namespace axom::utilities +{ +[[noreturn]] void processAbort() +{ +#ifndef AXOM_USE_MPI + abort(); +#else + int mpi = 0; + MPI_Initialized(&mpi); + if(mpi) + { + MPI_Abort(MPI_COMM_WORLD, EXIT_FAILURE); + } + abort(); +#endif +} +} // end namespace axom::utilities diff --git a/src/axom/core/utilities/Utilities.cpp b/src/axom/core/utilities/Utilities.cpp index c197f257d9..83d4ff70bf 100644 --- a/src/axom/core/utilities/Utilities.cpp +++ b/src/axom/core/utilities/Utilities.cpp @@ -14,30 +14,8 @@ #include "axom/config.hpp" #include "axom/core/utilities/Utilities.hpp" -#include // for exit, EXIT_SUCCESS, EXIT_FAILURE - -#ifdef AXOM_USE_MPI - #include -#endif - -namespace axom -{ -namespace utilities -{ -[[noreturn]] void processAbort() +namespace axom::utilities { -#ifndef AXOM_USE_MPI - abort(); -#else - int mpi = 0; - MPI_Initialized(&mpi); - if(mpi) - { - MPI_Abort(MPI_COMM_WORLD, EXIT_FAILURE); - } - abort(); -#endif -} int binomialCoefficient(int n, int k) { @@ -63,5 +41,4 @@ int binomialCoefficient(int n, int k) return val; } -} // end namespace utilities -} // end namespace axom +} // end namespace axom::utilities From 3dea8e132b195c5027829aa910a5a61b5f7e6e29 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 1 Jul 2026 09:14:48 -0700 Subject: [PATCH 609/986] slam: Adds IntType alias to RuntimeValue Per PR suggestion. It was already available in the CompileTimeValue. --- src/axom/slam/policies/ValuePolicies.hpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/axom/slam/policies/ValuePolicies.hpp b/src/axom/slam/policies/ValuePolicies.hpp index f7acafac93..9f1671da7b 100644 --- a/src/axom/slam/policies/ValuePolicies.hpp +++ b/src/axom/slam/policies/ValuePolicies.hpp @@ -101,10 +101,9 @@ struct RuntimeValue { public: using TagType = Tag; + using IntType = decltype(Tag::defaultValue()); - AXOM_HOST_DEVICE constexpr RuntimeValue(decltype(Tag::defaultValue()) val = Tag::defaultValue()) - : m_value(val) - { } + AXOM_HOST_DEVICE constexpr RuntimeValue(IntType val = Tag::defaultValue()) : m_value(val) { } AXOM_HOST_DEVICE constexpr auto value() const { return m_value; } AXOM_HOST_DEVICE constexpr auto& value() { return m_value; } @@ -115,7 +114,7 @@ struct RuntimeValue constexpr bool isValid(bool) const { return Tag::isValidValue(m_value); } protected: - decltype(Tag::defaultValue()) m_value; + IntType m_value; }; /*! From 10569dd678fe34d26e5238fbc7a1b7ef4351f7b0 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 1 Jul 2026 09:53:46 -0700 Subject: [PATCH 610/986] slam: Consistently use string_view in FieldRegistry --- src/axom/slam/FieldRegistry.hpp | 39 ++++++++++++---------- src/axom/slam/tests/slam_FieldRegistry.cpp | 12 +++++++ 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/src/axom/slam/FieldRegistry.hpp b/src/axom/slam/FieldRegistry.hpp index 9e7b12360e..da0c4fcfb2 100644 --- a/src/axom/slam/FieldRegistry.hpp +++ b/src/axom/slam/FieldRegistry.hpp @@ -106,9 +106,9 @@ class FieldRegistry * * \note If an entry with the same key already exists, it is overwritten. */ - MapType& addField(KeyType key, const SetType* theSet) + MapType& addField(std::string_view key, const SetType* theSet) { - auto [it, _] = m_maps.insert_or_assign(std::move(key), FieldStorageType(MapType(theSet))); + auto [it, _] = m_maps.insert_or_assign(KeyType(key), FieldStorageType(MapType(theSet))); return std::get(it->second); } @@ -122,10 +122,10 @@ class FieldRegistry * * \note If an entry with the same key already exists, it is overwritten. */ - ViewMapType& addField(KeyType key, const SetType* theSet, axom::ArrayView data) + ViewMapType& addField(std::string_view key, const SetType* theSet, axom::ArrayView data) { auto [it, _] = - m_maps.insert_or_assign(std::move(key), FieldStorageType(slam::make_map(theSet, data))); + m_maps.insert_or_assign(KeyType(key), FieldStorageType(slam::make_map(theSet, data))); return std::get(it->second); } @@ -140,10 +140,10 @@ class FieldRegistry * * \note If an entry with the same key already exists, it is overwritten. */ - ViewMapType& addField(KeyType key, const SetType* theSet, DataType* data) + ViewMapType& addField(std::string_view key, const SetType* theSet, DataType* data) { auto [it, _] = - m_maps.insert_or_assign(std::move(key), FieldStorageType(slam::make_map(theSet, data))); + m_maps.insert_or_assign(KeyType(key), FieldStorageType(slam::make_map(theSet, data))); return std::get(it->second); } @@ -272,9 +272,9 @@ class FieldRegistry * * \note If an entry with the same key already exists, it is overwritten. */ - ViewMapType& addFieldView(KeyType key, const SetType* theSet, axom::ArrayView data) + ViewMapType& addFieldView(std::string_view key, const SetType* theSet, axom::ArrayView data) { - return addField(std::move(key), theSet, data); + return addField(key, theSet, data); } /*! @@ -288,9 +288,9 @@ class FieldRegistry * * \note If an entry with the same key already exists, it is overwritten. */ - ViewMapType& addFieldView(KeyType key, const SetType* theSet, DataType* data) + ViewMapType& addFieldView(std::string_view key, const SetType* theSet, DataType* data) { - return addField(std::move(key), theSet, data); + return addField(key, theSet, data); } /*! @@ -390,9 +390,9 @@ class FieldRegistry * * \note If an entry with the same key already exists, it is overwritten. */ - BufferType& addBuffer(KeyType key, int size = 0) + BufferType& addBuffer(std::string_view key, int size = 0) { - auto [it, _] = m_buff.insert_or_assign(std::move(key), BufferType(size)); + auto [it, _] = m_buff.insert_or_assign(KeyType(key), BufferType(size)); return it->second; } @@ -475,9 +475,9 @@ class FieldRegistry * * \note If an entry with the same key already exists, it is overwritten. */ - ViewBufferType& addBufferView(KeyType key, ViewBufferType view) + ViewBufferType& addBufferView(std::string_view key, ViewBufferType view) { - auto [it, _] = m_view_buff.insert_or_assign(std::move(key), view); + auto [it, _] = m_view_buff.insert_or_assign(KeyType(key), view); return it->second; } @@ -489,10 +489,9 @@ class FieldRegistry * \param size Number of elements. * \return A mutable reference to the stored view. */ - ViewBufferType& addBufferView(KeyType key, DataType* data, PositionType size) + ViewBufferType& addBufferView(std::string_view key, DataType* data, PositionType size) { - return addBufferView(std::move(key), - axom::ArrayView(data, static_cast(size))); + return addBufferView(key, axom::ArrayView(data, static_cast(size))); } /*! @@ -576,7 +575,11 @@ class FieldRegistry * * \note If an entry with the same key already exists, it is overwritten. */ - DataType& addScalar(KeyType key, DataType val) { return m_scal[std::move(key)] = val; } + DataType& addScalar(std::string_view key, DataType val) + { + auto [it, _] = m_scal.insert_or_assign(KeyType(key), val); + return it->second; + } /*! * \brief Returns a mutable reference to the scalar for \a key. diff --git a/src/axom/slam/tests/slam_FieldRegistry.cpp b/src/axom/slam/tests/slam_FieldRegistry.cpp index d4ea508bba..520b8acc65 100644 --- a/src/axom/slam/tests/slam_FieldRegistry.cpp +++ b/src/axom/slam/tests/slam_FieldRegistry.cpp @@ -93,6 +93,7 @@ TEST(slam_FieldRegistry, find_buffer_optional) TEST(slam_FieldRegistry, heterogeneous_lookup_with_string_view_key) { + SetType s(3); ScalarRegistry reg; reg.addScalar("energy", 1.0); @@ -109,6 +110,17 @@ TEST(slam_FieldRegistry, heterogeneous_lookup_with_string_view_key) std::optional hit = reg.findScalar(sv); ASSERT_TRUE(hit.has_value()); EXPECT_DOUBLE_EQ(*hit, 1.0); + + // check that we're properly using string_view on substrings + std::string field_storage = "xxfield_nameyy"; + std::string_view field_key(field_storage.data() + 2, 10); + reg.addField(field_key, &s); + EXPECT_TRUE(reg.hasField("field_name")); + + std::string buffer_storage = "xxbuffer_nameyy"; + std::string_view buffer_key(buffer_storage.data() + 2, 11); + reg.addBuffer(buffer_key, 3); + EXPECT_TRUE(reg.hasBuffer("buffer_name")); } TEST(slam_FieldRegistry, nameless_keys_are_unique) From 352fe1a82ca067124e7d32e541694c1c466be7fa Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 1 Jul 2026 10:19:44 -0700 Subject: [PATCH 611/986] slam: Remove unnecessary reference_wrappers in FieldRegistry Per PR review suggestion. --- src/axom/slam/FieldRegistry.hpp | 24 ++++++++-------------- src/axom/slam/tests/slam_FieldRegistry.cpp | 15 ++++++++++---- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/src/axom/slam/FieldRegistry.hpp b/src/axom/slam/FieldRegistry.hpp index da0c4fcfb2..30ed52bb0a 100644 --- a/src/axom/slam/FieldRegistry.hpp +++ b/src/axom/slam/FieldRegistry.hpp @@ -338,7 +338,7 @@ class FieldRegistry * * \return An optional referencing the field if present, else empty. */ - [[nodiscard]] std::optional> findFieldView(std::string_view key) + [[nodiscard]] std::optional findFieldView(std::string_view key) { auto it = m_maps.find(key); if(it == m_maps.end()) @@ -347,8 +347,7 @@ class FieldRegistry } auto* field = std::get_if(&it->second); - return field != nullptr ? std::optional>(*field) - : std::nullopt; + return field != nullptr ? std::optional(*field) : std::nullopt; } /*! @@ -356,8 +355,7 @@ class FieldRegistry * * \return An optional referencing the field if present, else empty. */ - [[nodiscard]] std::optional> findFieldView( - std::string_view key) const + [[nodiscard]] std::optional findFieldView(std::string_view key) const { auto it = m_maps.find(key); if(it == m_maps.end()) @@ -366,8 +364,7 @@ class FieldRegistry } const auto* field = std::get_if(&it->second); - return field != nullptr ? std::optional>(*field) - : std::nullopt; + return field != nullptr ? std::optional(*field) : std::nullopt; } /// @} @@ -533,12 +530,10 @@ class FieldRegistry * * \return An optional referencing the buffer if present, else empty. */ - [[nodiscard]] std::optional> findBufferView(std::string_view key) + [[nodiscard]] std::optional findBufferView(std::string_view key) { auto it = m_view_buff.find(key); - return it != m_view_buff.end() - ? std::optional>(it->second) - : std::nullopt; + return it != m_view_buff.end() ? std::optional(it->second) : std::nullopt; } /*! @@ -546,13 +541,10 @@ class FieldRegistry * * \return An optional referencing the buffer if present, else empty. */ - [[nodiscard]] std::optional> findBufferView( - std::string_view key) const + [[nodiscard]] std::optional findBufferView(std::string_view key) const { auto it = m_view_buff.find(key); - return it != m_view_buff.end() - ? std::optional>(it->second) - : std::nullopt; + return it != m_view_buff.end() ? std::optional(it->second) : std::nullopt; } /// @} diff --git a/src/axom/slam/tests/slam_FieldRegistry.cpp b/src/axom/slam/tests/slam_FieldRegistry.cpp index 520b8acc65..1079a89777 100644 --- a/src/axom/slam/tests/slam_FieldRegistry.cpp +++ b/src/axom/slam/tests/slam_FieldRegistry.cpp @@ -151,12 +151,14 @@ TEST(slam_FieldRegistry, view_buffer_add_get_find) auto hit = reg.findBufferView("view"); ASSERT_TRUE(hit.has_value()); - EXPECT_EQ(hit->get()[3], 40); + EXPECT_EQ((*hit)[3], 40); + (*hit)[1] = 25; + EXPECT_EQ(data[1], 25); const IndexRegistry& creg = reg; auto chit = creg.findBufferView("view"); ASSERT_TRUE(chit.has_value()); - EXPECT_EQ(chit->get()[0], 10); + EXPECT_EQ((*chit)[0], 10); } TEST(slam_FieldRegistry, view_field_is_non_owning) @@ -200,8 +202,13 @@ TEST(slam_FieldRegistry, view_field_is_non_owning) auto hit = creg.findFieldView("temp"); ASSERT_TRUE(hit.has_value()); - EXPECT_EQ(hit->get().size(), 5); - EXPECT_DOUBLE_EQ(hit->get()[2], 42.0); + EXPECT_EQ(hit->size(), 5); + EXPECT_DOUBLE_EQ((*hit)[2], 42.0); + + auto mutable_hit = reg.findFieldView("temp"); + ASSERT_TRUE(mutable_hit.has_value()); + (*mutable_hit)[3] = 12.0; + EXPECT_DOUBLE_EQ(data[3], 12.0); } TEST(slam_FieldRegistry, field_storage_modes_share_keyspace) From 3ada2422472ffd6c52870ebf27b7e011655fab38 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 1 Jul 2026 15:29:48 -0700 Subject: [PATCH 612/986] Remove the device-accessible axom::Optional -- we can just use std::optional on device Thanks for pointing this out, Max! --- RELEASE-NOTES.md | 1 - src/axom/core/CMakeLists.txt | 1 - src/axom/core/Optional.hpp | 88 --------------------- src/axom/core/tests/CMakeLists.txt | 1 - src/axom/core/tests/core_optional.hpp | 45 ----------- src/axom/core/tests/core_serial_main.cpp | 1 - src/axom/slam/BivariateSet.hpp | 29 ++++--- src/axom/slam/DynamicSet.hpp | 11 +-- src/axom/slam/ProductSet.hpp | 27 ++++--- src/axom/slam/RelationSet.hpp | 28 ++++--- src/axom/slam/docs/sphinx/portability.rst | 22 ++---- src/axom/slam/tests/slam_static_asserts.cpp | 17 +--- 12 files changed, 59 insertions(+), 212 deletions(-) delete mode 100644 src/axom/core/Optional.hpp delete mode 100644 src/axom/core/tests/core_optional.hpp diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 16b88ea14f..a71cb3b46e 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -44,7 +44,6 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ - Primal: Adds a `primal::BezierTriangle` class - Inlet: Added the ability to have collections (array and dictionary) with variant values. - Inlet: Added the ability to have collections (array and dictionary) with variant user defined structures. -- Core: Adds `axom::Optional` as a device capable analog for `std::optional` - Core: Adds `AXOM_CONSTEXPR_ASSERT` macro for assertions that are usable within `constexpr` contexts - Slam: Adds `make_*_set`, `make_*relation` and `make_map` helper functions for building sets, relations and maps diff --git a/src/axom/core/CMakeLists.txt b/src/axom/core/CMakeLists.txt index f2d1f1455e..a393c350da 100644 --- a/src/axom/core/CMakeLists.txt +++ b/src/axom/core/CMakeLists.txt @@ -76,7 +76,6 @@ set(core_headers MDMapping.hpp NumericArray.hpp NumericLimits.hpp - Optional.hpp Path.hpp RangeAdapter.hpp StackArray.hpp diff --git a/src/axom/core/Optional.hpp b/src/axom/core/Optional.hpp deleted file mode 100644 index 9f8a577a04..0000000000 --- a/src/axom/core/Optional.hpp +++ /dev/null @@ -1,88 +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) - -/*! - * \file Optional.hpp - * - * \brief A minimal, host/device "maybe a value" type. - * - * `std::optional` is a host-only facility in Axom's portability model - * and is not guaranteed to be available/usable in device code across all backends (SEQ/OMP/CUDA/HIP). - * - * `axom::Optional` is a trivially-structured aggregate of `{storage, engaged flag}` - * that is `AXOM_HOST_DEVICE` throughout and has no throwing `value()` accessor. - * - * The contract is to check `has_value()` before accessing the value. - * In host debug builds, a disengaged dereference asserts, and during constant evaluation it is a compile error. - * - * This type is intentionally small and is not a drop-in replacement for `std::optional`. - * Specifically, it does not provide exceptions, monadic combinators, or in-place construction. - */ - -#ifndef AXOM_OPTIONAL_HPP_ -#define AXOM_OPTIONAL_HPP_ - -#include "axom/core/Macros.hpp" - -#include - -namespace axom -{ -/*! - * \class Optional - * \brief Host/device "maybe a value of type T". - * - * \tparam T The value type. Intended for small, trivially-copyable types - * that are safe to capture and use in device kernels. - */ -template -struct Optional -{ - static_assert(std::is_trivially_copyable_v, - "axom::Optional is intended for trivially-copyable value types"); - - T m_value {}; - bool m_engaged {false}; - - /// \brief Construct a disengaged Optional (no value). - AXOM_HOST_DEVICE constexpr Optional() = default; - - /// \brief Construct an engaged Optional holding \a value. - AXOM_HOST_DEVICE constexpr Optional(const T& value) : m_value(value), m_engaged(true) { } - - /// \brief Whether this Optional holds a value. - AXOM_HOST_DEVICE constexpr bool has_value() const { return m_engaged; } - - /// \brief Whether this Optional holds a value (bool conversion). - AXOM_HOST_DEVICE constexpr explicit operator bool() const { return m_engaged; } - - /*! - * \brief Access the contained value. - * \pre has_value() is true. There is no throwing accessor. - * In host debug builds, a disengaged access asserts, - * and during constant evaluation it is a compile error. - */ - AXOM_HOST_DEVICE constexpr const T& operator*() const - { - AXOM_CONSTEXPR_ASSERT(m_engaged); - return m_value; - } - AXOM_HOST_DEVICE constexpr T& operator*() - { - AXOM_CONSTEXPR_ASSERT(m_engaged); - return m_value; - } - - /// \brief Return the contained value if engaged, otherwise \a fallback. - AXOM_HOST_DEVICE constexpr T value_or(const T& fallback) const - { - return m_engaged ? m_value : fallback; - } -}; - -} // namespace axom - -#endif // AXOM_OPTIONAL_HPP_ diff --git a/src/axom/core/tests/CMakeLists.txt b/src/axom/core/tests/CMakeLists.txt index f35b7b4823..5fb81e4c46 100644 --- a/src/axom/core/tests/CMakeLists.txt +++ b/src/axom/core/tests/CMakeLists.txt @@ -23,7 +23,6 @@ set(core_serial_tests core_utilities.hpp core_bit_utilities.hpp core_constexpr_assert.hpp - core_optional.hpp core_device_hash.hpp core_execution_for_all.hpp core_execution_scans.hpp diff --git a/src/axom/core/tests/core_optional.hpp b/src/axom/core/tests/core_optional.hpp deleted file mode 100644 index e560cf0f01..0000000000 --- a/src/axom/core/tests/core_optional.hpp +++ /dev/null @@ -1,45 +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_CORE_TESTS_CORE_OPTIONAL_HPP_ -#define AXOM_CORE_TESTS_CORE_OPTIONAL_HPP_ - -#include "gtest/gtest.h" - -#include "axom/core/Optional.hpp" - -namespace -{ -static_assert(!axom::Optional().has_value(), "default Optional is disengaged"); -static_assert(axom::Optional(42).has_value(), "value-constructed Optional is engaged"); -static_assert(*axom::Optional(42) == 42, "engaged Optional yields its value"); -static_assert(axom::Optional(42).value_or(-1) == 42, "value_or returns the value when engaged"); -static_assert(axom::Optional().value_or(-1) == -1, "value_or returns fallback when empty"); -static_assert(static_cast(axom::Optional(0)), "engaged-with-zero is still engaged"); -static_assert(!static_cast(axom::Optional()), "disengaged converts to false"); - -static_assert(std::is_trivially_copyable_v>, - "axom::Optional is trivially copyable (device-capturable)"); -} // namespace - -TEST(core_optional, engaged_and_disengaged) -{ - axom::Optional empty; - EXPECT_FALSE(empty.has_value()); - EXPECT_FALSE(static_cast(empty)); - EXPECT_DOUBLE_EQ(empty.value_or(2.5), 2.5); - - axom::Optional full(3.25); - EXPECT_TRUE(full.has_value()); - EXPECT_TRUE(static_cast(full)); - EXPECT_DOUBLE_EQ(*full, 3.25); - EXPECT_DOUBLE_EQ(full.value_or(2.5), 3.25); - - *full = 9.0; - EXPECT_DOUBLE_EQ(*full, 9.0); -} - -#endif // AXOM_CORE_TESTS_CORE_OPTIONAL_HPP_ diff --git a/src/axom/core/tests/core_serial_main.cpp b/src/axom/core/tests/core_serial_main.cpp index 3cb13e2878..af7d5e17f0 100644 --- a/src/axom/core/tests/core_serial_main.cpp +++ b/src/axom/core/tests/core_serial_main.cpp @@ -15,7 +15,6 @@ #include "core_utilities.hpp" #include "core_bit_utilities.hpp" #include "core_constexpr_assert.hpp" -#include "core_optional.hpp" #include "core_device_hash.hpp" #include "core_execution_for_all.hpp" #include "core_execution_scans.hpp" diff --git a/src/axom/slam/BivariateSet.hpp b/src/axom/slam/BivariateSet.hpp index dfb50afce7..a4080ad7ff 100644 --- a/src/axom/slam/BivariateSet.hpp +++ b/src/axom/slam/BivariateSet.hpp @@ -22,9 +22,8 @@ #include "axom/slam/RangeSet.hpp" #include "axom/slam/policies/PolicyTraits.hpp" -#include "axom/core/Optional.hpp" - #include +#include #include namespace axom @@ -144,17 +143,17 @@ class BivariateSet /*! * \brief Finds the SparseIndex of the element given its DenseIndex. * - * \return An engaged `axom::Optional` containing the SparseIndex if the element exists, - * or an empty `axom::Optional` if the element does not exist. + * \return An engaged `std::optional` containing the SparseIndex if the element exists, + * or an empty `std::optional` if the element does not exist. * * \note This is a convenience wrapper around `findElementIndex(...)` that avoids * sentinel checks against `INVALID_POS`. */ - [[nodiscard]] axom::Optional findElementIndexOptional(PositionType pos1, - PositionType pos2) const + [[nodiscard]] std::optional findElementIndexOptional(PositionType pos1, + PositionType pos2) const { const auto idx = findElementIndex(pos1, pos2); - return idx != INVALID_POS ? axom::Optional(idx) : axom::Optional {}; + return idx != INVALID_POS ? std::optional(idx) : std::optional {}; } /** @@ -172,18 +171,18 @@ class BivariateSet /*! * \brief Finds the FlatIndex of the element given its DenseIndex. * - * \return An engaged `axom::Optional` containing the FlatIndex if the element exists, - * or an empty `axom::Optional` if the element does not exist. + * \return An engaged `std::optional` containing the FlatIndex if the element exists, + * or an empty `std::optional` if the element does not exist. * * \note This is a convenience wrapper around `findElementFlatIndex(...)` that avoids * sentinel checks against `INVALID_POS`. */ - [[nodiscard]] AXOM_HOST_DEVICE axom::Optional findElementFlatIndexOptional( + [[nodiscard]] AXOM_HOST_DEVICE std::optional findElementFlatIndexOptional( PositionType pos1, PositionType pos2) const { const auto idx = findElementFlatIndex(pos1, pos2); - return idx != INVALID_POS ? axom::Optional(idx) : axom::Optional {}; + return idx != INVALID_POS ? std::optional(idx) : std::optional {}; } /** @@ -200,16 +199,16 @@ class BivariateSet /*! * \brief Finds the FlatIndex of the first existing element in a row. * - * \return An engaged `axom::Optional` containing the FlatIndex if the row contains any elements, - * or an empty `axom::Optional` if the row is empty. + * \return An engaged `std::optional` containing the FlatIndex if the row contains any elements, + * or an empty `std::optional` if the row is empty. * * \note This is a convenience wrapper around `findElementFlatIndex(pos1)` that avoids * sentinel checks against `INVALID_POS`. */ - [[nodiscard]] axom::Optional findElementFlatIndexOptional(PositionType pos1) const + [[nodiscard]] std::optional findElementFlatIndexOptional(PositionType pos1) const { const auto idx = findElementFlatIndex(pos1); - return idx != INVALID_POS ? axom::Optional(idx) : axom::Optional {}; + return idx != INVALID_POS ? std::optional(idx) : std::optional {}; } /** diff --git a/src/axom/slam/DynamicSet.hpp b/src/axom/slam/DynamicSet.hpp index 775747fdab..748251bae7 100644 --- a/src/axom/slam/DynamicSet.hpp +++ b/src/axom/slam/DynamicSet.hpp @@ -16,10 +16,11 @@ #include "axom/config.hpp" #include "axom/core/IteratorBase.hpp" -#include "axom/core/Optional.hpp" #include "axom/slam/OrderedSet.hpp" #include "axom/slam/RangeSet.hpp" +#include + namespace axom { namespace slam @@ -304,15 +305,15 @@ class DynamicSet : public Set, SizePolicy /*! * \brief Given a value, find the index of the first entry containing it. * - * \return An engaged `axom::Optional` with the index of the first element with - * value \a e, or an empty `axom::Optional` if none can be found. + * \return An engaged `std::optional` with the index of the first element with + * value \a e, or an empty `std::optional` if none can be found. * \note This is an O(n) operation in the size of the set. */ - [[nodiscard]] axom::Optional findIndexOptional(ElementType e) const + [[nodiscard]] std::optional findIndexOptional(ElementType e) const { const IndexType idx = findIndex(e); const IndexType invalid = static_cast(INVALID_ENTRY); - return idx != invalid ? axom::Optional(idx) : axom::Optional {}; + return idx != invalid ? std::optional(idx) : std::optional {}; } /** diff --git a/src/axom/slam/ProductSet.hpp b/src/axom/slam/ProductSet.hpp index 4df3635b28..db40c7da65 100644 --- a/src/axom/slam/ProductSet.hpp +++ b/src/axom/slam/ProductSet.hpp @@ -20,6 +20,7 @@ #include "axom/slam/policies/BivariateSetInterfacePolicies.hpp" #include +#include namespace axom { @@ -139,14 +140,14 @@ class ProductSet final : public policies::BivariateSetInterface findElementIndexOptional(PositionType pos1, - PositionType pos2) const + [[nodiscard]] std::optional findElementIndexOptional(PositionType pos1, + PositionType pos2) const { const auto idx = findElementIndex(pos1, pos2); - return idx != BaseType::INVALID_POS ? axom::Optional(idx) - : axom::Optional {}; + return idx != BaseType::INVALID_POS ? std::optional(idx) + : std::optional {}; } /** @@ -173,15 +174,15 @@ class ProductSet final : public policies::BivariateSetInterface findElementFlatIndexOptional( + [[nodiscard]] AXOM_HOST_DEVICE std::optional findElementFlatIndexOptional( PositionType pos1, PositionType pos2) const { const auto idx = findElementFlatIndex(pos1, pos2); - return idx != BaseType::INVALID_POS ? axom::Optional(idx) - : axom::Optional {}; + return idx != BaseType::INVALID_POS ? std::optional(idx) + : std::optional {}; } /** @@ -207,13 +208,13 @@ class ProductSet final : public policies::BivariateSetInterface findElementFlatIndexOptional(PositionType pos1) const + [[nodiscard]] std::optional findElementFlatIndexOptional(PositionType pos1) const { const auto idx = findElementFlatIndex(pos1); - return idx != BaseType::INVALID_POS ? axom::Optional(idx) - : axom::Optional {}; + return idx != BaseType::INVALID_POS ? std::optional(idx) + : std::optional {}; } /** diff --git a/src/axom/slam/RelationSet.hpp b/src/axom/slam/RelationSet.hpp index 388556d58d..839ea3570e 100644 --- a/src/axom/slam/RelationSet.hpp +++ b/src/axom/slam/RelationSet.hpp @@ -11,6 +11,8 @@ #include "axom/slam/BivariateSet.hpp" #include "axom/slam/policies/BivariateSetInterfacePolicies.hpp" +#include + namespace axom { namespace slam @@ -120,14 +122,14 @@ class RelationSet final : public policies::BivariateSetInterface findElementIndexOptional(PositionType pos1, - PositionType pos2) const + [[nodiscard]] std::optional findElementIndexOptional(PositionType pos1, + PositionType pos2) const { const auto idx = findElementIndex(pos1, pos2); - return idx != BaseType::INVALID_POS ? axom::Optional(idx) - : axom::Optional {}; + return idx != BaseType::INVALID_POS ? std::optional(idx) + : std::optional {}; } /** @@ -156,15 +158,15 @@ class RelationSet final : public policies::BivariateSetInterface findElementFlatIndexOptional( + [[nodiscard]] AXOM_HOST_DEVICE std::optional findElementFlatIndexOptional( PositionType s1, PositionType s2) const { const auto idx = findElementFlatIndex(s1, s2); - return idx != BaseType::INVALID_POS ? axom::Optional(idx) - : axom::Optional {}; + return idx != BaseType::INVALID_POS ? std::optional(idx) + : std::optional {}; } /** @@ -192,13 +194,13 @@ class RelationSet final : public policies::BivariateSetInterface findElementFlatIndexOptional(PositionType pos1) const + [[nodiscard]] std::optional findElementFlatIndexOptional(PositionType pos1) const { const auto idx = findElementFlatIndex(pos1); - return idx != BaseType::INVALID_POS ? axom::Optional(idx) - : axom::Optional {}; + return idx != BaseType::INVALID_POS ? std::optional(idx) + : std::optional {}; } /** diff --git a/src/axom/slam/docs/sphinx/portability.rst b/src/axom/slam/docs/sphinx/portability.rst index 5a726c3edb..966b1173b6 100644 --- a/src/axom/slam/docs/sphinx/portability.rst +++ b/src/axom/slam/docs/sphinx/portability.rst @@ -28,10 +28,10 @@ and are therefore unconditionally kernel-safe. class template argument deduction (CTAD), non-type template parameters, fold expressions, type traits, ``constexpr`` evaluation) as well as Axom host-device types (``StackArray``, ``ArrayView``, - ``NumericLimits``, ``Optional``, ``utilities::*``). + ``NumericLimits``, ``utilities::*``). - everywhere, including kernels * - B - - ``std::optional``, ``std::string_view``, ``std::variant``, + - ``std::string_view``, ``std::variant``, ``std::ranges`` views/algorithms, ``std::vector``, ``std::map``, exceptions, and iostreams. - host only: builders, registries, ``isValid(verbose)``, and I/O @@ -40,18 +40,12 @@ and are therefore unconditionally kernel-safe. view types; throwing accessors on host-device paths. - nowhere (existing instances are migration targets) -Why not a device ``std::optional``? ------------------------------------ +Device ``std::optional`` +------------------------ -``libcu++`` and ``libhipcxx`` provide device-capable ``tuple``/``optional``/ ``variant``/``span``, -but we cannot depend on them for our non-GPU sequential and OpenMP builds. -Instead, Slam uses small internal host-device types in the spirit of ``axom::StackArray``. +Slam uses ``std::optional`` for optional-returning APIs. +The CUDA host-configs enable ``--expt-relaxed-constexpr``, +and HIP's compiler accepts these standard library calls from host-device paths in the supported builds. -:cpp:class:`axom::Optional` is the one such type Axom provides for this purpose. -It is a trivially-copyable aggregate of an engaged flag and storage, -is ``AXOM_HOST_DEVICE`` throughout, and has no throwing ``value()`` accessor. -The contract is to check ``has_value()`` (or use ``value_or``) before dereferencing. - -The host-side counterpart is unchanged: host-only registries and find APIs -(for example :cpp:class:`axom::slam::FieldRegistry`) return ``std::optional``. +The contract is to check ``has_value()`` (or use ``value_or``) before dereferencing. diff --git a/src/axom/slam/tests/slam_static_asserts.cpp b/src/axom/slam/tests/slam_static_asserts.cpp index bb3ecc8035..92ccd2cf11 100644 --- a/src/axom/slam/tests/slam_static_asserts.cpp +++ b/src/axom/slam/tests/slam_static_asserts.cpp @@ -18,13 +18,14 @@ #include "gtest/gtest.h" -#include "axom/core/Optional.hpp" #include "axom/slam/ModularInt.hpp" #include "axom/slam/policies/SizePolicies.hpp" #include "axom/slam/policies/StridePolicies.hpp" #include "axom/slam/policies/OffsetPolicies.hpp" #include "axom/slam/policies/ValuePolicies.hpp" +#include + namespace { namespace slam = axom::slam; @@ -126,20 +127,6 @@ static_assert(incTwice(4) == 1, "++ twice from 4 mod 5 == 1"); static_assert(Mod5(2) == Mod5(7), "2 and 7 are equal mod 5"); static_assert(Mod5(2) != Mod5(3), "2 and 3 differ mod 5"); -//------------------------------------------------------------------------------ -// axom::Optional: a device-safe "maybe a value", fully constexpr. -//------------------------------------------------------------------------------ -static_assert(!axom::Optional().has_value(), "default Optional is disengaged"); -static_assert(axom::Optional(42).has_value(), "value-constructed Optional is engaged"); -static_assert(*axom::Optional(42) == 42, "engaged Optional yields its value"); -static_assert(axom::Optional(42).value_or(-1) == 42, "value_or returns the value when engaged"); -static_assert(axom::Optional().value_or(-1) == -1, "value_or returns fallback when empty"); -static_assert(static_cast(axom::Optional(0)), "engaged-with-zero is still engaged"); -static_assert(!static_cast(axom::Optional()), "disengaged converts to false"); -// Trivial copyability allows it to be captured on device -static_assert(std::is_trivially_copyable_v>, - "axom::Optional is trivially copyable (device-capturable)"); - } // anonymous namespace //------------------------------------------------------------------------------ From 8038f6f6b371babb609da188c6aa87e5509453c8 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 1 Jul 2026 16:04:23 -0700 Subject: [PATCH 613/986] slam: Renormalizes CRLF line endings in some files --- src/axom/slam/BivariateSet.hpp | 846 +++--- src/axom/slam/RelationSet.hpp | 656 ++--- src/axom/slam/tests/slam_map_BivariateMap.cpp | 2344 ++++++++--------- 3 files changed, 1923 insertions(+), 1923 deletions(-) diff --git a/src/axom/slam/BivariateSet.hpp b/src/axom/slam/BivariateSet.hpp index a4080ad7ff..6072cb29d4 100644 --- a/src/axom/slam/BivariateSet.hpp +++ b/src/axom/slam/BivariateSet.hpp @@ -1,145 +1,145 @@ -// 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) - -/** - * \file BivariateSet.hpp - * - * \brief Contains the class BivariateSet and NullBivariateSet - * - */ - -#ifndef SLAM_BIVARIATE_SET_H_ -#define SLAM_BIVARIATE_SET_H_ - -#include "axom/slic.hpp" - -#include "axom/slam/Set.hpp" -#include "axom/slam/OrderedSet.hpp" -#include "axom/slam/NullSet.hpp" -#include "axom/slam/RangeSet.hpp" -#include "axom/slam/policies/PolicyTraits.hpp" - -#include -#include -#include - -namespace axom -{ -namespace slam -{ -template -struct BivariateSetIterator; - -/** - * \class BivariateSet - * - * \brief Abstract class that models a set whose elements are indexed by two - * indices. Each element in a BivariateSet is equivalent to an ordered - * pair containing a row and column index, similar to indexing in a matrix. - * - * \detail BivariateSet models a subset of the Cartesian product of its two - * sets. Elements of a BivariateSet can be represented as an ordered - * pair of indices into the two sets. - * - * For BivariateSets that do not model the entire Cartesian product, indices - * can be relative to the element positions in the original sets (in which - * case, we refer to them as a "DenseIndex"), or relative to the number of - * encoded indices, in which case we refer to them as a "SparseIndex". - * If we consider all the elements of a BivariateSet, we refer to this index - * space as the "FlatIndex". \n - * - * For example, a 2 x 4 sparse matrix below: - * \code - * 0 1 2 3 - * _ _ _ _ - * 0 | a b - * 1 | c d - * \endcode - * - * Access the elements using DenseIndex `(i,j)` would be...\n - * `(i = 0, j = 0) = a`\n - * `(i = 0, j = 2) = b`\n - * `(i = 1, j = 1) = c`\n - * `(i = 1, j = 3) = d`\n - * - * Using SparseIndex `(i,k)`...\n - * `(i = 0, k = 0) = a`\n - * `(i = 0, k = 1) = b`\n - * `(i = 1, k = 0) = c`\n - * `(i = 1, k = 1) = d`\n - * - * Using FlatIndex `[idx]`...\n - * `[idx = 0] = a`\n - * `[idx = 1] = b`\n - * `[idx = 2] = c`\n - * `[idx = 3] = d`\n - * - */ - -template , typename Set2 = slam::Set<>> -class BivariateSet -{ -public: - using FirstSetType = Set1; - using SecondSetType = Set2; - - using PositionType = typename FirstSetType::PositionType; - using ElementType = typename FirstSetType::ElementType; - using NullSetType = NullSet; - - using SubsetType = OrderedSet, - policies::RuntimeOffset, - policies::StrideOne, - policies::ArrayViewIndirection>; - - using RangeSetType = RangeSet; - using IteratorType = BivariateSetIterator; - -public: - static constexpr PositionType INVALID_POS = PositionType(-1); - static const NullSetType s_nullSet; - -public: - /** - * \brief Constructor taking pointers to the two sets that defines the range - * of the indices of the BivariateSet. - * - * \param set1 Pointer to the first Set. - * \param set2 Pointer to the second Set. - */ - BivariateSet(const Set1* set1 = policies::EmptySetTraits::emptySet(), - const Set2* set2 = policies::EmptySetTraits::emptySet()) - : m_set1(set1) - , m_set2(set2) - { } - - /** - * \brief Default virtual destructor - * - * \note BivariateSet does not own the two underlying sets - */ - virtual ~BivariateSet() = default; - - /** - * \brief Searches for the SparseIndex of the element given its DenseIndex. - * \detail If the element (i,j) is the kth non-zero in the row, - * then `findElementIndex(i,j)` returns `k`. If `element (i,j)` does - * not exist (such as the case of a zero in a sparse matrix), then - * `INVALID_POS` is returned. - * - * \param pos1 The first set position. - * \param pos2 The second set position. - * \return The DenseIndex of the given element, or INVALID_POS if such - * element is missing from the set. - * \pre 0 <= pos1 <= set1.size() && 0 <= pos2 <= size2.size() - */ - virtual PositionType findElementIndex(PositionType pos1, PositionType pos2) const = 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) + +/** + * \file BivariateSet.hpp + * + * \brief Contains the class BivariateSet and NullBivariateSet + * + */ + +#ifndef SLAM_BIVARIATE_SET_H_ +#define SLAM_BIVARIATE_SET_H_ + +#include "axom/slic.hpp" + +#include "axom/slam/Set.hpp" +#include "axom/slam/OrderedSet.hpp" +#include "axom/slam/NullSet.hpp" +#include "axom/slam/RangeSet.hpp" +#include "axom/slam/policies/PolicyTraits.hpp" + +#include +#include +#include + +namespace axom +{ +namespace slam +{ +template +struct BivariateSetIterator; + +/** + * \class BivariateSet + * + * \brief Abstract class that models a set whose elements are indexed by two + * indices. Each element in a BivariateSet is equivalent to an ordered + * pair containing a row and column index, similar to indexing in a matrix. + * + * \detail BivariateSet models a subset of the Cartesian product of its two + * sets. Elements of a BivariateSet can be represented as an ordered + * pair of indices into the two sets. + * + * For BivariateSets that do not model the entire Cartesian product, indices + * can be relative to the element positions in the original sets (in which + * case, we refer to them as a "DenseIndex"), or relative to the number of + * encoded indices, in which case we refer to them as a "SparseIndex". + * If we consider all the elements of a BivariateSet, we refer to this index + * space as the "FlatIndex". \n + * + * For example, a 2 x 4 sparse matrix below: + * \code + * 0 1 2 3 + * _ _ _ _ + * 0 | a b + * 1 | c d + * \endcode + * + * Access the elements using DenseIndex `(i,j)` would be...\n + * `(i = 0, j = 0) = a`\n + * `(i = 0, j = 2) = b`\n + * `(i = 1, j = 1) = c`\n + * `(i = 1, j = 3) = d`\n + * + * Using SparseIndex `(i,k)`...\n + * `(i = 0, k = 0) = a`\n + * `(i = 0, k = 1) = b`\n + * `(i = 1, k = 0) = c`\n + * `(i = 1, k = 1) = d`\n + * + * Using FlatIndex `[idx]`...\n + * `[idx = 0] = a`\n + * `[idx = 1] = b`\n + * `[idx = 2] = c`\n + * `[idx = 3] = d`\n + * + */ + +template , typename Set2 = slam::Set<>> +class BivariateSet +{ +public: + using FirstSetType = Set1; + using SecondSetType = Set2; + + using PositionType = typename FirstSetType::PositionType; + using ElementType = typename FirstSetType::ElementType; + using NullSetType = NullSet; + + using SubsetType = OrderedSet, + policies::RuntimeOffset, + policies::StrideOne, + policies::ArrayViewIndirection>; + + using RangeSetType = RangeSet; + using IteratorType = BivariateSetIterator; + +public: + static constexpr PositionType INVALID_POS = PositionType(-1); + static const NullSetType s_nullSet; + +public: + /** + * \brief Constructor taking pointers to the two sets that defines the range + * of the indices of the BivariateSet. + * + * \param set1 Pointer to the first Set. + * \param set2 Pointer to the second Set. + */ + BivariateSet(const Set1* set1 = policies::EmptySetTraits::emptySet(), + const Set2* set2 = policies::EmptySetTraits::emptySet()) + : m_set1(set1) + , m_set2(set2) + { } + + /** + * \brief Default virtual destructor + * + * \note BivariateSet does not own the two underlying sets + */ + virtual ~BivariateSet() = default; + + /** + * \brief Searches for the SparseIndex of the element given its DenseIndex. + * \detail If the element (i,j) is the kth non-zero in the row, + * then `findElementIndex(i,j)` returns `k`. If `element (i,j)` does + * not exist (such as the case of a zero in a sparse matrix), then + * `INVALID_POS` is returned. + * + * \param pos1 The first set position. + * \param pos2 The second set position. + * \return The DenseIndex of the given element, or INVALID_POS if such + * element is missing from the set. + * \pre 0 <= pos1 <= set1.size() && 0 <= pos2 <= size2.size() + */ + virtual PositionType findElementIndex(PositionType pos1, PositionType pos2) const = 0; + /*! * \brief Finds the SparseIndex of the element given its DenseIndex. * @@ -148,26 +148,26 @@ class BivariateSet * * \note This is a convenience wrapper around `findElementIndex(...)` that avoids * sentinel checks against `INVALID_POS`. - */ - [[nodiscard]] std::optional findElementIndexOptional(PositionType pos1, - PositionType pos2) const - { - const auto idx = findElementIndex(pos1, pos2); - return idx != INVALID_POS ? std::optional(idx) : std::optional {}; - } - + */ + [[nodiscard]] std::optional findElementIndexOptional(PositionType pos1, + PositionType pos2) const + { + const auto idx = findElementIndex(pos1, pos2); + return idx != INVALID_POS ? std::optional(idx) : std::optional {}; + } + /** * \brief Search for the FlatIndex of the element given its DenseIndex. * * \param pos1 The first set position. - * \param pos2 The second set position. - * - * \return The element's FlatIndex - * \pre 0 <= pos1 <= set1.size() && 0 <= pos2 <= size2.size() - */ - AXOM_HOST_DEVICE virtual PositionType findElementFlatIndex(PositionType pos1, - PositionType pos2) const = 0; - + * \param pos2 The second set position. + * + * \return The element's FlatIndex + * \pre 0 <= pos1 <= set1.size() && 0 <= pos2 <= size2.size() + */ + AXOM_HOST_DEVICE virtual PositionType findElementFlatIndex(PositionType pos1, + PositionType pos2) const = 0; + /*! * \brief Finds the FlatIndex of the element given its DenseIndex. * @@ -176,26 +176,26 @@ class BivariateSet * * \note This is a convenience wrapper around `findElementFlatIndex(...)` that avoids * sentinel checks against `INVALID_POS`. - */ - [[nodiscard]] AXOM_HOST_DEVICE std::optional findElementFlatIndexOptional( - PositionType pos1, - PositionType pos2) const - { - const auto idx = findElementFlatIndex(pos1, pos2); - return idx != INVALID_POS ? std::optional(idx) : std::optional {}; - } - + */ + [[nodiscard]] AXOM_HOST_DEVICE std::optional findElementFlatIndexOptional( + PositionType pos1, + PositionType pos2) const + { + const auto idx = findElementFlatIndex(pos1, pos2); + return idx != INVALID_POS ? std::optional(idx) : std::optional {}; + } + /** * \brief Searches for the first existing element given the row index (first * set position). - * - * \param pos1 The first set position. - * - * \return The found element's FlatIndex. - * \pre 0 <= pos1 <= set1.size() - */ - virtual PositionType findElementFlatIndex(PositionType pos1) const = 0; - + * + * \param pos1 The first set position. + * + * \return The found element's FlatIndex. + * \pre 0 <= pos1 <= set1.size() + */ + virtual PositionType findElementFlatIndex(PositionType pos1) const = 0; + /*! * \brief Finds the FlatIndex of the first existing element in a row. * @@ -204,255 +204,255 @@ class BivariateSet * * \note This is a convenience wrapper around `findElementFlatIndex(pos1)` that avoids * sentinel checks against `INVALID_POS`. - */ - [[nodiscard]] std::optional findElementFlatIndexOptional(PositionType pos1) const - { - const auto idx = findElementFlatIndex(pos1); - return idx != INVALID_POS ? std::optional(idx) : std::optional {}; - } - + */ + [[nodiscard]] std::optional findElementFlatIndexOptional(PositionType pos1) const + { + const auto idx = findElementFlatIndex(pos1); + return idx != INVALID_POS ? std::optional(idx) : std::optional {}; + } + /** * \brief Given the flat index, return the associated from-set index in the * relation pair. * - * \param flatIndex The FlatIndex of the from-set/to-set pair. - * - * \return pos1 The from-set index. - */ - AXOM_HOST_DEVICE virtual PositionType flatToFirstIndex(PositionType flatIndex) const = 0; - - /** - * \brief Given the flat index, return the associated to-set index in the - * relation pair. - * - * \param flatIndex The FlatIndex of the from-set/to-set pair. - * - * \return pos2 The to-set index. - */ - AXOM_HOST_DEVICE virtual PositionType flatToSecondIndex(PositionType flatIndex) const = 0; - - /** - * \brief Finds the range of indices of valid elements in the second set, - * given the index of an element in the first set. - * \param Position of the element in the first set - * - * \return A range set of the positions in the second set - */ - AXOM_HOST_DEVICE virtual RangeSetType elementRangeSet(PositionType pos1) const = 0; - - /// \brief The number of non-zero entries in the BivariateSet. - [[nodiscard]] AXOM_HOST_DEVICE virtual PositionType size() const = 0; - - /** - * \brief Number of elements of the BivariateSet whose first index is \a pos - * - * \pre 0 <= pos1 <= set1.size() - */ - virtual PositionType size(PositionType pos1) const = 0; //size of a row - - /** \brief Size of the first set. */ - [[nodiscard]] AXOM_HOST_DEVICE inline PositionType firstSetSize() const - { - return getSize(m_set1); - } - - /** \brief Size of the second set. */ - AXOM_SUPPRESS_HD_WARN - [[nodiscard]] AXOM_HOST_DEVICE inline PositionType secondSetSize() const - { - return getSize(m_set2); - } - - /** \brief Returns pointer to the first set. */ - const FirstSetType* getFirstSet() const { return m_set1; } - - /** \brief Returns pointer to the second set. */ - const SecondSetType* getSecondSet() const { return m_set2; } - - /** \brief Returns the element at the given FlatIndex \a pos */ - [[nodiscard]] AXOM_HOST_DEVICE virtual ElementType at(PositionType pos) const = 0; - - /** - * \brief A set of elements with the given first set index. - * - * \param s1 The first set index. - * \return An OrderedSet containing the elements - * \pre 0 <= pos1 <= set1.size() - */ - virtual SubsetType getElements(PositionType s1) const = 0; - - /// \brief Return an iterator to the first pair of set elements in the relation. - IteratorType begin() const { return IteratorType(this, 0); } - - /// \brief Return an iterator to one past the last pair of set elements in the relation. - IteratorType end() const { return IteratorType(this, size()); } - - [[nodiscard]] virtual bool isValid(bool verboseOutput = false) const; - -private: - virtual void verifyPosition(PositionType s1, PositionType s2) const = 0; - - AXOM_SUPPRESS_HD_WARN - template - AXOM_HOST_DEVICE typename std::enable_if::value, PositionType>::type - getSize(const SetType* s) const - { - SLIC_ASSERT_MSG(s != nullptr, "nullptr in BivariateSet::getSize()"); - return s->size(); - } - - template - AXOM_HOST_DEVICE typename std::enable_if::value, PositionType>::type - getSize(const SetType* s) const - { - SLIC_ASSERT_MSG(s != nullptr, "nullptr in BivariateSet::getSize()"); - return static_cast(*s).size(); - } - -protected: - const FirstSetType* m_set1; - const SecondSetType* m_set2; -}; - -template -const typename BivariateSet::NullSetType BivariateSet::s_nullSet; - -template -bool BivariateSet::isValid(bool verboseOutput) const -{ - if(m_set1 == nullptr || m_set2 == nullptr) - { - if(verboseOutput) - { - SLIC_INFO("BivariateSet is not valid: " << " Set pointers should not be null."); - } - return false; - } - return m_set1->isValid(verboseOutput) && m_set2->isValid(verboseOutput); -} - -/*! - * \class BivariateSetIterator - * - * \brief Implements a forward iterator concept on a BivariateSet type. - */ -template -struct BivariateSetIterator - : public IteratorBase, typename BivariateSetType::PositionType> -{ -public: - using IndexType = typename BivariateSetType::PositionType; - using BaseType = IteratorBase, IndexType>; - using difference_type = IndexType; - using value_type = std::pair; - using reference = value_type&; - using pointer = value_type*; - using iterator_category = std::forward_iterator_tag; - - AXOM_HOST_DEVICE BivariateSetIterator(const BivariateSetType* bset, IndexType flatPos = 0) - : BaseType(flatPos) - , m_bset(bset) - { } - - std::pair operator*() const - { - // Going from flat index to second index is always free for a StaticRelation. - return {firstIndex(), secondIndex()}; - } - - /// \brief Return the first set index pointed to by this iterator. - IndexType firstIndex() const { return m_bset->flatToFirstIndex(flatIndex()); } - - /// \brief Return the second set index pointed to by this iterator. - IndexType secondIndex() const { return m_bset->flatToSecondIndex(flatIndex()); } - - /// \brief Return the flat iteration index of this iterator. - AXOM_HOST_DEVICE IndexType flatIndex() const { return this->m_pos; } - -protected: - AXOM_HOST_DEVICE void advance(IndexType n) { this->m_pos += n; } - -private: - const BivariateSetType* m_bset; -}; - -/** - * \class NullBivariateSet - * - * \brief A Null BivariateSet class. Same as the NullSet for Set class. - */ -template , typename SetType2 = slam::Set<>> -class NullBivariateSet : public BivariateSet -{ -public: - using FirstSetType = SetType1; - using SecondSetType = SetType2; - using BSet = BivariateSet; - using PositionType = typename BSet::PositionType; - using ElementType = typename BSet::ElementType; - using SubsetType = typename BSet::SubsetType; - using RangeSetType = typename BSet::RangeSetType; - -public: - NullBivariateSet() = default; - - PositionType findElementIndex(PositionType pos1, PositionType pos2 = 0) const override - { - verifyPosition(pos1, pos2); - return PositionType(); - } - - AXOM_SUPPRESS_HD_WARN - AXOM_HOST_DEVICE PositionType findElementFlatIndex(PositionType s1, PositionType s2) const override - { - verifyPosition(s1, s2); - return PositionType(); - } - - PositionType findElementFlatIndex(PositionType s1) const override - { - return findElementFlatIndex(s1, 0); - } - - AXOM_HOST_DEVICE PositionType flatToFirstIndex(PositionType) const override - { - return PositionType(); - } - - AXOM_HOST_DEVICE PositionType flatToSecondIndex(PositionType) const override - { - return PositionType(); - } - - AXOM_SUPPRESS_HD_WARN - AXOM_HOST_DEVICE RangeSetType elementRangeSet(PositionType) const override - { - return RangeSetType(); - } - - AXOM_HOST_DEVICE ElementType at(PositionType) const override { return PositionType(); } - - AXOM_HOST_DEVICE PositionType size() const override { return PositionType(); } - - PositionType size(PositionType) const override { return PositionType(); } - - SubsetType getElements(PositionType) const override - { - using OrderedSetBuilder = typename SubsetType::SetBuilder; - return OrderedSetBuilder(); - } - -private: - void verifyPosition(PositionType AXOM_DEBUG_PARAM(pos1), - PositionType AXOM_DEBUG_PARAM(pos2)) const override - { - SLIC_ASSERT_MSG(false, - "Subscripting on NullSet is never valid." - << "\n\tAttempted to access item at index " << pos1 << "," << pos2 << "."); - } -}; - -} // end namespace slam -} // end namespace axom - -#endif // SLAM_BIVARIATE_SET_H_ + * \param flatIndex The FlatIndex of the from-set/to-set pair. + * + * \return pos1 The from-set index. + */ + AXOM_HOST_DEVICE virtual PositionType flatToFirstIndex(PositionType flatIndex) const = 0; + + /** + * \brief Given the flat index, return the associated to-set index in the + * relation pair. + * + * \param flatIndex The FlatIndex of the from-set/to-set pair. + * + * \return pos2 The to-set index. + */ + AXOM_HOST_DEVICE virtual PositionType flatToSecondIndex(PositionType flatIndex) const = 0; + + /** + * \brief Finds the range of indices of valid elements in the second set, + * given the index of an element in the first set. + * \param Position of the element in the first set + * + * \return A range set of the positions in the second set + */ + AXOM_HOST_DEVICE virtual RangeSetType elementRangeSet(PositionType pos1) const = 0; + + /// \brief The number of non-zero entries in the BivariateSet. + [[nodiscard]] AXOM_HOST_DEVICE virtual PositionType size() const = 0; + + /** + * \brief Number of elements of the BivariateSet whose first index is \a pos + * + * \pre 0 <= pos1 <= set1.size() + */ + virtual PositionType size(PositionType pos1) const = 0; //size of a row + + /** \brief Size of the first set. */ + [[nodiscard]] AXOM_HOST_DEVICE inline PositionType firstSetSize() const + { + return getSize(m_set1); + } + + /** \brief Size of the second set. */ + AXOM_SUPPRESS_HD_WARN + [[nodiscard]] AXOM_HOST_DEVICE inline PositionType secondSetSize() const + { + return getSize(m_set2); + } + + /** \brief Returns pointer to the first set. */ + const FirstSetType* getFirstSet() const { return m_set1; } + + /** \brief Returns pointer to the second set. */ + const SecondSetType* getSecondSet() const { return m_set2; } + + /** \brief Returns the element at the given FlatIndex \a pos */ + [[nodiscard]] AXOM_HOST_DEVICE virtual ElementType at(PositionType pos) const = 0; + + /** + * \brief A set of elements with the given first set index. + * + * \param s1 The first set index. + * \return An OrderedSet containing the elements + * \pre 0 <= pos1 <= set1.size() + */ + virtual SubsetType getElements(PositionType s1) const = 0; + + /// \brief Return an iterator to the first pair of set elements in the relation. + IteratorType begin() const { return IteratorType(this, 0); } + + /// \brief Return an iterator to one past the last pair of set elements in the relation. + IteratorType end() const { return IteratorType(this, size()); } + + [[nodiscard]] virtual bool isValid(bool verboseOutput = false) const; + +private: + virtual void verifyPosition(PositionType s1, PositionType s2) const = 0; + + AXOM_SUPPRESS_HD_WARN + template + AXOM_HOST_DEVICE typename std::enable_if::value, PositionType>::type + getSize(const SetType* s) const + { + SLIC_ASSERT_MSG(s != nullptr, "nullptr in BivariateSet::getSize()"); + return s->size(); + } + + template + AXOM_HOST_DEVICE typename std::enable_if::value, PositionType>::type + getSize(const SetType* s) const + { + SLIC_ASSERT_MSG(s != nullptr, "nullptr in BivariateSet::getSize()"); + return static_cast(*s).size(); + } + +protected: + const FirstSetType* m_set1; + const SecondSetType* m_set2; +}; + +template +const typename BivariateSet::NullSetType BivariateSet::s_nullSet; + +template +bool BivariateSet::isValid(bool verboseOutput) const +{ + if(m_set1 == nullptr || m_set2 == nullptr) + { + if(verboseOutput) + { + SLIC_INFO("BivariateSet is not valid: " << " Set pointers should not be null."); + } + return false; + } + return m_set1->isValid(verboseOutput) && m_set2->isValid(verboseOutput); +} + +/*! + * \class BivariateSetIterator + * + * \brief Implements a forward iterator concept on a BivariateSet type. + */ +template +struct BivariateSetIterator + : public IteratorBase, typename BivariateSetType::PositionType> +{ +public: + using IndexType = typename BivariateSetType::PositionType; + using BaseType = IteratorBase, IndexType>; + using difference_type = IndexType; + using value_type = std::pair; + using reference = value_type&; + using pointer = value_type*; + using iterator_category = std::forward_iterator_tag; + + AXOM_HOST_DEVICE BivariateSetIterator(const BivariateSetType* bset, IndexType flatPos = 0) + : BaseType(flatPos) + , m_bset(bset) + { } + + std::pair operator*() const + { + // Going from flat index to second index is always free for a StaticRelation. + return {firstIndex(), secondIndex()}; + } + + /// \brief Return the first set index pointed to by this iterator. + IndexType firstIndex() const { return m_bset->flatToFirstIndex(flatIndex()); } + + /// \brief Return the second set index pointed to by this iterator. + IndexType secondIndex() const { return m_bset->flatToSecondIndex(flatIndex()); } + + /// \brief Return the flat iteration index of this iterator. + AXOM_HOST_DEVICE IndexType flatIndex() const { return this->m_pos; } + +protected: + AXOM_HOST_DEVICE void advance(IndexType n) { this->m_pos += n; } + +private: + const BivariateSetType* m_bset; +}; + +/** + * \class NullBivariateSet + * + * \brief A Null BivariateSet class. Same as the NullSet for Set class. + */ +template , typename SetType2 = slam::Set<>> +class NullBivariateSet : public BivariateSet +{ +public: + using FirstSetType = SetType1; + using SecondSetType = SetType2; + using BSet = BivariateSet; + using PositionType = typename BSet::PositionType; + using ElementType = typename BSet::ElementType; + using SubsetType = typename BSet::SubsetType; + using RangeSetType = typename BSet::RangeSetType; + +public: + NullBivariateSet() = default; + + PositionType findElementIndex(PositionType pos1, PositionType pos2 = 0) const override + { + verifyPosition(pos1, pos2); + return PositionType(); + } + + AXOM_SUPPRESS_HD_WARN + AXOM_HOST_DEVICE PositionType findElementFlatIndex(PositionType s1, PositionType s2) const override + { + verifyPosition(s1, s2); + return PositionType(); + } + + PositionType findElementFlatIndex(PositionType s1) const override + { + return findElementFlatIndex(s1, 0); + } + + AXOM_HOST_DEVICE PositionType flatToFirstIndex(PositionType) const override + { + return PositionType(); + } + + AXOM_HOST_DEVICE PositionType flatToSecondIndex(PositionType) const override + { + return PositionType(); + } + + AXOM_SUPPRESS_HD_WARN + AXOM_HOST_DEVICE RangeSetType elementRangeSet(PositionType) const override + { + return RangeSetType(); + } + + AXOM_HOST_DEVICE ElementType at(PositionType) const override { return PositionType(); } + + AXOM_HOST_DEVICE PositionType size() const override { return PositionType(); } + + PositionType size(PositionType) const override { return PositionType(); } + + SubsetType getElements(PositionType) const override + { + using OrderedSetBuilder = typename SubsetType::SetBuilder; + return OrderedSetBuilder(); + } + +private: + void verifyPosition(PositionType AXOM_DEBUG_PARAM(pos1), + PositionType AXOM_DEBUG_PARAM(pos2)) const override + { + SLIC_ASSERT_MSG(false, + "Subscripting on NullSet is never valid." + << "\n\tAttempted to access item at index " << pos1 << "," << pos2 << "."); + } +}; + +} // end namespace slam +} // end namespace axom + +#endif // SLAM_BIVARIATE_SET_H_ diff --git a/src/axom/slam/RelationSet.hpp b/src/axom/slam/RelationSet.hpp index 839ea3570e..de45335466 100644 --- a/src/axom/slam/RelationSet.hpp +++ b/src/axom/slam/RelationSet.hpp @@ -1,340 +1,340 @@ -// 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 SLAM_MAPPED_RELATION_SET_H_ -#define SLAM_MAPPED_RELATION_SET_H_ - -#include "axom/slam/RangeSet.hpp" -#include "axom/slam/BivariateSet.hpp" -#include "axom/slam/policies/BivariateSetInterfacePolicies.hpp" - -#include - -namespace axom -{ -namespace slam -{ -/** - * \class RelationSet - * - * \brief Models a Set whose elements are derived from a relation, one element - * per fromSet and toSet pair in the relation. - * - * RelationSet models a subset of the Cartesian product of two sets. Users - * should refer to the BivariateSet documentation for descriptions of the - * different indexing names (SparseIndex, DenseIndex, FlatIndex). - * - * \tparam Relation The Relation type that this set uses. - * - * \see BivariateSet - */ - -template -class RelationSet final : public policies::BivariateSetInterface -{ -public: - using FirstSetType = SetType1; - using SecondSetType = SetType2; - - using RelationType = Relation; - -private: - using BaseType = policies::BivariateSetInterface; - using RangeSetType = typename BaseType::RangeSetType; - using BaseSubsetType = typename BaseType::SubsetType; - -public: - using PositionType = typename RelationType::SetPosition; - using ElementType = typename RelationType::SetElement; - - using RelationSubset = typename RelationType::RelationSubset; - using SubsetType = - std::conditional_t::value, RelationSubset, BaseSubsetType>; - - using BaseType::INVALID_POS; - - using IteratorType = BivariateSetIterator; - -public: - using ConcreteSet = RelationSet; - using VirtualSet = RelationSet; - - using OtherSet = - std::conditional_t::value, ConcreteSet, VirtualSet>; - - RelationSet(const OtherSet& other) - : BaseType(other.getFirstSet(), other.getSecondSet()) - , m_relation(other.getRelation()) - { } - -public: - RelationSet() = default; - - /** - * \brief Constructor taking in the relation this BivariateSet is based on. - * \pre relation pointer must not be a null pointer - */ - RelationSet(RelationType* relation) - : BaseType(relation ? relation->fromSet() : policies::EmptySetTraits::emptySet(), - relation ? relation->toSet() : policies::EmptySetTraits::emptySet()) - , m_relation(relation) - { - SLIC_ASSERT(relation != nullptr); - } - - /** - * \brief Searches for the SparseIndex of the element given its DenseIndex. - * \detail If the element (i,j) is the kth non-zero in the row, - * then `findElementIndex(i,j)` returns `k`. If `element(i,j)` does - * not exist (such as the case of a zero in a sparse matrix), then - * `INVALID_POS` is returned. - * - * \warning This function can be slow, since a linear search is performed on - * the row each time. - * - * \param pos1 The first set position. - * \param pos2 The second set position. - * - * \return The DenseIndex of the given element, or INVALID_POS if such - * element is missing from the set. - * \pre 0 <= pos1 <= set1.size() && 0 <= pos2 <= size2.size() - */ - - PositionType findElementIndex(PositionType pos1, PositionType pos2) const - { - RelationSubset ls = (*m_relation)[pos1]; - for(PositionType i = 0; i < ls.size(); i++) - { - if(ls[i] == pos2) - { - return i; - } - } - return BaseType::INVALID_POS; - } - +// 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 SLAM_MAPPED_RELATION_SET_H_ +#define SLAM_MAPPED_RELATION_SET_H_ + +#include "axom/slam/RangeSet.hpp" +#include "axom/slam/BivariateSet.hpp" +#include "axom/slam/policies/BivariateSetInterfacePolicies.hpp" + +#include + +namespace axom +{ +namespace slam +{ +/** + * \class RelationSet + * + * \brief Models a Set whose elements are derived from a relation, one element + * per fromSet and toSet pair in the relation. + * + * RelationSet models a subset of the Cartesian product of two sets. Users + * should refer to the BivariateSet documentation for descriptions of the + * different indexing names (SparseIndex, DenseIndex, FlatIndex). + * + * \tparam Relation The Relation type that this set uses. + * + * \see BivariateSet + */ + +template +class RelationSet final : public policies::BivariateSetInterface +{ +public: + using FirstSetType = SetType1; + using SecondSetType = SetType2; + + using RelationType = Relation; + +private: + using BaseType = policies::BivariateSetInterface; + using RangeSetType = typename BaseType::RangeSetType; + using BaseSubsetType = typename BaseType::SubsetType; + +public: + using PositionType = typename RelationType::SetPosition; + using ElementType = typename RelationType::SetElement; + + using RelationSubset = typename RelationType::RelationSubset; + using SubsetType = + std::conditional_t::value, RelationSubset, BaseSubsetType>; + + using BaseType::INVALID_POS; + + using IteratorType = BivariateSetIterator; + +public: + using ConcreteSet = RelationSet; + using VirtualSet = RelationSet; + + using OtherSet = + std::conditional_t::value, ConcreteSet, VirtualSet>; + + RelationSet(const OtherSet& other) + : BaseType(other.getFirstSet(), other.getSecondSet()) + , m_relation(other.getRelation()) + { } + +public: + RelationSet() = default; + + /** + * \brief Constructor taking in the relation this BivariateSet is based on. + * \pre relation pointer must not be a null pointer + */ + RelationSet(RelationType* relation) + : BaseType(relation ? relation->fromSet() : policies::EmptySetTraits::emptySet(), + relation ? relation->toSet() : policies::EmptySetTraits::emptySet()) + , m_relation(relation) + { + SLIC_ASSERT(relation != nullptr); + } + + /** + * \brief Searches for the SparseIndex of the element given its DenseIndex. + * \detail If the element (i,j) is the kth non-zero in the row, + * then `findElementIndex(i,j)` returns `k`. If `element(i,j)` does + * not exist (such as the case of a zero in a sparse matrix), then + * `INVALID_POS` is returned. + * + * \warning This function can be slow, since a linear search is performed on + * the row each time. + * + * \param pos1 The first set position. + * \param pos2 The second set position. + * + * \return The DenseIndex of the given element, or INVALID_POS if such + * element is missing from the set. + * \pre 0 <= pos1 <= set1.size() && 0 <= pos2 <= size2.size() + */ + + PositionType findElementIndex(PositionType pos1, PositionType pos2) const + { + RelationSubset ls = (*m_relation)[pos1]; + for(PositionType i = 0; i < ls.size(); i++) + { + if(ls[i] == pos2) + { + return i; + } + } + return BaseType::INVALID_POS; + } + /*! * \brief Optional-returning wrapper for `findElementIndex`. * * \return An engaged `std::optional` with the SparseIndex if the element exists, else empty. - */ - [[nodiscard]] std::optional findElementIndexOptional(PositionType pos1, - PositionType pos2) const - { - const auto idx = findElementIndex(pos1, pos2); - return idx != BaseType::INVALID_POS ? std::optional(idx) - : std::optional {}; - } - - /** - * \brief Search for the FlatIndex of the element given its DenseIndex. - * \warning This function can be slow, since a linear search is performed on the row each time. - * - * \param pos1 The first set position. - * \param pos2 The second set position. - * - * \return The element's FlatIndex - * \pre 0 <= pos1 <= set1.size() && 0 <= pos2 <= size2.size() - */ - AXOM_HOST_DEVICE PositionType findElementFlatIndex(PositionType s1, PositionType s2) const - { - RelationSubset ls = (*m_relation)[s1]; - for(PositionType i = 0; i < ls.size(); i++) - { - if(ls[i] == s2) - { - return ls.offset() + i; - } - } - return BaseType::INVALID_POS; - } - + */ + [[nodiscard]] std::optional findElementIndexOptional(PositionType pos1, + PositionType pos2) const + { + const auto idx = findElementIndex(pos1, pos2); + return idx != BaseType::INVALID_POS ? std::optional(idx) + : std::optional {}; + } + + /** + * \brief Search for the FlatIndex of the element given its DenseIndex. + * \warning This function can be slow, since a linear search is performed on the row each time. + * + * \param pos1 The first set position. + * \param pos2 The second set position. + * + * \return The element's FlatIndex + * \pre 0 <= pos1 <= set1.size() && 0 <= pos2 <= size2.size() + */ + AXOM_HOST_DEVICE PositionType findElementFlatIndex(PositionType s1, PositionType s2) const + { + RelationSubset ls = (*m_relation)[s1]; + for(PositionType i = 0; i < ls.size(); i++) + { + if(ls[i] == s2) + { + return ls.offset() + i; + } + } + return BaseType::INVALID_POS; + } + /*! * \brief Optional-returning wrapper for `findElementFlatIndex(s1, s2)`. * * \return An engaged `std::optional` with the FlatIndex if the element exists, else empty. - */ - [[nodiscard]] AXOM_HOST_DEVICE std::optional findElementFlatIndexOptional( - PositionType s1, - PositionType s2) const - { - const auto idx = findElementFlatIndex(s1, s2); - return idx != BaseType::INVALID_POS ? std::optional(idx) - : std::optional {}; - } - - /** - * \brief Given the from-set index pos1, return the FlatIndex of the first - * existing to-set element in the relation pair, or `INVALID_POS` if - * this row contains no elements. - * - * \param pos1 Index into the from-set. - * \param pos2 Index into the to-set. - * - * \return The FlatIndex of the first existing to-set element. - */ - PositionType findElementFlatIndex(PositionType pos1) const - { - RelationSubset ls = (*m_relation)[pos1]; - - if(ls.size() > 0) - { - return ls.offset(); - } - - return BaseType::INVALID_POS; - } - + */ + [[nodiscard]] AXOM_HOST_DEVICE std::optional findElementFlatIndexOptional( + PositionType s1, + PositionType s2) const + { + const auto idx = findElementFlatIndex(s1, s2); + return idx != BaseType::INVALID_POS ? std::optional(idx) + : std::optional {}; + } + + /** + * \brief Given the from-set index pos1, return the FlatIndex of the first + * existing to-set element in the relation pair, or `INVALID_POS` if + * this row contains no elements. + * + * \param pos1 Index into the from-set. + * \param pos2 Index into the to-set. + * + * \return The FlatIndex of the first existing to-set element. + */ + PositionType findElementFlatIndex(PositionType pos1) const + { + RelationSubset ls = (*m_relation)[pos1]; + + if(ls.size() > 0) + { + return ls.offset(); + } + + return BaseType::INVALID_POS; + } + /*! * \brief Optional-returning wrapper for `findElementFlatIndex(pos1)`. * * \return An engaged `std::optional` with the FlatIndex if the row contains any elements, else empty. - */ - [[nodiscard]] std::optional findElementFlatIndexOptional(PositionType pos1) const - { - const auto idx = findElementFlatIndex(pos1); - return idx != BaseType::INVALID_POS ? std::optional(idx) - : std::optional {}; - } - - /** - * \brief Given the flat index, return the associated to-set index in the relation pair. - * - * \param flatIndex The FlatIndex of the from-set/to-set pair. - * - * \return pos2 The to-set index. - */ - AXOM_SUPPRESS_HD_WARN - AXOM_HOST_DEVICE PositionType flatToSecondIndex(PositionType flatIndex) const - { - if(flatIndex < 0 || flatIndex > size()) - { - SLIC_ASSERT("Flat index out of bounds of the relation set."); - } - return m_relation->relationData()[flatIndex]; - } - - /** - * \brief Given the flat index, return the associated from-set index in the relation pair. - * - * \param flatIndex The FlatIndex of the from-set/to-set pair. - * - * \return pos1 The from-set index. - */ - AXOM_HOST_DEVICE PositionType flatToFirstIndex(PositionType flatIndex) const - { - if(flatIndex < 0 || flatIndex > size()) - { - SLIC_ASSERT("Flat index out of bounds of the relation set."); - } - return m_relation->firstIndex(flatIndex); - } - - AXOM_HOST_DEVICE RangeSetType elementRangeSet(PositionType pos1) const - { - return - typename RangeSetType::SetBuilder().size(m_relation->size(pos1)).offset(m_relation->offset(pos1)); - } - - /** - * \brief A set of elements with the given first set index. - * - * \param s1 The first set index. - * \return An OrderedSet containing the elements in the row. - * \pre 0 <= pos1 <= set1.size() - */ - SubsetType getElements(PositionType s1) const { return (*m_relation)[s1]; } - - AXOM_SUPPRESS_HD_WARN - [[nodiscard]] AXOM_HOST_DEVICE ElementType at(PositionType pos) const - { -#ifndef AXOM_DEVICE_CODE - RelationSet::verifyPosition(pos); -#endif - return m_relation->relationData()[pos]; - } - - /** \brief Returns the relation pointer */ - RelationType* getRelation() const { return m_relation; } - - RelationType* getRelation() { return m_relation; } - - /** \brief Return the size of the relation */ - PositionType totalSize() const { return PositionType(m_relation->relationData().size()); } - - /** - * \brief Return the size of a row, which is the number of to-set - * elements associated with the given from-set index. - * - * \param pos The from-set position. - */ - PositionType size(PositionType pos) const { return m_relation->size(pos); } - - /// \brief Return an iterator to the first pair of set elements in the relation. - IteratorType begin() const { return IteratorType(this, 0); } - - /// \brief Return an iterator to one past the last pair of set elements in the relation. - IteratorType end() const { return IteratorType(this, totalSize()); } - - [[nodiscard]] bool isValid(bool verboseOutput = false) const - { - if(m_relation == nullptr) - { - if(verboseOutput) - { - std::cout << "\n*** RelationSet is not valid:\n" - << "\t* Relation pointer should not be null.\n" - << std::endl; - } - return false; - } - return m_relation->isValid(verboseOutput); - } - -public: - //hiding size() from the Set base class, replaced with totalSize(). - //but still implemented due to the function being virtual - //(and can be called from base ptr) - // KW -- made this public to use from BivariateMap - AXOM_SUPPRESS_HD_WARN - [[nodiscard]] AXOM_HOST_DEVICE PositionType size() const - { - return PositionType(m_relation->relationData().size()); - } - -private: - //range check only - [[nodiscard]] bool isValidIndex(PositionType s1, PositionType s2) const - { - return s1 >= 0 && s1 < m_relation->fromSet()->size() && s2 >= 0 && s2 < m_relation->size(s1); - } - - void verifyPosition(PositionType AXOM_DEBUG_PARAM(sPos)) const - { - SLIC_ASSERT_MSG(sPos >= 0 && sPos < size(), - "SLAM::RelationSet -- requested out-of-range element at position " - << sPos << ", but set only has " << size() << " elements."); - } - - void verifyPosition(PositionType AXOM_DEBUG_PARAM(s1), PositionType AXOM_DEBUG_PARAM(s2)) const - { - SLIC_ASSERT_MSG(isValidIndex(s1, s2), - "SLAM::RelationSet -- requested out-of-range element at position (" - << s1 << "," << s2 << "), but set only has " << this->firstSetSize() << "x" - << this->secondSetSize() << " elements."); - } - -private: - RelationType* m_relation; //the relation that this set is based off of -}; - -} // end namespace slam -} // end namespace axom - -#endif // SLAM_MAPPED_RELATION_SET_H_ + */ + [[nodiscard]] std::optional findElementFlatIndexOptional(PositionType pos1) const + { + const auto idx = findElementFlatIndex(pos1); + return idx != BaseType::INVALID_POS ? std::optional(idx) + : std::optional {}; + } + + /** + * \brief Given the flat index, return the associated to-set index in the relation pair. + * + * \param flatIndex The FlatIndex of the from-set/to-set pair. + * + * \return pos2 The to-set index. + */ + AXOM_SUPPRESS_HD_WARN + AXOM_HOST_DEVICE PositionType flatToSecondIndex(PositionType flatIndex) const + { + if(flatIndex < 0 || flatIndex > size()) + { + SLIC_ASSERT("Flat index out of bounds of the relation set."); + } + return m_relation->relationData()[flatIndex]; + } + + /** + * \brief Given the flat index, return the associated from-set index in the relation pair. + * + * \param flatIndex The FlatIndex of the from-set/to-set pair. + * + * \return pos1 The from-set index. + */ + AXOM_HOST_DEVICE PositionType flatToFirstIndex(PositionType flatIndex) const + { + if(flatIndex < 0 || flatIndex > size()) + { + SLIC_ASSERT("Flat index out of bounds of the relation set."); + } + return m_relation->firstIndex(flatIndex); + } + + AXOM_HOST_DEVICE RangeSetType elementRangeSet(PositionType pos1) const + { + return + typename RangeSetType::SetBuilder().size(m_relation->size(pos1)).offset(m_relation->offset(pos1)); + } + + /** + * \brief A set of elements with the given first set index. + * + * \param s1 The first set index. + * \return An OrderedSet containing the elements in the row. + * \pre 0 <= pos1 <= set1.size() + */ + SubsetType getElements(PositionType s1) const { return (*m_relation)[s1]; } + + AXOM_SUPPRESS_HD_WARN + [[nodiscard]] AXOM_HOST_DEVICE ElementType at(PositionType pos) const + { +#ifndef AXOM_DEVICE_CODE + RelationSet::verifyPosition(pos); +#endif + return m_relation->relationData()[pos]; + } + + /** \brief Returns the relation pointer */ + RelationType* getRelation() const { return m_relation; } + + RelationType* getRelation() { return m_relation; } + + /** \brief Return the size of the relation */ + PositionType totalSize() const { return PositionType(m_relation->relationData().size()); } + + /** + * \brief Return the size of a row, which is the number of to-set + * elements associated with the given from-set index. + * + * \param pos The from-set position. + */ + PositionType size(PositionType pos) const { return m_relation->size(pos); } + + /// \brief Return an iterator to the first pair of set elements in the relation. + IteratorType begin() const { return IteratorType(this, 0); } + + /// \brief Return an iterator to one past the last pair of set elements in the relation. + IteratorType end() const { return IteratorType(this, totalSize()); } + + [[nodiscard]] bool isValid(bool verboseOutput = false) const + { + if(m_relation == nullptr) + { + if(verboseOutput) + { + std::cout << "\n*** RelationSet is not valid:\n" + << "\t* Relation pointer should not be null.\n" + << std::endl; + } + return false; + } + return m_relation->isValid(verboseOutput); + } + +public: + //hiding size() from the Set base class, replaced with totalSize(). + //but still implemented due to the function being virtual + //(and can be called from base ptr) + // KW -- made this public to use from BivariateMap + AXOM_SUPPRESS_HD_WARN + [[nodiscard]] AXOM_HOST_DEVICE PositionType size() const + { + return PositionType(m_relation->relationData().size()); + } + +private: + //range check only + [[nodiscard]] bool isValidIndex(PositionType s1, PositionType s2) const + { + return s1 >= 0 && s1 < m_relation->fromSet()->size() && s2 >= 0 && s2 < m_relation->size(s1); + } + + void verifyPosition(PositionType AXOM_DEBUG_PARAM(sPos)) const + { + SLIC_ASSERT_MSG(sPos >= 0 && sPos < size(), + "SLAM::RelationSet -- requested out-of-range element at position " + << sPos << ", but set only has " << size() << " elements."); + } + + void verifyPosition(PositionType AXOM_DEBUG_PARAM(s1), PositionType AXOM_DEBUG_PARAM(s2)) const + { + SLIC_ASSERT_MSG(isValidIndex(s1, s2), + "SLAM::RelationSet -- requested out-of-range element at position (" + << s1 << "," << s2 << "), but set only has " << this->firstSetSize() << "x" + << this->secondSetSize() << " elements."); + } + +private: + RelationType* m_relation; //the relation that this set is based off of +}; + +} // end namespace slam +} // end namespace axom + +#endif // SLAM_MAPPED_RELATION_SET_H_ diff --git a/src/axom/slam/tests/slam_map_BivariateMap.cpp b/src/axom/slam/tests/slam_map_BivariateMap.cpp index df7ef4030b..dd5f298aca 100644 --- a/src/axom/slam/tests/slam_map_BivariateMap.cpp +++ b/src/axom/slam/tests/slam_map_BivariateMap.cpp @@ -1,1172 +1,1172 @@ -// 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) - -/* - * \file slam_map_BivariateMap.cpp - * - * \brief Unit tests for Slam's Bivariate Map - */ - -#include -#include "gtest/gtest.h" - -#include "axom/core/execution/runtime_policy.hpp" -#include "axom/slic.hpp" -#include "axom/slam.hpp" - -namespace -{ -namespace slam = axom::slam; -namespace policies = axom::slam::policies; -namespace traits = axom::slam::traits; - -using SetPosition = slam::DefaultPositionType; -using SetElement = slam::DefaultElementType; -using SetType = slam::RangeSet; - -using StrideOneType = policies::StrideOne; - -template -using CompileTimeStrideType = policies::CompileTimeStride; - -using RuntimeStrideType = policies::RuntimeStride; - -template -using STLIndirection = policies::STLVectorIndirection; -using VariableCardinality = policies::VariableCardinality>; - -using RelationType = - slam::StaticRelation, SetType, SetType>; - -using BivariateSetType = slam::BivariateSet; -using ProductSetType = slam::ProductSet; -using RelationSetType = slam::RelationSet; - -template -using BivariateMapType = slam::BivariateMap; - -constexpr SetPosition MAX_SET_SIZE1 = 10; -constexpr SetPosition MAX_SET_SIZE2 = 15; - -constexpr double multFac3 = 0000.1; -constexpr double multFac1 = 1000.0; -constexpr double multFac2 = 0010.0; - -} // end anonymous namespace - -TEST(slam_bivariate_map, construct_empty_map) -{ - slam::BivariateMap m; - - EXPECT_TRUE(m.isValid(true)); - EXPECT_EQ(m.totalSize(), 0); - EXPECT_EQ(m.firstSetSize(), 0); - EXPECT_EQ(m.secondSetSize(), 0); -} - -template -AXOM_HOST_DEVICE inline T getVal(SetPosition idx1, SetPosition idx2, SetPosition idx3 = 0) -{ - return static_cast(idx1 * multFac1 + idx2 * multFac2 + idx3 * multFac3); -} - -template -void constructAndTestCartesianMap(int stride) -{ - SLIC_INFO("Testing BivariateMap on ProductSet with stride " << stride); - - SLIC_INFO("Creating set"); - using BMapType = BivariateMapType; - using SubMapType = typename BMapType::SubMapType; - - SetType s1(MAX_SET_SIZE1); - SetType s2(MAX_SET_SIZE2); - ProductSetType s(&s1, &s2); - - EXPECT_EQ(s.size(), MAX_SET_SIZE1 * MAX_SET_SIZE2); - EXPECT_TRUE(s.isValid()); - - SLIC_INFO("Creating map on the set "); - - BMapType m(&s, static_cast(0), stride); - - EXPECT_TRUE(m.isValid()); - EXPECT_EQ(s.size(), m.totalSize()); - EXPECT_EQ(m.stride(), stride); - - SLIC_INFO("Setting the elements in the map."); - - for(auto idx1 = 0; idx1 < m.firstSetSize(); ++idx1) - { - for(auto idx2 = 0; idx2 < m.secondSetSize(); ++idx2) - { - for(auto i = 0; i < stride; i++) - { - T* valPtr = m.findValue(idx1, idx2, i); - EXPECT_NE(valPtr, nullptr); - *valPtr = getVal(idx1, idx2, i); - } - } - } - - SLIC_INFO("Checking the elements with findValue()."); - for(auto idx1 = 0; idx1 < m.firstSetSize(); ++idx1) - { - for(auto idx2 = 0; idx2 < m.secondSetSize(); ++idx2) - { - for(auto i = 0; i < stride; i++) - { - T* ptr = m.findValue(idx1, idx2, i); - EXPECT_NE(ptr, nullptr); - EXPECT_EQ(*ptr, getVal(idx1, idx2, i)); - } - } - } - - SLIC_INFO("Checking the elements with SubMap."); - for(auto idx1 = 0; idx1 < m.firstSetSize(); ++idx1) - { - SubMapType sm = m(idx1); - for(auto idx2 = 0; idx2 < sm.size(); ++idx2) - { - for(auto i = 0; i < stride; i++) - { - T v = sm.value(idx2, i); - EXPECT_EQ(v, getVal(idx1, idx2, i)); - EXPECT_EQ(sm.index(idx2), idx2); - } - } - } - - EXPECT_TRUE(m.isValid()); -} - -TEST(slam_bivariate_map, construct_int_map) -{ - using BSet = BivariateSetType; - using IndPol = STLIndirection; - - constructAndTestCartesianMap(1); - constructAndTestCartesianMap(2); - constructAndTestCartesianMap(3); - - constructAndTestCartesianMap(1); - - constructAndTestCartesianMap>(1); - constructAndTestCartesianMap>(2); - constructAndTestCartesianMap>(3); -} - -TEST(slam_bivariate_map, construct_double_map) -{ - using BSet = BivariateSetType; - using IndPol = STLIndirection; - - constructAndTestCartesianMap(1); - - constructAndTestCartesianMap>(1); - constructAndTestCartesianMap>(2); - constructAndTestCartesianMap>(3); - - constructAndTestCartesianMap(1); - constructAndTestCartesianMap(2); - constructAndTestCartesianMap(3); -} - -template -void constructAndTestRelationSetMap(int stride) -{ - SLIC_INFO("Testing BivariateMap on RelationSet with stride " << stride); - - SLIC_INFO("Creating set"); - using MapType = BivariateMapType; - using SubMapType = typename MapType::SubMapType; - - SetType s1(MAX_SET_SIZE1); - SetType s2(MAX_SET_SIZE2); - - RelationType rel(&s1, &s2); - - std::vector begin_vec(MAX_SET_SIZE1 + 1, 0); - std::vector indice_vec; - - auto curIdx = SetPosition(); - - for(auto i = 0; i < MAX_SET_SIZE1; ++i) - { - begin_vec[i] = curIdx; - if(MAX_SET_SIZE1 / 4 <= i && i <= MAX_SET_SIZE1 / 4 * 3) - { - for(auto j = MAX_SET_SIZE2 / 4; j < MAX_SET_SIZE2 / 4 * 3; ++j) - { - indice_vec.push_back(j); - ++curIdx; - } - } - } - begin_vec[MAX_SET_SIZE1] = curIdx; - - rel.bindBeginOffsets(MAX_SET_SIZE1, &begin_vec); - rel.bindIndices(indice_vec.size(), &indice_vec); - RelationSetType s(&rel); - - const SetPosition indice_size = indice_vec.size(); - EXPECT_EQ(indice_size, s.totalSize()); - EXPECT_TRUE(s.isValid(true)); - - SLIC_INFO("Creating map on the set "); - - MapType m(&s, (T)0, stride); - - EXPECT_TRUE(m.isValid(true)); - EXPECT_EQ(indice_size, m.totalSize()); - EXPECT_EQ(rel.fromSetSize(), m.firstSetSize()); - EXPECT_EQ(rel.toSetSize(), m.secondSetSize()); - EXPECT_EQ(m.stride(), stride); - - SLIC_INFO("Setting the elements in the map."); - - for(auto idx1 = 0; idx1 < rel.fromSetSize(); idx1++) - { - auto relsubset = rel[idx1]; - for(auto si = 0; si < relsubset.size(); ++si) - { - auto idx2 = relsubset[si]; - for(auto i = 0; i < stride; i++) - { - T* valPtr = m.findValue(idx1, idx2, i); - EXPECT_NE(valPtr, nullptr); - *valPtr = getVal(idx1, idx2, i); - } - } - } - - SLIC_INFO("Checking the elements with findValue()."); - for(auto idx1 = 0; idx1 < rel.fromSetSize(); idx1++) - { - auto relsubset = rel[idx1]; - auto rel_idx = 0; - for(auto idx2 = 0; idx2 < rel.toSetSize(); ++idx2) - { - bool isInRel = relsubset.size() > rel_idx && relsubset[rel_idx] == idx2; - for(auto i = 0; i < stride; i++) - { - T* ptr = m.findValue(idx1, idx2, i); - if(isInRel) - { - EXPECT_NE(ptr, nullptr); - EXPECT_EQ(*ptr, getVal(idx1, idx2, i)); - } - else - { - EXPECT_EQ(ptr, nullptr); - } - } - if(isInRel) - { - rel_idx++; - } - } - } - - SLIC_INFO("Checking the elements with SubMap."); - for(auto idx1 = 0; idx1 < rel.fromSetSize(); idx1++) - { - auto relsubset = rel[idx1]; - SubMapType sm = m(idx1); - for(auto idx2 = 0; idx2 < sm.size(); ++idx2) - { - ASSERT_EQ(relsubset[idx2], sm.index(idx2)); - for(auto i = 0; i < stride; i++) - { - T v = sm.value(idx2, i); - EXPECT_EQ(v, getVal(idx1, sm.index(idx2), i)); - } - } - } - - EXPECT_TRUE(m.isValid()); -} - -TEST(slam_bivariate_map, construct_int_relset_map) -{ - using BSet = BivariateSetType; - using IndPol = STLIndirection; - - constructAndTestRelationSetMap(1); - constructAndTestRelationSetMap(2); - constructAndTestRelationSetMap(3); - - constructAndTestRelationSetMap(1); - - constructAndTestRelationSetMap>(1); - constructAndTestRelationSetMap>(2); - constructAndTestRelationSetMap>(3); -} - -TEST(slam_bivariate_map, construct_double_relset_map) -{ - using BSet = BivariateSetType; - using IndPol = STLIndirection; - - constructAndTestRelationSetMap(1); - - constructAndTestRelationSetMap>(1); - constructAndTestRelationSetMap>(2); - constructAndTestRelationSetMap>(3); - - constructAndTestRelationSetMap(1); - constructAndTestRelationSetMap(2); - constructAndTestRelationSetMap(3); -} - -template -void constructAndTestBivariateMapIterator(int stride) -{ - SLIC_INFO("Creating set"); - using DataType = double; - using IndPol = STLIndirection; - using MapType = BivariateMapType; - - SetType s1(MAX_SET_SIZE1); - SetType s2(MAX_SET_SIZE2); - ProductSetType s(&s1, &s2); - - EXPECT_EQ(s.size(), MAX_SET_SIZE1 * MAX_SET_SIZE2); - EXPECT_TRUE(s.isValid()); - - SLIC_INFO("Creating map on the set "); - MapType m(&s, 0.0, stride); - EXPECT_TRUE(m.isValid()); - EXPECT_EQ(s.size(), m.totalSize()); - EXPECT_EQ(m.stride(), stride); - - SLIC_INFO("Setting the elements in the map."); - //currently can't set value using iterator - for(auto idx1 = 0; idx1 < m.firstSetSize(); ++idx1) - { - for(auto idx2 = 0; idx2 < m.secondSetSize(); ++idx2) - { - for(auto i = 0; i < stride; i++) - { - DataType* valPtr = m.findValue(idx1, idx2, i); - EXPECT_NE(valPtr, nullptr); - *valPtr = getVal(idx1, idx2, i); - } - } - } - - SLIC_INFO("Checking the elements with SubMap flat iterator."); - for(auto idx1 = 0; idx1 < m.firstSetSize(); ++idx1) - { - int idx2 = 0; - int compIdx = 0; - for(auto iter = m.begin(idx1); iter != m.end(idx1); ++iter) - { - EXPECT_EQ(*iter, getVal(idx1, idx2, compIdx)); - compIdx++; - if(compIdx == m.numComp()) - { - compIdx = 0; - idx2++; - } - } - } - SLIC_INFO("Checking the elements with BivariateMap flat iterator."); - { - auto iter = m.begin(); - - for(auto idx1 = 0; idx1 < m.firstSetSize(); ++idx1) - { - for(auto idx2 = 0; idx2 < m.secondSetSize(); ++idx2) - { - for(auto i = 0; i < stride; i++) - { - // Check validity of indexing - EXPECT_EQ(iter.firstIndex(), idx1); - EXPECT_EQ(iter.secondIndex(), idx2); - EXPECT_EQ(iter.compIndex(), i); - EXPECT_NE(iter, m.end()); - - // Check equality of values - DataType val = getVal(idx1, idx2, i); - EXPECT_EQ(val, *iter); - - iter++; - // flat_idx++; - } - } - } - } - - SLIC_INFO("Checking the elements with BivariateMap range iterator."); - { - auto iter = m.set_begin(); - for(auto idx1 = 0; idx1 < m.firstSetSize(); ++idx1) - { - for(auto idx2 = 0; idx2 < m.secondSetSize(); ++idx2) - { - EXPECT_EQ((*iter).size(), stride); - EXPECT_EQ(iter.firstIndex(), idx1); - EXPECT_EQ(iter.secondIndex(), idx2); - for(auto i = 0; i < stride; i++) - { - DataType val = getVal(idx1, idx2, i); - EXPECT_EQ(iter.value(i), val); - EXPECT_EQ(iter(i), val); - // Below disabled because we can't test these with just a forward access iterator - //EXPECT_EQ((begin_iter + flat_idx).value(i), val); - //EXPECT_EQ((end_iter - (m.totalSize() - flat_idx)).value(i), val); - //EXPECT_EQ((inval_iter - (m.totalSize() - flat_idx + 1)).value(i), val); - } - iter++; - // flat_idx++; - } - } - } - - EXPECT_TRUE(m.isValid()); -} - -TEST(slam_bivariate_map, iterate) -{ - using BSet = BivariateSetType; - - constructAndTestBivariateMapIterator(1); - constructAndTestBivariateMapIterator(2); - constructAndTestBivariateMapIterator(3); - - constructAndTestBivariateMapIterator>(1); - constructAndTestBivariateMapIterator>(2); - constructAndTestBivariateMapIterator>(3); - - constructAndTestBivariateMapIterator(1); -} - -template -void testScopedCopyBehavior(int stride) -{ - using BMapType = BivariateMapType; - - SetType s1(MAX_SET_SIZE1); - SetType s2(MAX_SET_SIZE2); - ProductSetType s(&s1, &s2); - - EXPECT_EQ(s.size(), MAX_SET_SIZE1 * MAX_SET_SIZE2); - EXPECT_TRUE(s.isValid()); - - SLIC_INFO("Creating map on the set "); - - BMapType m; - { - BMapType m_inner(&s, static_cast(0), stride); - - EXPECT_TRUE(m_inner.isValid()); - EXPECT_EQ(s.size(), m_inner.totalSize()); - EXPECT_EQ(m_inner.stride(), stride); - - SLIC_INFO("Setting the elements in the map."); - - for(auto idx1 = 0; idx1 < m_inner.firstSetSize(); ++idx1) - { - for(auto idx2 = 0; idx2 < m_inner.secondSetSize(); ++idx2) - { - for(auto i = 0; i < stride; i++) - { - T* valPtr = m_inner.findValue(idx1, idx2, i); - EXPECT_NE(valPtr, nullptr); - *valPtr = getVal(idx1, idx2, i); - } - } - } - - m = m_inner; - } - - EXPECT_TRUE(m.isValid()); - EXPECT_EQ(s.size(), m.totalSize()); - EXPECT_EQ(m.stride(), stride); - - SLIC_INFO("Checking the elements with findValue()."); - for(auto idx1 = 0; idx1 < m.firstSetSize(); ++idx1) - { - for(auto idx2 = 0; idx2 < m.secondSetSize(); ++idx2) - { - for(auto i = 0; i < stride; i++) - { - T* ptr = m.findValue(idx1, idx2, i); - EXPECT_NE(ptr, nullptr); - EXPECT_EQ(*ptr, getVal(idx1, idx2, i)); - } - } - } -} - -TEST(slam_bivariate_map, testScopedMapBehavior) -{ - using BSet = BivariateSetType; - using IndPol = STLIndirection; - - testScopedCopyBehavior(1); - - testScopedCopyBehavior>(1); - testScopedCopyBehavior>(2); - testScopedCopyBehavior>(3); - - testScopedCopyBehavior(1); - testScopedCopyBehavior(2); - testScopedCopyBehavior(3); -} - -TEST(slam_bivariate_map, traits) -{ - EXPECT_TRUE(traits::indices_use_indirection::value); - EXPECT_TRUE(traits::indices_use_indirection::value); - EXPECT_FALSE(traits::indices_use_indirection::value); -} - -//---------------------------------------------------------------------- -namespace testing -{ -//------------------------------------------------------------------------------ -// Define some mappings between execution space and allocator. -// - Host/OpenMP -> Umpire host allocator/default -// - CUDA/HIP -> Umpire device/unified allocator -//------------------------------------------------------------------------------ -template -struct ExecTraits -{ - constexpr static bool OnDevice = false; - static int getAllocatorId() - { -#ifdef AXOM_USE_UMPIRE - return axom::getUmpireResourceAllocatorID(umpire::resource::MemoryResourceType::Host); -#else - return axom::getDefaultAllocatorID(); -#endif - } - - static int getUnifiedAllocatorId() - { -#ifdef AXOM_USE_UMPIRE - return axom::getUmpireResourceAllocatorID(umpire::resource::MemoryResourceType::Host); -#else - return axom::getDefaultAllocatorID(); -#endif - } -}; - -#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) -template -struct ExecTraits> -{ - constexpr static bool OnDevice = true; - - static int getAllocatorId() - { - return axom::getUmpireResourceAllocatorID(umpire::resource::MemoryResourceType::Device); - } - - static int getUnifiedAllocatorId() - { - return axom::getUmpireResourceAllocatorID(umpire::resource::MemoryResourceType::Unified); - } -}; -#endif - -#if defined(AXOM_RUNTIME_POLICY_USE_HIP) -template -struct ExecTraits> -{ - constexpr static bool OnDevice = true; - - static int getAllocatorId() - { - return axom::getUmpireResourceAllocatorID(umpire::resource::MemoryResourceType::Device); - } - - static int getUnifiedAllocatorId() - { - return axom::getUmpireResourceAllocatorID(umpire::resource::MemoryResourceType::Unified); - } -}; -#endif - -//------------------------------------------------------------------------------ -// This test harness defines some types that are useful for the tests below -//------------------------------------------------------------------------------ -template -class slam_bivariate_map_templated : public ::testing::Test -{ -public: - using ExecSpace = ExecutionSpace; - using ConcreteSetType = typename slam::RangeSet::ConcreteSet; - - // StaticRelation template types - using ElemIndirection = slam::policies::ArrayViewIndirection; - using VariableCardinality = policies::VariableCardinality; - using RelationType = - slam::StaticRelation; - - // BivariateSet concrete types -- ProductSet and RelationSet - using ProductSetType = typename slam::ProductSet::ConcreteSet; - using RelationSetType = typename slam::RelationSet::ConcreteSet; - - // BivariateMap template types - using RealData = axom::Array; - using IndirectionPolicy = slam::policies::ArrayViewIndirection; - using StridePolicy = slam::policies::RuntimeStride; - using InterfacePolicy = slam::policies::ConcreteInterface; - using RelationMapType = - slam::BivariateMap; - using CartesianMapType = - slam::BivariateMap; - - template - using NDStridePolicy = slam::policies::MultiDimStride; - template - using CartesianNDMapType = - slam::BivariateMap, InterfacePolicy>; - template - using RelationNDMapType = - slam::BivariateMap, InterfacePolicy>; - - slam_bivariate_map_templated() - : m_allocatorId(ExecTraits::getAllocatorId()) - , m_unifiedAllocatorId(ExecTraits::getUnifiedAllocatorId()) - { } - - void initializeAndTestCartesianMap(int stride); - - void initializeAndTestCartesianMap(axom::StackArray shape); - - void initializeAndTestRelationMap(int stride); - - void initializeAndTestRelationMap(axom::StackArray shape); - -protected: - int m_allocatorId; - int m_unifiedAllocatorId; -}; - -using MyTypes = ::testing::Types< -#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) - axom::OMP_EXEC, -#endif -#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) - axom::CUDA_EXEC<256>, -#endif -#if defined(AXOM_RUNTIME_POLICY_USE_HIP) - axom::HIP_EXEC<256>, -#endif - axom::SEQ_EXEC>; - -TYPED_TEST_SUITE(slam_bivariate_map_templated, MyTypes); - -//---------------------------------------------------------------------- -template -void slam_bivariate_map_templated::initializeAndTestCartesianMap(int stride) -{ - using MapType = CartesianMapType; - - // Create associated sets. - axom::Array sets(2, 2, m_unifiedAllocatorId); - sets[0] = ConcreteSetType(MAX_SET_SIZE1); - sets[1] = ConcreteSetType(MAX_SET_SIZE2); - - SLIC_INFO("Creating product set with size (" << MAX_SET_SIZE1 << ", " << MAX_SET_SIZE2 << ")"); - ProductSetType prodSet(&sets[0], &sets[1]); - EXPECT_EQ(prodSet.size(), MAX_SET_SIZE1 * MAX_SET_SIZE2); - EXPECT_TRUE(prodSet.isValid()); - - // Create array of elements to back the map. - m_allocatorId = ExecTraits::getAllocatorId(); - axom::IndexType backingSize = prodSet.size() * stride; - - RealData realBacking(backingSize, backingSize, m_allocatorId); - - SLIC_INFO("\nCreating double map with stride 1 on the set "); - const MapType m(prodSet, realBacking.view(), stride); - - EXPECT_EQ(m.stride(), stride); - SLIC_INFO("\nSetting the elements."); - axom::for_all( - m.firstSetSize(), - AXOM_LAMBDA(int idx1) { - for(auto idx2 = 0; idx2 < m.secondSetSize(); idx2++) - { - for(auto comp = 0; comp < stride; comp++) - { - m(idx1, idx2, comp) = getVal(idx1, idx2, comp); - } - } - }); - - int totalSize = prodSet.size() * stride; - axom::Array isValid(totalSize, totalSize, m_unifiedAllocatorId); - const auto isValid_view = isValid.data(); - - SLIC_INFO("\nChecking the elements with findValue()."); - axom::for_all( - m.firstSetSize(), - AXOM_LAMBDA(int idx1) { - for(auto idx2 = 0; idx2 < m.secondSetSize(); idx2++) - { - for(auto comp = 0; comp < stride; comp++) - { - int flatIdx = idx1 * m.secondSetSize() * stride; - flatIdx += idx2 * stride; - flatIdx += comp; - - double* ptr = m.findValue(idx1, idx2, comp); - bool hasValue = (ptr != nullptr); - hasValue = hasValue && (*ptr == getVal(idx1, idx2, comp)); - isValid_view[flatIdx] = hasValue; - } - } - }); - - for(int validEntry : isValid) - { - EXPECT_TRUE(validEntry); - } - - SLIC_INFO("\nChecking the elements with SubMap."); - axom::for_all( - m.firstSetSize(), - AXOM_LAMBDA(int idx1) { - auto submap = m(idx1); - for(auto idx2 = 0; idx2 < m.secondSetSize(); idx2++) - { - for(auto comp = 0; comp < stride; comp++) - { - int flatIdx = idx1 * m.secondSetSize() * stride; - flatIdx += idx2 * stride; - flatIdx += comp; - - double value = submap(idx2, comp); - bool hasValue = (value == getVal(idx1, idx2, comp)); - isValid_view[flatIdx] = hasValue; - } - } - }); - - for(int validEntry : isValid) - { - EXPECT_TRUE(validEntry); - } -} - -//---------------------------------------------------------------------- -template -void slam_bivariate_map_templated::initializeAndTestCartesianMap( - axom::StackArray shape) -{ - using MapType = CartesianNDMapType<3>; - - // Create associated sets. - axom::Array sets(2, 2, m_unifiedAllocatorId); - sets[0] = ConcreteSetType(MAX_SET_SIZE1); - sets[1] = ConcreteSetType(MAX_SET_SIZE2); - - SLIC_INFO("Creating product set with size (" << MAX_SET_SIZE1 << ", " << MAX_SET_SIZE2 << ")"); - ProductSetType prodSet(&sets[0], &sets[1]); - EXPECT_EQ(prodSet.size(), MAX_SET_SIZE1 * MAX_SET_SIZE2); - EXPECT_TRUE(prodSet.isValid()); - - int flatStride = shape[0] * shape[1] * shape[2]; - int strides[3] = {shape[1] * shape[2], shape[2], 1}; - - // Create array of elements to back the map. - m_allocatorId = ExecTraits::getAllocatorId(); - axom::IndexType backingSize = prodSet.size() * flatStride; - - RealData realBacking(backingSize, backingSize, m_unifiedAllocatorId); - - SLIC_INFO(axom::fmt::format("\nCreating double map with shape ({}) on the ProductSet", - axom::fmt::join(shape, ", "))); - const MapType m(prodSet, realBacking.view(), shape); - - EXPECT_EQ(m.stride(), flatStride); - SLIC_INFO("\nSetting the elements."); - axom::for_all( - m.firstSetSize(), - AXOM_LAMBDA(int idx1) { - for(auto idx2 = 0; idx2 < m.secondSetSize(); idx2++) - { - for(int i = 0; i < shape[0]; i++) - { - for(int j = 0; j < shape[1]; j++) - { - for(int k = 0; k < shape[2]; k++) - { - int flatCompIdx = i * strides[0] + j * strides[1] + k * strides[2]; - m(idx1, idx2, i, j, k) = getVal(idx1, idx2, flatCompIdx); - } - } - } - } - }); - - SLIC_INFO("\nChecking the elements with findValue()."); - for(int idx1 = 0; idx1 < m.firstSetSize(); idx1++) - { - for(auto idx2 = 0; idx2 < m.secondSetSize(); idx2++) - { - int bsetIndex = idx1 * m.secondSetSize() + idx2; - for(int i = 0; i < shape[0]; i++) - { - for(int j = 0; j < shape[1]; j++) - { - for(int k = 0; k < shape[2]; k++) - { - int flatCompIdx = i * strides[0] + j * strides[1] + k * strides[2]; - int flatIdx = bsetIndex * flatStride; - flatIdx += flatCompIdx; - - double* ptr = m.findValue(idx1, idx2, i, j, k); - EXPECT_NE(ptr, nullptr); - EXPECT_DOUBLE_EQ(*ptr, getVal(idx1, idx2, flatCompIdx)); - // Test other access methods: - EXPECT_DOUBLE_EQ(*ptr, m.flatValue(bsetIndex, i, j, k)); - EXPECT_DOUBLE_EQ(*ptr, m(idx1, idx2, i, j, k)); - EXPECT_DOUBLE_EQ(*ptr, m[flatIdx]); - } - } - } - } - } - - SLIC_INFO("\nChecking the elements with BivariateMap range iterator."); - for(auto it = m.set_begin(); it != m.set_end(); ++it) - { - int idx1 = it.firstIndex(); - int idx2 = it.secondIndex(); - int flatIdx = it.flatIndex(); - - EXPECT_EQ(idx1, m.set()->flatToFirstIndex(flatIdx)); - EXPECT_EQ(idx2, m.set()->flatToSecondIndex(flatIdx)); - EXPECT_EQ(it.numComp(), m.stride()); - - for(int i = 0; i < shape[0]; i++) - { - for(int j = 0; j < shape[1]; j++) - { - for(int k = 0; k < shape[2]; k++) - { - int flatCompIdx = i * strides[0] + j * strides[1] + k * strides[2]; - double expected_value = getVal(idx1, idx2, flatCompIdx); - - EXPECT_DOUBLE_EQ(expected_value, (*it)(i, j, k)); - EXPECT_DOUBLE_EQ(expected_value, it(i, j, k)); - EXPECT_DOUBLE_EQ(expected_value, it.value(i, j, k)); - } - } - } - } -} - -//---------------------------------------------------------------------- -template -void slam_bivariate_map_templated::initializeAndTestRelationMap(int stride) -{ - using MapType = RelationMapType; - - // Create associated sets. - axom::Array sets(2, 2, m_unifiedAllocatorId); - sets[0] = ConcreteSetType(MAX_SET_SIZE1); - sets[1] = ConcreteSetType(MAX_SET_SIZE2); - - // Create a relation on the two sets. - SLIC_INFO("Creating static relation between two sets."); - axom::Array rel(1, 1, m_unifiedAllocatorId); - rel[0] = RelationType(&sets[0], &sets[1]); - axom::Array begin_vec(MAX_SET_SIZE1 + 1, MAX_SET_SIZE1 + 1, m_unifiedAllocatorId); - axom::Array index_vec(0, 0, m_unifiedAllocatorId); - - SetPosition curIdx = 0; - - for(auto i = 0; i < MAX_SET_SIZE1; ++i) - { - begin_vec[i] = curIdx; - if(MAX_SET_SIZE1 / 4 <= i && i <= MAX_SET_SIZE1 / 4 * 3) - { - for(auto j = MAX_SET_SIZE2 / 4; j < MAX_SET_SIZE2 / 4 * 3; ++j) - { - index_vec.push_back(j); - ++curIdx; - } - } - } - begin_vec[MAX_SET_SIZE1] = curIdx; - - rel[0].bindBeginOffsets(MAX_SET_SIZE1, begin_vec.view()); - rel[0].bindIndices(index_vec.size(), index_vec.view()); - - RelationType* relPtr = &rel[0]; - - RelationSetType relSet(&rel[0]); - EXPECT_EQ(index_vec.size(), relSet.totalSize()); - EXPECT_TRUE(relSet.isValid()); - - // Create array of elements to back the map. - m_allocatorId = ExecTraits::getAllocatorId(); - axom::IndexType backingSize = index_vec.size() * stride; - - RealData realBacking(backingSize, backingSize, m_allocatorId); - - SLIC_INFO("\nCreating double map with stride " << stride << " on the RelationSet "); - - MapType m(relSet, realBacking.view(), stride); - - EXPECT_EQ(m.stride(), stride); - SLIC_INFO("\nSetting the elements."); - axom::for_all( - m.firstSetSize(), - AXOM_LAMBDA(int idx1) { - auto relSubset = (*relPtr)[idx1]; - for(auto slot = 0; slot < relSubset.size(); slot++) - { - auto idx2 = relSubset[slot]; - for(auto comp = 0; comp < stride; comp++) - { - double* valPtr = m.findValue(idx1, idx2, comp); -#ifndef AXOM_DEVICE_CODE - EXPECT_NE(valPtr, nullptr); -#endif - *valPtr = getVal(idx1, idx2, comp); - } - } - }); - - SLIC_INFO("\nChecking the elements with findValue()."); - { - axom::ReduceSum numIncorrect(0); - - axom::for_all( - m.firstSetSize(), - AXOM_LAMBDA(int idx1) { - auto relSubset = (*relPtr)[idx1]; - auto relIndex = 0; - for(auto idx2 = 0; idx2 < m.secondSetSize(); idx2++) - { - bool inRelation = relSubset.size() > relIndex && relSubset[relIndex] == idx2; - for(auto comp = 0; comp < stride; comp++) - { - double* ptr = m.findValue(idx1, idx2, comp); - if(inRelation) - { - numIncorrect += (ptr == nullptr); - numIncorrect += (*ptr != getVal(idx1, idx2, comp)); - } - else - { - numIncorrect += (ptr != nullptr); - } - } - if(inRelation) - { - relIndex++; - } - } - }); - - EXPECT_EQ(numIncorrect.get(), 0); - } -} -//---------------------------------------------------------------------- -template -void slam_bivariate_map_templated::initializeAndTestRelationMap( - axom::StackArray shape) -{ - using MapType = RelationNDMapType<3>; - - // Compute strides. - int flatStride = shape[0] * shape[1] * shape[2]; - int strides[3] = {shape[1] * shape[2], shape[2], 1}; - - // Create associated sets. - axom::Array sets(2, 2, m_unifiedAllocatorId); - sets[0] = ConcreteSetType(MAX_SET_SIZE1); - sets[1] = ConcreteSetType(MAX_SET_SIZE2); - - // Create a relation on the two sets. - SLIC_INFO("Creating static relation between two sets."); - axom::Array rel(1, 1, m_unifiedAllocatorId); - rel[0] = RelationType(&sets[0], &sets[1]); - axom::Array begin_vec(MAX_SET_SIZE1 + 1, MAX_SET_SIZE1 + 1, m_unifiedAllocatorId); - axom::Array index_vec(0, 0, m_unifiedAllocatorId); - - SetPosition curIdx = 0; - - for(auto i = 0; i < MAX_SET_SIZE1; ++i) - { - begin_vec[i] = curIdx; - if(MAX_SET_SIZE1 / 4 <= i && i <= MAX_SET_SIZE1 / 4 * 3) - { - for(auto j = MAX_SET_SIZE2 / 4; j < MAX_SET_SIZE2 / 4 * 3; ++j) - { - index_vec.push_back(j); - ++curIdx; - } - } - } - begin_vec[MAX_SET_SIZE1] = curIdx; - - rel[0].bindBeginOffsets(MAX_SET_SIZE1, begin_vec.view()); - rel[0].bindIndices(index_vec.size(), index_vec.view()); - - RelationSetType relSet(&rel[0]); - EXPECT_EQ(index_vec.size(), relSet.totalSize()); - EXPECT_TRUE(relSet.isValid()); - - // Create array of elements to back the map. - m_allocatorId = ExecTraits::getUnifiedAllocatorId(); - axom::IndexType backingSize = index_vec.size() * flatStride; - - RealData realBacking(backingSize, backingSize, m_allocatorId); - - SLIC_INFO(axom::fmt::format("\nCreating double map with shape ({}) on the ProductSet", - axom::fmt::join(shape, ", "))); - const MapType m(relSet, realBacking.view(), shape); - - EXPECT_EQ(m.stride(), flatStride); - axom::for_all( - m.firstSetSize(), - AXOM_LAMBDA(int idx1) { - auto submap = m(idx1); - for(auto slot = 0; slot < submap.size(); slot++) - { - for(int i = 0; i < shape[0]; i++) - { - for(int j = 0; j < shape[1]; j++) - { - for(int k = 0; k < shape[2]; k++) - { - int idx2 = submap.index(slot); - int flatCompIdx = i * strides[0] + j * strides[1] + k * strides[2]; - submap(slot, i, j, k) = getVal(idx1, idx2, flatCompIdx); - } - } - } - } - }); - - SLIC_INFO("\nChecking the elements with findValue()."); - for(int idx1 = 0; idx1 < m.firstSetSize(); idx1++) - { - auto submap = m(idx1); - int slot = 0; - for(int idx2 = 0; idx2 < m.secondSetSize(); idx2++) - { - bool inRelation = submap.size() > slot && submap.index(slot) == idx2; - for(int i = 0; i < shape[0]; i++) - { - for(int j = 0; j < shape[1]; j++) - { - for(int k = 0; k < shape[2]; k++) - { - int flatCompIdx = i * strides[0] + j * strides[1] + k * strides[2]; - double expected_value = getVal(idx1, idx2, flatCompIdx); - - double* valuePtr = m.findValue(idx1, idx2, i, j, k); - if(inRelation) - { - // Test set-based indexing: (idx1, idx2) - EXPECT_NE(valuePtr, nullptr); - EXPECT_DOUBLE_EQ(expected_value, *valuePtr); - EXPECT_DOUBLE_EQ(expected_value, m(idx1, idx2, i, j, k)); - } - else - { - EXPECT_EQ(valuePtr, nullptr); - } - } - } - } - if(inRelation) - { - slot++; - } - } - } - - SLIC_INFO("\nChecking the elements with BivariateMap range iterator."); - for(auto it = m.set_begin(); it != m.set_end(); ++it) - { - int idx1 = it.firstIndex(); - int idx2 = it.secondIndex(); - int flatIdx = it.flatIndex(); - - EXPECT_EQ(idx1, m.set()->flatToFirstIndex(flatIdx)); - EXPECT_EQ(idx2, m.set()->flatToSecondIndex(flatIdx)); - EXPECT_EQ(it.numComp(), m.stride()); - - for(int i = 0; i < shape[0]; i++) - { - for(int j = 0; j < shape[1]; j++) - { - for(int k = 0; k < shape[2]; k++) - { - int flatCompIdx = i * strides[0] + j * strides[1] + k * strides[2]; - double expected_value = getVal(idx1, idx2, flatCompIdx); - - EXPECT_DOUBLE_EQ(expected_value, (*it)(i, j, k)); - EXPECT_DOUBLE_EQ(expected_value, it(i, j, k)); - EXPECT_DOUBLE_EQ(expected_value, it.value(i, j, k)); - } - } - } - } -} - -//---------------------------------------------------------------------- -AXOM_TYPED_TEST(slam_bivariate_map_templated, constructAndTestProductSet) -{ - this->initializeAndTestCartesianMap(1); - this->initializeAndTestCartesianMap(2); - this->initializeAndTestCartesianMap(3); -} - -//---------------------------------------------------------------------- -AXOM_TYPED_TEST(slam_bivariate_map_templated, constructAndTestProductSet3D) -{ - this->initializeAndTestCartesianMap({2, 3, 5}); - this->initializeAndTestCartesianMap({3, 5, 7}); -} - -//---------------------------------------------------------------------- -AXOM_TYPED_TEST(slam_bivariate_map_templated, constructAndTestRelationSet) -{ - this->initializeAndTestRelationMap(1); - this->initializeAndTestRelationMap(2); - this->initializeAndTestRelationMap(3); -} - -//---------------------------------------------------------------------- -AXOM_TYPED_TEST(slam_bivariate_map_templated, constructAndTestRelationSet3D) -{ - this->initializeAndTestRelationMap({2, 3, 5}); - this->initializeAndTestRelationMap({3, 5, 7}); -} - -} // namespace testing -//---------------------------------------------------------------------- - -int main(int argc, char* argv[]) -{ - ::testing::InitGoogleTest(&argc, argv); -#ifdef AXOM_DEBUG - // add this line to avoid a warning in the output about thread safety - ::testing::FLAGS_gtest_death_test_style = "threadsafe"; -#endif - - axom::slic::SimpleLogger logger(axom::slic::message::Info); - - int result = RUN_ALL_TESTS(); - - return result; -} +// 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) + +/* + * \file slam_map_BivariateMap.cpp + * + * \brief Unit tests for Slam's Bivariate Map + */ + +#include +#include "gtest/gtest.h" + +#include "axom/core/execution/runtime_policy.hpp" +#include "axom/slic.hpp" +#include "axom/slam.hpp" + +namespace +{ +namespace slam = axom::slam; +namespace policies = axom::slam::policies; +namespace traits = axom::slam::traits; + +using SetPosition = slam::DefaultPositionType; +using SetElement = slam::DefaultElementType; +using SetType = slam::RangeSet; + +using StrideOneType = policies::StrideOne; + +template +using CompileTimeStrideType = policies::CompileTimeStride; + +using RuntimeStrideType = policies::RuntimeStride; + +template +using STLIndirection = policies::STLVectorIndirection; +using VariableCardinality = policies::VariableCardinality>; + +using RelationType = + slam::StaticRelation, SetType, SetType>; + +using BivariateSetType = slam::BivariateSet; +using ProductSetType = slam::ProductSet; +using RelationSetType = slam::RelationSet; + +template +using BivariateMapType = slam::BivariateMap; + +constexpr SetPosition MAX_SET_SIZE1 = 10; +constexpr SetPosition MAX_SET_SIZE2 = 15; + +constexpr double multFac3 = 0000.1; +constexpr double multFac1 = 1000.0; +constexpr double multFac2 = 0010.0; + +} // end anonymous namespace + +TEST(slam_bivariate_map, construct_empty_map) +{ + slam::BivariateMap m; + + EXPECT_TRUE(m.isValid(true)); + EXPECT_EQ(m.totalSize(), 0); + EXPECT_EQ(m.firstSetSize(), 0); + EXPECT_EQ(m.secondSetSize(), 0); +} + +template +AXOM_HOST_DEVICE inline T getVal(SetPosition idx1, SetPosition idx2, SetPosition idx3 = 0) +{ + return static_cast(idx1 * multFac1 + idx2 * multFac2 + idx3 * multFac3); +} + +template +void constructAndTestCartesianMap(int stride) +{ + SLIC_INFO("Testing BivariateMap on ProductSet with stride " << stride); + + SLIC_INFO("Creating set"); + using BMapType = BivariateMapType; + using SubMapType = typename BMapType::SubMapType; + + SetType s1(MAX_SET_SIZE1); + SetType s2(MAX_SET_SIZE2); + ProductSetType s(&s1, &s2); + + EXPECT_EQ(s.size(), MAX_SET_SIZE1 * MAX_SET_SIZE2); + EXPECT_TRUE(s.isValid()); + + SLIC_INFO("Creating map on the set "); + + BMapType m(&s, static_cast(0), stride); + + EXPECT_TRUE(m.isValid()); + EXPECT_EQ(s.size(), m.totalSize()); + EXPECT_EQ(m.stride(), stride); + + SLIC_INFO("Setting the elements in the map."); + + for(auto idx1 = 0; idx1 < m.firstSetSize(); ++idx1) + { + for(auto idx2 = 0; idx2 < m.secondSetSize(); ++idx2) + { + for(auto i = 0; i < stride; i++) + { + T* valPtr = m.findValue(idx1, idx2, i); + EXPECT_NE(valPtr, nullptr); + *valPtr = getVal(idx1, idx2, i); + } + } + } + + SLIC_INFO("Checking the elements with findValue()."); + for(auto idx1 = 0; idx1 < m.firstSetSize(); ++idx1) + { + for(auto idx2 = 0; idx2 < m.secondSetSize(); ++idx2) + { + for(auto i = 0; i < stride; i++) + { + T* ptr = m.findValue(idx1, idx2, i); + EXPECT_NE(ptr, nullptr); + EXPECT_EQ(*ptr, getVal(idx1, idx2, i)); + } + } + } + + SLIC_INFO("Checking the elements with SubMap."); + for(auto idx1 = 0; idx1 < m.firstSetSize(); ++idx1) + { + SubMapType sm = m(idx1); + for(auto idx2 = 0; idx2 < sm.size(); ++idx2) + { + for(auto i = 0; i < stride; i++) + { + T v = sm.value(idx2, i); + EXPECT_EQ(v, getVal(idx1, idx2, i)); + EXPECT_EQ(sm.index(idx2), idx2); + } + } + } + + EXPECT_TRUE(m.isValid()); +} + +TEST(slam_bivariate_map, construct_int_map) +{ + using BSet = BivariateSetType; + using IndPol = STLIndirection; + + constructAndTestCartesianMap(1); + constructAndTestCartesianMap(2); + constructAndTestCartesianMap(3); + + constructAndTestCartesianMap(1); + + constructAndTestCartesianMap>(1); + constructAndTestCartesianMap>(2); + constructAndTestCartesianMap>(3); +} + +TEST(slam_bivariate_map, construct_double_map) +{ + using BSet = BivariateSetType; + using IndPol = STLIndirection; + + constructAndTestCartesianMap(1); + + constructAndTestCartesianMap>(1); + constructAndTestCartesianMap>(2); + constructAndTestCartesianMap>(3); + + constructAndTestCartesianMap(1); + constructAndTestCartesianMap(2); + constructAndTestCartesianMap(3); +} + +template +void constructAndTestRelationSetMap(int stride) +{ + SLIC_INFO("Testing BivariateMap on RelationSet with stride " << stride); + + SLIC_INFO("Creating set"); + using MapType = BivariateMapType; + using SubMapType = typename MapType::SubMapType; + + SetType s1(MAX_SET_SIZE1); + SetType s2(MAX_SET_SIZE2); + + RelationType rel(&s1, &s2); + + std::vector begin_vec(MAX_SET_SIZE1 + 1, 0); + std::vector indice_vec; + + auto curIdx = SetPosition(); + + for(auto i = 0; i < MAX_SET_SIZE1; ++i) + { + begin_vec[i] = curIdx; + if(MAX_SET_SIZE1 / 4 <= i && i <= MAX_SET_SIZE1 / 4 * 3) + { + for(auto j = MAX_SET_SIZE2 / 4; j < MAX_SET_SIZE2 / 4 * 3; ++j) + { + indice_vec.push_back(j); + ++curIdx; + } + } + } + begin_vec[MAX_SET_SIZE1] = curIdx; + + rel.bindBeginOffsets(MAX_SET_SIZE1, &begin_vec); + rel.bindIndices(indice_vec.size(), &indice_vec); + RelationSetType s(&rel); + + const SetPosition indice_size = indice_vec.size(); + EXPECT_EQ(indice_size, s.totalSize()); + EXPECT_TRUE(s.isValid(true)); + + SLIC_INFO("Creating map on the set "); + + MapType m(&s, (T)0, stride); + + EXPECT_TRUE(m.isValid(true)); + EXPECT_EQ(indice_size, m.totalSize()); + EXPECT_EQ(rel.fromSetSize(), m.firstSetSize()); + EXPECT_EQ(rel.toSetSize(), m.secondSetSize()); + EXPECT_EQ(m.stride(), stride); + + SLIC_INFO("Setting the elements in the map."); + + for(auto idx1 = 0; idx1 < rel.fromSetSize(); idx1++) + { + auto relsubset = rel[idx1]; + for(auto si = 0; si < relsubset.size(); ++si) + { + auto idx2 = relsubset[si]; + for(auto i = 0; i < stride; i++) + { + T* valPtr = m.findValue(idx1, idx2, i); + EXPECT_NE(valPtr, nullptr); + *valPtr = getVal(idx1, idx2, i); + } + } + } + + SLIC_INFO("Checking the elements with findValue()."); + for(auto idx1 = 0; idx1 < rel.fromSetSize(); idx1++) + { + auto relsubset = rel[idx1]; + auto rel_idx = 0; + for(auto idx2 = 0; idx2 < rel.toSetSize(); ++idx2) + { + bool isInRel = relsubset.size() > rel_idx && relsubset[rel_idx] == idx2; + for(auto i = 0; i < stride; i++) + { + T* ptr = m.findValue(idx1, idx2, i); + if(isInRel) + { + EXPECT_NE(ptr, nullptr); + EXPECT_EQ(*ptr, getVal(idx1, idx2, i)); + } + else + { + EXPECT_EQ(ptr, nullptr); + } + } + if(isInRel) + { + rel_idx++; + } + } + } + + SLIC_INFO("Checking the elements with SubMap."); + for(auto idx1 = 0; idx1 < rel.fromSetSize(); idx1++) + { + auto relsubset = rel[idx1]; + SubMapType sm = m(idx1); + for(auto idx2 = 0; idx2 < sm.size(); ++idx2) + { + ASSERT_EQ(relsubset[idx2], sm.index(idx2)); + for(auto i = 0; i < stride; i++) + { + T v = sm.value(idx2, i); + EXPECT_EQ(v, getVal(idx1, sm.index(idx2), i)); + } + } + } + + EXPECT_TRUE(m.isValid()); +} + +TEST(slam_bivariate_map, construct_int_relset_map) +{ + using BSet = BivariateSetType; + using IndPol = STLIndirection; + + constructAndTestRelationSetMap(1); + constructAndTestRelationSetMap(2); + constructAndTestRelationSetMap(3); + + constructAndTestRelationSetMap(1); + + constructAndTestRelationSetMap>(1); + constructAndTestRelationSetMap>(2); + constructAndTestRelationSetMap>(3); +} + +TEST(slam_bivariate_map, construct_double_relset_map) +{ + using BSet = BivariateSetType; + using IndPol = STLIndirection; + + constructAndTestRelationSetMap(1); + + constructAndTestRelationSetMap>(1); + constructAndTestRelationSetMap>(2); + constructAndTestRelationSetMap>(3); + + constructAndTestRelationSetMap(1); + constructAndTestRelationSetMap(2); + constructAndTestRelationSetMap(3); +} + +template +void constructAndTestBivariateMapIterator(int stride) +{ + SLIC_INFO("Creating set"); + using DataType = double; + using IndPol = STLIndirection; + using MapType = BivariateMapType; + + SetType s1(MAX_SET_SIZE1); + SetType s2(MAX_SET_SIZE2); + ProductSetType s(&s1, &s2); + + EXPECT_EQ(s.size(), MAX_SET_SIZE1 * MAX_SET_SIZE2); + EXPECT_TRUE(s.isValid()); + + SLIC_INFO("Creating map on the set "); + MapType m(&s, 0.0, stride); + EXPECT_TRUE(m.isValid()); + EXPECT_EQ(s.size(), m.totalSize()); + EXPECT_EQ(m.stride(), stride); + + SLIC_INFO("Setting the elements in the map."); + //currently can't set value using iterator + for(auto idx1 = 0; idx1 < m.firstSetSize(); ++idx1) + { + for(auto idx2 = 0; idx2 < m.secondSetSize(); ++idx2) + { + for(auto i = 0; i < stride; i++) + { + DataType* valPtr = m.findValue(idx1, idx2, i); + EXPECT_NE(valPtr, nullptr); + *valPtr = getVal(idx1, idx2, i); + } + } + } + + SLIC_INFO("Checking the elements with SubMap flat iterator."); + for(auto idx1 = 0; idx1 < m.firstSetSize(); ++idx1) + { + int idx2 = 0; + int compIdx = 0; + for(auto iter = m.begin(idx1); iter != m.end(idx1); ++iter) + { + EXPECT_EQ(*iter, getVal(idx1, idx2, compIdx)); + compIdx++; + if(compIdx == m.numComp()) + { + compIdx = 0; + idx2++; + } + } + } + SLIC_INFO("Checking the elements with BivariateMap flat iterator."); + { + auto iter = m.begin(); + + for(auto idx1 = 0; idx1 < m.firstSetSize(); ++idx1) + { + for(auto idx2 = 0; idx2 < m.secondSetSize(); ++idx2) + { + for(auto i = 0; i < stride; i++) + { + // Check validity of indexing + EXPECT_EQ(iter.firstIndex(), idx1); + EXPECT_EQ(iter.secondIndex(), idx2); + EXPECT_EQ(iter.compIndex(), i); + EXPECT_NE(iter, m.end()); + + // Check equality of values + DataType val = getVal(idx1, idx2, i); + EXPECT_EQ(val, *iter); + + iter++; + // flat_idx++; + } + } + } + } + + SLIC_INFO("Checking the elements with BivariateMap range iterator."); + { + auto iter = m.set_begin(); + for(auto idx1 = 0; idx1 < m.firstSetSize(); ++idx1) + { + for(auto idx2 = 0; idx2 < m.secondSetSize(); ++idx2) + { + EXPECT_EQ((*iter).size(), stride); + EXPECT_EQ(iter.firstIndex(), idx1); + EXPECT_EQ(iter.secondIndex(), idx2); + for(auto i = 0; i < stride; i++) + { + DataType val = getVal(idx1, idx2, i); + EXPECT_EQ(iter.value(i), val); + EXPECT_EQ(iter(i), val); + // Below disabled because we can't test these with just a forward access iterator + //EXPECT_EQ((begin_iter + flat_idx).value(i), val); + //EXPECT_EQ((end_iter - (m.totalSize() - flat_idx)).value(i), val); + //EXPECT_EQ((inval_iter - (m.totalSize() - flat_idx + 1)).value(i), val); + } + iter++; + // flat_idx++; + } + } + } + + EXPECT_TRUE(m.isValid()); +} + +TEST(slam_bivariate_map, iterate) +{ + using BSet = BivariateSetType; + + constructAndTestBivariateMapIterator(1); + constructAndTestBivariateMapIterator(2); + constructAndTestBivariateMapIterator(3); + + constructAndTestBivariateMapIterator>(1); + constructAndTestBivariateMapIterator>(2); + constructAndTestBivariateMapIterator>(3); + + constructAndTestBivariateMapIterator(1); +} + +template +void testScopedCopyBehavior(int stride) +{ + using BMapType = BivariateMapType; + + SetType s1(MAX_SET_SIZE1); + SetType s2(MAX_SET_SIZE2); + ProductSetType s(&s1, &s2); + + EXPECT_EQ(s.size(), MAX_SET_SIZE1 * MAX_SET_SIZE2); + EXPECT_TRUE(s.isValid()); + + SLIC_INFO("Creating map on the set "); + + BMapType m; + { + BMapType m_inner(&s, static_cast(0), stride); + + EXPECT_TRUE(m_inner.isValid()); + EXPECT_EQ(s.size(), m_inner.totalSize()); + EXPECT_EQ(m_inner.stride(), stride); + + SLIC_INFO("Setting the elements in the map."); + + for(auto idx1 = 0; idx1 < m_inner.firstSetSize(); ++idx1) + { + for(auto idx2 = 0; idx2 < m_inner.secondSetSize(); ++idx2) + { + for(auto i = 0; i < stride; i++) + { + T* valPtr = m_inner.findValue(idx1, idx2, i); + EXPECT_NE(valPtr, nullptr); + *valPtr = getVal(idx1, idx2, i); + } + } + } + + m = m_inner; + } + + EXPECT_TRUE(m.isValid()); + EXPECT_EQ(s.size(), m.totalSize()); + EXPECT_EQ(m.stride(), stride); + + SLIC_INFO("Checking the elements with findValue()."); + for(auto idx1 = 0; idx1 < m.firstSetSize(); ++idx1) + { + for(auto idx2 = 0; idx2 < m.secondSetSize(); ++idx2) + { + for(auto i = 0; i < stride; i++) + { + T* ptr = m.findValue(idx1, idx2, i); + EXPECT_NE(ptr, nullptr); + EXPECT_EQ(*ptr, getVal(idx1, idx2, i)); + } + } + } +} + +TEST(slam_bivariate_map, testScopedMapBehavior) +{ + using BSet = BivariateSetType; + using IndPol = STLIndirection; + + testScopedCopyBehavior(1); + + testScopedCopyBehavior>(1); + testScopedCopyBehavior>(2); + testScopedCopyBehavior>(3); + + testScopedCopyBehavior(1); + testScopedCopyBehavior(2); + testScopedCopyBehavior(3); +} + +TEST(slam_bivariate_map, traits) +{ + EXPECT_TRUE(traits::indices_use_indirection::value); + EXPECT_TRUE(traits::indices_use_indirection::value); + EXPECT_FALSE(traits::indices_use_indirection::value); +} + +//---------------------------------------------------------------------- +namespace testing +{ +//------------------------------------------------------------------------------ +// Define some mappings between execution space and allocator. +// - Host/OpenMP -> Umpire host allocator/default +// - CUDA/HIP -> Umpire device/unified allocator +//------------------------------------------------------------------------------ +template +struct ExecTraits +{ + constexpr static bool OnDevice = false; + static int getAllocatorId() + { +#ifdef AXOM_USE_UMPIRE + return axom::getUmpireResourceAllocatorID(umpire::resource::MemoryResourceType::Host); +#else + return axom::getDefaultAllocatorID(); +#endif + } + + static int getUnifiedAllocatorId() + { +#ifdef AXOM_USE_UMPIRE + return axom::getUmpireResourceAllocatorID(umpire::resource::MemoryResourceType::Host); +#else + return axom::getDefaultAllocatorID(); +#endif + } +}; + +#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) +template +struct ExecTraits> +{ + constexpr static bool OnDevice = true; + + static int getAllocatorId() + { + return axom::getUmpireResourceAllocatorID(umpire::resource::MemoryResourceType::Device); + } + + static int getUnifiedAllocatorId() + { + return axom::getUmpireResourceAllocatorID(umpire::resource::MemoryResourceType::Unified); + } +}; +#endif + +#if defined(AXOM_RUNTIME_POLICY_USE_HIP) +template +struct ExecTraits> +{ + constexpr static bool OnDevice = true; + + static int getAllocatorId() + { + return axom::getUmpireResourceAllocatorID(umpire::resource::MemoryResourceType::Device); + } + + static int getUnifiedAllocatorId() + { + return axom::getUmpireResourceAllocatorID(umpire::resource::MemoryResourceType::Unified); + } +}; +#endif + +//------------------------------------------------------------------------------ +// This test harness defines some types that are useful for the tests below +//------------------------------------------------------------------------------ +template +class slam_bivariate_map_templated : public ::testing::Test +{ +public: + using ExecSpace = ExecutionSpace; + using ConcreteSetType = typename slam::RangeSet::ConcreteSet; + + // StaticRelation template types + using ElemIndirection = slam::policies::ArrayViewIndirection; + using VariableCardinality = policies::VariableCardinality; + using RelationType = + slam::StaticRelation; + + // BivariateSet concrete types -- ProductSet and RelationSet + using ProductSetType = typename slam::ProductSet::ConcreteSet; + using RelationSetType = typename slam::RelationSet::ConcreteSet; + + // BivariateMap template types + using RealData = axom::Array; + using IndirectionPolicy = slam::policies::ArrayViewIndirection; + using StridePolicy = slam::policies::RuntimeStride; + using InterfacePolicy = slam::policies::ConcreteInterface; + using RelationMapType = + slam::BivariateMap; + using CartesianMapType = + slam::BivariateMap; + + template + using NDStridePolicy = slam::policies::MultiDimStride; + template + using CartesianNDMapType = + slam::BivariateMap, InterfacePolicy>; + template + using RelationNDMapType = + slam::BivariateMap, InterfacePolicy>; + + slam_bivariate_map_templated() + : m_allocatorId(ExecTraits::getAllocatorId()) + , m_unifiedAllocatorId(ExecTraits::getUnifiedAllocatorId()) + { } + + void initializeAndTestCartesianMap(int stride); + + void initializeAndTestCartesianMap(axom::StackArray shape); + + void initializeAndTestRelationMap(int stride); + + void initializeAndTestRelationMap(axom::StackArray shape); + +protected: + int m_allocatorId; + int m_unifiedAllocatorId; +}; + +using MyTypes = ::testing::Types< +#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) + axom::OMP_EXEC, +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) + axom::CUDA_EXEC<256>, +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_HIP) + axom::HIP_EXEC<256>, +#endif + axom::SEQ_EXEC>; + +TYPED_TEST_SUITE(slam_bivariate_map_templated, MyTypes); + +//---------------------------------------------------------------------- +template +void slam_bivariate_map_templated::initializeAndTestCartesianMap(int stride) +{ + using MapType = CartesianMapType; + + // Create associated sets. + axom::Array sets(2, 2, m_unifiedAllocatorId); + sets[0] = ConcreteSetType(MAX_SET_SIZE1); + sets[1] = ConcreteSetType(MAX_SET_SIZE2); + + SLIC_INFO("Creating product set with size (" << MAX_SET_SIZE1 << ", " << MAX_SET_SIZE2 << ")"); + ProductSetType prodSet(&sets[0], &sets[1]); + EXPECT_EQ(prodSet.size(), MAX_SET_SIZE1 * MAX_SET_SIZE2); + EXPECT_TRUE(prodSet.isValid()); + + // Create array of elements to back the map. + m_allocatorId = ExecTraits::getAllocatorId(); + axom::IndexType backingSize = prodSet.size() * stride; + + RealData realBacking(backingSize, backingSize, m_allocatorId); + + SLIC_INFO("\nCreating double map with stride 1 on the set "); + const MapType m(prodSet, realBacking.view(), stride); + + EXPECT_EQ(m.stride(), stride); + SLIC_INFO("\nSetting the elements."); + axom::for_all( + m.firstSetSize(), + AXOM_LAMBDA(int idx1) { + for(auto idx2 = 0; idx2 < m.secondSetSize(); idx2++) + { + for(auto comp = 0; comp < stride; comp++) + { + m(idx1, idx2, comp) = getVal(idx1, idx2, comp); + } + } + }); + + int totalSize = prodSet.size() * stride; + axom::Array isValid(totalSize, totalSize, m_unifiedAllocatorId); + const auto isValid_view = isValid.data(); + + SLIC_INFO("\nChecking the elements with findValue()."); + axom::for_all( + m.firstSetSize(), + AXOM_LAMBDA(int idx1) { + for(auto idx2 = 0; idx2 < m.secondSetSize(); idx2++) + { + for(auto comp = 0; comp < stride; comp++) + { + int flatIdx = idx1 * m.secondSetSize() * stride; + flatIdx += idx2 * stride; + flatIdx += comp; + + double* ptr = m.findValue(idx1, idx2, comp); + bool hasValue = (ptr != nullptr); + hasValue = hasValue && (*ptr == getVal(idx1, idx2, comp)); + isValid_view[flatIdx] = hasValue; + } + } + }); + + for(int validEntry : isValid) + { + EXPECT_TRUE(validEntry); + } + + SLIC_INFO("\nChecking the elements with SubMap."); + axom::for_all( + m.firstSetSize(), + AXOM_LAMBDA(int idx1) { + auto submap = m(idx1); + for(auto idx2 = 0; idx2 < m.secondSetSize(); idx2++) + { + for(auto comp = 0; comp < stride; comp++) + { + int flatIdx = idx1 * m.secondSetSize() * stride; + flatIdx += idx2 * stride; + flatIdx += comp; + + double value = submap(idx2, comp); + bool hasValue = (value == getVal(idx1, idx2, comp)); + isValid_view[flatIdx] = hasValue; + } + } + }); + + for(int validEntry : isValid) + { + EXPECT_TRUE(validEntry); + } +} + +//---------------------------------------------------------------------- +template +void slam_bivariate_map_templated::initializeAndTestCartesianMap( + axom::StackArray shape) +{ + using MapType = CartesianNDMapType<3>; + + // Create associated sets. + axom::Array sets(2, 2, m_unifiedAllocatorId); + sets[0] = ConcreteSetType(MAX_SET_SIZE1); + sets[1] = ConcreteSetType(MAX_SET_SIZE2); + + SLIC_INFO("Creating product set with size (" << MAX_SET_SIZE1 << ", " << MAX_SET_SIZE2 << ")"); + ProductSetType prodSet(&sets[0], &sets[1]); + EXPECT_EQ(prodSet.size(), MAX_SET_SIZE1 * MAX_SET_SIZE2); + EXPECT_TRUE(prodSet.isValid()); + + int flatStride = shape[0] * shape[1] * shape[2]; + int strides[3] = {shape[1] * shape[2], shape[2], 1}; + + // Create array of elements to back the map. + m_allocatorId = ExecTraits::getAllocatorId(); + axom::IndexType backingSize = prodSet.size() * flatStride; + + RealData realBacking(backingSize, backingSize, m_unifiedAllocatorId); + + SLIC_INFO(axom::fmt::format("\nCreating double map with shape ({}) on the ProductSet", + axom::fmt::join(shape, ", "))); + const MapType m(prodSet, realBacking.view(), shape); + + EXPECT_EQ(m.stride(), flatStride); + SLIC_INFO("\nSetting the elements."); + axom::for_all( + m.firstSetSize(), + AXOM_LAMBDA(int idx1) { + for(auto idx2 = 0; idx2 < m.secondSetSize(); idx2++) + { + for(int i = 0; i < shape[0]; i++) + { + for(int j = 0; j < shape[1]; j++) + { + for(int k = 0; k < shape[2]; k++) + { + int flatCompIdx = i * strides[0] + j * strides[1] + k * strides[2]; + m(idx1, idx2, i, j, k) = getVal(idx1, idx2, flatCompIdx); + } + } + } + } + }); + + SLIC_INFO("\nChecking the elements with findValue()."); + for(int idx1 = 0; idx1 < m.firstSetSize(); idx1++) + { + for(auto idx2 = 0; idx2 < m.secondSetSize(); idx2++) + { + int bsetIndex = idx1 * m.secondSetSize() + idx2; + for(int i = 0; i < shape[0]; i++) + { + for(int j = 0; j < shape[1]; j++) + { + for(int k = 0; k < shape[2]; k++) + { + int flatCompIdx = i * strides[0] + j * strides[1] + k * strides[2]; + int flatIdx = bsetIndex * flatStride; + flatIdx += flatCompIdx; + + double* ptr = m.findValue(idx1, idx2, i, j, k); + EXPECT_NE(ptr, nullptr); + EXPECT_DOUBLE_EQ(*ptr, getVal(idx1, idx2, flatCompIdx)); + // Test other access methods: + EXPECT_DOUBLE_EQ(*ptr, m.flatValue(bsetIndex, i, j, k)); + EXPECT_DOUBLE_EQ(*ptr, m(idx1, idx2, i, j, k)); + EXPECT_DOUBLE_EQ(*ptr, m[flatIdx]); + } + } + } + } + } + + SLIC_INFO("\nChecking the elements with BivariateMap range iterator."); + for(auto it = m.set_begin(); it != m.set_end(); ++it) + { + int idx1 = it.firstIndex(); + int idx2 = it.secondIndex(); + int flatIdx = it.flatIndex(); + + EXPECT_EQ(idx1, m.set()->flatToFirstIndex(flatIdx)); + EXPECT_EQ(idx2, m.set()->flatToSecondIndex(flatIdx)); + EXPECT_EQ(it.numComp(), m.stride()); + + for(int i = 0; i < shape[0]; i++) + { + for(int j = 0; j < shape[1]; j++) + { + for(int k = 0; k < shape[2]; k++) + { + int flatCompIdx = i * strides[0] + j * strides[1] + k * strides[2]; + double expected_value = getVal(idx1, idx2, flatCompIdx); + + EXPECT_DOUBLE_EQ(expected_value, (*it)(i, j, k)); + EXPECT_DOUBLE_EQ(expected_value, it(i, j, k)); + EXPECT_DOUBLE_EQ(expected_value, it.value(i, j, k)); + } + } + } + } +} + +//---------------------------------------------------------------------- +template +void slam_bivariate_map_templated::initializeAndTestRelationMap(int stride) +{ + using MapType = RelationMapType; + + // Create associated sets. + axom::Array sets(2, 2, m_unifiedAllocatorId); + sets[0] = ConcreteSetType(MAX_SET_SIZE1); + sets[1] = ConcreteSetType(MAX_SET_SIZE2); + + // Create a relation on the two sets. + SLIC_INFO("Creating static relation between two sets."); + axom::Array rel(1, 1, m_unifiedAllocatorId); + rel[0] = RelationType(&sets[0], &sets[1]); + axom::Array begin_vec(MAX_SET_SIZE1 + 1, MAX_SET_SIZE1 + 1, m_unifiedAllocatorId); + axom::Array index_vec(0, 0, m_unifiedAllocatorId); + + SetPosition curIdx = 0; + + for(auto i = 0; i < MAX_SET_SIZE1; ++i) + { + begin_vec[i] = curIdx; + if(MAX_SET_SIZE1 / 4 <= i && i <= MAX_SET_SIZE1 / 4 * 3) + { + for(auto j = MAX_SET_SIZE2 / 4; j < MAX_SET_SIZE2 / 4 * 3; ++j) + { + index_vec.push_back(j); + ++curIdx; + } + } + } + begin_vec[MAX_SET_SIZE1] = curIdx; + + rel[0].bindBeginOffsets(MAX_SET_SIZE1, begin_vec.view()); + rel[0].bindIndices(index_vec.size(), index_vec.view()); + + RelationType* relPtr = &rel[0]; + + RelationSetType relSet(&rel[0]); + EXPECT_EQ(index_vec.size(), relSet.totalSize()); + EXPECT_TRUE(relSet.isValid()); + + // Create array of elements to back the map. + m_allocatorId = ExecTraits::getAllocatorId(); + axom::IndexType backingSize = index_vec.size() * stride; + + RealData realBacking(backingSize, backingSize, m_allocatorId); + + SLIC_INFO("\nCreating double map with stride " << stride << " on the RelationSet "); + + MapType m(relSet, realBacking.view(), stride); + + EXPECT_EQ(m.stride(), stride); + SLIC_INFO("\nSetting the elements."); + axom::for_all( + m.firstSetSize(), + AXOM_LAMBDA(int idx1) { + auto relSubset = (*relPtr)[idx1]; + for(auto slot = 0; slot < relSubset.size(); slot++) + { + auto idx2 = relSubset[slot]; + for(auto comp = 0; comp < stride; comp++) + { + double* valPtr = m.findValue(idx1, idx2, comp); +#ifndef AXOM_DEVICE_CODE + EXPECT_NE(valPtr, nullptr); +#endif + *valPtr = getVal(idx1, idx2, comp); + } + } + }); + + SLIC_INFO("\nChecking the elements with findValue()."); + { + axom::ReduceSum numIncorrect(0); + + axom::for_all( + m.firstSetSize(), + AXOM_LAMBDA(int idx1) { + auto relSubset = (*relPtr)[idx1]; + auto relIndex = 0; + for(auto idx2 = 0; idx2 < m.secondSetSize(); idx2++) + { + bool inRelation = relSubset.size() > relIndex && relSubset[relIndex] == idx2; + for(auto comp = 0; comp < stride; comp++) + { + double* ptr = m.findValue(idx1, idx2, comp); + if(inRelation) + { + numIncorrect += (ptr == nullptr); + numIncorrect += (*ptr != getVal(idx1, idx2, comp)); + } + else + { + numIncorrect += (ptr != nullptr); + } + } + if(inRelation) + { + relIndex++; + } + } + }); + + EXPECT_EQ(numIncorrect.get(), 0); + } +} +//---------------------------------------------------------------------- +template +void slam_bivariate_map_templated::initializeAndTestRelationMap( + axom::StackArray shape) +{ + using MapType = RelationNDMapType<3>; + + // Compute strides. + int flatStride = shape[0] * shape[1] * shape[2]; + int strides[3] = {shape[1] * shape[2], shape[2], 1}; + + // Create associated sets. + axom::Array sets(2, 2, m_unifiedAllocatorId); + sets[0] = ConcreteSetType(MAX_SET_SIZE1); + sets[1] = ConcreteSetType(MAX_SET_SIZE2); + + // Create a relation on the two sets. + SLIC_INFO("Creating static relation between two sets."); + axom::Array rel(1, 1, m_unifiedAllocatorId); + rel[0] = RelationType(&sets[0], &sets[1]); + axom::Array begin_vec(MAX_SET_SIZE1 + 1, MAX_SET_SIZE1 + 1, m_unifiedAllocatorId); + axom::Array index_vec(0, 0, m_unifiedAllocatorId); + + SetPosition curIdx = 0; + + for(auto i = 0; i < MAX_SET_SIZE1; ++i) + { + begin_vec[i] = curIdx; + if(MAX_SET_SIZE1 / 4 <= i && i <= MAX_SET_SIZE1 / 4 * 3) + { + for(auto j = MAX_SET_SIZE2 / 4; j < MAX_SET_SIZE2 / 4 * 3; ++j) + { + index_vec.push_back(j); + ++curIdx; + } + } + } + begin_vec[MAX_SET_SIZE1] = curIdx; + + rel[0].bindBeginOffsets(MAX_SET_SIZE1, begin_vec.view()); + rel[0].bindIndices(index_vec.size(), index_vec.view()); + + RelationSetType relSet(&rel[0]); + EXPECT_EQ(index_vec.size(), relSet.totalSize()); + EXPECT_TRUE(relSet.isValid()); + + // Create array of elements to back the map. + m_allocatorId = ExecTraits::getUnifiedAllocatorId(); + axom::IndexType backingSize = index_vec.size() * flatStride; + + RealData realBacking(backingSize, backingSize, m_allocatorId); + + SLIC_INFO(axom::fmt::format("\nCreating double map with shape ({}) on the ProductSet", + axom::fmt::join(shape, ", "))); + const MapType m(relSet, realBacking.view(), shape); + + EXPECT_EQ(m.stride(), flatStride); + axom::for_all( + m.firstSetSize(), + AXOM_LAMBDA(int idx1) { + auto submap = m(idx1); + for(auto slot = 0; slot < submap.size(); slot++) + { + for(int i = 0; i < shape[0]; i++) + { + for(int j = 0; j < shape[1]; j++) + { + for(int k = 0; k < shape[2]; k++) + { + int idx2 = submap.index(slot); + int flatCompIdx = i * strides[0] + j * strides[1] + k * strides[2]; + submap(slot, i, j, k) = getVal(idx1, idx2, flatCompIdx); + } + } + } + } + }); + + SLIC_INFO("\nChecking the elements with findValue()."); + for(int idx1 = 0; idx1 < m.firstSetSize(); idx1++) + { + auto submap = m(idx1); + int slot = 0; + for(int idx2 = 0; idx2 < m.secondSetSize(); idx2++) + { + bool inRelation = submap.size() > slot && submap.index(slot) == idx2; + for(int i = 0; i < shape[0]; i++) + { + for(int j = 0; j < shape[1]; j++) + { + for(int k = 0; k < shape[2]; k++) + { + int flatCompIdx = i * strides[0] + j * strides[1] + k * strides[2]; + double expected_value = getVal(idx1, idx2, flatCompIdx); + + double* valuePtr = m.findValue(idx1, idx2, i, j, k); + if(inRelation) + { + // Test set-based indexing: (idx1, idx2) + EXPECT_NE(valuePtr, nullptr); + EXPECT_DOUBLE_EQ(expected_value, *valuePtr); + EXPECT_DOUBLE_EQ(expected_value, m(idx1, idx2, i, j, k)); + } + else + { + EXPECT_EQ(valuePtr, nullptr); + } + } + } + } + if(inRelation) + { + slot++; + } + } + } + + SLIC_INFO("\nChecking the elements with BivariateMap range iterator."); + for(auto it = m.set_begin(); it != m.set_end(); ++it) + { + int idx1 = it.firstIndex(); + int idx2 = it.secondIndex(); + int flatIdx = it.flatIndex(); + + EXPECT_EQ(idx1, m.set()->flatToFirstIndex(flatIdx)); + EXPECT_EQ(idx2, m.set()->flatToSecondIndex(flatIdx)); + EXPECT_EQ(it.numComp(), m.stride()); + + for(int i = 0; i < shape[0]; i++) + { + for(int j = 0; j < shape[1]; j++) + { + for(int k = 0; k < shape[2]; k++) + { + int flatCompIdx = i * strides[0] + j * strides[1] + k * strides[2]; + double expected_value = getVal(idx1, idx2, flatCompIdx); + + EXPECT_DOUBLE_EQ(expected_value, (*it)(i, j, k)); + EXPECT_DOUBLE_EQ(expected_value, it(i, j, k)); + EXPECT_DOUBLE_EQ(expected_value, it.value(i, j, k)); + } + } + } + } +} + +//---------------------------------------------------------------------- +AXOM_TYPED_TEST(slam_bivariate_map_templated, constructAndTestProductSet) +{ + this->initializeAndTestCartesianMap(1); + this->initializeAndTestCartesianMap(2); + this->initializeAndTestCartesianMap(3); +} + +//---------------------------------------------------------------------- +AXOM_TYPED_TEST(slam_bivariate_map_templated, constructAndTestProductSet3D) +{ + this->initializeAndTestCartesianMap({2, 3, 5}); + this->initializeAndTestCartesianMap({3, 5, 7}); +} + +//---------------------------------------------------------------------- +AXOM_TYPED_TEST(slam_bivariate_map_templated, constructAndTestRelationSet) +{ + this->initializeAndTestRelationMap(1); + this->initializeAndTestRelationMap(2); + this->initializeAndTestRelationMap(3); +} + +//---------------------------------------------------------------------- +AXOM_TYPED_TEST(slam_bivariate_map_templated, constructAndTestRelationSet3D) +{ + this->initializeAndTestRelationMap({2, 3, 5}); + this->initializeAndTestRelationMap({3, 5, 7}); +} + +} // namespace testing +//---------------------------------------------------------------------- + +int main(int argc, char* argv[]) +{ + ::testing::InitGoogleTest(&argc, argv); +#ifdef AXOM_DEBUG + // add this line to avoid a warning in the output about thread safety + ::testing::FLAGS_gtest_death_test_style = "threadsafe"; +#endif + + axom::slic::SimpleLogger logger(axom::slic::message::Info); + + int result = RUN_ALL_TESTS(); + + return result; +} From ca6b4d5ff3ba73dde200cf857d0597a91930fcb3 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 1 Jul 2026 18:18:59 -0700 Subject: [PATCH 614/986] slam: Adds device tests for std::optional in kernels --- src/axom/slam/docs/sphinx/portability.rst | 6 +- src/axom/slam/tests/CMakeLists.txt | 1 + .../tests/slam_set_BivariateSet_device.cpp | 194 ++++++++++++++++++ 3 files changed, 199 insertions(+), 2 deletions(-) create mode 100644 src/axom/slam/tests/slam_set_BivariateSet_device.cpp diff --git a/src/axom/slam/docs/sphinx/portability.rst b/src/axom/slam/docs/sphinx/portability.rst index 966b1173b6..2222663870 100644 --- a/src/axom/slam/docs/sphinx/portability.rst +++ b/src/axom/slam/docs/sphinx/portability.rst @@ -47,5 +47,7 @@ Slam uses ``std::optional`` for optional-returning APIs. The CUDA host-configs enable ``--expt-relaxed-constexpr``, and HIP's compiler accepts these standard library calls from host-device paths in the supported builds. -The contract is to check ``has_value()`` (or use ``value_or``) before dereferencing. - +The device-side contract is to check ``has_value()`` before dereferencing with +``operator*``, or to use ``value_or``. Avoid ``value()`` in kernels because +standard library implementations can route the disengaged case through a +throwing host-only helper. diff --git a/src/axom/slam/tests/CMakeLists.txt b/src/axom/slam/tests/CMakeLists.txt index 95e7cf320d..85f2929327 100644 --- a/src/axom/slam/tests/CMakeLists.txt +++ b/src/axom/slam/tests/CMakeLists.txt @@ -22,6 +22,7 @@ set(gtest_slam_tests slam_set_DynamicSet.cpp slam_set_Iterator.cpp slam_set_BivariateSet.cpp + slam_set_BivariateSet_device.cpp # test relations slam_relation_StaticVariable.cpp diff --git a/src/axom/slam/tests/slam_set_BivariateSet_device.cpp b/src/axom/slam/tests/slam_set_BivariateSet_device.cpp new file mode 100644 index 0000000000..f1b4d4ffe9 --- /dev/null +++ b/src/axom/slam/tests/slam_set_BivariateSet_device.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) + +/** + * \file slam_set_BivariateSet_device.cpp + * + * \brief Device-execution tests for the BivariateSet std::optional-returning queries + * and for std::optional itself inside device kernels. + * + * These accompany the host-side tests in slam_set_BivariateSet.cpp. + * The `AXOM_HOST_DEVICE findElementFlatIndexOptional(...)` wrappers construct a + * `std::optional` in device code, so this file exercises two things on device: + * + * 1. that `std::optional` is constructible and queryable inside a RAJA kernel + * 2. that `findElementFlatIndexOptional(...)` returns the correct value when evaluated in a kernel. + * + * The wrapper is only device-callable on the concrete (non-virtual) set instantiation (`ProductSet::ConcreteSet`). + * The virtual `BivariateSet` interface would require a device-resident vtable, + * whereas on a concrete set captured by value the wrapper inlines + * and its internal `findElementFlatIndex` call devirtualizes -- no vtable is touched. + */ + +#include "gtest/gtest.h" + +#include "axom/core/execution/execution_space.hpp" +#include "axom/slic.hpp" +#include "axom/slam.hpp" + +#include + +namespace +{ +namespace slam = axom::slam; + +using SetPosition = slam::DefaultPositionType; +using SetElement = slam::DefaultElementType; + +template +int getHostAccessibleAllocatorId() +{ +#ifdef AXOM_USE_UMPIRE + if(axom::execution_space::onDevice()) + { + return axom::detail::getAllocatorID(); + } +#endif + + return axom::execution_space::allocatorID(); +} + +//------------------------------------------------------------------------------ +template +class slam_set_bivariate_optional_device : public ::testing::Test +{ +public: + using ExecSpace = ExecutionSpace; + + // The device-usable (non-virtual) set instantiations. + using ConcreteSetType = typename slam::RangeSet::ConcreteSet; + using ProductSetType = typename slam::ProductSet::ConcreteSet; +}; + +using MyTypes = ::testing::Types< +#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) + axom::OMP_EXEC, +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) + axom::CUDA_EXEC<256>, +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_HIP) + axom::HIP_EXEC<256>, +#endif + axom::SEQ_EXEC>; + +TYPED_TEST_SUITE(slam_set_bivariate_optional_device, MyTypes); + +//------------------------------------------------------------------------------ +// std::optional is constructible and queryable in a device kernel. +//------------------------------------------------------------------------------ +AXOM_TYPED_TEST(slam_set_bivariate_optional_device, std_optional_in_kernel) +{ + using ExecSpace = typename TestFixture::ExecSpace; + const int allocatorId = getHostAccessibleAllocatorId(); + + constexpr int N = 8; + constexpr axom::IndexType SENTINEL = -1; + + axom::Array results(N, N, allocatorId); + axom::Array consistent(N, N, allocatorId); + auto results_v = results.view(); + auto consistent_v = consistent.view(); + + axom::for_all( + N, + AXOM_LAMBDA(int i) { + std::optional opt; + if(i % 2 == 0) + { + opt = std::optional(static_cast(i * 10)); + } + + const bool has = opt.has_value(); + const bool asBool = static_cast(opt); + const axom::IndexType viaValueOr = opt.value_or(SENTINEL); + + axom::IndexType decoded = SENTINEL; + if(has) + { + decoded = *opt; + } + results_v[i] = decoded; + + const bool ok = (has == asBool) && (has ? (viaValueOr == decoded) : (viaValueOr == SENTINEL)); + consistent_v[i] = ok ? 1 : 0; + }); + + for(int i = 0; i < N; ++i) + { + EXPECT_EQ(consistent[i], 1) << "std::optional query surface inconsistent on device at i=" << i; + const axom::IndexType expected = (i % 2 == 0) ? static_cast(i * 10) : SENTINEL; + EXPECT_EQ(results[i], expected); + } +} + +//------------------------------------------------------------------------------ +// findElementFlatIndexOptional on a concrete ProductSet, evaluated in a kernel. +// +// A ProductSet is dense: every (i, j) exists, so the returned optional must be +// engaged with FlatIndex == secondSetSize * i + j. The sets are placed in unified +// memory and the (pointer-holding) ProductSet is captured by value into the +// kernel, matching the BivariateMap device tests. +//------------------------------------------------------------------------------ +AXOM_TYPED_TEST(slam_set_bivariate_optional_device, product_set_flat_index_optional_in_kernel) +{ + using ExecSpace = typename TestFixture::ExecSpace; + using ConcreteSetType = typename TestFixture::ConcreteSetType; + using ProductSetType = typename TestFixture::ProductSetType; + + const int allocatorId = getHostAccessibleAllocatorId(); + + constexpr SetPosition SZ1 = 4; + constexpr SetPosition SZ2 = 5; + + // Sets must be reachable from device code. + // Place them in unified memory and point the ProductSet at them. + axom::Array sets(2, 2, allocatorId); + sets[0] = ConcreteSetType(SZ1); + sets[1] = ConcreteSetType(SZ2); + + ProductSetType prodSet(&sets[0], &sets[1]); + EXPECT_EQ(prodSet.size(), SZ1 * SZ2); + EXPECT_TRUE(prodSet.isValid()); + + const int totalSize = static_cast(prodSet.size()); + axom::Array ok(totalSize, totalSize, allocatorId); + auto ok_v = ok.view(); + + axom::for_all( + prodSet.firstSetSize(), + AXOM_LAMBDA(int i) { + const auto sz2 = prodSet.secondSetSize(); + for(int j = 0; j < sz2; ++j) + { + const std::optional flat = prodSet.findElementFlatIndexOptional(i, j); + const SetPosition expected = sz2 * i + j; + const bool good = flat.has_value() && (*flat == expected); + ok_v[i * sz2 + j] = good ? 1 : 0; + } + }); + + for(int idx = 0; idx < totalSize; ++idx) + { + EXPECT_EQ(ok[idx], 1) << "findElementFlatIndexOptional mismatch on device at flat index " << idx; + } +} + +} // end anonymous namespace + +//------------------------------------------------------------------------------ +int main(int argc, char* argv[]) +{ + int result = 0; + + ::testing::InitGoogleTest(&argc, argv); + + axom::slic::SimpleLogger logger; + + result = RUN_ALL_TESTS(); + + return result; +} From 71716275725f3347016a81fbdc5bc368044c7e05 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 1 Jul 2026 18:34:20 -0700 Subject: [PATCH 615/986] slam: Adds basic validity check to make_*_relation utility functions --- src/axom/slam/RelationBuilders.hpp | 84 +++++++++++++++++++++++ src/axom/slam/tests/slam_make_helpers.cpp | 31 ++++++++- 2 files changed, 114 insertions(+), 1 deletion(-) diff --git a/src/axom/slam/RelationBuilders.hpp b/src/axom/slam/RelationBuilders.hpp index 018fee110b..9e1003ec9b 100644 --- a/src/axom/slam/RelationBuilders.hpp +++ b/src/axom/slam/RelationBuilders.hpp @@ -42,11 +42,75 @@ #include "axom/slam/policies/IndirectionPolicies.hpp" #include "axom/core/ArrayView.hpp" +#include "axom/slic.hpp" #include namespace axom::slam { +namespace detail +{ +/// Number of from-set elements (null-safe). Reads only the set's size scalar. +template +inline axom::IndexType relation_from_size(const FromSet* fromSet) +{ + return fromSet ? static_cast(fromSet->size()) : axom::IndexType {0}; +} + +/*! + * \brief Debug-only check that the begins array backing a variable-cardinality relation is correctly sized. + * + * A variable relation stores one begin offset per from-set element plus a terminal, + * so \a begins must contain exactly `fromSet->size() + 1` entries. + * A shorter begins array leads to out-of-bounds row traversal. + * This check inspects only sizes, so it is safe for relations built over device-resident storage. + * Deeper validity (e.g. monotonicity of begins and the terminal offset relative to the index count) + * is left to StaticRelation::isValid(), which reads the buffers on the appropriate memory space. + * Asserts in debug builds; a no-op in release builds. + */ +template +inline void check_variable_relation_size(const FromSet* fromSet, + axom::IndexType AXOM_DEBUG_PARAM(beginsSize)) +{ +#ifdef AXOM_DEBUG + const axom::IndexType expected = relation_from_size(fromSet) + 1; + SLIC_ASSERT_MSG(beginsSize == expected, + "slam::make_variable_relation -- begins has " + << beginsSize << " entries, but the from-set (size " + << relation_from_size(fromSet) << ") requires exactly " << expected + << " (one begin offset per element plus a terminal)."); +#else + AXOM_UNUSED_VAR(fromSet); +#endif +} + +/*! + * \brief Debug-only check that the indices array backing a constant-cardinality + * relation (with stride \a stride) is correctly sized. + * + * A constant-cardinality relation indexes through `pos * stride`, so \a indices must + * contain exactly `fromSet->size() * stride` entries. + * This check inspects only sizes, so it is safe for device-resident storage. + * Asserts in debug builds; a no-op in release builds. + */ +template +inline void check_constant_relation_size(const FromSet* fromSet, + PosType AXOM_DEBUG_PARAM(stride), + axom::IndexType AXOM_DEBUG_PARAM(indicesSize)) +{ +#ifdef AXOM_DEBUG + const axom::IndexType expected = relation_from_size(fromSet) * static_cast(stride); + SLIC_ASSERT_MSG(indicesSize == expected, + "slam::make_constant_relation -- indices has " + << indicesSize << " entries, but the from-set (size " + << relation_from_size(fromSet) << ") with stride " << stride + << " requires exactly " << expected << "."); +#else + AXOM_UNUSED_VAR(fromSet); +#endif +} +} // namespace detail + /// \name Relation construction helpers /// \{ @@ -82,6 +146,7 @@ auto make_variable_relation(FromSet* fromSet, StaticRelation; using Builder = typename RelationType::RelationBuilder; + detail::check_variable_relation_size(fromSet, static_cast(begins.size())); return RelationType( Builder() .fromSet(fromSet) @@ -135,6 +200,7 @@ auto make_variable_relation(FromSet* fromSet, StaticRelation; using Builder = typename RelationType::RelationBuilder; + detail::check_variable_relation_size(fromSet, static_cast(beginsSize)); return RelationType( Builder() .fromSet(fromSet) @@ -184,6 +250,7 @@ auto make_variable_relation(FromSet* fromSet, StaticRelation; using Builder = typename RelationType::RelationBuilder; + detail::check_variable_relation_size(fromSet, static_cast(begins.size())); return RelationType( Builder() .fromSet(fromSet) @@ -233,6 +300,7 @@ auto make_variable_relation(FromSet* fromSet, StaticRelation; using Builder = typename RelationType::RelationBuilder; + detail::check_variable_relation_size(fromSet, static_cast(begins.size())); return RelationType( Builder() .fromSet(fromSet) @@ -278,6 +346,7 @@ auto make_constant_relation(FromSet* fromSet, ToSet* toSet, PosType stride, std: using Builder = typename RelationType::RelationBuilder; auto begins_builder = typename Builder::BeginsSetBuilder().stride(stride); + detail::check_constant_relation_size(fromSet, stride, static_cast(indices.size())); return RelationType(Builder() .fromSet(fromSet) .toSet(toSet) @@ -318,6 +387,7 @@ auto make_constant_relation(FromSet* fromSet, using Builder = typename RelationType::RelationBuilder; auto begins_builder = typename Builder::BeginsSetBuilder().stride(stride); + detail::check_constant_relation_size(fromSet, stride, static_cast(indicesSize)); return RelationType( Builder() .fromSet(fromSet) @@ -360,6 +430,7 @@ auto make_constant_relation(FromSet* fromSet, using Builder = typename RelationType::RelationBuilder; auto begins_builder = typename Builder::BeginsSetBuilder().stride(stride); + detail::check_constant_relation_size(fromSet, stride, static_cast(indices.size())); return RelationType( Builder() .fromSet(fromSet) @@ -399,6 +470,7 @@ auto make_constant_relation(FromSet* fromSet, ToSet* toSet, PosType stride, axom using Builder = typename RelationType::RelationBuilder; auto begins_builder = typename Builder::BeginsSetBuilder().stride(stride); + detail::check_constant_relation_size(fromSet, stride, static_cast(indices.size())); return RelationType(Builder() .fromSet(fromSet) .toSet(toSet) @@ -438,6 +510,9 @@ auto make_constant_relation_ct(FromSet* fromSet, ToSet* toSet, ElemType* indices using RelationType = StaticRelation; using Builder = typename RelationType::RelationBuilder; + detail::check_constant_relation_size(fromSet, + static_cast(STRIDE), + static_cast(indicesSize)); return RelationType(Builder().fromSet(fromSet).toSet(toSet).indices( typename Builder::IndicesSetBuilder().size(indicesSize).data(indices, indicesSize))); } @@ -471,6 +546,9 @@ auto make_constant_relation_ct(FromSet* fromSet, ToSet* toSet, std::vector; using Builder = typename RelationType::RelationBuilder; + detail::check_constant_relation_size(fromSet, + static_cast(STRIDE), + static_cast(indices.size())); return RelationType(Builder().fromSet(fromSet).toSet(toSet).indices( typename Builder::IndicesSetBuilder().size(static_cast(indices.size())).data(&indices))); } @@ -504,6 +582,9 @@ auto make_constant_relation_ct(FromSet* fromSet, ToSet* toSet, axom::ArrayView; using Builder = typename RelationType::RelationBuilder; + detail::check_constant_relation_size(fromSet, + static_cast(STRIDE), + static_cast(indices.size())); return RelationType(Builder().fromSet(fromSet).toSet(toSet).indices( typename Builder::IndicesSetBuilder().size(static_cast(indices.size())).data(indices))); } @@ -537,6 +618,9 @@ auto make_constant_relation_ct(FromSet* fromSet, ToSet* toSet, axom::Array; using Builder = typename RelationType::RelationBuilder; + detail::check_constant_relation_size(fromSet, + static_cast(STRIDE), + static_cast(indices.size())); return RelationType(Builder().fromSet(fromSet).toSet(toSet).indices( typename Builder::IndicesSetBuilder().size(static_cast(indices.size())).data(&indices))); } diff --git a/src/axom/slam/tests/slam_make_helpers.cpp b/src/axom/slam/tests/slam_make_helpers.cpp index 98912f51aa..fd45fe5626 100644 --- a/src/axom/slam/tests/slam_make_helpers.cpp +++ b/src/axom/slam/tests/slam_make_helpers.cpp @@ -231,12 +231,38 @@ TEST(slam_make_helpers, make_variable_relation_carray_rejects_short_begins_size) auto fromSet = slam::make_range_set(3); auto toSet = slam::make_range_set(5); + // begins claims 3 offsets for a size-3 from-set, but needs 4 + // make_variable_relation asserts this invariant at construction in debug builds + // in release the check compiles out and the malformed relation is instead caught by isValid(). Pos begins[4] = {0, 2, 3, 3}; Pos indices[3] = {1, 2, 3}; +#ifdef AXOM_DEBUG + EXPECT_DEATH_IF_SUPPORTED( + slam::make_variable_relation(&fromSet, &toSet, begins, Pos {3}, indices, Pos {3}), + ""); +#else auto rel = slam::make_variable_relation(&fromSet, &toSet, begins, Pos {3}, indices, Pos {3}); - EXPECT_FALSE(rel.isValid()); +#endif +} + +TEST(slam_make_helpers, make_constant_relation_rejects_undersized_indices) +{ + auto fromSet = slam::make_range_set(3); + auto toSet = slam::make_range_set(5); + + // A stride-2 constant relation over a size-3 from-set needs 6 indices but we supply 4 here. + // make_constant_relation asserts the exact size at construction in debug builds + // the check compiles out in release builds. + Pos indices[4] = {0, 1, 2, 3}; + +#ifdef AXOM_DEBUG + EXPECT_DEATH_IF_SUPPORTED(slam::make_constant_relation(&fromSet, &toSet, Pos {2}, indices, Pos {4}), + ""); +#else + SLIC_INFO("Skipped constant-relation size assertion check in release mode."); +#endif } TEST(slam_make_helpers, make_variable_relation_axom_array_buffers) @@ -548,6 +574,9 @@ int main(int argc, char* argv[]) ::testing::InitGoogleTest(&argc, argv); axom::slic::SimpleLogger logger; + // Construction-precondition tests below use death tests in debug builds. + ::testing::FLAGS_gtest_death_test_style = "threadsafe"; + result = RUN_ALL_TESTS(); return result; From 00960d4b11cc99c81d911a82554f67279d7ee385 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 1 Jul 2026 18:37:33 -0700 Subject: [PATCH 616/986] Minor docs update --- RELEASE-NOTES.md | 2 +- src/axom/slam/BivariateSet.hpp | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index a71cb3b46e..9bd01e645c 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -45,7 +45,7 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ - Inlet: Added the ability to have collections (array and dictionary) with variant values. - Inlet: Added the ability to have collections (array and dictionary) with variant user defined structures. - Core: Adds `AXOM_CONSTEXPR_ASSERT` macro for assertions that are usable within `constexpr` contexts -- Slam: Adds `make_*_set`, `make_*relation` and `make_map` helper functions for building sets, relations and maps +- Slam: Adds `make_*_set`, `make_*_relation` and `make_map` helper functions for building sets, relations and maps ### Removed - Bump: Removed `axom::bump::views::MultiBufferMaterialView`, which was a view type for an obsolete flavor of Blueprint matset. diff --git a/src/axom/slam/BivariateSet.hpp b/src/axom/slam/BivariateSet.hpp index 6072cb29d4..3a815534b5 100644 --- a/src/axom/slam/BivariateSet.hpp +++ b/src/axom/slam/BivariateSet.hpp @@ -136,7 +136,7 @@ class BivariateSet * \param pos2 The second set position. * \return The DenseIndex of the given element, or INVALID_POS if such * element is missing from the set. - * \pre 0 <= pos1 <= set1.size() && 0 <= pos2 <= size2.size() + * \pre 0 <= pos1 < set1.size() && 0 <= pos2 < set2.size() */ virtual PositionType findElementIndex(PositionType pos1, PositionType pos2) const = 0; @@ -163,7 +163,7 @@ class BivariateSet * \param pos2 The second set position. * * \return The element's FlatIndex - * \pre 0 <= pos1 <= set1.size() && 0 <= pos2 <= size2.size() + * \pre 0 <= pos1 < set1.size() && 0 <= pos2 < set2.size() */ AXOM_HOST_DEVICE virtual PositionType findElementFlatIndex(PositionType pos1, PositionType pos2) const = 0; @@ -192,7 +192,7 @@ class BivariateSet * \param pos1 The first set position. * * \return The found element's FlatIndex. - * \pre 0 <= pos1 <= set1.size() + * \pre 0 <= pos1 < set1.size() */ virtual PositionType findElementFlatIndex(PositionType pos1) const = 0; @@ -246,7 +246,7 @@ class BivariateSet /** * \brief Number of elements of the BivariateSet whose first index is \a pos * - * \pre 0 <= pos1 <= set1.size() + * \pre 0 <= pos1 < set1.size() */ virtual PositionType size(PositionType pos1) const = 0; //size of a row @@ -277,7 +277,7 @@ class BivariateSet * * \param s1 The first set index. * \return An OrderedSet containing the elements - * \pre 0 <= pos1 <= set1.size() + * \pre 0 <= pos1 < set1.size() */ virtual SubsetType getElements(PositionType s1) const = 0; From ef77db4032598d5fc3afe4d9c709f0d82c67574c Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 6 Jul 2026 12:32:20 -0700 Subject: [PATCH 617/986] Moved files --- src/axom/{klee => core/utilities}/Units.cpp | 0 src/axom/{klee => core/utilities}/Units.hpp | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/axom/{klee => core/utilities}/Units.cpp (100%) rename src/axom/{klee => core/utilities}/Units.hpp (100%) diff --git a/src/axom/klee/Units.cpp b/src/axom/core/utilities/Units.cpp similarity index 100% rename from src/axom/klee/Units.cpp rename to src/axom/core/utilities/Units.cpp diff --git a/src/axom/klee/Units.hpp b/src/axom/core/utilities/Units.hpp similarity index 100% rename from src/axom/klee/Units.hpp rename to src/axom/core/utilities/Units.hpp From 10b3d088bbe9004e94ec38eeef18891a5c627371 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 6 Jul 2026 12:44:20 -0700 Subject: [PATCH 618/986] Updated Units and moved tests --- src/axom/core/CMakeLists.txt | 2 + src/axom/core/tests/CMakeLists.txt | 1 + src/axom/core/tests/core_serial_main.cpp | 1 + src/axom/core/utilities/Units.cpp | 20 +--- src/axom/core/utilities/Units.hpp | 30 ++--- src/axom/klee/CMakeLists.txt | 3 +- src/axom/klee/io/IOUtil.cpp | 19 ++++ src/axom/klee/tests/CMakeLists.txt | 2 - src/axom/klee/tests/klee_units.cpp | 139 ----------------------- 9 files changed, 37 insertions(+), 180 deletions(-) delete mode 100644 src/axom/klee/tests/klee_units.cpp diff --git a/src/axom/core/CMakeLists.txt b/src/axom/core/CMakeLists.txt index a393c350da..168c0c59d2 100644 --- a/src/axom/core/CMakeLists.txt +++ b/src/axom/core/CMakeLists.txt @@ -34,6 +34,7 @@ set(core_headers utilities/StringUtilities.hpp utilities/System.hpp utilities/Timer.hpp + utilities/Units.hpp utilities/Utilities.hpp ## numerics @@ -108,6 +109,7 @@ set(core_sources utilities/FileUtilities.cpp utilities/StringUtilities.cpp utilities/System.cpp + utilities/Units.cpp utilities/Utilities.cpp ${PROJECT_BINARY_DIR}/axom/core/utilities/About.cpp diff --git a/src/axom/core/tests/CMakeLists.txt b/src/axom/core/tests/CMakeLists.txt index 5fb81e4c46..07c07abad0 100644 --- a/src/axom/core/tests/CMakeLists.txt +++ b/src/axom/core/tests/CMakeLists.txt @@ -36,6 +36,7 @@ set(core_serial_tests core_Path.hpp core_stack_array.hpp core_static_array.hpp + core_units.hpp numerics_determinants.hpp numerics_eigen_solve.hpp diff --git a/src/axom/core/tests/core_serial_main.cpp b/src/axom/core/tests/core_serial_main.cpp index af7d5e17f0..4f2ca0139a 100644 --- a/src/axom/core/tests/core_serial_main.cpp +++ b/src/axom/core/tests/core_serial_main.cpp @@ -27,6 +27,7 @@ #include "core_Path.hpp" #include "core_stack_array.hpp" #include "core_static_array.hpp" +#include "core_units.hpp" #ifndef AXOM_USE_MPI #include "core_types.hpp" diff --git a/src/axom/core/utilities/Units.cpp b/src/axom/core/utilities/Units.cpp index 6353f25051..bf7d0e87e3 100644 --- a/src/axom/core/utilities/Units.cpp +++ b/src/axom/core/utilities/Units.cpp @@ -4,17 +4,14 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#include "axom/inlet/Proxy.hpp" - -#include "axom/klee/Units.hpp" -#include "axom/klee/KleeError.hpp" +#include "axom/core/utilities/Units.hpp" #include #include namespace axom { -namespace klee +namespace utilities { namespace { @@ -29,7 +26,7 @@ struct LengthUnitHash }; } // namespace -LengthUnit parseLengthUnits(const std::string &unitsAsString, const std::string &path) +LengthUnit parseLengthUnits(const std::string &unitsAsString) { static const std::unordered_map UNITS_BY_NAME { {"km", LengthUnit::km}, @@ -53,17 +50,12 @@ LengthUnit parseLengthUnits(const std::string &unitsAsString, const std::string { std::string message = "Unrecognized units: "; message += unitsAsString; - throw KleeError({path, message}); + throw std::invalid_argument(message); } return iter->second; } -LengthUnit parseLengthUnits(const inlet::Proxy &unitsAsProxy) -{ - return parseLengthUnits(unitsAsProxy.get(), unitsAsProxy.name()); -} - double getConversionFactor(LengthUnit sourceUnits, LengthUnit targetUnits) { static const std::unordered_map CONVERSION_TO_CM { @@ -103,5 +95,5 @@ double convert(double sourceValue, LengthUnit sourceUnits, LengthUnit targetUnit return sourceValue * getConversionFactor(sourceUnits, targetUnits); } -} // namespace klee -} // namespace axom \ No newline at end of file +} // namespace utilities +} // namespace axom diff --git a/src/axom/core/utilities/Units.hpp b/src/axom/core/utilities/Units.hpp index bd9b736dda..8157fb4305 100644 --- a/src/axom/core/utilities/Units.hpp +++ b/src/axom/core/utilities/Units.hpp @@ -3,18 +3,14 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_KLEE_UNITS_HPP -#define AXOM_KLEE_UNITS_HPP +#ifndef AXOM_CORE_UTILITIES_UNITS_HPP +#define AXOM_CORE_UTILITIES_UNITS_HPP #include namespace axom { -namespace inlet -{ -class Proxy; -} -namespace klee +namespace utilities { /** * Units of length in which users can express lengths and in which client @@ -41,22 +37,10 @@ enum class LengthUnit * Convert a string to a LengthUnit. * * \param unitsAsString the units as a string - * \param path the Path where the length units were found in the document. - * Used for error reporting. - * \return the parsed units - * \throws KleeError if the string does not represent known - * units - */ -LengthUnit parseLengthUnits(const std::string &unitsAsString, const std::string &path); - -/** - * Convert a proxy to a LengthUnit. - * - * \param unitsAsProxy the units as a proxy * \return the parsed units - * \throws KleeError if the string does not represent known units + * \throws std::invalid_argument if the string does not represent known units */ -LengthUnit parseLengthUnits(const inlet::Proxy &unitsAsProxy); +LengthUnit parseLengthUnits(const std::string &unitsAsString); /** * Get the conversion factor to convert from the given source units to the target units. @@ -97,6 +81,6 @@ void convertAll(T &values, LengthUnit sourceUnits, LengthUnit targetUnits) } } -} // namespace klee +} // namespace utilities } // namespace axom -#endif // AXOM_KLEE_UNITS_HPP +#endif // AXOM_CORE_UTILITIES_UNITS_HPP diff --git a/src/axom/klee/CMakeLists.txt b/src/axom/klee/CMakeLists.txt index 875af45917..0bbfa9c175 100644 --- a/src/axom/klee/CMakeLists.txt +++ b/src/axom/klee/CMakeLists.txt @@ -33,7 +33,6 @@ set(klee_sources KleeError.cpp Shape.cpp ShapeSet.cpp - Units.cpp io/GeometryOperatorsIO.cpp io/IO.cpp io/IOUtil.cpp @@ -45,7 +44,7 @@ set(klee_sources axom_add_library(NAME klee SOURCES ${klee_sources} HEADERS ${klee_headers} ${klee_internal_headers} - DEPENDS_ON inlet + DEPENDS_ON core inlet FOLDER axom/klee ) diff --git a/src/axom/klee/io/IOUtil.cpp b/src/axom/klee/io/IOUtil.cpp index bbe3a0cd9a..b04bdce8ac 100644 --- a/src/axom/klee/io/IOUtil.cpp +++ b/src/axom/klee/io/IOUtil.cpp @@ -9,10 +9,29 @@ #include "axom/inlet.hpp" #include "axom/klee/KleeError.hpp" +#include + namespace axom { namespace klee { +LengthUnit parseLengthUnits(const std::string &unitsAsString, const std::string &path) +{ + try + { + return utilities::parseLengthUnits(unitsAsString); + } + catch(const std::invalid_argument &ex) + { + throw KleeError({path, ex.what()}); + } +} + +LengthUnit parseLengthUnits(const inlet::Proxy &unitsAsProxy) +{ + return parseLengthUnits(unitsAsProxy.get(), unitsAsProxy.name()); +} + namespace internal { std::vector toDoubleVector(inlet::Proxy const &field, diff --git a/src/axom/klee/tests/CMakeLists.txt b/src/axom/klee/tests/CMakeLists.txt index fbf86dcfa9..7a6e50259a 100644 --- a/src/axom/klee/tests/CMakeLists.txt +++ b/src/axom/klee/tests/CMakeLists.txt @@ -21,7 +21,6 @@ set(gtest_klee_tests klee_io_util.cpp klee_shape.cpp klee_shape_set.cpp - klee_units.cpp ) axom_add_library( @@ -44,4 +43,3 @@ foreach(test ${gtest_klee_tests}) axom_add_test(NAME ${test_name} COMMAND ${test_name}_test ) endforeach() - diff --git a/src/axom/klee/tests/klee_units.cpp b/src/axom/klee/tests/klee_units.cpp deleted file mode 100644 index 5a4c1ee09c..0000000000 --- a/src/axom/klee/tests/klee_units.cpp +++ /dev/null @@ -1,139 +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/klee/Units.hpp" -#include "axom/klee/KleeError.hpp" - -#include "gtest/gtest.h" -#include "gmock/gmock.h" - -namespace axom -{ -namespace klee -{ -using ::testing::DoubleEq; -using ::testing::ElementsAre; -using ::testing::HasSubstr; -using ::testing::Matches; - -struct Length -{ - double value; - LengthUnit units; -}; - -bool convertsTo(Length length1, Length length2) -{ - double length2InLength1Units = convert(length2.value, length2.units, length1.units); - return Matches(DoubleEq(length1.value))(length2InLength1Units); -} - -bool areEquivalent(Length length1, Length length2) -{ - return convertsTo(length1, length2) && convertsTo(length2, length1); -} - -TEST(Units, parseLengthUnits) -{ - EXPECT_EQ(LengthUnit::km, parseLengthUnits("km", Path {})); - EXPECT_EQ(LengthUnit::m, parseLengthUnits("m", Path {})); - EXPECT_EQ(LengthUnit::dm, parseLengthUnits("dm", Path {})); - EXPECT_EQ(LengthUnit::cm, parseLengthUnits("cm", Path {})); - EXPECT_EQ(LengthUnit::mm, parseLengthUnits("mm", Path {})); - EXPECT_EQ(LengthUnit::um, parseLengthUnits("um", Path {})); - EXPECT_EQ(LengthUnit::nm, parseLengthUnits("nm", Path {})); - EXPECT_EQ(LengthUnit::angstrom, parseLengthUnits("A", Path {})); - EXPECT_EQ(LengthUnit::miles, parseLengthUnits("miles", Path {})); - EXPECT_EQ(LengthUnit::feet, parseLengthUnits("ft", Path {})); - EXPECT_EQ(LengthUnit::feet, parseLengthUnits("feet", Path {})); - EXPECT_EQ(LengthUnit::inches, parseLengthUnits("in", Path {})); - EXPECT_EQ(LengthUnit::inches, parseLengthUnits("inches", Path {})); - EXPECT_EQ(LengthUnit::mils, parseLengthUnits("mils", Path {})); - - Path errorPath {"some/path"}; - try - { - parseLengthUnits("bad_units", errorPath); - FAIL() << "Should have thrown"; - } - catch(const KleeError &error) - { - ASSERT_EQ(1u, error.getErrors().size()); - EXPECT_THAT(error.getErrors()[0].message, HasSubstr("bad_units")); - EXPECT_EQ(errorPath, error.getErrors()[0].path); - } -} - -TEST(Units, getConversionFactor) -{ - EXPECT_DOUBLE_EQ(1000, getConversionFactor(LengthUnit::km, LengthUnit::m)); - EXPECT_DOUBLE_EQ(10, getConversionFactor(LengthUnit::m, LengthUnit::dm)); - EXPECT_DOUBLE_EQ(10, getConversionFactor(LengthUnit::dm, LengthUnit::cm)); - EXPECT_DOUBLE_EQ(10, getConversionFactor(LengthUnit::cm, LengthUnit::mm)); - EXPECT_DOUBLE_EQ(1000, getConversionFactor(LengthUnit::mm, LengthUnit::um)); - EXPECT_DOUBLE_EQ(1000, getConversionFactor(LengthUnit::um, LengthUnit::nm)); - EXPECT_DOUBLE_EQ(10, getConversionFactor(LengthUnit::nm, LengthUnit::angstrom)); - EXPECT_DOUBLE_EQ(2.54, getConversionFactor(LengthUnit::inches, LengthUnit::cm)); - EXPECT_DOUBLE_EQ(1000, getConversionFactor(LengthUnit::inches, LengthUnit::mils)); - EXPECT_DOUBLE_EQ(12, getConversionFactor(LengthUnit::feet, LengthUnit::inches)); - EXPECT_DOUBLE_EQ(5280, getConversionFactor(LengthUnit::miles, LengthUnit::feet)); - - EXPECT_DOUBLE_EQ(1, getConversionFactor(LengthUnit::miles, LengthUnit::miles)); - - EXPECT_THROW(getConversionFactor(LengthUnit::cm, LengthUnit::unspecified), std::invalid_argument); - EXPECT_THROW(getConversionFactor(LengthUnit::unspecified, LengthUnit::cm), std::invalid_argument); -} - -TEST(Units, convert_adjacent) -{ - EXPECT_TRUE(areEquivalent({1, LengthUnit::km}, {1000, LengthUnit::m})); - EXPECT_TRUE(areEquivalent({1, LengthUnit::m}, {10, LengthUnit::dm})); - EXPECT_TRUE(areEquivalent({1, LengthUnit::dm}, {10, LengthUnit::cm})); - EXPECT_TRUE(areEquivalent({1, LengthUnit::cm}, {10, LengthUnit::mm})); - EXPECT_TRUE(areEquivalent({1, LengthUnit::mm}, {1000, LengthUnit::um})); - EXPECT_TRUE(areEquivalent({1, LengthUnit::um}, {1000, LengthUnit::nm})); - EXPECT_TRUE(areEquivalent({1, LengthUnit::nm}, {10, LengthUnit::angstrom})); - EXPECT_TRUE(areEquivalent({1, LengthUnit::inches}, {2.54, LengthUnit::cm})); - EXPECT_TRUE(areEquivalent({1, LengthUnit::inches}, {1000, LengthUnit::mils})); - EXPECT_TRUE(areEquivalent({1, LengthUnit::feet}, {12, LengthUnit::inches})); - EXPECT_TRUE(areEquivalent({1, LengthUnit::miles}, {5280, LengthUnit::feet})); -} - -TEST(Units, convert_all) -{ - Length equivalentLengths[] = { - {0.254254e-3, LengthUnit::km}, - {0.254254, LengthUnit::m}, - {0.254254e1, LengthUnit::dm}, - {0.254254e2, LengthUnit::cm}, - {0.254254e3, LengthUnit::mm}, - {0.254254e6, LengthUnit::um}, - {0.254254e9, LengthUnit::nm}, - {0.254254e10, LengthUnit::angstrom}, - {10.01, LengthUnit::inches}, - {10.01e3, LengthUnit::mils}, - {10.01 / 12, LengthUnit::feet}, - {10.01 / 12 / 5280, LengthUnit::miles}, - }; - - for(auto source : equivalentLengths) - { - for(auto target : equivalentLengths) - { - EXPECT_TRUE(areEquivalent(source, target)); - } - } -} - -TEST(Units, convertAll) -{ - std::vector values {1.0, 2.0, 3.0}; - convertAll(values, LengthUnit::m, LengthUnit::cm); - EXPECT_THAT(values, ElementsAre(100, 200, 300)); -} - -} // namespace klee -} // namespace axom \ No newline at end of file From 21c8755b095c000989fa6254ef62fb1a2bdc4da9 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 6 Jul 2026 13:59:59 -0700 Subject: [PATCH 619/986] Change C2CReader so it uses axom::core units code. --- src/axom/quest/io/C2CReader.cpp | 46 ++++++++++++++++++++++- src/axom/quest/io/C2CReader.hpp | 6 +-- src/axom/quest/tests/quest_c2c_reader.cpp | 42 +++++++++++++++++++++ 3 files changed, 90 insertions(+), 4 deletions(-) diff --git a/src/axom/quest/io/C2CReader.cpp b/src/axom/quest/io/C2CReader.cpp index d9f05c83f0..bc6d269df9 100644 --- a/src/axom/quest/io/C2CReader.cpp +++ b/src/axom/quest/io/C2CReader.cpp @@ -15,7 +15,10 @@ #include "axom/primal.hpp" #include "axom/fmt.hpp" +#include "c2c/C2C.hpp" + #include +#include #include #include @@ -23,9 +26,49 @@ namespace axom { namespace quest { +namespace +{ +c2c::LengthUnit toC2CLengthUnit(utilities::LengthUnit unit) +{ + switch(unit) + { + case utilities::LengthUnit::km: + return c2c::LengthUnit::km; + case utilities::LengthUnit::m: + return c2c::LengthUnit::m; + case utilities::LengthUnit::cm: + return c2c::LengthUnit::cm; + case utilities::LengthUnit::mm: + return c2c::LengthUnit::mm; + case utilities::LengthUnit::um: + return c2c::LengthUnit::um; + case utilities::LengthUnit::miles: + return c2c::LengthUnit::miles; + case utilities::LengthUnit::feet: + return c2c::LengthUnit::ft; + case utilities::LengthUnit::inches: + return c2c::LengthUnit::in; + case utilities::LengthUnit::mils: + return c2c::LengthUnit::mils; + case utilities::LengthUnit::dm: + case utilities::LengthUnit::nm: + case utilities::LengthUnit::angstrom: + case utilities::LengthUnit::unspecified: + throw std::invalid_argument("Length unit is not supported by c2c"); + } + + throw std::invalid_argument("Unknown length unit"); +} +} // namespace void C2CReader::clear() { m_nurbsData.clear(); } +void C2CReader::setLengthUnit(utilities::LengthUnit lengthUnit) +{ + toC2CLengthUnit(lengthUnit); + m_lengthUnit = lengthUnit; +} + int C2CReader::read() { SLIC_WARNING_IF(m_fileName.empty(), "Missing a filename in C2CReader::read()"); @@ -58,6 +101,7 @@ int C2CReader::readContour() using PointType = primal::Point; c2c::Contour contour = c2c::parseContour(m_fileName); + const c2c::LengthUnit c2cLengthUnit = toC2CLengthUnit(m_lengthUnit); SLIC_INFO(fmt::format("Loading contour with {} pieces", contour.getPieces().size())); @@ -68,7 +112,7 @@ int C2CReader::readContour() int piece_index = 0; for(auto* piece : contour.getPieces()) { - const auto nurbsData = c2c::toNurbs(*piece, m_lengthUnit); + const auto nurbsData = c2c::toNurbs(*piece, c2cLengthUnit); // Load control points axom::Array controlPoints; diff --git a/src/axom/quest/io/C2CReader.hpp b/src/axom/quest/io/C2CReader.hpp index 6901333e52..09e99ec441 100644 --- a/src/axom/quest/io/C2CReader.hpp +++ b/src/axom/quest/io/C2CReader.hpp @@ -15,9 +15,9 @@ #include "axom/core/Array.hpp" #include "axom/core/ArrayView.hpp" +#include "axom/core/utilities/Units.hpp" #include "axom/mint.hpp" #include "axom/primal.hpp" -#include "c2c/C2C.hpp" #include #include @@ -49,7 +49,7 @@ class C2CReader void setFileName(const std::string &fileName) { m_fileName = fileName; } /// Sets the length unit. All lengths will be converted to this unit when reading the mesh - void setLengthUnit(c2c::LengthUnit lengthUnit) { m_lengthUnit = lengthUnit; } + void setLengthUnit(utilities::LengthUnit lengthUnit); /// Clears data associated with this reader void clear(); @@ -76,7 +76,7 @@ class C2CReader protected: std::string m_fileName; - c2c::LengthUnit m_lengthUnit {c2c::LengthUnit::cm}; + utilities::LengthUnit m_lengthUnit {utilities::LengthUnit::cm}; CurveArray m_nurbsData; }; diff --git a/src/axom/quest/tests/quest_c2c_reader.cpp b/src/axom/quest/tests/quest_c2c_reader.cpp index 35dd2ddf33..2831edf1d6 100644 --- a/src/axom/quest/tests/quest_c2c_reader.cpp +++ b/src/axom/quest/tests/quest_c2c_reader.cpp @@ -26,12 +26,14 @@ #include #include #include +#include #include // namespace aliases namespace mint = axom::mint; namespace primal = axom::primal; namespace quest = axom::quest; +namespace utilities = axom::utilities; namespace { @@ -99,6 +101,15 @@ void writeSpline(const std::string& filename) c2cFile << "piece = line(end=spline_start)" << std::endl; } +TEST(quest_c2c_reader, unsupported_length_units) +{ + quest::C2CReader reader; + EXPECT_THROW(reader.setLengthUnit(utilities::LengthUnit::dm), std::invalid_argument); + EXPECT_THROW(reader.setLengthUnit(utilities::LengthUnit::nm), std::invalid_argument); + EXPECT_THROW(reader.setLengthUnit(utilities::LengthUnit::angstrom), std::invalid_argument); + EXPECT_THROW(reader.setLengthUnit(utilities::LengthUnit::unspecified), std::invalid_argument); +} + TEST(quest_c2c_reader, basic_read) { const std::string fileName = C2C_CIRCLE_FILENAME; @@ -157,6 +168,37 @@ TEST(quest_c2c_reader, interpolate_circle) delete mesh; } +TEST(quest_c2c_reader, read_with_axom_length_unit) +{ + const std::string fileName = C2C_CIRCLE_FILENAME; + writeSimpleCircle(fileName); + + quest::C2CReader reader; + reader.setFileName(fileName); + reader.setLengthUnit(utilities::LengthUnit::mm); + + EXPECT_EQ(0, reader.read()); + + constexpr int DIM = 2; + using MeshType = mint::UnstructuredMesh; + MeshType* mesh = new MeshType(DIM, mint::SEGMENT); + + const int segmentsPerKnotSpan = 25; + axom::quest::LinearizeCurves lin; + lin.getLinearMeshUniform(reader.getCurvesView(), mesh, segmentsPerKnotSpan); + + double* x = mesh->getCoordinateArray(mint::X_COORDINATE); + double* y = mesh->getCoordinateArray(mint::Y_COORDINATE); + const int numPts = mesh->getNumberOfNodes(); + for(int i = 0; i < numPts; ++i) + { + double mag = primal::Vector {x[i], y[i]}.norm(); + EXPECT_DOUBLE_EQ(10., mag); + } + + delete mesh; +} + TEST(quest_c2c_reader, interpolate_square) { const std::string fileName = C2C_SQUARE_FILENAME; From 9f52676667fcd7df6ea65b5efb3052b793745fc5 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 6 Jul 2026 14:30:34 -0700 Subject: [PATCH 620/986] Refactor unit conversion --- src/axom/core/tests/core_units.hpp | 190 ++++++++++++++++++++++ src/axom/core/utilities/Units.cpp | 163 +++++++++++++++++-- src/axom/core/utilities/Units.hpp | 34 ++++ src/axom/quest/io/C2CReader.cpp | 5 + src/axom/quest/io/STEPReader.cpp | 86 +--------- src/axom/quest/tests/quest_c2c_reader.cpp | 5 + 6 files changed, 382 insertions(+), 101 deletions(-) create mode 100644 src/axom/core/tests/core_units.hpp diff --git a/src/axom/core/tests/core_units.hpp b/src/axom/core/tests/core_units.hpp new file mode 100644 index 0000000000..9be54e6dc0 --- /dev/null +++ b/src/axom/core/tests/core_units.hpp @@ -0,0 +1,190 @@ +// 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/core/utilities/Units.hpp" + +#include "gtest/gtest.h" + +#include +#include +#include + +namespace axom +{ +namespace utilities +{ +struct Length +{ + double value; + LengthUnit units; +}; + +void expectConvertsTo(Length length1, Length length2) +{ + double length2InLength1Units = convert(length2.value, length2.units, length1.units); + EXPECT_DOUBLE_EQ(length1.value, length2InLength1Units); +} + +void expectEquivalent(Length length1, Length length2) +{ + expectConvertsTo(length1, length2); + expectConvertsTo(length2, length1); +} + +TEST(Units, parseLengthUnits) +{ + EXPECT_EQ(LengthUnit::am, parseLengthUnits("attometer")); + EXPECT_EQ(LengthUnit::fm, parseLengthUnits("femtometre")); + EXPECT_EQ(LengthUnit::pm, parseLengthUnits("picometers")); + EXPECT_EQ(LengthUnit::km, parseLengthUnits("km")); + EXPECT_EQ(LengthUnit::hm, parseLengthUnits("hectometer")); + EXPECT_EQ(LengthUnit::dam, parseLengthUnits("decametre")); + EXPECT_EQ(LengthUnit::m, parseLengthUnits("m")); + EXPECT_EQ(LengthUnit::dm, parseLengthUnits("dm")); + EXPECT_EQ(LengthUnit::cm, parseLengthUnits("cm")); + EXPECT_EQ(LengthUnit::mm, parseLengthUnits("mm")); + EXPECT_EQ(LengthUnit::um, parseLengthUnits("um")); + EXPECT_EQ(LengthUnit::nm, parseLengthUnits("nm")); + EXPECT_EQ(LengthUnit::angstrom, parseLengthUnits("A")); + EXPECT_EQ(LengthUnit::miles, parseLengthUnits("miles")); + EXPECT_EQ(LengthUnit::feet, parseLengthUnits("ft")); + EXPECT_EQ(LengthUnit::feet, parseLengthUnits("feet")); + EXPECT_EQ(LengthUnit::inches, parseLengthUnits("in")); + EXPECT_EQ(LengthUnit::inches, parseLengthUnits("inches")); + EXPECT_EQ(LengthUnit::mils, parseLengthUnits("mils")); + + try + { + parseLengthUnits("bad_units"); + FAIL() << "Should have thrown"; + } + catch(const std::invalid_argument &error) + { + EXPECT_NE(std::string::npos, std::string(error.what()).find("bad_units")); + } +} + +TEST(Units, getConversionFactor) +{ + EXPECT_DOUBLE_EQ(1000, getConversionFactor(LengthUnit::fm, LengthUnit::am)); + EXPECT_DOUBLE_EQ(1000, getConversionFactor(LengthUnit::pm, LengthUnit::fm)); + EXPECT_DOUBLE_EQ(1000, getConversionFactor(LengthUnit::nm, LengthUnit::pm)); + EXPECT_DOUBLE_EQ(1000, getConversionFactor(LengthUnit::km, LengthUnit::m)); + EXPECT_DOUBLE_EQ(10, getConversionFactor(LengthUnit::km, LengthUnit::hm)); + EXPECT_DOUBLE_EQ(10, getConversionFactor(LengthUnit::hm, LengthUnit::dam)); + EXPECT_DOUBLE_EQ(10, getConversionFactor(LengthUnit::dam, LengthUnit::m)); + EXPECT_DOUBLE_EQ(10, getConversionFactor(LengthUnit::m, LengthUnit::dm)); + EXPECT_DOUBLE_EQ(10, getConversionFactor(LengthUnit::dm, LengthUnit::cm)); + EXPECT_DOUBLE_EQ(10, getConversionFactor(LengthUnit::cm, LengthUnit::mm)); + EXPECT_DOUBLE_EQ(1000, getConversionFactor(LengthUnit::mm, LengthUnit::um)); + EXPECT_DOUBLE_EQ(1000, getConversionFactor(LengthUnit::um, LengthUnit::nm)); + EXPECT_DOUBLE_EQ(10, getConversionFactor(LengthUnit::nm, LengthUnit::angstrom)); + EXPECT_DOUBLE_EQ(2.54, getConversionFactor(LengthUnit::inches, LengthUnit::cm)); + EXPECT_DOUBLE_EQ(1000, getConversionFactor(LengthUnit::inches, LengthUnit::mils)); + EXPECT_DOUBLE_EQ(12, getConversionFactor(LengthUnit::feet, LengthUnit::inches)); + EXPECT_DOUBLE_EQ(5280, getConversionFactor(LengthUnit::miles, LengthUnit::feet)); + + EXPECT_DOUBLE_EQ(1, getConversionFactor(LengthUnit::miles, LengthUnit::miles)); + + EXPECT_THROW(getConversionFactor(LengthUnit::cm, LengthUnit::unspecified), std::invalid_argument); + EXPECT_THROW(getConversionFactor(LengthUnit::unspecified, LengthUnit::cm), std::invalid_argument); +} + +TEST(Units, getCanonicalUnit) +{ + EXPECT_EQ(LengthUnit::cm, getCanonicalUnit("centimeter")); + EXPECT_EQ(LengthUnit::m, getCanonicalUnit("METRES")); + EXPECT_EQ(LengthUnit::feet, getCanonicalUnit("feet")); + EXPECT_EQ(LengthUnit::miles, getCanonicalUnit("mi")); + EXPECT_EQ(LengthUnit::fm, getCanonicalUnit("femtometres")); + + EXPECT_THROW(getCanonicalUnit("bad_units"), std::invalid_argument); +} + +TEST(Units, getCanonicalUnitName) +{ + EXPECT_EQ("cm", getCanonicalUnitName("centimeter")); + EXPECT_EQ("um", getCanonicalUnitName("micrometers")); + EXPECT_EQ("dam", getCanonicalUnitName("decametre")); + EXPECT_EQ("fm", getCanonicalUnitName("femtometer")); + EXPECT_EQ("ft", getCanonicalUnitName("feet")); + EXPECT_EQ("miles", getCanonicalUnitName("mi")); +} + +TEST(Units, getConversionFactor_string) +{ + EXPECT_DOUBLE_EQ(10, getConversionFactor("centimeter", "mm")); + EXPECT_DOUBLE_EQ(25.4, getConversionFactor("inch", "millimeter")); + EXPECT_DOUBLE_EQ(1000, getConversionFactor("kilometer", "m")); + EXPECT_DOUBLE_EQ(5280, getConversionFactor("mile", "ft")); + EXPECT_DOUBLE_EQ(1e-3, getConversionFactor("femtometer", "pm")); + + EXPECT_THROW(getConversionFactor("cm", "bad_units"), std::invalid_argument); +} + +TEST(Units, convert_adjacent) +{ + expectEquivalent({1, LengthUnit::km}, {1000, LengthUnit::m}); + expectEquivalent({1, LengthUnit::hm}, {100, LengthUnit::m}); + expectEquivalent({1, LengthUnit::dam}, {10, LengthUnit::m}); + expectEquivalent({1, LengthUnit::m}, {10, LengthUnit::dm}); + expectEquivalent({1, LengthUnit::dm}, {10, LengthUnit::cm}); + expectEquivalent({1, LengthUnit::cm}, {10, LengthUnit::mm}); + expectEquivalent({1, LengthUnit::mm}, {1000, LengthUnit::um}); + expectEquivalent({1, LengthUnit::um}, {1000, LengthUnit::nm}); + expectEquivalent({1, LengthUnit::nm}, {1000, LengthUnit::pm}); + expectEquivalent({1, LengthUnit::pm}, {1000, LengthUnit::fm}); + expectEquivalent({1, LengthUnit::fm}, {1000, LengthUnit::am}); + expectEquivalent({1, LengthUnit::nm}, {10, LengthUnit::angstrom}); + expectEquivalent({1, LengthUnit::inches}, {2.54, LengthUnit::cm}); + expectEquivalent({1, LengthUnit::inches}, {1000, LengthUnit::mils}); + expectEquivalent({1, LengthUnit::feet}, {12, LengthUnit::inches}); + expectEquivalent({1, LengthUnit::miles}, {5280, LengthUnit::feet}); +} + +TEST(Units, convert_all) +{ + Length equivalentLengths[] = { + {0.254254e-3, LengthUnit::km}, + {0.254254e-2, LengthUnit::hm}, + {0.254254e-1, LengthUnit::dam}, + {0.254254, LengthUnit::m}, + {0.254254e1, LengthUnit::dm}, + {0.254254e2, LengthUnit::cm}, + {0.254254e3, LengthUnit::mm}, + {0.254254e6, LengthUnit::um}, + {0.254254e9, LengthUnit::nm}, + {0.254254e12, LengthUnit::pm}, + {0.254254e15, LengthUnit::fm}, + {0.254254e18, LengthUnit::am}, + {0.254254e10, LengthUnit::angstrom}, + {10.01, LengthUnit::inches}, + {10.01e3, LengthUnit::mils}, + {10.01 / 12, LengthUnit::feet}, + {10.01 / 12 / 5280, LengthUnit::miles}, + }; + + for(auto source : equivalentLengths) + { + for(auto target : equivalentLengths) + { + expectEquivalent(source, target); + } + } +} + +TEST(Units, convertAll) +{ + std::vector values {1.0, 2.0, 3.0}; + convertAll(values, LengthUnit::m, LengthUnit::cm); + ASSERT_EQ(3u, values.size()); + EXPECT_DOUBLE_EQ(100, values[0]); + EXPECT_DOUBLE_EQ(200, values[1]); + EXPECT_DOUBLE_EQ(300, values[2]); +} + +} // namespace utilities +} // namespace axom diff --git a/src/axom/core/utilities/Units.cpp b/src/axom/core/utilities/Units.cpp index bf7d0e87e3..65e5764cb3 100644 --- a/src/axom/core/utilities/Units.cpp +++ b/src/axom/core/utilities/Units.cpp @@ -6,7 +6,10 @@ #include "axom/core/utilities/Units.hpp" +#include +#include #include +#include #include namespace axom @@ -24,42 +27,161 @@ struct LengthUnitHash { std::size_t operator()(LengthUnit unit) const { return static_cast(unit); } }; + +std::string toLower(std::string str) +{ + std::transform(str.begin(), str.end(), str.begin(), [](unsigned char ch) { + return static_cast(std::tolower(ch)); + }); + return str; +} + +std::string unrecognizedUnitsMessage(const std::string &unitsAsString) +{ + std::string message = "Unrecognized units: "; + message += unitsAsString; + return message; +} } // namespace LengthUnit parseLengthUnits(const std::string &unitsAsString) { + return getCanonicalUnit(unitsAsString); +} + +LengthUnit getCanonicalUnit(const std::string &unit) +{ + const std::string lowerUnit = toLower(unit); + static const std::unordered_map UNITS_BY_NAME { - {"km", LengthUnit::km}, - {"m", LengthUnit::m}, - {"dm", LengthUnit::dm}, - {"cm", LengthUnit::cm}, - {"mm", LengthUnit::mm}, - {"um", LengthUnit::um}, - {"nm", LengthUnit::nm}, - {"A", LengthUnit::angstrom}, - {"miles", LengthUnit::miles}, - {"ft", LengthUnit::feet}, - {"feet", LengthUnit::feet}, - {"in", LengthUnit::inches}, + {"inch", LengthUnit::inches}, {"inches", LengthUnit::inches}, - {"mils", LengthUnit::mils}}; - - auto iter = UNITS_BY_NAME.find(unitsAsString); + {"in", LengthUnit::inches}, + {"foot", LengthUnit::feet}, + {"feet", LengthUnit::feet}, + {"ft", LengthUnit::feet}, + {"mile", LengthUnit::miles}, + {"miles", LengthUnit::miles}, + {"mi", LengthUnit::miles}, + {"mil", LengthUnit::mils}, + {"mils", LengthUnit::mils}, + {"a", LengthUnit::angstrom}, + {"angstrom", LengthUnit::angstrom}, + {"angstroms", LengthUnit::angstrom}, + {"am", LengthUnit::am}, + {"attometer", LengthUnit::am}, + {"attometers", LengthUnit::am}, + {"attometre", LengthUnit::am}, + {"attometres", LengthUnit::am}, + {"fm", LengthUnit::fm}, + {"femtometer", LengthUnit::fm}, + {"femtometers", LengthUnit::fm}, + {"femtometre", LengthUnit::fm}, + {"femtometres", LengthUnit::fm}, + {"pm", LengthUnit::pm}, + {"picometer", LengthUnit::pm}, + {"picometers", LengthUnit::pm}, + {"picometre", LengthUnit::pm}, + {"picometres", LengthUnit::pm}, + {"nm", LengthUnit::nm}, + {"nanometer", LengthUnit::nm}, + {"nanometers", LengthUnit::nm}, + {"nanometre", LengthUnit::nm}, + {"nanometres", LengthUnit::nm}, + {"um", LengthUnit::um}, + {"micrometer", LengthUnit::um}, + {"micrometers", LengthUnit::um}, + {"micrometre", LengthUnit::um}, + {"micrometres", LengthUnit::um}, + {"micron", LengthUnit::um}, + {"microns", LengthUnit::um}, + {"mm", LengthUnit::mm}, + {"millimeter", LengthUnit::mm}, + {"millimeters", LengthUnit::mm}, + {"millimetre", LengthUnit::mm}, + {"millimetres", LengthUnit::mm}, + {"cm", LengthUnit::cm}, + {"centimeter", LengthUnit::cm}, + {"centimeters", LengthUnit::cm}, + {"centimetre", LengthUnit::cm}, + {"centimetres", LengthUnit::cm}, + {"dm", LengthUnit::dm}, + {"decimeter", LengthUnit::dm}, + {"decimeters", LengthUnit::dm}, + {"decimetre", LengthUnit::dm}, + {"decimetres", LengthUnit::dm}, + {"m", LengthUnit::m}, + {"meter", LengthUnit::m}, + {"meters", LengthUnit::m}, + {"metre", LengthUnit::m}, + {"metres", LengthUnit::m}, + {"dam", LengthUnit::dam}, + {"decameter", LengthUnit::dam}, + {"decameters", LengthUnit::dam}, + {"decametre", LengthUnit::dam}, + {"decametres", LengthUnit::dam}, + {"hm", LengthUnit::hm}, + {"hectometer", LengthUnit::hm}, + {"hectometers", LengthUnit::hm}, + {"hectometre", LengthUnit::hm}, + {"hectometres", LengthUnit::hm}, + {"km", LengthUnit::km}, + {"kilometer", LengthUnit::km}, + {"kilometers", LengthUnit::km}, + {"kilometre", LengthUnit::km}, + {"kilometres", LengthUnit::km}, + }; + auto iter = UNITS_BY_NAME.find(lowerUnit); if(iter == UNITS_BY_NAME.end()) { - std::string message = "Unrecognized units: "; - message += unitsAsString; - throw std::invalid_argument(message); + throw std::invalid_argument(unrecognizedUnitsMessage(unit)); } return iter->second; } +std::string getCanonicalUnitName(const std::string &unit) +{ + static const std::unordered_map UNIT_NAMES { + {LengthUnit::am, "am"}, + {LengthUnit::fm, "fm"}, + {LengthUnit::pm, "pm"}, + {LengthUnit::nm, "nm"}, + {LengthUnit::um, "um"}, + {LengthUnit::mm, "mm"}, + {LengthUnit::cm, "cm"}, + {LengthUnit::dm, "dm"}, + {LengthUnit::m, "m"}, + {LengthUnit::dam, "dam"}, + {LengthUnit::hm, "hm"}, + {LengthUnit::km, "km"}, + {LengthUnit::miles, "miles"}, + {LengthUnit::feet, "ft"}, + {LengthUnit::inches, "in"}, + {LengthUnit::mils, "mils"}, + {LengthUnit::angstrom, "A"}, + {LengthUnit::unspecified, "unspecified"}}; + + auto canonicalUnit = getCanonicalUnit(unit); + + auto iter = UNIT_NAMES.find(canonicalUnit); + if(iter == UNIT_NAMES.end()) + { + throw std::invalid_argument(unrecognizedUnitsMessage(unit)); + } + return iter->second; +} + double getConversionFactor(LengthUnit sourceUnits, LengthUnit targetUnits) { static const std::unordered_map CONVERSION_TO_CM { + {LengthUnit::am, 1e-16}, + {LengthUnit::fm, 1e-13}, + {LengthUnit::pm, 1e-10}, {LengthUnit::km, 1e5}, + {LengthUnit::hm, 1e4}, + {LengthUnit::dam, 1e3}, {LengthUnit::m, 1e2}, {LengthUnit::dm, 1e1}, {LengthUnit::cm, 1.0}, @@ -95,5 +217,10 @@ double convert(double sourceValue, LengthUnit sourceUnits, LengthUnit targetUnit return sourceValue * getConversionFactor(sourceUnits, targetUnits); } +double getConversionFactor(const std::string &sourceUnits, const std::string &targetUnits) +{ + return getConversionFactor(getCanonicalUnit(sourceUnits), getCanonicalUnit(targetUnits)); +} + } // namespace utilities } // namespace axom diff --git a/src/axom/core/utilities/Units.hpp b/src/axom/core/utilities/Units.hpp index 8157fb4305..7d5cd0344e 100644 --- a/src/axom/core/utilities/Units.hpp +++ b/src/axom/core/utilities/Units.hpp @@ -18,7 +18,12 @@ namespace utilities */ enum class LengthUnit { + am, + fm, + pm, km, + hm, + dam, m, dm, cm, @@ -42,6 +47,24 @@ enum class LengthUnit */ LengthUnit parseLengthUnits(const std::string &unitsAsString); +/** + * Get the canonical representation of a length unit string. + * + * \param unit the unit as a string + * \return the canonical unit + * \throws std::invalid_argument if the string does not represent known units + */ +LengthUnit getCanonicalUnit(const std::string &unit); + +/** + * Get the canonical short name of a length unit string. + * + * \param unit the unit as a string + * \return the canonical short unit name + * \throws std::invalid_argument if the string does not represent known units + */ +std::string getCanonicalUnitName(const std::string &unit); + /** * Get the conversion factor to convert from the given source units to the target units. * @@ -52,6 +75,17 @@ LengthUnit parseLengthUnits(const std::string &unitsAsString); */ double getConversionFactor(LengthUnit sourceUnits, LengthUnit targetUnits); +/** + * Get the conversion factor to convert from the given source units to the target units. + * + * \param sourceUnits the original units as a string + * \param targetUnits the target units as a string + * \return the value by which to multiply lengths in the original units + * to get the target units + * \throws std::invalid_argument if either string does not represent known units + */ +double getConversionFactor(const std::string &sourceUnits, const std::string &targetUnits); + /** * Convert a value from one set of units to another. * diff --git a/src/axom/quest/io/C2CReader.cpp b/src/axom/quest/io/C2CReader.cpp index bc6d269df9..794ab53402 100644 --- a/src/axom/quest/io/C2CReader.cpp +++ b/src/axom/quest/io/C2CReader.cpp @@ -51,6 +51,11 @@ c2c::LengthUnit toC2CLengthUnit(utilities::LengthUnit unit) case utilities::LengthUnit::mils: return c2c::LengthUnit::mils; case utilities::LengthUnit::dm: + case utilities::LengthUnit::hm: + case utilities::LengthUnit::dam: + case utilities::LengthUnit::am: + case utilities::LengthUnit::fm: + case utilities::LengthUnit::pm: case utilities::LengthUnit::nm: case utilities::LengthUnit::angstrom: case utilities::LengthUnit::unspecified: diff --git a/src/axom/quest/io/STEPReader.cpp b/src/axom/quest/io/STEPReader.cpp index 9e1aeaec7a..37d735c9bf 100644 --- a/src/axom/quest/io/STEPReader.cpp +++ b/src/axom/quest/io/STEPReader.cpp @@ -13,6 +13,7 @@ #include "axom/slic.hpp" #include "axom/fmt.hpp" +#include "axom/core/utilities/Units.hpp" #include "opencascade/BRep_Tool.hxx" #include "opencascade/BRepAdaptor_Curve.hxx" @@ -966,87 +967,6 @@ class StepFileProcessor std::string getFileUnits() const { return m_fileUnits; } private: - /// Returns the canonical representation of a unit string (e.g. "centimeter" -> "cm") - std::string getCanonicalUnit(const std::string& unit) const - { - // we'll convert all units to lower case - auto toLower = [](std::string str) { - std::transform(str.begin(), str.end(), str.begin(), ::tolower); - return str; - }; - - // start with imperial units - std::map unitCanonicalMap = {{"inch", "in"}, - {"inches", "in"}, - {"in", "in"}, - {"foot", "ft"}, - {"feet", "ft"}, - {"ft", "ft"}, - {"mile", "mi"}, - {"miles", "mi"}, - {"mi", "mi"}}; - - // now add the SI units w/ several suffixes - // we're going to reverse this for the map to canonical units - std::map prefixes = { - {"am", "atto"}, - {"fm", "femto"}, - {"pm", "pico"}, - {"nm", "nano"}, - {"um", "micro"}, - {"mm", "milli"}, - {"cm", "centi"}, - {"dm", "deci"}, - {"m", ""}, - {"dam", "deca"}, - {"hm", "hecto"}, - {"km", "kilo"}, - }; - - for(const auto& kv : prefixes) - { - const std::string& canonical = kv.first; - const std::string& prefix = kv.second; - unitCanonicalMap[canonical] = canonical; - for(const std::string& suffix : {"meter", "meters", "metre", "metres"}) - { - unitCanonicalMap[prefix + suffix] = canonical; - } - } - - return unitCanonicalMap[toLower(unit)]; - } - - /** - * Returns the conversion factor from an input unit to an output unit - * - * \note Converts the units to their canonical form - * \sa getCanonicalUnit - */ - double getConversionFactor(const std::string& fileUnits, const std::string& defaultUnits = "mm") const - { - std::map unitConversionMap = {{"am", 1e-15}, - {"fm", 1e-12}, - {"pm", 1e-9}, - {"nm", 1e-6}, - {"um", 1e-3}, - {"mm", 1.0}, - {"cm", 10.0}, - {"dm", 100.0}, - {"m", 1e3}, - {"dam", 1e4}, - {"hm", 1e5}, - {"km", 1e6}, - {"in", 25.4}, - {"ft", 304.8}, - {"mi", 1609344.0}}; - - const double fileUnitFactor = unitConversionMap[getCanonicalUnit(fileUnits)]; - const double defaultUnitFactor = unitConversionMap[getCanonicalUnit(defaultUnits)]; - - return fileUnitFactor / defaultUnitFactor; - }; - /// Loads the step file \a filename from disk /// Uses the units from \a filename TopoDS_Shape loadStepFile(const std::string& filename) @@ -1068,9 +988,9 @@ class StepFileProcessor reader.FileUnits(anUnitLengthNames, anUnitAngleNames, anUnitSolidAngleNames); if(anUnitLengthNames.Size() > 0) { - m_fileUnits = getCanonicalUnit(anUnitLengthNames(1).ToCString()); + m_fileUnits = axom::utilities::getCanonicalUnitName(anUnitLengthNames(1).ToCString()); std::string defaultUnit = Interface_Static::CVal("xstep.cascade.unit"); - const double lengthUnit = getConversionFactor(m_fileUnits, defaultUnit); + const double lengthUnit = axom::utilities::getConversionFactor(m_fileUnits, defaultUnit); reader.SetSystemLengthUnit(lengthUnit); } diff --git a/src/axom/quest/tests/quest_c2c_reader.cpp b/src/axom/quest/tests/quest_c2c_reader.cpp index 2831edf1d6..c163368686 100644 --- a/src/axom/quest/tests/quest_c2c_reader.cpp +++ b/src/axom/quest/tests/quest_c2c_reader.cpp @@ -104,7 +104,12 @@ void writeSpline(const std::string& filename) TEST(quest_c2c_reader, unsupported_length_units) { quest::C2CReader reader; + EXPECT_THROW(reader.setLengthUnit(utilities::LengthUnit::am), std::invalid_argument); + EXPECT_THROW(reader.setLengthUnit(utilities::LengthUnit::fm), std::invalid_argument); + EXPECT_THROW(reader.setLengthUnit(utilities::LengthUnit::pm), std::invalid_argument); EXPECT_THROW(reader.setLengthUnit(utilities::LengthUnit::dm), std::invalid_argument); + EXPECT_THROW(reader.setLengthUnit(utilities::LengthUnit::dam), std::invalid_argument); + EXPECT_THROW(reader.setLengthUnit(utilities::LengthUnit::hm), std::invalid_argument); EXPECT_THROW(reader.setLengthUnit(utilities::LengthUnit::nm), std::invalid_argument); EXPECT_THROW(reader.setLengthUnit(utilities::LengthUnit::angstrom), std::invalid_argument); EXPECT_THROW(reader.setLengthUnit(utilities::LengthUnit::unspecified), std::invalid_argument); From eb83531674cd624308e5d1666bf387ede6681cc4 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 6 Jul 2026 14:41:05 -0700 Subject: [PATCH 621/986] Updated tutorial for units --- src/axom/core/tests/core_units.hpp | 4 +++ src/axom/core/utilities/Units.cpp | 11 ++++--- src/axom/core/utilities/Units.hpp | 9 ++++++ .../klee_operators_and_validation.cpp | 30 ++----------------- 4 files changed, 23 insertions(+), 31 deletions(-) diff --git a/src/axom/core/tests/core_units.hpp b/src/axom/core/tests/core_units.hpp index 9be54e6dc0..a60ddd7bb7 100644 --- a/src/axom/core/tests/core_units.hpp +++ b/src/axom/core/tests/core_units.hpp @@ -112,6 +112,10 @@ TEST(Units, getCanonicalUnitName) EXPECT_EQ("fm", getCanonicalUnitName("femtometer")); EXPECT_EQ("ft", getCanonicalUnitName("feet")); EXPECT_EQ("miles", getCanonicalUnitName("mi")); + + EXPECT_EQ("cm", getCanonicalUnitName(LengthUnit::cm)); + EXPECT_EQ("dam", getCanonicalUnitName(LengthUnit::dam)); + EXPECT_EQ("A", getCanonicalUnitName(LengthUnit::angstrom)); } TEST(Units, getConversionFactor_string) diff --git a/src/axom/core/utilities/Units.cpp b/src/axom/core/utilities/Units.cpp index 65e5764cb3..760031ca32 100644 --- a/src/axom/core/utilities/Units.cpp +++ b/src/axom/core/utilities/Units.cpp @@ -142,6 +142,11 @@ LengthUnit getCanonicalUnit(const std::string &unit) } std::string getCanonicalUnitName(const std::string &unit) +{ + return getCanonicalUnitName(getCanonicalUnit(unit)); +} + +std::string getCanonicalUnitName(LengthUnit unit) { static const std::unordered_map UNIT_NAMES { {LengthUnit::am, "am"}, @@ -163,12 +168,10 @@ std::string getCanonicalUnitName(const std::string &unit) {LengthUnit::angstrom, "A"}, {LengthUnit::unspecified, "unspecified"}}; - auto canonicalUnit = getCanonicalUnit(unit); - - auto iter = UNIT_NAMES.find(canonicalUnit); + auto iter = UNIT_NAMES.find(unit); if(iter == UNIT_NAMES.end()) { - throw std::invalid_argument(unrecognizedUnitsMessage(unit)); + throw std::invalid_argument("Unknown length unit"); } return iter->second; } diff --git a/src/axom/core/utilities/Units.hpp b/src/axom/core/utilities/Units.hpp index 7d5cd0344e..69958a823d 100644 --- a/src/axom/core/utilities/Units.hpp +++ b/src/axom/core/utilities/Units.hpp @@ -65,6 +65,15 @@ LengthUnit getCanonicalUnit(const std::string &unit); */ std::string getCanonicalUnitName(const std::string &unit); +/** + * Get the canonical short name of a length unit. + * + * \param unit the unit + * \return the canonical short unit name + * \throws std::invalid_argument if the unit is not known + */ +std::string getCanonicalUnitName(LengthUnit unit); + /** * Get the conversion factor to convert from the given source units to the target units. * diff --git a/src/examples/shaping_tutorial/lesson_03/klee_operators_and_validation.cpp b/src/examples/shaping_tutorial/lesson_03/klee_operators_and_validation.cpp index 39d2cf6964..f520fde455 100644 --- a/src/examples/shaping_tutorial/lesson_03/klee_operators_and_validation.cpp +++ b/src/examples/shaping_tutorial/lesson_03/klee_operators_and_validation.cpp @@ -61,36 +61,12 @@ void printShapeSetInfo(const axom::klee::ShapeSet& shapeSet) // lambda to help format a klee::LengthUnit auto lengthUnitToString = [](axom::klee::LengthUnit unit) -> std::string { - switch(unit) + if(unit == axom::klee::LengthUnit::unspecified) { - case axom::klee::LengthUnit::km: - return "km"; - case axom::klee::LengthUnit::m: - return "m"; - case axom::klee::LengthUnit::dm: - return "dm"; - case axom::klee::LengthUnit::cm: - return "cm"; - case axom::klee::LengthUnit::mm: - return "mm"; - case axom::klee::LengthUnit::um: - return "um"; - case axom::klee::LengthUnit::nm: - return "nm"; - case axom::klee::LengthUnit::angstrom: - return "A"; - case axom::klee::LengthUnit::miles: - return "miles"; - case axom::klee::LengthUnit::feet: - return "feet"; - case axom::klee::LengthUnit::inches: - return "inches"; - case axom::klee::LengthUnit::mils: - return "mils"; - case axom::klee::LengthUnit::unspecified: - default: return ""; } + + return axom::utilities::getCanonicalUnitName(unit); }; // lambda to help format a parir of klee::LengthUnits From 9de1074200d92b7d1d8e99590ce3df8c39079a92 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 6 Jul 2026 15:16:55 -0700 Subject: [PATCH 622/986] Simplify --- src/axom/core/tests/core_units.hpp | 57 +++++++++++++----------------- src/axom/core/utilities/Units.cpp | 5 --- src/axom/core/utilities/Units.hpp | 45 ++++++++++------------- src/axom/klee/io/IOUtil.cpp | 2 +- 4 files changed, 44 insertions(+), 65 deletions(-) diff --git a/src/axom/core/tests/core_units.hpp b/src/axom/core/tests/core_units.hpp index a60ddd7bb7..e1ce20864e 100644 --- a/src/axom/core/tests/core_units.hpp +++ b/src/axom/core/tests/core_units.hpp @@ -34,31 +34,35 @@ void expectEquivalent(Length length1, Length length2) expectConvertsTo(length2, length1); } -TEST(Units, parseLengthUnits) +TEST(Units, getCanonicalUnit) { - EXPECT_EQ(LengthUnit::am, parseLengthUnits("attometer")); - EXPECT_EQ(LengthUnit::fm, parseLengthUnits("femtometre")); - EXPECT_EQ(LengthUnit::pm, parseLengthUnits("picometers")); - EXPECT_EQ(LengthUnit::km, parseLengthUnits("km")); - EXPECT_EQ(LengthUnit::hm, parseLengthUnits("hectometer")); - EXPECT_EQ(LengthUnit::dam, parseLengthUnits("decametre")); - EXPECT_EQ(LengthUnit::m, parseLengthUnits("m")); - EXPECT_EQ(LengthUnit::dm, parseLengthUnits("dm")); - EXPECT_EQ(LengthUnit::cm, parseLengthUnits("cm")); - EXPECT_EQ(LengthUnit::mm, parseLengthUnits("mm")); - EXPECT_EQ(LengthUnit::um, parseLengthUnits("um")); - EXPECT_EQ(LengthUnit::nm, parseLengthUnits("nm")); - EXPECT_EQ(LengthUnit::angstrom, parseLengthUnits("A")); - EXPECT_EQ(LengthUnit::miles, parseLengthUnits("miles")); - EXPECT_EQ(LengthUnit::feet, parseLengthUnits("ft")); - EXPECT_EQ(LengthUnit::feet, parseLengthUnits("feet")); - EXPECT_EQ(LengthUnit::inches, parseLengthUnits("in")); - EXPECT_EQ(LengthUnit::inches, parseLengthUnits("inches")); - EXPECT_EQ(LengthUnit::mils, parseLengthUnits("mils")); + EXPECT_EQ(LengthUnit::am, getCanonicalUnit("attometer")); + EXPECT_EQ(LengthUnit::fm, getCanonicalUnit("femtometre")); + EXPECT_EQ(LengthUnit::pm, getCanonicalUnit("picometers")); + EXPECT_EQ(LengthUnit::km, getCanonicalUnit("km")); + EXPECT_EQ(LengthUnit::hm, getCanonicalUnit("hectometer")); + EXPECT_EQ(LengthUnit::dam, getCanonicalUnit("decametre")); + EXPECT_EQ(LengthUnit::m, getCanonicalUnit("m")); + EXPECT_EQ(LengthUnit::dm, getCanonicalUnit("dm")); + EXPECT_EQ(LengthUnit::cm, getCanonicalUnit("cm")); + EXPECT_EQ(LengthUnit::cm, getCanonicalUnit("centimeter")); + EXPECT_EQ(LengthUnit::mm, getCanonicalUnit("mm")); + EXPECT_EQ(LengthUnit::um, getCanonicalUnit("um")); + EXPECT_EQ(LengthUnit::nm, getCanonicalUnit("nm")); + EXPECT_EQ(LengthUnit::m, getCanonicalUnit("METRES")); + EXPECT_EQ(LengthUnit::angstrom, getCanonicalUnit("A")); + EXPECT_EQ(LengthUnit::fm, getCanonicalUnit("femtometres")); + EXPECT_EQ(LengthUnit::miles, getCanonicalUnit("miles")); + EXPECT_EQ(LengthUnit::miles, getCanonicalUnit("mi")); + EXPECT_EQ(LengthUnit::feet, getCanonicalUnit("ft")); + EXPECT_EQ(LengthUnit::feet, getCanonicalUnit("feet")); + EXPECT_EQ(LengthUnit::inches, getCanonicalUnit("in")); + EXPECT_EQ(LengthUnit::inches, getCanonicalUnit("inches")); + EXPECT_EQ(LengthUnit::mils, getCanonicalUnit("mils")); try { - parseLengthUnits("bad_units"); + getCanonicalUnit("bad_units"); FAIL() << "Should have thrown"; } catch(const std::invalid_argument &error) @@ -93,17 +97,6 @@ TEST(Units, getConversionFactor) EXPECT_THROW(getConversionFactor(LengthUnit::unspecified, LengthUnit::cm), std::invalid_argument); } -TEST(Units, getCanonicalUnit) -{ - EXPECT_EQ(LengthUnit::cm, getCanonicalUnit("centimeter")); - EXPECT_EQ(LengthUnit::m, getCanonicalUnit("METRES")); - EXPECT_EQ(LengthUnit::feet, getCanonicalUnit("feet")); - EXPECT_EQ(LengthUnit::miles, getCanonicalUnit("mi")); - EXPECT_EQ(LengthUnit::fm, getCanonicalUnit("femtometres")); - - EXPECT_THROW(getCanonicalUnit("bad_units"), std::invalid_argument); -} - TEST(Units, getCanonicalUnitName) { EXPECT_EQ("cm", getCanonicalUnitName("centimeter")); diff --git a/src/axom/core/utilities/Units.cpp b/src/axom/core/utilities/Units.cpp index 760031ca32..04d92b659a 100644 --- a/src/axom/core/utilities/Units.cpp +++ b/src/axom/core/utilities/Units.cpp @@ -44,11 +44,6 @@ std::string unrecognizedUnitsMessage(const std::string &unitsAsString) } } // namespace -LengthUnit parseLengthUnits(const std::string &unitsAsString) -{ - return getCanonicalUnit(unitsAsString); -} - LengthUnit getCanonicalUnit(const std::string &unit) { const std::string lowerUnit = toLower(unit); diff --git a/src/axom/core/utilities/Units.hpp b/src/axom/core/utilities/Units.hpp index 69958a823d..92350f3394 100644 --- a/src/axom/core/utilities/Units.hpp +++ b/src/axom/core/utilities/Units.hpp @@ -18,35 +18,26 @@ namespace utilities */ enum class LengthUnit { - am, - fm, - pm, - km, - hm, - dam, - m, - dm, - cm, - mm, - um, - nm, - angstrom, - miles, - feet, - inches, - mils, - unspecified + am, // attometers + fm, // femtometers + pm, // picometers + km, // kilometers + hm, // hectometers + dam, // decameters + m, // meters + dm, // decimeters + cm, // centimeters + mm, // millimeters + um, // micrometers + nm, // nanometers + angstrom, // angstroms + miles, // miles + feet, // feet + inches, // inches + mils, // thousandths of an inch + unspecified // no length unit specified }; -/** - * Convert a string to a LengthUnit. - * - * \param unitsAsString the units as a string - * \return the parsed units - * \throws std::invalid_argument if the string does not represent known units - */ -LengthUnit parseLengthUnits(const std::string &unitsAsString); - /** * Get the canonical representation of a length unit string. * diff --git a/src/axom/klee/io/IOUtil.cpp b/src/axom/klee/io/IOUtil.cpp index b04bdce8ac..cdd84d2f28 100644 --- a/src/axom/klee/io/IOUtil.cpp +++ b/src/axom/klee/io/IOUtil.cpp @@ -19,7 +19,7 @@ LengthUnit parseLengthUnits(const std::string &unitsAsString, const std::string { try { - return utilities::parseLengthUnits(unitsAsString); + return utilities::getCanonicalUnit(unitsAsString); } catch(const std::invalid_argument &ex) { From 2317b6512924dd938aa2adb18d1b1abd8c641cec Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 6 Jul 2026 15:19:36 -0700 Subject: [PATCH 623/986] Removed an overload. --- src/axom/core/tests/core_units.hpp | 11 ++++------- src/axom/core/utilities/Units.cpp | 5 ----- src/axom/core/utilities/Units.hpp | 9 --------- src/axom/quest/io/STEPReader.cpp | 3 ++- 4 files changed, 6 insertions(+), 22 deletions(-) diff --git a/src/axom/core/tests/core_units.hpp b/src/axom/core/tests/core_units.hpp index e1ce20864e..e5c67e252a 100644 --- a/src/axom/core/tests/core_units.hpp +++ b/src/axom/core/tests/core_units.hpp @@ -99,15 +99,12 @@ TEST(Units, getConversionFactor) TEST(Units, getCanonicalUnitName) { - EXPECT_EQ("cm", getCanonicalUnitName("centimeter")); - EXPECT_EQ("um", getCanonicalUnitName("micrometers")); - EXPECT_EQ("dam", getCanonicalUnitName("decametre")); - EXPECT_EQ("fm", getCanonicalUnitName("femtometer")); - EXPECT_EQ("ft", getCanonicalUnitName("feet")); - EXPECT_EQ("miles", getCanonicalUnitName("mi")); - EXPECT_EQ("cm", getCanonicalUnitName(LengthUnit::cm)); + EXPECT_EQ("um", getCanonicalUnitName(LengthUnit::um)); + EXPECT_EQ("fm", getCanonicalUnitName(LengthUnit::fm)); EXPECT_EQ("dam", getCanonicalUnitName(LengthUnit::dam)); + EXPECT_EQ("ft", getCanonicalUnitName(LengthUnit::feet)); + EXPECT_EQ("miles", getCanonicalUnitName(LengthUnit::miles)); EXPECT_EQ("A", getCanonicalUnitName(LengthUnit::angstrom)); } diff --git a/src/axom/core/utilities/Units.cpp b/src/axom/core/utilities/Units.cpp index 04d92b659a..62ae99a932 100644 --- a/src/axom/core/utilities/Units.cpp +++ b/src/axom/core/utilities/Units.cpp @@ -136,11 +136,6 @@ LengthUnit getCanonicalUnit(const std::string &unit) return iter->second; } -std::string getCanonicalUnitName(const std::string &unit) -{ - return getCanonicalUnitName(getCanonicalUnit(unit)); -} - std::string getCanonicalUnitName(LengthUnit unit) { static const std::unordered_map UNIT_NAMES { diff --git a/src/axom/core/utilities/Units.hpp b/src/axom/core/utilities/Units.hpp index 92350f3394..b4ffea8ea5 100644 --- a/src/axom/core/utilities/Units.hpp +++ b/src/axom/core/utilities/Units.hpp @@ -47,15 +47,6 @@ enum class LengthUnit */ LengthUnit getCanonicalUnit(const std::string &unit); -/** - * Get the canonical short name of a length unit string. - * - * \param unit the unit as a string - * \return the canonical short unit name - * \throws std::invalid_argument if the string does not represent known units - */ -std::string getCanonicalUnitName(const std::string &unit); - /** * Get the canonical short name of a length unit. * diff --git a/src/axom/quest/io/STEPReader.cpp b/src/axom/quest/io/STEPReader.cpp index 37d735c9bf..544e929a5f 100644 --- a/src/axom/quest/io/STEPReader.cpp +++ b/src/axom/quest/io/STEPReader.cpp @@ -988,7 +988,8 @@ class StepFileProcessor reader.FileUnits(anUnitLengthNames, anUnitAngleNames, anUnitSolidAngleNames); if(anUnitLengthNames.Size() > 0) { - m_fileUnits = axom::utilities::getCanonicalUnitName(anUnitLengthNames(1).ToCString()); + const auto fileUnits = axom::utilities::getCanonicalUnit(anUnitLengthNames(1).ToCString()); + m_fileUnits = axom::utilities::getCanonicalUnitName(fileUnits); std::string defaultUnit = Interface_Static::CVal("xstep.cascade.unit"); const double lengthUnit = axom::utilities::getConversionFactor(m_fileUnits, defaultUnit); reader.SetSystemLengthUnit(lengthUnit); From 2b94672983160ac9343db3094575f42fa189ddf5 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 6 Jul 2026 15:26:18 -0700 Subject: [PATCH 624/986] Removed a unit overload --- src/axom/core/tests/core_units.hpp | 11 ----------- src/axom/core/utilities/Units.cpp | 5 ----- src/axom/core/utilities/Units.hpp | 11 ----------- src/axom/quest/io/STEPReader.cpp | 4 ++-- 4 files changed, 2 insertions(+), 29 deletions(-) diff --git a/src/axom/core/tests/core_units.hpp b/src/axom/core/tests/core_units.hpp index e5c67e252a..8159eee247 100644 --- a/src/axom/core/tests/core_units.hpp +++ b/src/axom/core/tests/core_units.hpp @@ -108,17 +108,6 @@ TEST(Units, getCanonicalUnitName) EXPECT_EQ("A", getCanonicalUnitName(LengthUnit::angstrom)); } -TEST(Units, getConversionFactor_string) -{ - EXPECT_DOUBLE_EQ(10, getConversionFactor("centimeter", "mm")); - EXPECT_DOUBLE_EQ(25.4, getConversionFactor("inch", "millimeter")); - EXPECT_DOUBLE_EQ(1000, getConversionFactor("kilometer", "m")); - EXPECT_DOUBLE_EQ(5280, getConversionFactor("mile", "ft")); - EXPECT_DOUBLE_EQ(1e-3, getConversionFactor("femtometer", "pm")); - - EXPECT_THROW(getConversionFactor("cm", "bad_units"), std::invalid_argument); -} - TEST(Units, convert_adjacent) { expectEquivalent({1, LengthUnit::km}, {1000, LengthUnit::m}); diff --git a/src/axom/core/utilities/Units.cpp b/src/axom/core/utilities/Units.cpp index 62ae99a932..c93a3308c9 100644 --- a/src/axom/core/utilities/Units.cpp +++ b/src/axom/core/utilities/Units.cpp @@ -210,10 +210,5 @@ double convert(double sourceValue, LengthUnit sourceUnits, LengthUnit targetUnit return sourceValue * getConversionFactor(sourceUnits, targetUnits); } -double getConversionFactor(const std::string &sourceUnits, const std::string &targetUnits) -{ - return getConversionFactor(getCanonicalUnit(sourceUnits), getCanonicalUnit(targetUnits)); -} - } // namespace utilities } // namespace axom diff --git a/src/axom/core/utilities/Units.hpp b/src/axom/core/utilities/Units.hpp index b4ffea8ea5..0c2c2112b9 100644 --- a/src/axom/core/utilities/Units.hpp +++ b/src/axom/core/utilities/Units.hpp @@ -66,17 +66,6 @@ std::string getCanonicalUnitName(LengthUnit unit); */ double getConversionFactor(LengthUnit sourceUnits, LengthUnit targetUnits); -/** - * Get the conversion factor to convert from the given source units to the target units. - * - * \param sourceUnits the original units as a string - * \param targetUnits the target units as a string - * \return the value by which to multiply lengths in the original units - * to get the target units - * \throws std::invalid_argument if either string does not represent known units - */ -double getConversionFactor(const std::string &sourceUnits, const std::string &targetUnits); - /** * Convert a value from one set of units to another. * diff --git a/src/axom/quest/io/STEPReader.cpp b/src/axom/quest/io/STEPReader.cpp index 544e929a5f..d21f52cd2e 100644 --- a/src/axom/quest/io/STEPReader.cpp +++ b/src/axom/quest/io/STEPReader.cpp @@ -990,8 +990,8 @@ class StepFileProcessor { const auto fileUnits = axom::utilities::getCanonicalUnit(anUnitLengthNames(1).ToCString()); m_fileUnits = axom::utilities::getCanonicalUnitName(fileUnits); - std::string defaultUnit = Interface_Static::CVal("xstep.cascade.unit"); - const double lengthUnit = axom::utilities::getConversionFactor(m_fileUnits, defaultUnit); + const auto defaultUnit = axom::utilities::getCanonicalUnit(Interface_Static::CVal("xstep.cascade.unit")); + const double lengthUnit = axom::utilities::getConversionFactor(fileUnits, defaultUnit); reader.SetSystemLengthUnit(lengthUnit); } From 1c092f95087dac50c920a038778594f98c6efc33 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 6 Jul 2026 15:53:18 -0700 Subject: [PATCH 625/986] Renamed methods --- src/axom/core/tests/core_units.hpp | 66 +++++++++---------- src/axom/core/utilities/Units.cpp | 4 +- src/axom/core/utilities/Units.hpp | 12 ++-- src/axom/quest/io/STEPReader.cpp | 6 +- .../klee_operators_and_validation.cpp | 2 +- 5 files changed, 45 insertions(+), 45 deletions(-) diff --git a/src/axom/core/tests/core_units.hpp b/src/axom/core/tests/core_units.hpp index 8159eee247..70b3de00fe 100644 --- a/src/axom/core/tests/core_units.hpp +++ b/src/axom/core/tests/core_units.hpp @@ -34,35 +34,35 @@ void expectEquivalent(Length length1, Length length2) expectConvertsTo(length2, length1); } -TEST(Units, getCanonicalUnit) +TEST(Units, getLengthUnit) { - EXPECT_EQ(LengthUnit::am, getCanonicalUnit("attometer")); - EXPECT_EQ(LengthUnit::fm, getCanonicalUnit("femtometre")); - EXPECT_EQ(LengthUnit::pm, getCanonicalUnit("picometers")); - EXPECT_EQ(LengthUnit::km, getCanonicalUnit("km")); - EXPECT_EQ(LengthUnit::hm, getCanonicalUnit("hectometer")); - EXPECT_EQ(LengthUnit::dam, getCanonicalUnit("decametre")); - EXPECT_EQ(LengthUnit::m, getCanonicalUnit("m")); - EXPECT_EQ(LengthUnit::dm, getCanonicalUnit("dm")); - EXPECT_EQ(LengthUnit::cm, getCanonicalUnit("cm")); - EXPECT_EQ(LengthUnit::cm, getCanonicalUnit("centimeter")); - EXPECT_EQ(LengthUnit::mm, getCanonicalUnit("mm")); - EXPECT_EQ(LengthUnit::um, getCanonicalUnit("um")); - EXPECT_EQ(LengthUnit::nm, getCanonicalUnit("nm")); - EXPECT_EQ(LengthUnit::m, getCanonicalUnit("METRES")); - EXPECT_EQ(LengthUnit::angstrom, getCanonicalUnit("A")); - EXPECT_EQ(LengthUnit::fm, getCanonicalUnit("femtometres")); - EXPECT_EQ(LengthUnit::miles, getCanonicalUnit("miles")); - EXPECT_EQ(LengthUnit::miles, getCanonicalUnit("mi")); - EXPECT_EQ(LengthUnit::feet, getCanonicalUnit("ft")); - EXPECT_EQ(LengthUnit::feet, getCanonicalUnit("feet")); - EXPECT_EQ(LengthUnit::inches, getCanonicalUnit("in")); - EXPECT_EQ(LengthUnit::inches, getCanonicalUnit("inches")); - EXPECT_EQ(LengthUnit::mils, getCanonicalUnit("mils")); + EXPECT_EQ(LengthUnit::am, getLengthUnit("attometer")); + EXPECT_EQ(LengthUnit::fm, getLengthUnit("femtometre")); + EXPECT_EQ(LengthUnit::pm, getLengthUnit("picometers")); + EXPECT_EQ(LengthUnit::km, getLengthUnit("km")); + EXPECT_EQ(LengthUnit::hm, getLengthUnit("hectometer")); + EXPECT_EQ(LengthUnit::dam, getLengthUnit("decametre")); + EXPECT_EQ(LengthUnit::m, getLengthUnit("m")); + EXPECT_EQ(LengthUnit::dm, getLengthUnit("dm")); + EXPECT_EQ(LengthUnit::cm, getLengthUnit("cm")); + EXPECT_EQ(LengthUnit::cm, getLengthUnit("centimeter")); + EXPECT_EQ(LengthUnit::mm, getLengthUnit("mm")); + EXPECT_EQ(LengthUnit::um, getLengthUnit("um")); + EXPECT_EQ(LengthUnit::nm, getLengthUnit("nm")); + EXPECT_EQ(LengthUnit::m, getLengthUnit("METRES")); + EXPECT_EQ(LengthUnit::angstrom, getLengthUnit("A")); + EXPECT_EQ(LengthUnit::fm, getLengthUnit("femtometres")); + EXPECT_EQ(LengthUnit::miles, getLengthUnit("miles")); + EXPECT_EQ(LengthUnit::miles, getLengthUnit("mi")); + EXPECT_EQ(LengthUnit::feet, getLengthUnit("ft")); + EXPECT_EQ(LengthUnit::feet, getLengthUnit("feet")); + EXPECT_EQ(LengthUnit::inches, getLengthUnit("in")); + EXPECT_EQ(LengthUnit::inches, getLengthUnit("inches")); + EXPECT_EQ(LengthUnit::mils, getLengthUnit("mils")); try { - getCanonicalUnit("bad_units"); + getLengthUnit("bad_units"); FAIL() << "Should have thrown"; } catch(const std::invalid_argument &error) @@ -97,15 +97,15 @@ TEST(Units, getConversionFactor) EXPECT_THROW(getConversionFactor(LengthUnit::unspecified, LengthUnit::cm), std::invalid_argument); } -TEST(Units, getCanonicalUnitName) +TEST(Units, getLengthUnitName) { - EXPECT_EQ("cm", getCanonicalUnitName(LengthUnit::cm)); - EXPECT_EQ("um", getCanonicalUnitName(LengthUnit::um)); - EXPECT_EQ("fm", getCanonicalUnitName(LengthUnit::fm)); - EXPECT_EQ("dam", getCanonicalUnitName(LengthUnit::dam)); - EXPECT_EQ("ft", getCanonicalUnitName(LengthUnit::feet)); - EXPECT_EQ("miles", getCanonicalUnitName(LengthUnit::miles)); - EXPECT_EQ("A", getCanonicalUnitName(LengthUnit::angstrom)); + EXPECT_EQ("cm", getLengthUnitName(LengthUnit::cm)); + EXPECT_EQ("um", getLengthUnitName(LengthUnit::um)); + EXPECT_EQ("fm", getLengthUnitName(LengthUnit::fm)); + EXPECT_EQ("dam", getLengthUnitName(LengthUnit::dam)); + EXPECT_EQ("ft", getLengthUnitName(LengthUnit::feet)); + EXPECT_EQ("miles", getLengthUnitName(LengthUnit::miles)); + EXPECT_EQ("A", getLengthUnitName(LengthUnit::angstrom)); } TEST(Units, convert_adjacent) diff --git a/src/axom/core/utilities/Units.cpp b/src/axom/core/utilities/Units.cpp index c93a3308c9..89e11b4cb7 100644 --- a/src/axom/core/utilities/Units.cpp +++ b/src/axom/core/utilities/Units.cpp @@ -44,7 +44,7 @@ std::string unrecognizedUnitsMessage(const std::string &unitsAsString) } } // namespace -LengthUnit getCanonicalUnit(const std::string &unit) +LengthUnit getLengthUnit(const std::string &unit) { const std::string lowerUnit = toLower(unit); @@ -136,7 +136,7 @@ LengthUnit getCanonicalUnit(const std::string &unit) return iter->second; } -std::string getCanonicalUnitName(LengthUnit unit) +std::string getLengthUnitName(LengthUnit unit) { static const std::unordered_map UNIT_NAMES { {LengthUnit::am, "am"}, diff --git a/src/axom/core/utilities/Units.hpp b/src/axom/core/utilities/Units.hpp index 0c2c2112b9..a065a3a3ee 100644 --- a/src/axom/core/utilities/Units.hpp +++ b/src/axom/core/utilities/Units.hpp @@ -39,22 +39,22 @@ enum class LengthUnit }; /** - * Get the canonical representation of a length unit string. + * Get the length unit represented by a string. * * \param unit the unit as a string - * \return the canonical unit + * \return the length unit * \throws std::invalid_argument if the string does not represent known units */ -LengthUnit getCanonicalUnit(const std::string &unit); +LengthUnit getLengthUnit(const std::string &unit); /** - * Get the canonical short name of a length unit. + * Get the short name of a length unit. * * \param unit the unit - * \return the canonical short unit name + * \return the short unit name * \throws std::invalid_argument if the unit is not known */ -std::string getCanonicalUnitName(LengthUnit unit); +std::string getLengthUnitName(LengthUnit unit); /** * Get the conversion factor to convert from the given source units to the target units. diff --git a/src/axom/quest/io/STEPReader.cpp b/src/axom/quest/io/STEPReader.cpp index d21f52cd2e..f5aadf134b 100644 --- a/src/axom/quest/io/STEPReader.cpp +++ b/src/axom/quest/io/STEPReader.cpp @@ -988,9 +988,9 @@ class StepFileProcessor reader.FileUnits(anUnitLengthNames, anUnitAngleNames, anUnitSolidAngleNames); if(anUnitLengthNames.Size() > 0) { - const auto fileUnits = axom::utilities::getCanonicalUnit(anUnitLengthNames(1).ToCString()); - m_fileUnits = axom::utilities::getCanonicalUnitName(fileUnits); - const auto defaultUnit = axom::utilities::getCanonicalUnit(Interface_Static::CVal("xstep.cascade.unit")); + const auto fileUnits = axom::utilities::getLengthUnit(anUnitLengthNames(1).ToCString()); + m_fileUnits = axom::utilities::getLengthUnitName(fileUnits); + const auto defaultUnit = axom::utilities::getLengthUnit(Interface_Static::CVal("xstep.cascade.unit")); const double lengthUnit = axom::utilities::getConversionFactor(fileUnits, defaultUnit); reader.SetSystemLengthUnit(lengthUnit); } diff --git a/src/examples/shaping_tutorial/lesson_03/klee_operators_and_validation.cpp b/src/examples/shaping_tutorial/lesson_03/klee_operators_and_validation.cpp index f520fde455..de8c08b170 100644 --- a/src/examples/shaping_tutorial/lesson_03/klee_operators_and_validation.cpp +++ b/src/examples/shaping_tutorial/lesson_03/klee_operators_and_validation.cpp @@ -66,7 +66,7 @@ void printShapeSetInfo(const axom::klee::ShapeSet& shapeSet) return ""; } - return axom::utilities::getCanonicalUnitName(unit); + return axom::utilities::getLengthUnitName(unit); }; // lambda to help format a parir of klee::LengthUnits From c1bf180fcb32053e7b2587ad02209a772ff7fefd Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 6 Jul 2026 16:01:09 -0700 Subject: [PATCH 626/986] renamed a function --- src/axom/klee/io/IOUtil.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/axom/klee/io/IOUtil.cpp b/src/axom/klee/io/IOUtil.cpp index cdd84d2f28..ca89f883c2 100644 --- a/src/axom/klee/io/IOUtil.cpp +++ b/src/axom/klee/io/IOUtil.cpp @@ -19,7 +19,7 @@ LengthUnit parseLengthUnits(const std::string &unitsAsString, const std::string { try { - return utilities::getCanonicalUnit(unitsAsString); + return utilities::getLengthUnit(unitsAsString); } catch(const std::invalid_argument &ex) { From 9b5c1797ae8538b8a4e760b7b0f0041c8965739d Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 6 Jul 2026 16:02:08 -0700 Subject: [PATCH 627/986] Call a different function --- src/axom/klee/GeometryOperators.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/axom/klee/GeometryOperators.cpp b/src/axom/klee/GeometryOperators.cpp index 625b95aad2..2daf6af7c6 100644 --- a/src/axom/klee/GeometryOperators.cpp +++ b/src/axom/klee/GeometryOperators.cpp @@ -8,7 +8,6 @@ #include "axom/core/numerics/transforms.hpp" #include "axom/klee/GeometryOperators.hpp" -#include "axom/klee/Units.hpp" #include #include @@ -171,7 +170,7 @@ void UnitConverter::accept(GeometryOperatorVisitor &visitor) const { visitor.vis double UnitConverter::getConversionFactor() const { - return klee::getConversionFactor(getStartProperties().units, m_endUnits); + return utilities::getConversionFactor(getStartProperties().units, m_endUnits); }; SliceOperator::SliceOperator(const primal::Point3D &origin, From ac10e2c32665c6de2dbcc6447f58c8b0b84c95cc Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 6 Jul 2026 16:03:24 -0700 Subject: [PATCH 628/986] Compatibility header --- src/axom/klee/Units.hpp | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/axom/klee/Units.hpp diff --git a/src/axom/klee/Units.hpp b/src/axom/klee/Units.hpp new file mode 100644 index 0000000000..031ac84c0b --- /dev/null +++ b/src/axom/klee/Units.hpp @@ -0,0 +1,32 @@ +// 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_KLEE_UNITS_HPP +#define AXOM_KLEE_UNITS_HPP + +#include "axom/core/utilities/Units.hpp" + +#include + +namespace axom +{ +namespace inlet +{ +class Proxy; +} +namespace klee +{ + +using utilities::LengthUnit; +using utilities::getLengthUnitName; + +LengthUnit parseLengthUnits(const std::string &unitsAsString, const std::string &path); + +LengthUnit parseLengthUnits(const inlet::Proxy &unitsAsProxy); + +} // namespace klee +} // namespace axom + +#endif // AXOM_KLEE_UNITS_HPP From a0629ac5021db795b857b706b7c64c754af32158 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 6 Jul 2026 16:12:54 -0700 Subject: [PATCH 629/986] make style --- src/axom/core/tests/core_units.hpp | 28 ++++-- src/axom/core/utilities/Units.cpp | 150 ++++++++++++++--------------- src/axom/core/utilities/Units.hpp | 36 +++---- src/axom/quest/io/STEPReader.cpp | 3 +- 4 files changed, 112 insertions(+), 105 deletions(-) diff --git a/src/axom/core/tests/core_units.hpp b/src/axom/core/tests/core_units.hpp index 70b3de00fe..79128d7c8f 100644 --- a/src/axom/core/tests/core_units.hpp +++ b/src/axom/core/tests/core_units.hpp @@ -36,22 +36,22 @@ void expectEquivalent(Length length1, Length length2) TEST(Units, getLengthUnit) { - EXPECT_EQ(LengthUnit::am, getLengthUnit("attometer")); - EXPECT_EQ(LengthUnit::fm, getLengthUnit("femtometre")); - EXPECT_EQ(LengthUnit::pm, getLengthUnit("picometers")); EXPECT_EQ(LengthUnit::km, getLengthUnit("km")); EXPECT_EQ(LengthUnit::hm, getLengthUnit("hectometer")); EXPECT_EQ(LengthUnit::dam, getLengthUnit("decametre")); EXPECT_EQ(LengthUnit::m, getLengthUnit("m")); + EXPECT_EQ(LengthUnit::m, getLengthUnit("METRES")); EXPECT_EQ(LengthUnit::dm, getLengthUnit("dm")); EXPECT_EQ(LengthUnit::cm, getLengthUnit("cm")); EXPECT_EQ(LengthUnit::cm, getLengthUnit("centimeter")); EXPECT_EQ(LengthUnit::mm, getLengthUnit("mm")); EXPECT_EQ(LengthUnit::um, getLengthUnit("um")); EXPECT_EQ(LengthUnit::nm, getLengthUnit("nm")); - EXPECT_EQ(LengthUnit::m, getLengthUnit("METRES")); - EXPECT_EQ(LengthUnit::angstrom, getLengthUnit("A")); + EXPECT_EQ(LengthUnit::pm, getLengthUnit("picometers")); + EXPECT_EQ(LengthUnit::fm, getLengthUnit("femtometre")); EXPECT_EQ(LengthUnit::fm, getLengthUnit("femtometres")); + EXPECT_EQ(LengthUnit::am, getLengthUnit("attometer")); + EXPECT_EQ(LengthUnit::angstrom, getLengthUnit("A")); EXPECT_EQ(LengthUnit::miles, getLengthUnit("miles")); EXPECT_EQ(LengthUnit::miles, getLengthUnit("mi")); EXPECT_EQ(LengthUnit::feet, getLengthUnit("ft")); @@ -73,9 +73,6 @@ TEST(Units, getLengthUnit) TEST(Units, getConversionFactor) { - EXPECT_DOUBLE_EQ(1000, getConversionFactor(LengthUnit::fm, LengthUnit::am)); - EXPECT_DOUBLE_EQ(1000, getConversionFactor(LengthUnit::pm, LengthUnit::fm)); - EXPECT_DOUBLE_EQ(1000, getConversionFactor(LengthUnit::nm, LengthUnit::pm)); EXPECT_DOUBLE_EQ(1000, getConversionFactor(LengthUnit::km, LengthUnit::m)); EXPECT_DOUBLE_EQ(10, getConversionFactor(LengthUnit::km, LengthUnit::hm)); EXPECT_DOUBLE_EQ(10, getConversionFactor(LengthUnit::hm, LengthUnit::dam)); @@ -85,6 +82,9 @@ TEST(Units, getConversionFactor) EXPECT_DOUBLE_EQ(10, getConversionFactor(LengthUnit::cm, LengthUnit::mm)); EXPECT_DOUBLE_EQ(1000, getConversionFactor(LengthUnit::mm, LengthUnit::um)); EXPECT_DOUBLE_EQ(1000, getConversionFactor(LengthUnit::um, LengthUnit::nm)); + EXPECT_DOUBLE_EQ(1000, getConversionFactor(LengthUnit::nm, LengthUnit::pm)); + EXPECT_DOUBLE_EQ(1000, getConversionFactor(LengthUnit::pm, LengthUnit::fm)); + EXPECT_DOUBLE_EQ(1000, getConversionFactor(LengthUnit::fm, LengthUnit::am)); EXPECT_DOUBLE_EQ(10, getConversionFactor(LengthUnit::nm, LengthUnit::angstrom)); EXPECT_DOUBLE_EQ(2.54, getConversionFactor(LengthUnit::inches, LengthUnit::cm)); EXPECT_DOUBLE_EQ(1000, getConversionFactor(LengthUnit::inches, LengthUnit::mils)); @@ -99,13 +99,21 @@ TEST(Units, getConversionFactor) TEST(Units, getLengthUnitName) { + EXPECT_EQ("km", getLengthUnitName(LengthUnit::km)); + EXPECT_EQ("hm", getLengthUnitName(LengthUnit::hm)); + EXPECT_EQ("dam", getLengthUnitName(LengthUnit::dam)); + EXPECT_EQ("m", getLengthUnitName(LengthUnit::m)); + EXPECT_EQ("dm", getLengthUnitName(LengthUnit::dm)); EXPECT_EQ("cm", getLengthUnitName(LengthUnit::cm)); + EXPECT_EQ("mm", getLengthUnitName(LengthUnit::mm)); EXPECT_EQ("um", getLengthUnitName(LengthUnit::um)); + EXPECT_EQ("nm", getLengthUnitName(LengthUnit::nm)); + EXPECT_EQ("pm", getLengthUnitName(LengthUnit::pm)); EXPECT_EQ("fm", getLengthUnitName(LengthUnit::fm)); - EXPECT_EQ("dam", getLengthUnitName(LengthUnit::dam)); + EXPECT_EQ("am", getLengthUnitName(LengthUnit::am)); + EXPECT_EQ("A", getLengthUnitName(LengthUnit::angstrom)); EXPECT_EQ("ft", getLengthUnitName(LengthUnit::feet)); EXPECT_EQ("miles", getLengthUnitName(LengthUnit::miles)); - EXPECT_EQ("A", getLengthUnitName(LengthUnit::angstrom)); } TEST(Units, convert_adjacent) diff --git a/src/axom/core/utilities/Units.cpp b/src/axom/core/utilities/Units.cpp index 89e11b4cb7..bdfaabd94e 100644 --- a/src/axom/core/utilities/Units.cpp +++ b/src/axom/core/utilities/Units.cpp @@ -19,9 +19,7 @@ namespace utilities namespace { /** - * A simple functor which can be used for hashing length units. Needed because - * C++11 does not support hashing enums by default (fixed in 14, this can - * go away when we commit to that standard). + * A simple functor which can be used for hashing length units. */ struct LengthUnitHash { @@ -60,29 +58,41 @@ LengthUnit getLengthUnit(const std::string &unit) {"mi", LengthUnit::miles}, {"mil", LengthUnit::mils}, {"mils", LengthUnit::mils}, - {"a", LengthUnit::angstrom}, - {"angstrom", LengthUnit::angstrom}, - {"angstroms", LengthUnit::angstrom}, - {"am", LengthUnit::am}, - {"attometer", LengthUnit::am}, - {"attometers", LengthUnit::am}, - {"attometre", LengthUnit::am}, - {"attometres", LengthUnit::am}, - {"fm", LengthUnit::fm}, - {"femtometer", LengthUnit::fm}, - {"femtometers", LengthUnit::fm}, - {"femtometre", LengthUnit::fm}, - {"femtometres", LengthUnit::fm}, - {"pm", LengthUnit::pm}, - {"picometer", LengthUnit::pm}, - {"picometers", LengthUnit::pm}, - {"picometre", LengthUnit::pm}, - {"picometres", LengthUnit::pm}, - {"nm", LengthUnit::nm}, - {"nanometer", LengthUnit::nm}, - {"nanometers", LengthUnit::nm}, - {"nanometre", LengthUnit::nm}, - {"nanometres", LengthUnit::nm}, + {"km", LengthUnit::km}, + {"kilometer", LengthUnit::km}, + {"kilometers", LengthUnit::km}, + {"kilometre", LengthUnit::km}, + {"kilometres", LengthUnit::km}, + {"hm", LengthUnit::hm}, + {"hectometer", LengthUnit::hm}, + {"hectometers", LengthUnit::hm}, + {"hectometre", LengthUnit::hm}, + {"hectometres", LengthUnit::hm}, + {"dam", LengthUnit::dam}, + {"decameter", LengthUnit::dam}, + {"decameters", LengthUnit::dam}, + {"decametre", LengthUnit::dam}, + {"decametres", LengthUnit::dam}, + {"m", LengthUnit::m}, + {"meter", LengthUnit::m}, + {"meters", LengthUnit::m}, + {"metre", LengthUnit::m}, + {"metres", LengthUnit::m}, + {"dm", LengthUnit::dm}, + {"decimeter", LengthUnit::dm}, + {"decimeters", LengthUnit::dm}, + {"decimetre", LengthUnit::dm}, + {"decimetres", LengthUnit::dm}, + {"cm", LengthUnit::cm}, + {"centimeter", LengthUnit::cm}, + {"centimeters", LengthUnit::cm}, + {"centimetre", LengthUnit::cm}, + {"centimetres", LengthUnit::cm}, + {"mm", LengthUnit::mm}, + {"millimeter", LengthUnit::mm}, + {"millimeters", LengthUnit::mm}, + {"millimetre", LengthUnit::mm}, + {"millimetres", LengthUnit::mm}, {"um", LengthUnit::um}, {"micrometer", LengthUnit::um}, {"micrometers", LengthUnit::um}, @@ -90,41 +100,29 @@ LengthUnit getLengthUnit(const std::string &unit) {"micrometres", LengthUnit::um}, {"micron", LengthUnit::um}, {"microns", LengthUnit::um}, - {"mm", LengthUnit::mm}, - {"millimeter", LengthUnit::mm}, - {"millimeters", LengthUnit::mm}, - {"millimetre", LengthUnit::mm}, - {"millimetres", LengthUnit::mm}, - {"cm", LengthUnit::cm}, - {"centimeter", LengthUnit::cm}, - {"centimeters", LengthUnit::cm}, - {"centimetre", LengthUnit::cm}, - {"centimetres", LengthUnit::cm}, - {"dm", LengthUnit::dm}, - {"decimeter", LengthUnit::dm}, - {"decimeters", LengthUnit::dm}, - {"decimetre", LengthUnit::dm}, - {"decimetres", LengthUnit::dm}, - {"m", LengthUnit::m}, - {"meter", LengthUnit::m}, - {"meters", LengthUnit::m}, - {"metre", LengthUnit::m}, - {"metres", LengthUnit::m}, - {"dam", LengthUnit::dam}, - {"decameter", LengthUnit::dam}, - {"decameters", LengthUnit::dam}, - {"decametre", LengthUnit::dam}, - {"decametres", LengthUnit::dam}, - {"hm", LengthUnit::hm}, - {"hectometer", LengthUnit::hm}, - {"hectometers", LengthUnit::hm}, - {"hectometre", LengthUnit::hm}, - {"hectometres", LengthUnit::hm}, - {"km", LengthUnit::km}, - {"kilometer", LengthUnit::km}, - {"kilometers", LengthUnit::km}, - {"kilometre", LengthUnit::km}, - {"kilometres", LengthUnit::km}, + {"nm", LengthUnit::nm}, + {"nanometer", LengthUnit::nm}, + {"nanometers", LengthUnit::nm}, + {"nanometre", LengthUnit::nm}, + {"nanometres", LengthUnit::nm}, + {"pm", LengthUnit::pm}, + {"picometer", LengthUnit::pm}, + {"picometers", LengthUnit::pm}, + {"picometre", LengthUnit::pm}, + {"picometres", LengthUnit::pm}, + {"fm", LengthUnit::fm}, + {"femtometer", LengthUnit::fm}, + {"femtometers", LengthUnit::fm}, + {"femtometre", LengthUnit::fm}, + {"femtometres", LengthUnit::fm}, + {"am", LengthUnit::am}, + {"attometer", LengthUnit::am}, + {"attometers", LengthUnit::am}, + {"attometre", LengthUnit::am}, + {"attometres", LengthUnit::am}, + {"a", LengthUnit::angstrom}, + {"angstrom", LengthUnit::angstrom}, + {"angstroms", LengthUnit::angstrom}, }; auto iter = UNITS_BY_NAME.find(lowerUnit); @@ -139,23 +137,23 @@ LengthUnit getLengthUnit(const std::string &unit) std::string getLengthUnitName(LengthUnit unit) { static const std::unordered_map UNIT_NAMES { - {LengthUnit::am, "am"}, - {LengthUnit::fm, "fm"}, - {LengthUnit::pm, "pm"}, - {LengthUnit::nm, "nm"}, - {LengthUnit::um, "um"}, - {LengthUnit::mm, "mm"}, - {LengthUnit::cm, "cm"}, - {LengthUnit::dm, "dm"}, - {LengthUnit::m, "m"}, - {LengthUnit::dam, "dam"}, - {LengthUnit::hm, "hm"}, {LengthUnit::km, "km"}, + {LengthUnit::hm, "hm"}, + {LengthUnit::dam, "dam"}, + {LengthUnit::m, "m"}, + {LengthUnit::dm, "dm"}, + {LengthUnit::cm, "cm"}, + {LengthUnit::mm, "mm"}, + {LengthUnit::um, "um"}, + {LengthUnit::nm, "nm"}, + {LengthUnit::pm, "pm"}, + {LengthUnit::fm, "fm"}, + {LengthUnit::am, "am"}, + {LengthUnit::angstrom, "A"}, {LengthUnit::miles, "miles"}, {LengthUnit::feet, "ft"}, {LengthUnit::inches, "in"}, {LengthUnit::mils, "mils"}, - {LengthUnit::angstrom, "A"}, {LengthUnit::unspecified, "unspecified"}}; auto iter = UNIT_NAMES.find(unit); @@ -169,9 +167,6 @@ std::string getLengthUnitName(LengthUnit unit) double getConversionFactor(LengthUnit sourceUnits, LengthUnit targetUnits) { static const std::unordered_map CONVERSION_TO_CM { - {LengthUnit::am, 1e-16}, - {LengthUnit::fm, 1e-13}, - {LengthUnit::pm, 1e-10}, {LengthUnit::km, 1e5}, {LengthUnit::hm, 1e4}, {LengthUnit::dam, 1e3}, @@ -181,6 +176,9 @@ double getConversionFactor(LengthUnit sourceUnits, LengthUnit targetUnits) {LengthUnit::mm, 1e-1}, {LengthUnit::um, 1e-4}, {LengthUnit::nm, 1e-7}, + {LengthUnit::pm, 1e-10}, + {LengthUnit::fm, 1e-13}, + {LengthUnit::am, 1e-16}, {LengthUnit::angstrom, 1e-8}, {LengthUnit::miles, 2.54 * 12.0 * 5280}, {LengthUnit::feet, 2.54 * 12.0}, diff --git a/src/axom/core/utilities/Units.hpp b/src/axom/core/utilities/Units.hpp index a065a3a3ee..e3b02dcca4 100644 --- a/src/axom/core/utilities/Units.hpp +++ b/src/axom/core/utilities/Units.hpp @@ -18,24 +18,24 @@ namespace utilities */ enum class LengthUnit { - am, // attometers - fm, // femtometers - pm, // picometers - km, // kilometers - hm, // hectometers - dam, // decameters - m, // meters - dm, // decimeters - cm, // centimeters - mm, // millimeters - um, // micrometers - nm, // nanometers - angstrom, // angstroms - miles, // miles - feet, // feet - inches, // inches - mils, // thousandths of an inch - unspecified // no length unit specified + km, // kilometers + hm, // hectometers + dam, // decameters + m, // meters + dm, // decimeters + cm, // centimeters + mm, // millimeters + um, // micrometers + nm, // nanometers + pm, // picometers + fm, // femtometers + am, // attometers + angstrom, // angstroms + miles, // miles + feet, // feet + inches, // inches + mils, // thousandths of an inch + unspecified // no length unit specified }; /** diff --git a/src/axom/quest/io/STEPReader.cpp b/src/axom/quest/io/STEPReader.cpp index f5aadf134b..7326c3728b 100644 --- a/src/axom/quest/io/STEPReader.cpp +++ b/src/axom/quest/io/STEPReader.cpp @@ -990,7 +990,8 @@ class StepFileProcessor { const auto fileUnits = axom::utilities::getLengthUnit(anUnitLengthNames(1).ToCString()); m_fileUnits = axom::utilities::getLengthUnitName(fileUnits); - const auto defaultUnit = axom::utilities::getLengthUnit(Interface_Static::CVal("xstep.cascade.unit")); + const auto defaultUnit = + axom::utilities::getLengthUnit(Interface_Static::CVal("xstep.cascade.unit")); const double lengthUnit = axom::utilities::getConversionFactor(fileUnits, defaultUnit); reader.SetSystemLengthUnit(lengthUnit); } From bdbf292dfba4211cbeab6d7e92d2269dc5edc6fe Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 6 Jul 2026 16:17:29 -0700 Subject: [PATCH 630/986] Update release notes --- RELEASE-NOTES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 921fb49011..47f35e6991 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -56,6 +56,9 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ ### Changed - Updates CMake code check targets to only use checked in files (via `git ls-files`, when available) - CMake: Simplified execution policy logic through use of `AXOM_EXECUTION_POLICIES` variable. +- Core: Moved length unit parsing and conversion helpers into `axom::utilities`. +- Quest: Updated `C2CReader` to use `axom::utilities::LengthUnit` at its public length-unit interface. +- Quest: Updated `STEPReader` to use centralized length unit parsing and conversion logic. - Core: Optimization for axom::Array indirection -- since the stride is always 1, we can remove the runtime multiplication - Python: Removes build and test dependencies from `run_python_with_axom.sh` wrapper script From d4641bf6849e65ed4a83acef62ed34b1d374ccf9 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 6 Jul 2026 16:31:50 -0700 Subject: [PATCH 631/986] Added tests --- src/axom/core/tests/core_units.hpp | 44 ++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/axom/core/tests/core_units.hpp b/src/axom/core/tests/core_units.hpp index 79128d7c8f..be710c648c 100644 --- a/src/axom/core/tests/core_units.hpp +++ b/src/axom/core/tests/core_units.hpp @@ -46,6 +46,7 @@ TEST(Units, getLengthUnit) EXPECT_EQ(LengthUnit::cm, getLengthUnit("centimeter")); EXPECT_EQ(LengthUnit::mm, getLengthUnit("mm")); EXPECT_EQ(LengthUnit::um, getLengthUnit("um")); + EXPECT_EQ(LengthUnit::um, getLengthUnit("micron")); EXPECT_EQ(LengthUnit::nm, getLengthUnit("nm")); EXPECT_EQ(LengthUnit::pm, getLengthUnit("picometers")); EXPECT_EQ(LengthUnit::fm, getLengthUnit("femtometre")); @@ -113,7 +114,10 @@ TEST(Units, getLengthUnitName) EXPECT_EQ("am", getLengthUnitName(LengthUnit::am)); EXPECT_EQ("A", getLengthUnitName(LengthUnit::angstrom)); EXPECT_EQ("ft", getLengthUnitName(LengthUnit::feet)); + EXPECT_EQ("in", getLengthUnitName(LengthUnit::inches)); + EXPECT_EQ("mils", getLengthUnitName(LengthUnit::mils)); EXPECT_EQ("miles", getLengthUnitName(LengthUnit::miles)); + EXPECT_EQ("unspecified", getLengthUnitName(LengthUnit::unspecified)); } TEST(Units, convert_adjacent) @@ -167,6 +171,12 @@ TEST(Units, convert_all) } } +TEST(Units, convert_throws_on_unspecified) +{ + EXPECT_THROW(convert(1.0, LengthUnit::cm, LengthUnit::unspecified), std::invalid_argument); + EXPECT_THROW(convert(1.0, LengthUnit::unspecified, LengthUnit::cm), std::invalid_argument); +} + TEST(Units, convertAll) { std::vector values {1.0, 2.0, 3.0}; @@ -177,5 +187,39 @@ TEST(Units, convertAll) EXPECT_DOUBLE_EQ(300, values[2]); } +TEST(Units, convertAll_same_units_is_noop) +{ + std::vector values {1.0, 2.0, 3.0}; + convertAll(values, LengthUnit::feet, LengthUnit::feet); + ASSERT_EQ(3u, values.size()); + EXPECT_DOUBLE_EQ(1.0, values[0]); + EXPECT_DOUBLE_EQ(2.0, values[1]); + EXPECT_DOUBLE_EQ(3.0, values[2]); +} + +TEST(Units, convertAll_imperial_to_metric) +{ + std::vector values {1.0, 2.5, 10.0}; + convertAll(values, LengthUnit::feet, LengthUnit::inches); + ASSERT_EQ(3u, values.size()); + EXPECT_DOUBLE_EQ(12.0, values[0]); + EXPECT_DOUBLE_EQ(30.0, values[1]); + EXPECT_DOUBLE_EQ(120.0, values[2]); +} + +TEST(Units, convertAll_empty) +{ + std::vector values; + convertAll(values, LengthUnit::m, LengthUnit::cm); + EXPECT_TRUE(values.empty()); +} + +TEST(Units, convertAll_throws_on_unspecified) +{ + std::vector values {1.0, 2.0, 3.0}; + EXPECT_THROW(convertAll(values, LengthUnit::cm, LengthUnit::unspecified), std::invalid_argument); + EXPECT_THROW(convertAll(values, LengthUnit::unspecified, LengthUnit::cm), std::invalid_argument); +} + } // namespace utilities } // namespace axom From aeb2ee94ef7e2da974ad673dc9943fad4917a0c7 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 6 Jul 2026 16:38:28 -0700 Subject: [PATCH 632/986] make style --- src/axom/klee/Units.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/axom/klee/Units.hpp b/src/axom/klee/Units.hpp index 031ac84c0b..311a533b52 100644 --- a/src/axom/klee/Units.hpp +++ b/src/axom/klee/Units.hpp @@ -19,8 +19,8 @@ class Proxy; namespace klee { -using utilities::LengthUnit; using utilities::getLengthUnitName; +using utilities::LengthUnit; LengthUnit parseLengthUnits(const std::string &unitsAsString, const std::string &path); From 68ec04eb3c0b2cb8acfa1d5d1f97686c006e9833 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 6 Jul 2026 16:47:37 -0700 Subject: [PATCH 633/986] Moved some function defs to their own cpp file --- src/axom/klee/CMakeLists.txt | 1 + src/axom/klee/Units.cpp | 36 ++++++++++++++++++++++++++++++++++++ src/axom/klee/io/IOUtil.cpp | 19 ------------------- 3 files changed, 37 insertions(+), 19 deletions(-) create mode 100644 src/axom/klee/Units.cpp diff --git a/src/axom/klee/CMakeLists.txt b/src/axom/klee/CMakeLists.txt index 0bbfa9c175..7750c5c321 100644 --- a/src/axom/klee/CMakeLists.txt +++ b/src/axom/klee/CMakeLists.txt @@ -33,6 +33,7 @@ set(klee_sources KleeError.cpp Shape.cpp ShapeSet.cpp + Units.cpp io/GeometryOperatorsIO.cpp io/IO.cpp io/IOUtil.cpp diff --git a/src/axom/klee/Units.cpp b/src/axom/klee/Units.cpp new file mode 100644 index 0000000000..53c225a63e --- /dev/null +++ b/src/axom/klee/Units.cpp @@ -0,0 +1,36 @@ +// 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/klee/Units.hpp" + +#include "axom/inlet.hpp" +#include "axom/klee/KleeError.hpp" + +#include + +namespace axom +{ +namespace klee +{ +LengthUnit parseLengthUnits(const std::string &unitsAsString, const std::string &path) +{ + try + { + return utilities::getLengthUnit(unitsAsString); + } + catch(const std::invalid_argument &ex) + { + throw KleeError({path, ex.what()}); + } +} + +LengthUnit parseLengthUnits(const inlet::Proxy &unitsAsProxy) +{ + return parseLengthUnits(unitsAsProxy.get(), unitsAsProxy.name()); +} + +} // namespace klee +} // namespace axom diff --git a/src/axom/klee/io/IOUtil.cpp b/src/axom/klee/io/IOUtil.cpp index ca89f883c2..bbe3a0cd9a 100644 --- a/src/axom/klee/io/IOUtil.cpp +++ b/src/axom/klee/io/IOUtil.cpp @@ -9,29 +9,10 @@ #include "axom/inlet.hpp" #include "axom/klee/KleeError.hpp" -#include - namespace axom { namespace klee { -LengthUnit parseLengthUnits(const std::string &unitsAsString, const std::string &path) -{ - try - { - return utilities::getLengthUnit(unitsAsString); - } - catch(const std::invalid_argument &ex) - { - throw KleeError({path, ex.what()}); - } -} - -LengthUnit parseLengthUnits(const inlet::Proxy &unitsAsProxy) -{ - return parseLengthUnits(unitsAsProxy.get(), unitsAsProxy.name()); -} - namespace internal { std::vector toDoubleVector(inlet::Proxy const &field, From e789416fddeec1c124688d6785a91fb99d2e935b Mon Sep 17 00:00:00 2001 From: Brian Han Date: Tue, 7 Jul 2026 08:20:34 -0700 Subject: [PATCH 634/986] Add -Werror (ENABLE_WARNINGS_AS_ERRORS) to clang & gcc to dane Gitlab CI --- .gitlab/build_dane.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitlab/build_dane.yml b/.gitlab/build_dane.yml index e5a6f7b4d4..bb9e41dcba 100644 --- a/.gitlab/build_dane.yml +++ b/.gitlab/build_dane.yml @@ -37,10 +37,11 @@ #### # PR Build jobs -dane-llvm_19_1_3-debug-src: +dane-llvm_19_1_3-werror-debug-src: variables: COMPILER: "llvm@19.1.3" HOST_CONFIG: "dane-toss_4_x86_64_ib-${COMPILER}.cmake" + EXTRA_CMAKE_OPTIONS: "-DENABLE_WARNINGS_AS_ERRORS:BOOL=ON" extends: .src_build_on_dane dane-llvm_19_1_3-release-src: @@ -51,10 +52,11 @@ dane-llvm_19_1_3-release-src: EXTRA_CMAKE_OPTIONS: "-DAXOM_QUEST_ENABLE_EXTRA_REGRESSION_TESTS:BOOL=ON" extends: .src_build_on_dane -dane-gcc_13_3_1-src: +dane-gcc_13_3_1-werror-src: variables: COMPILER: "gcc@13.3.1" HOST_CONFIG: "dane-toss_4_x86_64_ib-${COMPILER}.cmake" + EXTRA_CMAKE_OPTIONS: "-DENABLE_WARNINGS_AS_ERRORS:BOOL=ON" extends: .src_build_on_dane dane-sanitizers-gcc_13_3_1-src: From c4d23e6e674c4584f3390b2c8bdf42511ec95c71 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 7 Jul 2026 10:37:12 -0700 Subject: [PATCH 635/986] Expose one parseLengthUnits function, put it into internal namespace. --- src/axom/klee/Units.cpp | 4 +++- src/axom/klee/Units.hpp | 13 +++++++++++-- src/axom/klee/io/GeometryOperatorsIO.cpp | 2 +- src/axom/klee/io/IOUtil.cpp | 6 +++--- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/axom/klee/Units.cpp b/src/axom/klee/Units.cpp index 53c225a63e..eb1a0c8315 100644 --- a/src/axom/klee/Units.cpp +++ b/src/axom/klee/Units.cpp @@ -15,6 +15,8 @@ namespace axom { namespace klee { +namespace internal +{ LengthUnit parseLengthUnits(const std::string &unitsAsString, const std::string &path) { try @@ -31,6 +33,6 @@ LengthUnit parseLengthUnits(const inlet::Proxy &unitsAsProxy) { return parseLengthUnits(unitsAsProxy.get(), unitsAsProxy.name()); } - +} // namespace internal } // namespace klee } // namespace axom diff --git a/src/axom/klee/Units.hpp b/src/axom/klee/Units.hpp index 311a533b52..bd75775b4d 100644 --- a/src/axom/klee/Units.hpp +++ b/src/axom/klee/Units.hpp @@ -19,13 +19,22 @@ class Proxy; namespace klee { -using utilities::getLengthUnitName; using utilities::LengthUnit; -LengthUnit parseLengthUnits(const std::string &unitsAsString, const std::string &path); +namespace internal +{ +/*! + * \brief This function parses a string and returns a LengthUnit. It is a compatibility + * function that throws a KleeError if the unit is invalid. + * + * \param unitsAsProxy The Inlet proxy from which to get the unit string. + * + * \return A LengthUnit containing the unit type. + */ LengthUnit parseLengthUnits(const inlet::Proxy &unitsAsProxy); +} // namespace internal } // namespace klee } // namespace axom diff --git a/src/axom/klee/io/GeometryOperatorsIO.cpp b/src/axom/klee/io/GeometryOperatorsIO.cpp index 9a1daf4024..e676c9e2ba 100644 --- a/src/axom/klee/io/GeometryOperatorsIO.cpp +++ b/src/axom/klee/io/GeometryOperatorsIO.cpp @@ -391,7 +391,7 @@ OpPtr parseConvertUnits(const inlet::Container &opContainer, const TransformableGeometryProperties &startProperties) { verifyObjectFields(opContainer, "convert_units_to", FieldSet {}, FieldSet {}); - auto endUnits = parseLengthUnits(opContainer["convert_units_to"]); + auto endUnits = internal::parseLengthUnits(opContainer["convert_units_to"]); return std::make_shared(endUnits, startProperties); } diff --git a/src/axom/klee/io/IOUtil.cpp b/src/axom/klee/io/IOUtil.cpp index bbe3a0cd9a..7c8265443c 100644 --- a/src/axom/klee/io/IOUtil.cpp +++ b/src/axom/klee/io/IOUtil.cpp @@ -87,7 +87,7 @@ std::tuple getOptionalStartAndEndUnits(const inlet::Cont { throw KleeError({container.name(), "Can't specify 'units' with 'start_units' or 'end_units'"}); } - auto units = parseLengthUnits(container["units"]); + auto units = internal::parseLengthUnits(container["units"]); return std::make_tuple(units, units); } else if(hasStartUnits || hasEndUnits) @@ -96,8 +96,8 @@ std::tuple getOptionalStartAndEndUnits(const inlet::Cont { throw KleeError({container.name(), "Must specify both 'start_units' and 'end_units'"}); } - auto startUnits = parseLengthUnits(container["start_units"]); - auto endUnits = parseLengthUnits(container["end_units"]); + auto startUnits = internal::parseLengthUnits(container["start_units"]); + auto endUnits = internal::parseLengthUnits(container["end_units"]); return std::make_tuple(startUnits, endUnits); } return std::make_tuple(LengthUnit::unspecified, LengthUnit::unspecified); From 184a633f6a8e0d234b2af38b37400bf753ddcf8e Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 7 Jul 2026 10:37:24 -0700 Subject: [PATCH 636/986] Improve comments --- src/axom/core/utilities/Units.cpp | 18 ++++++++---------- src/axom/core/utilities/Units.hpp | 12 ++++++------ 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/axom/core/utilities/Units.cpp b/src/axom/core/utilities/Units.cpp index bdfaabd94e..42e8d340c5 100644 --- a/src/axom/core/utilities/Units.cpp +++ b/src/axom/core/utilities/Units.cpp @@ -5,6 +5,7 @@ // SPDX-License-Identifier: (BSD-3-Clause) #include "axom/core/utilities/Units.hpp" +#include "axom/core/utilities/StringUtilities.hpp" #include #include @@ -12,13 +13,17 @@ #include #include +/* + * NOTE: Much of the code here was gathered/adapted from various parts of Axom + * authored by @kennyweiss and others. + */ namespace axom { namespace utilities { namespace { -/** +/*! * A simple functor which can be used for hashing length units. */ struct LengthUnitHash @@ -26,14 +31,6 @@ struct LengthUnitHash std::size_t operator()(LengthUnit unit) const { return static_cast(unit); } }; -std::string toLower(std::string str) -{ - std::transform(str.begin(), str.end(), str.begin(), [](unsigned char ch) { - return static_cast(std::tolower(ch)); - }); - return str; -} - std::string unrecognizedUnitsMessage(const std::string &unitsAsString) { std::string message = "Unrecognized units: "; @@ -44,7 +41,8 @@ std::string unrecognizedUnitsMessage(const std::string &unitsAsString) LengthUnit getLengthUnit(const std::string &unit) { - const std::string lowerUnit = toLower(unit); + std::string lowerUnit(unit); + string::toLower(lowerUnit); static const std::unordered_map UNITS_BY_NAME { {"inch", LengthUnit::inches}, diff --git a/src/axom/core/utilities/Units.hpp b/src/axom/core/utilities/Units.hpp index e3b02dcca4..2b1f54bfb9 100644 --- a/src/axom/core/utilities/Units.hpp +++ b/src/axom/core/utilities/Units.hpp @@ -12,7 +12,7 @@ namespace axom { namespace utilities { -/** +/*! * Units of length in which users can express lengths and in which client * codes can request them. */ @@ -38,7 +38,7 @@ enum class LengthUnit unspecified // no length unit specified }; -/** +/*! * Get the length unit represented by a string. * * \param unit the unit as a string @@ -47,7 +47,7 @@ enum class LengthUnit */ LengthUnit getLengthUnit(const std::string &unit); -/** +/*! * Get the short name of a length unit. * * \param unit the unit @@ -56,7 +56,7 @@ LengthUnit getLengthUnit(const std::string &unit); */ std::string getLengthUnitName(LengthUnit unit); -/** +/*! * Get the conversion factor to convert from the given source units to the target units. * * \param sourceUnits the original units @@ -66,7 +66,7 @@ std::string getLengthUnitName(LengthUnit unit); */ double getConversionFactor(LengthUnit sourceUnits, LengthUnit targetUnits); -/** +/*! * Convert a value from one set of units to another. * * \param sourceValue the value of the length in the original units @@ -76,7 +76,7 @@ double getConversionFactor(LengthUnit sourceUnits, LengthUnit targetUnits); */ double convert(double sourceValue, LengthUnit sourceUnits, LengthUnit targetUnits); -/** +/*! * Convert multiple lengths in place. * * \tparam T the type containing the units. Must be iterable. From 28464fcd33e1d620cc8a60ad518cb2bbcf8d6875 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 7 Jul 2026 17:51:21 -0700 Subject: [PATCH 637/986] primal: Adds `Sphere::contains(Point, includeBoundary)` Using an efficient test that avoids sqrt. --- src/axom/primal/geometry/Sphere.hpp | 21 ++++++ src/axom/primal/tests/primal_sphere.cpp | 98 +++++++++++++++++++++++++ 2 files changed, 119 insertions(+) diff --git a/src/axom/primal/geometry/Sphere.hpp b/src/axom/primal/geometry/Sphere.hpp index 04965a3cec..a70ec2933f 100644 --- a/src/axom/primal/geometry/Sphere.hpp +++ b/src/axom/primal/geometry/Sphere.hpp @@ -167,6 +167,27 @@ class Sphere return (signed_distance < T {0}) ? primal::ON_NEGATIVE_SIDE : primal::ON_POSITIVE_SIDE; } + /*! + * \brief Tests if a point lies inside this sphere. + * + * \param [in] q The test point + * \param [in] includeBoundary should points on the boundary count as contained? (default true) + * \return true if \a q lies inside (and possibly on) the sphere, false otherwise. + * + * \note This is an exact-arithmetic-free containment test. + * When a tolerance-aware answer is required (for example, to treat points within + * \a EPS of the surface as lying on the boundary), use getOrientation(), + * which returns primal::ON_BOUNDARY within the supplied tolerance, or compare + * the result of computeSignedDistance() against your own scale-aware tolerance. + */ + AXOM_HOST_DEVICE + inline bool contains(const PointType& q, bool includeBoundary = true) const + { + const T dist_sq = (q - m_center).squared_norm(); + const T radius_sq = m_radius * m_radius; + return includeBoundary ? (dist_sq <= radius_sq) : (dist_sq < radius_sq); + } + /*! * \brief Tests if this sphere instance intersects with another sphere. * diff --git a/src/axom/primal/tests/primal_sphere.cpp b/src/axom/primal/tests/primal_sphere.cpp index ef99111a67..31b1a7c9d9 100644 --- a/src/axom/primal/tests/primal_sphere.cpp +++ b/src/axom/primal/tests/primal_sphere.cpp @@ -7,6 +7,8 @@ #include "axom/primal/geometry/Sphere.hpp" #include "axom/primal/geometry/Point.hpp" +#include "axom/core/utilities/Utilities.hpp" + #include "axom/slic.hpp" #include "gtest/gtest.h" @@ -179,6 +181,95 @@ void check_sphere_containment() EXPECT_FALSE(S0.contains(S2)); } +//------------------------------------------------------------------------------ +template +void check_point_containment() +{ + using PointType = primal::Point; + using SphereType = primal::Sphere; + + // An off-origin sphere so the test is not accidentally centered. + PointType center {3.0, 4.0, 0.0}; + const double radius = 5.0; + SphereType sphere(center, radius); + + // --- Analytic checks: known inside / boundary / outside points --------- + + // Center is strictly inside. + EXPECT_TRUE(sphere.contains(center)); + + for(int j = 0; j < NDIMS; ++j) + { + // Strictly inside (half a radius out along axis j): flag-independent. + PointType inside = center; + inside[j] = center[j] + 0.5 * radius; + EXPECT_TRUE(sphere.contains(inside, true)); + EXPECT_TRUE(sphere.contains(inside, false)); + + // Exactly on the boundary. + // An axis-aligned offset of exactly `radius` is representable exactly in floating point + PointType on_pos = center; + on_pos[j] = center[j] + radius; + EXPECT_TRUE(sphere.contains(on_pos, true)); + EXPECT_FALSE(sphere.contains(on_pos, false)); + EXPECT_TRUE(sphere.contains(on_pos)); // default == closed ball + + PointType on_neg = center; + on_neg[j] = center[j] - radius; + EXPECT_TRUE(sphere.contains(on_neg, true)); + EXPECT_FALSE(sphere.contains(on_neg, false)); + EXPECT_TRUE(sphere.contains(on_neg)); + + // Strictly outside (two radii out along axis j): flag-independent. + PointType outside = center; + outside[j] = center[j] + 2. * radius; + EXPECT_FALSE(sphere.contains(outside, true)); + EXPECT_FALSE(sphere.contains(outside, false)); + } + + // --- Agreement with computeSignedDistance() and getOrientation() ------- + // contains() must agree with the sign of the (sqrt-based) signed distance + // and with getOrientation() everywhere except within the provided boundary tolerance + constexpr double tol = 1e-12; + constexpr double lo = -4.; + constexpr double hi = 12.; + constexpr double step = 0.37; // deliberately not aligned to the radius + + int comparisons = 0; + PointType q {}; + for(double x = lo; x <= hi; x += step) + { + q[0] = x; + for(double y = lo; y <= hi; y += step) + { + q[1] = y; + + const double signed_distance = sphere.computeSignedDistance(q); + + // skip points within tolerance of boundary due to possible rounding diffs + if(axom::utilities::isNearlyEqual(signed_distance, 0.0, tol)) + { + continue; + } + + const int orientation = sphere.getOrientation(q, tol); + + const bool contained_closed = sphere.contains(q, true); + EXPECT_EQ(contained_closed, (signed_distance <= 0.0)); + EXPECT_EQ(contained_closed, (orientation != primal::ON_POSITIVE_SIDE)); + + const bool contained_open = sphere.contains(q, false); + EXPECT_EQ(contained_open, (signed_distance < 0.0)); + EXPECT_EQ(contained_open, (orientation == primal::ON_NEGATIVE_SIDE)); + + ++comparisons; + } + } + + // Guard against a degenerate loop silently exercising nothing. + EXPECT_GT(comparisons, 0); +} + //------------------------------------------------------------------------------ template void check_copy_constructor() @@ -280,6 +371,13 @@ TEST(primal_sphere, sphere_sphere_containment) check_sphere_containment<3>(); } +//------------------------------------------------------------------------------ +TEST(primal_sphere, sphere_point_containment) +{ + check_point_containment<2>(); + check_point_containment<3>(); +} + //------------------------------------------------------------------------------ int main(int argc, char* argv[]) { From 23d40b6de527c340cc3d863a0e8c93beb70021c9 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 7 Jul 2026 18:01:17 -0700 Subject: [PATCH 638/986] primal: Use Sphere::contains(Point) in NURBSPatch implementation The current test was using the less efficient version based on distance rather than squared distance. --- src/axom/primal/geometry/NURBSPatch.hpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/axom/primal/geometry/NURBSPatch.hpp b/src/axom/primal/geometry/NURBSPatch.hpp index 6609b4a49e..fefe70ae80 100644 --- a/src/axom/primal/geometry/NURBSPatch.hpp +++ b/src/axom/primal/geometry/NURBSPatch.hpp @@ -4206,10 +4206,8 @@ class NURBSPatch for(const auto& curve : split_trimming_curves) { - auto curve_midpoint = curve.evaluate(0.5 * (curve.getMinKnot() + curve.getMaxKnot())); - bool isInDisk = circle_obj.computeSignedDistance(curve_midpoint) < 0; - - if(isInDisk) + // if (parametric) curve midpoint is in the circle add it to the_disk, otherwise, add it to the_rest + if(circle_obj.contains(curve.evaluate(0.5 * (curve.getMinKnot() + curve.getMaxKnot())), false)) { the_disk.addTrimmingCurve(curve); } From ac1220660cf7e5c16997962e8f71a9c04e0f8767 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 7 Jul 2026 18:15:35 -0700 Subject: [PATCH 639/986] primal (bugfix): Sphere::getVolume() was previously hard-coded for DIM==3 --- src/axom/primal/geometry/Sphere.hpp | 16 +++++++++++---- src/axom/primal/tests/primal_sphere.cpp | 27 +++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/axom/primal/geometry/Sphere.hpp b/src/axom/primal/geometry/Sphere.hpp index a70ec2933f..530abb8a94 100644 --- a/src/axom/primal/geometry/Sphere.hpp +++ b/src/axom/primal/geometry/Sphere.hpp @@ -113,11 +113,19 @@ class Sphere AXOM_HOST_DEVICE inline const PointType& getCenter() const { return m_center; }; - /*! - * \brief Returns the volume of the Sphere. - */ + /// \brief Returns the n-dimensional volume enclosed by the Sphere. AXOM_HOST_DEVICE - inline T getVolume() const { return 4.0 / 3 * M_PI * m_radius * m_radius * m_radius; }; + inline T getVolume() const + { + if constexpr(NDIMS == 2) + { + return M_PI * m_radius * m_radius; + } + else + { + return 4. / 3. * M_PI * m_radius * m_radius * m_radius; + } + } /*! * \brief Computes the signed distance of a point to the Sphere's boundary. diff --git a/src/axom/primal/tests/primal_sphere.cpp b/src/axom/primal/tests/primal_sphere.cpp index 31b1a7c9d9..e3f2a18764 100644 --- a/src/axom/primal/tests/primal_sphere.cpp +++ b/src/axom/primal/tests/primal_sphere.cpp @@ -62,6 +62,26 @@ void check_constructor() check_array(S5.getCenter(), prescribed_center); } +//------------------------------------------------------------------------------ +template +void check_volume() +{ + using SphereType = primal::Sphere; + using PointType = primal::Point; + + constexpr double radius = 2.5; + const SphereType sphere1(PointType::zero(), radius); + const SphereType sphere2(PointType {3., 4., 0.}, radius); + + // 2D "volume" is the enclosed area (pi r^2); 3D is (4/3) pi r^3. + constexpr double expected = + (NDIMS == 2) ? M_PI * radius * radius : 4. / 3. * M_PI * radius * radius * radius; + + EXPECT_DOUBLE_EQ(sphere1.getVolume(), expected); + EXPECT_DOUBLE_EQ(sphere2.getVolume(), expected); + EXPECT_DOUBLE_EQ(sphere1.getVolume(), sphere2.getVolume()); +} + //------------------------------------------------------------------------------ template void check_signed_distance_and_orientation() @@ -336,6 +356,13 @@ TEST(primal_sphere, constructor) check_constructor<3>(); } +//------------------------------------------------------------------------------ +TEST(primal_sphere, volume) +{ + check_volume<2>(); + check_volume<3>(); +} + //------------------------------------------------------------------------------ TEST(primal_sphere, copy_constructor) { From 03d67cfde1b0474efd03e71b9ca6099c907e55f0 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 7 Jul 2026 18:17:47 -0700 Subject: [PATCH 640/986] Updates RELEASE-NOTES --- RELEASE-NOTES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index adecd0770b..71661ff173 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -47,6 +47,8 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ - Sidre: Added `axom::sidre::View::checksum()` and `axom::sidre::Group::checksum()` methods that return checksum values. A `Group::checksum(conduit::Node&)` overload emits diffable checksum metadata for group/view subtrees. - Core: Adds `AXOM_CONSTEXPR_ASSERT` macro for assertions that are usable within `constexpr` contexts - Slam: Adds `make_*_set`, `make_*_relation` and `make_map` helper functions for building sets, relations and maps +- Primal: Adds `primal::Sphere::contains(const Point&, bool includeBoundary = true)` to efficiently test whether + a point lies within a sphere. Use `getOrientation()` when a tolerance-aware boundary classification is needed. ### Removed - Bump: Removed `axom::bump::views::MultiBufferMaterialView`, which was a view type for an obsolete flavor of Blueprint matset. @@ -72,6 +74,7 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ - Core: Avoids a first-use race in `axom::copy()` when multiple OpenMP threads concurrently trigger Umpire fallback host-copy initialization. - Python: Improves lifetime handling for python wrapped sidre entities, including support for external views into numpy arrays. - Sidre: Vector-valued MFEM `QuadratureFunction` fields exported through `MFEMSidreDataCollection` now use Blueprint mcarray component storage under `values`, instead of a single scalar array. +- Primal: Fixes `primal::Sphere::getVolume()`, which was previously hard-coded for volume of 3D sphere ## [Version 0.14.0] - Release date 2026-03-31 From dfc163ccde9f0cc76a90a575d28625461a674231 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 7 Jul 2026 20:21:36 -0700 Subject: [PATCH 641/986] sidre: Adds const overload to View::getData() Uses const_cast (as in getVoidPtr()) since the View is const but the contained data is not. Also updates Python bindings for getData(). --- src/axom/sidre/core/View.hpp | 14 +++++++++++ src/axom/sidre/nanobind_sidre.cpp | 18 +++++++------- src/axom/sidre/tests/sidre_view.cpp | 37 +++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 8 deletions(-) diff --git a/src/axom/sidre/core/View.hpp b/src/axom/sidre/core/View.hpp index 58dab11460..1d992226d2 100644 --- a/src/axom/sidre/core/View.hpp +++ b/src/axom/sidre/core/View.hpp @@ -934,6 +934,7 @@ class View * * \sa getData() */ + /// @{ template DataType getData() { @@ -941,6 +942,19 @@ class View return data; } + /*! + * \overload + */ + template + DataType getData() const + { + // Mirror getVoidPtr() const: the View is logically const + // (its description is not modified) but the data it references remains mutable. + DataType data = const_cast(m_node).value(); + return data; + } + /// @} + /*! * \brief Returns a void pointer to the view's data * diff --git a/src/axom/sidre/nanobind_sidre.cpp b/src/axom/sidre/nanobind_sidre.cpp index 98d3ed8309..9c04940034 100644 --- a/src/axom/sidre/nanobind_sidre.cpp +++ b/src/axom/sidre/nanobind_sidre.cpp @@ -982,14 +982,16 @@ NB_MODULE(pysidre, m_sidre) "Return the string contained in the View.") .def("getDataArray", &viewToNumpyArray, "Return the data held by the View as a numpy array.") - .def("getDataInt", - &View::getData, - nb::rv_policy::reference, - "Return the scalar data held by the View as an python int type.") - .def("getDataFloat", - &View::getData, - nb::rv_policy::reference, - "Return the data held by the View as a python float type (C++ double).") + .def( + "getDataInt", + [](View& self) { return self.getData(); }, + nb::rv_policy::reference, + "Return the scalar data held by the View as an python int type.") + .def( + "getDataFloat", + [](View& self) { return self.getData(); }, + nb::rv_policy::reference, + "Return the data held by the View as a python float type (C++ double).") .def("print", nb::overload_cast<>(&View::print, nb::const_), "Print JSON description of the View.") diff --git a/src/axom/sidre/tests/sidre_view.cpp b/src/axom/sidre/tests/sidre_view.cpp index 37068f48db..6224ff3e3b 100644 --- a/src/axom/sidre/tests/sidre_view.cpp +++ b/src/axom/sidre/tests/sidre_view.cpp @@ -373,6 +373,43 @@ TEST(sidre_view, scalar_view) delete ds; } +//------------------------------------------------------------------------------ +// Regression test for https://github.com/LLNL/axom/issues/1695 +// The templated View::getData() must be callable through a const View. +TEST(sidre_view, const_get_data) +{ + constexpr int num_elts = 4; + + DataStore ds; + Group* root = ds.getRoot(); + + // A scalar view and an array view to cover both value and pointer DataTypes. + { + root->createViewScalar("i0", 42); + + View* arrView = root->createViewAndAllocate("arr", INT_ID, num_elts); + int* arrData = arrView->getData(); + for(int i = 0; i < num_elts; ++i) + { + arrData[i] = 10 * i; + } + } + + // Access the same views through a const reference + { + const View& constScalarView = *root->getView("i0"); + EXPECT_EQ(42, constScalarView.getData()); + + const View& constArrView = *root->getView("arr"); + int* constAccessData = constArrView.getData(); + ASSERT_NE(nullptr, constAccessData); + for(int i = 0; i < num_elts; ++i) + { + EXPECT_EQ(10 * i, constAccessData[i]); + } + } +} + //------------------------------------------------------------------------------ TEST(sidre_view, io_state_string_compatibility) From 181d0eb9180d55217c2ac522e21238b717fe0a9f Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 7 Jul 2026 20:25:25 -0700 Subject: [PATCH 642/986] sidre: Fixes type -- missing operator<< in a log statement --- src/axom/sidre/core/View.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/axom/sidre/core/View.hpp b/src/axom/sidre/core/View.hpp index 1d992226d2..8663e60159 100644 --- a/src/axom/sidre/core/View.hpp +++ b/src/axom/sidre/core/View.hpp @@ -921,7 +921,7 @@ class View SLIC_CHECK_MSG( isAllocated(), SIDRE_VIEW_LOG_PREPEND << "No view data present, memory has not been allocated."); - SLIC_CHECK_MSG(isDescribed(), SIDRE_VIEW_LOG_PREPEND "View data description not present."); + SLIC_CHECK_MSG(isDescribed(), SIDRE_VIEW_LOG_PREPEND << "View data description not present."); // this will return a default value return m_node.value(); From 5f2333146feb8d004b5a2a71f53c2f4a965835f6 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 7 Jul 2026 20:36:09 -0700 Subject: [PATCH 643/986] sidre: Adds const overloads to Buffer::getVoidPointer() and Buffer::getData() --- src/axom/sidre/core/Buffer.hpp | 31 +++++++++++++++++++++++++ src/axom/sidre/tests/sidre_buffer.cpp | 33 +++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/src/axom/sidre/core/Buffer.hpp b/src/axom/sidre/core/Buffer.hpp index ed220936a6..e5cf9bdd50 100644 --- a/src/axom/sidre/core/Buffer.hpp +++ b/src/axom/sidre/core/Buffer.hpp @@ -93,8 +93,19 @@ class Buffer /*! * \brief Return void-pointer to data held by Buffer. */ + /// @{ void* getVoidPtr() { return m_node.data_ptr(); } + /*! + * \overload + * + * \note A const Buffer still exposes its data as a mutable pointer: + * const-ness refers to the Buffer's description, not to the data it holds. + * \sa View::getVoidPtr() + */ + void* getVoidPtr() const { return const_cast(m_node).data_ptr(); } + /// @} + /*! * \brief Return data held by Buffer (return type is type caller assigns * return value to). @@ -102,6 +113,7 @@ class Buffer * Note that if Buffer is not allocated, an empty Conduit * Node::Value is returned. */ + /// @{ Node::Value getData() { if(!isAllocated()) @@ -113,6 +125,25 @@ class Buffer return m_node.value(); } + /*! + * \overload + * + * \note A const Buffer exposes its data as a mutable Node::Value + * const-ness refers to the Buffer's description, not its data. + * \sa View::getdata() const + */ + Node::Value getData() const + { + if(!isAllocated()) + { + SLIC_CHECK_MSG(isAllocated(), "Buffer data is not allocated."); + return Node().value(); + } + + return const_cast(m_node).value(); + } + /// @} + /*! * \brief Return type of data owned by this Buffer object. */ diff --git a/src/axom/sidre/tests/sidre_buffer.cpp b/src/axom/sidre/tests/sidre_buffer.cpp index 2c66a24a99..860781677a 100644 --- a/src/axom/sidre/tests/sidre_buffer.cpp +++ b/src/axom/sidre/tests/sidre_buffer.cpp @@ -118,6 +118,39 @@ TEST(sidre_buffer, alloc_buffer_for_int_array) delete ds; } +//------------------------------------------------------------------------------ +// Regression test (companion to https://github.com/LLNL/axom/issues/1695): +// Buffer::getData() and Buffer::getVoidPtr() must be callable on a const Buffer, +// e.g. one obtained through View::getBuffer() const. +TEST(sidre_buffer, const_get_data) +{ + DataStore ds; + + Buffer* dbuff = ds.createBuffer(); + dbuff->allocate(INT_ID, 10); + + int* data_ptr = dbuff->getData(); + for(int i = 0; i < 10; i++) + { + data_ptr[i] = i * i; + } + + // Access through a const reference + const Buffer& constBuff = *dbuff; + + void* vptr = constBuff.getVoidPtr(); + EXPECT_EQ(data_ptr, static_cast(vptr)); + + int* const_access = constBuff.getData(); + ASSERT_NE(nullptr, const_access); + for(int i = 0; i < 10; i++) + { + EXPECT_EQ(i * i, const_access[i]); + } + + dbuff->deallocate(); +} + //------------------------------------------------------------------------------ TEST(sidre_buffer, init_buffer_for_int_array) From 9705303a4c15eb5b91268557698ae941d26e9a97 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 7 Jul 2026 20:42:50 -0700 Subject: [PATCH 644/986] sidre (const-correctness): Marks View::getAtrributeScalar() as const --- src/axom/sidre/core/View.hpp | 21 +++++++++------------ src/axom/sidre/tests/sidre_attribute.cpp | 8 ++++++++ 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/axom/sidre/core/View.hpp b/src/axom/sidre/core/View.hpp index 8663e60159..74e7bd0909 100644 --- a/src/axom/sidre/core/View.hpp +++ b/src/axom/sidre/core/View.hpp @@ -1251,14 +1251,13 @@ class View } /*! - * \brief Lightweight templated wrapper around getAttributeScalar() - * that can be used when you are calling getAttributeScalar(), but not - * assigning the return type. + * \brief Lightweight templated wrapper around getAttributeScalar() that can be used + * when you are calling getAttributeScalar(), but not assigning the return type. * * \sa getAttributeScalar() */ template - DataType getAttributeScalar(IndexType idx) + DataType getAttributeScalar(IndexType idx) const { const Attribute* attr = getAttribute(idx); const Node& node = m_attr_values.getValueNodeRef(attr); @@ -1267,14 +1266,13 @@ class View } /*! - * \brief Lightweight templated wrapper around getAttributeScalar() - * that can be used when you are calling getAttributeScalar(), but not - * assigning the return type. + * \brief Lightweight templated wrapper around getAttributeScalar() that can be used + * when you are calling getAttributeScalar(), but not assigning the return type. * * \sa getAttributeScalar() */ template - DataType getAttributeScalar(const std::string& name) + DataType getAttributeScalar(const std::string& name) const { const Attribute* attr = getAttribute(name); const Node& node = m_attr_values.getValueNodeRef(attr); @@ -1283,14 +1281,13 @@ class View } /*! - * \brief Lightweight templated wrapper around getAttributeScalar() - * that can be used when you are calling getAttributeScalar(), but not - * assigning the return type. + * \brief Lightweight templated wrapper around getAttributeScalar() that can be used + * when you are calling getAttributeScalar(), but not assigning the return type. * * \sa getAttributeScalar() */ template - DataType getAttributeScalar(const Attribute* attr) + DataType getAttributeScalar(const Attribute* attr) const { SLIC_CHECK_MSG(attr != nullptr, SIDRE_VIEW_LOG_PREPEND << "getAttributeScalar: called with a null Attribute"); diff --git a/src/axom/sidre/tests/sidre_attribute.cpp b/src/axom/sidre/tests/sidre_attribute.cpp index 7d8a133c21..64ba94591b 100644 --- a/src/axom/sidre/tests/sidre_attribute.cpp +++ b/src/axom/sidre/tests/sidre_attribute.cpp @@ -553,6 +553,14 @@ TEST(sidre_attribute, overloads) EXPECT_EQ(g_dump_yes, view->getAttributeScalar(idump)); EXPECT_EQ(g_dump_yes, view->getAttributeScalar(g_name_dump)); + // The templated getAttributeScalar() must also be callable on a const View + { + const View& constView = *view; + EXPECT_EQ(g_dump_yes, constView.getAttributeScalar(attr_dump)); + EXPECT_EQ(g_dump_yes, constView.getAttributeScalar(idump)); + EXPECT_EQ(g_dump_yes, constView.getAttributeScalar(g_name_dump)); + } + const Node& node1 = view->getAttributeNodeRef(attr_dump); EXPECT_EQ(g_dump_yes, node1.as_int()); const Node& node2 = view->getAttributeNodeRef(idump); From d7bc34a63d6079147d0460750134ab56db7dc685 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 7 Jul 2026 20:47:22 -0700 Subject: [PATCH 645/986] Updates RELEASE_NOTES --- RELEASE-NOTES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 71661ff173..4a8f7b8f7d 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -75,6 +75,10 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ - Python: Improves lifetime handling for python wrapped sidre entities, including support for external views into numpy arrays. - Sidre: Vector-valued MFEM `QuadratureFunction` fields exported through `MFEMSidreDataCollection` now use Blueprint mcarray component storage under `values`, instead of a single scalar array. - Primal: Fixes `primal::Sphere::getVolume()`, which was previously hard-coded for volume of 3D sphere +- Sidre: Adds a `const` overload of the templated `axom::sidre::View::getData()`, +and marked the templated `axom::sidre::View::getAttributeScalar()` overloads `const` +so they can be called on a `const View`. Also added `const` overloads for `axom::sidre::Buffer::getData()` +and `axom::sidre::Buffer::getVoidPtr()` so they can be called on a `const Buffer`. ## [Version 0.14.0] - Release date 2026-03-31 From 0a5b4fc45b364044fe8376bc7219b337fb5bed10 Mon Sep 17 00:00:00 2001 From: Guy Bergel Date: Wed, 8 Jul 2026 10:52:41 -0700 Subject: [PATCH 646/986] make MPI buffer managed by non-collective root communicator to avoid deleting the buffer before message is finished being sent --- src/axom/lumberjack/MPIUtility.cpp | 13 +++- src/axom/lumberjack/MPIUtility.hpp | 19 ++++++ .../NonCollectiveRootCommunicator.cpp | 63 ++++++++++++++++++- .../NonCollectiveRootCommunicator.hpp | 13 ++++ 4 files changed, 103 insertions(+), 5 deletions(-) diff --git a/src/axom/lumberjack/MPIUtility.cpp b/src/axom/lumberjack/MPIUtility.cpp index 202473d372..68e8030536 100644 --- a/src/axom/lumberjack/MPIUtility.cpp +++ b/src/axom/lumberjack/MPIUtility.cpp @@ -68,16 +68,25 @@ const char* mpiBlockingReceiveIfMessagesExist(MPI_Comm comm) return charArray; } -void mpiNonBlockingSendMessages(MPI_Comm comm, int destinationRank, const char* packedMessagesToBeSent) +MPI_Request mpiNonBlockingSendMessagesWithRequest(MPI_Comm comm, + int destinationRank, + const char* packedMessagesToBeSent) { MPI_Request mpiRequest; MPI_Isend(const_cast(packedMessagesToBeSent), - strlen(packedMessagesToBeSent), + std::strlen(packedMessagesToBeSent), MPI_CHAR, destinationRank, LJ_TAG, comm, &mpiRequest); + return mpiRequest; +} + +void mpiNonBlockingSendMessages(MPI_Comm comm, int destinationRank, const char* packedMessagesToBeSent) +{ + MPI_Request mpiRequest = + mpiNonBlockingSendMessagesWithRequest(comm, destinationRank, packedMessagesToBeSent); MPI_Request_free(&mpiRequest); } diff --git a/src/axom/lumberjack/MPIUtility.hpp b/src/axom/lumberjack/MPIUtility.hpp index 166b9503f2..2878d38a02 100644 --- a/src/axom/lumberjack/MPIUtility.hpp +++ b/src/axom/lumberjack/MPIUtility.hpp @@ -45,6 +45,25 @@ const char* mpiBlockingReceiveMessages(MPI_Comm comm); */ const char* mpiBlockingReceiveIfMessagesExist(MPI_Comm comm); +/*! + ***************************************************************************** + * \brief Starts a non-blocking send to the given rank. + * + * The caller owns the returned MPI_Request and must keep + * packedMessagesToBeSent valid until the request completes. + * + * \param [in] comm The MPI Communicator. + * \param [in] destinationRank Where the Message classes is being sent. + * \param [in,out] packedMessagesToBeSent All of the Message classes to be sent + * packed together. + * + * \return Request associated with the non-blocking send. + ***************************************************************************** + */ +MPI_Request mpiNonBlockingSendMessagesWithRequest(MPI_Comm comm, + int destinationRank, + const char* packedMessagesToBeSent); + /*! ***************************************************************************** * \brief Sends all Message sent to the given rank. diff --git a/src/axom/lumberjack/NonCollectiveRootCommunicator.cpp b/src/axom/lumberjack/NonCollectiveRootCommunicator.cpp index 554a0489a0..80cdf86d51 100644 --- a/src/axom/lumberjack/NonCollectiveRootCommunicator.cpp +++ b/src/axom/lumberjack/NonCollectiveRootCommunicator.cpp @@ -14,8 +14,10 @@ ****************************************************************************** */ -#include #include +#include +#include +#include #include "axom/lumberjack/NonCollectiveRootCommunicator.hpp" #include "axom/lumberjack/MPIUtility.hpp" @@ -26,6 +28,7 @@ namespace lumberjack { void NonCollectiveRootCommunicator::initialize(MPI_Comm comm, int ranksLimit) { + m_pendingSends.clear(); if(ranksLimit < 1) { std::cerr << "Error: Ranks limit passed to NonCollectiveRootCommunicator " @@ -41,7 +44,11 @@ void NonCollectiveRootCommunicator::initialize(MPI_Comm comm, int ranksLimit) m_ranksLimit = ranksLimit; } -void NonCollectiveRootCommunicator::finalize() { MPI_Comm_free(&m_mpiComm); } +void NonCollectiveRootCommunicator::finalize() +{ + releasePendingSends(); + MPI_Comm_free(&m_mpiComm); +} MPI_Comm NonCollectiveRootCommunicator::comm() { return m_mpiComm; } @@ -85,11 +92,61 @@ void NonCollectiveRootCommunicator::push(const char* packedMessagesToBeSent, } else { + drainCompletedSends(); if(isPackedMessagesEmpty(packedMessagesToBeSent) == false) { - mpiNonBlockingSendMessages(m_mpiComm, 0, packedMessagesToBeSent); + const int messageSize = static_cast(std::strlen(packedMessagesToBeSent)); + PendingSend pendingSend; + pendingSend.request = MPI_REQUEST_NULL; + pendingSend.buffer.reset(new char[messageSize + 1]); + std::memcpy(pendingSend.buffer.get(), packedMessagesToBeSent, messageSize + 1); + + pendingSend.request = + mpiNonBlockingSendMessagesWithRequest(m_mpiComm, 0, pendingSend.buffer.get()); + m_pendingSends.push_back(std::move(pendingSend)); + } + drainCompletedSends(); + } +} + +void NonCollectiveRootCommunicator::drainCompletedSends() +{ + for(auto it = m_pendingSends.begin(); it != m_pendingSends.end();) + { + int complete = 0; + MPI_Test(&it->request, &complete, MPI_STATUS_IGNORE); + if(complete) + { + it = m_pendingSends.erase(it); + } + else + { + ++it; + } + } +} + +void NonCollectiveRootCommunicator::releasePendingSends() +{ + for(auto& pendingSend : m_pendingSends) + { + if(pendingSend.request == MPI_REQUEST_NULL) + { + continue; + } + + int complete = 0; + MPI_Test(&pendingSend.request, &complete, MPI_STATUS_IGNORE); + if(!complete) + { + // Keep the send buffer valid without blocking finalization. This + // communicator is used for best-effort non-collective error reporting; + // waiting here could hang if root is no longer receiving. + MPI_Request_free(&pendingSend.request); + pendingSend.buffer.release(); } } + m_pendingSends.clear(); } bool NonCollectiveRootCommunicator::isOutputNode() diff --git a/src/axom/lumberjack/NonCollectiveRootCommunicator.hpp b/src/axom/lumberjack/NonCollectiveRootCommunicator.hpp index 1c0345b1d8..6298c27c2b 100644 --- a/src/axom/lumberjack/NonCollectiveRootCommunicator.hpp +++ b/src/axom/lumberjack/NonCollectiveRootCommunicator.hpp @@ -19,6 +19,9 @@ #include "axom/lumberjack/Lumberjack.hpp" #include "axom/lumberjack/Communicator.hpp" +#include +#include + namespace axom { namespace lumberjack @@ -140,11 +143,21 @@ class NonCollectiveRootCommunicator : public axom::lumberjack::Communicator double startTime(); private: + struct PendingSend + { + MPI_Request request; + std::unique_ptr buffer; + }; + + void drainCompletedSends(); + void releasePendingSends(); + MPI_Comm m_mpiComm; int m_mpiCommRank; int m_mpiCommSize; int m_ranksLimit; double m_startTime; + std::vector m_pendingSends; }; } // end namespace lumberjack From 9ea68e64de38078c2eedcb058faeeb91da4fedf2 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 9 Jul 2026 11:42:01 -0700 Subject: [PATCH 647/986] 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 c6c96b66f5c3b955ac54cdafc250da8b8335c81c Mon Sep 17 00:00:00 2001 From: Brian Han Date: Wed, 6 May 2026 16:39:36 -0700 Subject: [PATCH 648/986] Wip - intel compiler not working --- .uberenv_config.json | 2 +- scripts/spack/configs/versions.yaml | 6 ++--- scripts/spack/packages/axom/package.py | 33 ++++++++++++++++---------- scripts/spack/specs.json | 2 +- src/CMakeLists.txt | 2 +- 5 files changed, 27 insertions(+), 18 deletions(-) diff --git a/.uberenv_config.json b/.uberenv_config.json index 810d574338..12524fa366 100644 --- a/.uberenv_config.json +++ b/.uberenv_config.json @@ -6,7 +6,7 @@ "spack_url": "https://github.com/spack/spack.git", "spack_commit": "2e2169d5282d166f63e3ee4db8d4446c43cefa8a", "spack_configs_path": "scripts/spack/configs", -"spack_packages_commit": "17ea6a6d483fc0bde1b2cca96ddaad0a10f2ef69", +"spack_packages_commit": "0e093dad700a9be836b0d4aefc4bb5183e4990bd", "spack_packages_path": "scripts/spack/packages", "spack_concretizer": "clingo", "vcpkg_url": "https://github.com/microsoft/vcpkg", diff --git a/scripts/spack/configs/versions.yaml b/scripts/spack/configs/versions.yaml index cd91af6af0..a678fbe1b4 100644 --- a/scripts/spack/configs/versions.yaml +++ b/scripts/spack/configs/versions.yaml @@ -13,7 +13,7 @@ packages: # v2025.12 camp: require: - - spec: "@git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325=main" + - spec: "@git.36b724872427cbea8f4aedab31da6af21d221cf3=main" conduit: require: - spec: "@0.9.5" @@ -32,10 +32,10 @@ packages: # v2025.12.1 raja: require: - - spec: "@git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2=develop" + - spec: "@git.c2573cbe8b1941a0d0f335e5f5f0e43779308215=develop" scr: require: - spec: "@3.0.1" umpire: require: - - spec: "@2025.12.0" + - spec: "@git.f81afcab0e07c431288e950d471aa739df68f7f7=develop" diff --git a/scripts/spack/packages/axom/package.py b/scripts/spack/packages/axom/package.py index a0f54db43c..cee8948cef 100644 --- a/scripts/spack/packages/axom/package.py +++ b/scripts/spack/packages/axom/package.py @@ -113,9 +113,6 @@ class Axom(CachedCMakePackage, CudaPackage, ROCmPackage): variant("tools", default=True, description="Build tools") variant("tutorials", default=True, description="Build tutorials") - # Hard requirement after Axom 0.6.1 - variant("cpp14", default=True, description="Build with C++14 support") - variant("fortran", default=True, description="Build with Fortran support") variant("python", default=False, description="Build python support") @@ -162,6 +159,19 @@ class Axom(CachedCMakePackage, CudaPackage, ROCmPackage): varmsg = "Build development tools (such as Sphinx, Doxygen, etc...)" variant("devtools", default=False, description=varmsg) + variant( + "cxxstd", + default="20", + values=("11", "14", "17", "20"), + description="C++ standard to build with", + ) + # C++14 required as of 0.7.0 + conflicts("cxxstd=11", when="@0.7.0:") + # C++17 required as of 0.12.0 + conflicts("cxxstd=14", when="@0.12.0:") + # C++17 required as of unreleased 0.15.0 (Should be 0.15.0) + conflicts("cxxstd=17", when="@0.14.0:") + # ----------------------------------------------------------------------- # Dependencies # ----------------------------------------------------------------------- @@ -330,8 +340,6 @@ class Axom(CachedCMakePackage, CudaPackage, ROCmPackage): # ----------------------------------------------------------------------- # Conflicts # ----------------------------------------------------------------------- - # Hard requirement after Axom 0.6.1 - conflicts("~cpp14", when="@0.6.2:") # Conduit's cmake config files moved and < 0.4.0 can't find it conflicts("^conduit@0.7.2:", when="@:0.4.0") @@ -348,6 +356,9 @@ class Axom(CachedCMakePackage, CudaPackage, ROCmPackage): conflicts("^blt@:0.3.6", when="+rocm") + # python interface requires mpi + conflicts("~mpi", when="+python") + def flag_handler(self, name, flags): if self.spec.satisfies("%cce") and name == "fflags": flags.append("-ef") @@ -389,6 +400,10 @@ def cache_name(self): special_case, ) + @property + def cxx_std(self): + return self.spec.variants.get("cxxstd").value + def initconfig_compiler_entries(self): spec = self.spec entries = super().initconfig_compiler_entries() @@ -410,9 +425,6 @@ def initconfig_compiler_entries(self): else: entries.append(cmake_cache_option("ENABLE_FORTRAN", False)) - if spec.satisfies("+cpp14") and spec.satisfies("@:0.6.1"): - entries.append(cmake_cache_string("BLT_CXX_STD", "c++14", "")) - # Add optimization flag workaround for builds with cray compiler if spec.satisfies("%cce"): entries.append(cmake_cache_string("CMAKE_CXX_FLAGS_DEBUG", "-O1 -g")) @@ -450,10 +462,7 @@ def initconfig_hardware_entries(self): if spec.satisfies("^blt@:0.5.1"): # This is handled internally by BLT now - if spec.satisfies("+cpp14"): - cudaflags += " -std=c++14" - else: - cudaflags += " -std=c++11" + cudaflags += " -std=c++14" entries.append(cmake_cache_string("CMAKE_CUDA_FLAGS", cudaflags, force=True)) entries.append("# nvcc does not like gtest's 'pthreads' flag\n") diff --git a/scripts/spack/specs.json b/scripts/spack/specs.json index 09a02a1335..833a6961bf 100644 --- a/scripts/spack/specs.json +++ b/scripts/spack/specs.json @@ -18,7 +18,7 @@ "__comment__":"# compiler preferences to prevent compiler mixing", "__comment__":"# Configs are for dane/rzwhippet", "toss_4_x86_64_ib": - [ "+python+devtools+hdf5+mfem+c2c+adiak+caliper %%intel_25 ^mfem cxxflags=-fp-speculation=safe %intel_25", + [ "~python+devtools+hdf5+mfem+c2c+adiak+caliper %%intel_25 ^mfem cxxflags=-fp-speculation=safe %intel_25 ^raja cxxstd=20", "+python+devtools+hdf5+mfem+c2c+scr+adiak+caliper %gcc_13", "+python+devtools+hdf5+mfem+c2c+scr+adiak+caliper+opencascade %clang_19"], diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 08eabf44f7..923befd920 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -46,7 +46,7 @@ if ("${PROJECT_SOURCE_DIR}" STREQUAL "${CMAKE_SOURCE_DIR}") # Set some default BLT options before loading BLT only if not included in # another project if (NOT BLT_CXX_STD) - set(BLT_CXX_STD "c++17" CACHE STRING "") + set(BLT_CXX_STD "c++20" CACHE STRING "") endif() # These are not used in Axom, turn them off From 4d9d02979fd2bac86ba476c1e4ff801d0a390282 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Tue, 2 Jun 2026 16:12:31 -0700 Subject: [PATCH 649/986] Use namespaced umpire:: --- src/cmake/thirdparty/SetupAxomThirdParty.cmake | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cmake/thirdparty/SetupAxomThirdParty.cmake b/src/cmake/thirdparty/SetupAxomThirdParty.cmake index c46f1c6966..12e544d9ac 100644 --- a/src/cmake/thirdparty/SetupAxomThirdParty.cmake +++ b/src/cmake/thirdparty/SetupAxomThirdParty.cmake @@ -53,16 +53,16 @@ if (UMPIRE_DIR) axom_assert_is_directory(DIR_VARIABLE UMPIRE_DIR) find_dependency(umpire REQUIRED PATHS "${UMPIRE_DIR}" NO_SYSTEM_ENVIRONMENT_PATH) axom_assert_find_succeeded(PROJECT_NAME Umpire - TARGET umpire + TARGET umpire::umpire DIR_VARIABLE UMPIRE_DIR) set(UMPIRE_FOUND TRUE) - blt_convert_to_system_includes(TARGET umpire) + blt_convert_to_system_includes(TARGET umpire::umpire) # Check whether the Umpire defines symbols for shared memory blt_check_code_compiles(CODE_COMPILES UMPIRE_SHARED_MEMORY VERBOSE_OUTPUT OFF - DEPENDS_ON umpire + DEPENDS_ON umpire::umpire SOURCE_STRING [=[ #include #if defined(UMPIRE_ENABLE_IPC_SHARED_MEMORY) || defined(UMPIRE_ENABLE_MPI3_SHARED_MEMORY) From 314d041072a6cddd2a7d40fd5961f61036f6536d Mon Sep 17 00:00:00 2001 From: Brian Han Date: Wed, 3 Jun 2026 09:56:40 -0700 Subject: [PATCH 650/986] Trying newer spack-packages commit for cxxstd 20 for raja --- .uberenv_config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.uberenv_config.json b/.uberenv_config.json index 12524fa366..511a8fde3e 100644 --- a/.uberenv_config.json +++ b/.uberenv_config.json @@ -6,7 +6,7 @@ "spack_url": "https://github.com/spack/spack.git", "spack_commit": "2e2169d5282d166f63e3ee4db8d4446c43cefa8a", "spack_configs_path": "scripts/spack/configs", -"spack_packages_commit": "0e093dad700a9be836b0d4aefc4bb5183e4990bd", +"spack_packages_commit": "be7d9351f901af0d7bc179a79cb14229a1785e67", "spack_packages_path": "scripts/spack/packages", "spack_concretizer": "clingo", "vcpkg_url": "https://github.com/microsoft/vcpkg", From 623d849c3c19072d5deeeb6ae2cada2f2c174b28 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Mon, 8 Jun 2026 09:57:29 -0700 Subject: [PATCH 651/986] Test versions for raja and umpire --- scripts/spack/configs/versions.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/spack/configs/versions.yaml b/scripts/spack/configs/versions.yaml index a678fbe1b4..14455836a8 100644 --- a/scripts/spack/configs/versions.yaml +++ b/scripts/spack/configs/versions.yaml @@ -10,10 +10,10 @@ packages: caliper: require: - spec: "@git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c=master" - # v2025.12 + # testing camp: require: - - spec: "@git.36b724872427cbea8f4aedab31da6af21d221cf3=main" + - spec: "@git.e75ab64c029aa27c80593715cb2a3ccad7453c8c=main" conduit: require: - spec: "@0.9.5" @@ -29,10 +29,10 @@ packages: py-jsonschema: require: - spec: "@:4.17" #Anything after this requires py-rpds which requires rust and adds 40 minutes - # v2025.12.1 + # testing raja: require: - - spec: "@git.c2573cbe8b1941a0d0f335e5f5f0e43779308215=develop" + - spec: "@develop" scr: require: - spec: "@3.0.1" From 953de46e265c1492d9f9990ecd14c330ec9aabd7 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Mon, 8 Jun 2026 13:31:56 -0700 Subject: [PATCH 652/986] Set mfem to use cxxstd=20 --- scripts/spack/configs/defaults.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/spack/configs/defaults.yaml b/scripts/spack/configs/defaults.yaml index 54614f30bf..62585ecfae 100644 --- a/scripts/spack/configs/defaults.yaml +++ b/scripts/spack/configs/defaults.yaml @@ -14,7 +14,7 @@ packages: - spec: "~shared~mpi" mfem: require: - - spec: "cxxstd=17" + - spec: "cxxstd=20" # note: for mfem, we use the default +shared to allow PIC flag opencascade: require: From 89db3cd290cb0ac3fd9429f32d995e41c8e35ac0 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Tue, 9 Jun 2026 07:58:41 -0700 Subject: [PATCH 653/986] Try updating to cray-mpich 9.0.1 --- .../configs/toss_4_x86_64_ib_cray/spack.yaml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/scripts/spack/configs/toss_4_x86_64_ib_cray/spack.yaml b/scripts/spack/configs/toss_4_x86_64_ib_cray/spack.yaml index dd49d5ba25..477578c69c 100644 --- a/scripts/spack/configs/toss_4_x86_64_ib_cray/spack.yaml +++ b/scripts/spack/configs/toss_4_x86_64_ib_cray/spack.yaml @@ -31,7 +31,7 @@ spack: when: '%cxx' - spec: '%fortran=llvm-amdgpu@6.4.3' when: '%fortran' - - spec: '%cray-mpich@8.1.29.rocm_6.4.3' + - spec: '%cray-mpich@9.0.1.rocm_6.4.3' when: '%mpi' rocm_6_3_1: - spec: fflags=-Mfreeform @@ -41,7 +41,7 @@ spack: when: '%cxx' - spec: '%fortran=llvm-amdgpu@6.3.1' when: '%fortran' - - spec: '%cray-mpich@8.1.29.rocm_6_3_1' + - spec: '%cray-mpich@9.0.1.rocm_6_3_1' when: '%mpi' cce_20: # Flag for lowercase Fortran module names @@ -52,7 +52,7 @@ spack: when: '%cxx' - spec: '%fortran=cce@20.0.0' when: '%fortran' - - spec: '%cray-mpich@8.1.32.cce_20' + - spec: '%cray-mpich@9.0.1.cce_20' when: '%mpi' packages: @@ -180,12 +180,12 @@ spack: cray-mpich: buildable: false externals: - - spec: cray-mpich@8.1.29.rocm_6_3_1+slurm - prefix: /usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.3.1/ - - spec: cray-mpich@8.1.29.rocm_6_4_3+slurm - prefix: /usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.4.3/ - - spec: cray-mpich@8.1.32.cce_20+slurm - prefix: /usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.32-rocmcc-6.4.3/ + - spec: cray-mpich@9.0.1.rocm_6_3_1+slurm + prefix: /usr/tce/packages/cray-mpich-tce/cray-mpich-9.0.1-rocmcc-6.3.1/ + - spec: cray-mpich@9.0.1.rocm_6_4_3+slurm + prefix: /usr/tce/packages/cray-mpich-tce/cray-mpich-9.0.1-rocmcc-6.4.3/ + - spec: cray-mpich@9.0.1.cce_20+slurm + prefix: /usr/tce/packages/cray-mpich-tce/cray-mpich-9.0.1-rocmcc-6.4.3/ # blas is a bit more complicated because its a virtual package so fake it with # the following per spack docs From 7b2cee242b4080313c2594c1476b220f00575c6d Mon Sep 17 00:00:00 2001 From: Brian Han Date: Fri, 12 Jun 2026 15:52:05 -0700 Subject: [PATCH 654/986] seemingly working raja pre-std::memcpy change --- scripts/spack/configs/versions.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/spack/configs/versions.yaml b/scripts/spack/configs/versions.yaml index 14455836a8..51a6766d72 100644 --- a/scripts/spack/configs/versions.yaml +++ b/scripts/spack/configs/versions.yaml @@ -29,10 +29,10 @@ packages: py-jsonschema: require: - spec: "@:4.17" #Anything after this requires py-rpds which requires rust and adds 40 minutes - # testing + # testing commit before PR #2005 (HIP error std::memcpy TypeConverts) raja: require: - - spec: "@develop" + - spec: "@c3e4576eccff2435c358b691e4fcad4e601a7441=develop" scr: require: - spec: "@3.0.1" From ea778fdf8f8e5b66eadef02c4fa00b513de949e9 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Fri, 12 Jun 2026 15:53:01 -0700 Subject: [PATCH 655/986] Add workaround for wrong gcc-toolset 12 for C++20 with +fortran --- scripts/spack/packages/axom/package.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/scripts/spack/packages/axom/package.py b/scripts/spack/packages/axom/package.py index cee8948cef..548aaadf2c 100644 --- a/scripts/spack/packages/axom/package.py +++ b/scripts/spack/packages/axom/package.py @@ -514,20 +514,32 @@ def initconfig_hardware_entries(self): hip_link_flags += "-L{0} -Wl,-rpath,{0}".format(lib_path) if spec.satisfies("+fortran"): - link_remove_list = [] + link_lib_remove_list = [] + link_dir_remove_list = [] + + if self.cxx_std == "20": + link_dir_remove_list += ["/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12"] + link_dir_remove_list += ["/opt/rh/gcc-toolset-12/root/usr/lib64"] # Remove extra link library for crayftn if self.is_fortran_compiler("crayftn"): - link_remove_list += ["unwind"] + link_lib_remove_list += ["unwind"] # Remove injected OpenMP stub library if spec.satisfies("+openmp"): - link_remove_list += ["ompstub"] + link_lib_remove_list += ["ompstub"] + + if link_lib_remove_list: + entries.append( + cmake_cache_string( + "BLT_CMAKE_IMPLICIT_LINK_LIBRARIES_EXCLUDE", ";".join(link_lib_remove_list) + ) + ) - if link_remove_list: + if link_dir_remove_list: entries.append( cmake_cache_string( - "BLT_CMAKE_IMPLICIT_LINK_LIBRARIES_EXCLUDE", ";".join(link_remove_list) + "BLT_CMAKE_IMPLICIT_LINK_DIRECTORIES_EXCLUDE", ";".join(link_dir_remove_list) ) ) From 552dcd9e71e31715ed040c7503adebcd17cfe310 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Mon, 15 Jun 2026 15:19:20 -0700 Subject: [PATCH 656/986] Revert "Try updating to cray-mpich 9.0.1" - mfem hangs with newer MPI This reverts commit 86c04bcaccc5d3bc7eefcb15794d0abfbd3fa547. --- .../configs/toss_4_x86_64_ib_cray/spack.yaml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/scripts/spack/configs/toss_4_x86_64_ib_cray/spack.yaml b/scripts/spack/configs/toss_4_x86_64_ib_cray/spack.yaml index 477578c69c..dd49d5ba25 100644 --- a/scripts/spack/configs/toss_4_x86_64_ib_cray/spack.yaml +++ b/scripts/spack/configs/toss_4_x86_64_ib_cray/spack.yaml @@ -31,7 +31,7 @@ spack: when: '%cxx' - spec: '%fortran=llvm-amdgpu@6.4.3' when: '%fortran' - - spec: '%cray-mpich@9.0.1.rocm_6.4.3' + - spec: '%cray-mpich@8.1.29.rocm_6.4.3' when: '%mpi' rocm_6_3_1: - spec: fflags=-Mfreeform @@ -41,7 +41,7 @@ spack: when: '%cxx' - spec: '%fortran=llvm-amdgpu@6.3.1' when: '%fortran' - - spec: '%cray-mpich@9.0.1.rocm_6_3_1' + - spec: '%cray-mpich@8.1.29.rocm_6_3_1' when: '%mpi' cce_20: # Flag for lowercase Fortran module names @@ -52,7 +52,7 @@ spack: when: '%cxx' - spec: '%fortran=cce@20.0.0' when: '%fortran' - - spec: '%cray-mpich@9.0.1.cce_20' + - spec: '%cray-mpich@8.1.32.cce_20' when: '%mpi' packages: @@ -180,12 +180,12 @@ spack: cray-mpich: buildable: false externals: - - spec: cray-mpich@9.0.1.rocm_6_3_1+slurm - prefix: /usr/tce/packages/cray-mpich-tce/cray-mpich-9.0.1-rocmcc-6.3.1/ - - spec: cray-mpich@9.0.1.rocm_6_4_3+slurm - prefix: /usr/tce/packages/cray-mpich-tce/cray-mpich-9.0.1-rocmcc-6.4.3/ - - spec: cray-mpich@9.0.1.cce_20+slurm - prefix: /usr/tce/packages/cray-mpich-tce/cray-mpich-9.0.1-rocmcc-6.4.3/ + - spec: cray-mpich@8.1.29.rocm_6_3_1+slurm + prefix: /usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.3.1/ + - spec: cray-mpich@8.1.29.rocm_6_4_3+slurm + prefix: /usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.4.3/ + - spec: cray-mpich@8.1.32.cce_20+slurm + prefix: /usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.32-rocmcc-6.4.3/ # blas is a bit more complicated because its a virtual package so fake it with # the following per spack docs From feef0ba73855f030ccfe026759b6b0db29785887 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Wed, 17 Jun 2026 16:38:22 -0700 Subject: [PATCH 657/986] Attempt to upgrade HIP compilers - rocm@7.2.1 deadlocks with caliper --- .../configs/toss_4_x86_64_ib_cray/spack.yaml | 91 ++++++++++--------- scripts/spack/specs.json | 6 +- 2 files changed, 53 insertions(+), 44 deletions(-) diff --git a/scripts/spack/configs/toss_4_x86_64_ib_cray/spack.yaml b/scripts/spack/configs/toss_4_x86_64_ib_cray/spack.yaml index dd49d5ba25..9d9de92a71 100644 --- a/scripts/spack/configs/toss_4_x86_64_ib_cray/spack.yaml +++ b/scripts/spack/configs/toss_4_x86_64_ib_cray/spack.yaml @@ -33,26 +33,25 @@ spack: when: '%fortran' - spec: '%cray-mpich@8.1.29.rocm_6.4.3' when: '%mpi' - rocm_6_3_1: - - spec: fflags=-Mfreeform - - spec: '%c=llvm-amdgpu@6.3.1' + rocm_7_2_1: + - spec: '%c=llvm-amdgpu@7.2.1' when: '%c' - - spec: '%cxx=llvm-amdgpu@6.3.1' + - spec: '%cxx=llvm-amdgpu@7.2.1' when: '%cxx' - - spec: '%fortran=llvm-amdgpu@6.3.1' + - spec: '%fortran=llvm-amdgpu@7.2.1' when: '%fortran' - - spec: '%cray-mpich@8.1.29.rocm_6_3_1' + - spec: '%cray-mpich@9.1.0.rocm_7.2.1' when: '%mpi' - cce_20: + cce_21: # Flag for lowercase Fortran module names - spec: fflags=-ef - - spec: '%c=cce@20.0.0' + - spec: '%c=cce@21.0.0' when: '%c' - - spec: '%cxx=cce@20.0.0' + - spec: '%cxx=cce@21.0.0' when: '%cxx' - - spec: '%fortran=cce@20.0.0' + - spec: '%fortran=cce@21.0.0' when: '%fortran' - - spec: '%cray-mpich@8.1.32.cce_20' + - spec: '%cray-mpich@8.1.32.cce_21' when: '%mpi' packages: @@ -71,13 +70,13 @@ spack: # - Use amdgpu_target=gfx908 for rznevada llvm-amdgpu: externals: - - spec: llvm-amdgpu@6.3.1 - prefix: /opt/rocm-6.3.1 + - spec: llvm-amdgpu@7.2.1 + prefix: /opt/rocm-7.2.1 extra_attributes: compilers: - c: /opt/rocm-6.3.1/llvm/bin/amdclang - cxx: /opt/rocm-6.3.1/llvm/bin/amdclang++ - fortran: /opt/rocm-6.3.1/llvm/bin/amdflang + c: /opt/rocm-7.2.1/llvm/bin/amdclang + cxx: /opt/rocm-7.2.1/llvm/bin/amdclang++ + fortran: /opt/rocm-7.2.1/llvm/bin/amdflang environment: {} extra_rpaths: [] - spec: llvm-amdgpu@6.4.3 @@ -91,85 +90,95 @@ spack: extra_rpaths: [] cce: externals: - - spec: cce@20.0.0 - prefix: /usr/tce/packages/cce-tce/cce-20.0.0 + - spec: cce@21.0.0 + prefix: /usr/tce/packages/cce-tce/cce-21.0.0 extra_attributes: compilers: - c: /usr/tce/packages/cce-tce/cce-20.0.0/bin/craycc - cxx: /usr/tce/packages/cce-tce/cce-20.0.0/bin/crayCC - fortran: /usr/tce/packages/cce-tce/cce-20.0.0/bin/crayftn + c: /usr/tce/packages/cce-tce/cce-21.0.0/bin/craycc + cxx: /usr/tce/packages/cce-tce/cce-21.0.0/bin/crayCC + fortran: /usr/tce/packages/cce-tce/cce-21.0.0/bin/crayftn environment: {} extra_rpaths: [] hip: - # version: [6.3.1, 6.4.3] + # version: [7.2.1, 6.4.3] buildable: false externals: - - spec: hip@6.3.1 - prefix: /opt/rocm-6.3.1 + - spec: hip@7.2.1 + prefix: /opt/rocm-7.2.1 - spec: hip@6.4.3 prefix: /opt/rocm-6.4.3 hipblas: buildable: false externals: - - spec: hipblas@6.3.1 - prefix: /opt/rocm-6.3.1 + - spec: hipblas@7.2.1 + prefix: /opt/rocm-7.2.1 - spec: hipblas@6.4.3 prefix: /opt/rocm-6.4.3 + hipcub: + buildable: false + externals: + - spec: hipcub@7.2.1 + prefix: /opt/rocm-7.2.1 + - spec: hipcub@6.4.3 + prefix: /opt/rocm-6.4.3 + hipsparse: buildable: false externals: - - spec: hipsparse@6.3.1 - prefix: /opt/rocm-6.3.1 + - spec: hipsparse@7.2.1 + prefix: /opt/rocm-7.2.1 - spec: hipsparse@6.4.3 prefix: /opt/rocm-6.4.3 hsa-rocr-dev: buildable: false externals: - - spec: hsa-rocr-dev@6.3.1 - prefix: /opt/rocm-6.3.1/ + - spec: hsa-rocr-dev@7.2.1 + prefix: /opt/rocm-7.2.1/ - spec: hsa-rocr-dev@6.4.3 prefix: /opt/rocm-6.4.3 rocblas: buildable: false externals: - - spec: rocblas@6.3.1 - prefix: /opt/rocm-6.3.1/ + - spec: rocblas@7.2.1 + prefix: /opt/rocm-7.2.1/ - spec: rocblas@6.4.3 prefix: /opt/rocm-6.4.3/ rocminfo: buildable: false externals: - - spec: rocminfo@6.3.1 - prefix: /opt/rocm-6.3.1 + - spec: rocminfo@7.2.1 + prefix: /opt/rocm-7.2.1 - spec: rocminfo@6.4.3 prefix: /opt/rocm-6.4.3 rocprim: buildable: false externals: - - spec: rocprim@6.3.1 - prefix: /opt/rocm-6.3.1/ + - spec: rocprim@7.2.1 + prefix: /opt/rocm-7.2.1/ - spec: rocprim@6.4.3 prefix: /opt/rocm-6.4.3 rocm-device-libs: buildable: false externals: - - spec: rocm-device-libs@6.3.1 - prefix: /opt/rocm-6.3.1/ + - spec: rocm-device-libs@7.2.1 + prefix: /opt/rocm-7.2.1/ - spec: rocm-device-libs@6.4.3 prefix: /opt/rocm-6.4.3 rocprofiler-sdk: buildable: false externals: + - spec: rocprofiler-sdk@7.2.1 + prefix: /opt/rocm-7.2.1 - spec: rocprofiler-sdk@6.4.3 prefix: /opt/rocm-6.4.3 @@ -180,11 +189,11 @@ spack: cray-mpich: buildable: false externals: - - spec: cray-mpich@8.1.29.rocm_6_3_1+slurm - prefix: /usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.3.1/ + - spec: cray-mpich@9.1.0.rocm_7.2.1+slurm + prefix: /usr/tce/packages/cray-mpich-tce/cray-mpich-9.1.0-rocmcc-7.2.1/ - spec: cray-mpich@8.1.29.rocm_6_4_3+slurm prefix: /usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.4.3/ - - spec: cray-mpich@8.1.32.cce_20+slurm + - spec: cray-mpich@8.1.32.cce_21+slurm prefix: /usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.32-rocmcc-6.4.3/ # blas is a bit more complicated because its a virtual package so fake it with diff --git a/scripts/spack/specs.json b/scripts/spack/specs.json index 833a6961bf..b81f7b508f 100644 --- a/scripts/spack/specs.json +++ b/scripts/spack/specs.json @@ -36,11 +36,11 @@ "__comment__":"# Use amdgpu_target=gfx90a for tioga/rzvernal", "__comment__":"# Use amdgpu_target=gfx908 for rznevada", "__comment__":"# -Wno-int-conversion flag needed for building HDF5", - "__comment__":"# caliper disabled for rocm@6.3.1, fails to compile", + "__comment__":"# caliper disabled for rocm@7.2.1, rocprof deadlock", "toss_4_x86_64_ib_cray": [ "+python+devtools+openmp+mfem+c2c+adiak+caliper+rocm amdgpu_target=gfx942,gfx90a %rocm_6_4_3 ^hip@6.4.3 ^hipblas@6.4.3 ^hipsparse@6.4.3 ^hsa-rocr-dev@6.4.3 ^rocprim@6.4.3 ^mfem+raja+umpire ^raja+openmp+rocm ^umpire+openmp+rocm ^hdf5 cflags=-Wno-int-conversion", - "+python+devtools+openmp+mfem+c2c+adiak~caliper+rocm amdgpu_target=gfx942,gfx90a %rocm_6_3_1 ^hip@6.3.1 ^hipblas@6.3.1 ^hipsparse@6.3.1 ^hsa-rocr-dev@6.3.1 ^rocprim@6.3.1 ^mfem+raja+umpire ^raja+openmp+rocm ^umpire+openmp+rocm ^hdf5 cflags=-Wno-int-conversion", - "+python+devtools+openmp+mfem+c2c+adiak+caliper+rocm amdgpu_target=gfx942,gfx90a %cce_20 ^hip@6.4.3 ^hipblas@6.4.3 ^hipsparse@6.4.3 ^hsa-rocr-dev@6.4.3 ^rocprim@6.4.3 ^caliper~shared ^mfem+raja+umpire ^raja+openmp+rocm ^umpire+openmp+rocm ^hdf5 cflags=-Wno-int-conversion" ], + "+python+devtools+openmp+mfem+c2c+adiak~caliper+rocm amdgpu_target=gfx942,gfx90a %rocm_7_2_1 ^hip@7.2.1 ^hipblas@7.2.1 ^hipsparse@7.2.1 ^hsa-rocr-dev@7.2.1 ^rocprim@7.2.1 ^mfem+raja+umpire ^raja+openmp+rocm ^umpire+openmp+rocm ^hdf5 cflags=-Wno-int-conversion", + "+python+devtools+openmp+mfem+c2c+adiak+caliper+rocm amdgpu_target=gfx942,gfx90a %cce_21 ^hip@6.4.3 ^hipblas@6.4.3 ^hipsparse@6.4.3 ^hsa-rocr-dev@6.4.3 ^rocprim@6.4.3 ^caliper~shared ^mfem+raja+umpire ^raja+openmp+rocm ^umpire+openmp+rocm ^hdf5 cflags=-Wno-int-conversion" ], "darwin-x86_64": [ "+python+devtools+mfem %clang@9.0.0" ] From 13bf431ad5376289f70c17a446eaa64d3d79429e Mon Sep 17 00:00:00 2001 From: Brian Han Date: Mon, 22 Jun 2026 07:51:23 -0700 Subject: [PATCH 658/986] Update to spack 1.2 and spack-packages 6/26 --- .uberenv_config.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.uberenv_config.json b/.uberenv_config.json index 511a8fde3e..18957bea1c 100644 --- a/.uberenv_config.json +++ b/.uberenv_config.json @@ -4,9 +4,9 @@ "package_final_phase": "initconfig", "package_source_dir": "../..", "spack_url": "https://github.com/spack/spack.git", -"spack_commit": "2e2169d5282d166f63e3ee4db8d4446c43cefa8a", +"spack_commit": "63962ed90680d339004305075da272ae93c04a41", "spack_configs_path": "scripts/spack/configs", -"spack_packages_commit": "be7d9351f901af0d7bc179a79cb14229a1785e67", +"spack_packages_commit": "d4f7c711a6a42f1c4d551c8fd10fce9a11340a81", "spack_packages_path": "scripts/spack/packages", "spack_concretizer": "clingo", "vcpkg_url": "https://github.com/microsoft/vcpkg", From 1ba06e963de1baa028377d417fe253bafa1c4a71 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Mon, 22 Jun 2026 09:42:50 -0700 Subject: [PATCH 659/986] Workaround for spack gmake error related to nanobind --- scripts/spack/packages/py-nanobind/package.py | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 scripts/spack/packages/py-nanobind/package.py diff --git a/scripts/spack/packages/py-nanobind/package.py b/scripts/spack/packages/py-nanobind/package.py new file mode 100644 index 0000000000..b733f8909e --- /dev/null +++ b/scripts/spack/packages/py-nanobind/package.py @@ -0,0 +1,10 @@ +import os + +from spack.package import * +from spack_repo.builtin.packages.py_nanobind.package import PyNanobind as BuiltinPyNanobind + +class PyNanobind(BuiltinPyNanobind): + + # Workaround for "gmake: *** internal error: invalid --jobserver-auth string": + # https://github.com/spack/spack-packages/issues/5106#issuecomment-4679593276 + depends_on("gmake", type="build") From bc59a7c743e0a5b9c940e203414f9991c8804874 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Tue, 23 Jun 2026 11:04:22 -0700 Subject: [PATCH 660/986] HIP related changes - added a patch to cce recipe, cce@21 no longer requires openmp flags, guard old usage --- .../configs/toss_4_x86_64_ib_cray/spack.yaml | 18 +++++++++++++++--- scripts/spack/packages/axom/package.py | 2 +- scripts/spack/packages/cce/package.py | 18 ++++++++++++++++++ scripts/spack/specs.json | 8 ++++---- 4 files changed, 38 insertions(+), 8 deletions(-) create mode 100644 scripts/spack/packages/cce/package.py diff --git a/scripts/spack/configs/toss_4_x86_64_ib_cray/spack.yaml b/scripts/spack/configs/toss_4_x86_64_ib_cray/spack.yaml index 9d9de92a71..98642c4bc7 100644 --- a/scripts/spack/configs/toss_4_x86_64_ib_cray/spack.yaml +++ b/scripts/spack/configs/toss_4_x86_64_ib_cray/spack.yaml @@ -51,7 +51,7 @@ spack: when: '%cxx' - spec: '%fortran=cce@21.0.0' when: '%fortran' - - spec: '%cray-mpich@8.1.32.cce_21' + - spec: '%cray-mpich@9.1.0.cce_21' when: '%mpi' packages: @@ -77,6 +77,10 @@ spack: c: /opt/rocm-7.2.1/llvm/bin/amdclang cxx: /opt/rocm-7.2.1/llvm/bin/amdclang++ fortran: /opt/rocm-7.2.1/llvm/bin/amdflang + flags: + cflags: -fPIC + cxxflags: -fPIC + fflags: -fPIC environment: {} extra_rpaths: [] - spec: llvm-amdgpu@6.4.3 @@ -86,6 +90,10 @@ spack: c: /opt/rocm-6.4.3/llvm/bin/amdclang cxx: /opt/rocm-6.4.3/llvm/bin/amdclang++ fortran: /opt/rocm-6.4.3/llvm/bin/amdflang + flags: + cflags: -fPIC + cxxflags: -fPIC + fflags: -fPIC environment: {} extra_rpaths: [] cce: @@ -97,6 +105,10 @@ spack: c: /usr/tce/packages/cce-tce/cce-21.0.0/bin/craycc cxx: /usr/tce/packages/cce-tce/cce-21.0.0/bin/crayCC fortran: /usr/tce/packages/cce-tce/cce-21.0.0/bin/crayftn + flags: + cflags: -fPIC + cxxflags: -fPIC + fflags: -fPIC environment: {} extra_rpaths: [] @@ -193,8 +205,8 @@ spack: prefix: /usr/tce/packages/cray-mpich-tce/cray-mpich-9.1.0-rocmcc-7.2.1/ - spec: cray-mpich@8.1.29.rocm_6_4_3+slurm prefix: /usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.4.3/ - - spec: cray-mpich@8.1.32.cce_21+slurm - prefix: /usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.32-rocmcc-6.4.3/ + - spec: cray-mpich@9.1.0.cce_21+slurm + prefix: /usr/tce/packages/cray-mpich-tce/cray-mpich-9.1.0-cce-21.0.0/ # blas is a bit more complicated because its a virtual package so fake it with # the following per spack docs diff --git a/scripts/spack/packages/axom/package.py b/scripts/spack/packages/axom/package.py index 548aaadf2c..32581f5291 100644 --- a/scripts/spack/packages/axom/package.py +++ b/scripts/spack/packages/axom/package.py @@ -601,7 +601,7 @@ def initconfig_hardware_entries(self): cmake_cache_string("BLT_OPENMP_LINK_FLAGS", openmp_gen_exp, description) ) - if spec.satisfies("+openmp") and spec.satisfies("+rocm") and spec.satisfies("%cce"): + if spec.satisfies("+openmp") and spec.satisfies("+rocm") and spec.satisfies("%cce@:20"): openmp_gen_exp = ( "$<$>:" "-fopenmp=libomp>;$<$ Date: Tue, 23 Jun 2026 13:40:54 -0700 Subject: [PATCH 661/986] Tentative workaround for cuda core_flatmap insert_batched_with_existing failure --- src/axom/core/FlatMap.hpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/axom/core/FlatMap.hpp b/src/axom/core/FlatMap.hpp index be1958a9bf..e350181ec6 100644 --- a/src/axom/core/FlatMap.hpp +++ b/src/axom/core/FlatMap.hpp @@ -634,7 +634,16 @@ class FlatMap : detail::flat_map::SequentialLookupPolicy(); + FlatMap host_map(*this, axom::Allocator {host_allocator_id}); + host_map.rehash(count); + + FlatMap rehashed_device(host_map, m_allocator); + this->swap(rehashed_device); + return; + #elif defined(AXOM_USE_HIP) + #if !defined(AXOM_GPUCC) // Similar to the issue in ArrayBase (PR #1582), using FlatMap from // a GPU-enabled Axom library but with a host-only compiler results // in an ODR violation. @@ -647,13 +656,12 @@ class FlatMap : detail::flat_map::SequentialLookupPolicy; - #elif defined(AXOM_USE_HIP) + #else using ExecSpace = axom::HIP_EXEC<256>; - #endif + #endif this->parallelRehash(count); return; + #endif } #endif } From 5a48776ff4ba61ae4f375cdb0920b703958c857c Mon Sep 17 00:00:00 2001 From: Brian Han Date: Tue, 23 Jun 2026 13:44:27 -0700 Subject: [PATCH 662/986] Update to blt 0.7.2 --- src/cmake/blt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmake/blt b/src/cmake/blt index ccd6d31c70..f08f95e0f4 160000 --- a/src/cmake/blt +++ b/src/cmake/blt @@ -1 +1 @@ -Subproject commit ccd6d31c7072ff984c7c8b723fc51f69e677ca72 +Subproject commit f08f95e0f49b1d53db8be2dd577d1cdbfd9e9f90 From 8ff77191472f50deae23b9afb200a17c71d45bd9 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Tue, 23 Jun 2026 14:02:38 -0700 Subject: [PATCH 663/986] Small recipe fixes for axom and cce --- scripts/spack/packages/axom/package.py | 11 ++++++----- scripts/spack/packages/cce/package.py | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/scripts/spack/packages/axom/package.py b/scripts/spack/packages/axom/package.py index 32581f5291..d13ad849ba 100644 --- a/scripts/spack/packages/axom/package.py +++ b/scripts/spack/packages/axom/package.py @@ -169,7 +169,7 @@ class Axom(CachedCMakePackage, CudaPackage, ROCmPackage): conflicts("cxxstd=11", when="@0.7.0:") # C++17 required as of 0.12.0 conflicts("cxxstd=14", when="@0.12.0:") - # C++17 required as of unreleased 0.15.0 (Should be 0.15.0) + # C++20 required as of unreleased 0.15.0 (Should be 0.15.0) conflicts("cxxstd=17", when="@0.14.0:") # ----------------------------------------------------------------------- @@ -356,9 +356,6 @@ class Axom(CachedCMakePackage, CudaPackage, ROCmPackage): conflicts("^blt@:0.3.6", when="+rocm") - # python interface requires mpi - conflicts("~mpi", when="+python") - def flag_handler(self, name, flags): if self.spec.satisfies("%cce") and name == "fflags": flags.append("-ef") @@ -462,7 +459,10 @@ def initconfig_hardware_entries(self): if spec.satisfies("^blt@:0.5.1"): # This is handled internally by BLT now - cudaflags += " -std=c++14" + if self.cxx_std == "14": + cudaflags += " -std=c++14" + if self.cxx_std == "11": + cudaflags += " -std=c++11" entries.append(cmake_cache_string("CMAKE_CUDA_FLAGS", cudaflags, force=True)) entries.append("# nvcc does not like gtest's 'pthreads' flag\n") @@ -601,6 +601,7 @@ def initconfig_hardware_entries(self): cmake_cache_string("BLT_OPENMP_LINK_FLAGS", openmp_gen_exp, description) ) + # For cce up to version 20.0.0 if spec.satisfies("+openmp") and spec.satisfies("+rocm") and spec.satisfies("%cce@:20"): openmp_gen_exp = ( "$<$>:" diff --git a/scripts/spack/packages/cce/package.py b/scripts/spack/packages/cce/package.py index fecddb2915..230503be91 100644 --- a/scripts/spack/packages/cce/package.py +++ b/scripts/spack/packages/cce/package.py @@ -15,4 +15,4 @@ def _standard_flag(self, *, language, standard): "cxx": {"11": "-std=c++11", "14": "-std=c++14", "17": "-std=c++17", "20": "-std=c++20"}, "c": {"99": "-std=c99", "11": "-std=c11"}, } - return flags[language][standard] \ No newline at end of file + return flags[language][standard] From 595a2cb453781d05066e4acd1e2b9001d8afe1b9 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Tue, 23 Jun 2026 14:03:11 -0700 Subject: [PATCH 664/986] Attempt to update other package versions in versions.yaml --- scripts/spack/configs/versions.yaml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/scripts/spack/configs/versions.yaml b/scripts/spack/configs/versions.yaml index 51a6766d72..ce86e5bdd0 100644 --- a/scripts/spack/configs/versions.yaml +++ b/scripts/spack/configs/versions.yaml @@ -5,7 +5,7 @@ packages: adiak: require: - - spec: "@0.4.0" + - spec: "@0.5.0" # Newer commit than 2.14.0 that fixes rocprofiler dependency caliper: require: @@ -16,16 +16,16 @@ packages: - spec: "@git.e75ab64c029aa27c80593715cb2a3ccad7453c8c=main" conduit: require: - - spec: "@0.9.5" + - spec: "@0.9.7" hypre: require: - - spec: "@2.27.0" + - spec: "@3.1.0" mfem: require: - spec: "@4.9" opencascade: require: - - spec: "@7.8.1" + - spec: "@7.9.3" py-jsonschema: require: - spec: "@:4.17" #Anything after this requires py-rpds which requires rust and adds 40 minutes @@ -35,7 +35,8 @@ packages: - spec: "@c3e4576eccff2435c358b691e4fcad4e601a7441=develop" scr: require: - - spec: "@3.0.1" + - spec: "@3.1.0" + # testing umpire: require: - spec: "@git.f81afcab0e07c431288e950d471aa739df68f7f7=develop" From fb2805ad7f301a09182f35f576494d8edc3f6d40 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Wed, 24 Jun 2026 10:14:17 -0700 Subject: [PATCH 665/986] Fix deadlock issue with rocm@7.2.1 by building caliper~shared - now have both mfem and caliper enabled --- scripts/spack/specs.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/spack/specs.json b/scripts/spack/specs.json index 533bc1c90d..9552e2a9f6 100644 --- a/scripts/spack/specs.json +++ b/scripts/spack/specs.json @@ -36,10 +36,10 @@ "__comment__":"# Use amdgpu_target=gfx90a for tioga/rzvernal", "__comment__":"# Use amdgpu_target=gfx908 for rznevada", "__comment__":"# -Wno-int-conversion flag needed for building HDF5", - "__comment__":"# caliper disabled for rocm@7.2.1, rocprof deadlock", + "__comment__":"# caliper~shared required for rocm@7.2.1 to prevent rocprof runtime deadlock", "toss_4_x86_64_ib_cray": [ "+python+devtools+openmp+mfem+c2c+adiak+caliper+rocm amdgpu_target=gfx942,gfx90a %rocm_6_4_3 ^hip@6.4.3 ^hipblas@6.4.3 ^hipsparse@6.4.3 ^hsa-rocr-dev@6.4.3 ^rocprim@6.4.3 ^mfem+raja+umpire ^raja+openmp+rocm ^umpire+openmp+rocm ^hdf5 cflags=-Wno-int-conversion", - "+python+devtools+openmp+mfem+c2c+adiak~caliper+rocm amdgpu_target=gfx942,gfx90a %rocm_7_2_1 ^hip@7.2.1 ^hipblas@7.2.1 ^hipsparse@7.2.1 ^hsa-rocr-dev@7.2.1 ^rocprim@7.2.1 ^mfem+raja+umpire ^raja+openmp+rocm ^umpire+openmp+rocm ^hdf5 cflags=-Wno-int-conversion", + "+python+devtools+openmp+mfem+c2c+adiak+caliper+rocm amdgpu_target=gfx942,gfx90a %rocm_7_2_1 ^hip@7.2.1 ^hipblas@7.2.1 ^hipsparse@7.2.1 ^hsa-rocr-dev@7.2.1 ^rocprim@7.2.1 ^caliper~shared ^mfem+raja+umpire ^raja+openmp+rocm ^umpire+openmp+rocm ^hdf5 cflags=-Wno-int-conversion", "+python+devtools+openmp+mfem+c2c+adiak+caliper+rocm amdgpu_target=gfx942,gfx90a %cce_21 ^hip@7.2.1 ^hipblas@7.2.1 ^hipsparse@7.2.1 ^hsa-rocr-dev@7.2.1 ^rocprim@7.2.1 ^caliper~shared ^mfem+raja+umpire ^raja+openmp+rocm ^umpire+openmp+rocm ^hdf5 cflags=-Wno-int-conversion" ], "darwin-x86_64": From a32584999dedd7292f2ee68884ad4fcf29b579ce Mon Sep 17 00:00:00 2001 From: Brian Han Date: Wed, 24 Jun 2026 10:14:49 -0700 Subject: [PATCH 666/986] Update spack-packages commit to include cuda 13 enablement --- .uberenv_config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.uberenv_config.json b/.uberenv_config.json index 18957bea1c..bc5ff888b7 100644 --- a/.uberenv_config.json +++ b/.uberenv_config.json @@ -6,7 +6,7 @@ "spack_url": "https://github.com/spack/spack.git", "spack_commit": "63962ed90680d339004305075da272ae93c04a41", "spack_configs_path": "scripts/spack/configs", -"spack_packages_commit": "d4f7c711a6a42f1c4d551c8fd10fce9a11340a81", +"spack_packages_commit": "4d3d0f8ee5ffd6f099727f46ec704f3f4ee98e5c", "spack_packages_path": "scripts/spack/packages", "spack_concretizer": "clingo", "vcpkg_url": "https://github.com/microsoft/vcpkg", From 30f1850305e01c00227fedbbd21db932f7972508 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Wed, 24 Jun 2026 16:54:05 -0700 Subject: [PATCH 667/986] Rolling back some of the package updates due to failures, CUDA 13.1 working, but requires two in-flight raja PRs --- scripts/spack/configs/toss_4_x86_64_ib/spack.yaml | 5 ++--- scripts/spack/configs/versions.yaml | 8 ++++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/scripts/spack/configs/toss_4_x86_64_ib/spack.yaml b/scripts/spack/configs/toss_4_x86_64_ib/spack.yaml index fe29440ba3..efc83e02b1 100644 --- a/scripts/spack/configs/toss_4_x86_64_ib/spack.yaml +++ b/scripts/spack/configs/toss_4_x86_64_ib/spack.yaml @@ -164,8 +164,8 @@ spack: cuda: buildable: False externals: - - spec: cuda@12.9.1 +allow-unsupported-compilers - prefix: /usr/tce/packages/cuda/cuda-12.9.1 + - spec: cuda@13.1.1 +allow-unsupported-compilers + prefix: /usr/tce/packages/cuda/cuda-13.1.1 netlib-lapack: buildable: false @@ -400,4 +400,3 @@ spack: externals: - spec: py-yapf@0.43.0 prefix: /collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11/ - diff --git a/scripts/spack/configs/versions.yaml b/scripts/spack/configs/versions.yaml index ce86e5bdd0..1ebff30055 100644 --- a/scripts/spack/configs/versions.yaml +++ b/scripts/spack/configs/versions.yaml @@ -25,17 +25,17 @@ packages: - spec: "@4.9" opencascade: require: - - spec: "@7.9.3" + - spec: "@7.9.1" py-jsonschema: require: - spec: "@:4.17" #Anything after this requires py-rpds which requires rust and adds 40 minutes - # testing commit before PR #2005 (HIP error std::memcpy TypeConverts) + # testing branch #2011 (will partially break since requires #2048) raja: require: - - spec: "@c3e4576eccff2435c358b691e4fcad4e601a7441=develop" + - spec: "@git.bb9b6b8e789ff838c844a58461ba49663ddf5a15=develop" scr: require: - - spec: "@3.1.0" + - spec: "@3.0.1" # testing umpire: require: From a4e1c21330b380ff8277096a2ad5669b64a48abb Mon Sep 17 00:00:00 2001 From: Brian Han Date: Thu, 25 Jun 2026 09:55:58 -0700 Subject: [PATCH 668/986] Anticipating compiler change for docker clang --- scripts/docker/dockerfile_clang-19 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/docker/dockerfile_clang-19 b/scripts/docker/dockerfile_clang-19 index 01f04c69aa..4500d47d94 100644 --- a/scripts/docker/dockerfile_clang-19 +++ b/scripts/docker/dockerfile_clang-19 @@ -29,7 +29,7 @@ RUN git clone --recursive --branch $branch --single-branch --depth 1 https://git # Build/install TPLs via spack and then remove the temporary build directory on success RUN cd axom_repo && python3 ./scripts/uberenv/uberenv.py --spack-env-file=./scripts/spack/configs/docker/ubuntu24/spack.yaml \ --project-json=.uberenv_config.json \ - --spec="+python+mfem+raja+umpire+adiak+caliper %clang_19" --prefix=/home/axom/axom_tpls -k \ + --spec="+python+mfem+raja+umpire+adiak+caliper %%clang_19" --prefix=/home/axom/axom_tpls -k \ && rm -rf /home/axom/axom_tpls/build_stage /home/axom/axom_tpls/spack RUN mkdir -p /home/axom/export_hostconfig @@ -37,4 +37,4 @@ RUN cp ./axom_repo/*.cmake /home/axom/export_hostconfig # Make sure the new hostconfig worked # Note: having high job slots causes build log to disappear and job to fail -RUN cd axom_repo && python3 config-build.py -hc *.cmake -bp build && cd build && make -j4 VERBOSE=1 && make -j4 test && cd /home/axom && rm -rf axom_repo \ No newline at end of file +RUN cd axom_repo && python3 config-build.py -hc *.cmake -bp build && cd build && make -j4 VERBOSE=1 && make -j4 test && cd /home/axom && rm -rf axom_repo From 15b751071463ad7ac37b1b0a58e80a1d983ed18f Mon Sep 17 00:00:00 2001 From: Brian Han Date: Wed, 1 Jul 2026 15:49:55 -0700 Subject: [PATCH 669/986] Remove intel testing variants, caliped~shared fix is an anomaly --- scripts/spack/specs.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/spack/specs.json b/scripts/spack/specs.json index 9552e2a9f6..36cc4d6f01 100644 --- a/scripts/spack/specs.json +++ b/scripts/spack/specs.json @@ -18,7 +18,7 @@ "__comment__":"# compiler preferences to prevent compiler mixing", "__comment__":"# Configs are for dane/rzwhippet", "toss_4_x86_64_ib": - [ "~python+devtools+hdf5+mfem+c2c+adiak+caliper %%intel_25 ^mfem cxxflags=-fp-speculation=safe %intel_25 ^raja cxxstd=20", + [ "+python+devtools+hdf5+mfem+c2c+adiak+caliper %%intel_25 ^mfem cxxflags=-fp-speculation=safe %intel_25", "+python+devtools+hdf5+mfem+c2c+scr+adiak+caliper %gcc_13", "+python+devtools+hdf5+mfem+c2c+scr+adiak+caliper+opencascade %%clang_19"], @@ -36,7 +36,7 @@ "__comment__":"# Use amdgpu_target=gfx90a for tioga/rzvernal", "__comment__":"# Use amdgpu_target=gfx908 for rznevada", "__comment__":"# -Wno-int-conversion flag needed for building HDF5", - "__comment__":"# caliper~shared required for rocm@7.2.1 to prevent rocprof runtime deadlock", + "__comment__":"# caliper~shared required for mfem/caliper/mpi(?) to prevent rocprof runtime deadlock", "toss_4_x86_64_ib_cray": [ "+python+devtools+openmp+mfem+c2c+adiak+caliper+rocm amdgpu_target=gfx942,gfx90a %rocm_6_4_3 ^hip@6.4.3 ^hipblas@6.4.3 ^hipsparse@6.4.3 ^hsa-rocr-dev@6.4.3 ^rocprim@6.4.3 ^mfem+raja+umpire ^raja+openmp+rocm ^umpire+openmp+rocm ^hdf5 cflags=-Wno-int-conversion", "+python+devtools+openmp+mfem+c2c+adiak+caliper+rocm amdgpu_target=gfx942,gfx90a %rocm_7_2_1 ^hip@7.2.1 ^hipblas@7.2.1 ^hipsparse@7.2.1 ^hsa-rocr-dev@7.2.1 ^rocprim@7.2.1 ^caliper~shared ^mfem+raja+umpire ^raja+openmp+rocm ^umpire+openmp+rocm ^hdf5 cflags=-Wno-int-conversion", From 35bd06edc9c55165b3bbc575e0109f5275ddfc8e Mon Sep 17 00:00:00 2001 From: Brian Han Date: Tue, 7 Jul 2026 09:51:53 -0700 Subject: [PATCH 670/986] Update version hashes, give more descript descriptions --- scripts/spack/configs/versions.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/spack/configs/versions.yaml b/scripts/spack/configs/versions.yaml index 1ebff30055..5672040d06 100644 --- a/scripts/spack/configs/versions.yaml +++ b/scripts/spack/configs/versions.yaml @@ -10,7 +10,7 @@ packages: caliper: require: - spec: "@git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c=master" - # testing + # main - 5/7/26 - Set by: https://github.com/spack/spack-packages/blob/1928a2383d6e0d536cfa2b0e7bc6bee8a70315d4/repos/spack_repo/builtin/packages/raja/package.py#L292-L293 camp: require: - spec: "@git.e75ab64c029aa27c80593715cb2a3ccad7453c8c=main" @@ -29,14 +29,14 @@ packages: py-jsonschema: require: - spec: "@:4.17" #Anything after this requires py-rpds which requires rust and adds 40 minutes - # testing branch #2011 (will partially break since requires #2048) + # develop - 7/7/26 raja: require: - - spec: "@git.bb9b6b8e789ff838c844a58461ba49663ddf5a15=develop" + - spec: "@git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b=develop" scr: require: - spec: "@3.0.1" - # testing + # develop - 6/15/26 umpire: require: - - spec: "@git.f81afcab0e07c431288e950d471aa739df68f7f7=develop" + - spec: "@git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae=develop" From fc8e2df7f0d372a0ad8aa201f28edf0c74bfc29d Mon Sep 17 00:00:00 2001 From: Brian Han Date: Tue, 7 Jul 2026 13:56:40 -0700 Subject: [PATCH 671/986] Update RZ host-configs - remove older HIP compilers --- ...toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake | 169 ----------------- ...toss_4_x86_64_ib_cray-cce@21.0.0_hip.cmake | 171 ++++++++++++++++++ ...x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake | 163 ----------------- ...x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake | 56 +++--- ...x86_64_ib_cray-llvm-amdgpu@7.2.1_hip.cmake | 169 +++++++++++++++++ ...tor-toss_4_x86_64_ib-gcc@13.3.1_cuda.cmake | 50 ++--- ...or-toss_4_x86_64_ib-llvm@19.1.3_cuda.cmake | 50 ++--- ...toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake | 169 ----------------- ...toss_4_x86_64_ib_cray-cce@21.0.0_hip.cmake | 171 ++++++++++++++++++ ...x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake | 163 ----------------- ...x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake | 56 +++--- ...x86_64_ib_cray-llvm-amdgpu@7.2.1_hip.cmake | 169 +++++++++++++++++ ...zwhippet-toss_4_x86_64_ib-gcc@13.3.1.cmake | 68 +++---- ...4_ib-intel-oneapi-compilers@2025.2.0.cmake | 46 ++--- ...whippet-toss_4_x86_64_ib-llvm@19.1.3.cmake | 70 +++---- 15 files changed, 884 insertions(+), 856 deletions(-) delete mode 100644 host-configs/rzadams-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake create mode 100644 host-configs/rzadams-toss_4_x86_64_ib_cray-cce@21.0.0_hip.cmake delete mode 100644 host-configs/rzadams-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake create mode 100644 host-configs/rzadams-toss_4_x86_64_ib_cray-llvm-amdgpu@7.2.1_hip.cmake delete mode 100644 host-configs/rzvernal-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake create mode 100644 host-configs/rzvernal-toss_4_x86_64_ib_cray-cce@21.0.0_hip.cmake delete mode 100644 host-configs/rzvernal-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake create mode 100644 host-configs/rzvernal-toss_4_x86_64_ib_cray-llvm-amdgpu@7.2.1_hip.cmake diff --git a/host-configs/rzadams-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake b/host-configs/rzadams-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake deleted file mode 100644 index 2f449c7a36..0000000000 --- a/host-configs/rzadams-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake +++ /dev/null @@ -1,169 +0,0 @@ -#------------------------------------------------------------------------------ -# !!!! This is a generated file, edit at own risk !!!! -#------------------------------------------------------------------------------ -# CMake executable path: /usr/tce/packages/cmake/cmake-3.29.2/bin/cmake -#------------------------------------------------------------------------------ - -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/blt-0.7.1-af45hpsxdxploxhqqi4irjmy4vz2omqx;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/c2c-1.8.0-cpdkv2uxkhs3qmzqakt4sht3vuiucdwt;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-wytd46zvuquxg2rqf3zec5ldbmjeaat6;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/conduit-0.9.5-pbrrhvrsmnewby6q3u35ceppt5fxme2p;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/lua-5.4.8-y6z3polqakbuhn6nh6muri3rjxqmiioa;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/mfem-4.9.0-wpbbiugqvdw7bylkmyyf2quazcoptzah;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/py-nanobind-2.7.0-ieb2bzuz435ydpyiq2uj36yyqjbrffml;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/adiak-0.4.0-lzgexbo5qyzepgju2acyy6d2qft443tb;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/libunwind-1.8.3-zvteigxlopfo5tkdr2n2pglgosg42bah;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/hdf5-1.8.23-to3lvqwzxrqzad65zbvnbtvyt4k63uxe;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/parmetis-4.0.3-bajtzsmahjg2wvcjzo5gfb2hr5oj2elc;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/py-mpi4py-4.1.1-e5tepnokpccpvjfp6lwnwfi5laakuhdh;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/py-numpy-2.4.2-xw5ysy75zfqlalmbmtl5phd2qip43hlo;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/hypre-2.27.0-knkmxfbf3xcut2wmamjsmhrqf7u7xmi7;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-z3eibnxgk3jeplydlcizrhurpibzqzqv;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/umpire-2025.12.0-eqokbtuch3pwifb225w2i3rrft5ddcgv;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/zlib-1.3.1-f3sdiqmy2whvmdn2fxeol3k7oqwouays;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/metis-5.1.0-v4d34x7btqhotktvu5bgk4iwk6h2lumb;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-eplsxvli6nnrgdl4l3hl7cizytginfhp;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/fmt-11.0.2-nzzmwf4nofjv46yeugehjo64opvfbbzr;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cce-tce/cce-20.0.0;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.32-rocmcc-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3" CACHE STRING "") - -set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") - -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/axom-develop-mdr65pqpz5pq5uhe57wjeu6vp5q3kuoa/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/axom-develop-mdr65pqpz5pq5uhe57wjeu6vp5q3kuoa/lib64;;/opt/cray/pe/cce/20.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") - -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/axom-develop-mdr65pqpz5pq5uhe57wjeu6vp5q3kuoa/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0/axom-develop-mdr65pqpz5pq5uhe57wjeu6vp5q3kuoa/lib64;;/opt/cray/pe/cce/20.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") - -set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") - -#------------------------------------------------------------------------------ -# Compilers -#------------------------------------------------------------------------------ -# Compiler Spec: cce@20.0.0/v7nrkimihxta4odatu45wrllosgeklrd -#------------------------------------------------------------------------------ -if(DEFINED ENV{SPACK_CC}) - - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/cce/craycc" CACHE PATH "") - - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/cce/case-insensitive/crayCC" CACHE PATH "") - - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/cce/crayftn" CACHE PATH "") - -else() - - set(CMAKE_C_COMPILER "/usr/tce/packages/cce-tce/cce-20.0.0/bin/craycc" CACHE PATH "") - - set(CMAKE_CXX_COMPILER "/usr/tce/packages/cce-tce/cce-20.0.0/bin/crayCC" CACHE PATH "") - - set(CMAKE_Fortran_COMPILER "/usr/tce/packages/cce-tce/cce-20.0.0/bin/crayftn" CACHE PATH "") - -endif() - -set(CMAKE_Fortran_FLAGS "-ef" CACHE STRING "") - -set(ENABLE_FORTRAN ON CACHE BOOL "") - -set(CMAKE_CXX_FLAGS_DEBUG "-O1 -g" CACHE STRING "") - -#------------------------------------------------------------------------------ -# MPI -#------------------------------------------------------------------------------ - -set(MPI_C_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.32-rocmcc-6.4.3/bin/mpicc" CACHE PATH "") - -set(MPI_CXX_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.32-rocmcc-6.4.3/bin/mpicxx" CACHE PATH "") - -set(MPI_Fortran_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.32-rocmcc-6.4.3/bin/mpif90" CACHE PATH "") - -set(MPIEXEC_EXECUTABLE "/usr/global/tools/flux_wrappers/bin/srun" CACHE PATH "") - -set(MPIEXEC_NUMPROC_FLAG "-n" CACHE STRING "") - -set(ENABLE_MPI ON CACHE BOOL "") - -#------------------------------------------------------------------------------ -# Hardware -#------------------------------------------------------------------------------ - -#------------------------------------------------ -# ROCm -#------------------------------------------------ - -set(ROCM_PATH "/opt/rocm-6.4.3" CACHE PATH "") - -set(CMAKE_HIP_ARCHITECTURES "gfx90a;gfx942" CACHE STRING "") - -set(CMAKE_HIP_COMPILER "/opt/rocm-6.4.3/bin/amdclang++" CACHE FILEPATH "") - -#------------------------------------------------------------------------------ - -# Axom ROCm specifics - -#------------------------------------------------------------------------------ - - -set(ENABLE_HIP ON CACHE BOOL "") - -set(ROCM_ROOT_DIR "/opt/rocm-6.4.3" CACHE PATH "") - -set(BLT_CMAKE_IMPLICIT_LINK_LIBRARIES_EXCLUDE "unwind;ompstub" CACHE STRING "") - -set(CMAKE_EXE_LINKER_FLAGS "-lxpmem -L/opt/cray/pe/mpich/8.1.32/gtl/lib -Wl,-rpath,/opt/cray/pe/mpich/8.1.32/gtl/lib -lmpi_gtl_hsa -L/opt/rocm-6.4.3/lib/llvm/lib -Wl,-rpath,/opt/rocm-6.4.3/lib/llvm/lib -L/opt/rocm-6.4.3/lib -Wl,-rpath,/opt/rocm-6.4.3/lib -lpgmath -L/opt/cray/pe/cce/20.0.0/cce/x86_64/lib -Wl,-rpath,/opt/cray/pe/cce/20.0.0/cce/x86_64/lib -lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " CACHE STRING "") - -#------------------------------------------------ -# Hardware Specifics -#------------------------------------------------ - -set(ENABLE_OPENMP ON CACHE BOOL "") - -set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") - -set(BLT_OPENMP_COMPILE_FLAGS "$<$>:-fopenmp=libomp>;$<$:-fopenmp>" CACHE STRING "Different OpenMP compile & link flags between HIP and CXX compilers (amdclang++)") - -set(BLT_OPENMP_LINK_FLAGS "$<$>:-fopenmp=libomp>;$<$:-fopenmp>" CACHE STRING "Different OpenMP compile & link flags between HIP and CXX compilers (amdclang++)") - -#------------------------------------------------------------------------------ -# TPLs -#------------------------------------------------------------------------------ - -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/cce-20.0.0" CACHE PATH "") - -set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-pbrrhvrsmnewby6q3u35ceppt5fxme2p" CACHE PATH "") - -set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-cpdkv2uxkhs3qmzqakt4sht3vuiucdwt" CACHE PATH "") - -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-wpbbiugqvdw7bylkmyyf2quazcoptzah" CACHE PATH "") - -set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-to3lvqwzxrqzad65zbvnbtvyt4k63uxe" CACHE PATH "") - -set(LUA_DIR "${TPL_ROOT}/lua-5.4.8-y6z3polqakbuhn6nh6muri3rjxqmiioa" CACHE PATH "") - -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-z3eibnxgk3jeplydlcizrhurpibzqzqv" CACHE PATH "") - -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-eqokbtuch3pwifb225w2i3rrft5ddcgv" CACHE PATH "") - -# OPENCASCADE not built - -set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-lzgexbo5qyzepgju2acyy6d2qft443tb" CACHE PATH "") - -set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-wytd46zvuquxg2rqf3zec5ldbmjeaat6" CACHE PATH "") - -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-eplsxvli6nnrgdl4l3hl7cizytginfhp" CACHE PATH "") - -# scr not built - -#------------------------------------------------------------------------------ -# Devtools & Python -#------------------------------------------------------------------------------ - -set(DEVTOOLS_ROOT "/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25" CACHE PATH "") - -set(CLANGFORMAT_EXECUTABLE "/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm/bin/clang-format" CACHE PATH "") - -set(Python_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/python3" CACHE PATH "") - -set(JSONSCHEMA_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/jsonschema" CACHE PATH "") - -set(ENABLE_DOCS ON CACHE BOOL "") - -set(SPHINX_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/sphinx-build" CACHE PATH "") - -set(YAPF_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/yapf" CACHE PATH "") - -set(SHROUD_EXECUTABLE "/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0/bin/shroud" CACHE PATH "") - -set(CPPCHECK_EXECUTABLE "${DEVTOOLS_ROOT}/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27/bin/cppcheck" CACHE PATH "") - -set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/doxygen-1.15.0/bin/doxygen" CACHE PATH "") - -set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-ieb2bzuz435ydpyiq2uj36yyqjbrffml/lib/python3.13/site-packages" CACHE PATH "") - -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5/lib/python3.13/site-packages" CACHE PATH "") - -set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-xw5ysy75zfqlalmbmtl5phd2qip43hlo/lib/python3.13/site-packages" CACHE PATH "") - -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/py-pluggy-1.6.0-n4ojmtqyawxz6gipk64pup3ahwz3xqpu/lib/python3.13/site-packages" CACHE PATH "") - -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/py-iniconfig-2.1.0-lf3yvch65se22jr7b5unfoxtse4ugkrk/lib/python3.13/site-packages" CACHE PATH "") - -set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-e5tepnokpccpvjfp6lwnwfi5laakuhdh/lib/python3.13/site-packages" CACHE PATH "") - - diff --git a/host-configs/rzadams-toss_4_x86_64_ib_cray-cce@21.0.0_hip.cmake b/host-configs/rzadams-toss_4_x86_64_ib_cray-cce@21.0.0_hip.cmake new file mode 100644 index 0000000000..3c379f5e91 --- /dev/null +++ b/host-configs/rzadams-toss_4_x86_64_ib_cray-cce@21.0.0_hip.cmake @@ -0,0 +1,171 @@ +#------------------------------------------------------------------------------ +# !!!! This is a generated file, edit at own risk !!!! +#------------------------------------------------------------------------------ +# CMake executable path: /usr/tce/packages/cmake/cmake-3.29.2/bin/cmake +#------------------------------------------------------------------------------ + +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/cce-21.0.0/blt-0.7.2-nksv5wzh2ckjebp3gaczjotyxzkt5tuw;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/cce-21.0.0/c2c-1.8.0-elfizos6urooc46kuyg4gkxlm2k5xn54;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/cce-21.0.0/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-qnpa4ygyxq2jvwwgjmnb24eprakzuzio;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/cce-21.0.0/conduit-0.9.7-vexctj4cctvlrc6kmkpkn2ay4qcdbwrj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/cce-21.0.0/lua-5.4.8-5eyihk35qulaupbroruuuax2uqjvq5a4;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/cce-21.0.0/mfem-4.9.0-pk6k52bpyhqvlro22ivxspmnkaub5ws7;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/cce-21.0.0/py-nanobind-2.12.0-he5xenbyflysayerkt6ovwhjktryej6j;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/none-none/py-pytest-9.0.3-4yefg77cpcyymgmjssuqsb7ojxs7aiuo;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/cce-21.0.0/adiak-0.5.0-4iigkdlf2fzswe6dx3mh5turnlgflb5p;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/cce-21.0.0/libunwind-1.8.3-nyc3zz6art2rizfwhlyoq6zeia5fjnju;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/cce-21.0.0/hdf5-1.8.23-npwvi7rxvqyndhdmecm2lhf64qokekbm;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/cce-21.0.0/parmetis-4.0.3-okyjdeot3ycg4b7coi67cvxwzdu6i7tm;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/cce-21.0.0/py-mpi4py-4.1.1-jbvd3mlwrl5y7vgyd2b3qxkbws3ah6qn;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/cce-21.0.0/py-numpy-2.4.6-k7fnyomjx5pjsntja3yw7n2r3i7a45nh;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/cce-21.0.0/hypre-3.1.0-y7fwwlmj624ra3b7a4oor4bdw67y5k4s;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/cce-21.0.0/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-5x26ho4hxwgcxcjryppsw5gbcxvvhhnz;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/cce-21.0.0/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-kzkhyazpya22yjeonk3yrp32ld3cbx54;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/cce-21.0.0/zlib-1.3.2-eso4ifs5c2jwdtzfnxgzr6cad42brvcu;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/cce-21.0.0/metis-5.1.0-2vjouzdzzgoibxzivuiavaz6laeukj7s;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/cce-21.0.0/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-by47x6vtpcmgyi5nbxytaptyxeglkgds;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/cce-21.0.0/fmt-12.1.0-4yp5h7bwfiwhjbsdfyyxdsblkiipbii7;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/none-none/compiler-wrapper-1.1.0-yz3pcrs3u74ghvkyrkg5krf3a3y72be7;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cce-tce/cce-21.0.0;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-9.1.0-cce-21.0.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-7.2.1;/opt/rocm-7.2.1;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-7.2.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-7.2.1;/opt/rocm-7.2.1;/opt/rocm-7.2.1;/opt/rocm-7.2.1;/opt/rocm-7.2.1" CACHE STRING "") + +set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") + +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/cce-21.0.0/axom-develop-i4hvzqjodkkpqogefzv5lqi5ogdhfdlz/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/cce-21.0.0/axom-develop-i4hvzqjodkkpqogefzv5lqi5ogdhfdlz/lib64;;/opt/cray/pe/cce/21.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") + +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/cce-21.0.0/axom-develop-i4hvzqjodkkpqogefzv5lqi5ogdhfdlz/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/cce-21.0.0/axom-develop-i4hvzqjodkkpqogefzv5lqi5ogdhfdlz/lib64;;/opt/cray/pe/cce/21.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") + +set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") + +#------------------------------------------------------------------------------ +# Compilers +#------------------------------------------------------------------------------ +# Compiler Spec: cce@21.0.0/nmaros4ushwf5auvt3asaaazfjerle4l +#------------------------------------------------------------------------------ +if(DEFINED ENV{SPACK_CC}) + + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/none-none/compiler-wrapper-1.1.0-yz3pcrs3u74ghvkyrkg5krf3a3y72be7/libexec/spack/cce/craycc" CACHE PATH "") + + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/none-none/compiler-wrapper-1.1.0-yz3pcrs3u74ghvkyrkg5krf3a3y72be7/libexec/spack/cce/case-insensitive/crayCC" CACHE PATH "") + + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/none-none/compiler-wrapper-1.1.0-yz3pcrs3u74ghvkyrkg5krf3a3y72be7/libexec/spack/cce/crayftn" CACHE PATH "") + +else() + + set(CMAKE_C_COMPILER "/usr/tce/packages/cce-tce/cce-21.0.0/bin/craycc" CACHE PATH "") + + set(CMAKE_CXX_COMPILER "/usr/tce/packages/cce-tce/cce-21.0.0/bin/crayCC" CACHE PATH "") + + set(CMAKE_Fortran_COMPILER "/usr/tce/packages/cce-tce/cce-21.0.0/bin/crayftn" CACHE PATH "") + +endif() + +set(CMAKE_C_FLAGS "-fPIC" CACHE STRING "") + +set(CMAKE_CXX_FLAGS "-fPIC" CACHE STRING "") + +set(CMAKE_Fortran_FLAGS "-fPIC -ef" CACHE STRING "") + +set(ENABLE_FORTRAN ON CACHE BOOL "") + +set(CMAKE_CXX_FLAGS_DEBUG "-O1 -g" CACHE STRING "") + +#------------------------------------------------------------------------------ +# MPI +#------------------------------------------------------------------------------ + +set(MPI_C_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-9.1.0-cce-21.0.0/bin/mpicc" CACHE PATH "") + +set(MPI_CXX_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-9.1.0-cce-21.0.0/bin/mpicxx" CACHE PATH "") + +set(MPI_Fortran_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-9.1.0-cce-21.0.0/bin/mpif90" CACHE PATH "") + +set(MPIEXEC_EXECUTABLE "/usr/global/tools/flux_wrappers/bin/srun" CACHE PATH "") + +set(MPIEXEC_NUMPROC_FLAG "-n" CACHE STRING "") + +set(ENABLE_MPI ON CACHE BOOL "") + +#------------------------------------------------------------------------------ +# Hardware +#------------------------------------------------------------------------------ + +#------------------------------------------------ +# ROCm +#------------------------------------------------ + +set(ROCM_PATH "/opt/rocm-7.2.1" CACHE PATH "") + +set(CMAKE_HIP_ARCHITECTURES "gfx90a;gfx942" CACHE STRING "") + +set(CMAKE_HIP_COMPILER "/opt/rocm-7.2.1/bin/amdclang++" CACHE FILEPATH "") + +#------------------------------------------------------------------------------ + +# Axom ROCm specifics + +#------------------------------------------------------------------------------ + + +set(ENABLE_HIP ON CACHE BOOL "") + +set(ROCM_ROOT_DIR "/opt/rocm-7.2.1" CACHE PATH "") + +set(BLT_CMAKE_IMPLICIT_LINK_LIBRARIES_EXCLUDE "unwind;ompstub" CACHE STRING "") + +set(BLT_CMAKE_IMPLICIT_LINK_DIRECTORIES_EXCLUDE "/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12;/opt/rh/gcc-toolset-12/root/usr/lib64" CACHE STRING "") + +set(CMAKE_EXE_LINKER_FLAGS "-lxpmem -L/opt/cray/pe/mpich/9.1.0/gtl/lib -Wl,-rpath,/opt/cray/pe/mpich/9.1.0/gtl/lib -lmpi_gtl_hsa -L/opt/rocm-7.2.1/lib/llvm/lib -Wl,-rpath,/opt/rocm-7.2.1/lib/llvm/lib -L/opt/rocm-7.2.1/lib -Wl,-rpath,/opt/rocm-7.2.1/lib -lpgmath -L/opt/cray/pe/cce/21.0.0/cce/x86_64/lib -Wl,-rpath,/opt/cray/pe/cce/21.0.0/cce/x86_64/lib-lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " CACHE STRING "") + +#------------------------------------------------ +# Hardware Specifics +#------------------------------------------------ + +set(ENABLE_OPENMP ON CACHE BOOL "") + +set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") + +#------------------------------------------------------------------------------ +# TPLs +#------------------------------------------------------------------------------ + +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/cce-21.0.0" CACHE PATH "") + +set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.7-vexctj4cctvlrc6kmkpkn2ay4qcdbwrj" CACHE PATH "") + +set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-elfizos6urooc46kuyg4gkxlm2k5xn54" CACHE PATH "") + +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-pk6k52bpyhqvlro22ivxspmnkaub5ws7" CACHE PATH "") + +set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-npwvi7rxvqyndhdmecm2lhf64qokekbm" CACHE PATH "") + +set(LUA_DIR "${TPL_ROOT}/lua-5.4.8-5eyihk35qulaupbroruuuax2uqjvq5a4" CACHE PATH "") + +set(RAJA_DIR "${TPL_ROOT}/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-5x26ho4hxwgcxcjryppsw5gbcxvvhhnz" CACHE PATH "") + +set(UMPIRE_DIR "${TPL_ROOT}/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-kzkhyazpya22yjeonk3yrp32ld3cbx54" CACHE PATH "") + +# OPENCASCADE not built + +set(ADIAK_DIR "${TPL_ROOT}/adiak-0.5.0-4iigkdlf2fzswe6dx3mh5turnlgflb5p" CACHE PATH "") + +set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-qnpa4ygyxq2jvwwgjmnb24eprakzuzio" CACHE PATH "") + +set(CAMP_DIR "${TPL_ROOT}/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-by47x6vtpcmgyi5nbxytaptyxeglkgds" CACHE PATH "") + +# scr not built + +#------------------------------------------------------------------------------ +# Devtools & Python +#------------------------------------------------------------------------------ + +set(DEVTOOLS_ROOT "/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25" CACHE PATH "") + +set(CLANGFORMAT_EXECUTABLE "/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm/bin/clang-format" CACHE PATH "") + +set(Python_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/python3" CACHE PATH "") + +set(JSONSCHEMA_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/jsonschema" CACHE PATH "") + +set(ENABLE_DOCS ON CACHE BOOL "") + +set(SPHINX_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/sphinx-build" CACHE PATH "") + +set(YAPF_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/yapf" CACHE PATH "") + +set(SHROUD_EXECUTABLE "/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0/bin/shroud" CACHE PATH "") + +set(CPPCHECK_EXECUTABLE "${DEVTOOLS_ROOT}/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27/bin/cppcheck" CACHE PATH "") + +set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/doxygen-1.15.0/bin/doxygen" CACHE PATH "") + +set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.12.0-he5xenbyflysayerkt6ovwhjktryej6j/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/none-none/py-pytest-9.0.3-4yefg77cpcyymgmjssuqsb7ojxs7aiuo/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.6-k7fnyomjx5pjsntja3yw7n2r3i7a45nh/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/none-none/py-pluggy-1.6.0-ient4fgfjoacxpdsxw5qw6vqya3gid4b/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/none-none/py-iniconfig-2.1.0-h3ficswruattwshoykrh4mp2ds6fwnjg/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-jbvd3mlwrl5y7vgyd2b3qxkbws3ah6qn/lib/python3.13/site-packages" CACHE PATH "") + + diff --git a/host-configs/rzadams-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake b/host-configs/rzadams-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake deleted file mode 100644 index 01aaac36a5..0000000000 --- a/host-configs/rzadams-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake +++ /dev/null @@ -1,163 +0,0 @@ -#------------------------------------------------------------------------------ -# !!!! This is a generated file, edit at own risk !!!! -#------------------------------------------------------------------------------ -# CMake executable path: /usr/tce/packages/cmake/cmake-3.29.2/bin/cmake -#------------------------------------------------------------------------------ - -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1/adiak-0.4.0-c4srnrz5apjetx74dt6xjah63o3a7xcx;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1/blt-0.7.1-kzmxfjvxm4drr2qhspckvwee6mrlc3e5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1/c2c-1.8.0-lm64gc55hpxilmtsw7geirys3q4wacu5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1/conduit-0.9.5-wswspowegvp5byl5inc2cikljzkcqg64;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1/lua-5.4.8-j7ngytniswhoa536kqfzarcraarkicbm;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1/mfem-4.9.0-xwg5un3mlbeog3j4voeltekra43ix3ga;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1/py-nanobind-2.7.0-q2kwozph3gwgfyjnx43jp2gkbaghwxoj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1/hdf5-1.8.23-rb4cjthx4wz4wnpaizwmm2jycijdthqm;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1/parmetis-4.0.3-ng66tt5fmjyfoiuuxyl4rxqv4ompawmd;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1/py-mpi4py-4.1.1-s4a64moil7qmgczpelmmb344qxxavrzk;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1/py-numpy-2.4.2-zcxbrxijjc3miotq6s3kq446afrgycuu;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1/hypre-2.27.0-6m2jwyzqbksxgmdznvh54ytysxyme7jy;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-wcyqyggju4u2ornjjnamgdgfbqjrzsir;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1/umpire-2025.12.0-5w5v22edixuttxwueaagiobpjzn5mcgr;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1/zlib-1.3.1-5w5muvpvct6uruxrddscblsiyizl3uoc;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1/metis-5.1.0-4punmdkye6rovr3u7ph7bgj66qim77du;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-jnalmgzqx2nn2afq4gwhyjda3u6yub7y;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1/fmt-11.0.2-opsfnufdiedpfpob5wgbvhcrhyhzfkaq;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.3.1;/opt/rocm-6.3.1;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.3.1;/opt/rocm-6.3.1;/opt/rocm-6.3.1" CACHE STRING "") - -set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") - -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1/axom-develop-4srm7we3wjp7l4cfcshzov5nr52jna4r/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1/axom-develop-4srm7we3wjp7l4cfcshzov5nr52jna4r/lib64;;/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12" CACHE STRING "") - -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1/axom-develop-4srm7we3wjp7l4cfcshzov5nr52jna4r/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1/axom-develop-4srm7we3wjp7l4cfcshzov5nr52jna4r/lib64;;/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12" CACHE STRING "") - -set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") - -#------------------------------------------------------------------------------ -# Compilers -#------------------------------------------------------------------------------ -# Compiler Spec: llvm-amdgpu@6.3.1/ns2pv5bzkpjolxzmhgkckj6glqymgd7e -#------------------------------------------------------------------------------ -if(DEFINED ENV{SPACK_CC}) - - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdclang" CACHE PATH "") - - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdclang++" CACHE PATH "") - - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdflang" CACHE PATH "") - -else() - - set(CMAKE_C_COMPILER "/opt/rocm-6.3.1/llvm/bin/amdclang" CACHE PATH "") - - set(CMAKE_CXX_COMPILER "/opt/rocm-6.3.1/llvm/bin/amdclang++" CACHE PATH "") - - set(CMAKE_Fortran_COMPILER "/opt/rocm-6.3.1/llvm/bin/amdflang" CACHE PATH "") - -endif() - -set(CMAKE_Fortran_FLAGS "-Mfreeform" CACHE STRING "") - -set(ENABLE_FORTRAN ON CACHE BOOL "") - -#------------------------------------------------------------------------------ -# MPI -#------------------------------------------------------------------------------ - -set(MPI_C_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.3.1/bin/mpicc" CACHE PATH "") - -set(MPI_CXX_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.3.1/bin/mpicxx" CACHE PATH "") - -set(MPI_Fortran_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.3.1/bin/mpif90" CACHE PATH "") - -set(MPIEXEC_EXECUTABLE "/usr/global/tools/flux_wrappers/bin/srun" CACHE PATH "") - -set(MPIEXEC_NUMPROC_FLAG "-n" CACHE STRING "") - -set(ENABLE_MPI ON CACHE BOOL "") - -#------------------------------------------------------------------------------ -# Hardware -#------------------------------------------------------------------------------ - -#------------------------------------------------ -# ROCm -#------------------------------------------------ - -set(ROCM_PATH "/opt/rocm-6.3.1" CACHE PATH "") - -set(CMAKE_HIP_ARCHITECTURES "gfx90a;gfx942" CACHE STRING "") - -set(CMAKE_HIP_COMPILER "/opt/rocm-6.3.1/bin/amdclang++" CACHE FILEPATH "") - -#------------------------------------------------------------------------------ - -# Axom ROCm specifics - -#------------------------------------------------------------------------------ - - -set(ENABLE_HIP ON CACHE BOOL "") - -set(ROCM_ROOT_DIR "/opt/rocm-6.3.1" CACHE PATH "") - -set(BLT_CMAKE_IMPLICIT_LINK_LIBRARIES_EXCLUDE "ompstub" CACHE STRING "") - -set(CMAKE_EXE_LINKER_FLAGS "-lxpmem -L/opt/cray/pe/mpich/8.1.29/gtl/lib -Wl,-rpath,/opt/cray/pe/mpich/8.1.29/gtl/lib -lmpi_gtl_hsa -L/opt/rocm-6.3.1/lib/llvm/lib -Wl,-rpath,/opt/rocm-6.3.1/lib/llvm/lib -L/opt/rocm-6.3.1/lib -Wl,-rpath,/opt/rocm-6.3.1/lib -lpgmath -Wl,--disable-new-dtags -lflang -lflangrti -lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " CACHE STRING "") - -#------------------------------------------------ -# Hardware Specifics -#------------------------------------------------ - -set(ENABLE_OPENMP ON CACHE BOOL "") - -set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") - -#------------------------------------------------------------------------------ -# TPLs -#------------------------------------------------------------------------------ - -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.3.1" CACHE PATH "") - -set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-wswspowegvp5byl5inc2cikljzkcqg64" CACHE PATH "") - -set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-lm64gc55hpxilmtsw7geirys3q4wacu5" CACHE PATH "") - -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-xwg5un3mlbeog3j4voeltekra43ix3ga" CACHE PATH "") - -set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-rb4cjthx4wz4wnpaizwmm2jycijdthqm" CACHE PATH "") - -set(LUA_DIR "${TPL_ROOT}/lua-5.4.8-j7ngytniswhoa536kqfzarcraarkicbm" CACHE PATH "") - -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-wcyqyggju4u2ornjjnamgdgfbqjrzsir" CACHE PATH "") - -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-5w5v22edixuttxwueaagiobpjzn5mcgr" CACHE PATH "") - -# OPENCASCADE not built - -set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-c4srnrz5apjetx74dt6xjah63o3a7xcx" CACHE PATH "") - -# CALIPER not built - -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-jnalmgzqx2nn2afq4gwhyjda3u6yub7y" CACHE PATH "") - -# scr not built - -#------------------------------------------------------------------------------ -# Devtools & Python -#------------------------------------------------------------------------------ - -set(DEVTOOLS_ROOT "/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25" CACHE PATH "") - -set(CLANGFORMAT_EXECUTABLE "/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm/bin/clang-format" CACHE PATH "") - -set(Python_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/python3" CACHE PATH "") - -set(JSONSCHEMA_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/jsonschema" CACHE PATH "") - -set(ENABLE_DOCS ON CACHE BOOL "") - -set(SPHINX_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/sphinx-build" CACHE PATH "") - -set(YAPF_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/yapf" CACHE PATH "") - -set(SHROUD_EXECUTABLE "/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0/bin/shroud" CACHE PATH "") - -set(CPPCHECK_EXECUTABLE "${DEVTOOLS_ROOT}/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27/bin/cppcheck" CACHE PATH "") - -set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/doxygen-1.15.0/bin/doxygen" CACHE PATH "") - -set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-q2kwozph3gwgfyjnx43jp2gkbaghwxoj/lib/python3.13/site-packages" CACHE PATH "") - -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5/lib/python3.13/site-packages" CACHE PATH "") - -set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-zcxbrxijjc3miotq6s3kq446afrgycuu/lib/python3.13/site-packages" CACHE PATH "") - -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/py-pluggy-1.6.0-n4ojmtqyawxz6gipk64pup3ahwz3xqpu/lib/python3.13/site-packages" CACHE PATH "") - -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/py-iniconfig-2.1.0-lf3yvch65se22jr7b5unfoxtse4ugkrk/lib/python3.13/site-packages" CACHE PATH "") - -set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-s4a64moil7qmgczpelmmb344qxxavrzk/lib/python3.13/site-packages" CACHE PATH "") - - diff --git a/host-configs/rzadams-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake b/host-configs/rzadams-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake index c7f0986432..08aed62c2e 100644 --- a/host-configs/rzadams-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake +++ b/host-configs/rzadams-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake @@ -4,28 +4,28 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.29.2/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/blt-0.7.1-4kxhk7nbuiv5oh2ymh7sdimnmor36hmo;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/c2c-1.8.0-h5kio7nseyazg4pzgvj4kxanb3plhbxm;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-rmlwpdacdtpzf4u72ctyroj72soqzf3f;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/conduit-0.9.5-zqywrasos2vbears5hvdwxhulm3xox6q;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/lua-5.4.8-gp4kdewsy4uiy6flsx342lgogcm7fym2;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/mfem-4.9.0-6cgokeizyrjzsjzwmtf35p5jvhnws7n5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/py-nanobind-2.7.0-2kp35jcpb73xuujvcz6kmg4q2npek4gg;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/adiak-0.4.0-cropyu42jr4q6k3zar6mkma6gc2vp6tk;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/libunwind-1.8.3-plszp43gkd75kzjcvge3qxrtb6j2budl;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/hdf5-1.8.23-mgic2kxojd5zeivhaz473zzmzrg3gdqe;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/parmetis-4.0.3-hd2qoczrh3vw3ejm6msitqdgecjk7xwe;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/py-mpi4py-4.1.1-wimx6qtzmouefjmhidpsho6iipv234ay;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/py-numpy-2.4.2-a3mo27shjtpliwewbm5cci2kf37crrab;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/hypre-2.27.0-4nrl5egspbxmnkpzfym5udby6uruhn3i;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-v74yskgcb7tsgho6bdush5jdlbigkttf;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/umpire-2025.12.0-jft5rocv5ev4b57f3664b2kagyfjra2e;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/zlib-1.3.1-slqujeue3l44x3pgqbu7e2pw3lh2mj5i;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/metis-5.1.0-puzeddu6ufhz5vpxnw7i3i2i6slsvuuj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-zadbjpethvnlhpqwx6vm6ahphk42a4gq;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/fmt-11.0.2-lpnox72rqcneew5hucwzq4nmms4at525;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-6.4.3/blt-0.7.2-bzkumxze6js6wdthtsyl4ftpehsiaxio;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-6.4.3/c2c-1.8.0-ydvwtgebkhgntradrdhlngk2e2ins6ts;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-6.4.3/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-emtyurqbikhifn5wtfw474oe3kn64mfi;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-6.4.3/conduit-0.9.7-f6ii6fwopqrxtnu3wbzur6pp5toyxhii;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-6.4.3/lua-5.4.8-tje5nnei5fpukdicoscj7qxbzqjpe4m3;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-6.4.3/mfem-4.9.0-pjfp26ljzvrlffcxgse2jlc5g5n5246y;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-6.4.3/py-nanobind-2.12.0-numnrtajdqauya2idgshkccahuczfpkw;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/none-none/py-pytest-9.0.3-4yefg77cpcyymgmjssuqsb7ojxs7aiuo;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-6.4.3/adiak-0.5.0-y4dumyncsqyo6zpicmqathscj7ifygfz;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-6.4.3/libunwind-1.8.3-pi2bqmxzk2wzxc7bzen4e4prp6ne6kdt;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-6.4.3/hdf5-1.8.23-3hacwkv36ucg3rpihr47xas6hs5jsa4y;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-6.4.3/parmetis-4.0.3-4vksfdsbadamrzasmbyzj36urczamcob;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-6.4.3/py-mpi4py-4.1.1-6gainjtlacfzkhyqqz7urkdo3yj3236p;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-6.4.3/py-numpy-2.4.6-cajeg4ft7ph2grvmugdwnusk5qdn6ykl;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-6.4.3/hypre-3.1.0-icopylgqlovyr65krmmtz4grihalnwpa;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-6.4.3/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-3ov5ypd4f3gv7l4f4kb2ihulqjntt2d3;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-6.4.3/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-hiu6spvj4flzb626dg3hxhgp45jnnbt6;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-6.4.3/zlib-1.3.2-yrtzpfoi5ikkd74jkizit5jxnmb4rdxc;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-6.4.3/metis-5.1.0-i5u67yfxxq355s6bplogrdwr6mnynz4s;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-6.4.3/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-2jyb2ctyhlpuidy4z256v7j2ugzqfdpc;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-6.4.3/fmt-12.1.0-4obgphnlva42oeyokwqzkpmsooswpbux;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/none-none/compiler-wrapper-1.1.0-yz3pcrs3u74ghvkyrkg5krf3a3y72be7;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.4.3;/opt/rocm-7.2.1;/opt/rocm-6.4.3;/opt/rocm-7.2.1;/opt/rocm-6.4.3" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/axom-develop-iczg7quatuysr5e2ggwwzdsnmdzw7odk/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/axom-develop-iczg7quatuysr5e2ggwwzdsnmdzw7odk/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-6.4.3/axom-develop-3funbci2tffxb7kc5a75aj6no7mebalr/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-6.4.3/axom-develop-3funbci2tffxb7kc5a75aj6no7mebalr/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/axom-develop-iczg7quatuysr5e2ggwwzdsnmdzw7odk/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3/axom-develop-iczg7quatuysr5e2ggwwzdsnmdzw7odk/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-6.4.3/axom-develop-3funbci2tffxb7kc5a75aj6no7mebalr/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-6.4.3/axom-develop-3funbci2tffxb7kc5a75aj6no7mebalr/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ # Compilers #------------------------------------------------------------------------------ -# Compiler Spec: llvm-amdgpu@6.4.3/u67i6xutdthzl3yti7tv3klyhyzddbkv +# Compiler Spec: llvm-amdgpu@6.4.3/j3unaymyuiplqezbi6qng6k2nyia7raa #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdclang" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/none-none/compiler-wrapper-1.1.0-yz3pcrs3u74ghvkyrkg5krf3a3y72be7/libexec/spack/rocmcc/amdclang" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdclang++" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/none-none/compiler-wrapper-1.1.0-yz3pcrs3u74ghvkyrkg5krf3a3y72be7/libexec/spack/rocmcc/amdclang++" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdflang" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/none-none/compiler-wrapper-1.1.0-yz3pcrs3u74ghvkyrkg5krf3a3y72be7/libexec/spack/rocmcc/amdflang" CACHE PATH "") else() @@ -37,7 +37,11 @@ else() endif() -set(CMAKE_Fortran_FLAGS "-Mfreeform" CACHE STRING "") +set(CMAKE_C_FLAGS "-fPIC" CACHE STRING "") + +set(CMAKE_CXX_FLAGS "-fPIC" CACHE STRING "") + +set(CMAKE_Fortran_FLAGS "-fPIC -Mfreeform" CACHE STRING "") set(ENABLE_FORTRAN ON CACHE BOOL "") @@ -84,6 +88,8 @@ set(ROCM_ROOT_DIR "/opt/rocm-6.4.3" CACHE PATH "") set(BLT_CMAKE_IMPLICIT_LINK_LIBRARIES_EXCLUDE "ompstub" CACHE STRING "") +set(BLT_CMAKE_IMPLICIT_LINK_DIRECTORIES_EXCLUDE "/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12;/opt/rh/gcc-toolset-12/root/usr/lib64" CACHE STRING "") + set(CMAKE_EXE_LINKER_FLAGS "-lxpmem -L/opt/cray/pe/mpich/8.1.29/gtl/lib -Wl,-rpath,/opt/cray/pe/mpich/8.1.29/gtl/lib -lmpi_gtl_hsa -L/opt/rocm-6.4.3/lib/llvm/lib -Wl,-rpath,/opt/rocm-6.4.3/lib/llvm/lib -L/opt/rocm-6.4.3/lib -Wl,-rpath,/opt/rocm-6.4.3/lib -lpgmath -Wl,--disable-new-dtags -lflang -lflangrti -lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " CACHE STRING "") #------------------------------------------------ @@ -98,29 +104,29 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/llvm-amdgpu-6.4.3" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-6.4.3" CACHE PATH "") -set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-zqywrasos2vbears5hvdwxhulm3xox6q" CACHE PATH "") +set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.7-f6ii6fwopqrxtnu3wbzur6pp5toyxhii" CACHE PATH "") -set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-h5kio7nseyazg4pzgvj4kxanb3plhbxm" CACHE PATH "") +set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-ydvwtgebkhgntradrdhlngk2e2ins6ts" CACHE PATH "") -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-6cgokeizyrjzsjzwmtf35p5jvhnws7n5" CACHE PATH "") +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-pjfp26ljzvrlffcxgse2jlc5g5n5246y" CACHE PATH "") -set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-mgic2kxojd5zeivhaz473zzmzrg3gdqe" CACHE PATH "") +set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-3hacwkv36ucg3rpihr47xas6hs5jsa4y" CACHE PATH "") -set(LUA_DIR "${TPL_ROOT}/lua-5.4.8-gp4kdewsy4uiy6flsx342lgogcm7fym2" CACHE PATH "") +set(LUA_DIR "${TPL_ROOT}/lua-5.4.8-tje5nnei5fpukdicoscj7qxbzqjpe4m3" CACHE PATH "") -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-v74yskgcb7tsgho6bdush5jdlbigkttf" CACHE PATH "") +set(RAJA_DIR "${TPL_ROOT}/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-3ov5ypd4f3gv7l4f4kb2ihulqjntt2d3" CACHE PATH "") -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-jft5rocv5ev4b57f3664b2kagyfjra2e" CACHE PATH "") +set(UMPIRE_DIR "${TPL_ROOT}/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-hiu6spvj4flzb626dg3hxhgp45jnnbt6" CACHE PATH "") # OPENCASCADE not built -set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-cropyu42jr4q6k3zar6mkma6gc2vp6tk" CACHE PATH "") +set(ADIAK_DIR "${TPL_ROOT}/adiak-0.5.0-y4dumyncsqyo6zpicmqathscj7ifygfz" CACHE PATH "") -set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-rmlwpdacdtpzf4u72ctyroj72soqzf3f" CACHE PATH "") +set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-emtyurqbikhifn5wtfw474oe3kn64mfi" CACHE PATH "") -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-zadbjpethvnlhpqwx6vm6ahphk42a4gq" CACHE PATH "") +set(CAMP_DIR "${TPL_ROOT}/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-2jyb2ctyhlpuidy4z256v7j2ugzqfdpc" CACHE PATH "") # scr not built @@ -148,16 +154,16 @@ set(CPPCHECK_EXECUTABLE "${DEVTOOLS_ROOT}/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/doxygen-1.15.0/bin/doxygen" CACHE PATH "") -set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-2kp35jcpb73xuujvcz6kmg4q2npek4gg/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.12.0-numnrtajdqauya2idgshkccahuczfpkw/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/none-none/py-pytest-9.0.3-4yefg77cpcyymgmjssuqsb7ojxs7aiuo/lib/python3.13/site-packages" CACHE PATH "") -set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-a3mo27shjtpliwewbm5cci2kf37crrab/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.6-cajeg4ft7ph2grvmugdwnusk5qdn6ykl/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/py-pluggy-1.6.0-n4ojmtqyawxz6gipk64pup3ahwz3xqpu/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/none-none/py-pluggy-1.6.0-ient4fgfjoacxpdsxw5qw6vqya3gid4b/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_56_56/none-none/py-iniconfig-2.1.0-lf3yvch65se22jr7b5unfoxtse4ugkrk/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/none-none/py-iniconfig-2.1.0-h3ficswruattwshoykrh4mp2ds6fwnjg/lib/python3.13/site-packages" CACHE PATH "") -set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-wimx6qtzmouefjmhidpsho6iipv234ay/lib/python3.13/site-packages" CACHE PATH "") +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-6gainjtlacfzkhyqqz7urkdo3yj3236p/lib/python3.13/site-packages" CACHE PATH "") diff --git a/host-configs/rzadams-toss_4_x86_64_ib_cray-llvm-amdgpu@7.2.1_hip.cmake b/host-configs/rzadams-toss_4_x86_64_ib_cray-llvm-amdgpu@7.2.1_hip.cmake new file mode 100644 index 0000000000..6a72ceb22c --- /dev/null +++ b/host-configs/rzadams-toss_4_x86_64_ib_cray-llvm-amdgpu@7.2.1_hip.cmake @@ -0,0 +1,169 @@ +#------------------------------------------------------------------------------ +# !!!! This is a generated file, edit at own risk !!!! +#------------------------------------------------------------------------------ +# CMake executable path: /usr/tce/packages/cmake/cmake-3.29.2/bin/cmake +#------------------------------------------------------------------------------ + +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-7.2.1/blt-0.7.2-nxjxxyf2y5djqa4vln5xupa6ew3ynot3;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-7.2.1/c2c-1.8.0-h4dewx3nxtgmrrh6lp6qan5prczqgfaj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-7.2.1/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-zbyb2mglk6bf5a3ximlcf6ku7k66zybh;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-7.2.1/conduit-0.9.7-kfuntpxuli4mn2pwj35inyxavtzc6cr7;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-7.2.1/lua-5.4.8-nxtvprvcyhamwfbtybkegxasjkzccxqy;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-7.2.1/mfem-4.9.0-bcwbljz6eiilxpjtkkdfda4kk3lfwazl;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-7.2.1/py-nanobind-2.12.0-zogbnvfusufkfpdv57rgb5tplbzlvklj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/none-none/py-pytest-9.0.3-4yefg77cpcyymgmjssuqsb7ojxs7aiuo;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-7.2.1/adiak-0.5.0-dg7yk2rewn3kqsz7fqdgginoknzvxamg;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-7.2.1/libunwind-1.8.3-iz7k4qtg4do67ekisexgc2tfil5vrhv3;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-7.2.1/hdf5-1.8.23-evyfjqno2x77bsy7wjoxjw6xg5lz2ndv;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-7.2.1/parmetis-4.0.3-yskd6p7t2azzm3vyjvig42tfpi2rkcua;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-7.2.1/py-mpi4py-4.1.1-bnctpy3xteakdhzawno2xnpof7keqrhb;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-7.2.1/py-numpy-2.4.6-vtwtcnpk3cxghcggxaaohuxmvdtcqavf;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-7.2.1/hypre-3.1.0-mws6aupd6t33sb3c6prw3e2qa2be5glv;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-7.2.1/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-x3acixz33hp3o6zoob76rfwqx7vyy3rw;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-7.2.1/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-ypm44b4vaj27oi6drmdn5hvmggfo4oq4;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-7.2.1/zlib-1.3.2-iqcsdt34b6drsoycmdkejtnrwheup3s5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-7.2.1/metis-5.1.0-jahfgmirxbyg3ljsuef4dm5jx27d23op;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-7.2.1/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-ipojuvuh27sjhbzzikt2trem3zlmaxkx;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-7.2.1/fmt-12.1.0-d7qzs4sxkrgsyhpdvpu33hcrzkxulav4;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/none-none/compiler-wrapper-1.1.0-yz3pcrs3u74ghvkyrkg5krf3a3y72be7;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-9.1.0-rocmcc-7.2.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-7.2.1;/opt/rocm-7.2.1;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-7.2.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-7.2.1;/opt/rocm-7.2.1;/opt/rocm-7.2.1;/opt/rocm-7.2.1;/opt/rocm-7.2.1" CACHE STRING "") + +set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") + +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-7.2.1/axom-develop-b7t6pgp63sylxbredxuhfauebh2aerf7/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-7.2.1/axom-develop-b7t6pgp63sylxbredxuhfauebh2aerf7/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") + +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-7.2.1/axom-develop-b7t6pgp63sylxbredxuhfauebh2aerf7/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-7.2.1/axom-develop-b7t6pgp63sylxbredxuhfauebh2aerf7/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") + +set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") + +#------------------------------------------------------------------------------ +# Compilers +#------------------------------------------------------------------------------ +# Compiler Spec: llvm-amdgpu@7.2.1/titlcdv5eonxuakrjl6bcxvgndtjk4xy +#------------------------------------------------------------------------------ +if(DEFINED ENV{SPACK_CC}) + + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/none-none/compiler-wrapper-1.1.0-yz3pcrs3u74ghvkyrkg5krf3a3y72be7/libexec/spack/rocmcc/amdclang" CACHE PATH "") + + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/none-none/compiler-wrapper-1.1.0-yz3pcrs3u74ghvkyrkg5krf3a3y72be7/libexec/spack/rocmcc/amdclang++" CACHE PATH "") + + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/none-none/compiler-wrapper-1.1.0-yz3pcrs3u74ghvkyrkg5krf3a3y72be7/libexec/spack/rocmcc/amdflang" CACHE PATH "") + +else() + + set(CMAKE_C_COMPILER "/opt/rocm-7.2.1/llvm/bin/amdclang" CACHE PATH "") + + set(CMAKE_CXX_COMPILER "/opt/rocm-7.2.1/llvm/bin/amdclang++" CACHE PATH "") + + set(CMAKE_Fortran_COMPILER "/opt/rocm-7.2.1/llvm/bin/amdflang" CACHE PATH "") + +endif() + +set(CMAKE_C_FLAGS "-fPIC" CACHE STRING "") + +set(CMAKE_CXX_FLAGS "-fPIC" CACHE STRING "") + +set(CMAKE_Fortran_FLAGS "-fPIC" CACHE STRING "") + +set(ENABLE_FORTRAN ON CACHE BOOL "") + +#------------------------------------------------------------------------------ +# MPI +#------------------------------------------------------------------------------ + +set(MPI_C_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-9.1.0-rocmcc-7.2.1/bin/mpicc" CACHE PATH "") + +set(MPI_CXX_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-9.1.0-rocmcc-7.2.1/bin/mpicxx" CACHE PATH "") + +set(MPI_Fortran_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-9.1.0-rocmcc-7.2.1/bin/mpif90" CACHE PATH "") + +set(MPIEXEC_EXECUTABLE "/usr/global/tools/flux_wrappers/bin/srun" CACHE PATH "") + +set(MPIEXEC_NUMPROC_FLAG "-n" CACHE STRING "") + +set(ENABLE_MPI ON CACHE BOOL "") + +#------------------------------------------------------------------------------ +# Hardware +#------------------------------------------------------------------------------ + +#------------------------------------------------ +# ROCm +#------------------------------------------------ + +set(ROCM_PATH "/opt/rocm-7.2.1" CACHE PATH "") + +set(CMAKE_HIP_ARCHITECTURES "gfx90a;gfx942" CACHE STRING "") + +set(CMAKE_HIP_COMPILER "/opt/rocm-7.2.1/bin/amdclang++" CACHE FILEPATH "") + +#------------------------------------------------------------------------------ + +# Axom ROCm specifics + +#------------------------------------------------------------------------------ + + +set(ENABLE_HIP ON CACHE BOOL "") + +set(ROCM_ROOT_DIR "/opt/rocm-7.2.1" CACHE PATH "") + +set(BLT_CMAKE_IMPLICIT_LINK_LIBRARIES_EXCLUDE "ompstub" CACHE STRING "") + +set(BLT_CMAKE_IMPLICIT_LINK_DIRECTORIES_EXCLUDE "/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12;/opt/rh/gcc-toolset-12/root/usr/lib64" CACHE STRING "") + +set(CMAKE_EXE_LINKER_FLAGS "-lxpmem -L/opt/cray/pe/mpich/9.1.0/gtl/lib -Wl,-rpath,/opt/cray/pe/mpich/9.1.0/gtl/lib -lmpi_gtl_hsa -L/opt/rocm-7.2.1/lib/llvm/lib -Wl,-rpath,/opt/rocm-7.2.1/lib/llvm/lib -L/opt/rocm-7.2.1/lib -Wl,-rpath,/opt/rocm-7.2.1/lib -lpgmath -Wl,--disable-new-dtags -lflang -lflangrti -lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " CACHE STRING "") + +#------------------------------------------------ +# Hardware Specifics +#------------------------------------------------ + +set(ENABLE_OPENMP ON CACHE BOOL "") + +set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") + +#------------------------------------------------------------------------------ +# TPLs +#------------------------------------------------------------------------------ + +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/llvm-amdgpu-7.2.1" CACHE PATH "") + +set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.7-kfuntpxuli4mn2pwj35inyxavtzc6cr7" CACHE PATH "") + +set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-h4dewx3nxtgmrrh6lp6qan5prczqgfaj" CACHE PATH "") + +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-bcwbljz6eiilxpjtkkdfda4kk3lfwazl" CACHE PATH "") + +set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-evyfjqno2x77bsy7wjoxjw6xg5lz2ndv" CACHE PATH "") + +set(LUA_DIR "${TPL_ROOT}/lua-5.4.8-nxtvprvcyhamwfbtybkegxasjkzccxqy" CACHE PATH "") + +set(RAJA_DIR "${TPL_ROOT}/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-x3acixz33hp3o6zoob76rfwqx7vyy3rw" CACHE PATH "") + +set(UMPIRE_DIR "${TPL_ROOT}/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-ypm44b4vaj27oi6drmdn5hvmggfo4oq4" CACHE PATH "") + +# OPENCASCADE not built + +set(ADIAK_DIR "${TPL_ROOT}/adiak-0.5.0-dg7yk2rewn3kqsz7fqdgginoknzvxamg" CACHE PATH "") + +set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-zbyb2mglk6bf5a3ximlcf6ku7k66zybh" CACHE PATH "") + +set(CAMP_DIR "${TPL_ROOT}/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-ipojuvuh27sjhbzzikt2trem3zlmaxkx" CACHE PATH "") + +# scr not built + +#------------------------------------------------------------------------------ +# Devtools & Python +#------------------------------------------------------------------------------ + +set(DEVTOOLS_ROOT "/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25" CACHE PATH "") + +set(CLANGFORMAT_EXECUTABLE "/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm/bin/clang-format" CACHE PATH "") + +set(Python_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/python3" CACHE PATH "") + +set(JSONSCHEMA_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/jsonschema" CACHE PATH "") + +set(ENABLE_DOCS ON CACHE BOOL "") + +set(SPHINX_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/sphinx-build" CACHE PATH "") + +set(YAPF_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/yapf" CACHE PATH "") + +set(SHROUD_EXECUTABLE "/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0/bin/shroud" CACHE PATH "") + +set(CPPCHECK_EXECUTABLE "${DEVTOOLS_ROOT}/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27/bin/cppcheck" CACHE PATH "") + +set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/doxygen-1.15.0/bin/doxygen" CACHE PATH "") + +set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.12.0-zogbnvfusufkfpdv57rgb5tplbzlvklj/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/none-none/py-pytest-9.0.3-4yefg77cpcyymgmjssuqsb7ojxs7aiuo/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.6-vtwtcnpk3cxghcggxaaohuxmvdtcqavf/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/none-none/py-pluggy-1.6.0-ient4fgfjoacxpdsxw5qw6vqya3gid4b/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_59_19/none-none/py-iniconfig-2.1.0-h3ficswruattwshoykrh4mp2ds6fwnjg/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-bnctpy3xteakdhzawno2xnpof7keqrhb/lib/python3.13/site-packages" CACHE PATH "") + + diff --git a/host-configs/rzvector-toss_4_x86_64_ib-gcc@13.3.1_cuda.cmake b/host-configs/rzvector-toss_4_x86_64_ib-gcc@13.3.1_cuda.cmake index 8af38036da..075dbdfa6e 100644 --- a/host-configs/rzvector-toss_4_x86_64_ib-gcc@13.3.1_cuda.cmake +++ b/host-configs/rzvector-toss_4_x86_64_ib-gcc@13.3.1_cuda.cmake @@ -4,28 +4,28 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.26.3/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1/blt-0.7.1-lj5bghgkodlzmfs75wimnoxuui2quc3a;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1/c2c-1.8.0-tyixdhhwshu35d2vuh6gw5bk3b3k7nqz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-apockdkmc2slp4nexkn6sbspz7pibric;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1/conduit-0.9.5-vlm6nb2rwwmvnxtnxw5p3o27rzsap6vt;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1/mfem-4.9.0-dyhiljftw2yufyry2ybddjlzrst7usbg;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1/py-nanobind-2.7.0-e42etfacct5pxga65kryysakw52u4c63;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-hjzgnbnbz4sifpanhmuqhuuydzmctbbl;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1/umpire-2025.12.0-yxkn4g7yhresecqk2dkjivbahvu3qhpp;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1/adiak-0.4.0-nqiuo5ltc24jpawzi6mlp3xtmngcopiu;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1/libunwind-1.8.3-a7zvz3euhmbhk4as6h6j66ytabzwztxb;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1/hdf5-1.8.23-zyfoez4ztydoqfeubcamjypyhprjpuca;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1/parmetis-4.0.3-sxnyn5chjyiyowzk32dsynslufrofyho;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1/py-mpi4py-4.1.1-l3h5jmmg773bd5oer7makte3ln6rn7xv;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1/py-numpy-2.4.2-to6o5czwkczlh46d6gkubixw5bhzxakl;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1/hypre-2.27.0-3ckgmamfpdf3ng74h76dziarqz63godz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-bt6ckjhqcfr3gjiu76sdhabhppjhjd6t;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1/fmt-11.0.2-5wsybhdkos4xu5i7thboag6245lzc6jj;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1/metis-5.1.0-3qhomb7zt2n7nh46kmc2egkzmcd3tafm;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/none-none/gcc-runtime-13.3.1-xlvjd7vcdngtnvtxcdyqwtquvenekynf;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.26.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66xzj4wm5kfqxyc4vgox7gb3;/usr/tce/packages/cuda/cuda-12.9.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/doxygen-1.15.0;/usr/tce/packages/gcc/gcc-13.3.1;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-gcc-13.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/gcc-13.3.1/blt-0.7.2-kgttmwj56kwpge3oveotwgx7amhmc54b;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/gcc-13.3.1/c2c-1.8.0-ilhafup6lemuluscuhe2wcw6dhqwukac;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/gcc-13.3.1/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-7t2cu7tn3ltyjwyknkt3dnsgru7iynhx;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/gcc-13.3.1/conduit-0.9.7-cj7f7ue7tuh5za3vyckhvazoaa5dirlz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/gcc-13.3.1/mfem-4.9.0-tjktdj5puze2pgidvstbs54ivwo3p426;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/gcc-13.3.1/py-nanobind-2.12.0-2slokzflsbdyymtlubukr5uv352aaqol;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/none-none/py-pytest-9.0.3-gzmipids43fsxiidqzdglv4cbaxj26gk;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/gcc-13.3.1/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-r7pmta3pbsca6mmndrycorx34iog7kp7;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/gcc-13.3.1/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-xub4ymu4oek5r2fsa3rwq3dv6vtkicur;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/gcc-13.3.1/adiak-0.5.0-rv7mu2xwq332xz2xqgfjtond4hvjlcvi;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/gcc-13.3.1/libunwind-1.8.3-3ltfsqv444quhz3wviidadz2x57mcmfd;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/gcc-13.3.1/hdf5-1.8.23-rephuaeqmzpcjiazcflzeswdvsbwuvd2;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/gcc-13.3.1/parmetis-4.0.3-ykjugf2p3cjircoep575ra3cn4we724b;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/gcc-13.3.1/py-mpi4py-4.1.1-susobssvjtqo5inxlvwts2s5qacifmqy;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/gcc-13.3.1/py-numpy-2.4.6-owxvcv322niqkkfckectzayaxxwre7f4;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/gcc-13.3.1/hypre-3.1.0-nnik4j245aktkt2xzeichbdv3ynbcleb;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/gcc-13.3.1/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-63o35ugwr356fphe57yjnkberdna3ehj;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/gcc-13.3.1/fmt-12.1.0-zqxr3fxqwzr32g2k5bo2wsixnw67co57;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/gcc-13.3.1/metis-5.1.0-ugyr2txwucmnoa74sjorvtvktey4s7j4;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/none-none/gcc-runtime-13.3.1-idl2e7ehceec4tq2xcci4n27sch4dkvu;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/none-none/compiler-wrapper-1.1.0-pbd2ayub7z6lkuzpvgsnzfsycl3xbuy3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.26.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66xzj4wm5kfqxyc4vgox7gb3;/usr/tce/packages/cuda/cuda-13.1.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/doxygen-1.15.0;/usr/tce/packages/gcc/gcc-13.3.1;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-gcc-13.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1/axom-develop-fzmegf77c74yozjd72rl5iqynh23wqhx/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1/axom-develop-fzmegf77c74yozjd72rl5iqynh23wqhx/lib64;;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/gcc-13.3.1/axom-develop-5u34dolzb5yselqhoa6kom4xf5ndk2ce/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/gcc-13.3.1/axom-develop-5u34dolzb5yselqhoa6kom4xf5ndk2ce/lib64;;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1/axom-develop-fzmegf77c74yozjd72rl5iqynh23wqhx/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1/axom-develop-fzmegf77c74yozjd72rl5iqynh23wqhx/lib64;;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/gcc-13.3.1/axom-develop-5u34dolzb5yselqhoa6kom4xf5ndk2ce/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/gcc-13.3.1/axom-develop-5u34dolzb5yselqhoa6kom4xf5ndk2ce/lib64;;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ # Compilers #------------------------------------------------------------------------------ -# Compiler Spec: gcc@13.3.1/zbmuyoury7g5npf6fa7lwwxbjninyheh +# Compiler Spec: gcc@13.3.1/coban4v6d67qsc2lmofonv7wkeuxgg55 #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/gcc" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/none-none/compiler-wrapper-1.1.0-pbd2ayub7z6lkuzpvgsnzfsycl3xbuy3/libexec/spack/gcc/gcc" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/g++" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/none-none/compiler-wrapper-1.1.0-pbd2ayub7z6lkuzpvgsnzfsycl3xbuy3/libexec/spack/gcc/g++" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/gfortran" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/none-none/compiler-wrapper-1.1.0-pbd2ayub7z6lkuzpvgsnzfsycl3xbuy3/libexec/spack/gcc/gfortran" CACHE PATH "") else() @@ -69,13 +69,13 @@ set(ENABLE_MPI ON CACHE BOOL "") # Cuda #------------------------------------------------ -set(CUDAToolkit_ROOT "/usr/tce/packages/cuda/cuda-12.9.1" CACHE PATH "") +set(CUDAToolkit_ROOT "/usr/tce/packages/cuda/cuda-13.1.1" CACHE PATH "") set(CMAKE_CUDA_COMPILER "${CUDAToolkit_ROOT}/bin/nvcc" CACHE PATH "") set(CMAKE_CUDA_HOST_COMPILER "${CMAKE_CXX_COMPILER}" CACHE PATH "") -set(CUDA_TOOLKIT_ROOT_DIR "/usr/tce/packages/cuda/cuda-12.9.1" CACHE PATH "") +set(CUDA_TOOLKIT_ROOT_DIR "/usr/tce/packages/cuda/cuda-13.1.1" CACHE PATH "") set(CMAKE_CUDA_ARCHITECTURES "90" CACHE STRING "") @@ -103,29 +103,29 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/gcc-13.3.1" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/gcc-13.3.1" CACHE PATH "") -set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-vlm6nb2rwwmvnxtnxw5p3o27rzsap6vt" CACHE PATH "") +set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.7-cj7f7ue7tuh5za3vyckhvazoaa5dirlz" CACHE PATH "") -set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-tyixdhhwshu35d2vuh6gw5bk3b3k7nqz" CACHE PATH "") +set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-ilhafup6lemuluscuhe2wcw6dhqwukac" CACHE PATH "") -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-dyhiljftw2yufyry2ybddjlzrst7usbg" CACHE PATH "") +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-tjktdj5puze2pgidvstbs54ivwo3p426" CACHE PATH "") -set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-zyfoez4ztydoqfeubcamjypyhprjpuca" CACHE PATH "") +set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-rephuaeqmzpcjiazcflzeswdvsbwuvd2" CACHE PATH "") set(LUA_DIR "/usr" CACHE PATH "") -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-hjzgnbnbz4sifpanhmuqhuuydzmctbbl" CACHE PATH "") +set(RAJA_DIR "${TPL_ROOT}/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-r7pmta3pbsca6mmndrycorx34iog7kp7" CACHE PATH "") -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-yxkn4g7yhresecqk2dkjivbahvu3qhpp" CACHE PATH "") +set(UMPIRE_DIR "${TPL_ROOT}/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-xub4ymu4oek5r2fsa3rwq3dv6vtkicur" CACHE PATH "") # OPENCASCADE not built -set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-nqiuo5ltc24jpawzi6mlp3xtmngcopiu" CACHE PATH "") +set(ADIAK_DIR "${TPL_ROOT}/adiak-0.5.0-rv7mu2xwq332xz2xqgfjtond4hvjlcvi" CACHE PATH "") -set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-apockdkmc2slp4nexkn6sbspz7pibric" CACHE PATH "") +set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-7t2cu7tn3ltyjwyknkt3dnsgru7iynhx" CACHE PATH "") -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-bt6ckjhqcfr3gjiu76sdhabhppjhjd6t" CACHE PATH "") +set(CAMP_DIR "${TPL_ROOT}/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-63o35ugwr356fphe57yjnkberdna3ehj" CACHE PATH "") # scr not built @@ -153,16 +153,16 @@ set(CPPCHECK_EXECUTABLE "${DEVTOOLS_ROOT}/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66x set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/kky5oskfhacihjb5gpy7qkeh6edlrtcb/doxygen-1.15.0/bin/doxygen" CACHE PATH "") -set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-e42etfacct5pxga65kryysakw52u4c63/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.12.0-2slokzflsbdyymtlubukr5uv352aaqol/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/none-none/py-pytest-9.0.3-gzmipids43fsxiidqzdglv4cbaxj26gk/lib/python3.13/site-packages" CACHE PATH "") -set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-to6o5czwkczlh46d6gkubixw5bhzxakl/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.6-owxvcv322niqkkfckectzayaxxwre7f4/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/none-none/py-pluggy-1.6.0-6zab4pck6v7johubbhalr2waowbwpjei/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/none-none/py-pluggy-1.6.0-7gqc6q7eser33fbaul2h4xxpqo67fphj/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/none-none/py-iniconfig-2.1.0-xwmbxuhk2oo3dcuizdp3z4hqbpqykkom/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/none-none/py-iniconfig-2.1.0-7flbc7ylpqkqn4swh7u5lojtj2ipn7bx/lib/python3.13/site-packages" CACHE PATH "") -set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-l3h5jmmg773bd5oer7makte3ln6rn7xv/lib/python3.13/site-packages" CACHE PATH "") +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-susobssvjtqo5inxlvwts2s5qacifmqy/lib/python3.13/site-packages" CACHE PATH "") diff --git a/host-configs/rzvector-toss_4_x86_64_ib-llvm@19.1.3_cuda.cmake b/host-configs/rzvector-toss_4_x86_64_ib-llvm@19.1.3_cuda.cmake index 8988cc45cf..3a8aa4d4aa 100644 --- a/host-configs/rzvector-toss_4_x86_64_ib-llvm@19.1.3_cuda.cmake +++ b/host-configs/rzvector-toss_4_x86_64_ib-llvm@19.1.3_cuda.cmake @@ -4,28 +4,28 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.26.3/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3/blt-0.7.1-ux6wzebtj6vtgnayf4gqv2rr62ulwwfc;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3/c2c-1.8.0-wto5u3qsffjhbxirygr7kqqjlg55pyv6;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-lpeeewbzwhzppynnxvwzs6kz7dyblozq;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3/conduit-0.9.5-vqo2x2n6mz2vs4eg7kynwmlpeolsuim5;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3/mfem-4.9.0-6kl7ian5mzf5ypjvbjaghzk7s3ez62nz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3/py-nanobind-2.7.0-zxodixu5egwgeorhpe3ciggwsv3ndfxb;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-7hlq5rt36tbau3kruxvg7y62rs3wgn7k;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3/umpire-2025.12.0-feuig5ip7m5e2b4ejmchtyir77g5dxeb;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3/adiak-0.4.0-do3kop54yldi4hz43usrtnx2zyhvwfsv;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3/libunwind-1.8.3-ck74b2escwzf2xtpiafdvqwmat26uuoe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3/hdf5-1.8.23-vpibouztpyzmgikg4dg2egwf2u53mmpl;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3/parmetis-4.0.3-fs7gps55rp6znnpz3b4zkd35jjkfycfc;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3/py-mpi4py-4.1.1-hrvj5zyy3xo2rbzvbz4ed7kjye7ssgmk;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3/py-numpy-2.4.2-73lpwcid33wlekelrxoh24535lzexwwg;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3/hypre-2.27.0-vksttbhyimswj3oxopuulk5xfa7nkhmk;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-biicbsrzigcqj4oofalk4povvcnibwea;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3/fmt-11.0.2-ekc2idnbqipl2pqb4nugxoa7sj7aigpo;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/none-none/gcc-runtime-13.3.1-xlvjd7vcdngtnvtxcdyqwtquvenekynf;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3/metis-5.1.0-7gsm27yycsippuh2l4fwei4dtvevbqgh;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.26.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66xzj4wm5kfqxyc4vgox7gb3;/usr/tce/packages/cuda/cuda-12.9.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/doxygen-1.15.0;/usr/tce/packages/gcc/gcc-13.3.1;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-clang-19.1.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/gcc-13.3.1/blt-0.7.2-kgttmwj56kwpge3oveotwgx7amhmc54b;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/llvm-19.1.3/c2c-1.8.0-cc4zkpkb3tvtflcodt4bhjiexgbd5pf3;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/llvm-19.1.3/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-bdkhoostmyjhyvbtlogmbet7fr3ebr2a;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/llvm-19.1.3/conduit-0.9.7-bcuudf26kmnocne4wa2pq4csunshwyqz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/llvm-19.1.3/mfem-4.9.0-tkazvohdxofvabidsnxeutjpol7ryper;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/llvm-19.1.3/py-nanobind-2.12.0-jvqsaw246m2x5v3cw25qpwmgcjhbcxok;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/none-none/py-pytest-9.0.3-gzmipids43fsxiidqzdglv4cbaxj26gk;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/llvm-19.1.3/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-y37o3il4xkabmj5sqe7423ngtwupmpo7;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/llvm-19.1.3/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-w6deoinom2xrpucfsktvmpbzqssfazhd;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/llvm-19.1.3/adiak-0.5.0-bn7vvphqubchyzrll3ktmsihnzdbbb6b;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/llvm-19.1.3/libunwind-1.8.3-3j43eldxfwjlrs6gnf33vpfpox5tbqjr;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/llvm-19.1.3/hdf5-1.8.23-pqeunv22a7jyh66n7b4mrbvccatr32eu;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/llvm-19.1.3/parmetis-4.0.3-tc7yzrxqscciment55kdr5uzym5ijilj;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/llvm-19.1.3/py-mpi4py-4.1.1-rifw3jmftcx6ezx2qnb4u64gxpcptcl5;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/llvm-19.1.3/py-numpy-2.4.6-ygvifcqpoaj23qtkbllsgdgge62vtlzv;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/llvm-19.1.3/hypre-3.1.0-y36tujkd3l3kuwitqvi3h4nx7irf2adk;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/llvm-19.1.3/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-p4zhei2mmy2n4e3xkeq4npqxucppjfjk;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/llvm-19.1.3/fmt-12.1.0-ew4w4he3l62ai5pbto4li23fao2wa2u6;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/none-none/gcc-runtime-13.3.1-idl2e7ehceec4tq2xcci4n27sch4dkvu;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/llvm-19.1.3/metis-5.1.0-o23lru3echahpaspbjkpdjfudtgjg6vj;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/none-none/compiler-wrapper-1.1.0-pbd2ayub7z6lkuzpvgsnzfsycl3xbuy3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.26.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66xzj4wm5kfqxyc4vgox7gb3;/usr/tce/packages/cuda/cuda-13.1.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/doxygen-1.15.0;/usr/tce/packages/gcc/gcc-13.3.1;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-clang-19.1.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3/axom-develop-wxy5sh2abd2fekvyyulaxy3y3zrwcn7w/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3/axom-develop-wxy5sh2abd2fekvyyulaxy3y3zrwcn7w/lib64;;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/llvm-19.1.3-gy2lu5xbi4csr2k47emlajzfs5mlsd4g/lib/x86_64-unknown-linux-gnu;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13;/usr/tce/packages/clang/clang-19.1.3/lib;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/llvm-19.1.3/axom-develop-pbfyvrjwt2zyqilucf36p3t5ym6bstev/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/llvm-19.1.3/axom-develop-pbfyvrjwt2zyqilucf36p3t5ym6bstev/lib64;;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/llvm-19.1.3-gy2lu5xbi4csr2k47emlajzfs5mlsd4g/lib/x86_64-unknown-linux-gnu;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13;/usr/tce/packages/clang/clang-19.1.3/lib;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3/axom-develop-wxy5sh2abd2fekvyyulaxy3y3zrwcn7w/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3/axom-develop-wxy5sh2abd2fekvyyulaxy3y3zrwcn7w/lib64;;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/llvm-19.1.3-gy2lu5xbi4csr2k47emlajzfs5mlsd4g/lib/x86_64-unknown-linux-gnu;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13;/usr/tce/packages/clang/clang-19.1.3/lib;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/llvm-19.1.3/axom-develop-pbfyvrjwt2zyqilucf36p3t5ym6bstev/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/llvm-19.1.3/axom-develop-pbfyvrjwt2zyqilucf36p3t5ym6bstev/lib64;;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/llvm-19.1.3-gy2lu5xbi4csr2k47emlajzfs5mlsd4g/lib/x86_64-unknown-linux-gnu;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13;/usr/tce/packages/clang/clang-19.1.3/lib;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ # Compilers #------------------------------------------------------------------------------ -# Compiler Spec: llvm@19.1.3/m2tcgbxfcdjwdmyrkqwamlljh4qeiie2 +# Compiler Spec: llvm@19.1.3/pa7jv5ejrkrucsgwtv4kde2cazhq3v6i #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/clang/clang" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/none-none/compiler-wrapper-1.1.0-pbd2ayub7z6lkuzpvgsnzfsycl3xbuy3/libexec/spack/clang/clang" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/clang/clang++" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/none-none/compiler-wrapper-1.1.0-pbd2ayub7z6lkuzpvgsnzfsycl3xbuy3/libexec/spack/clang/clang++" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/gfortran" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/none-none/compiler-wrapper-1.1.0-pbd2ayub7z6lkuzpvgsnzfsycl3xbuy3/libexec/spack/gcc/gfortran" CACHE PATH "") else() @@ -71,13 +71,13 @@ set(ENABLE_MPI ON CACHE BOOL "") # Cuda #------------------------------------------------ -set(CUDAToolkit_ROOT "/usr/tce/packages/cuda/cuda-12.9.1" CACHE PATH "") +set(CUDAToolkit_ROOT "/usr/tce/packages/cuda/cuda-13.1.1" CACHE PATH "") set(CMAKE_CUDA_COMPILER "${CUDAToolkit_ROOT}/bin/nvcc" CACHE PATH "") set(CMAKE_CUDA_HOST_COMPILER "${CMAKE_CXX_COMPILER}" CACHE PATH "") -set(CUDA_TOOLKIT_ROOT_DIR "/usr/tce/packages/cuda/cuda-12.9.1" CACHE PATH "") +set(CUDA_TOOLKIT_ROOT_DIR "/usr/tce/packages/cuda/cuda-13.1.1" CACHE PATH "") set(CMAKE_CUDA_ARCHITECTURES "90" CACHE STRING "") @@ -105,29 +105,29 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/llvm-19.1.3" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/llvm-19.1.3" CACHE PATH "") -set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-vqo2x2n6mz2vs4eg7kynwmlpeolsuim5" CACHE PATH "") +set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.7-bcuudf26kmnocne4wa2pq4csunshwyqz" CACHE PATH "") -set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-wto5u3qsffjhbxirygr7kqqjlg55pyv6" CACHE PATH "") +set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-cc4zkpkb3tvtflcodt4bhjiexgbd5pf3" CACHE PATH "") -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-6kl7ian5mzf5ypjvbjaghzk7s3ez62nz" CACHE PATH "") +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-tkazvohdxofvabidsnxeutjpol7ryper" CACHE PATH "") -set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-vpibouztpyzmgikg4dg2egwf2u53mmpl" CACHE PATH "") +set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-pqeunv22a7jyh66n7b4mrbvccatr32eu" CACHE PATH "") set(LUA_DIR "/usr" CACHE PATH "") -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-7hlq5rt36tbau3kruxvg7y62rs3wgn7k" CACHE PATH "") +set(RAJA_DIR "${TPL_ROOT}/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-y37o3il4xkabmj5sqe7423ngtwupmpo7" CACHE PATH "") -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-feuig5ip7m5e2b4ejmchtyir77g5dxeb" CACHE PATH "") +set(UMPIRE_DIR "${TPL_ROOT}/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-w6deoinom2xrpucfsktvmpbzqssfazhd" CACHE PATH "") # OPENCASCADE not built -set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-do3kop54yldi4hz43usrtnx2zyhvwfsv" CACHE PATH "") +set(ADIAK_DIR "${TPL_ROOT}/adiak-0.5.0-bn7vvphqubchyzrll3ktmsihnzdbbb6b" CACHE PATH "") -set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-lpeeewbzwhzppynnxvwzs6kz7dyblozq" CACHE PATH "") +set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-bdkhoostmyjhyvbtlogmbet7fr3ebr2a" CACHE PATH "") -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-biicbsrzigcqj4oofalk4povvcnibwea" CACHE PATH "") +set(CAMP_DIR "${TPL_ROOT}/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-p4zhei2mmy2n4e3xkeq4npqxucppjfjk" CACHE PATH "") # scr not built @@ -155,16 +155,16 @@ set(CPPCHECK_EXECUTABLE "${DEVTOOLS_ROOT}/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66x set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/kky5oskfhacihjb5gpy7qkeh6edlrtcb/doxygen-1.15.0/bin/doxygen" CACHE PATH "") -set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-zxodixu5egwgeorhpe3ciggwsv3ndfxb/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.12.0-jvqsaw246m2x5v3cw25qpwmgcjhbcxok/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/none-none/py-pytest-9.0.3-gzmipids43fsxiidqzdglv4cbaxj26gk/lib/python3.13/site-packages" CACHE PATH "") -set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-73lpwcid33wlekelrxoh24535lzexwwg/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.6-ygvifcqpoaj23qtkbllsgdgge62vtlzv/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/none-none/py-pluggy-1.6.0-6zab4pck6v7johubbhalr2waowbwpjei/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/none-none/py-pluggy-1.6.0-7gqc6q7eser33fbaul2h4xxpqo67fphj/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_26_09/none-none/py-iniconfig-2.1.0-xwmbxuhk2oo3dcuizdp3z4hqbpqykkom/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_57_56/none-none/py-iniconfig-2.1.0-7flbc7ylpqkqn4swh7u5lojtj2ipn7bx/lib/python3.13/site-packages" CACHE PATH "") -set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-hrvj5zyy3xo2rbzvbz4ed7kjye7ssgmk/lib/python3.13/site-packages" CACHE PATH "") +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-rifw3jmftcx6ezx2qnb4u64gxpcptcl5/lib/python3.13/site-packages" CACHE PATH "") diff --git a/host-configs/rzvernal-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake b/host-configs/rzvernal-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake deleted file mode 100644 index e4bfd0146e..0000000000 --- a/host-configs/rzvernal-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake +++ /dev/null @@ -1,169 +0,0 @@ -#------------------------------------------------------------------------------ -# !!!! This is a generated file, edit at own risk !!!! -#------------------------------------------------------------------------------ -# CMake executable path: /usr/tce/packages/cmake/cmake-3.29.2/bin/cmake -#------------------------------------------------------------------------------ - -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/blt-0.7.1-tj6h3jr6fjdlxux2yd4wvdy7xwxxuh23;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/c2c-1.8.0-xav3pjepsvpzrcqd5dcom4is4rioejzr;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-37ef6g3godgxug45i3u3x26awgovbhxa;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/conduit-0.9.5-oyjekm66om6cx7yndhpv3yumtxgaaroi;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/lua-5.4.8-ty6vxrgtb5lsthp7rrcy7vx3x4yknrms;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/mfem-4.9.0-iiiowpg2cxn346mv67ycscrcrt5eacdk;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/py-nanobind-2.7.0-h7v5ggotyqa5ul7kpyc2yfojjkah4ium;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/adiak-0.4.0-w7lj246x4whjefszgyhapkebe3cjd7bi;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/libunwind-1.8.3-htd47rshpgaa6w73jemf7tycnnlkn5ws;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/hdf5-1.8.23-vy4rnk6awhcfgte2wuwisgp3t3y4gzwy;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/parmetis-4.0.3-jqlganykrtm62xb3ve2eetb3gltn5nki;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/py-mpi4py-4.1.1-gla2ur2g4lb44jyi2tky6p2ynzgseonn;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/py-numpy-2.4.2-vdrslpslcsdjba3zyryp27z4tmpn3ayu;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/hypre-2.27.0-gjrhbi2v66y2yfxgq7vegqpcawo2b35r;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-iwmhvbuhywrcyi4ojshrizfpccb53g3u;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/umpire-2025.12.0-qkp2thztbvdojybrli7tpxea5duxagkn;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/zlib-1.3.1-6yddik2qxvkwtfcckdhbdgos7awok5za;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/metis-5.1.0-bryzqxg7sgprevvt3ux5np2uxam4wbvp;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-xdstcq3vkkl6gysd7agincseoncdi5nz;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/fmt-11.0.2-vjndv6co4e6qonqazcmklw3pamvmos74;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cce-tce/cce-20.0.0;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.32-rocmcc-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3" CACHE STRING "") - -set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") - -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/axom-develop-scv2k3ekpxlqvhgfmb6tt3qfk6vez3q5/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/axom-develop-scv2k3ekpxlqvhgfmb6tt3qfk6vez3q5/lib64;;/opt/cray/pe/cce/20.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") - -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/axom-develop-scv2k3ekpxlqvhgfmb6tt3qfk6vez3q5/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0/axom-develop-scv2k3ekpxlqvhgfmb6tt3qfk6vez3q5/lib64;;/opt/cray/pe/cce/20.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") - -set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") - -#------------------------------------------------------------------------------ -# Compilers -#------------------------------------------------------------------------------ -# Compiler Spec: cce@20.0.0/v7nrkimihxta4odatu45wrllosgeklrd -#------------------------------------------------------------------------------ -if(DEFINED ENV{SPACK_CC}) - - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/cce/craycc" CACHE PATH "") - - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/cce/case-insensitive/crayCC" CACHE PATH "") - - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/cce/crayftn" CACHE PATH "") - -else() - - set(CMAKE_C_COMPILER "/usr/tce/packages/cce-tce/cce-20.0.0/bin/craycc" CACHE PATH "") - - set(CMAKE_CXX_COMPILER "/usr/tce/packages/cce-tce/cce-20.0.0/bin/crayCC" CACHE PATH "") - - set(CMAKE_Fortran_COMPILER "/usr/tce/packages/cce-tce/cce-20.0.0/bin/crayftn" CACHE PATH "") - -endif() - -set(CMAKE_Fortran_FLAGS "-ef" CACHE STRING "") - -set(ENABLE_FORTRAN ON CACHE BOOL "") - -set(CMAKE_CXX_FLAGS_DEBUG "-O1 -g" CACHE STRING "") - -#------------------------------------------------------------------------------ -# MPI -#------------------------------------------------------------------------------ - -set(MPI_C_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.32-rocmcc-6.4.3/bin/mpicc" CACHE PATH "") - -set(MPI_CXX_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.32-rocmcc-6.4.3/bin/mpicxx" CACHE PATH "") - -set(MPI_Fortran_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.32-rocmcc-6.4.3/bin/mpif90" CACHE PATH "") - -set(MPIEXEC_EXECUTABLE "/usr/global/tools/flux_wrappers/bin/srun" CACHE PATH "") - -set(MPIEXEC_NUMPROC_FLAG "-n" CACHE STRING "") - -set(ENABLE_MPI ON CACHE BOOL "") - -#------------------------------------------------------------------------------ -# Hardware -#------------------------------------------------------------------------------ - -#------------------------------------------------ -# ROCm -#------------------------------------------------ - -set(ROCM_PATH "/opt/rocm-6.4.3" CACHE PATH "") - -set(CMAKE_HIP_ARCHITECTURES "gfx90a;gfx942" CACHE STRING "") - -set(CMAKE_HIP_COMPILER "/opt/rocm-6.4.3/bin/amdclang++" CACHE FILEPATH "") - -#------------------------------------------------------------------------------ - -# Axom ROCm specifics - -#------------------------------------------------------------------------------ - - -set(ENABLE_HIP ON CACHE BOOL "") - -set(ROCM_ROOT_DIR "/opt/rocm-6.4.3" CACHE PATH "") - -set(BLT_CMAKE_IMPLICIT_LINK_LIBRARIES_EXCLUDE "unwind;ompstub" CACHE STRING "") - -set(CMAKE_EXE_LINKER_FLAGS "-lxpmem -L/opt/cray/pe/mpich/8.1.32/gtl/lib -Wl,-rpath,/opt/cray/pe/mpich/8.1.32/gtl/lib -lmpi_gtl_hsa -L/opt/rocm-6.4.3/lib/llvm/lib -Wl,-rpath,/opt/rocm-6.4.3/lib/llvm/lib -L/opt/rocm-6.4.3/lib -Wl,-rpath,/opt/rocm-6.4.3/lib -lpgmath -L/opt/cray/pe/cce/20.0.0/cce/x86_64/lib -Wl,-rpath,/opt/cray/pe/cce/20.0.0/cce/x86_64/lib -lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " CACHE STRING "") - -#------------------------------------------------ -# Hardware Specifics -#------------------------------------------------ - -set(ENABLE_OPENMP ON CACHE BOOL "") - -set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") - -set(BLT_OPENMP_COMPILE_FLAGS "$<$>:-fopenmp=libomp>;$<$:-fopenmp>" CACHE STRING "Different OpenMP compile & link flags between HIP and CXX compilers (amdclang++)") - -set(BLT_OPENMP_LINK_FLAGS "$<$>:-fopenmp=libomp>;$<$:-fopenmp>" CACHE STRING "Different OpenMP compile & link flags between HIP and CXX compilers (amdclang++)") - -#------------------------------------------------------------------------------ -# TPLs -#------------------------------------------------------------------------------ - -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/cce-20.0.0" CACHE PATH "") - -set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-oyjekm66om6cx7yndhpv3yumtxgaaroi" CACHE PATH "") - -set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-xav3pjepsvpzrcqd5dcom4is4rioejzr" CACHE PATH "") - -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-iiiowpg2cxn346mv67ycscrcrt5eacdk" CACHE PATH "") - -set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-vy4rnk6awhcfgte2wuwisgp3t3y4gzwy" CACHE PATH "") - -set(LUA_DIR "${TPL_ROOT}/lua-5.4.8-ty6vxrgtb5lsthp7rrcy7vx3x4yknrms" CACHE PATH "") - -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-iwmhvbuhywrcyi4ojshrizfpccb53g3u" CACHE PATH "") - -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-qkp2thztbvdojybrli7tpxea5duxagkn" CACHE PATH "") - -# OPENCASCADE not built - -set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-w7lj246x4whjefszgyhapkebe3cjd7bi" CACHE PATH "") - -set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-37ef6g3godgxug45i3u3x26awgovbhxa" CACHE PATH "") - -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-xdstcq3vkkl6gysd7agincseoncdi5nz" CACHE PATH "") - -# scr not built - -#------------------------------------------------------------------------------ -# Devtools & Python -#------------------------------------------------------------------------------ - -set(DEVTOOLS_ROOT "/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25" CACHE PATH "") - -set(CLANGFORMAT_EXECUTABLE "/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm/bin/clang-format" CACHE PATH "") - -set(Python_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/python3" CACHE PATH "") - -set(JSONSCHEMA_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/jsonschema" CACHE PATH "") - -set(ENABLE_DOCS ON CACHE BOOL "") - -set(SPHINX_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/sphinx-build" CACHE PATH "") - -set(YAPF_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/yapf" CACHE PATH "") - -set(SHROUD_EXECUTABLE "/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0/bin/shroud" CACHE PATH "") - -set(CPPCHECK_EXECUTABLE "${DEVTOOLS_ROOT}/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27/bin/cppcheck" CACHE PATH "") - -set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/doxygen-1.15.0/bin/doxygen" CACHE PATH "") - -set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-h7v5ggotyqa5ul7kpyc2yfojjkah4ium/lib/python3.13/site-packages" CACHE PATH "") - -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj/lib/python3.13/site-packages" CACHE PATH "") - -set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-vdrslpslcsdjba3zyryp27z4tmpn3ayu/lib/python3.13/site-packages" CACHE PATH "") - -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/py-pluggy-1.6.0-27aoz44dedjxa6j6r6cavpqyqmpknidb/lib/python3.13/site-packages" CACHE PATH "") - -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/py-iniconfig-2.1.0-7rty7n4wq6kxxrzflm7wbxqzhhc6ph7q/lib/python3.13/site-packages" CACHE PATH "") - -set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-gla2ur2g4lb44jyi2tky6p2ynzgseonn/lib/python3.13/site-packages" CACHE PATH "") - - diff --git a/host-configs/rzvernal-toss_4_x86_64_ib_cray-cce@21.0.0_hip.cmake b/host-configs/rzvernal-toss_4_x86_64_ib_cray-cce@21.0.0_hip.cmake new file mode 100644 index 0000000000..e5a7351963 --- /dev/null +++ b/host-configs/rzvernal-toss_4_x86_64_ib_cray-cce@21.0.0_hip.cmake @@ -0,0 +1,171 @@ +#------------------------------------------------------------------------------ +# !!!! This is a generated file, edit at own risk !!!! +#------------------------------------------------------------------------------ +# CMake executable path: /usr/tce/packages/cmake/cmake-3.29.2/bin/cmake +#------------------------------------------------------------------------------ + +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/cce-21.0.0/blt-0.7.2-qcnw7gz36yrtqxyhknwmllqxiq6dfb6s;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/cce-21.0.0/c2c-1.8.0-hjdyjpgqtmpwjfn4duo46obwwpgk4ksg;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/cce-21.0.0/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-y5wnj5sz23xeqfif4w6y7q34ctrx7gjj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/cce-21.0.0/conduit-0.9.7-zmsisfkaybg6pmsjielb4hmdx73vdzpf;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/cce-21.0.0/lua-5.4.8-5uxxpoxmmlbb7ycut4ipoammpgvb4rec;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/cce-21.0.0/mfem-4.9.0-stqwo5oobxtngqnfhi5jw2ycnkbrmpqe;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/cce-21.0.0/py-nanobind-2.12.0-on6m44wfnk4okaxandfqbhe7s5mp54me;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/none-none/py-pytest-9.0.3-2suvy7b3lmshqhnbylnif2jz2735dbbe;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/cce-21.0.0/adiak-0.5.0-dmhmhtynt2mfhoq7u6f7av3kqfdonm2x;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/cce-21.0.0/libunwind-1.8.3-vxrm4ca45t6tnuaslyozmbtbxx537qtw;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/cce-21.0.0/hdf5-1.8.23-37k7d6kxmofx22lygi5u2lc2qdpb2tki;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/cce-21.0.0/parmetis-4.0.3-cqtdekoqt5lzzx75l6fwbty4adcbuxh4;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/cce-21.0.0/py-mpi4py-4.1.1-cw3kxr5n6s7yqijn2rjjxwml4och5p47;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/cce-21.0.0/py-numpy-2.4.6-z2qb4fs2yqhjjyogkhvmrnrldxrwue5x;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/cce-21.0.0/hypre-3.1.0-omodg4j7lrrqcv7upadxp2noucgc6al7;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/cce-21.0.0/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-gxewkhrwy4674kazjiqjuz6kjavx6dll;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/cce-21.0.0/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-ljw6iam75tymsghtjygomslfzlwgjvpn;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/cce-21.0.0/zlib-1.3.2-h26urlixzfwlyzkpktmknpvojoxzkolk;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/cce-21.0.0/metis-5.1.0-xukleyfstw2jisuarjubgfgymgfnvlbu;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/cce-21.0.0/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-sjgym4hdzfj2p7rszzr27t7maxp4lpk2;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/cce-21.0.0/fmt-12.1.0-5xa4prr2jcvhwnwgu2stc3j62tgckgps;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/none-none/compiler-wrapper-1.1.0-qqipzhe4rz5fmoiuvlqgarjeevl2kzuh;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cce-tce/cce-21.0.0;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-9.1.0-cce-21.0.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-7.2.1;/opt/rocm-7.2.1;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-7.2.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-7.2.1;/opt/rocm-7.2.1;/opt/rocm-7.2.1;/opt/rocm-7.2.1;/opt/rocm-7.2.1" CACHE STRING "") + +set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") + +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/cce-21.0.0/axom-develop-ilegqoq3lzjh6f4cczurkzoqyw6gigds/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/cce-21.0.0/axom-develop-ilegqoq3lzjh6f4cczurkzoqyw6gigds/lib64;;/opt/cray/pe/cce/21.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") + +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/cce-21.0.0/axom-develop-ilegqoq3lzjh6f4cczurkzoqyw6gigds/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/cce-21.0.0/axom-develop-ilegqoq3lzjh6f4cczurkzoqyw6gigds/lib64;;/opt/cray/pe/cce/21.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") + +set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") + +#------------------------------------------------------------------------------ +# Compilers +#------------------------------------------------------------------------------ +# Compiler Spec: cce@21.0.0/nmaros4ushwf5auvt3asaaazfjerle4l +#------------------------------------------------------------------------------ +if(DEFINED ENV{SPACK_CC}) + + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/none-none/compiler-wrapper-1.1.0-qqipzhe4rz5fmoiuvlqgarjeevl2kzuh/libexec/spack/cce/craycc" CACHE PATH "") + + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/none-none/compiler-wrapper-1.1.0-qqipzhe4rz5fmoiuvlqgarjeevl2kzuh/libexec/spack/cce/case-insensitive/crayCC" CACHE PATH "") + + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/none-none/compiler-wrapper-1.1.0-qqipzhe4rz5fmoiuvlqgarjeevl2kzuh/libexec/spack/cce/crayftn" CACHE PATH "") + +else() + + set(CMAKE_C_COMPILER "/usr/tce/packages/cce-tce/cce-21.0.0/bin/craycc" CACHE PATH "") + + set(CMAKE_CXX_COMPILER "/usr/tce/packages/cce-tce/cce-21.0.0/bin/crayCC" CACHE PATH "") + + set(CMAKE_Fortran_COMPILER "/usr/tce/packages/cce-tce/cce-21.0.0/bin/crayftn" CACHE PATH "") + +endif() + +set(CMAKE_C_FLAGS "-fPIC" CACHE STRING "") + +set(CMAKE_CXX_FLAGS "-fPIC" CACHE STRING "") + +set(CMAKE_Fortran_FLAGS "-fPIC -ef" CACHE STRING "") + +set(ENABLE_FORTRAN ON CACHE BOOL "") + +set(CMAKE_CXX_FLAGS_DEBUG "-O1 -g" CACHE STRING "") + +#------------------------------------------------------------------------------ +# MPI +#------------------------------------------------------------------------------ + +set(MPI_C_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-9.1.0-cce-21.0.0/bin/mpicc" CACHE PATH "") + +set(MPI_CXX_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-9.1.0-cce-21.0.0/bin/mpicxx" CACHE PATH "") + +set(MPI_Fortran_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-9.1.0-cce-21.0.0/bin/mpif90" CACHE PATH "") + +set(MPIEXEC_EXECUTABLE "/usr/global/tools/flux_wrappers/bin/srun" CACHE PATH "") + +set(MPIEXEC_NUMPROC_FLAG "-n" CACHE STRING "") + +set(ENABLE_MPI ON CACHE BOOL "") + +#------------------------------------------------------------------------------ +# Hardware +#------------------------------------------------------------------------------ + +#------------------------------------------------ +# ROCm +#------------------------------------------------ + +set(ROCM_PATH "/opt/rocm-7.2.1" CACHE PATH "") + +set(CMAKE_HIP_ARCHITECTURES "gfx90a;gfx942" CACHE STRING "") + +set(CMAKE_HIP_COMPILER "/opt/rocm-7.2.1/bin/amdclang++" CACHE FILEPATH "") + +#------------------------------------------------------------------------------ + +# Axom ROCm specifics + +#------------------------------------------------------------------------------ + + +set(ENABLE_HIP ON CACHE BOOL "") + +set(ROCM_ROOT_DIR "/opt/rocm-7.2.1" CACHE PATH "") + +set(BLT_CMAKE_IMPLICIT_LINK_LIBRARIES_EXCLUDE "unwind;ompstub" CACHE STRING "") + +set(BLT_CMAKE_IMPLICIT_LINK_DIRECTORIES_EXCLUDE "/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12;/opt/rh/gcc-toolset-12/root/usr/lib64" CACHE STRING "") + +set(CMAKE_EXE_LINKER_FLAGS "-lxpmem -L/opt/cray/pe/mpich/9.1.0/gtl/lib -Wl,-rpath,/opt/cray/pe/mpich/9.1.0/gtl/lib -lmpi_gtl_hsa -L/opt/rocm-7.2.1/lib/llvm/lib -Wl,-rpath,/opt/rocm-7.2.1/lib/llvm/lib -L/opt/rocm-7.2.1/lib -Wl,-rpath,/opt/rocm-7.2.1/lib -lpgmath -L/opt/cray/pe/cce/21.0.0/cce/x86_64/lib -Wl,-rpath,/opt/cray/pe/cce/21.0.0/cce/x86_64/lib-lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " CACHE STRING "") + +#------------------------------------------------ +# Hardware Specifics +#------------------------------------------------ + +set(ENABLE_OPENMP ON CACHE BOOL "") + +set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") + +#------------------------------------------------------------------------------ +# TPLs +#------------------------------------------------------------------------------ + +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/cce-21.0.0" CACHE PATH "") + +set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.7-zmsisfkaybg6pmsjielb4hmdx73vdzpf" CACHE PATH "") + +set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-hjdyjpgqtmpwjfn4duo46obwwpgk4ksg" CACHE PATH "") + +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-stqwo5oobxtngqnfhi5jw2ycnkbrmpqe" CACHE PATH "") + +set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-37k7d6kxmofx22lygi5u2lc2qdpb2tki" CACHE PATH "") + +set(LUA_DIR "${TPL_ROOT}/lua-5.4.8-5uxxpoxmmlbb7ycut4ipoammpgvb4rec" CACHE PATH "") + +set(RAJA_DIR "${TPL_ROOT}/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-gxewkhrwy4674kazjiqjuz6kjavx6dll" CACHE PATH "") + +set(UMPIRE_DIR "${TPL_ROOT}/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-ljw6iam75tymsghtjygomslfzlwgjvpn" CACHE PATH "") + +# OPENCASCADE not built + +set(ADIAK_DIR "${TPL_ROOT}/adiak-0.5.0-dmhmhtynt2mfhoq7u6f7av3kqfdonm2x" CACHE PATH "") + +set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-y5wnj5sz23xeqfif4w6y7q34ctrx7gjj" CACHE PATH "") + +set(CAMP_DIR "${TPL_ROOT}/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-sjgym4hdzfj2p7rszzr27t7maxp4lpk2" CACHE PATH "") + +# scr not built + +#------------------------------------------------------------------------------ +# Devtools & Python +#------------------------------------------------------------------------------ + +set(DEVTOOLS_ROOT "/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25" CACHE PATH "") + +set(CLANGFORMAT_EXECUTABLE "/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm/bin/clang-format" CACHE PATH "") + +set(Python_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/python3" CACHE PATH "") + +set(JSONSCHEMA_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/jsonschema" CACHE PATH "") + +set(ENABLE_DOCS ON CACHE BOOL "") + +set(SPHINX_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/sphinx-build" CACHE PATH "") + +set(YAPF_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/yapf" CACHE PATH "") + +set(SHROUD_EXECUTABLE "/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0/bin/shroud" CACHE PATH "") + +set(CPPCHECK_EXECUTABLE "${DEVTOOLS_ROOT}/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27/bin/cppcheck" CACHE PATH "") + +set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/doxygen-1.15.0/bin/doxygen" CACHE PATH "") + +set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.12.0-on6m44wfnk4okaxandfqbhe7s5mp54me/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/none-none/py-pytest-9.0.3-2suvy7b3lmshqhnbylnif2jz2735dbbe/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.6-z2qb4fs2yqhjjyogkhvmrnrldxrwue5x/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/none-none/py-pluggy-1.6.0-eemjlppu7ynjgedbqabclpfgxt4hxdoh/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/none-none/py-iniconfig-2.1.0-kss2a5udi5pgct5uvmgjh3ojkir5hpoo/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-cw3kxr5n6s7yqijn2rjjxwml4och5p47/lib/python3.13/site-packages" CACHE PATH "") + + diff --git a/host-configs/rzvernal-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake b/host-configs/rzvernal-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake deleted file mode 100644 index 7319ae5656..0000000000 --- a/host-configs/rzvernal-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake +++ /dev/null @@ -1,163 +0,0 @@ -#------------------------------------------------------------------------------ -# !!!! This is a generated file, edit at own risk !!!! -#------------------------------------------------------------------------------ -# CMake executable path: /usr/tce/packages/cmake/cmake-3.29.2/bin/cmake -#------------------------------------------------------------------------------ - -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1/adiak-0.4.0-vlugt2gwkx5wzlwudznxg3p2plq76pjq;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1/blt-0.7.1-sv74qxlkpkll7mm6dhfjagyfog254w7c;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1/c2c-1.8.0-tfrrofjzn3rdvyktevespobwrpjai5j4;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1/conduit-0.9.5-vnlbr73jbwhqyvh2q76csdtve3ekpncc;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1/lua-5.4.8-e3i2pbc52bfhrnxbaniektzp3x5jvyxr;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1/mfem-4.9.0-jxeezxksp3c7i53h6dhqf2zz6gnaipub;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1/py-nanobind-2.7.0-4sgpkalsntbmuim5oltb7ue4gggt6rv5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1/hdf5-1.8.23-liive5kzeooveetbjhil4ic7hi3ccsg4;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1/parmetis-4.0.3-fiyw3adearn3nmxjifcqtuzwy24fshp5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1/py-mpi4py-4.1.1-ukxrsa2wugnmhh3glq25muf5hqexaf62;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1/py-numpy-2.4.2-ij66enxl4hmrmwua5bsec2yslylneqbw;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1/hypre-2.27.0-cnukrpfl32kl5q6ud4wozdqbgtqoo7jx;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-5c3lqbqcjmekrwbfeg6ogptz7ssj3n4y;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1/umpire-2025.12.0-i2dwv2l2jra74eazijtvprcx7acsupx4;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1/zlib-1.3.1-dsduxyfnks4bhrkpbskff35l5y36ofp7;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1/metis-5.1.0-6mtxrj6jbrvyd3xpq4pvluywbndjx42w;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-7nvneljwisxwircaqydhfcl3hl65bmr2;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1/fmt-11.0.2-kw3vpg2ajhrgi7kozhcioz3nrgc6fh3o;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.3.1;/opt/rocm-6.3.1;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.3.1;/opt/rocm-6.3.1;/opt/rocm-6.3.1" CACHE STRING "") - -set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") - -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1/axom-develop-7trkhc3ctsx3bgt6uv2brdgeeyalxomu/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1/axom-develop-7trkhc3ctsx3bgt6uv2brdgeeyalxomu/lib64;;/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12" CACHE STRING "") - -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1/axom-develop-7trkhc3ctsx3bgt6uv2brdgeeyalxomu/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1/axom-develop-7trkhc3ctsx3bgt6uv2brdgeeyalxomu/lib64;;/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12" CACHE STRING "") - -set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") - -#------------------------------------------------------------------------------ -# Compilers -#------------------------------------------------------------------------------ -# Compiler Spec: llvm-amdgpu@6.3.1/ns2pv5bzkpjolxzmhgkckj6glqymgd7e -#------------------------------------------------------------------------------ -if(DEFINED ENV{SPACK_CC}) - - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdclang" CACHE PATH "") - - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdclang++" CACHE PATH "") - - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdflang" CACHE PATH "") - -else() - - set(CMAKE_C_COMPILER "/opt/rocm-6.3.1/llvm/bin/amdclang" CACHE PATH "") - - set(CMAKE_CXX_COMPILER "/opt/rocm-6.3.1/llvm/bin/amdclang++" CACHE PATH "") - - set(CMAKE_Fortran_COMPILER "/opt/rocm-6.3.1/llvm/bin/amdflang" CACHE PATH "") - -endif() - -set(CMAKE_Fortran_FLAGS "-Mfreeform" CACHE STRING "") - -set(ENABLE_FORTRAN ON CACHE BOOL "") - -#------------------------------------------------------------------------------ -# MPI -#------------------------------------------------------------------------------ - -set(MPI_C_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.3.1/bin/mpicc" CACHE PATH "") - -set(MPI_CXX_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.3.1/bin/mpicxx" CACHE PATH "") - -set(MPI_Fortran_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.3.1/bin/mpif90" CACHE PATH "") - -set(MPIEXEC_EXECUTABLE "/usr/global/tools/flux_wrappers/bin/srun" CACHE PATH "") - -set(MPIEXEC_NUMPROC_FLAG "-n" CACHE STRING "") - -set(ENABLE_MPI ON CACHE BOOL "") - -#------------------------------------------------------------------------------ -# Hardware -#------------------------------------------------------------------------------ - -#------------------------------------------------ -# ROCm -#------------------------------------------------ - -set(ROCM_PATH "/opt/rocm-6.3.1" CACHE PATH "") - -set(CMAKE_HIP_ARCHITECTURES "gfx90a;gfx942" CACHE STRING "") - -set(CMAKE_HIP_COMPILER "/opt/rocm-6.3.1/bin/amdclang++" CACHE FILEPATH "") - -#------------------------------------------------------------------------------ - -# Axom ROCm specifics - -#------------------------------------------------------------------------------ - - -set(ENABLE_HIP ON CACHE BOOL "") - -set(ROCM_ROOT_DIR "/opt/rocm-6.3.1" CACHE PATH "") - -set(BLT_CMAKE_IMPLICIT_LINK_LIBRARIES_EXCLUDE "ompstub" CACHE STRING "") - -set(CMAKE_EXE_LINKER_FLAGS "-lxpmem -L/opt/cray/pe/mpich/8.1.29/gtl/lib -Wl,-rpath,/opt/cray/pe/mpich/8.1.29/gtl/lib -lmpi_gtl_hsa -L/opt/rocm-6.3.1/lib/llvm/lib -Wl,-rpath,/opt/rocm-6.3.1/lib/llvm/lib -L/opt/rocm-6.3.1/lib -Wl,-rpath,/opt/rocm-6.3.1/lib -lpgmath -Wl,--disable-new-dtags -lflang -lflangrti -lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " CACHE STRING "") - -#------------------------------------------------ -# Hardware Specifics -#------------------------------------------------ - -set(ENABLE_OPENMP ON CACHE BOOL "") - -set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") - -#------------------------------------------------------------------------------ -# TPLs -#------------------------------------------------------------------------------ - -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.3.1" CACHE PATH "") - -set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-vnlbr73jbwhqyvh2q76csdtve3ekpncc" CACHE PATH "") - -set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-tfrrofjzn3rdvyktevespobwrpjai5j4" CACHE PATH "") - -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-jxeezxksp3c7i53h6dhqf2zz6gnaipub" CACHE PATH "") - -set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-liive5kzeooveetbjhil4ic7hi3ccsg4" CACHE PATH "") - -set(LUA_DIR "${TPL_ROOT}/lua-5.4.8-e3i2pbc52bfhrnxbaniektzp3x5jvyxr" CACHE PATH "") - -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-5c3lqbqcjmekrwbfeg6ogptz7ssj3n4y" CACHE PATH "") - -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-i2dwv2l2jra74eazijtvprcx7acsupx4" CACHE PATH "") - -# OPENCASCADE not built - -set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-vlugt2gwkx5wzlwudznxg3p2plq76pjq" CACHE PATH "") - -# CALIPER not built - -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-7nvneljwisxwircaqydhfcl3hl65bmr2" CACHE PATH "") - -# scr not built - -#------------------------------------------------------------------------------ -# Devtools & Python -#------------------------------------------------------------------------------ - -set(DEVTOOLS_ROOT "/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25" CACHE PATH "") - -set(CLANGFORMAT_EXECUTABLE "/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm/bin/clang-format" CACHE PATH "") - -set(Python_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/python3" CACHE PATH "") - -set(JSONSCHEMA_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/jsonschema" CACHE PATH "") - -set(ENABLE_DOCS ON CACHE BOOL "") - -set(SPHINX_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/sphinx-build" CACHE PATH "") - -set(YAPF_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/yapf" CACHE PATH "") - -set(SHROUD_EXECUTABLE "/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0/bin/shroud" CACHE PATH "") - -set(CPPCHECK_EXECUTABLE "${DEVTOOLS_ROOT}/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27/bin/cppcheck" CACHE PATH "") - -set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/doxygen-1.15.0/bin/doxygen" CACHE PATH "") - -set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-4sgpkalsntbmuim5oltb7ue4gggt6rv5/lib/python3.13/site-packages" CACHE PATH "") - -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj/lib/python3.13/site-packages" CACHE PATH "") - -set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-ij66enxl4hmrmwua5bsec2yslylneqbw/lib/python3.13/site-packages" CACHE PATH "") - -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/py-pluggy-1.6.0-27aoz44dedjxa6j6r6cavpqyqmpknidb/lib/python3.13/site-packages" CACHE PATH "") - -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/py-iniconfig-2.1.0-7rty7n4wq6kxxrzflm7wbxqzhhc6ph7q/lib/python3.13/site-packages" CACHE PATH "") - -set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-ukxrsa2wugnmhh3glq25muf5hqexaf62/lib/python3.13/site-packages" CACHE PATH "") - - diff --git a/host-configs/rzvernal-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake b/host-configs/rzvernal-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake index cfe45cbeea..5b2a5d6d42 100644 --- a/host-configs/rzvernal-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake +++ b/host-configs/rzvernal-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake @@ -4,28 +4,28 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.29.2/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/blt-0.7.1-2eahqqwjyauupjq3baxftq7kuv3pvdgp;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/c2c-1.8.0-oxqhr2ybswpnzt74f4iritx263ji3uqy;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-sos5uklbsgaycb776a2ufcyje4y24yls;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/conduit-0.9.5-cxdq4qnoptjq6lry5asb2bqodvm3zjig;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/lua-5.4.8-lsey5vgxvpqf5j3xbrlzswrizuwm65vh;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/mfem-4.9.0-xwok5jck2jjg76k4575wkzgyvgopipz2;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/py-nanobind-2.7.0-3nchqpyclgle5w56it23ozl4ttbnrfjd;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/adiak-0.4.0-cbxp6kv6rm4f7oeczuhbplm3yb3zxb6z;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/libunwind-1.8.3-r4b3cptnf27zp34jbrdtlrf6r6dp6n4l;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/hdf5-1.8.23-wb7u6epuww3tretkcto7jhq2i7pffimg;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/parmetis-4.0.3-d6ed2qesb4yprvpetx3fso35rx6amf5b;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/py-mpi4py-4.1.1-kwpdge3wqmmxvxlocfdz4mgbc3zfihn6;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/py-numpy-2.4.2-t7cvohiksz7deeldwxrtqfmahcvanapb;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/hypre-2.27.0-pxqhlqwksiscco65tsz5somb4ctdq3q2;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-i5stpeet653njjadpjsekbvxlqkng2zn;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/umpire-2025.12.0-hpevb7stqsg6dltp3t2tcnzzps7tgywb;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/zlib-1.3.1-ijxa3tdiibemxtng46hrremmbytl2dqq;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/metis-5.1.0-mubwzuka5byhpbzmwaxjhtffhst3oj3q;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-j7my6ilwzwzvbaxzscbvishmj5mgyxnz;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/fmt-11.0.2-unmvs6vefcyfcfnzlie2eag5i2eib35d;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-6.4.3/blt-0.7.2-fi7f3mxamqq44js7xbmnpu2xk4te75da;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-6.4.3/c2c-1.8.0-dv5ggyguvmw4xalzzpexufqsmvv646t6;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-6.4.3/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-cqtqav6iwrg3vqvujd377ygs4i5mk6ua;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-6.4.3/conduit-0.9.7-eyc3vrxbq7iasvyvtqf7luvnjjcfdwhj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-6.4.3/lua-5.4.8-gzh3ylkvrwkcyegwxb4j47rjsqm7khl6;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-6.4.3/mfem-4.9.0-hdr4zw2h2pxdcw2vg3dskrxwzzhfcwhj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-6.4.3/py-nanobind-2.12.0-dmnviatiwcoe2u4tqwb4m5dtzy2k2hvs;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/none-none/py-pytest-9.0.3-2suvy7b3lmshqhnbylnif2jz2735dbbe;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-6.4.3/adiak-0.5.0-juupsnv6gao5vckdx65vodr5isbt2w3i;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-6.4.3/libunwind-1.8.3-2mcjpbkdqvhxjo64yjl2ghdk2uvz374g;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-6.4.3/hdf5-1.8.23-b62qyycwsymkjttcujtzrj5mt2y67khv;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-6.4.3/parmetis-4.0.3-wnhslyv7bnqiueidwkmvjof7odloc6xd;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-6.4.3/py-mpi4py-4.1.1-zbu3rkawlulkaynkbal35q2dfq7a63r7;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-6.4.3/py-numpy-2.4.6-6beieyegdqtn32ex6mmqlpi5tukuzz7h;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-6.4.3/hypre-3.1.0-5jc6ubqvzucfld4prwv6ilvkspfy2fga;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-6.4.3/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-cjz62wwi4yc2ipyua542r233xb324exk;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-6.4.3/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-c6iizeka2omkycs7ngwghv773nrspzl7;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-6.4.3/zlib-1.3.2-czlezml6nlt3eitzwqz7dxost6agewfj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-6.4.3/metis-5.1.0-kexg6pguakcgo6sra67vpp3hgppocicb;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-6.4.3/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-bntapsyjnuvbkmetoqruiuk2ejkpoj2e;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-6.4.3/fmt-12.1.0-po24stdzkwj4obatcqfus4uszhdbfd7m;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/none-none/compiler-wrapper-1.1.0-qqipzhe4rz5fmoiuvlqgarjeevl2kzuh;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.4.3;/opt/rocm-7.2.1;/opt/rocm-6.4.3;/opt/rocm-7.2.1;/opt/rocm-6.4.3" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/axom-develop-6a3pinjancledu7q5pzdp5ykhictf5ej/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/axom-develop-6a3pinjancledu7q5pzdp5ykhictf5ej/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-6.4.3/axom-develop-s3kbq23em6yhxeo4sw5k6olpuy25ft2d/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-6.4.3/axom-develop-s3kbq23em6yhxeo4sw5k6olpuy25ft2d/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/axom-develop-6a3pinjancledu7q5pzdp5ykhictf5ej/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3/axom-develop-6a3pinjancledu7q5pzdp5ykhictf5ej/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-6.4.3/axom-develop-s3kbq23em6yhxeo4sw5k6olpuy25ft2d/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-6.4.3/axom-develop-s3kbq23em6yhxeo4sw5k6olpuy25ft2d/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ # Compilers #------------------------------------------------------------------------------ -# Compiler Spec: llvm-amdgpu@6.4.3/u67i6xutdthzl3yti7tv3klyhyzddbkv +# Compiler Spec: llvm-amdgpu@6.4.3/j3unaymyuiplqezbi6qng6k2nyia7raa #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdclang" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/none-none/compiler-wrapper-1.1.0-qqipzhe4rz5fmoiuvlqgarjeevl2kzuh/libexec/spack/rocmcc/amdclang" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdclang++" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/none-none/compiler-wrapper-1.1.0-qqipzhe4rz5fmoiuvlqgarjeevl2kzuh/libexec/spack/rocmcc/amdclang++" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdflang" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/none-none/compiler-wrapper-1.1.0-qqipzhe4rz5fmoiuvlqgarjeevl2kzuh/libexec/spack/rocmcc/amdflang" CACHE PATH "") else() @@ -37,7 +37,11 @@ else() endif() -set(CMAKE_Fortran_FLAGS "-Mfreeform" CACHE STRING "") +set(CMAKE_C_FLAGS "-fPIC" CACHE STRING "") + +set(CMAKE_CXX_FLAGS "-fPIC" CACHE STRING "") + +set(CMAKE_Fortran_FLAGS "-fPIC -Mfreeform" CACHE STRING "") set(ENABLE_FORTRAN ON CACHE BOOL "") @@ -84,6 +88,8 @@ set(ROCM_ROOT_DIR "/opt/rocm-6.4.3" CACHE PATH "") set(BLT_CMAKE_IMPLICIT_LINK_LIBRARIES_EXCLUDE "ompstub" CACHE STRING "") +set(BLT_CMAKE_IMPLICIT_LINK_DIRECTORIES_EXCLUDE "/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12;/opt/rh/gcc-toolset-12/root/usr/lib64" CACHE STRING "") + set(CMAKE_EXE_LINKER_FLAGS "-lxpmem -L/opt/cray/pe/mpich/8.1.29/gtl/lib -Wl,-rpath,/opt/cray/pe/mpich/8.1.29/gtl/lib -lmpi_gtl_hsa -L/opt/rocm-6.4.3/lib/llvm/lib -Wl,-rpath,/opt/rocm-6.4.3/lib/llvm/lib -L/opt/rocm-6.4.3/lib -Wl,-rpath,/opt/rocm-6.4.3/lib -lpgmath -Wl,--disable-new-dtags -lflang -lflangrti -lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " CACHE STRING "") #------------------------------------------------ @@ -98,29 +104,29 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/llvm-amdgpu-6.4.3" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-6.4.3" CACHE PATH "") -set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-cxdq4qnoptjq6lry5asb2bqodvm3zjig" CACHE PATH "") +set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.7-eyc3vrxbq7iasvyvtqf7luvnjjcfdwhj" CACHE PATH "") -set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-oxqhr2ybswpnzt74f4iritx263ji3uqy" CACHE PATH "") +set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-dv5ggyguvmw4xalzzpexufqsmvv646t6" CACHE PATH "") -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-xwok5jck2jjg76k4575wkzgyvgopipz2" CACHE PATH "") +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-hdr4zw2h2pxdcw2vg3dskrxwzzhfcwhj" CACHE PATH "") -set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-wb7u6epuww3tretkcto7jhq2i7pffimg" CACHE PATH "") +set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-b62qyycwsymkjttcujtzrj5mt2y67khv" CACHE PATH "") -set(LUA_DIR "${TPL_ROOT}/lua-5.4.8-lsey5vgxvpqf5j3xbrlzswrizuwm65vh" CACHE PATH "") +set(LUA_DIR "${TPL_ROOT}/lua-5.4.8-gzh3ylkvrwkcyegwxb4j47rjsqm7khl6" CACHE PATH "") -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-i5stpeet653njjadpjsekbvxlqkng2zn" CACHE PATH "") +set(RAJA_DIR "${TPL_ROOT}/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-cjz62wwi4yc2ipyua542r233xb324exk" CACHE PATH "") -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-hpevb7stqsg6dltp3t2tcnzzps7tgywb" CACHE PATH "") +set(UMPIRE_DIR "${TPL_ROOT}/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-c6iizeka2omkycs7ngwghv773nrspzl7" CACHE PATH "") # OPENCASCADE not built -set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-cbxp6kv6rm4f7oeczuhbplm3yb3zxb6z" CACHE PATH "") +set(ADIAK_DIR "${TPL_ROOT}/adiak-0.5.0-juupsnv6gao5vckdx65vodr5isbt2w3i" CACHE PATH "") -set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-sos5uklbsgaycb776a2ufcyje4y24yls" CACHE PATH "") +set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-cqtqav6iwrg3vqvujd377ygs4i5mk6ua" CACHE PATH "") -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-j7my6ilwzwzvbaxzscbvishmj5mgyxnz" CACHE PATH "") +set(CAMP_DIR "${TPL_ROOT}/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-bntapsyjnuvbkmetoqruiuk2ejkpoj2e" CACHE PATH "") # scr not built @@ -148,16 +154,16 @@ set(CPPCHECK_EXECUTABLE "${DEVTOOLS_ROOT}/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/doxygen-1.15.0/bin/doxygen" CACHE PATH "") -set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-3nchqpyclgle5w56it23ozl4ttbnrfjd/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.12.0-dmnviatiwcoe2u4tqwb4m5dtzy2k2hvs/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/none-none/py-pytest-9.0.3-2suvy7b3lmshqhnbylnif2jz2735dbbe/lib/python3.13/site-packages" CACHE PATH "") -set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-t7cvohiksz7deeldwxrtqfmahcvanapb/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.6-6beieyegdqtn32ex6mmqlpi5tukuzz7h/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/py-pluggy-1.6.0-27aoz44dedjxa6j6r6cavpqyqmpknidb/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/none-none/py-pluggy-1.6.0-eemjlppu7ynjgedbqabclpfgxt4hxdoh/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_38_29/none-none/py-iniconfig-2.1.0-7rty7n4wq6kxxrzflm7wbxqzhhc6ph7q/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/none-none/py-iniconfig-2.1.0-kss2a5udi5pgct5uvmgjh3ojkir5hpoo/lib/python3.13/site-packages" CACHE PATH "") -set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-kwpdge3wqmmxvxlocfdz4mgbc3zfihn6/lib/python3.13/site-packages" CACHE PATH "") +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-zbu3rkawlulkaynkbal35q2dfq7a63r7/lib/python3.13/site-packages" CACHE PATH "") diff --git a/host-configs/rzvernal-toss_4_x86_64_ib_cray-llvm-amdgpu@7.2.1_hip.cmake b/host-configs/rzvernal-toss_4_x86_64_ib_cray-llvm-amdgpu@7.2.1_hip.cmake new file mode 100644 index 0000000000..fb4d4df63d --- /dev/null +++ b/host-configs/rzvernal-toss_4_x86_64_ib_cray-llvm-amdgpu@7.2.1_hip.cmake @@ -0,0 +1,169 @@ +#------------------------------------------------------------------------------ +# !!!! This is a generated file, edit at own risk !!!! +#------------------------------------------------------------------------------ +# CMake executable path: /usr/tce/packages/cmake/cmake-3.29.2/bin/cmake +#------------------------------------------------------------------------------ + +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-7.2.1/blt-0.7.2-4z7pqwbd3sf2qsenzgnzsoe5nvvtnm4x;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-7.2.1/c2c-1.8.0-tofdu5fhegkweai4md5q34r2m3rx6kng;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-7.2.1/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-eaiv5wbcyvlgg5gsydyvpphdqjo5nmeg;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-7.2.1/conduit-0.9.7-ahosvgx7w3pz7gwxjbsmusiqonkapwl6;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-7.2.1/lua-5.4.8-6fgci4pl7sbuc2rjjogfzrqumbizqjf6;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-7.2.1/mfem-4.9.0-jth3kgaagpo2geurhvdnnw7g7x3gtsi5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-7.2.1/py-nanobind-2.12.0-hkbx76svqw6adn3pq5dp3ryivd4rutir;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/none-none/py-pytest-9.0.3-2suvy7b3lmshqhnbylnif2jz2735dbbe;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-7.2.1/adiak-0.5.0-m3k5dosirkwzjpdsi3wucl6qld2h4skf;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-7.2.1/libunwind-1.8.3-2wgdo2h5zm43g5w7lzvcgfss3dak4jl4;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-7.2.1/hdf5-1.8.23-cd7kyazwjebsg3fqw44spapntrd43ju6;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-7.2.1/parmetis-4.0.3-r5tsx55ljcnbws7o6mg4nmkjsxamocrx;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-7.2.1/py-mpi4py-4.1.1-j2etz7tywsmxg7ut3rhtnjc6z56ggwue;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-7.2.1/py-numpy-2.4.6-verhhzd4242w4cxgvn2vdbaqf2bdrc5z;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-7.2.1/hypre-3.1.0-synizlg2jgfdz56b5ul2kseit5vrufd7;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-7.2.1/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-imyucyzqy3fe35azgt64midjcyyv65to;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-7.2.1/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-xkis4avuybnc3zjmbdark6fnl4tpjfwq;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-7.2.1/zlib-1.3.2-53253klbr4xexkw2aidk7umh754b4be2;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-7.2.1/metis-5.1.0-qb4krznzcfbr2kyyro6y7hlh4t5zjj2l;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-7.2.1/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-uyzaudb5eouvgsyg2be4lztwocpnevud;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-7.2.1/fmt-12.1.0-pu7rys2ucvbk3pgkol2wgtl6d5wf3vrd;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/none-none/compiler-wrapper-1.1.0-qqipzhe4rz5fmoiuvlqgarjeevl2kzuh;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-9.1.0-rocmcc-7.2.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-7.2.1;/opt/rocm-7.2.1;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-7.2.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-7.2.1;/opt/rocm-7.2.1;/opt/rocm-7.2.1;/opt/rocm-7.2.1;/opt/rocm-7.2.1" CACHE STRING "") + +set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") + +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-7.2.1/axom-develop-pbz2vk7ummjo6knote5ahukq7vzycru2/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-7.2.1/axom-develop-pbz2vk7ummjo6knote5ahukq7vzycru2/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") + +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-7.2.1/axom-develop-pbz2vk7ummjo6knote5ahukq7vzycru2/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-7.2.1/axom-develop-pbz2vk7ummjo6knote5ahukq7vzycru2/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") + +set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") + +#------------------------------------------------------------------------------ +# Compilers +#------------------------------------------------------------------------------ +# Compiler Spec: llvm-amdgpu@7.2.1/titlcdv5eonxuakrjl6bcxvgndtjk4xy +#------------------------------------------------------------------------------ +if(DEFINED ENV{SPACK_CC}) + + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/none-none/compiler-wrapper-1.1.0-qqipzhe4rz5fmoiuvlqgarjeevl2kzuh/libexec/spack/rocmcc/amdclang" CACHE PATH "") + + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/none-none/compiler-wrapper-1.1.0-qqipzhe4rz5fmoiuvlqgarjeevl2kzuh/libexec/spack/rocmcc/amdclang++" CACHE PATH "") + + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/none-none/compiler-wrapper-1.1.0-qqipzhe4rz5fmoiuvlqgarjeevl2kzuh/libexec/spack/rocmcc/amdflang" CACHE PATH "") + +else() + + set(CMAKE_C_COMPILER "/opt/rocm-7.2.1/llvm/bin/amdclang" CACHE PATH "") + + set(CMAKE_CXX_COMPILER "/opt/rocm-7.2.1/llvm/bin/amdclang++" CACHE PATH "") + + set(CMAKE_Fortran_COMPILER "/opt/rocm-7.2.1/llvm/bin/amdflang" CACHE PATH "") + +endif() + +set(CMAKE_C_FLAGS "-fPIC" CACHE STRING "") + +set(CMAKE_CXX_FLAGS "-fPIC" CACHE STRING "") + +set(CMAKE_Fortran_FLAGS "-fPIC" CACHE STRING "") + +set(ENABLE_FORTRAN ON CACHE BOOL "") + +#------------------------------------------------------------------------------ +# MPI +#------------------------------------------------------------------------------ + +set(MPI_C_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-9.1.0-rocmcc-7.2.1/bin/mpicc" CACHE PATH "") + +set(MPI_CXX_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-9.1.0-rocmcc-7.2.1/bin/mpicxx" CACHE PATH "") + +set(MPI_Fortran_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-9.1.0-rocmcc-7.2.1/bin/mpif90" CACHE PATH "") + +set(MPIEXEC_EXECUTABLE "/usr/global/tools/flux_wrappers/bin/srun" CACHE PATH "") + +set(MPIEXEC_NUMPROC_FLAG "-n" CACHE STRING "") + +set(ENABLE_MPI ON CACHE BOOL "") + +#------------------------------------------------------------------------------ +# Hardware +#------------------------------------------------------------------------------ + +#------------------------------------------------ +# ROCm +#------------------------------------------------ + +set(ROCM_PATH "/opt/rocm-7.2.1" CACHE PATH "") + +set(CMAKE_HIP_ARCHITECTURES "gfx90a;gfx942" CACHE STRING "") + +set(CMAKE_HIP_COMPILER "/opt/rocm-7.2.1/bin/amdclang++" CACHE FILEPATH "") + +#------------------------------------------------------------------------------ + +# Axom ROCm specifics + +#------------------------------------------------------------------------------ + + +set(ENABLE_HIP ON CACHE BOOL "") + +set(ROCM_ROOT_DIR "/opt/rocm-7.2.1" CACHE PATH "") + +set(BLT_CMAKE_IMPLICIT_LINK_LIBRARIES_EXCLUDE "ompstub" CACHE STRING "") + +set(BLT_CMAKE_IMPLICIT_LINK_DIRECTORIES_EXCLUDE "/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12;/opt/rh/gcc-toolset-12/root/usr/lib64" CACHE STRING "") + +set(CMAKE_EXE_LINKER_FLAGS "-lxpmem -L/opt/cray/pe/mpich/9.1.0/gtl/lib -Wl,-rpath,/opt/cray/pe/mpich/9.1.0/gtl/lib -lmpi_gtl_hsa -L/opt/rocm-7.2.1/lib/llvm/lib -Wl,-rpath,/opt/rocm-7.2.1/lib/llvm/lib -L/opt/rocm-7.2.1/lib -Wl,-rpath,/opt/rocm-7.2.1/lib -lpgmath -Wl,--disable-new-dtags -lflang -lflangrti -lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " CACHE STRING "") + +#------------------------------------------------ +# Hardware Specifics +#------------------------------------------------ + +set(ENABLE_OPENMP ON CACHE BOOL "") + +set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") + +#------------------------------------------------------------------------------ +# TPLs +#------------------------------------------------------------------------------ + +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/llvm-amdgpu-7.2.1" CACHE PATH "") + +set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.7-ahosvgx7w3pz7gwxjbsmusiqonkapwl6" CACHE PATH "") + +set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-tofdu5fhegkweai4md5q34r2m3rx6kng" CACHE PATH "") + +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-jth3kgaagpo2geurhvdnnw7g7x3gtsi5" CACHE PATH "") + +set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-cd7kyazwjebsg3fqw44spapntrd43ju6" CACHE PATH "") + +set(LUA_DIR "${TPL_ROOT}/lua-5.4.8-6fgci4pl7sbuc2rjjogfzrqumbizqjf6" CACHE PATH "") + +set(RAJA_DIR "${TPL_ROOT}/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-imyucyzqy3fe35azgt64midjcyyv65to" CACHE PATH "") + +set(UMPIRE_DIR "${TPL_ROOT}/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-xkis4avuybnc3zjmbdark6fnl4tpjfwq" CACHE PATH "") + +# OPENCASCADE not built + +set(ADIAK_DIR "${TPL_ROOT}/adiak-0.5.0-m3k5dosirkwzjpdsi3wucl6qld2h4skf" CACHE PATH "") + +set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-eaiv5wbcyvlgg5gsydyvpphdqjo5nmeg" CACHE PATH "") + +set(CAMP_DIR "${TPL_ROOT}/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-uyzaudb5eouvgsyg2be4lztwocpnevud" CACHE PATH "") + +# scr not built + +#------------------------------------------------------------------------------ +# Devtools & Python +#------------------------------------------------------------------------------ + +set(DEVTOOLS_ROOT "/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25" CACHE PATH "") + +set(CLANGFORMAT_EXECUTABLE "/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm/bin/clang-format" CACHE PATH "") + +set(Python_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/python3" CACHE PATH "") + +set(JSONSCHEMA_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/jsonschema" CACHE PATH "") + +set(ENABLE_DOCS ON CACHE BOOL "") + +set(SPHINX_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/sphinx-build" CACHE PATH "") + +set(YAPF_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/yapf" CACHE PATH "") + +set(SHROUD_EXECUTABLE "/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0/bin/shroud" CACHE PATH "") + +set(CPPCHECK_EXECUTABLE "${DEVTOOLS_ROOT}/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27/bin/cppcheck" CACHE PATH "") + +set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/doxygen-1.15.0/bin/doxygen" CACHE PATH "") + +set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.12.0-hkbx76svqw6adn3pq5dp3ryivd4rutir/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/none-none/py-pytest-9.0.3-2suvy7b3lmshqhnbylnif2jz2735dbbe/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.6-verhhzd4242w4cxgvn2vdbaqf2bdrc5z/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/none-none/py-pluggy-1.6.0-eemjlppu7ynjgedbqabclpfgxt4hxdoh/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_09_58_29/none-none/py-iniconfig-2.1.0-kss2a5udi5pgct5uvmgjh3ojkir5hpoo/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-j2etz7tywsmxg7ut3rhtnjc6z56ggwue/lib/python3.13/site-packages" CACHE PATH "") + + diff --git a/host-configs/rzwhippet-toss_4_x86_64_ib-gcc@13.3.1.cmake b/host-configs/rzwhippet-toss_4_x86_64_ib-gcc@13.3.1.cmake index dab70400d2..f5425829d8 100644 --- a/host-configs/rzwhippet-toss_4_x86_64_ib-gcc@13.3.1.cmake +++ b/host-configs/rzwhippet-toss_4_x86_64_ib-gcc@13.3.1.cmake @@ -4,28 +4,28 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.26.3/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/blt-0.7.1-lj5bghgkodlzmfs75wimnoxuui2quc3a;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/c2c-1.8.0-tyixdhhwshu35d2vuh6gw5bk3b3k7nqz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-bzioyf6catu3cjwaz4cplt7ubtvavgbl;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/conduit-0.9.5-vlm6nb2rwwmvnxtnxw5p3o27rzsap6vt;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/mfem-4.9.0-xodkornev7gcvmi3idsoxtdsnlph4rqv;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/py-nanobind-2.7.0-e42etfacct5pxga65kryysakw52u4c63;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-o23ptcxtve2rphukiakbezevsssiqqmq;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/scr-3.0.1-ov4mwvpcd75glzu2lfuivq3oibr4vvpd;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/umpire-2025.12.0-drla577bebgu5wx6q7y37qxedmgmpcgi;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/adiak-0.4.0-nqiuo5ltc24jpawzi6mlp3xtmngcopiu;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/libunwind-1.8.3-a7zvz3euhmbhk4as6h6j66ytabzwztxb;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/hdf5-1.8.23-zyfoez4ztydoqfeubcamjypyhprjpuca;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/parmetis-4.0.3-sxnyn5chjyiyowzk32dsynslufrofyho;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/py-mpi4py-4.1.1-l3h5jmmg773bd5oer7makte3ln6rn7xv;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/py-numpy-2.4.2-to6o5czwkczlh46d6gkubixw5bhzxakl;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/hypre-2.27.0-qvi4rtn4tbrduxkdpfugf6f5lkzirnel;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/axl-0.7.1-ljgukulclybf6cdbcj7u3w7o63v43zcg;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/dtcmp-1.1.5-taj5rsqbrki5hk2fg5pxkg6xsz5tfjxk;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/er-0.3.0-ozpsif2qsi7or56avv6fxwn3at2hrhli;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/libyogrt-1.35-y4nvgwhpokw2x6lekbubcmxv5wweidg4;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/spath-0.2.0-5zx5r6jnfiqbfh2hfkzvuvqmam6j67dt;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-kjufdwlliz23qzwoy2xygn3w6dsq6tec;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/fmt-11.0.2-5wsybhdkos4xu5i7thboag6245lzc6jj;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/metis-5.1.0-3qhomb7zt2n7nh46kmc2egkzmcd3tafm;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/lwgrp-1.0.6-e2smb3vzymlqmy35nuipgi6cv6o2ezyz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/redset-0.2.0-itxhprv2jiz4uw4oa42jribcmnc6z2xr;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/shuffile-0.2.0-jpcg5fljyjaf3sjmnasiiv4brhw47vcc;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/rankstr-0.2.0-dpmjoa5upfcwzpbvxmhrbgupiqtu4uoz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/kvtree-1.3.0-3tlf6x5raibwnal2uo2l7feyjrqmkrhd;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/gcc-runtime-13.3.1-xlvjd7vcdngtnvtxcdyqwtquvenekynf;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.26.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66xzj4wm5kfqxyc4vgox7gb3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/doxygen-1.15.0;/usr/tce/packages/gcc/gcc-13.3.1;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-gcc-13.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/gcc-13.3.1/blt-0.7.2-kgttmwj56kwpge3oveotwgx7amhmc54b;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/gcc-13.3.1/c2c-1.8.0-ilhafup6lemuluscuhe2wcw6dhqwukac;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/gcc-13.3.1/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-56wmby3kxz4tfujjmxqkvklthqkbmyyn;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/gcc-13.3.1/conduit-0.9.7-cj7f7ue7tuh5za3vyckhvazoaa5dirlz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/gcc-13.3.1/mfem-4.9.0-7d7eeipuqb2muks5bczjac63jcokn3iz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/gcc-13.3.1/py-nanobind-2.12.0-2slokzflsbdyymtlubukr5uv352aaqol;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/none-none/py-pytest-9.0.3-gzmipids43fsxiidqzdglv4cbaxj26gk;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/gcc-13.3.1/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-jmywz4ts2sygkyau5dafh6ikwnbjhtye;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/gcc-13.3.1/scr-3.0.1-basfrtnpqf4c53b4rje6jsvep5zsnrwe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/gcc-13.3.1/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-vlfk7hrrnglk46jwsd7tfpgkcvqed4ox;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/gcc-13.3.1/adiak-0.5.0-rv7mu2xwq332xz2xqgfjtond4hvjlcvi;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/gcc-13.3.1/libunwind-1.8.3-3ltfsqv444quhz3wviidadz2x57mcmfd;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/gcc-13.3.1/hdf5-1.8.23-rephuaeqmzpcjiazcflzeswdvsbwuvd2;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/gcc-13.3.1/parmetis-4.0.3-ykjugf2p3cjircoep575ra3cn4we724b;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/gcc-13.3.1/py-mpi4py-4.1.1-susobssvjtqo5inxlvwts2s5qacifmqy;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/gcc-13.3.1/py-numpy-2.4.6-owxvcv322niqkkfckectzayaxxwre7f4;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/gcc-13.3.1/hypre-3.1.0-5zpehk5up5jposhv6pkpcemzl57l7o2k;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/gcc-13.3.1/axl-0.7.1-r3e4275my4pohksumwdh3alreqir3l5d;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/gcc-13.3.1/dtcmp-1.1.5-a3lz6mmn25p3jh4sanryhtd7d6ligiv2;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/gcc-13.3.1/er-0.3.0-ct43o2nsxxs3o6lzx2fqq7x6hodycdxj;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/gcc-13.3.1/libyogrt-1.35-mofsq5rrmoqrthm3vbb6qits2zxd7mb5;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/gcc-13.3.1/spath-0.2.0-hna6nu4oneotlxwcfrvsfmekoh5ppjgi;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/gcc-13.3.1/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-bfs3xf2vc2vqmawm6kpspcyscpkqxxqc;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/gcc-13.3.1/fmt-12.1.0-zqxr3fxqwzr32g2k5bo2wsixnw67co57;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/gcc-13.3.1/metis-5.1.0-ugyr2txwucmnoa74sjorvtvktey4s7j4;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/gcc-13.3.1/lwgrp-1.0.6-hlxl5ibgd7nuqhmyn3644y43u567odhy;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/gcc-13.3.1/redset-0.2.0-d7uq623m37qhjj6leet4x3fvx2fesdrq;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/gcc-13.3.1/shuffile-0.2.0-o7x3cedrhi2gqwezlgtkszdayyi7aeqp;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/gcc-13.3.1/rankstr-0.2.0-om247obwembbr3pzzvqelcploi3clul3;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/gcc-13.3.1/kvtree-1.3.0-mymtbems5iw4xq3kfo6eyzpnbml7xv5m;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/none-none/gcc-runtime-13.3.1-idl2e7ehceec4tq2xcci4n27sch4dkvu;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/none-none/compiler-wrapper-1.1.0-pbd2ayub7z6lkuzpvgsnzfsycl3xbuy3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.26.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66xzj4wm5kfqxyc4vgox7gb3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/doxygen-1.15.0;/usr/tce/packages/gcc/gcc-13.3.1;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-gcc-13.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/axom-develop-vfi5qherug7c2n7iyqkyclwbajst4kps/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/axom-develop-vfi5qherug7c2n7iyqkyclwbajst4kps/lib64;;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/gcc-13.3.1/axom-develop-elsiwuivxzov45w4u2z4zlz3hhjxfl3f/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/gcc-13.3.1/axom-develop-elsiwuivxzov45w4u2z4zlz3hhjxfl3f/lib64;;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/axom-develop-vfi5qherug7c2n7iyqkyclwbajst4kps/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/axom-develop-vfi5qherug7c2n7iyqkyclwbajst4kps/lib64;;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/gcc-13.3.1/axom-develop-elsiwuivxzov45w4u2z4zlz3hhjxfl3f/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/gcc-13.3.1/axom-develop-elsiwuivxzov45w4u2z4zlz3hhjxfl3f/lib64;;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ # Compilers #------------------------------------------------------------------------------ -# Compiler Spec: gcc@13.3.1/zbmuyoury7g5npf6fa7lwwxbjninyheh +# Compiler Spec: gcc@13.3.1/coban4v6d67qsc2lmofonv7wkeuxgg55 #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/gcc" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/none-none/compiler-wrapper-1.1.0-pbd2ayub7z6lkuzpvgsnzfsycl3xbuy3/libexec/spack/gcc/gcc" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/g++" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/none-none/compiler-wrapper-1.1.0-pbd2ayub7z6lkuzpvgsnzfsycl3xbuy3/libexec/spack/gcc/g++" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/gfortran" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/none-none/compiler-wrapper-1.1.0-pbd2ayub7z6lkuzpvgsnzfsycl3xbuy3/libexec/spack/gcc/gfortran" CACHE PATH "") else() @@ -77,51 +77,51 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/gcc-13.3.1" CACHE PATH "") -set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-vlm6nb2rwwmvnxtnxw5p3o27rzsap6vt" CACHE PATH "") +set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.7-cj7f7ue7tuh5za3vyckhvazoaa5dirlz" CACHE PATH "") -set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-tyixdhhwshu35d2vuh6gw5bk3b3k7nqz" CACHE PATH "") +set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-ilhafup6lemuluscuhe2wcw6dhqwukac" CACHE PATH "") -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-xodkornev7gcvmi3idsoxtdsnlph4rqv" CACHE PATH "") +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-7d7eeipuqb2muks5bczjac63jcokn3iz" CACHE PATH "") -set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-zyfoez4ztydoqfeubcamjypyhprjpuca" CACHE PATH "") +set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-rephuaeqmzpcjiazcflzeswdvsbwuvd2" CACHE PATH "") set(LUA_DIR "/usr" CACHE PATH "") -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-o23ptcxtve2rphukiakbezevsssiqqmq" CACHE PATH "") +set(RAJA_DIR "${TPL_ROOT}/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-jmywz4ts2sygkyau5dafh6ikwnbjhtye" CACHE PATH "") -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-drla577bebgu5wx6q7y37qxedmgmpcgi" CACHE PATH "") +set(UMPIRE_DIR "${TPL_ROOT}/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-vlfk7hrrnglk46jwsd7tfpgkcvqed4ox" CACHE PATH "") # OPENCASCADE not built -set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-nqiuo5ltc24jpawzi6mlp3xtmngcopiu" CACHE PATH "") +set(ADIAK_DIR "${TPL_ROOT}/adiak-0.5.0-rv7mu2xwq332xz2xqgfjtond4hvjlcvi" CACHE PATH "") -set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-bzioyf6catu3cjwaz4cplt7ubtvavgbl" CACHE PATH "") +set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-56wmby3kxz4tfujjmxqkvklthqkbmyyn" CACHE PATH "") -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-kjufdwlliz23qzwoy2xygn3w6dsq6tec" CACHE PATH "") +set(CAMP_DIR "${TPL_ROOT}/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-bfs3xf2vc2vqmawm6kpspcyscpkqxxqc" CACHE PATH "") -set(SCR_DIR "${TPL_ROOT}/scr-3.0.1-ov4mwvpcd75glzu2lfuivq3oibr4vvpd" CACHE PATH "") +set(SCR_DIR "${TPL_ROOT}/scr-3.0.1-basfrtnpqf4c53b4rje6jsvep5zsnrwe" CACHE PATH "") -set(KVTREE_DIR "${TPL_ROOT}/kvtree-1.3.0-3tlf6x5raibwnal2uo2l7feyjrqmkrhd" CACHE PATH "") +set(KVTREE_DIR "${TPL_ROOT}/kvtree-1.3.0-mymtbems5iw4xq3kfo6eyzpnbml7xv5m" CACHE PATH "") -set(DTCMP_DIR "${TPL_ROOT}/dtcmp-1.1.5-taj5rsqbrki5hk2fg5pxkg6xsz5tfjxk" CACHE PATH "") +set(DTCMP_DIR "${TPL_ROOT}/dtcmp-1.1.5-a3lz6mmn25p3jh4sanryhtd7d6ligiv2" CACHE PATH "") -set(SPATH_DIR "${TPL_ROOT}/spath-0.2.0-5zx5r6jnfiqbfh2hfkzvuvqmam6j67dt" CACHE PATH "") +set(SPATH_DIR "${TPL_ROOT}/spath-0.2.0-hna6nu4oneotlxwcfrvsfmekoh5ppjgi" CACHE PATH "") -set(AXL_DIR "${TPL_ROOT}/axl-0.7.1-ljgukulclybf6cdbcj7u3w7o63v43zcg" CACHE PATH "") +set(AXL_DIR "${TPL_ROOT}/axl-0.7.1-r3e4275my4pohksumwdh3alreqir3l5d" CACHE PATH "") -set(LWGRP_DIR "${TPL_ROOT}/lwgrp-1.0.6-e2smb3vzymlqmy35nuipgi6cv6o2ezyz" CACHE PATH "") +set(LWGRP_DIR "${TPL_ROOT}/lwgrp-1.0.6-hlxl5ibgd7nuqhmyn3644y43u567odhy" CACHE PATH "") -set(ER_DIR "${TPL_ROOT}/er-0.3.0-ozpsif2qsi7or56avv6fxwn3at2hrhli" CACHE PATH "") +set(ER_DIR "${TPL_ROOT}/er-0.3.0-ct43o2nsxxs3o6lzx2fqq7x6hodycdxj" CACHE PATH "") -set(RANKSTR_DIR "${TPL_ROOT}/rankstr-0.2.0-dpmjoa5upfcwzpbvxmhrbgupiqtu4uoz" CACHE PATH "") +set(RANKSTR_DIR "${TPL_ROOT}/rankstr-0.2.0-om247obwembbr3pzzvqelcploi3clul3" CACHE PATH "") -set(REDSET_DIR "${TPL_ROOT}/redset-0.2.0-itxhprv2jiz4uw4oa42jribcmnc6z2xr" CACHE PATH "") +set(REDSET_DIR "${TPL_ROOT}/redset-0.2.0-d7uq623m37qhjj6leet4x3fvx2fesdrq" CACHE PATH "") -set(SHUFFILE_DIR "${TPL_ROOT}/shuffile-0.2.0-jpcg5fljyjaf3sjmnasiiv4brhw47vcc" CACHE PATH "") +set(SHUFFILE_DIR "${TPL_ROOT}/shuffile-0.2.0-o7x3cedrhi2gqwezlgtkszdayyi7aeqp" CACHE PATH "") -set(LIBYOGRT_DIR "${TPL_ROOT}/libyogrt-1.35-y4nvgwhpokw2x6lekbubcmxv5wweidg4" CACHE PATH "") +set(LIBYOGRT_DIR "${TPL_ROOT}/libyogrt-1.35-mofsq5rrmoqrthm3vbb6qits2zxd7mb5" CACHE PATH "") #------------------------------------------------------------------------------ # Devtools & Python @@ -147,16 +147,16 @@ set(CPPCHECK_EXECUTABLE "${DEVTOOLS_ROOT}/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66x set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/kky5oskfhacihjb5gpy7qkeh6edlrtcb/doxygen-1.15.0/bin/doxygen" CACHE PATH "") -set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-e42etfacct5pxga65kryysakw52u4c63/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.12.0-2slokzflsbdyymtlubukr5uv352aaqol/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/none-none/py-pytest-9.0.3-gzmipids43fsxiidqzdglv4cbaxj26gk/lib/python3.13/site-packages" CACHE PATH "") -set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-to6o5czwkczlh46d6gkubixw5bhzxakl/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.6-owxvcv322niqkkfckectzayaxxwre7f4/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/py-pluggy-1.6.0-6zab4pck6v7johubbhalr2waowbwpjei/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/none-none/py-pluggy-1.6.0-7gqc6q7eser33fbaul2h4xxpqo67fphj/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/py-iniconfig-2.1.0-xwmbxuhk2oo3dcuizdp3z4hqbpqykkom/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/none-none/py-iniconfig-2.1.0-7flbc7ylpqkqn4swh7u5lojtj2ipn7bx/lib/python3.13/site-packages" CACHE PATH "") -set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-l3h5jmmg773bd5oer7makte3ln6rn7xv/lib/python3.13/site-packages" CACHE PATH "") +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-susobssvjtqo5inxlvwts2s5qacifmqy/lib/python3.13/site-packages" CACHE PATH "") diff --git a/host-configs/rzwhippet-toss_4_x86_64_ib-intel-oneapi-compilers@2025.2.0.cmake b/host-configs/rzwhippet-toss_4_x86_64_ib-intel-oneapi-compilers@2025.2.0.cmake index 67622cf4c5..48e411947e 100644 --- a/host-configs/rzwhippet-toss_4_x86_64_ib-intel-oneapi-compilers@2025.2.0.cmake +++ b/host-configs/rzwhippet-toss_4_x86_64_ib-intel-oneapi-compilers@2025.2.0.cmake @@ -4,28 +4,28 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.26.3/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/intel-oneapi-compilers-2025.2.0/blt-0.7.1-fnswc6mwjgunle3ushrnp3e6w6s25qra;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/intel-oneapi-compilers-2025.2.0/c2c-1.8.0-fdbbv7fzmylpoctjgdai2kc4xfcz2vqt;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/intel-oneapi-compilers-2025.2.0/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-bheefndoamg47jey4mbq2ofgflz7kvz4;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/intel-oneapi-compilers-2025.2.0/conduit-0.9.5-lqjx3vlbi5prcmle2gsgc7yopbglp3ab;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/intel-oneapi-compilers-2025.2.0/mfem-4.9.0-npcozwxpczgzuya2z6iailvycmbbc6nd;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/py-nanobind-2.7.0-iky6agz5lprxpnf2nffxwgqcgiagqiz5;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/intel-oneapi-compilers-2025.2.0/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-ppbrbxztecp4363lchwihhymqrhv75un;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/intel-oneapi-compilers-2025.2.0/umpire-2025.12.0-tdpnmcmwvlz77qwdeupgufa6s5aswwp5;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/intel-oneapi-compilers-2025.2.0/adiak-0.4.0-gxozhembb6br5wkhuxetug25ndaodstk;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/intel-oneapi-compilers-2025.2.0/libunwind-1.8.3-lgvf5mwbhqt4is2gg3uuafuyxv35lwd7;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/intel-oneapi-compilers-2025.2.0/hdf5-1.8.23-zhxj7u6hk266vklzloqcp3qlahtbta4l;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/intel-oneapi-compilers-2025.2.0/parmetis-4.0.3-dred3k4i2un2bw3w4st5pl2zvpvpfr37;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/intel-oneapi-compilers-2025.2.0/py-mpi4py-4.1.1-7mymbwx2ifuqlotydf6dx6hd6wmz4xhd;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/py-numpy-2.4.2-to6o5czwkczlh46d6gkubixw5bhzxakl;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/intel-oneapi-compilers-2025.2.0/hypre-2.27.0-jtpmchw5qcrtezcac6mp53wlxcncp5ne;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/intel-oneapi-compilers-2025.2.0/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-k4dewbo7oilgt6zjhkze2hyqyc6xn3if;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/intel-oneapi-compilers-2025.2.0/fmt-11.0.2-6ut7qqyjzqqbc6ggi4o7jthpsofhhu65;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/intel-oneapi-compilers-2025.2.0/metis-5.1.0-psuyihfrmzzgjxljxkoqbaqdjs53253r;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/intel-oneapi-runtime-2025.2.0-3ym7ceyrw63moiirt4tqoqubxhkiqv63;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/gcc-runtime-13.3.1-xlvjd7vcdngtnvtxcdyqwtquvenekynf;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.26.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66xzj4wm5kfqxyc4vgox7gb3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/doxygen-1.15.0;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/intel-oneapi-compilers-2025.2.0-ngszoacyp2i5d5nawitlyuh37jswjtie/compiler/2025.2;/usr/tce/packages/intel/intel-2025.2.0;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-intel-2025.2.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/intel-oneapi-compilers-2025.2.0/blt-0.7.2-evtmihs46mbczrqbjfwwmroywmgxxjma;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/intel-oneapi-compilers-2025.2.0/c2c-1.8.0-5wjdmt55f7nuftxsaxqvrb4zrl72dovf;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/intel-oneapi-compilers-2025.2.0/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-6znuaaeszc2faabgq55yrahxv5yqnmu6;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/intel-oneapi-compilers-2025.2.0/conduit-0.9.7-b5kuchct7dt5zox4ck7rtprtznejh2il;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/intel-oneapi-compilers-2025.2.0/mfem-4.9.0-wwgragi5vtbzo5wkjmjrthjjk3ggzs2l;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/intel-oneapi-compilers-2025.2.0/py-nanobind-2.12.0-kfwke5jj6qxsoayuruy7zvp6jb757x5r;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/none-none/py-pytest-9.0.3-gzmipids43fsxiidqzdglv4cbaxj26gk;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/intel-oneapi-compilers-2025.2.0/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-q24rhtgaxvk36dia7o3zisrq23r2icp6;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/intel-oneapi-compilers-2025.2.0/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-u6xviw2os347tdexix55hj5xgxebvtb4;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/intel-oneapi-compilers-2025.2.0/adiak-0.5.0-fcafug75o3ehttelhmvzcq6at3blpqfz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/intel-oneapi-compilers-2025.2.0/libunwind-1.8.3-yawby7qknroemwpzfyvgukoe2nqlgcu2;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/intel-oneapi-compilers-2025.2.0/hdf5-1.8.23-7x2tmsqn2zeo2tm74ih6ovlt3ayejfxo;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/intel-oneapi-compilers-2025.2.0/parmetis-4.0.3-667nagnshxefhyr235hoclqgn7vbr3zg;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/intel-oneapi-compilers-2025.2.0/py-mpi4py-4.1.1-qw72rhicazqo2tf4wu3emk4kxeaza3zv;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/gcc-13.3.1/py-numpy-2.4.6-f2gs5srtddls5yewq6xbs7fyal24q3j6;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/intel-oneapi-compilers-2025.2.0/hypre-3.1.0-uzilxxkm36njhhag27mrhvbm3g5eiya6;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/intel-oneapi-compilers-2025.2.0/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-vhpjt4ws6affncnj6xweeazpltvii6nl;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/intel-oneapi-compilers-2025.2.0/fmt-12.1.0-53kznw5w4ud7gsawjaa2b7utwzctfxg7;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/intel-oneapi-compilers-2025.2.0/metis-5.1.0-gbsrm6zpdb3t6dp4ba4ag6yrs26576rm;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/none-none/intel-oneapi-runtime-2025.2.0-hpokdteeh7mtzizfnhrbiaovr622es6q;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/none-none/gcc-runtime-13.3.1-idl2e7ehceec4tq2xcci4n27sch4dkvu;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/none-none/compiler-wrapper-1.1.0-pbd2ayub7z6lkuzpvgsnzfsycl3xbuy3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.26.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66xzj4wm5kfqxyc4vgox7gb3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/doxygen-1.15.0;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/intel-oneapi-compilers-2025.2.0-ngszoacyp2i5d5nawitlyuh37jswjtie/compiler/2025.2;/usr/tce/packages/intel/intel-2025.2.0;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-intel-2025.2.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/intel-oneapi-compilers-2025.2.0/axom-develop-g2h45edliw4q2vgz7cfusag64p3ulopb/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/intel-oneapi-compilers-2025.2.0/axom-develop-g2h45edliw4q2vgz7cfusag64p3ulopb/lib64;;/usr/tce/packages/intel/intel-2025.2.0/compiler/2025.2/lib;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/intel-oneapi-compilers-2025.2.0-ngszoacyp2i5d5nawitlyuh37jswjtie/compiler/2025.2/lib;/usr/tce/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/intel-oneapi-compilers-2025.2.0/axom-develop-ctb4mtm5xxvp4rnyiejj36iuyoonzu35/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/intel-oneapi-compilers-2025.2.0/axom-develop-ctb4mtm5xxvp4rnyiejj36iuyoonzu35/lib64;;/usr/tce/packages/intel/intel-2025.2.0/compiler/2025.2/lib;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/intel-oneapi-compilers-2025.2.0-ngszoacyp2i5d5nawitlyuh37jswjtie/compiler/2025.2/lib;/usr/tce/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/intel-oneapi-compilers-2025.2.0/axom-develop-g2h45edliw4q2vgz7cfusag64p3ulopb/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/intel-oneapi-compilers-2025.2.0/axom-develop-g2h45edliw4q2vgz7cfusag64p3ulopb/lib64;;/usr/tce/packages/intel/intel-2025.2.0/compiler/2025.2/lib;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/intel-oneapi-compilers-2025.2.0-ngszoacyp2i5d5nawitlyuh37jswjtie/compiler/2025.2/lib;/usr/tce/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/intel-oneapi-compilers-2025.2.0/axom-develop-ctb4mtm5xxvp4rnyiejj36iuyoonzu35/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/intel-oneapi-compilers-2025.2.0/axom-develop-ctb4mtm5xxvp4rnyiejj36iuyoonzu35/lib64;;/usr/tce/packages/intel/intel-2025.2.0/compiler/2025.2/lib;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/intel-oneapi-compilers-2025.2.0-ngszoacyp2i5d5nawitlyuh37jswjtie/compiler/2025.2/lib;/usr/tce/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ # Compilers #------------------------------------------------------------------------------ -# Compiler Spec: intel-oneapi-compilers@2025.2.0/pure57vovckospvgxd2lr56fpsa636dr +# Compiler Spec: intel-oneapi-compilers@2025.2.0/4u7gc5dscq6xcei2akjdj7uefz56obcl #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/oneapi/icx" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/none-none/compiler-wrapper-1.1.0-pbd2ayub7z6lkuzpvgsnzfsycl3xbuy3/libexec/spack/oneapi/icx" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/oneapi/icpx" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/none-none/compiler-wrapper-1.1.0-pbd2ayub7z6lkuzpvgsnzfsycl3xbuy3/libexec/spack/oneapi/icpx" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/oneapi/ifx" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/none-none/compiler-wrapper-1.1.0-pbd2ayub7z6lkuzpvgsnzfsycl3xbuy3/libexec/spack/oneapi/ifx" CACHE PATH "") else() @@ -79,29 +79,29 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/intel-oneapi-compilers-2025.2.0" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/intel-oneapi-compilers-2025.2.0" CACHE PATH "") -set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-lqjx3vlbi5prcmle2gsgc7yopbglp3ab" CACHE PATH "") +set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.7-b5kuchct7dt5zox4ck7rtprtznejh2il" CACHE PATH "") -set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-fdbbv7fzmylpoctjgdai2kc4xfcz2vqt" CACHE PATH "") +set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-5wjdmt55f7nuftxsaxqvrb4zrl72dovf" CACHE PATH "") -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-npcozwxpczgzuya2z6iailvycmbbc6nd" CACHE PATH "") +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-wwgragi5vtbzo5wkjmjrthjjk3ggzs2l" CACHE PATH "") -set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-zhxj7u6hk266vklzloqcp3qlahtbta4l" CACHE PATH "") +set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-7x2tmsqn2zeo2tm74ih6ovlt3ayejfxo" CACHE PATH "") set(LUA_DIR "/usr" CACHE PATH "") -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-ppbrbxztecp4363lchwihhymqrhv75un" CACHE PATH "") +set(RAJA_DIR "${TPL_ROOT}/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-q24rhtgaxvk36dia7o3zisrq23r2icp6" CACHE PATH "") -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-tdpnmcmwvlz77qwdeupgufa6s5aswwp5" CACHE PATH "") +set(UMPIRE_DIR "${TPL_ROOT}/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-u6xviw2os347tdexix55hj5xgxebvtb4" CACHE PATH "") # OPENCASCADE not built -set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-gxozhembb6br5wkhuxetug25ndaodstk" CACHE PATH "") +set(ADIAK_DIR "${TPL_ROOT}/adiak-0.5.0-fcafug75o3ehttelhmvzcq6at3blpqfz" CACHE PATH "") -set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-bheefndoamg47jey4mbq2ofgflz7kvz4" CACHE PATH "") +set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-6znuaaeszc2faabgq55yrahxv5yqnmu6" CACHE PATH "") -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-k4dewbo7oilgt6zjhkze2hyqyc6xn3if" CACHE PATH "") +set(CAMP_DIR "${TPL_ROOT}/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-vhpjt4ws6affncnj6xweeazpltvii6nl" CACHE PATH "") # scr not built @@ -129,16 +129,16 @@ set(CPPCHECK_EXECUTABLE "${DEVTOOLS_ROOT}/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66x set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/kky5oskfhacihjb5gpy7qkeh6edlrtcb/doxygen-1.15.0/bin/doxygen" CACHE PATH "") -set(PY_NANOBIND_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/py-nanobind-2.7.0-iky6agz5lprxpnf2nffxwgqcgiagqiz5/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.12.0-kfwke5jj6qxsoayuruy7zvp6jb757x5r/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/none-none/py-pytest-9.0.3-gzmipids43fsxiidqzdglv4cbaxj26gk/lib/python3.13/site-packages" CACHE PATH "") -set(PY_NUMPY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/gcc-13.3.1/py-numpy-2.4.2-to6o5czwkczlh46d6gkubixw5bhzxakl/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NUMPY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/gcc-13.3.1/py-numpy-2.4.6-f2gs5srtddls5yewq6xbs7fyal24q3j6/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/py-pluggy-1.6.0-6zab4pck6v7johubbhalr2waowbwpjei/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/none-none/py-pluggy-1.6.0-7gqc6q7eser33fbaul2h4xxpqo67fphj/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/py-iniconfig-2.1.0-xwmbxuhk2oo3dcuizdp3z4hqbpqykkom/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/none-none/py-iniconfig-2.1.0-7flbc7ylpqkqn4swh7u5lojtj2ipn7bx/lib/python3.13/site-packages" CACHE PATH "") -set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-7mymbwx2ifuqlotydf6dx6hd6wmz4xhd/lib/python3.13/site-packages" CACHE PATH "") +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-qw72rhicazqo2tf4wu3emk4kxeaza3zv/lib/python3.13/site-packages" CACHE PATH "") diff --git a/host-configs/rzwhippet-toss_4_x86_64_ib-llvm@19.1.3.cmake b/host-configs/rzwhippet-toss_4_x86_64_ib-llvm@19.1.3.cmake index 1584af7a82..09b1bfa53d 100644 --- a/host-configs/rzwhippet-toss_4_x86_64_ib-llvm@19.1.3.cmake +++ b/host-configs/rzwhippet-toss_4_x86_64_ib-llvm@19.1.3.cmake @@ -4,28 +4,28 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.26.3/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/blt-0.7.1-ux6wzebtj6vtgnayf4gqv2rr62ulwwfc;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/c2c-1.8.0-wto5u3qsffjhbxirygr7kqqjlg55pyv6;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-iuatdlntm2zcg2vuygvsc7say6wfx353;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/conduit-0.9.5-vqo2x2n6mz2vs4eg7kynwmlpeolsuim5;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/mfem-4.9.0-jqqjlgxdiebyz7gblihglbgu2jbnqnqc;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/opencascade-7.8.1-pyde26mlsvvcvw75lxvoeshe6r6aobxe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/py-nanobind-2.7.0-zxodixu5egwgeorhpe3ciggwsv3ndfxb;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-ur2yappntfb4cptytvwwwtmsf7irqgxa;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/scr-3.0.1-l7heipi5ftfkbxqiytyxr4legakgjzq2;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/umpire-2025.12.0-l3l75r2uuai7rl3m4e7fpdw446ankcxp;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/adiak-0.4.0-do3kop54yldi4hz43usrtnx2zyhvwfsv;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/libunwind-1.8.3-ck74b2escwzf2xtpiafdvqwmat26uuoe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/hdf5-1.8.23-vpibouztpyzmgikg4dg2egwf2u53mmpl;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/parmetis-4.0.3-fs7gps55rp6znnpz3b4zkd35jjkfycfc;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/py-mpi4py-4.1.1-hrvj5zyy3xo2rbzvbz4ed7kjye7ssgmk;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/py-numpy-2.4.2-73lpwcid33wlekelrxoh24535lzexwwg;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/hypre-2.27.0-zef2jav5gijraygsr2tumdy32outkfxp;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/glx-1.4-4kahrsr5vciz372vpdvlmvxahspecwif;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/tcl-8.6.17-qjflnn6itwshm5i6ckchochc7t2cuw6w;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/axl-0.7.1-z4zopytuytxgn5wds3bnhqpqmfju54h7;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/dtcmp-1.1.5-b6g6n4hdju6ol5g2pcizzey7dthbgapt;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/er-0.3.0-eabe4roqqpiyjenasayt6pupercipzak;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/libyogrt-1.35-5geyvlylxzqsc6g6s6xiq6aakvy2qiu7;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/spath-0.2.0-6sxvboh2q5kmo7q5tcchgao2dgx5g72q;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-omiy5m3jikdlf6gicad34w5vkfz7huex;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/fmt-11.0.2-ekc2idnbqipl2pqb4nugxoa7sj7aigpo;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/metis-5.1.0-7gsm27yycsippuh2l4fwei4dtvevbqgh;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/lwgrp-1.0.6-u4qqk5djarvhyvk3fg7ls4r6ltx4anmr;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/redset-0.2.0-5b3fl74qgvvfturxdgz44rurhjdm4en4;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/shuffile-0.2.0-tpvuy3dcxpakepz77nxygcb7cqjwtwmv;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/gcc-runtime-13.3.1-xlvjd7vcdngtnvtxcdyqwtquvenekynf;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/rankstr-0.2.0-njavwb2qlbesemqc6ac7hjeezjxtydv7;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/kvtree-1.3.0-l3jatuuigf2z2swzxrdzkbrxhdinaqfz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.26.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66xzj4wm5kfqxyc4vgox7gb3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/doxygen-1.15.0;/usr/tce/packages/gcc/gcc-13.3.1;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-clang-19.1.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/gcc-13.3.1/blt-0.7.2-kgttmwj56kwpge3oveotwgx7amhmc54b;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/llvm-19.1.3/c2c-1.8.0-cc4zkpkb3tvtflcodt4bhjiexgbd5pf3;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/llvm-19.1.3/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-xltandsuzmuxk4rlilbiyfiichj2tzq5;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/llvm-19.1.3/conduit-0.9.7-bcuudf26kmnocne4wa2pq4csunshwyqz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/llvm-19.1.3/mfem-4.9.0-ioqmhevjhvecwiwpypcnxp6qdzfhyuz4;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/llvm-19.1.3/opencascade-7.9.1-syvfdepri7ee4rlem4ygy77vni3bqvez;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/llvm-19.1.3/py-nanobind-2.12.0-jvqsaw246m2x5v3cw25qpwmgcjhbcxok;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/none-none/py-pytest-9.0.3-gzmipids43fsxiidqzdglv4cbaxj26gk;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/llvm-19.1.3/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-wjftha5hizxnpzcw35pnjv5st2xdpn3d;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/llvm-19.1.3/scr-3.0.1-7hkagbjyzitmsuf3icffzr75amvnms2f;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/llvm-19.1.3/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-ronmxvxkvy52nshco4uw5ldkncp2l26x;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/llvm-19.1.3/adiak-0.5.0-bn7vvphqubchyzrll3ktmsihnzdbbb6b;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/llvm-19.1.3/libunwind-1.8.3-3j43eldxfwjlrs6gnf33vpfpox5tbqjr;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/llvm-19.1.3/hdf5-1.8.23-pqeunv22a7jyh66n7b4mrbvccatr32eu;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/llvm-19.1.3/parmetis-4.0.3-tc7yzrxqscciment55kdr5uzym5ijilj;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/llvm-19.1.3/py-mpi4py-4.1.1-rifw3jmftcx6ezx2qnb4u64gxpcptcl5;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/llvm-19.1.3/py-numpy-2.4.6-ygvifcqpoaj23qtkbllsgdgge62vtlzv;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/llvm-19.1.3/hypre-3.1.0-ee4mrker7mlj5t2ztiztrzfz6m4kcjsc;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/none-none/glx-1.4-dx6c77ydg6r34fqvr54ams4342vbwugg;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/llvm-19.1.3/tcl-8.6.17-rf4e2svjggt6u3mxw4jjqia34736tfft;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/llvm-19.1.3/axl-0.7.1-lnonzejh7rsqkod2y4mbwlnzeql5hmnt;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/llvm-19.1.3/dtcmp-1.1.5-ephq3djkfkny3jlpyo25bwkw6ko3zigb;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/llvm-19.1.3/er-0.3.0-4m4r25nk7lwhvt7tf437mykvodgyt5wq;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/llvm-19.1.3/libyogrt-1.35-zindsc3anw7vrfxm55lwgamzm7buinkx;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/llvm-19.1.3/spath-0.2.0-xgbpz3szq3rblgi3zgty2qcuneciuful;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/llvm-19.1.3/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-5lxncn6hshlgiweeldarjcahegtq6gkv;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/llvm-19.1.3/fmt-12.1.0-ew4w4he3l62ai5pbto4li23fao2wa2u6;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/llvm-19.1.3/metis-5.1.0-o23lru3echahpaspbjkpdjfudtgjg6vj;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/llvm-19.1.3/lwgrp-1.0.6-ujfzcbrrl6gihaqtgzazwo3cwl2fh5pd;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/llvm-19.1.3/redset-0.2.0-4hhtwmzrib62y4qdlq6ogi5ia34m27vn;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/llvm-19.1.3/shuffile-0.2.0-ued3q4imojriofxxdxpk2zvinozvqcq5;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/none-none/gcc-runtime-13.3.1-idl2e7ehceec4tq2xcci4n27sch4dkvu;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/llvm-19.1.3/rankstr-0.2.0-egxh5fy7htezki3xoelx2a5iajgtusss;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/llvm-19.1.3/kvtree-1.3.0-43hhkxdkzfr6egqdrvdtht5p7amthsnl;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/none-none/compiler-wrapper-1.1.0-pbd2ayub7z6lkuzpvgsnzfsycl3xbuy3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.26.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66xzj4wm5kfqxyc4vgox7gb3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/doxygen-1.15.0;/usr/tce/packages/gcc/gcc-13.3.1;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-clang-19.1.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/axom-develop-oab3m23sasflxabp3rqhmbk2cxsnxv4t/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/axom-develop-oab3m23sasflxabp3rqhmbk2cxsnxv4t/lib64;;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/llvm-19.1.3-gy2lu5xbi4csr2k47emlajzfs5mlsd4g/lib/x86_64-unknown-linux-gnu;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13;/usr/tce/packages/clang/clang-19.1.3/lib;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/llvm-19.1.3/axom-develop-3s3ethp7dp5gx2ol223qk6yp67ao75gh/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/llvm-19.1.3/axom-develop-3s3ethp7dp5gx2ol223qk6yp67ao75gh/lib64;;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/llvm-19.1.3-gy2lu5xbi4csr2k47emlajzfs5mlsd4g/lib/x86_64-unknown-linux-gnu;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13;/usr/tce/packages/clang/clang-19.1.3/lib;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/axom-develop-oab3m23sasflxabp3rqhmbk2cxsnxv4t/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3/axom-develop-oab3m23sasflxabp3rqhmbk2cxsnxv4t/lib64;;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/llvm-19.1.3-gy2lu5xbi4csr2k47emlajzfs5mlsd4g/lib/x86_64-unknown-linux-gnu;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13;/usr/tce/packages/clang/clang-19.1.3/lib;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/llvm-19.1.3/axom-develop-3s3ethp7dp5gx2ol223qk6yp67ao75gh/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/llvm-19.1.3/axom-develop-3s3ethp7dp5gx2ol223qk6yp67ao75gh/lib64;;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/llvm-19.1.3-gy2lu5xbi4csr2k47emlajzfs5mlsd4g/lib/x86_64-unknown-linux-gnu;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13;/usr/tce/packages/clang/clang-19.1.3/lib;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ # Compilers #------------------------------------------------------------------------------ -# Compiler Spec: llvm@19.1.3/m2tcgbxfcdjwdmyrkqwamlljh4qeiie2 +# Compiler Spec: llvm@19.1.3/pa7jv5ejrkrucsgwtv4kde2cazhq3v6i #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/clang/clang" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/none-none/compiler-wrapper-1.1.0-pbd2ayub7z6lkuzpvgsnzfsycl3xbuy3/libexec/spack/clang/clang" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/clang/clang++" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/none-none/compiler-wrapper-1.1.0-pbd2ayub7z6lkuzpvgsnzfsycl3xbuy3/libexec/spack/clang/clang++" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/gfortran" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/none-none/compiler-wrapper-1.1.0-pbd2ayub7z6lkuzpvgsnzfsycl3xbuy3/libexec/spack/gcc/gfortran" CACHE PATH "") else() @@ -79,51 +79,51 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/llvm-19.1.3" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/llvm-19.1.3" CACHE PATH "") -set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-vqo2x2n6mz2vs4eg7kynwmlpeolsuim5" CACHE PATH "") +set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.7-bcuudf26kmnocne4wa2pq4csunshwyqz" CACHE PATH "") -set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-wto5u3qsffjhbxirygr7kqqjlg55pyv6" CACHE PATH "") +set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-cc4zkpkb3tvtflcodt4bhjiexgbd5pf3" CACHE PATH "") -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-jqqjlgxdiebyz7gblihglbgu2jbnqnqc" CACHE PATH "") +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-ioqmhevjhvecwiwpypcnxp6qdzfhyuz4" CACHE PATH "") -set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-vpibouztpyzmgikg4dg2egwf2u53mmpl" CACHE PATH "") +set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-pqeunv22a7jyh66n7b4mrbvccatr32eu" CACHE PATH "") set(LUA_DIR "/usr" CACHE PATH "") -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-ur2yappntfb4cptytvwwwtmsf7irqgxa" CACHE PATH "") +set(RAJA_DIR "${TPL_ROOT}/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-wjftha5hizxnpzcw35pnjv5st2xdpn3d" CACHE PATH "") -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-l3l75r2uuai7rl3m4e7fpdw446ankcxp" CACHE PATH "") +set(UMPIRE_DIR "${TPL_ROOT}/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-ronmxvxkvy52nshco4uw5ldkncp2l26x" CACHE PATH "") -set(OPENCASCADE_DIR "${TPL_ROOT}/opencascade-7.8.1-pyde26mlsvvcvw75lxvoeshe6r6aobxe" CACHE PATH "") +set(OPENCASCADE_DIR "${TPL_ROOT}/opencascade-7.9.1-syvfdepri7ee4rlem4ygy77vni3bqvez" CACHE PATH "") -set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-do3kop54yldi4hz43usrtnx2zyhvwfsv" CACHE PATH "") +set(ADIAK_DIR "${TPL_ROOT}/adiak-0.5.0-bn7vvphqubchyzrll3ktmsihnzdbbb6b" CACHE PATH "") -set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-iuatdlntm2zcg2vuygvsc7say6wfx353" CACHE PATH "") +set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-xltandsuzmuxk4rlilbiyfiichj2tzq5" CACHE PATH "") -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-omiy5m3jikdlf6gicad34w5vkfz7huex" CACHE PATH "") +set(CAMP_DIR "${TPL_ROOT}/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-5lxncn6hshlgiweeldarjcahegtq6gkv" CACHE PATH "") -set(SCR_DIR "${TPL_ROOT}/scr-3.0.1-l7heipi5ftfkbxqiytyxr4legakgjzq2" CACHE PATH "") +set(SCR_DIR "${TPL_ROOT}/scr-3.0.1-7hkagbjyzitmsuf3icffzr75amvnms2f" CACHE PATH "") -set(KVTREE_DIR "${TPL_ROOT}/kvtree-1.3.0-l3jatuuigf2z2swzxrdzkbrxhdinaqfz" CACHE PATH "") +set(KVTREE_DIR "${TPL_ROOT}/kvtree-1.3.0-43hhkxdkzfr6egqdrvdtht5p7amthsnl" CACHE PATH "") -set(DTCMP_DIR "${TPL_ROOT}/dtcmp-1.1.5-b6g6n4hdju6ol5g2pcizzey7dthbgapt" CACHE PATH "") +set(DTCMP_DIR "${TPL_ROOT}/dtcmp-1.1.5-ephq3djkfkny3jlpyo25bwkw6ko3zigb" CACHE PATH "") -set(SPATH_DIR "${TPL_ROOT}/spath-0.2.0-6sxvboh2q5kmo7q5tcchgao2dgx5g72q" CACHE PATH "") +set(SPATH_DIR "${TPL_ROOT}/spath-0.2.0-xgbpz3szq3rblgi3zgty2qcuneciuful" CACHE PATH "") -set(AXL_DIR "${TPL_ROOT}/axl-0.7.1-z4zopytuytxgn5wds3bnhqpqmfju54h7" CACHE PATH "") +set(AXL_DIR "${TPL_ROOT}/axl-0.7.1-lnonzejh7rsqkod2y4mbwlnzeql5hmnt" CACHE PATH "") -set(LWGRP_DIR "${TPL_ROOT}/lwgrp-1.0.6-u4qqk5djarvhyvk3fg7ls4r6ltx4anmr" CACHE PATH "") +set(LWGRP_DIR "${TPL_ROOT}/lwgrp-1.0.6-ujfzcbrrl6gihaqtgzazwo3cwl2fh5pd" CACHE PATH "") -set(ER_DIR "${TPL_ROOT}/er-0.3.0-eabe4roqqpiyjenasayt6pupercipzak" CACHE PATH "") +set(ER_DIR "${TPL_ROOT}/er-0.3.0-4m4r25nk7lwhvt7tf437mykvodgyt5wq" CACHE PATH "") -set(RANKSTR_DIR "${TPL_ROOT}/rankstr-0.2.0-njavwb2qlbesemqc6ac7hjeezjxtydv7" CACHE PATH "") +set(RANKSTR_DIR "${TPL_ROOT}/rankstr-0.2.0-egxh5fy7htezki3xoelx2a5iajgtusss" CACHE PATH "") -set(REDSET_DIR "${TPL_ROOT}/redset-0.2.0-5b3fl74qgvvfturxdgz44rurhjdm4en4" CACHE PATH "") +set(REDSET_DIR "${TPL_ROOT}/redset-0.2.0-4hhtwmzrib62y4qdlq6ogi5ia34m27vn" CACHE PATH "") -set(SHUFFILE_DIR "${TPL_ROOT}/shuffile-0.2.0-tpvuy3dcxpakepz77nxygcb7cqjwtwmv" CACHE PATH "") +set(SHUFFILE_DIR "${TPL_ROOT}/shuffile-0.2.0-ued3q4imojriofxxdxpk2zvinozvqcq5" CACHE PATH "") -set(LIBYOGRT_DIR "${TPL_ROOT}/libyogrt-1.35-5geyvlylxzqsc6g6s6xiq6aakvy2qiu7" CACHE PATH "") +set(LIBYOGRT_DIR "${TPL_ROOT}/libyogrt-1.35-zindsc3anw7vrfxm55lwgamzm7buinkx" CACHE PATH "") #------------------------------------------------------------------------------ # Devtools & Python @@ -149,16 +149,16 @@ set(CPPCHECK_EXECUTABLE "${DEVTOOLS_ROOT}/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66x set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/kky5oskfhacihjb5gpy7qkeh6edlrtcb/doxygen-1.15.0/bin/doxygen" CACHE PATH "") -set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-zxodixu5egwgeorhpe3ciggwsv3ndfxb/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.12.0-jvqsaw246m2x5v3cw25qpwmgcjhbcxok/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/none-none/py-pytest-9.0.3-gzmipids43fsxiidqzdglv4cbaxj26gk/lib/python3.13/site-packages" CACHE PATH "") -set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-73lpwcid33wlekelrxoh24535lzexwwg/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.6-ygvifcqpoaj23qtkbllsgdgge62vtlzv/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/py-pluggy-1.6.0-6zab4pck6v7johubbhalr2waowbwpjei/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/none-none/py-pluggy-1.6.0-7gqc6q7eser33fbaul2h4xxpqo67fphj/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_10_58/none-none/py-iniconfig-2.1.0-xwmbxuhk2oo3dcuizdp3z4hqbpqykkom/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_09_56_55/none-none/py-iniconfig-2.1.0-7flbc7ylpqkqn4swh7u5lojtj2ipn7bx/lib/python3.13/site-packages" CACHE PATH "") -set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-hrvj5zyy3xo2rbzvbz4ed7kjye7ssgmk/lib/python3.13/site-packages" CACHE PATH "") +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-rifw3jmftcx6ezx2qnb4u64gxpcptcl5/lib/python3.13/site-packages" CACHE PATH "") From 87253c67b5775c582d4c37b22b7fead367d9f436 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Tue, 7 Jul 2026 16:18:21 -0700 Subject: [PATCH 672/986] Update windows version and hashes --- .github/workflows/test_windows_tpls.yml | 2 +- scripts/vcpkg_ports/blt/portfile.cmake | 4 ++-- scripts/vcpkg_ports/blt/vcpkg.json | 2 +- scripts/vcpkg_ports/camp/portfile.cmake | 4 ++-- scripts/vcpkg_ports/camp/vcpkg.json | 2 +- scripts/vcpkg_ports/conduit/portfile.cmake | 4 ++-- scripts/vcpkg_ports/conduit/vcpkg.json | 2 +- scripts/vcpkg_ports/opencascade/portfile.cmake | 2 +- scripts/vcpkg_ports/opencascade/vcpkg.json | 2 +- scripts/vcpkg_ports/raja/portfile.cmake | 4 ++-- scripts/vcpkg_ports/raja/vcpkg.json | 2 +- scripts/vcpkg_ports/umpire/portfile.cmake | 4 ++-- scripts/vcpkg_ports/umpire/vcpkg.json | 2 +- 13 files changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/test_windows_tpls.yml b/.github/workflows/test_windows_tpls.yml index b4ad0c2d41..0b7a425006 100644 --- a/.github/workflows/test_windows_tpls.yml +++ b/.github/workflows/test_windows_tpls.yml @@ -13,7 +13,7 @@ jobs: run_uberenv: name: Runs ${{ matrix.triplet }} ${{ matrix.cfg }} uberenv with vcpkg # The type of runner that the job will run on - runs-on: windows-latest + runs-on: windows-2022 strategy: fail-fast: false matrix: diff --git a/scripts/vcpkg_ports/blt/portfile.cmake b/scripts/vcpkg_ports/blt/portfile.cmake index 7d90525801..7d659f5931 100644 --- a/scripts/vcpkg_ports/blt/portfile.cmake +++ b/scripts/vcpkg_ports/blt/portfile.cmake @@ -1,8 +1,8 @@ vcpkg_from_github( OUT_SOURCE_PATH SOURCE_PATH REPO llnl/blt - REF ccd6d31c7072ff984c7c8b723fc51f69e677ca72 - SHA512 dc3ab1b01293772a048d1531f6953e82cb9b38a162153c2002f89f1dfd07c0cf087e40153024ceeee2f5040ae2611ed8936292ce0bec568b068313ec2dfd853c + REF v0.7.2 + SHA512 4e73eee3dfdf552f5ea183c0093a144b70335a6d086aac8574457086e005b5a96e23c82122e1a72ffcf6647c48fb98bbb404a059b65f80c3ef4c38bb90b544dc HEAD_REF develop ) diff --git a/scripts/vcpkg_ports/blt/vcpkg.json b/scripts/vcpkg_ports/blt/vcpkg.json index 6bdd03eb47..319a07f7c3 100644 --- a/scripts/vcpkg_ports/blt/vcpkg.json +++ b/scripts/vcpkg_ports/blt/vcpkg.json @@ -1,6 +1,6 @@ { "name": "blt", - "version": "0.7.1", + "version": "0.7.2", "homepage": "https://github.com/llnl/blt", "description": "A streamlined CMake build system foundation for developing HPC software", "supports": "!uwp" diff --git a/scripts/vcpkg_ports/camp/portfile.cmake b/scripts/vcpkg_ports/camp/portfile.cmake index 99b6e74f95..1874d1a150 100644 --- a/scripts/vcpkg_ports/camp/portfile.cmake +++ b/scripts/vcpkg_ports/camp/portfile.cmake @@ -1,8 +1,8 @@ vcpkg_from_github( OUT_SOURCE_PATH SOURCE_PATH REPO llnl/camp - REF v2025.12.0 - SHA512 caff00944ad27bbd819f0ebf0bec8ffbe4579ab9e39dc8da97a52954293bb4450605bc624dc0d5c7ab0fc1c445088c34a1ec10352499d5e4ff90fd126e809860 + REF e75ab64c029aa27c80593715cb2a3ccad7453c8c + SHA512 bdb5fca01c117f9c5ce6f070dd6d0e30d25cf25ede3818194e500870dd892b05ff925062c77715f8a57a8d7b6e9a5404efb047d10ef75b9d380c43b3e4d4e2d7 ) set(_is_shared TRUE) diff --git a/scripts/vcpkg_ports/camp/vcpkg.json b/scripts/vcpkg_ports/camp/vcpkg.json index 00906fa15c..a9942b569c 100644 --- a/scripts/vcpkg_ports/camp/vcpkg.json +++ b/scripts/vcpkg_ports/camp/vcpkg.json @@ -1,6 +1,6 @@ { "name": "camp", - "version-string": "2025.12.0", + "version-string": "main-commit", "homepage": "https://github.com/llnl/camp", "description": "Compiler agnostic metaprogramming library", "dependencies": [ diff --git a/scripts/vcpkg_ports/conduit/portfile.cmake b/scripts/vcpkg_ports/conduit/portfile.cmake index b34467ece9..13974f3a6d 100644 --- a/scripts/vcpkg_ports/conduit/portfile.cmake +++ b/scripts/vcpkg_ports/conduit/portfile.cmake @@ -1,8 +1,8 @@ vcpkg_from_github( OUT_SOURCE_PATH SOURCE_PATH REPO llnl/conduit - REF v0.9.5 - SHA512 c0b8e92e6eb3c42a2efc8656c488b62c2fbacbe015df7820d8eae8752deea8271db65f2d129df251a7eb97402352ff2260e3ac8e38a3c37c8606ab1096511757 + REF v0.9.7 + SHA512 f36e5644e1a86660f32dd8651b88274ff4a77b05cc69e0b42976720f73fd6562a08a4bc3fd6f594e422143738f50240723d69b7612fa91d22030302165879acb HEAD_REF develop PATCHES "./setup_deps_vcpkg_triplet.patch" diff --git a/scripts/vcpkg_ports/conduit/vcpkg.json b/scripts/vcpkg_ports/conduit/vcpkg.json index 16ee72f6c3..1eaf221f1b 100644 --- a/scripts/vcpkg_ports/conduit/vcpkg.json +++ b/scripts/vcpkg_ports/conduit/vcpkg.json @@ -1,6 +1,6 @@ { "name": "conduit", - "version": "0.9.5", + "version": "0.9.7", "homepage": "https://github.com/llnl/conduit", "description": "Simplified Data Exchange for HPC Simulations", "dependencies": [ diff --git a/scripts/vcpkg_ports/opencascade/portfile.cmake b/scripts/vcpkg_ports/opencascade/portfile.cmake index aa4b47d200..2750016b21 100644 --- a/scripts/vcpkg_ports/opencascade/portfile.cmake +++ b/scripts/vcpkg_ports/opencascade/portfile.cmake @@ -3,7 +3,7 @@ vcpkg_from_github( OUT_SOURCE_PATH SOURCE_PATH REPO Open-Cascade-SAS/OCCT REF "${VERSION_STR}" - SHA512 af176cbd105c49949282c16bb6e30f69167bc6c00a50e0ae69aea555815d47ac3c4540c233e596c5add7cb846c2b33d7be267d8e02472286e758b662b4a652ab + SHA512 4ec271ec8db5f0d6f77ea5c0633b40334c796421806a344c568fb8d9ed942fec63f8dfcc65ab9f65e0446d5cd7a49beede5ac693421971b350e828ab1a19d773 HEAD_REF master PATCHES drop-bin-letter-d.patch diff --git a/scripts/vcpkg_ports/opencascade/vcpkg.json b/scripts/vcpkg_ports/opencascade/vcpkg.json index b97447263d..95d7d544d5 100644 --- a/scripts/vcpkg_ports/opencascade/vcpkg.json +++ b/scripts/vcpkg_ports/opencascade/vcpkg.json @@ -1,6 +1,6 @@ { "name": "opencascade", - "version": "7.8.0", + "version": "7.9.1", "description": "Open Cascade Technology (OCCT) is an open-source software development platform for 3D CAD, CAM, CAE.", "homepage": "https://github.com/Open-Cascade-SAS/OCCT", "license": "LGPL-2.1-only", diff --git a/scripts/vcpkg_ports/raja/portfile.cmake b/scripts/vcpkg_ports/raja/portfile.cmake index 9747d00b1f..d5bd55bf29 100644 --- a/scripts/vcpkg_ports/raja/portfile.cmake +++ b/scripts/vcpkg_ports/raja/portfile.cmake @@ -1,8 +1,8 @@ vcpkg_from_github( OUT_SOURCE_PATH SOURCE_PATH REPO llnl/raja - REF v2025.12.1 - SHA512 fca1d5d336cb552bbee1f33da69734c3c0dd53632e04db3ae76f08e1160801676e085c41b52fa1d1e7bd91b8b880338c30396a03c13dd32f6941c327c52cf5e8 + REF 6e4fe62d810711a0af9020d4e94c6c41c9a6117b + SHA512 1268e0bd175c59a5a1fe03d311ed66fc6a473aab0570b75ddd52693c05de9fffa4c4293dddb98d64b3cced6fb8dac31c97b5b6ad3ecd233049012b87dab50efd ) set(_is_shared TRUE) diff --git a/scripts/vcpkg_ports/raja/vcpkg.json b/scripts/vcpkg_ports/raja/vcpkg.json index 778aa789aa..ec7a035a54 100644 --- a/scripts/vcpkg_ports/raja/vcpkg.json +++ b/scripts/vcpkg_ports/raja/vcpkg.json @@ -1,6 +1,6 @@ { "name": "raja", - "version-string": "2025.12.0", + "version-string": "develop-commit", "homepage": "https://github.com/llnl/raja", "description": "RAJA Performance Portability Layer (C++)", "dependencies": [ diff --git a/scripts/vcpkg_ports/umpire/portfile.cmake b/scripts/vcpkg_ports/umpire/portfile.cmake index 7cf83ab670..2800c15337 100644 --- a/scripts/vcpkg_ports/umpire/portfile.cmake +++ b/scripts/vcpkg_ports/umpire/portfile.cmake @@ -1,8 +1,8 @@ vcpkg_from_github( OUT_SOURCE_PATH SOURCE_PATH REPO llnl/umpire - REF v2025.12.0 - SHA512 3dbb321b3b79ae60ecad4ef81ee40e98e41b370da77dfbd9bd037b118d05da9ea67a8aabf82f60d4302ab40897a0f58b75ad910cce3ec831984b6bd47530760b + REF f544027ef118133f1ecbc26e64c04ea77c3eb5ae + SHA512 d6b8914b0b3a153a9eaa1a02f160f20d0c4c8748f0efd08f889dab3e7a221b46a0fd1a8798f1ae054cf2fb05560d63d6c4d95059e47cdc6d40f5a8959a35c8d3 HEAD_REF develop ) diff --git a/scripts/vcpkg_ports/umpire/vcpkg.json b/scripts/vcpkg_ports/umpire/vcpkg.json index 7441f79d8d..e8b02e5b55 100644 --- a/scripts/vcpkg_ports/umpire/vcpkg.json +++ b/scripts/vcpkg_ports/umpire/vcpkg.json @@ -1,6 +1,6 @@ { "name": "umpire", - "version-string": "2025.12.0", + "version-string": "develop-commit", "homepage": "https://github.com/llnl/umpire", "description": "An application-focused API for memory management on NUMA and GPU architectures", "dependencies": [ From 1ccab674a9efa57dce7a72027d4a2e9ee0f058f0 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Wed, 8 Jul 2026 08:11:23 -0700 Subject: [PATCH 673/986] vcpkg - umpire needs C++20 --- scripts/vcpkg_ports/umpire/portfile.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/vcpkg_ports/umpire/portfile.cmake b/scripts/vcpkg_ports/umpire/portfile.cmake index 2800c15337..c04a610e0d 100644 --- a/scripts/vcpkg_ports/umpire/portfile.cmake +++ b/scripts/vcpkg_ports/umpire/portfile.cmake @@ -34,7 +34,7 @@ vcpkg_configure_cmake( -DENABLE_TESTS:BOOL=OFF -DENABLE_BENCHMARKS:BOOL=OFF -DUMPIRE_ENABLE_FILESYSTEM:BOOL=ON - -DBLT_CXX_STD:STRING=c++17 + -DBLT_CXX_STD:STRING=c++20 -DBLT_OPENMP_LINK_FLAGS:STRING=" " -DUMPIRE_ENABLE_TOOLS:BOOL=OFF -DUMPIRE_ENABLE_TESTS:BOOL=OFF From 7e7f1257e08ba4e7ba920f5f87d327b55e883d52 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Sun, 14 Jun 2026 17:02:14 -0700 Subject: [PATCH 674/986] Fix for sparsehash with msvc and cuda --- src/thirdparty/axom/sparsehash/internal/hashtable-common.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/thirdparty/axom/sparsehash/internal/hashtable-common.h b/src/thirdparty/axom/sparsehash/internal/hashtable-common.h index 3e0d483208..14efbe8daa 100644 --- a/src/thirdparty/axom/sparsehash/internal/hashtable-common.h +++ b/src/thirdparty/axom/sparsehash/internal/hashtable-common.h @@ -50,8 +50,13 @@ _START_GOOGLE_NAMESPACE_ template struct SparsehashCompileAssert { }; +#if defined(_MSC_VER) +#define SPARSEHASH_COMPILE_ASSERT(expr, msg) \ + typedef SparsehashCompileAssert<(bool(expr))> msg[bool(expr) ? 1 : -1] +#else #define SPARSEHASH_COMPILE_ASSERT(expr, msg) \ __attribute__((unused)) typedef SparsehashCompileAssert<(bool(expr))> msg[bool(expr) ? 1 : -1] +#endif namespace sparsehash_internal { From 993e34fa4946af4ce08ff9e502e3a6fa124c82d6 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Thu, 9 Jul 2026 15:24:45 -0700 Subject: [PATCH 675/986] Update CZ host-configs - remove older HIP compilers --- .../dane-toss_4_x86_64_ib-gcc@13.3.1.cmake | 68 +++---- ...4_ib-intel-oneapi-compilers@2025.2.0.cmake | 46 ++--- .../dane-toss_4_x86_64_ib-llvm@19.1.3.cmake | 70 +++---- ...rix-toss_4_x86_64_ib-gcc@13.3.1_cuda.cmake | 50 ++--- ...ix-toss_4_x86_64_ib-llvm@19.1.3_cuda.cmake | 50 ++--- ...toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake | 169 ----------------- ...toss_4_x86_64_ib_cray-cce@21.0.0_hip.cmake | 171 ++++++++++++++++++ ...x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake | 163 ----------------- ...x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake | 56 +++--- ...x86_64_ib_cray-llvm-amdgpu@7.2.1_hip.cmake | 169 +++++++++++++++++ ...toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake | 169 ----------------- ...toss_4_x86_64_ib_cray-cce@21.0.0_hip.cmake | 171 ++++++++++++++++++ ...x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake | 163 ----------------- ...x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake | 56 +++--- ...x86_64_ib_cray-llvm-amdgpu@7.2.1_hip.cmake | 169 +++++++++++++++++ 15 files changed, 884 insertions(+), 856 deletions(-) delete mode 100644 host-configs/tioga-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake create mode 100644 host-configs/tioga-toss_4_x86_64_ib_cray-cce@21.0.0_hip.cmake delete mode 100644 host-configs/tioga-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake create mode 100644 host-configs/tioga-toss_4_x86_64_ib_cray-llvm-amdgpu@7.2.1_hip.cmake delete mode 100644 host-configs/tuolumne-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake create mode 100644 host-configs/tuolumne-toss_4_x86_64_ib_cray-cce@21.0.0_hip.cmake delete mode 100644 host-configs/tuolumne-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake create mode 100644 host-configs/tuolumne-toss_4_x86_64_ib_cray-llvm-amdgpu@7.2.1_hip.cmake diff --git a/host-configs/dane-toss_4_x86_64_ib-gcc@13.3.1.cmake b/host-configs/dane-toss_4_x86_64_ib-gcc@13.3.1.cmake index 1437c26d2d..80d4ba9e33 100644 --- a/host-configs/dane-toss_4_x86_64_ib-gcc@13.3.1.cmake +++ b/host-configs/dane-toss_4_x86_64_ib-gcc@13.3.1.cmake @@ -4,28 +4,28 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.26.3/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/blt-0.7.1-lj5bghgkodlzmfs75wimnoxuui2quc3a;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/c2c-1.8.0-tyixdhhwshu35d2vuh6gw5bk3b3k7nqz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-bzioyf6catu3cjwaz4cplt7ubtvavgbl;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/conduit-0.9.5-vlm6nb2rwwmvnxtnxw5p3o27rzsap6vt;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/mfem-4.9.0-xodkornev7gcvmi3idsoxtdsnlph4rqv;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/py-nanobind-2.7.0-e42etfacct5pxga65kryysakw52u4c63;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-o23ptcxtve2rphukiakbezevsssiqqmq;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/scr-3.0.1-ov4mwvpcd75glzu2lfuivq3oibr4vvpd;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/umpire-2025.12.0-drla577bebgu5wx6q7y37qxedmgmpcgi;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/adiak-0.4.0-nqiuo5ltc24jpawzi6mlp3xtmngcopiu;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/libunwind-1.8.3-a7zvz3euhmbhk4as6h6j66ytabzwztxb;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/hdf5-1.8.23-zyfoez4ztydoqfeubcamjypyhprjpuca;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/parmetis-4.0.3-sxnyn5chjyiyowzk32dsynslufrofyho;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/py-mpi4py-4.1.1-l3h5jmmg773bd5oer7makte3ln6rn7xv;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/py-numpy-2.4.2-to6o5czwkczlh46d6gkubixw5bhzxakl;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/hypre-2.27.0-qvi4rtn4tbrduxkdpfugf6f5lkzirnel;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/axl-0.7.1-ljgukulclybf6cdbcj7u3w7o63v43zcg;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/dtcmp-1.1.5-taj5rsqbrki5hk2fg5pxkg6xsz5tfjxk;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/er-0.3.0-ozpsif2qsi7or56avv6fxwn3at2hrhli;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/libyogrt-1.35-y4nvgwhpokw2x6lekbubcmxv5wweidg4;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/spath-0.2.0-5zx5r6jnfiqbfh2hfkzvuvqmam6j67dt;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-kjufdwlliz23qzwoy2xygn3w6dsq6tec;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/fmt-11.0.2-5wsybhdkos4xu5i7thboag6245lzc6jj;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/metis-5.1.0-3qhomb7zt2n7nh46kmc2egkzmcd3tafm;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/lwgrp-1.0.6-e2smb3vzymlqmy35nuipgi6cv6o2ezyz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/redset-0.2.0-itxhprv2jiz4uw4oa42jribcmnc6z2xr;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/shuffile-0.2.0-jpcg5fljyjaf3sjmnasiiv4brhw47vcc;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/rankstr-0.2.0-dpmjoa5upfcwzpbvxmhrbgupiqtu4uoz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/kvtree-1.3.0-3tlf6x5raibwnal2uo2l7feyjrqmkrhd;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/gcc-runtime-13.3.1-xlvjd7vcdngtnvtxcdyqwtquvenekynf;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.26.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66xzj4wm5kfqxyc4vgox7gb3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/doxygen-1.15.0;/usr/tce/packages/gcc/gcc-13.3.1;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-gcc-13.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_07_45/gcc-13.3.1/blt-0.7.2-kgttmwj56kwpge3oveotwgx7amhmc54b;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_07_45/gcc-13.3.1/c2c-1.8.0-ilhafup6lemuluscuhe2wcw6dhqwukac;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_07_45/gcc-13.3.1/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-56wmby3kxz4tfujjmxqkvklthqkbmyyn;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_07_45/gcc-13.3.1/conduit-0.9.7-cj7f7ue7tuh5za3vyckhvazoaa5dirlz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_07_45/gcc-13.3.1/mfem-4.9.0-7d7eeipuqb2muks5bczjac63jcokn3iz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_07_45/gcc-13.3.1/py-nanobind-2.12.0-2slokzflsbdyymtlubukr5uv352aaqol;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_07_45/none-none/py-pytest-9.0.3-gzmipids43fsxiidqzdglv4cbaxj26gk;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_07_45/gcc-13.3.1/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-jmywz4ts2sygkyau5dafh6ikwnbjhtye;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_07_45/gcc-13.3.1/scr-3.0.1-basfrtnpqf4c53b4rje6jsvep5zsnrwe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_07_45/gcc-13.3.1/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-vlfk7hrrnglk46jwsd7tfpgkcvqed4ox;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_07_45/gcc-13.3.1/adiak-0.5.0-rv7mu2xwq332xz2xqgfjtond4hvjlcvi;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_07_45/gcc-13.3.1/libunwind-1.8.3-3ltfsqv444quhz3wviidadz2x57mcmfd;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_07_45/gcc-13.3.1/hdf5-1.8.23-rephuaeqmzpcjiazcflzeswdvsbwuvd2;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_07_45/gcc-13.3.1/parmetis-4.0.3-ykjugf2p3cjircoep575ra3cn4we724b;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_07_45/gcc-13.3.1/py-mpi4py-4.1.1-susobssvjtqo5inxlvwts2s5qacifmqy;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_07_45/gcc-13.3.1/py-numpy-2.4.6-owxvcv322niqkkfckectzayaxxwre7f4;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_07_45/gcc-13.3.1/hypre-3.1.0-5zpehk5up5jposhv6pkpcemzl57l7o2k;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_07_45/gcc-13.3.1/axl-0.7.1-r3e4275my4pohksumwdh3alreqir3l5d;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_07_45/gcc-13.3.1/dtcmp-1.1.5-a3lz6mmn25p3jh4sanryhtd7d6ligiv2;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_07_45/gcc-13.3.1/er-0.3.0-ct43o2nsxxs3o6lzx2fqq7x6hodycdxj;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_07_45/gcc-13.3.1/libyogrt-1.35-mofsq5rrmoqrthm3vbb6qits2zxd7mb5;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_07_45/gcc-13.3.1/spath-0.2.0-hna6nu4oneotlxwcfrvsfmekoh5ppjgi;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_07_45/gcc-13.3.1/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-bfs3xf2vc2vqmawm6kpspcyscpkqxxqc;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_07_45/gcc-13.3.1/fmt-12.1.0-zqxr3fxqwzr32g2k5bo2wsixnw67co57;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_07_45/gcc-13.3.1/metis-5.1.0-ugyr2txwucmnoa74sjorvtvktey4s7j4;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_07_45/gcc-13.3.1/lwgrp-1.0.6-hlxl5ibgd7nuqhmyn3644y43u567odhy;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_07_45/gcc-13.3.1/redset-0.2.0-d7uq623m37qhjj6leet4x3fvx2fesdrq;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_07_45/gcc-13.3.1/shuffile-0.2.0-o7x3cedrhi2gqwezlgtkszdayyi7aeqp;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_07_45/gcc-13.3.1/rankstr-0.2.0-om247obwembbr3pzzvqelcploi3clul3;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_07_45/gcc-13.3.1/kvtree-1.3.0-mymtbems5iw4xq3kfo6eyzpnbml7xv5m;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_07_45/none-none/gcc-runtime-13.3.1-idl2e7ehceec4tq2xcci4n27sch4dkvu;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_07_45/none-none/compiler-wrapper-1.1.0-pbd2ayub7z6lkuzpvgsnzfsycl3xbuy3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.26.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66xzj4wm5kfqxyc4vgox7gb3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/doxygen-1.15.0;/usr/tce/packages/gcc/gcc-13.3.1;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-gcc-13.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/axom-develop-vfi5qherug7c2n7iyqkyclwbajst4kps/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/axom-develop-vfi5qherug7c2n7iyqkyclwbajst4kps/lib64;;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_07_45/gcc-13.3.1/axom-develop-elsiwuivxzov45w4u2z4zlz3hhjxfl3f/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_07_45/gcc-13.3.1/axom-develop-elsiwuivxzov45w4u2z4zlz3hhjxfl3f/lib64;;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/axom-develop-vfi5qherug7c2n7iyqkyclwbajst4kps/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/axom-develop-vfi5qherug7c2n7iyqkyclwbajst4kps/lib64;;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_07_45/gcc-13.3.1/axom-develop-elsiwuivxzov45w4u2z4zlz3hhjxfl3f/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_07_45/gcc-13.3.1/axom-develop-elsiwuivxzov45w4u2z4zlz3hhjxfl3f/lib64;;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ # Compilers #------------------------------------------------------------------------------ -# Compiler Spec: gcc@13.3.1/zbmuyoury7g5npf6fa7lwwxbjninyheh +# Compiler Spec: gcc@13.3.1/coban4v6d67qsc2lmofonv7wkeuxgg55 #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/gcc" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_07_45/none-none/compiler-wrapper-1.1.0-pbd2ayub7z6lkuzpvgsnzfsycl3xbuy3/libexec/spack/gcc/gcc" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/g++" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_07_45/none-none/compiler-wrapper-1.1.0-pbd2ayub7z6lkuzpvgsnzfsycl3xbuy3/libexec/spack/gcc/g++" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/gfortran" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_07_45/none-none/compiler-wrapper-1.1.0-pbd2ayub7z6lkuzpvgsnzfsycl3xbuy3/libexec/spack/gcc/gfortran" CACHE PATH "") else() @@ -77,51 +77,51 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_07_45/gcc-13.3.1" CACHE PATH "") -set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-vlm6nb2rwwmvnxtnxw5p3o27rzsap6vt" CACHE PATH "") +set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.7-cj7f7ue7tuh5za3vyckhvazoaa5dirlz" CACHE PATH "") -set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-tyixdhhwshu35d2vuh6gw5bk3b3k7nqz" CACHE PATH "") +set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-ilhafup6lemuluscuhe2wcw6dhqwukac" CACHE PATH "") -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-xodkornev7gcvmi3idsoxtdsnlph4rqv" CACHE PATH "") +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-7d7eeipuqb2muks5bczjac63jcokn3iz" CACHE PATH "") -set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-zyfoez4ztydoqfeubcamjypyhprjpuca" CACHE PATH "") +set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-rephuaeqmzpcjiazcflzeswdvsbwuvd2" CACHE PATH "") set(LUA_DIR "/usr" CACHE PATH "") -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-o23ptcxtve2rphukiakbezevsssiqqmq" CACHE PATH "") +set(RAJA_DIR "${TPL_ROOT}/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-jmywz4ts2sygkyau5dafh6ikwnbjhtye" CACHE PATH "") -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-drla577bebgu5wx6q7y37qxedmgmpcgi" CACHE PATH "") +set(UMPIRE_DIR "${TPL_ROOT}/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-vlfk7hrrnglk46jwsd7tfpgkcvqed4ox" CACHE PATH "") # OPENCASCADE not built -set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-nqiuo5ltc24jpawzi6mlp3xtmngcopiu" CACHE PATH "") +set(ADIAK_DIR "${TPL_ROOT}/adiak-0.5.0-rv7mu2xwq332xz2xqgfjtond4hvjlcvi" CACHE PATH "") -set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-bzioyf6catu3cjwaz4cplt7ubtvavgbl" CACHE PATH "") +set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-56wmby3kxz4tfujjmxqkvklthqkbmyyn" CACHE PATH "") -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-kjufdwlliz23qzwoy2xygn3w6dsq6tec" CACHE PATH "") +set(CAMP_DIR "${TPL_ROOT}/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-bfs3xf2vc2vqmawm6kpspcyscpkqxxqc" CACHE PATH "") -set(SCR_DIR "${TPL_ROOT}/scr-3.0.1-ov4mwvpcd75glzu2lfuivq3oibr4vvpd" CACHE PATH "") +set(SCR_DIR "${TPL_ROOT}/scr-3.0.1-basfrtnpqf4c53b4rje6jsvep5zsnrwe" CACHE PATH "") -set(KVTREE_DIR "${TPL_ROOT}/kvtree-1.3.0-3tlf6x5raibwnal2uo2l7feyjrqmkrhd" CACHE PATH "") +set(KVTREE_DIR "${TPL_ROOT}/kvtree-1.3.0-mymtbems5iw4xq3kfo6eyzpnbml7xv5m" CACHE PATH "") -set(DTCMP_DIR "${TPL_ROOT}/dtcmp-1.1.5-taj5rsqbrki5hk2fg5pxkg6xsz5tfjxk" CACHE PATH "") +set(DTCMP_DIR "${TPL_ROOT}/dtcmp-1.1.5-a3lz6mmn25p3jh4sanryhtd7d6ligiv2" CACHE PATH "") -set(SPATH_DIR "${TPL_ROOT}/spath-0.2.0-5zx5r6jnfiqbfh2hfkzvuvqmam6j67dt" CACHE PATH "") +set(SPATH_DIR "${TPL_ROOT}/spath-0.2.0-hna6nu4oneotlxwcfrvsfmekoh5ppjgi" CACHE PATH "") -set(AXL_DIR "${TPL_ROOT}/axl-0.7.1-ljgukulclybf6cdbcj7u3w7o63v43zcg" CACHE PATH "") +set(AXL_DIR "${TPL_ROOT}/axl-0.7.1-r3e4275my4pohksumwdh3alreqir3l5d" CACHE PATH "") -set(LWGRP_DIR "${TPL_ROOT}/lwgrp-1.0.6-e2smb3vzymlqmy35nuipgi6cv6o2ezyz" CACHE PATH "") +set(LWGRP_DIR "${TPL_ROOT}/lwgrp-1.0.6-hlxl5ibgd7nuqhmyn3644y43u567odhy" CACHE PATH "") -set(ER_DIR "${TPL_ROOT}/er-0.3.0-ozpsif2qsi7or56avv6fxwn3at2hrhli" CACHE PATH "") +set(ER_DIR "${TPL_ROOT}/er-0.3.0-ct43o2nsxxs3o6lzx2fqq7x6hodycdxj" CACHE PATH "") -set(RANKSTR_DIR "${TPL_ROOT}/rankstr-0.2.0-dpmjoa5upfcwzpbvxmhrbgupiqtu4uoz" CACHE PATH "") +set(RANKSTR_DIR "${TPL_ROOT}/rankstr-0.2.0-om247obwembbr3pzzvqelcploi3clul3" CACHE PATH "") -set(REDSET_DIR "${TPL_ROOT}/redset-0.2.0-itxhprv2jiz4uw4oa42jribcmnc6z2xr" CACHE PATH "") +set(REDSET_DIR "${TPL_ROOT}/redset-0.2.0-d7uq623m37qhjj6leet4x3fvx2fesdrq" CACHE PATH "") -set(SHUFFILE_DIR "${TPL_ROOT}/shuffile-0.2.0-jpcg5fljyjaf3sjmnasiiv4brhw47vcc" CACHE PATH "") +set(SHUFFILE_DIR "${TPL_ROOT}/shuffile-0.2.0-o7x3cedrhi2gqwezlgtkszdayyi7aeqp" CACHE PATH "") -set(LIBYOGRT_DIR "${TPL_ROOT}/libyogrt-1.35-y4nvgwhpokw2x6lekbubcmxv5wweidg4" CACHE PATH "") +set(LIBYOGRT_DIR "${TPL_ROOT}/libyogrt-1.35-mofsq5rrmoqrthm3vbb6qits2zxd7mb5" CACHE PATH "") #------------------------------------------------------------------------------ # Devtools & Python @@ -147,16 +147,16 @@ set(CPPCHECK_EXECUTABLE "${DEVTOOLS_ROOT}/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66x set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/kky5oskfhacihjb5gpy7qkeh6edlrtcb/doxygen-1.15.0/bin/doxygen" CACHE PATH "") -set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-e42etfacct5pxga65kryysakw52u4c63/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.12.0-2slokzflsbdyymtlubukr5uv352aaqol/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_07_45/none-none/py-pytest-9.0.3-gzmipids43fsxiidqzdglv4cbaxj26gk/lib/python3.13/site-packages" CACHE PATH "") -set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-to6o5czwkczlh46d6gkubixw5bhzxakl/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.6-owxvcv322niqkkfckectzayaxxwre7f4/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/py-pluggy-1.6.0-6zab4pck6v7johubbhalr2waowbwpjei/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_07_45/none-none/py-pluggy-1.6.0-7gqc6q7eser33fbaul2h4xxpqo67fphj/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/py-iniconfig-2.1.0-xwmbxuhk2oo3dcuizdp3z4hqbpqykkom/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_07_45/none-none/py-iniconfig-2.1.0-7flbc7ylpqkqn4swh7u5lojtj2ipn7bx/lib/python3.13/site-packages" CACHE PATH "") -set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-l3h5jmmg773bd5oer7makte3ln6rn7xv/lib/python3.13/site-packages" CACHE PATH "") +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-susobssvjtqo5inxlvwts2s5qacifmqy/lib/python3.13/site-packages" CACHE PATH "") diff --git a/host-configs/dane-toss_4_x86_64_ib-intel-oneapi-compilers@2025.2.0.cmake b/host-configs/dane-toss_4_x86_64_ib-intel-oneapi-compilers@2025.2.0.cmake index 01c07dc010..71fc8e6e0c 100644 --- a/host-configs/dane-toss_4_x86_64_ib-intel-oneapi-compilers@2025.2.0.cmake +++ b/host-configs/dane-toss_4_x86_64_ib-intel-oneapi-compilers@2025.2.0.cmake @@ -4,28 +4,28 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.26.3/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/intel-oneapi-compilers-2025.2.0/blt-0.7.1-fnswc6mwjgunle3ushrnp3e6w6s25qra;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/intel-oneapi-compilers-2025.2.0/c2c-1.8.0-fdbbv7fzmylpoctjgdai2kc4xfcz2vqt;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/intel-oneapi-compilers-2025.2.0/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-bheefndoamg47jey4mbq2ofgflz7kvz4;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/intel-oneapi-compilers-2025.2.0/conduit-0.9.5-lqjx3vlbi5prcmle2gsgc7yopbglp3ab;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/intel-oneapi-compilers-2025.2.0/mfem-4.9.0-npcozwxpczgzuya2z6iailvycmbbc6nd;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/py-nanobind-2.7.0-iky6agz5lprxpnf2nffxwgqcgiagqiz5;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/intel-oneapi-compilers-2025.2.0/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-ppbrbxztecp4363lchwihhymqrhv75un;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/intel-oneapi-compilers-2025.2.0/umpire-2025.12.0-tdpnmcmwvlz77qwdeupgufa6s5aswwp5;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/intel-oneapi-compilers-2025.2.0/adiak-0.4.0-gxozhembb6br5wkhuxetug25ndaodstk;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/intel-oneapi-compilers-2025.2.0/libunwind-1.8.3-lgvf5mwbhqt4is2gg3uuafuyxv35lwd7;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/intel-oneapi-compilers-2025.2.0/hdf5-1.8.23-zhxj7u6hk266vklzloqcp3qlahtbta4l;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/intel-oneapi-compilers-2025.2.0/parmetis-4.0.3-dred3k4i2un2bw3w4st5pl2zvpvpfr37;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/intel-oneapi-compilers-2025.2.0/py-mpi4py-4.1.1-7mymbwx2ifuqlotydf6dx6hd6wmz4xhd;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/py-numpy-2.4.2-to6o5czwkczlh46d6gkubixw5bhzxakl;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/intel-oneapi-compilers-2025.2.0/hypre-2.27.0-jtpmchw5qcrtezcac6mp53wlxcncp5ne;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/intel-oneapi-compilers-2025.2.0/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-k4dewbo7oilgt6zjhkze2hyqyc6xn3if;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/intel-oneapi-compilers-2025.2.0/fmt-11.0.2-6ut7qqyjzqqbc6ggi4o7jthpsofhhu65;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/intel-oneapi-compilers-2025.2.0/metis-5.1.0-psuyihfrmzzgjxljxkoqbaqdjs53253r;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/intel-oneapi-runtime-2025.2.0-3ym7ceyrw63moiirt4tqoqubxhkiqv63;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/gcc-runtime-13.3.1-xlvjd7vcdngtnvtxcdyqwtquvenekynf;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.26.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66xzj4wm5kfqxyc4vgox7gb3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/doxygen-1.15.0;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/intel-oneapi-compilers-2025.2.0-ngszoacyp2i5d5nawitlyuh37jswjtie/compiler/2025.2;/usr/tce/packages/intel/intel-2025.2.0;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-intel-2025.2.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_12_45_10/intel-oneapi-compilers-2025.2.0/blt-0.7.2-evtmihs46mbczrqbjfwwmroywmgxxjma;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_12_45_10/intel-oneapi-compilers-2025.2.0/c2c-1.8.0-5wjdmt55f7nuftxsaxqvrb4zrl72dovf;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_12_45_10/intel-oneapi-compilers-2025.2.0/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-6znuaaeszc2faabgq55yrahxv5yqnmu6;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_12_45_10/intel-oneapi-compilers-2025.2.0/conduit-0.9.7-b5kuchct7dt5zox4ck7rtprtznejh2il;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_12_45_10/intel-oneapi-compilers-2025.2.0/mfem-4.9.0-wwgragi5vtbzo5wkjmjrthjjk3ggzs2l;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_12_45_10/intel-oneapi-compilers-2025.2.0/py-nanobind-2.12.0-kfwke5jj6qxsoayuruy7zvp6jb757x5r;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_12_45_10/none-none/py-pytest-9.0.3-gzmipids43fsxiidqzdglv4cbaxj26gk;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_12_45_10/intel-oneapi-compilers-2025.2.0/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-q24rhtgaxvk36dia7o3zisrq23r2icp6;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_12_45_10/intel-oneapi-compilers-2025.2.0/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-u6xviw2os347tdexix55hj5xgxebvtb4;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_12_45_10/intel-oneapi-compilers-2025.2.0/adiak-0.5.0-fcafug75o3ehttelhmvzcq6at3blpqfz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_12_45_10/intel-oneapi-compilers-2025.2.0/libunwind-1.8.3-yawby7qknroemwpzfyvgukoe2nqlgcu2;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_12_45_10/intel-oneapi-compilers-2025.2.0/hdf5-1.8.23-7x2tmsqn2zeo2tm74ih6ovlt3ayejfxo;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_12_45_10/intel-oneapi-compilers-2025.2.0/parmetis-4.0.3-667nagnshxefhyr235hoclqgn7vbr3zg;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_12_45_10/intel-oneapi-compilers-2025.2.0/py-mpi4py-4.1.1-qw72rhicazqo2tf4wu3emk4kxeaza3zv;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_12_45_10/gcc-13.3.1/py-numpy-2.4.6-f2gs5srtddls5yewq6xbs7fyal24q3j6;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_12_45_10/intel-oneapi-compilers-2025.2.0/hypre-3.1.0-uzilxxkm36njhhag27mrhvbm3g5eiya6;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_12_45_10/intel-oneapi-compilers-2025.2.0/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-vhpjt4ws6affncnj6xweeazpltvii6nl;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_12_45_10/intel-oneapi-compilers-2025.2.0/fmt-12.1.0-53kznw5w4ud7gsawjaa2b7utwzctfxg7;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_12_45_10/intel-oneapi-compilers-2025.2.0/metis-5.1.0-gbsrm6zpdb3t6dp4ba4ag6yrs26576rm;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_12_45_10/none-none/intel-oneapi-runtime-2025.2.0-hpokdteeh7mtzizfnhrbiaovr622es6q;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_12_45_10/none-none/gcc-runtime-13.3.1-idl2e7ehceec4tq2xcci4n27sch4dkvu;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_12_45_10/none-none/compiler-wrapper-1.1.0-pbd2ayub7z6lkuzpvgsnzfsycl3xbuy3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.26.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66xzj4wm5kfqxyc4vgox7gb3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/doxygen-1.15.0;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/intel-oneapi-compilers-2025.2.0-ngszoacyp2i5d5nawitlyuh37jswjtie/compiler/2025.2;/usr/tce/packages/intel/intel-2025.2.0;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-intel-2025.2.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/intel-oneapi-compilers-2025.2.0/axom-develop-g2h45edliw4q2vgz7cfusag64p3ulopb/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/intel-oneapi-compilers-2025.2.0/axom-develop-g2h45edliw4q2vgz7cfusag64p3ulopb/lib64;;/usr/tce/packages/intel/intel-2025.2.0/compiler/2025.2/lib;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/intel-oneapi-compilers-2025.2.0-ngszoacyp2i5d5nawitlyuh37jswjtie/compiler/2025.2/lib;/usr/tce/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_12_45_10/intel-oneapi-compilers-2025.2.0/axom-develop-ctb4mtm5xxvp4rnyiejj36iuyoonzu35/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_12_45_10/intel-oneapi-compilers-2025.2.0/axom-develop-ctb4mtm5xxvp4rnyiejj36iuyoonzu35/lib64;;/usr/tce/packages/intel/intel-2025.2.0/compiler/2025.2/lib;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/intel-oneapi-compilers-2025.2.0-ngszoacyp2i5d5nawitlyuh37jswjtie/compiler/2025.2/lib;/usr/tce/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/intel-oneapi-compilers-2025.2.0/axom-develop-g2h45edliw4q2vgz7cfusag64p3ulopb/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/intel-oneapi-compilers-2025.2.0/axom-develop-g2h45edliw4q2vgz7cfusag64p3ulopb/lib64;;/usr/tce/packages/intel/intel-2025.2.0/compiler/2025.2/lib;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/intel-oneapi-compilers-2025.2.0-ngszoacyp2i5d5nawitlyuh37jswjtie/compiler/2025.2/lib;/usr/tce/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_12_45_10/intel-oneapi-compilers-2025.2.0/axom-develop-ctb4mtm5xxvp4rnyiejj36iuyoonzu35/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_12_45_10/intel-oneapi-compilers-2025.2.0/axom-develop-ctb4mtm5xxvp4rnyiejj36iuyoonzu35/lib64;;/usr/tce/packages/intel/intel-2025.2.0/compiler/2025.2/lib;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/intel-oneapi-compilers-2025.2.0-ngszoacyp2i5d5nawitlyuh37jswjtie/compiler/2025.2/lib;/usr/tce/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ # Compilers #------------------------------------------------------------------------------ -# Compiler Spec: intel-oneapi-compilers@2025.2.0/pure57vovckospvgxd2lr56fpsa636dr +# Compiler Spec: intel-oneapi-compilers@2025.2.0/4u7gc5dscq6xcei2akjdj7uefz56obcl #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/oneapi/icx" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_12_45_10/none-none/compiler-wrapper-1.1.0-pbd2ayub7z6lkuzpvgsnzfsycl3xbuy3/libexec/spack/oneapi/icx" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/oneapi/icpx" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_12_45_10/none-none/compiler-wrapper-1.1.0-pbd2ayub7z6lkuzpvgsnzfsycl3xbuy3/libexec/spack/oneapi/icpx" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/oneapi/ifx" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_12_45_10/none-none/compiler-wrapper-1.1.0-pbd2ayub7z6lkuzpvgsnzfsycl3xbuy3/libexec/spack/oneapi/ifx" CACHE PATH "") else() @@ -79,29 +79,29 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/intel-oneapi-compilers-2025.2.0" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_12_45_10/intel-oneapi-compilers-2025.2.0" CACHE PATH "") -set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-lqjx3vlbi5prcmle2gsgc7yopbglp3ab" CACHE PATH "") +set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.7-b5kuchct7dt5zox4ck7rtprtznejh2il" CACHE PATH "") -set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-fdbbv7fzmylpoctjgdai2kc4xfcz2vqt" CACHE PATH "") +set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-5wjdmt55f7nuftxsaxqvrb4zrl72dovf" CACHE PATH "") -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-npcozwxpczgzuya2z6iailvycmbbc6nd" CACHE PATH "") +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-wwgragi5vtbzo5wkjmjrthjjk3ggzs2l" CACHE PATH "") -set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-zhxj7u6hk266vklzloqcp3qlahtbta4l" CACHE PATH "") +set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-7x2tmsqn2zeo2tm74ih6ovlt3ayejfxo" CACHE PATH "") set(LUA_DIR "/usr" CACHE PATH "") -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-ppbrbxztecp4363lchwihhymqrhv75un" CACHE PATH "") +set(RAJA_DIR "${TPL_ROOT}/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-q24rhtgaxvk36dia7o3zisrq23r2icp6" CACHE PATH "") -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-tdpnmcmwvlz77qwdeupgufa6s5aswwp5" CACHE PATH "") +set(UMPIRE_DIR "${TPL_ROOT}/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-u6xviw2os347tdexix55hj5xgxebvtb4" CACHE PATH "") # OPENCASCADE not built -set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-gxozhembb6br5wkhuxetug25ndaodstk" CACHE PATH "") +set(ADIAK_DIR "${TPL_ROOT}/adiak-0.5.0-fcafug75o3ehttelhmvzcq6at3blpqfz" CACHE PATH "") -set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-bheefndoamg47jey4mbq2ofgflz7kvz4" CACHE PATH "") +set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-6znuaaeszc2faabgq55yrahxv5yqnmu6" CACHE PATH "") -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-k4dewbo7oilgt6zjhkze2hyqyc6xn3if" CACHE PATH "") +set(CAMP_DIR "${TPL_ROOT}/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-vhpjt4ws6affncnj6xweeazpltvii6nl" CACHE PATH "") # scr not built @@ -129,16 +129,16 @@ set(CPPCHECK_EXECUTABLE "${DEVTOOLS_ROOT}/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66x set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/kky5oskfhacihjb5gpy7qkeh6edlrtcb/doxygen-1.15.0/bin/doxygen" CACHE PATH "") -set(PY_NANOBIND_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/py-nanobind-2.7.0-iky6agz5lprxpnf2nffxwgqcgiagqiz5/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.12.0-kfwke5jj6qxsoayuruy7zvp6jb757x5r/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_12_45_10/none-none/py-pytest-9.0.3-gzmipids43fsxiidqzdglv4cbaxj26gk/lib/python3.13/site-packages" CACHE PATH "") -set(PY_NUMPY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/gcc-13.3.1/py-numpy-2.4.2-to6o5czwkczlh46d6gkubixw5bhzxakl/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NUMPY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_12_45_10/gcc-13.3.1/py-numpy-2.4.6-f2gs5srtddls5yewq6xbs7fyal24q3j6/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/py-pluggy-1.6.0-6zab4pck6v7johubbhalr2waowbwpjei/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_12_45_10/none-none/py-pluggy-1.6.0-7gqc6q7eser33fbaul2h4xxpqo67fphj/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/py-iniconfig-2.1.0-xwmbxuhk2oo3dcuizdp3z4hqbpqykkom/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_12_45_10/none-none/py-iniconfig-2.1.0-7flbc7ylpqkqn4swh7u5lojtj2ipn7bx/lib/python3.13/site-packages" CACHE PATH "") -set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-7mymbwx2ifuqlotydf6dx6hd6wmz4xhd/lib/python3.13/site-packages" CACHE PATH "") +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-qw72rhicazqo2tf4wu3emk4kxeaza3zv/lib/python3.13/site-packages" CACHE PATH "") diff --git a/host-configs/dane-toss_4_x86_64_ib-llvm@19.1.3.cmake b/host-configs/dane-toss_4_x86_64_ib-llvm@19.1.3.cmake index 0e1f433159..5901b9e453 100644 --- a/host-configs/dane-toss_4_x86_64_ib-llvm@19.1.3.cmake +++ b/host-configs/dane-toss_4_x86_64_ib-llvm@19.1.3.cmake @@ -4,28 +4,28 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.26.3/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/blt-0.7.1-ux6wzebtj6vtgnayf4gqv2rr62ulwwfc;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/c2c-1.8.0-wto5u3qsffjhbxirygr7kqqjlg55pyv6;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-iuatdlntm2zcg2vuygvsc7say6wfx353;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/conduit-0.9.5-vqo2x2n6mz2vs4eg7kynwmlpeolsuim5;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/mfem-4.9.0-jqqjlgxdiebyz7gblihglbgu2jbnqnqc;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/opencascade-7.8.1-pyde26mlsvvcvw75lxvoeshe6r6aobxe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/py-nanobind-2.7.0-zxodixu5egwgeorhpe3ciggwsv3ndfxb;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-ur2yappntfb4cptytvwwwtmsf7irqgxa;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/scr-3.0.1-l7heipi5ftfkbxqiytyxr4legakgjzq2;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/umpire-2025.12.0-l3l75r2uuai7rl3m4e7fpdw446ankcxp;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/adiak-0.4.0-do3kop54yldi4hz43usrtnx2zyhvwfsv;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/libunwind-1.8.3-ck74b2escwzf2xtpiafdvqwmat26uuoe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/hdf5-1.8.23-vpibouztpyzmgikg4dg2egwf2u53mmpl;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/parmetis-4.0.3-fs7gps55rp6znnpz3b4zkd35jjkfycfc;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/py-mpi4py-4.1.1-hrvj5zyy3xo2rbzvbz4ed7kjye7ssgmk;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/py-numpy-2.4.2-73lpwcid33wlekelrxoh24535lzexwwg;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/hypre-2.27.0-zef2jav5gijraygsr2tumdy32outkfxp;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/glx-1.4-4kahrsr5vciz372vpdvlmvxahspecwif;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/tcl-8.6.17-qjflnn6itwshm5i6ckchochc7t2cuw6w;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/axl-0.7.1-z4zopytuytxgn5wds3bnhqpqmfju54h7;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/dtcmp-1.1.5-b6g6n4hdju6ol5g2pcizzey7dthbgapt;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/er-0.3.0-eabe4roqqpiyjenasayt6pupercipzak;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/libyogrt-1.35-5geyvlylxzqsc6g6s6xiq6aakvy2qiu7;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/spath-0.2.0-6sxvboh2q5kmo7q5tcchgao2dgx5g72q;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-omiy5m3jikdlf6gicad34w5vkfz7huex;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/fmt-11.0.2-ekc2idnbqipl2pqb4nugxoa7sj7aigpo;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/metis-5.1.0-7gsm27yycsippuh2l4fwei4dtvevbqgh;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/lwgrp-1.0.6-u4qqk5djarvhyvk3fg7ls4r6ltx4anmr;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/redset-0.2.0-5b3fl74qgvvfturxdgz44rurhjdm4en4;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/shuffile-0.2.0-tpvuy3dcxpakepz77nxygcb7cqjwtwmv;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/gcc-runtime-13.3.1-xlvjd7vcdngtnvtxcdyqwtquvenekynf;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/rankstr-0.2.0-njavwb2qlbesemqc6ac7hjeezjxtydv7;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/kvtree-1.3.0-l3jatuuigf2z2swzxrdzkbrxhdinaqfz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.26.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66xzj4wm5kfqxyc4vgox7gb3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/doxygen-1.15.0;/usr/tce/packages/gcc/gcc-13.3.1;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-clang-19.1.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/gcc-13.3.1/blt-0.7.2-kgttmwj56kwpge3oveotwgx7amhmc54b;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/llvm-19.1.3/c2c-1.8.0-cc4zkpkb3tvtflcodt4bhjiexgbd5pf3;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/llvm-19.1.3/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-xltandsuzmuxk4rlilbiyfiichj2tzq5;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/llvm-19.1.3/conduit-0.9.7-bcuudf26kmnocne4wa2pq4csunshwyqz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/llvm-19.1.3/mfem-4.9.0-ioqmhevjhvecwiwpypcnxp6qdzfhyuz4;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/llvm-19.1.3/opencascade-7.9.1-syvfdepri7ee4rlem4ygy77vni3bqvez;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/llvm-19.1.3/py-nanobind-2.12.0-jvqsaw246m2x5v3cw25qpwmgcjhbcxok;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/none-none/py-pytest-9.0.3-gzmipids43fsxiidqzdglv4cbaxj26gk;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/llvm-19.1.3/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-wjftha5hizxnpzcw35pnjv5st2xdpn3d;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/llvm-19.1.3/scr-3.0.1-7hkagbjyzitmsuf3icffzr75amvnms2f;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/llvm-19.1.3/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-ronmxvxkvy52nshco4uw5ldkncp2l26x;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/llvm-19.1.3/adiak-0.5.0-bn7vvphqubchyzrll3ktmsihnzdbbb6b;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/llvm-19.1.3/libunwind-1.8.3-3j43eldxfwjlrs6gnf33vpfpox5tbqjr;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/llvm-19.1.3/hdf5-1.8.23-pqeunv22a7jyh66n7b4mrbvccatr32eu;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/llvm-19.1.3/parmetis-4.0.3-tc7yzrxqscciment55kdr5uzym5ijilj;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/llvm-19.1.3/py-mpi4py-4.1.1-rifw3jmftcx6ezx2qnb4u64gxpcptcl5;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/llvm-19.1.3/py-numpy-2.4.6-ygvifcqpoaj23qtkbllsgdgge62vtlzv;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/llvm-19.1.3/hypre-3.1.0-ee4mrker7mlj5t2ztiztrzfz6m4kcjsc;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/none-none/glx-1.4-dx6c77ydg6r34fqvr54ams4342vbwugg;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/llvm-19.1.3/tcl-8.6.17-rf4e2svjggt6u3mxw4jjqia34736tfft;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/llvm-19.1.3/axl-0.7.1-lnonzejh7rsqkod2y4mbwlnzeql5hmnt;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/llvm-19.1.3/dtcmp-1.1.5-ephq3djkfkny3jlpyo25bwkw6ko3zigb;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/llvm-19.1.3/er-0.3.0-4m4r25nk7lwhvt7tf437mykvodgyt5wq;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/llvm-19.1.3/libyogrt-1.35-zindsc3anw7vrfxm55lwgamzm7buinkx;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/llvm-19.1.3/spath-0.2.0-xgbpz3szq3rblgi3zgty2qcuneciuful;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/llvm-19.1.3/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-5lxncn6hshlgiweeldarjcahegtq6gkv;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/llvm-19.1.3/fmt-12.1.0-ew4w4he3l62ai5pbto4li23fao2wa2u6;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/llvm-19.1.3/metis-5.1.0-o23lru3echahpaspbjkpdjfudtgjg6vj;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/llvm-19.1.3/lwgrp-1.0.6-ujfzcbrrl6gihaqtgzazwo3cwl2fh5pd;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/llvm-19.1.3/redset-0.2.0-4hhtwmzrib62y4qdlq6ogi5ia34m27vn;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/llvm-19.1.3/shuffile-0.2.0-ued3q4imojriofxxdxpk2zvinozvqcq5;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/none-none/gcc-runtime-13.3.1-idl2e7ehceec4tq2xcci4n27sch4dkvu;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/llvm-19.1.3/rankstr-0.2.0-egxh5fy7htezki3xoelx2a5iajgtusss;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/llvm-19.1.3/kvtree-1.3.0-43hhkxdkzfr6egqdrvdtht5p7amthsnl;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/none-none/compiler-wrapper-1.1.0-pbd2ayub7z6lkuzpvgsnzfsycl3xbuy3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.26.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66xzj4wm5kfqxyc4vgox7gb3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/doxygen-1.15.0;/usr/tce/packages/gcc/gcc-13.3.1;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-clang-19.1.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/axom-develop-oab3m23sasflxabp3rqhmbk2cxsnxv4t/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/axom-develop-oab3m23sasflxabp3rqhmbk2cxsnxv4t/lib64;;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/llvm-19.1.3-gy2lu5xbi4csr2k47emlajzfs5mlsd4g/lib/x86_64-unknown-linux-gnu;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13;/usr/tce/packages/clang/clang-19.1.3/lib;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/llvm-19.1.3/axom-develop-3s3ethp7dp5gx2ol223qk6yp67ao75gh/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/llvm-19.1.3/axom-develop-3s3ethp7dp5gx2ol223qk6yp67ao75gh/lib64;;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/llvm-19.1.3-gy2lu5xbi4csr2k47emlajzfs5mlsd4g/lib/x86_64-unknown-linux-gnu;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13;/usr/tce/packages/clang/clang-19.1.3/lib;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/axom-develop-oab3m23sasflxabp3rqhmbk2cxsnxv4t/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3/axom-develop-oab3m23sasflxabp3rqhmbk2cxsnxv4t/lib64;;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/llvm-19.1.3-gy2lu5xbi4csr2k47emlajzfs5mlsd4g/lib/x86_64-unknown-linux-gnu;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13;/usr/tce/packages/clang/clang-19.1.3/lib;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/llvm-19.1.3/axom-develop-3s3ethp7dp5gx2ol223qk6yp67ao75gh/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/llvm-19.1.3/axom-develop-3s3ethp7dp5gx2ol223qk6yp67ao75gh/lib64;;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/llvm-19.1.3-gy2lu5xbi4csr2k47emlajzfs5mlsd4g/lib/x86_64-unknown-linux-gnu;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13;/usr/tce/packages/clang/clang-19.1.3/lib;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ # Compilers #------------------------------------------------------------------------------ -# Compiler Spec: llvm@19.1.3/m2tcgbxfcdjwdmyrkqwamlljh4qeiie2 +# Compiler Spec: llvm@19.1.3/pa7jv5ejrkrucsgwtv4kde2cazhq3v6i #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/clang/clang" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/none-none/compiler-wrapper-1.1.0-pbd2ayub7z6lkuzpvgsnzfsycl3xbuy3/libexec/spack/clang/clang" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/clang/clang++" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/none-none/compiler-wrapper-1.1.0-pbd2ayub7z6lkuzpvgsnzfsycl3xbuy3/libexec/spack/clang/clang++" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/gfortran" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/none-none/compiler-wrapper-1.1.0-pbd2ayub7z6lkuzpvgsnzfsycl3xbuy3/libexec/spack/gcc/gfortran" CACHE PATH "") else() @@ -79,51 +79,51 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/llvm-19.1.3" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/llvm-19.1.3" CACHE PATH "") -set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-vqo2x2n6mz2vs4eg7kynwmlpeolsuim5" CACHE PATH "") +set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.7-bcuudf26kmnocne4wa2pq4csunshwyqz" CACHE PATH "") -set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-wto5u3qsffjhbxirygr7kqqjlg55pyv6" CACHE PATH "") +set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-cc4zkpkb3tvtflcodt4bhjiexgbd5pf3" CACHE PATH "") -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-jqqjlgxdiebyz7gblihglbgu2jbnqnqc" CACHE PATH "") +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-ioqmhevjhvecwiwpypcnxp6qdzfhyuz4" CACHE PATH "") -set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-vpibouztpyzmgikg4dg2egwf2u53mmpl" CACHE PATH "") +set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-pqeunv22a7jyh66n7b4mrbvccatr32eu" CACHE PATH "") set(LUA_DIR "/usr" CACHE PATH "") -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-ur2yappntfb4cptytvwwwtmsf7irqgxa" CACHE PATH "") +set(RAJA_DIR "${TPL_ROOT}/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-wjftha5hizxnpzcw35pnjv5st2xdpn3d" CACHE PATH "") -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-l3l75r2uuai7rl3m4e7fpdw446ankcxp" CACHE PATH "") +set(UMPIRE_DIR "${TPL_ROOT}/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-ronmxvxkvy52nshco4uw5ldkncp2l26x" CACHE PATH "") -set(OPENCASCADE_DIR "${TPL_ROOT}/opencascade-7.8.1-pyde26mlsvvcvw75lxvoeshe6r6aobxe" CACHE PATH "") +set(OPENCASCADE_DIR "${TPL_ROOT}/opencascade-7.9.1-syvfdepri7ee4rlem4ygy77vni3bqvez" CACHE PATH "") -set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-do3kop54yldi4hz43usrtnx2zyhvwfsv" CACHE PATH "") +set(ADIAK_DIR "${TPL_ROOT}/adiak-0.5.0-bn7vvphqubchyzrll3ktmsihnzdbbb6b" CACHE PATH "") -set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-iuatdlntm2zcg2vuygvsc7say6wfx353" CACHE PATH "") +set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-xltandsuzmuxk4rlilbiyfiichj2tzq5" CACHE PATH "") -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-omiy5m3jikdlf6gicad34w5vkfz7huex" CACHE PATH "") +set(CAMP_DIR "${TPL_ROOT}/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-5lxncn6hshlgiweeldarjcahegtq6gkv" CACHE PATH "") -set(SCR_DIR "${TPL_ROOT}/scr-3.0.1-l7heipi5ftfkbxqiytyxr4legakgjzq2" CACHE PATH "") +set(SCR_DIR "${TPL_ROOT}/scr-3.0.1-7hkagbjyzitmsuf3icffzr75amvnms2f" CACHE PATH "") -set(KVTREE_DIR "${TPL_ROOT}/kvtree-1.3.0-l3jatuuigf2z2swzxrdzkbrxhdinaqfz" CACHE PATH "") +set(KVTREE_DIR "${TPL_ROOT}/kvtree-1.3.0-43hhkxdkzfr6egqdrvdtht5p7amthsnl" CACHE PATH "") -set(DTCMP_DIR "${TPL_ROOT}/dtcmp-1.1.5-b6g6n4hdju6ol5g2pcizzey7dthbgapt" CACHE PATH "") +set(DTCMP_DIR "${TPL_ROOT}/dtcmp-1.1.5-ephq3djkfkny3jlpyo25bwkw6ko3zigb" CACHE PATH "") -set(SPATH_DIR "${TPL_ROOT}/spath-0.2.0-6sxvboh2q5kmo7q5tcchgao2dgx5g72q" CACHE PATH "") +set(SPATH_DIR "${TPL_ROOT}/spath-0.2.0-xgbpz3szq3rblgi3zgty2qcuneciuful" CACHE PATH "") -set(AXL_DIR "${TPL_ROOT}/axl-0.7.1-z4zopytuytxgn5wds3bnhqpqmfju54h7" CACHE PATH "") +set(AXL_DIR "${TPL_ROOT}/axl-0.7.1-lnonzejh7rsqkod2y4mbwlnzeql5hmnt" CACHE PATH "") -set(LWGRP_DIR "${TPL_ROOT}/lwgrp-1.0.6-u4qqk5djarvhyvk3fg7ls4r6ltx4anmr" CACHE PATH "") +set(LWGRP_DIR "${TPL_ROOT}/lwgrp-1.0.6-ujfzcbrrl6gihaqtgzazwo3cwl2fh5pd" CACHE PATH "") -set(ER_DIR "${TPL_ROOT}/er-0.3.0-eabe4roqqpiyjenasayt6pupercipzak" CACHE PATH "") +set(ER_DIR "${TPL_ROOT}/er-0.3.0-4m4r25nk7lwhvt7tf437mykvodgyt5wq" CACHE PATH "") -set(RANKSTR_DIR "${TPL_ROOT}/rankstr-0.2.0-njavwb2qlbesemqc6ac7hjeezjxtydv7" CACHE PATH "") +set(RANKSTR_DIR "${TPL_ROOT}/rankstr-0.2.0-egxh5fy7htezki3xoelx2a5iajgtusss" CACHE PATH "") -set(REDSET_DIR "${TPL_ROOT}/redset-0.2.0-5b3fl74qgvvfturxdgz44rurhjdm4en4" CACHE PATH "") +set(REDSET_DIR "${TPL_ROOT}/redset-0.2.0-4hhtwmzrib62y4qdlq6ogi5ia34m27vn" CACHE PATH "") -set(SHUFFILE_DIR "${TPL_ROOT}/shuffile-0.2.0-tpvuy3dcxpakepz77nxygcb7cqjwtwmv" CACHE PATH "") +set(SHUFFILE_DIR "${TPL_ROOT}/shuffile-0.2.0-ued3q4imojriofxxdxpk2zvinozvqcq5" CACHE PATH "") -set(LIBYOGRT_DIR "${TPL_ROOT}/libyogrt-1.35-5geyvlylxzqsc6g6s6xiq6aakvy2qiu7" CACHE PATH "") +set(LIBYOGRT_DIR "${TPL_ROOT}/libyogrt-1.35-zindsc3anw7vrfxm55lwgamzm7buinkx" CACHE PATH "") #------------------------------------------------------------------------------ # Devtools & Python @@ -149,16 +149,16 @@ set(CPPCHECK_EXECUTABLE "${DEVTOOLS_ROOT}/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66x set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/kky5oskfhacihjb5gpy7qkeh6edlrtcb/doxygen-1.15.0/bin/doxygen" CACHE PATH "") -set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-zxodixu5egwgeorhpe3ciggwsv3ndfxb/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.12.0-jvqsaw246m2x5v3cw25qpwmgcjhbcxok/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/none-none/py-pytest-9.0.3-gzmipids43fsxiidqzdglv4cbaxj26gk/lib/python3.13/site-packages" CACHE PATH "") -set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-73lpwcid33wlekelrxoh24535lzexwwg/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.6-ygvifcqpoaj23qtkbllsgdgge62vtlzv/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/py-pluggy-1.6.0-6zab4pck6v7johubbhalr2waowbwpjei/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/none-none/py-pluggy-1.6.0-7gqc6q7eser33fbaul2h4xxpqo67fphj/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_16_24_28/none-none/py-iniconfig-2.1.0-xwmbxuhk2oo3dcuizdp3z4hqbpqykkom/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_09_14_44_39/none-none/py-iniconfig-2.1.0-7flbc7ylpqkqn4swh7u5lojtj2ipn7bx/lib/python3.13/site-packages" CACHE PATH "") -set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-hrvj5zyy3xo2rbzvbz4ed7kjye7ssgmk/lib/python3.13/site-packages" CACHE PATH "") +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-rifw3jmftcx6ezx2qnb4u64gxpcptcl5/lib/python3.13/site-packages" CACHE PATH "") diff --git a/host-configs/matrix-toss_4_x86_64_ib-gcc@13.3.1_cuda.cmake b/host-configs/matrix-toss_4_x86_64_ib-gcc@13.3.1_cuda.cmake index dd91c265a3..3bfac1c1b7 100644 --- a/host-configs/matrix-toss_4_x86_64_ib-gcc@13.3.1_cuda.cmake +++ b/host-configs/matrix-toss_4_x86_64_ib-gcc@13.3.1_cuda.cmake @@ -4,28 +4,28 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.26.3/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1/blt-0.7.1-lj5bghgkodlzmfs75wimnoxuui2quc3a;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1/c2c-1.8.0-tyixdhhwshu35d2vuh6gw5bk3b3k7nqz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-apockdkmc2slp4nexkn6sbspz7pibric;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1/conduit-0.9.5-vlm6nb2rwwmvnxtnxw5p3o27rzsap6vt;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1/mfem-4.9.0-dyhiljftw2yufyry2ybddjlzrst7usbg;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1/py-nanobind-2.7.0-e42etfacct5pxga65kryysakw52u4c63;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-hjzgnbnbz4sifpanhmuqhuuydzmctbbl;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1/umpire-2025.12.0-yxkn4g7yhresecqk2dkjivbahvu3qhpp;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1/adiak-0.4.0-nqiuo5ltc24jpawzi6mlp3xtmngcopiu;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1/libunwind-1.8.3-a7zvz3euhmbhk4as6h6j66ytabzwztxb;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1/hdf5-1.8.23-zyfoez4ztydoqfeubcamjypyhprjpuca;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1/parmetis-4.0.3-sxnyn5chjyiyowzk32dsynslufrofyho;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1/py-mpi4py-4.1.1-l3h5jmmg773bd5oer7makte3ln6rn7xv;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1/py-numpy-2.4.2-to6o5czwkczlh46d6gkubixw5bhzxakl;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1/hypre-2.27.0-3ckgmamfpdf3ng74h76dziarqz63godz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-bt6ckjhqcfr3gjiu76sdhabhppjhjd6t;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1/fmt-11.0.2-5wsybhdkos4xu5i7thboag6245lzc6jj;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1/metis-5.1.0-3qhomb7zt2n7nh46kmc2egkzmcd3tafm;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/none-none/gcc-runtime-13.3.1-xlvjd7vcdngtnvtxcdyqwtquvenekynf;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.26.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66xzj4wm5kfqxyc4vgox7gb3;/usr/tce/packages/cuda/cuda-12.9.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/doxygen-1.15.0;/usr/tce/packages/gcc/gcc-13.3.1;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-gcc-13.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/gcc-13.3.1/blt-0.7.2-kgttmwj56kwpge3oveotwgx7amhmc54b;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/gcc-13.3.1/c2c-1.8.0-ilhafup6lemuluscuhe2wcw6dhqwukac;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/gcc-13.3.1/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-7t2cu7tn3ltyjwyknkt3dnsgru7iynhx;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/gcc-13.3.1/conduit-0.9.7-cj7f7ue7tuh5za3vyckhvazoaa5dirlz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/gcc-13.3.1/mfem-4.9.0-tjktdj5puze2pgidvstbs54ivwo3p426;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/gcc-13.3.1/py-nanobind-2.12.0-2slokzflsbdyymtlubukr5uv352aaqol;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/none-none/py-pytest-9.0.3-gzmipids43fsxiidqzdglv4cbaxj26gk;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/gcc-13.3.1/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-r7pmta3pbsca6mmndrycorx34iog7kp7;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/gcc-13.3.1/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-xub4ymu4oek5r2fsa3rwq3dv6vtkicur;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/gcc-13.3.1/adiak-0.5.0-rv7mu2xwq332xz2xqgfjtond4hvjlcvi;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/gcc-13.3.1/libunwind-1.8.3-3ltfsqv444quhz3wviidadz2x57mcmfd;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/gcc-13.3.1/hdf5-1.8.23-rephuaeqmzpcjiazcflzeswdvsbwuvd2;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/gcc-13.3.1/parmetis-4.0.3-ykjugf2p3cjircoep575ra3cn4we724b;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/gcc-13.3.1/py-mpi4py-4.1.1-susobssvjtqo5inxlvwts2s5qacifmqy;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/gcc-13.3.1/py-numpy-2.4.6-owxvcv322niqkkfckectzayaxxwre7f4;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/gcc-13.3.1/hypre-3.1.0-nnik4j245aktkt2xzeichbdv3ynbcleb;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/gcc-13.3.1/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-63o35ugwr356fphe57yjnkberdna3ehj;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/gcc-13.3.1/fmt-12.1.0-zqxr3fxqwzr32g2k5bo2wsixnw67co57;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/gcc-13.3.1/metis-5.1.0-ugyr2txwucmnoa74sjorvtvktey4s7j4;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/none-none/gcc-runtime-13.3.1-idl2e7ehceec4tq2xcci4n27sch4dkvu;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/none-none/compiler-wrapper-1.1.0-pbd2ayub7z6lkuzpvgsnzfsycl3xbuy3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.26.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66xzj4wm5kfqxyc4vgox7gb3;/usr/tce/packages/cuda/cuda-13.1.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/doxygen-1.15.0;/usr/tce/packages/gcc/gcc-13.3.1;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-gcc-13.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1/axom-develop-fzmegf77c74yozjd72rl5iqynh23wqhx/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1/axom-develop-fzmegf77c74yozjd72rl5iqynh23wqhx/lib64;;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/gcc-13.3.1/axom-develop-5u34dolzb5yselqhoa6kom4xf5ndk2ce/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/gcc-13.3.1/axom-develop-5u34dolzb5yselqhoa6kom4xf5ndk2ce/lib64;;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1/axom-develop-fzmegf77c74yozjd72rl5iqynh23wqhx/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1/axom-develop-fzmegf77c74yozjd72rl5iqynh23wqhx/lib64;;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/gcc-13.3.1/axom-develop-5u34dolzb5yselqhoa6kom4xf5ndk2ce/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/gcc-13.3.1/axom-develop-5u34dolzb5yselqhoa6kom4xf5ndk2ce/lib64;;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ # Compilers #------------------------------------------------------------------------------ -# Compiler Spec: gcc@13.3.1/zbmuyoury7g5npf6fa7lwwxbjninyheh +# Compiler Spec: gcc@13.3.1/coban4v6d67qsc2lmofonv7wkeuxgg55 #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/gcc" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/none-none/compiler-wrapper-1.1.0-pbd2ayub7z6lkuzpvgsnzfsycl3xbuy3/libexec/spack/gcc/gcc" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/g++" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/none-none/compiler-wrapper-1.1.0-pbd2ayub7z6lkuzpvgsnzfsycl3xbuy3/libexec/spack/gcc/g++" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/gfortran" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/none-none/compiler-wrapper-1.1.0-pbd2ayub7z6lkuzpvgsnzfsycl3xbuy3/libexec/spack/gcc/gfortran" CACHE PATH "") else() @@ -69,13 +69,13 @@ set(ENABLE_MPI ON CACHE BOOL "") # Cuda #------------------------------------------------ -set(CUDAToolkit_ROOT "/usr/tce/packages/cuda/cuda-12.9.1" CACHE PATH "") +set(CUDAToolkit_ROOT "/usr/tce/packages/cuda/cuda-13.1.1" CACHE PATH "") set(CMAKE_CUDA_COMPILER "${CUDAToolkit_ROOT}/bin/nvcc" CACHE PATH "") set(CMAKE_CUDA_HOST_COMPILER "${CMAKE_CXX_COMPILER}" CACHE PATH "") -set(CUDA_TOOLKIT_ROOT_DIR "/usr/tce/packages/cuda/cuda-12.9.1" CACHE PATH "") +set(CUDA_TOOLKIT_ROOT_DIR "/usr/tce/packages/cuda/cuda-13.1.1" CACHE PATH "") set(CMAKE_CUDA_ARCHITECTURES "90" CACHE STRING "") @@ -103,29 +103,29 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/gcc-13.3.1" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/gcc-13.3.1" CACHE PATH "") -set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-vlm6nb2rwwmvnxtnxw5p3o27rzsap6vt" CACHE PATH "") +set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.7-cj7f7ue7tuh5za3vyckhvazoaa5dirlz" CACHE PATH "") -set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-tyixdhhwshu35d2vuh6gw5bk3b3k7nqz" CACHE PATH "") +set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-ilhafup6lemuluscuhe2wcw6dhqwukac" CACHE PATH "") -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-dyhiljftw2yufyry2ybddjlzrst7usbg" CACHE PATH "") +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-tjktdj5puze2pgidvstbs54ivwo3p426" CACHE PATH "") -set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-zyfoez4ztydoqfeubcamjypyhprjpuca" CACHE PATH "") +set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-rephuaeqmzpcjiazcflzeswdvsbwuvd2" CACHE PATH "") set(LUA_DIR "/usr" CACHE PATH "") -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-hjzgnbnbz4sifpanhmuqhuuydzmctbbl" CACHE PATH "") +set(RAJA_DIR "${TPL_ROOT}/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-r7pmta3pbsca6mmndrycorx34iog7kp7" CACHE PATH "") -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-yxkn4g7yhresecqk2dkjivbahvu3qhpp" CACHE PATH "") +set(UMPIRE_DIR "${TPL_ROOT}/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-xub4ymu4oek5r2fsa3rwq3dv6vtkicur" CACHE PATH "") # OPENCASCADE not built -set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-nqiuo5ltc24jpawzi6mlp3xtmngcopiu" CACHE PATH "") +set(ADIAK_DIR "${TPL_ROOT}/adiak-0.5.0-rv7mu2xwq332xz2xqgfjtond4hvjlcvi" CACHE PATH "") -set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-apockdkmc2slp4nexkn6sbspz7pibric" CACHE PATH "") +set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-7t2cu7tn3ltyjwyknkt3dnsgru7iynhx" CACHE PATH "") -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-bt6ckjhqcfr3gjiu76sdhabhppjhjd6t" CACHE PATH "") +set(CAMP_DIR "${TPL_ROOT}/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-63o35ugwr356fphe57yjnkberdna3ehj" CACHE PATH "") # scr not built @@ -153,16 +153,16 @@ set(CPPCHECK_EXECUTABLE "${DEVTOOLS_ROOT}/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66x set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/kky5oskfhacihjb5gpy7qkeh6edlrtcb/doxygen-1.15.0/bin/doxygen" CACHE PATH "") -set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-e42etfacct5pxga65kryysakw52u4c63/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.12.0-2slokzflsbdyymtlubukr5uv352aaqol/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/none-none/py-pytest-9.0.3-gzmipids43fsxiidqzdglv4cbaxj26gk/lib/python3.13/site-packages" CACHE PATH "") -set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-to6o5czwkczlh46d6gkubixw5bhzxakl/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.6-owxvcv322niqkkfckectzayaxxwre7f4/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/none-none/py-pluggy-1.6.0-6zab4pck6v7johubbhalr2waowbwpjei/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/none-none/py-pluggy-1.6.0-7gqc6q7eser33fbaul2h4xxpqo67fphj/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/none-none/py-iniconfig-2.1.0-xwmbxuhk2oo3dcuizdp3z4hqbpqykkom/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/none-none/py-iniconfig-2.1.0-7flbc7ylpqkqn4swh7u5lojtj2ipn7bx/lib/python3.13/site-packages" CACHE PATH "") -set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-l3h5jmmg773bd5oer7makte3ln6rn7xv/lib/python3.13/site-packages" CACHE PATH "") +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-susobssvjtqo5inxlvwts2s5qacifmqy/lib/python3.13/site-packages" CACHE PATH "") diff --git a/host-configs/matrix-toss_4_x86_64_ib-llvm@19.1.3_cuda.cmake b/host-configs/matrix-toss_4_x86_64_ib-llvm@19.1.3_cuda.cmake index c707a14df8..9373971c87 100644 --- a/host-configs/matrix-toss_4_x86_64_ib-llvm@19.1.3_cuda.cmake +++ b/host-configs/matrix-toss_4_x86_64_ib-llvm@19.1.3_cuda.cmake @@ -4,28 +4,28 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.26.3/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3/blt-0.7.1-ux6wzebtj6vtgnayf4gqv2rr62ulwwfc;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3/c2c-1.8.0-wto5u3qsffjhbxirygr7kqqjlg55pyv6;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-lpeeewbzwhzppynnxvwzs6kz7dyblozq;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3/conduit-0.9.5-vqo2x2n6mz2vs4eg7kynwmlpeolsuim5;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3/mfem-4.9.0-6kl7ian5mzf5ypjvbjaghzk7s3ez62nz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3/py-nanobind-2.7.0-zxodixu5egwgeorhpe3ciggwsv3ndfxb;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-7hlq5rt36tbau3kruxvg7y62rs3wgn7k;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3/umpire-2025.12.0-feuig5ip7m5e2b4ejmchtyir77g5dxeb;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3/adiak-0.4.0-do3kop54yldi4hz43usrtnx2zyhvwfsv;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3/libunwind-1.8.3-ck74b2escwzf2xtpiafdvqwmat26uuoe;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3/hdf5-1.8.23-vpibouztpyzmgikg4dg2egwf2u53mmpl;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3/parmetis-4.0.3-fs7gps55rp6znnpz3b4zkd35jjkfycfc;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3/py-mpi4py-4.1.1-hrvj5zyy3xo2rbzvbz4ed7kjye7ssgmk;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3/py-numpy-2.4.2-73lpwcid33wlekelrxoh24535lzexwwg;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3/hypre-2.27.0-vksttbhyimswj3oxopuulk5xfa7nkhmk;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-biicbsrzigcqj4oofalk4povvcnibwea;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3/fmt-11.0.2-ekc2idnbqipl2pqb4nugxoa7sj7aigpo;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/none-none/gcc-runtime-13.3.1-xlvjd7vcdngtnvtxcdyqwtquvenekynf;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3/metis-5.1.0-7gsm27yycsippuh2l4fwei4dtvevbqgh;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.26.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66xzj4wm5kfqxyc4vgox7gb3;/usr/tce/packages/cuda/cuda-12.9.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/doxygen-1.15.0;/usr/tce/packages/gcc/gcc-13.3.1;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-clang-19.1.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/gcc-13.3.1/blt-0.7.2-kgttmwj56kwpge3oveotwgx7amhmc54b;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/llvm-19.1.3/c2c-1.8.0-cc4zkpkb3tvtflcodt4bhjiexgbd5pf3;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/llvm-19.1.3/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-bdkhoostmyjhyvbtlogmbet7fr3ebr2a;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/llvm-19.1.3/conduit-0.9.7-bcuudf26kmnocne4wa2pq4csunshwyqz;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/llvm-19.1.3/mfem-4.9.0-tkazvohdxofvabidsnxeutjpol7ryper;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/llvm-19.1.3/py-nanobind-2.12.0-jvqsaw246m2x5v3cw25qpwmgcjhbcxok;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/none-none/py-pytest-9.0.3-gzmipids43fsxiidqzdglv4cbaxj26gk;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/llvm-19.1.3/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-y37o3il4xkabmj5sqe7423ngtwupmpo7;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/llvm-19.1.3/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-w6deoinom2xrpucfsktvmpbzqssfazhd;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/llvm-19.1.3/adiak-0.5.0-bn7vvphqubchyzrll3ktmsihnzdbbb6b;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/llvm-19.1.3/libunwind-1.8.3-3j43eldxfwjlrs6gnf33vpfpox5tbqjr;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/llvm-19.1.3/hdf5-1.8.23-pqeunv22a7jyh66n7b4mrbvccatr32eu;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/llvm-19.1.3/parmetis-4.0.3-tc7yzrxqscciment55kdr5uzym5ijilj;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/llvm-19.1.3/py-mpi4py-4.1.1-rifw3jmftcx6ezx2qnb4u64gxpcptcl5;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/llvm-19.1.3/py-numpy-2.4.6-ygvifcqpoaj23qtkbllsgdgge62vtlzv;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/llvm-19.1.3/hypre-3.1.0-y36tujkd3l3kuwitqvi3h4nx7irf2adk;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/llvm-19.1.3/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-p4zhei2mmy2n4e3xkeq4npqxucppjfjk;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/llvm-19.1.3/fmt-12.1.0-ew4w4he3l62ai5pbto4li23fao2wa2u6;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/none-none/gcc-runtime-13.3.1-idl2e7ehceec4tq2xcci4n27sch4dkvu;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/llvm-19.1.3/metis-5.1.0-o23lru3echahpaspbjkpdjfudtgjg6vj;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/none-none/compiler-wrapper-1.1.0-pbd2ayub7z6lkuzpvgsnzfsycl3xbuy3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.26.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66xzj4wm5kfqxyc4vgox7gb3;/usr/tce/packages/cuda/cuda-13.1.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/doxygen-1.15.0;/usr/tce/packages/gcc/gcc-13.3.1;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-clang-19.1.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2026_02_17_14_42_14/view/python-3.13.11" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3/axom-develop-wxy5sh2abd2fekvyyulaxy3y3zrwcn7w/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3/axom-develop-wxy5sh2abd2fekvyyulaxy3y3zrwcn7w/lib64;;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/llvm-19.1.3-gy2lu5xbi4csr2k47emlajzfs5mlsd4g/lib/x86_64-unknown-linux-gnu;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13;/usr/tce/packages/clang/clang-19.1.3/lib;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/llvm-19.1.3/axom-develop-pbfyvrjwt2zyqilucf36p3t5ym6bstev/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/llvm-19.1.3/axom-develop-pbfyvrjwt2zyqilucf36p3t5ym6bstev/lib64;;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/llvm-19.1.3-gy2lu5xbi4csr2k47emlajzfs5mlsd4g/lib/x86_64-unknown-linux-gnu;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13;/usr/tce/packages/clang/clang-19.1.3/lib;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3/axom-develop-wxy5sh2abd2fekvyyulaxy3y3zrwcn7w/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3/axom-develop-wxy5sh2abd2fekvyyulaxy3y3zrwcn7w/lib64;;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/llvm-19.1.3-gy2lu5xbi4csr2k47emlajzfs5mlsd4g/lib/x86_64-unknown-linux-gnu;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13;/usr/tce/packages/clang/clang-19.1.3/lib;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/llvm-19.1.3/axom-develop-pbfyvrjwt2zyqilucf36p3t5ym6bstev/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/llvm-19.1.3/axom-develop-pbfyvrjwt2zyqilucf36p3t5ym6bstev/lib64;;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/llvm-19.1.3-gy2lu5xbi4csr2k47emlajzfs5mlsd4g/lib/x86_64-unknown-linux-gnu;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13;/usr/tce/packages/clang/clang-19.1.3/lib;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ # Compilers #------------------------------------------------------------------------------ -# Compiler Spec: llvm@19.1.3/m2tcgbxfcdjwdmyrkqwamlljh4qeiie2 +# Compiler Spec: llvm@19.1.3/pa7jv5ejrkrucsgwtv4kde2cazhq3v6i #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/clang/clang" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/none-none/compiler-wrapper-1.1.0-pbd2ayub7z6lkuzpvgsnzfsycl3xbuy3/libexec/spack/clang/clang" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/clang/clang++" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/none-none/compiler-wrapper-1.1.0-pbd2ayub7z6lkuzpvgsnzfsycl3xbuy3/libexec/spack/clang/clang++" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/none-none/compiler-wrapper-1.0-uesxbqunebynqp5g2i6r3corj4oval5q/libexec/spack/gcc/gfortran" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/none-none/compiler-wrapper-1.1.0-pbd2ayub7z6lkuzpvgsnzfsycl3xbuy3/libexec/spack/gcc/gfortran" CACHE PATH "") else() @@ -71,13 +71,13 @@ set(ENABLE_MPI ON CACHE BOOL "") # Cuda #------------------------------------------------ -set(CUDAToolkit_ROOT "/usr/tce/packages/cuda/cuda-12.9.1" CACHE PATH "") +set(CUDAToolkit_ROOT "/usr/tce/packages/cuda/cuda-13.1.1" CACHE PATH "") set(CMAKE_CUDA_COMPILER "${CUDAToolkit_ROOT}/bin/nvcc" CACHE PATH "") set(CMAKE_CUDA_HOST_COMPILER "${CMAKE_CXX_COMPILER}" CACHE PATH "") -set(CUDA_TOOLKIT_ROOT_DIR "/usr/tce/packages/cuda/cuda-12.9.1" CACHE PATH "") +set(CUDA_TOOLKIT_ROOT_DIR "/usr/tce/packages/cuda/cuda-13.1.1" CACHE PATH "") set(CMAKE_CUDA_ARCHITECTURES "90" CACHE STRING "") @@ -105,29 +105,29 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/llvm-19.1.3" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/llvm-19.1.3" CACHE PATH "") -set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-vqo2x2n6mz2vs4eg7kynwmlpeolsuim5" CACHE PATH "") +set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.7-bcuudf26kmnocne4wa2pq4csunshwyqz" CACHE PATH "") -set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-wto5u3qsffjhbxirygr7kqqjlg55pyv6" CACHE PATH "") +set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-cc4zkpkb3tvtflcodt4bhjiexgbd5pf3" CACHE PATH "") -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-6kl7ian5mzf5ypjvbjaghzk7s3ez62nz" CACHE PATH "") +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-tkazvohdxofvabidsnxeutjpol7ryper" CACHE PATH "") -set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-vpibouztpyzmgikg4dg2egwf2u53mmpl" CACHE PATH "") +set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-pqeunv22a7jyh66n7b4mrbvccatr32eu" CACHE PATH "") set(LUA_DIR "/usr" CACHE PATH "") -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-7hlq5rt36tbau3kruxvg7y62rs3wgn7k" CACHE PATH "") +set(RAJA_DIR "${TPL_ROOT}/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-y37o3il4xkabmj5sqe7423ngtwupmpo7" CACHE PATH "") -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-feuig5ip7m5e2b4ejmchtyir77g5dxeb" CACHE PATH "") +set(UMPIRE_DIR "${TPL_ROOT}/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-w6deoinom2xrpucfsktvmpbzqssfazhd" CACHE PATH "") # OPENCASCADE not built -set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-do3kop54yldi4hz43usrtnx2zyhvwfsv" CACHE PATH "") +set(ADIAK_DIR "${TPL_ROOT}/adiak-0.5.0-bn7vvphqubchyzrll3ktmsihnzdbbb6b" CACHE PATH "") -set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-lpeeewbzwhzppynnxvwzs6kz7dyblozq" CACHE PATH "") +set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-bdkhoostmyjhyvbtlogmbet7fr3ebr2a" CACHE PATH "") -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-biicbsrzigcqj4oofalk4povvcnibwea" CACHE PATH "") +set(CAMP_DIR "${TPL_ROOT}/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-p4zhei2mmy2n4e3xkeq4npqxucppjfjk" CACHE PATH "") # scr not built @@ -155,16 +155,16 @@ set(CPPCHECK_EXECUTABLE "${DEVTOOLS_ROOT}/gcc-13.3.1/cppcheck-2.18.0-i3p56kpf66x set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/kky5oskfhacihjb5gpy7qkeh6edlrtcb/doxygen-1.15.0/bin/doxygen" CACHE PATH "") -set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-zxodixu5egwgeorhpe3ciggwsv3ndfxb/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.12.0-jvqsaw246m2x5v3cw25qpwmgcjhbcxok/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/none-none/py-pytest-9.0.0-vozuj6nkk42nhxeo5mawnux3w7bj3awe/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/none-none/py-pytest-9.0.3-gzmipids43fsxiidqzdglv4cbaxj26gk/lib/python3.13/site-packages" CACHE PATH "") -set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-73lpwcid33wlekelrxoh24535lzexwwg/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.6-ygvifcqpoaj23qtkbllsgdgge62vtlzv/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/none-none/py-pluggy-1.6.0-6zab4pck6v7johubbhalr2waowbwpjei/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/none-none/py-pluggy-1.6.0-7gqc6q7eser33fbaul2h4xxpqo67fphj/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_04_22_11_56_41/none-none/py-iniconfig-2.1.0-xwmbxuhk2oo3dcuizdp3z4hqbpqykkom/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib/2026_07_07_11_57_07/none-none/py-iniconfig-2.1.0-7flbc7ylpqkqn4swh7u5lojtj2ipn7bx/lib/python3.13/site-packages" CACHE PATH "") -set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-hrvj5zyy3xo2rbzvbz4ed7kjye7ssgmk/lib/python3.13/site-packages" CACHE PATH "") +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-rifw3jmftcx6ezx2qnb4u64gxpcptcl5/lib/python3.13/site-packages" CACHE PATH "") diff --git a/host-configs/tioga-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake b/host-configs/tioga-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake deleted file mode 100644 index a5afa28ab3..0000000000 --- a/host-configs/tioga-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake +++ /dev/null @@ -1,169 +0,0 @@ -#------------------------------------------------------------------------------ -# !!!! This is a generated file, edit at own risk !!!! -#------------------------------------------------------------------------------ -# CMake executable path: /usr/tce/packages/cmake/cmake-3.29.2/bin/cmake -#------------------------------------------------------------------------------ - -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/blt-0.7.1-tj6h3jr6fjdlxux2yd4wvdy7xwxxuh23;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/c2c-1.8.0-xav3pjepsvpzrcqd5dcom4is4rioejzr;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-37ef6g3godgxug45i3u3x26awgovbhxa;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/conduit-0.9.5-oyjekm66om6cx7yndhpv3yumtxgaaroi;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/lua-5.4.8-ty6vxrgtb5lsthp7rrcy7vx3x4yknrms;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/mfem-4.9.0-iiiowpg2cxn346mv67ycscrcrt5eacdk;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/py-nanobind-2.7.0-h7v5ggotyqa5ul7kpyc2yfojjkah4ium;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/adiak-0.4.0-w7lj246x4whjefszgyhapkebe3cjd7bi;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/libunwind-1.8.3-htd47rshpgaa6w73jemf7tycnnlkn5ws;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/hdf5-1.8.23-vy4rnk6awhcfgte2wuwisgp3t3y4gzwy;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/parmetis-4.0.3-jqlganykrtm62xb3ve2eetb3gltn5nki;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/py-mpi4py-4.1.1-gla2ur2g4lb44jyi2tky6p2ynzgseonn;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/py-numpy-2.4.2-vdrslpslcsdjba3zyryp27z4tmpn3ayu;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/hypre-2.27.0-gjrhbi2v66y2yfxgq7vegqpcawo2b35r;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-iwmhvbuhywrcyi4ojshrizfpccb53g3u;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/umpire-2025.12.0-qkp2thztbvdojybrli7tpxea5duxagkn;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/zlib-1.3.1-6yddik2qxvkwtfcckdhbdgos7awok5za;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/metis-5.1.0-bryzqxg7sgprevvt3ux5np2uxam4wbvp;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-xdstcq3vkkl6gysd7agincseoncdi5nz;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/fmt-11.0.2-vjndv6co4e6qonqazcmklw3pamvmos74;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cce-tce/cce-20.0.0;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.32-rocmcc-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3" CACHE STRING "") - -set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") - -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/axom-develop-scv2k3ekpxlqvhgfmb6tt3qfk6vez3q5/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/axom-develop-scv2k3ekpxlqvhgfmb6tt3qfk6vez3q5/lib64;;/opt/cray/pe/cce/20.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") - -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/axom-develop-scv2k3ekpxlqvhgfmb6tt3qfk6vez3q5/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0/axom-develop-scv2k3ekpxlqvhgfmb6tt3qfk6vez3q5/lib64;;/opt/cray/pe/cce/20.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") - -set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") - -#------------------------------------------------------------------------------ -# Compilers -#------------------------------------------------------------------------------ -# Compiler Spec: cce@20.0.0/v7nrkimihxta4odatu45wrllosgeklrd -#------------------------------------------------------------------------------ -if(DEFINED ENV{SPACK_CC}) - - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/cce/craycc" CACHE PATH "") - - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/cce/case-insensitive/crayCC" CACHE PATH "") - - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/cce/crayftn" CACHE PATH "") - -else() - - set(CMAKE_C_COMPILER "/usr/tce/packages/cce-tce/cce-20.0.0/bin/craycc" CACHE PATH "") - - set(CMAKE_CXX_COMPILER "/usr/tce/packages/cce-tce/cce-20.0.0/bin/crayCC" CACHE PATH "") - - set(CMAKE_Fortran_COMPILER "/usr/tce/packages/cce-tce/cce-20.0.0/bin/crayftn" CACHE PATH "") - -endif() - -set(CMAKE_Fortran_FLAGS "-ef" CACHE STRING "") - -set(ENABLE_FORTRAN ON CACHE BOOL "") - -set(CMAKE_CXX_FLAGS_DEBUG "-O1 -g" CACHE STRING "") - -#------------------------------------------------------------------------------ -# MPI -#------------------------------------------------------------------------------ - -set(MPI_C_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.32-rocmcc-6.4.3/bin/mpicc" CACHE PATH "") - -set(MPI_CXX_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.32-rocmcc-6.4.3/bin/mpicxx" CACHE PATH "") - -set(MPI_Fortran_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.32-rocmcc-6.4.3/bin/mpif90" CACHE PATH "") - -set(MPIEXEC_EXECUTABLE "/usr/global/tools/flux_wrappers/bin/srun" CACHE PATH "") - -set(MPIEXEC_NUMPROC_FLAG "-n" CACHE STRING "") - -set(ENABLE_MPI ON CACHE BOOL "") - -#------------------------------------------------------------------------------ -# Hardware -#------------------------------------------------------------------------------ - -#------------------------------------------------ -# ROCm -#------------------------------------------------ - -set(ROCM_PATH "/opt/rocm-6.4.3" CACHE PATH "") - -set(CMAKE_HIP_ARCHITECTURES "gfx90a;gfx942" CACHE STRING "") - -set(CMAKE_HIP_COMPILER "/opt/rocm-6.4.3/bin/amdclang++" CACHE FILEPATH "") - -#------------------------------------------------------------------------------ - -# Axom ROCm specifics - -#------------------------------------------------------------------------------ - - -set(ENABLE_HIP ON CACHE BOOL "") - -set(ROCM_ROOT_DIR "/opt/rocm-6.4.3" CACHE PATH "") - -set(BLT_CMAKE_IMPLICIT_LINK_LIBRARIES_EXCLUDE "unwind;ompstub" CACHE STRING "") - -set(CMAKE_EXE_LINKER_FLAGS "-lxpmem -L/opt/cray/pe/mpich/8.1.32/gtl/lib -Wl,-rpath,/opt/cray/pe/mpich/8.1.32/gtl/lib -lmpi_gtl_hsa -L/opt/rocm-6.4.3/lib/llvm/lib -Wl,-rpath,/opt/rocm-6.4.3/lib/llvm/lib -L/opt/rocm-6.4.3/lib -Wl,-rpath,/opt/rocm-6.4.3/lib -lpgmath -L/opt/cray/pe/cce/20.0.0/cce/x86_64/lib -Wl,-rpath,/opt/cray/pe/cce/20.0.0/cce/x86_64/lib -lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " CACHE STRING "") - -#------------------------------------------------ -# Hardware Specifics -#------------------------------------------------ - -set(ENABLE_OPENMP ON CACHE BOOL "") - -set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") - -set(BLT_OPENMP_COMPILE_FLAGS "$<$>:-fopenmp=libomp>;$<$:-fopenmp>" CACHE STRING "Different OpenMP compile & link flags between HIP and CXX compilers (amdclang++)") - -set(BLT_OPENMP_LINK_FLAGS "$<$>:-fopenmp=libomp>;$<$:-fopenmp>" CACHE STRING "Different OpenMP compile & link flags between HIP and CXX compilers (amdclang++)") - -#------------------------------------------------------------------------------ -# TPLs -#------------------------------------------------------------------------------ - -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/cce-20.0.0" CACHE PATH "") - -set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-oyjekm66om6cx7yndhpv3yumtxgaaroi" CACHE PATH "") - -set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-xav3pjepsvpzrcqd5dcom4is4rioejzr" CACHE PATH "") - -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-iiiowpg2cxn346mv67ycscrcrt5eacdk" CACHE PATH "") - -set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-vy4rnk6awhcfgte2wuwisgp3t3y4gzwy" CACHE PATH "") - -set(LUA_DIR "${TPL_ROOT}/lua-5.4.8-ty6vxrgtb5lsthp7rrcy7vx3x4yknrms" CACHE PATH "") - -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-iwmhvbuhywrcyi4ojshrizfpccb53g3u" CACHE PATH "") - -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-qkp2thztbvdojybrli7tpxea5duxagkn" CACHE PATH "") - -# OPENCASCADE not built - -set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-w7lj246x4whjefszgyhapkebe3cjd7bi" CACHE PATH "") - -set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-37ef6g3godgxug45i3u3x26awgovbhxa" CACHE PATH "") - -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-xdstcq3vkkl6gysd7agincseoncdi5nz" CACHE PATH "") - -# scr not built - -#------------------------------------------------------------------------------ -# Devtools & Python -#------------------------------------------------------------------------------ - -set(DEVTOOLS_ROOT "/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25" CACHE PATH "") - -set(CLANGFORMAT_EXECUTABLE "/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm/bin/clang-format" CACHE PATH "") - -set(Python_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/python3" CACHE PATH "") - -set(JSONSCHEMA_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/jsonschema" CACHE PATH "") - -set(ENABLE_DOCS ON CACHE BOOL "") - -set(SPHINX_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/sphinx-build" CACHE PATH "") - -set(YAPF_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/yapf" CACHE PATH "") - -set(SHROUD_EXECUTABLE "/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0/bin/shroud" CACHE PATH "") - -set(CPPCHECK_EXECUTABLE "${DEVTOOLS_ROOT}/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27/bin/cppcheck" CACHE PATH "") - -set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/doxygen-1.15.0/bin/doxygen" CACHE PATH "") - -set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-h7v5ggotyqa5ul7kpyc2yfojjkah4ium/lib/python3.13/site-packages" CACHE PATH "") - -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj/lib/python3.13/site-packages" CACHE PATH "") - -set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-vdrslpslcsdjba3zyryp27z4tmpn3ayu/lib/python3.13/site-packages" CACHE PATH "") - -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/py-pluggy-1.6.0-27aoz44dedjxa6j6r6cavpqyqmpknidb/lib/python3.13/site-packages" CACHE PATH "") - -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/py-iniconfig-2.1.0-7rty7n4wq6kxxrzflm7wbxqzhhc6ph7q/lib/python3.13/site-packages" CACHE PATH "") - -set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-gla2ur2g4lb44jyi2tky6p2ynzgseonn/lib/python3.13/site-packages" CACHE PATH "") - - diff --git a/host-configs/tioga-toss_4_x86_64_ib_cray-cce@21.0.0_hip.cmake b/host-configs/tioga-toss_4_x86_64_ib_cray-cce@21.0.0_hip.cmake new file mode 100644 index 0000000000..844ffc1420 --- /dev/null +++ b/host-configs/tioga-toss_4_x86_64_ib_cray-cce@21.0.0_hip.cmake @@ -0,0 +1,171 @@ +#------------------------------------------------------------------------------ +# !!!! This is a generated file, edit at own risk !!!! +#------------------------------------------------------------------------------ +# CMake executable path: /usr/tce/packages/cmake/cmake-3.29.2/bin/cmake +#------------------------------------------------------------------------------ + +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/cce-21.0.0/blt-0.7.2-qcnw7gz36yrtqxyhknwmllqxiq6dfb6s;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/cce-21.0.0/c2c-1.8.0-hjdyjpgqtmpwjfn4duo46obwwpgk4ksg;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/cce-21.0.0/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-y5wnj5sz23xeqfif4w6y7q34ctrx7gjj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/cce-21.0.0/conduit-0.9.7-zmsisfkaybg6pmsjielb4hmdx73vdzpf;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/cce-21.0.0/lua-5.4.8-5uxxpoxmmlbb7ycut4ipoammpgvb4rec;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/cce-21.0.0/mfem-4.9.0-stqwo5oobxtngqnfhi5jw2ycnkbrmpqe;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/cce-21.0.0/py-nanobind-2.12.0-on6m44wfnk4okaxandfqbhe7s5mp54me;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/none-none/py-pytest-9.0.3-2suvy7b3lmshqhnbylnif2jz2735dbbe;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/cce-21.0.0/adiak-0.5.0-dmhmhtynt2mfhoq7u6f7av3kqfdonm2x;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/cce-21.0.0/libunwind-1.8.3-vxrm4ca45t6tnuaslyozmbtbxx537qtw;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/cce-21.0.0/hdf5-1.8.23-37k7d6kxmofx22lygi5u2lc2qdpb2tki;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/cce-21.0.0/parmetis-4.0.3-cqtdekoqt5lzzx75l6fwbty4adcbuxh4;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/cce-21.0.0/py-mpi4py-4.1.1-cw3kxr5n6s7yqijn2rjjxwml4och5p47;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/cce-21.0.0/py-numpy-2.4.6-z2qb4fs2yqhjjyogkhvmrnrldxrwue5x;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/cce-21.0.0/hypre-3.1.0-omodg4j7lrrqcv7upadxp2noucgc6al7;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/cce-21.0.0/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-gxewkhrwy4674kazjiqjuz6kjavx6dll;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/cce-21.0.0/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-ljw6iam75tymsghtjygomslfzlwgjvpn;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/cce-21.0.0/zlib-1.3.2-h26urlixzfwlyzkpktmknpvojoxzkolk;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/cce-21.0.0/metis-5.1.0-xukleyfstw2jisuarjubgfgymgfnvlbu;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/cce-21.0.0/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-sjgym4hdzfj2p7rszzr27t7maxp4lpk2;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/cce-21.0.0/fmt-12.1.0-5xa4prr2jcvhwnwgu2stc3j62tgckgps;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/none-none/compiler-wrapper-1.1.0-qqipzhe4rz5fmoiuvlqgarjeevl2kzuh;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cce-tce/cce-21.0.0;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-9.1.0-cce-21.0.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-7.2.1;/opt/rocm-7.2.1;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-7.2.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-7.2.1;/opt/rocm-7.2.1;/opt/rocm-7.2.1;/opt/rocm-7.2.1;/opt/rocm-7.2.1" CACHE STRING "") + +set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") + +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/cce-21.0.0/axom-develop-ilegqoq3lzjh6f4cczurkzoqyw6gigds/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/cce-21.0.0/axom-develop-ilegqoq3lzjh6f4cczurkzoqyw6gigds/lib64;;/opt/cray/pe/cce/21.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") + +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/cce-21.0.0/axom-develop-ilegqoq3lzjh6f4cczurkzoqyw6gigds/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/cce-21.0.0/axom-develop-ilegqoq3lzjh6f4cczurkzoqyw6gigds/lib64;;/opt/cray/pe/cce/21.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") + +set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") + +#------------------------------------------------------------------------------ +# Compilers +#------------------------------------------------------------------------------ +# Compiler Spec: cce@21.0.0/nmaros4ushwf5auvt3asaaazfjerle4l +#------------------------------------------------------------------------------ +if(DEFINED ENV{SPACK_CC}) + + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/none-none/compiler-wrapper-1.1.0-qqipzhe4rz5fmoiuvlqgarjeevl2kzuh/libexec/spack/cce/craycc" CACHE PATH "") + + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/none-none/compiler-wrapper-1.1.0-qqipzhe4rz5fmoiuvlqgarjeevl2kzuh/libexec/spack/cce/case-insensitive/crayCC" CACHE PATH "") + + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/none-none/compiler-wrapper-1.1.0-qqipzhe4rz5fmoiuvlqgarjeevl2kzuh/libexec/spack/cce/crayftn" CACHE PATH "") + +else() + + set(CMAKE_C_COMPILER "/usr/tce/packages/cce-tce/cce-21.0.0/bin/craycc" CACHE PATH "") + + set(CMAKE_CXX_COMPILER "/usr/tce/packages/cce-tce/cce-21.0.0/bin/crayCC" CACHE PATH "") + + set(CMAKE_Fortran_COMPILER "/usr/tce/packages/cce-tce/cce-21.0.0/bin/crayftn" CACHE PATH "") + +endif() + +set(CMAKE_C_FLAGS "-fPIC" CACHE STRING "") + +set(CMAKE_CXX_FLAGS "-fPIC" CACHE STRING "") + +set(CMAKE_Fortran_FLAGS "-fPIC -ef" CACHE STRING "") + +set(ENABLE_FORTRAN ON CACHE BOOL "") + +set(CMAKE_CXX_FLAGS_DEBUG "-O1 -g" CACHE STRING "") + +#------------------------------------------------------------------------------ +# MPI +#------------------------------------------------------------------------------ + +set(MPI_C_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-9.1.0-cce-21.0.0/bin/mpicc" CACHE PATH "") + +set(MPI_CXX_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-9.1.0-cce-21.0.0/bin/mpicxx" CACHE PATH "") + +set(MPI_Fortran_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-9.1.0-cce-21.0.0/bin/mpif90" CACHE PATH "") + +set(MPIEXEC_EXECUTABLE "/usr/global/tools/flux_wrappers/bin/srun" CACHE PATH "") + +set(MPIEXEC_NUMPROC_FLAG "-n" CACHE STRING "") + +set(ENABLE_MPI ON CACHE BOOL "") + +#------------------------------------------------------------------------------ +# Hardware +#------------------------------------------------------------------------------ + +#------------------------------------------------ +# ROCm +#------------------------------------------------ + +set(ROCM_PATH "/opt/rocm-7.2.1" CACHE PATH "") + +set(CMAKE_HIP_ARCHITECTURES "gfx90a;gfx942" CACHE STRING "") + +set(CMAKE_HIP_COMPILER "/opt/rocm-7.2.1/bin/amdclang++" CACHE FILEPATH "") + +#------------------------------------------------------------------------------ + +# Axom ROCm specifics + +#------------------------------------------------------------------------------ + + +set(ENABLE_HIP ON CACHE BOOL "") + +set(ROCM_ROOT_DIR "/opt/rocm-7.2.1" CACHE PATH "") + +set(BLT_CMAKE_IMPLICIT_LINK_LIBRARIES_EXCLUDE "unwind;ompstub" CACHE STRING "") + +set(BLT_CMAKE_IMPLICIT_LINK_DIRECTORIES_EXCLUDE "/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12;/opt/rh/gcc-toolset-12/root/usr/lib64" CACHE STRING "") + +set(CMAKE_EXE_LINKER_FLAGS "-lxpmem -L/opt/cray/pe/mpich/9.1.0/gtl/lib -Wl,-rpath,/opt/cray/pe/mpich/9.1.0/gtl/lib -lmpi_gtl_hsa -L/opt/rocm-7.2.1/lib/llvm/lib -Wl,-rpath,/opt/rocm-7.2.1/lib/llvm/lib -L/opt/rocm-7.2.1/lib -Wl,-rpath,/opt/rocm-7.2.1/lib -lpgmath -L/opt/cray/pe/cce/21.0.0/cce/x86_64/lib -Wl,-rpath,/opt/cray/pe/cce/21.0.0/cce/x86_64/lib-lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " CACHE STRING "") + +#------------------------------------------------ +# Hardware Specifics +#------------------------------------------------ + +set(ENABLE_OPENMP ON CACHE BOOL "") + +set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") + +#------------------------------------------------------------------------------ +# TPLs +#------------------------------------------------------------------------------ + +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/cce-21.0.0" CACHE PATH "") + +set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.7-zmsisfkaybg6pmsjielb4hmdx73vdzpf" CACHE PATH "") + +set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-hjdyjpgqtmpwjfn4duo46obwwpgk4ksg" CACHE PATH "") + +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-stqwo5oobxtngqnfhi5jw2ycnkbrmpqe" CACHE PATH "") + +set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-37k7d6kxmofx22lygi5u2lc2qdpb2tki" CACHE PATH "") + +set(LUA_DIR "${TPL_ROOT}/lua-5.4.8-5uxxpoxmmlbb7ycut4ipoammpgvb4rec" CACHE PATH "") + +set(RAJA_DIR "${TPL_ROOT}/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-gxewkhrwy4674kazjiqjuz6kjavx6dll" CACHE PATH "") + +set(UMPIRE_DIR "${TPL_ROOT}/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-ljw6iam75tymsghtjygomslfzlwgjvpn" CACHE PATH "") + +# OPENCASCADE not built + +set(ADIAK_DIR "${TPL_ROOT}/adiak-0.5.0-dmhmhtynt2mfhoq7u6f7av3kqfdonm2x" CACHE PATH "") + +set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-y5wnj5sz23xeqfif4w6y7q34ctrx7gjj" CACHE PATH "") + +set(CAMP_DIR "${TPL_ROOT}/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-sjgym4hdzfj2p7rszzr27t7maxp4lpk2" CACHE PATH "") + +# scr not built + +#------------------------------------------------------------------------------ +# Devtools & Python +#------------------------------------------------------------------------------ + +set(DEVTOOLS_ROOT "/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25" CACHE PATH "") + +set(CLANGFORMAT_EXECUTABLE "/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm/bin/clang-format" CACHE PATH "") + +set(Python_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/python3" CACHE PATH "") + +set(JSONSCHEMA_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/jsonschema" CACHE PATH "") + +set(ENABLE_DOCS ON CACHE BOOL "") + +set(SPHINX_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/sphinx-build" CACHE PATH "") + +set(YAPF_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/yapf" CACHE PATH "") + +set(SHROUD_EXECUTABLE "/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0/bin/shroud" CACHE PATH "") + +set(CPPCHECK_EXECUTABLE "${DEVTOOLS_ROOT}/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27/bin/cppcheck" CACHE PATH "") + +set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/doxygen-1.15.0/bin/doxygen" CACHE PATH "") + +set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.12.0-on6m44wfnk4okaxandfqbhe7s5mp54me/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/none-none/py-pytest-9.0.3-2suvy7b3lmshqhnbylnif2jz2735dbbe/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.6-z2qb4fs2yqhjjyogkhvmrnrldxrwue5x/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/none-none/py-pluggy-1.6.0-eemjlppu7ynjgedbqabclpfgxt4hxdoh/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/none-none/py-iniconfig-2.1.0-kss2a5udi5pgct5uvmgjh3ojkir5hpoo/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-cw3kxr5n6s7yqijn2rjjxwml4och5p47/lib/python3.13/site-packages" CACHE PATH "") + + diff --git a/host-configs/tioga-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake b/host-configs/tioga-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake deleted file mode 100644 index 63b059e020..0000000000 --- a/host-configs/tioga-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake +++ /dev/null @@ -1,163 +0,0 @@ -#------------------------------------------------------------------------------ -# !!!! This is a generated file, edit at own risk !!!! -#------------------------------------------------------------------------------ -# CMake executable path: /usr/tce/packages/cmake/cmake-3.29.2/bin/cmake -#------------------------------------------------------------------------------ - -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1/adiak-0.4.0-vlugt2gwkx5wzlwudznxg3p2plq76pjq;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1/blt-0.7.1-sv74qxlkpkll7mm6dhfjagyfog254w7c;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1/c2c-1.8.0-tfrrofjzn3rdvyktevespobwrpjai5j4;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1/conduit-0.9.5-vnlbr73jbwhqyvh2q76csdtve3ekpncc;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1/lua-5.4.8-e3i2pbc52bfhrnxbaniektzp3x5jvyxr;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1/mfem-4.9.0-jxeezxksp3c7i53h6dhqf2zz6gnaipub;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1/py-nanobind-2.7.0-4sgpkalsntbmuim5oltb7ue4gggt6rv5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1/hdf5-1.8.23-liive5kzeooveetbjhil4ic7hi3ccsg4;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1/parmetis-4.0.3-fiyw3adearn3nmxjifcqtuzwy24fshp5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1/py-mpi4py-4.1.1-ukxrsa2wugnmhh3glq25muf5hqexaf62;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1/py-numpy-2.4.2-ij66enxl4hmrmwua5bsec2yslylneqbw;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1/hypre-2.27.0-cnukrpfl32kl5q6ud4wozdqbgtqoo7jx;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-5c3lqbqcjmekrwbfeg6ogptz7ssj3n4y;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1/umpire-2025.12.0-i2dwv2l2jra74eazijtvprcx7acsupx4;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1/zlib-1.3.1-dsduxyfnks4bhrkpbskff35l5y36ofp7;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1/metis-5.1.0-6mtxrj6jbrvyd3xpq4pvluywbndjx42w;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-7nvneljwisxwircaqydhfcl3hl65bmr2;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1/fmt-11.0.2-kw3vpg2ajhrgi7kozhcioz3nrgc6fh3o;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.3.1;/opt/rocm-6.3.1;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.3.1;/opt/rocm-6.3.1;/opt/rocm-6.3.1" CACHE STRING "") - -set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") - -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1/axom-develop-7trkhc3ctsx3bgt6uv2brdgeeyalxomu/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1/axom-develop-7trkhc3ctsx3bgt6uv2brdgeeyalxomu/lib64;;/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12" CACHE STRING "") - -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1/axom-develop-7trkhc3ctsx3bgt6uv2brdgeeyalxomu/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1/axom-develop-7trkhc3ctsx3bgt6uv2brdgeeyalxomu/lib64;;/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12" CACHE STRING "") - -set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") - -#------------------------------------------------------------------------------ -# Compilers -#------------------------------------------------------------------------------ -# Compiler Spec: llvm-amdgpu@6.3.1/ns2pv5bzkpjolxzmhgkckj6glqymgd7e -#------------------------------------------------------------------------------ -if(DEFINED ENV{SPACK_CC}) - - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdclang" CACHE PATH "") - - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdclang++" CACHE PATH "") - - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdflang" CACHE PATH "") - -else() - - set(CMAKE_C_COMPILER "/opt/rocm-6.3.1/llvm/bin/amdclang" CACHE PATH "") - - set(CMAKE_CXX_COMPILER "/opt/rocm-6.3.1/llvm/bin/amdclang++" CACHE PATH "") - - set(CMAKE_Fortran_COMPILER "/opt/rocm-6.3.1/llvm/bin/amdflang" CACHE PATH "") - -endif() - -set(CMAKE_Fortran_FLAGS "-Mfreeform" CACHE STRING "") - -set(ENABLE_FORTRAN ON CACHE BOOL "") - -#------------------------------------------------------------------------------ -# MPI -#------------------------------------------------------------------------------ - -set(MPI_C_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.3.1/bin/mpicc" CACHE PATH "") - -set(MPI_CXX_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.3.1/bin/mpicxx" CACHE PATH "") - -set(MPI_Fortran_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.3.1/bin/mpif90" CACHE PATH "") - -set(MPIEXEC_EXECUTABLE "/usr/global/tools/flux_wrappers/bin/srun" CACHE PATH "") - -set(MPIEXEC_NUMPROC_FLAG "-n" CACHE STRING "") - -set(ENABLE_MPI ON CACHE BOOL "") - -#------------------------------------------------------------------------------ -# Hardware -#------------------------------------------------------------------------------ - -#------------------------------------------------ -# ROCm -#------------------------------------------------ - -set(ROCM_PATH "/opt/rocm-6.3.1" CACHE PATH "") - -set(CMAKE_HIP_ARCHITECTURES "gfx90a;gfx942" CACHE STRING "") - -set(CMAKE_HIP_COMPILER "/opt/rocm-6.3.1/bin/amdclang++" CACHE FILEPATH "") - -#------------------------------------------------------------------------------ - -# Axom ROCm specifics - -#------------------------------------------------------------------------------ - - -set(ENABLE_HIP ON CACHE BOOL "") - -set(ROCM_ROOT_DIR "/opt/rocm-6.3.1" CACHE PATH "") - -set(BLT_CMAKE_IMPLICIT_LINK_LIBRARIES_EXCLUDE "ompstub" CACHE STRING "") - -set(CMAKE_EXE_LINKER_FLAGS "-lxpmem -L/opt/cray/pe/mpich/8.1.29/gtl/lib -Wl,-rpath,/opt/cray/pe/mpich/8.1.29/gtl/lib -lmpi_gtl_hsa -L/opt/rocm-6.3.1/lib/llvm/lib -Wl,-rpath,/opt/rocm-6.3.1/lib/llvm/lib -L/opt/rocm-6.3.1/lib -Wl,-rpath,/opt/rocm-6.3.1/lib -lpgmath -Wl,--disable-new-dtags -lflang -lflangrti -lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " CACHE STRING "") - -#------------------------------------------------ -# Hardware Specifics -#------------------------------------------------ - -set(ENABLE_OPENMP ON CACHE BOOL "") - -set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") - -#------------------------------------------------------------------------------ -# TPLs -#------------------------------------------------------------------------------ - -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.3.1" CACHE PATH "") - -set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-vnlbr73jbwhqyvh2q76csdtve3ekpncc" CACHE PATH "") - -set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-tfrrofjzn3rdvyktevespobwrpjai5j4" CACHE PATH "") - -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-jxeezxksp3c7i53h6dhqf2zz6gnaipub" CACHE PATH "") - -set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-liive5kzeooveetbjhil4ic7hi3ccsg4" CACHE PATH "") - -set(LUA_DIR "${TPL_ROOT}/lua-5.4.8-e3i2pbc52bfhrnxbaniektzp3x5jvyxr" CACHE PATH "") - -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-5c3lqbqcjmekrwbfeg6ogptz7ssj3n4y" CACHE PATH "") - -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-i2dwv2l2jra74eazijtvprcx7acsupx4" CACHE PATH "") - -# OPENCASCADE not built - -set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-vlugt2gwkx5wzlwudznxg3p2plq76pjq" CACHE PATH "") - -# CALIPER not built - -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-7nvneljwisxwircaqydhfcl3hl65bmr2" CACHE PATH "") - -# scr not built - -#------------------------------------------------------------------------------ -# Devtools & Python -#------------------------------------------------------------------------------ - -set(DEVTOOLS_ROOT "/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25" CACHE PATH "") - -set(CLANGFORMAT_EXECUTABLE "/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm/bin/clang-format" CACHE PATH "") - -set(Python_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/python3" CACHE PATH "") - -set(JSONSCHEMA_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/jsonschema" CACHE PATH "") - -set(ENABLE_DOCS ON CACHE BOOL "") - -set(SPHINX_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/sphinx-build" CACHE PATH "") - -set(YAPF_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/yapf" CACHE PATH "") - -set(SHROUD_EXECUTABLE "/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0/bin/shroud" CACHE PATH "") - -set(CPPCHECK_EXECUTABLE "${DEVTOOLS_ROOT}/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27/bin/cppcheck" CACHE PATH "") - -set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/doxygen-1.15.0/bin/doxygen" CACHE PATH "") - -set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-4sgpkalsntbmuim5oltb7ue4gggt6rv5/lib/python3.13/site-packages" CACHE PATH "") - -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj/lib/python3.13/site-packages" CACHE PATH "") - -set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-ij66enxl4hmrmwua5bsec2yslylneqbw/lib/python3.13/site-packages" CACHE PATH "") - -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/py-pluggy-1.6.0-27aoz44dedjxa6j6r6cavpqyqmpknidb/lib/python3.13/site-packages" CACHE PATH "") - -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/py-iniconfig-2.1.0-7rty7n4wq6kxxrzflm7wbxqzhhc6ph7q/lib/python3.13/site-packages" CACHE PATH "") - -set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-ukxrsa2wugnmhh3glq25muf5hqexaf62/lib/python3.13/site-packages" CACHE PATH "") - - diff --git a/host-configs/tioga-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake b/host-configs/tioga-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake index 44fb13835a..a270a28a66 100644 --- a/host-configs/tioga-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake +++ b/host-configs/tioga-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake @@ -4,28 +4,28 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.29.2/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/blt-0.7.1-2eahqqwjyauupjq3baxftq7kuv3pvdgp;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/c2c-1.8.0-oxqhr2ybswpnzt74f4iritx263ji3uqy;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-sos5uklbsgaycb776a2ufcyje4y24yls;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/conduit-0.9.5-cxdq4qnoptjq6lry5asb2bqodvm3zjig;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/lua-5.4.8-lsey5vgxvpqf5j3xbrlzswrizuwm65vh;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/mfem-4.9.0-xwok5jck2jjg76k4575wkzgyvgopipz2;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/py-nanobind-2.7.0-3nchqpyclgle5w56it23ozl4ttbnrfjd;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/adiak-0.4.0-cbxp6kv6rm4f7oeczuhbplm3yb3zxb6z;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/libunwind-1.8.3-r4b3cptnf27zp34jbrdtlrf6r6dp6n4l;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/hdf5-1.8.23-wb7u6epuww3tretkcto7jhq2i7pffimg;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/parmetis-4.0.3-d6ed2qesb4yprvpetx3fso35rx6amf5b;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/py-mpi4py-4.1.1-kwpdge3wqmmxvxlocfdz4mgbc3zfihn6;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/py-numpy-2.4.2-t7cvohiksz7deeldwxrtqfmahcvanapb;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/hypre-2.27.0-pxqhlqwksiscco65tsz5somb4ctdq3q2;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-i5stpeet653njjadpjsekbvxlqkng2zn;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/umpire-2025.12.0-hpevb7stqsg6dltp3t2tcnzzps7tgywb;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/zlib-1.3.1-ijxa3tdiibemxtng46hrremmbytl2dqq;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/metis-5.1.0-mubwzuka5byhpbzmwaxjhtffhst3oj3q;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-j7my6ilwzwzvbaxzscbvishmj5mgyxnz;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/fmt-11.0.2-unmvs6vefcyfcfnzlie2eag5i2eib35d;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-6.4.3/blt-0.7.2-fi7f3mxamqq44js7xbmnpu2xk4te75da;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-6.4.3/c2c-1.8.0-dv5ggyguvmw4xalzzpexufqsmvv646t6;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-6.4.3/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-cqtqav6iwrg3vqvujd377ygs4i5mk6ua;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-6.4.3/conduit-0.9.7-eyc3vrxbq7iasvyvtqf7luvnjjcfdwhj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-6.4.3/lua-5.4.8-gzh3ylkvrwkcyegwxb4j47rjsqm7khl6;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-6.4.3/mfem-4.9.0-hdr4zw2h2pxdcw2vg3dskrxwzzhfcwhj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-6.4.3/py-nanobind-2.12.0-dmnviatiwcoe2u4tqwb4m5dtzy2k2hvs;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/none-none/py-pytest-9.0.3-2suvy7b3lmshqhnbylnif2jz2735dbbe;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-6.4.3/adiak-0.5.0-juupsnv6gao5vckdx65vodr5isbt2w3i;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-6.4.3/libunwind-1.8.3-2mcjpbkdqvhxjo64yjl2ghdk2uvz374g;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-6.4.3/hdf5-1.8.23-b62qyycwsymkjttcujtzrj5mt2y67khv;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-6.4.3/parmetis-4.0.3-wnhslyv7bnqiueidwkmvjof7odloc6xd;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-6.4.3/py-mpi4py-4.1.1-zbu3rkawlulkaynkbal35q2dfq7a63r7;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-6.4.3/py-numpy-2.4.6-6beieyegdqtn32ex6mmqlpi5tukuzz7h;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-6.4.3/hypre-3.1.0-5jc6ubqvzucfld4prwv6ilvkspfy2fga;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-6.4.3/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-cjz62wwi4yc2ipyua542r233xb324exk;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-6.4.3/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-c6iizeka2omkycs7ngwghv773nrspzl7;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-6.4.3/zlib-1.3.2-czlezml6nlt3eitzwqz7dxost6agewfj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-6.4.3/metis-5.1.0-kexg6pguakcgo6sra67vpp3hgppocicb;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-6.4.3/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-bntapsyjnuvbkmetoqruiuk2ejkpoj2e;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-6.4.3/fmt-12.1.0-po24stdzkwj4obatcqfus4uszhdbfd7m;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/none-none/compiler-wrapper-1.1.0-qqipzhe4rz5fmoiuvlqgarjeevl2kzuh;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.4.3;/opt/rocm-7.2.1;/opt/rocm-6.4.3;/opt/rocm-7.2.1;/opt/rocm-6.4.3" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/axom-develop-6a3pinjancledu7q5pzdp5ykhictf5ej/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/axom-develop-6a3pinjancledu7q5pzdp5ykhictf5ej/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-6.4.3/axom-develop-s3kbq23em6yhxeo4sw5k6olpuy25ft2d/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-6.4.3/axom-develop-s3kbq23em6yhxeo4sw5k6olpuy25ft2d/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/axom-develop-6a3pinjancledu7q5pzdp5ykhictf5ej/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3/axom-develop-6a3pinjancledu7q5pzdp5ykhictf5ej/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-6.4.3/axom-develop-s3kbq23em6yhxeo4sw5k6olpuy25ft2d/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-6.4.3/axom-develop-s3kbq23em6yhxeo4sw5k6olpuy25ft2d/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ # Compilers #------------------------------------------------------------------------------ -# Compiler Spec: llvm-amdgpu@6.4.3/u67i6xutdthzl3yti7tv3klyhyzddbkv +# Compiler Spec: llvm-amdgpu@6.4.3/j3unaymyuiplqezbi6qng6k2nyia7raa #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdclang" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/none-none/compiler-wrapper-1.1.0-qqipzhe4rz5fmoiuvlqgarjeevl2kzuh/libexec/spack/rocmcc/amdclang" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdclang++" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/none-none/compiler-wrapper-1.1.0-qqipzhe4rz5fmoiuvlqgarjeevl2kzuh/libexec/spack/rocmcc/amdclang++" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/compiler-wrapper-1.0-r533h4sr4uzpv32pv2w4ootpawmxmmgf/libexec/spack/rocmcc/amdflang" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/none-none/compiler-wrapper-1.1.0-qqipzhe4rz5fmoiuvlqgarjeevl2kzuh/libexec/spack/rocmcc/amdflang" CACHE PATH "") else() @@ -37,7 +37,11 @@ else() endif() -set(CMAKE_Fortran_FLAGS "-Mfreeform" CACHE STRING "") +set(CMAKE_C_FLAGS "-fPIC" CACHE STRING "") + +set(CMAKE_CXX_FLAGS "-fPIC" CACHE STRING "") + +set(CMAKE_Fortran_FLAGS "-fPIC -Mfreeform" CACHE STRING "") set(ENABLE_FORTRAN ON CACHE BOOL "") @@ -84,6 +88,8 @@ set(ROCM_ROOT_DIR "/opt/rocm-6.4.3" CACHE PATH "") set(BLT_CMAKE_IMPLICIT_LINK_LIBRARIES_EXCLUDE "ompstub" CACHE STRING "") +set(BLT_CMAKE_IMPLICIT_LINK_DIRECTORIES_EXCLUDE "/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12;/opt/rh/gcc-toolset-12/root/usr/lib64" CACHE STRING "") + set(CMAKE_EXE_LINKER_FLAGS "-lxpmem -L/opt/cray/pe/mpich/8.1.29/gtl/lib -Wl,-rpath,/opt/cray/pe/mpich/8.1.29/gtl/lib -lmpi_gtl_hsa -L/opt/rocm-6.4.3/lib/llvm/lib -Wl,-rpath,/opt/rocm-6.4.3/lib/llvm/lib -L/opt/rocm-6.4.3/lib -Wl,-rpath,/opt/rocm-6.4.3/lib -lpgmath -Wl,--disable-new-dtags -lflang -lflangrti -lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " CACHE STRING "") #------------------------------------------------ @@ -98,29 +104,29 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/llvm-amdgpu-6.4.3" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-6.4.3" CACHE PATH "") -set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-cxdq4qnoptjq6lry5asb2bqodvm3zjig" CACHE PATH "") +set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.7-eyc3vrxbq7iasvyvtqf7luvnjjcfdwhj" CACHE PATH "") -set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-oxqhr2ybswpnzt74f4iritx263ji3uqy" CACHE PATH "") +set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-dv5ggyguvmw4xalzzpexufqsmvv646t6" CACHE PATH "") -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-xwok5jck2jjg76k4575wkzgyvgopipz2" CACHE PATH "") +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-hdr4zw2h2pxdcw2vg3dskrxwzzhfcwhj" CACHE PATH "") -set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-wb7u6epuww3tretkcto7jhq2i7pffimg" CACHE PATH "") +set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-b62qyycwsymkjttcujtzrj5mt2y67khv" CACHE PATH "") -set(LUA_DIR "${TPL_ROOT}/lua-5.4.8-lsey5vgxvpqf5j3xbrlzswrizuwm65vh" CACHE PATH "") +set(LUA_DIR "${TPL_ROOT}/lua-5.4.8-gzh3ylkvrwkcyegwxb4j47rjsqm7khl6" CACHE PATH "") -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-i5stpeet653njjadpjsekbvxlqkng2zn" CACHE PATH "") +set(RAJA_DIR "${TPL_ROOT}/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-cjz62wwi4yc2ipyua542r233xb324exk" CACHE PATH "") -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-hpevb7stqsg6dltp3t2tcnzzps7tgywb" CACHE PATH "") +set(UMPIRE_DIR "${TPL_ROOT}/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-c6iizeka2omkycs7ngwghv773nrspzl7" CACHE PATH "") # OPENCASCADE not built -set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-cbxp6kv6rm4f7oeczuhbplm3yb3zxb6z" CACHE PATH "") +set(ADIAK_DIR "${TPL_ROOT}/adiak-0.5.0-juupsnv6gao5vckdx65vodr5isbt2w3i" CACHE PATH "") -set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-sos5uklbsgaycb776a2ufcyje4y24yls" CACHE PATH "") +set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-cqtqav6iwrg3vqvujd377ygs4i5mk6ua" CACHE PATH "") -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-j7my6ilwzwzvbaxzscbvishmj5mgyxnz" CACHE PATH "") +set(CAMP_DIR "${TPL_ROOT}/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-bntapsyjnuvbkmetoqruiuk2ejkpoj2e" CACHE PATH "") # scr not built @@ -148,16 +154,16 @@ set(CPPCHECK_EXECUTABLE "${DEVTOOLS_ROOT}/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/doxygen-1.15.0/bin/doxygen" CACHE PATH "") -set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-3nchqpyclgle5w56it23ozl4ttbnrfjd/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.12.0-dmnviatiwcoe2u4tqwb4m5dtzy2k2hvs/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/py-pytest-9.0.0-dn2hwdtaqec2dkxtx7bnvykyxacfdagj/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/none-none/py-pytest-9.0.3-2suvy7b3lmshqhnbylnif2jz2735dbbe/lib/python3.13/site-packages" CACHE PATH "") -set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-t7cvohiksz7deeldwxrtqfmahcvanapb/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.6-6beieyegdqtn32ex6mmqlpi5tukuzz7h/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/py-pluggy-1.6.0-27aoz44dedjxa6j6r6cavpqyqmpknidb/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/none-none/py-pluggy-1.6.0-eemjlppu7ynjgedbqabclpfgxt4hxdoh/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_12_02_33/none-none/py-iniconfig-2.1.0-7rty7n4wq6kxxrzflm7wbxqzhhc6ph7q/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/none-none/py-iniconfig-2.1.0-kss2a5udi5pgct5uvmgjh3ojkir5hpoo/lib/python3.13/site-packages" CACHE PATH "") -set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-kwpdge3wqmmxvxlocfdz4mgbc3zfihn6/lib/python3.13/site-packages" CACHE PATH "") +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-zbu3rkawlulkaynkbal35q2dfq7a63r7/lib/python3.13/site-packages" CACHE PATH "") diff --git a/host-configs/tioga-toss_4_x86_64_ib_cray-llvm-amdgpu@7.2.1_hip.cmake b/host-configs/tioga-toss_4_x86_64_ib_cray-llvm-amdgpu@7.2.1_hip.cmake new file mode 100644 index 0000000000..2c82558d21 --- /dev/null +++ b/host-configs/tioga-toss_4_x86_64_ib_cray-llvm-amdgpu@7.2.1_hip.cmake @@ -0,0 +1,169 @@ +#------------------------------------------------------------------------------ +# !!!! This is a generated file, edit at own risk !!!! +#------------------------------------------------------------------------------ +# CMake executable path: /usr/tce/packages/cmake/cmake-3.29.2/bin/cmake +#------------------------------------------------------------------------------ + +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-7.2.1/blt-0.7.2-4z7pqwbd3sf2qsenzgnzsoe5nvvtnm4x;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-7.2.1/c2c-1.8.0-tofdu5fhegkweai4md5q34r2m3rx6kng;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-7.2.1/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-eaiv5wbcyvlgg5gsydyvpphdqjo5nmeg;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-7.2.1/conduit-0.9.7-ahosvgx7w3pz7gwxjbsmusiqonkapwl6;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-7.2.1/lua-5.4.8-6fgci4pl7sbuc2rjjogfzrqumbizqjf6;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-7.2.1/mfem-4.9.0-jth3kgaagpo2geurhvdnnw7g7x3gtsi5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-7.2.1/py-nanobind-2.12.0-hkbx76svqw6adn3pq5dp3ryivd4rutir;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/none-none/py-pytest-9.0.3-2suvy7b3lmshqhnbylnif2jz2735dbbe;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-7.2.1/adiak-0.5.0-m3k5dosirkwzjpdsi3wucl6qld2h4skf;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-7.2.1/libunwind-1.8.3-2wgdo2h5zm43g5w7lzvcgfss3dak4jl4;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-7.2.1/hdf5-1.8.23-cd7kyazwjebsg3fqw44spapntrd43ju6;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-7.2.1/parmetis-4.0.3-r5tsx55ljcnbws7o6mg4nmkjsxamocrx;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-7.2.1/py-mpi4py-4.1.1-j2etz7tywsmxg7ut3rhtnjc6z56ggwue;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-7.2.1/py-numpy-2.4.6-verhhzd4242w4cxgvn2vdbaqf2bdrc5z;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-7.2.1/hypre-3.1.0-synizlg2jgfdz56b5ul2kseit5vrufd7;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-7.2.1/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-imyucyzqy3fe35azgt64midjcyyv65to;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-7.2.1/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-xkis4avuybnc3zjmbdark6fnl4tpjfwq;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-7.2.1/zlib-1.3.2-53253klbr4xexkw2aidk7umh754b4be2;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-7.2.1/metis-5.1.0-qb4krznzcfbr2kyyro6y7hlh4t5zjj2l;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-7.2.1/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-uyzaudb5eouvgsyg2be4lztwocpnevud;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-7.2.1/fmt-12.1.0-pu7rys2ucvbk3pgkol2wgtl6d5wf3vrd;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/none-none/compiler-wrapper-1.1.0-qqipzhe4rz5fmoiuvlqgarjeevl2kzuh;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-9.1.0-rocmcc-7.2.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-7.2.1;/opt/rocm-7.2.1;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-7.2.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-7.2.1;/opt/rocm-7.2.1;/opt/rocm-7.2.1;/opt/rocm-7.2.1;/opt/rocm-7.2.1" CACHE STRING "") + +set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") + +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-7.2.1/axom-develop-pbz2vk7ummjo6knote5ahukq7vzycru2/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-7.2.1/axom-develop-pbz2vk7ummjo6knote5ahukq7vzycru2/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") + +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-7.2.1/axom-develop-pbz2vk7ummjo6knote5ahukq7vzycru2/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-7.2.1/axom-develop-pbz2vk7ummjo6knote5ahukq7vzycru2/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") + +set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") + +#------------------------------------------------------------------------------ +# Compilers +#------------------------------------------------------------------------------ +# Compiler Spec: llvm-amdgpu@7.2.1/titlcdv5eonxuakrjl6bcxvgndtjk4xy +#------------------------------------------------------------------------------ +if(DEFINED ENV{SPACK_CC}) + + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/none-none/compiler-wrapper-1.1.0-qqipzhe4rz5fmoiuvlqgarjeevl2kzuh/libexec/spack/rocmcc/amdclang" CACHE PATH "") + + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/none-none/compiler-wrapper-1.1.0-qqipzhe4rz5fmoiuvlqgarjeevl2kzuh/libexec/spack/rocmcc/amdclang++" CACHE PATH "") + + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/none-none/compiler-wrapper-1.1.0-qqipzhe4rz5fmoiuvlqgarjeevl2kzuh/libexec/spack/rocmcc/amdflang" CACHE PATH "") + +else() + + set(CMAKE_C_COMPILER "/opt/rocm-7.2.1/llvm/bin/amdclang" CACHE PATH "") + + set(CMAKE_CXX_COMPILER "/opt/rocm-7.2.1/llvm/bin/amdclang++" CACHE PATH "") + + set(CMAKE_Fortran_COMPILER "/opt/rocm-7.2.1/llvm/bin/amdflang" CACHE PATH "") + +endif() + +set(CMAKE_C_FLAGS "-fPIC" CACHE STRING "") + +set(CMAKE_CXX_FLAGS "-fPIC" CACHE STRING "") + +set(CMAKE_Fortran_FLAGS "-fPIC" CACHE STRING "") + +set(ENABLE_FORTRAN ON CACHE BOOL "") + +#------------------------------------------------------------------------------ +# MPI +#------------------------------------------------------------------------------ + +set(MPI_C_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-9.1.0-rocmcc-7.2.1/bin/mpicc" CACHE PATH "") + +set(MPI_CXX_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-9.1.0-rocmcc-7.2.1/bin/mpicxx" CACHE PATH "") + +set(MPI_Fortran_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-9.1.0-rocmcc-7.2.1/bin/mpif90" CACHE PATH "") + +set(MPIEXEC_EXECUTABLE "/usr/global/tools/flux_wrappers/bin/srun" CACHE PATH "") + +set(MPIEXEC_NUMPROC_FLAG "-n" CACHE STRING "") + +set(ENABLE_MPI ON CACHE BOOL "") + +#------------------------------------------------------------------------------ +# Hardware +#------------------------------------------------------------------------------ + +#------------------------------------------------ +# ROCm +#------------------------------------------------ + +set(ROCM_PATH "/opt/rocm-7.2.1" CACHE PATH "") + +set(CMAKE_HIP_ARCHITECTURES "gfx90a;gfx942" CACHE STRING "") + +set(CMAKE_HIP_COMPILER "/opt/rocm-7.2.1/bin/amdclang++" CACHE FILEPATH "") + +#------------------------------------------------------------------------------ + +# Axom ROCm specifics + +#------------------------------------------------------------------------------ + + +set(ENABLE_HIP ON CACHE BOOL "") + +set(ROCM_ROOT_DIR "/opt/rocm-7.2.1" CACHE PATH "") + +set(BLT_CMAKE_IMPLICIT_LINK_LIBRARIES_EXCLUDE "ompstub" CACHE STRING "") + +set(BLT_CMAKE_IMPLICIT_LINK_DIRECTORIES_EXCLUDE "/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12;/opt/rh/gcc-toolset-12/root/usr/lib64" CACHE STRING "") + +set(CMAKE_EXE_LINKER_FLAGS "-lxpmem -L/opt/cray/pe/mpich/9.1.0/gtl/lib -Wl,-rpath,/opt/cray/pe/mpich/9.1.0/gtl/lib -lmpi_gtl_hsa -L/opt/rocm-7.2.1/lib/llvm/lib -Wl,-rpath,/opt/rocm-7.2.1/lib/llvm/lib -L/opt/rocm-7.2.1/lib -Wl,-rpath,/opt/rocm-7.2.1/lib -lpgmath -Wl,--disable-new-dtags -lflang -lflangrti -lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " CACHE STRING "") + +#------------------------------------------------ +# Hardware Specifics +#------------------------------------------------ + +set(ENABLE_OPENMP ON CACHE BOOL "") + +set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") + +#------------------------------------------------------------------------------ +# TPLs +#------------------------------------------------------------------------------ + +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/llvm-amdgpu-7.2.1" CACHE PATH "") + +set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.7-ahosvgx7w3pz7gwxjbsmusiqonkapwl6" CACHE PATH "") + +set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-tofdu5fhegkweai4md5q34r2m3rx6kng" CACHE PATH "") + +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-jth3kgaagpo2geurhvdnnw7g7x3gtsi5" CACHE PATH "") + +set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-cd7kyazwjebsg3fqw44spapntrd43ju6" CACHE PATH "") + +set(LUA_DIR "${TPL_ROOT}/lua-5.4.8-6fgci4pl7sbuc2rjjogfzrqumbizqjf6" CACHE PATH "") + +set(RAJA_DIR "${TPL_ROOT}/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-imyucyzqy3fe35azgt64midjcyyv65to" CACHE PATH "") + +set(UMPIRE_DIR "${TPL_ROOT}/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-xkis4avuybnc3zjmbdark6fnl4tpjfwq" CACHE PATH "") + +# OPENCASCADE not built + +set(ADIAK_DIR "${TPL_ROOT}/adiak-0.5.0-m3k5dosirkwzjpdsi3wucl6qld2h4skf" CACHE PATH "") + +set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-eaiv5wbcyvlgg5gsydyvpphdqjo5nmeg" CACHE PATH "") + +set(CAMP_DIR "${TPL_ROOT}/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-uyzaudb5eouvgsyg2be4lztwocpnevud" CACHE PATH "") + +# scr not built + +#------------------------------------------------------------------------------ +# Devtools & Python +#------------------------------------------------------------------------------ + +set(DEVTOOLS_ROOT "/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25" CACHE PATH "") + +set(CLANGFORMAT_EXECUTABLE "/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm/bin/clang-format" CACHE PATH "") + +set(Python_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/python3" CACHE PATH "") + +set(JSONSCHEMA_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/jsonschema" CACHE PATH "") + +set(ENABLE_DOCS ON CACHE BOOL "") + +set(SPHINX_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/sphinx-build" CACHE PATH "") + +set(YAPF_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/yapf" CACHE PATH "") + +set(SHROUD_EXECUTABLE "/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0/bin/shroud" CACHE PATH "") + +set(CPPCHECK_EXECUTABLE "${DEVTOOLS_ROOT}/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27/bin/cppcheck" CACHE PATH "") + +set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/doxygen-1.15.0/bin/doxygen" CACHE PATH "") + +set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.12.0-hkbx76svqw6adn3pq5dp3ryivd4rutir/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/none-none/py-pytest-9.0.3-2suvy7b3lmshqhnbylnif2jz2735dbbe/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.6-verhhzd4242w4cxgvn2vdbaqf2bdrc5z/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/none-none/py-pluggy-1.6.0-eemjlppu7ynjgedbqabclpfgxt4hxdoh/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_07_10_16_52/none-none/py-iniconfig-2.1.0-kss2a5udi5pgct5uvmgjh3ojkir5hpoo/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-j2etz7tywsmxg7ut3rhtnjc6z56ggwue/lib/python3.13/site-packages" CACHE PATH "") + + diff --git a/host-configs/tuolumne-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake b/host-configs/tuolumne-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake deleted file mode 100644 index f62de2da76..0000000000 --- a/host-configs/tuolumne-toss_4_x86_64_ib_cray-cce@20.0.0_hip.cmake +++ /dev/null @@ -1,169 +0,0 @@ -#------------------------------------------------------------------------------ -# !!!! This is a generated file, edit at own risk !!!! -#------------------------------------------------------------------------------ -# CMake executable path: /usr/tce/packages/cmake/cmake-3.29.2/bin/cmake -#------------------------------------------------------------------------------ - -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/blt-0.7.1-af45hpsxdxploxhqqi4irjmy4vz2omqx;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/c2c-1.8.0-cpdkv2uxkhs3qmzqakt4sht3vuiucdwt;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-wytd46zvuquxg2rqf3zec5ldbmjeaat6;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/conduit-0.9.5-pbrrhvrsmnewby6q3u35ceppt5fxme2p;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/lua-5.4.8-y6z3polqakbuhn6nh6muri3rjxqmiioa;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/mfem-4.9.0-wpbbiugqvdw7bylkmyyf2quazcoptzah;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/py-nanobind-2.7.0-ieb2bzuz435ydpyiq2uj36yyqjbrffml;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/adiak-0.4.0-lzgexbo5qyzepgju2acyy6d2qft443tb;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/libunwind-1.8.3-zvteigxlopfo5tkdr2n2pglgosg42bah;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/hdf5-1.8.23-to3lvqwzxrqzad65zbvnbtvyt4k63uxe;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/parmetis-4.0.3-bajtzsmahjg2wvcjzo5gfb2hr5oj2elc;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/py-mpi4py-4.1.1-e5tepnokpccpvjfp6lwnwfi5laakuhdh;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/py-numpy-2.4.2-xw5ysy75zfqlalmbmtl5phd2qip43hlo;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/hypre-2.27.0-knkmxfbf3xcut2wmamjsmhrqf7u7xmi7;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-z3eibnxgk3jeplydlcizrhurpibzqzqv;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/umpire-2025.12.0-eqokbtuch3pwifb225w2i3rrft5ddcgv;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/zlib-1.3.1-f3sdiqmy2whvmdn2fxeol3k7oqwouays;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/metis-5.1.0-v4d34x7btqhotktvu5bgk4iwk6h2lumb;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-eplsxvli6nnrgdl4l3hl7cizytginfhp;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/fmt-11.0.2-nzzmwf4nofjv46yeugehjo64opvfbbzr;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cce-tce/cce-20.0.0;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.32-rocmcc-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3" CACHE STRING "") - -set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") - -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/axom-develop-mdr65pqpz5pq5uhe57wjeu6vp5q3kuoa/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/axom-develop-mdr65pqpz5pq5uhe57wjeu6vp5q3kuoa/lib64;;/opt/cray/pe/cce/20.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") - -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/axom-develop-mdr65pqpz5pq5uhe57wjeu6vp5q3kuoa/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0/axom-develop-mdr65pqpz5pq5uhe57wjeu6vp5q3kuoa/lib64;;/opt/cray/pe/cce/20.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") - -set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") - -#------------------------------------------------------------------------------ -# Compilers -#------------------------------------------------------------------------------ -# Compiler Spec: cce@20.0.0/v7nrkimihxta4odatu45wrllosgeklrd -#------------------------------------------------------------------------------ -if(DEFINED ENV{SPACK_CC}) - - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/cce/craycc" CACHE PATH "") - - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/cce/case-insensitive/crayCC" CACHE PATH "") - - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/cce/crayftn" CACHE PATH "") - -else() - - set(CMAKE_C_COMPILER "/usr/tce/packages/cce-tce/cce-20.0.0/bin/craycc" CACHE PATH "") - - set(CMAKE_CXX_COMPILER "/usr/tce/packages/cce-tce/cce-20.0.0/bin/crayCC" CACHE PATH "") - - set(CMAKE_Fortran_COMPILER "/usr/tce/packages/cce-tce/cce-20.0.0/bin/crayftn" CACHE PATH "") - -endif() - -set(CMAKE_Fortran_FLAGS "-ef" CACHE STRING "") - -set(ENABLE_FORTRAN ON CACHE BOOL "") - -set(CMAKE_CXX_FLAGS_DEBUG "-O1 -g" CACHE STRING "") - -#------------------------------------------------------------------------------ -# MPI -#------------------------------------------------------------------------------ - -set(MPI_C_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.32-rocmcc-6.4.3/bin/mpicc" CACHE PATH "") - -set(MPI_CXX_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.32-rocmcc-6.4.3/bin/mpicxx" CACHE PATH "") - -set(MPI_Fortran_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.32-rocmcc-6.4.3/bin/mpif90" CACHE PATH "") - -set(MPIEXEC_EXECUTABLE "/usr/global/tools/flux_wrappers/bin/srun" CACHE PATH "") - -set(MPIEXEC_NUMPROC_FLAG "-n" CACHE STRING "") - -set(ENABLE_MPI ON CACHE BOOL "") - -#------------------------------------------------------------------------------ -# Hardware -#------------------------------------------------------------------------------ - -#------------------------------------------------ -# ROCm -#------------------------------------------------ - -set(ROCM_PATH "/opt/rocm-6.4.3" CACHE PATH "") - -set(CMAKE_HIP_ARCHITECTURES "gfx90a;gfx942" CACHE STRING "") - -set(CMAKE_HIP_COMPILER "/opt/rocm-6.4.3/bin/amdclang++" CACHE FILEPATH "") - -#------------------------------------------------------------------------------ - -# Axom ROCm specifics - -#------------------------------------------------------------------------------ - - -set(ENABLE_HIP ON CACHE BOOL "") - -set(ROCM_ROOT_DIR "/opt/rocm-6.4.3" CACHE PATH "") - -set(BLT_CMAKE_IMPLICIT_LINK_LIBRARIES_EXCLUDE "unwind;ompstub" CACHE STRING "") - -set(CMAKE_EXE_LINKER_FLAGS "-lxpmem -L/opt/cray/pe/mpich/8.1.32/gtl/lib -Wl,-rpath,/opt/cray/pe/mpich/8.1.32/gtl/lib -lmpi_gtl_hsa -L/opt/rocm-6.4.3/lib/llvm/lib -Wl,-rpath,/opt/rocm-6.4.3/lib/llvm/lib -L/opt/rocm-6.4.3/lib -Wl,-rpath,/opt/rocm-6.4.3/lib -lpgmath -L/opt/cray/pe/cce/20.0.0/cce/x86_64/lib -Wl,-rpath,/opt/cray/pe/cce/20.0.0/cce/x86_64/lib -lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " CACHE STRING "") - -#------------------------------------------------ -# Hardware Specifics -#------------------------------------------------ - -set(ENABLE_OPENMP ON CACHE BOOL "") - -set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") - -set(BLT_OPENMP_COMPILE_FLAGS "$<$>:-fopenmp=libomp>;$<$:-fopenmp>" CACHE STRING "Different OpenMP compile & link flags between HIP and CXX compilers (amdclang++)") - -set(BLT_OPENMP_LINK_FLAGS "$<$>:-fopenmp=libomp>;$<$:-fopenmp>" CACHE STRING "Different OpenMP compile & link flags between HIP and CXX compilers (amdclang++)") - -#------------------------------------------------------------------------------ -# TPLs -#------------------------------------------------------------------------------ - -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/cce-20.0.0" CACHE PATH "") - -set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-pbrrhvrsmnewby6q3u35ceppt5fxme2p" CACHE PATH "") - -set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-cpdkv2uxkhs3qmzqakt4sht3vuiucdwt" CACHE PATH "") - -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-wpbbiugqvdw7bylkmyyf2quazcoptzah" CACHE PATH "") - -set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-to3lvqwzxrqzad65zbvnbtvyt4k63uxe" CACHE PATH "") - -set(LUA_DIR "${TPL_ROOT}/lua-5.4.8-y6z3polqakbuhn6nh6muri3rjxqmiioa" CACHE PATH "") - -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-z3eibnxgk3jeplydlcizrhurpibzqzqv" CACHE PATH "") - -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-eqokbtuch3pwifb225w2i3rrft5ddcgv" CACHE PATH "") - -# OPENCASCADE not built - -set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-lzgexbo5qyzepgju2acyy6d2qft443tb" CACHE PATH "") - -set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-wytd46zvuquxg2rqf3zec5ldbmjeaat6" CACHE PATH "") - -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-eplsxvli6nnrgdl4l3hl7cizytginfhp" CACHE PATH "") - -# scr not built - -#------------------------------------------------------------------------------ -# Devtools & Python -#------------------------------------------------------------------------------ - -set(DEVTOOLS_ROOT "/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25" CACHE PATH "") - -set(CLANGFORMAT_EXECUTABLE "/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm/bin/clang-format" CACHE PATH "") - -set(Python_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/python3" CACHE PATH "") - -set(JSONSCHEMA_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/jsonschema" CACHE PATH "") - -set(ENABLE_DOCS ON CACHE BOOL "") - -set(SPHINX_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/sphinx-build" CACHE PATH "") - -set(YAPF_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/yapf" CACHE PATH "") - -set(SHROUD_EXECUTABLE "/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0/bin/shroud" CACHE PATH "") - -set(CPPCHECK_EXECUTABLE "${DEVTOOLS_ROOT}/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27/bin/cppcheck" CACHE PATH "") - -set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/doxygen-1.15.0/bin/doxygen" CACHE PATH "") - -set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-ieb2bzuz435ydpyiq2uj36yyqjbrffml/lib/python3.13/site-packages" CACHE PATH "") - -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5/lib/python3.13/site-packages" CACHE PATH "") - -set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-xw5ysy75zfqlalmbmtl5phd2qip43hlo/lib/python3.13/site-packages" CACHE PATH "") - -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/py-pluggy-1.6.0-n4ojmtqyawxz6gipk64pup3ahwz3xqpu/lib/python3.13/site-packages" CACHE PATH "") - -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/py-iniconfig-2.1.0-lf3yvch65se22jr7b5unfoxtse4ugkrk/lib/python3.13/site-packages" CACHE PATH "") - -set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-e5tepnokpccpvjfp6lwnwfi5laakuhdh/lib/python3.13/site-packages" CACHE PATH "") - - diff --git a/host-configs/tuolumne-toss_4_x86_64_ib_cray-cce@21.0.0_hip.cmake b/host-configs/tuolumne-toss_4_x86_64_ib_cray-cce@21.0.0_hip.cmake new file mode 100644 index 0000000000..340cb9d426 --- /dev/null +++ b/host-configs/tuolumne-toss_4_x86_64_ib_cray-cce@21.0.0_hip.cmake @@ -0,0 +1,171 @@ +#------------------------------------------------------------------------------ +# !!!! This is a generated file, edit at own risk !!!! +#------------------------------------------------------------------------------ +# CMake executable path: /usr/tce/packages/cmake/cmake-3.29.2/bin/cmake +#------------------------------------------------------------------------------ + +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/cce-21.0.0/blt-0.7.2-nksv5wzh2ckjebp3gaczjotyxzkt5tuw;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/cce-21.0.0/c2c-1.8.0-elfizos6urooc46kuyg4gkxlm2k5xn54;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/cce-21.0.0/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-qnpa4ygyxq2jvwwgjmnb24eprakzuzio;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/cce-21.0.0/conduit-0.9.7-vexctj4cctvlrc6kmkpkn2ay4qcdbwrj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/cce-21.0.0/lua-5.4.8-5eyihk35qulaupbroruuuax2uqjvq5a4;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/cce-21.0.0/mfem-4.9.0-pk6k52bpyhqvlro22ivxspmnkaub5ws7;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/cce-21.0.0/py-nanobind-2.12.0-he5xenbyflysayerkt6ovwhjktryej6j;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/none-none/py-pytest-9.0.3-4yefg77cpcyymgmjssuqsb7ojxs7aiuo;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/cce-21.0.0/adiak-0.5.0-4iigkdlf2fzswe6dx3mh5turnlgflb5p;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/cce-21.0.0/libunwind-1.8.3-nyc3zz6art2rizfwhlyoq6zeia5fjnju;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/cce-21.0.0/hdf5-1.8.23-npwvi7rxvqyndhdmecm2lhf64qokekbm;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/cce-21.0.0/parmetis-4.0.3-okyjdeot3ycg4b7coi67cvxwzdu6i7tm;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/cce-21.0.0/py-mpi4py-4.1.1-jbvd3mlwrl5y7vgyd2b3qxkbws3ah6qn;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/cce-21.0.0/py-numpy-2.4.6-k7fnyomjx5pjsntja3yw7n2r3i7a45nh;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/cce-21.0.0/hypre-3.1.0-y7fwwlmj624ra3b7a4oor4bdw67y5k4s;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/cce-21.0.0/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-5x26ho4hxwgcxcjryppsw5gbcxvvhhnz;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/cce-21.0.0/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-kzkhyazpya22yjeonk3yrp32ld3cbx54;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/cce-21.0.0/zlib-1.3.2-eso4ifs5c2jwdtzfnxgzr6cad42brvcu;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/cce-21.0.0/metis-5.1.0-2vjouzdzzgoibxzivuiavaz6laeukj7s;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/cce-21.0.0/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-by47x6vtpcmgyi5nbxytaptyxeglkgds;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/cce-21.0.0/fmt-12.1.0-4yp5h7bwfiwhjbsdfyyxdsblkiipbii7;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/none-none/compiler-wrapper-1.1.0-yz3pcrs3u74ghvkyrkg5krf3a3y72be7;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cce-tce/cce-21.0.0;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-9.1.0-cce-21.0.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-7.2.1;/opt/rocm-7.2.1;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-7.2.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-7.2.1;/opt/rocm-7.2.1;/opt/rocm-7.2.1;/opt/rocm-7.2.1;/opt/rocm-7.2.1" CACHE STRING "") + +set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") + +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/cce-21.0.0/axom-develop-jwm43obyvfz2t4lbn3ufrmsw7kogw3qe/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/cce-21.0.0/axom-develop-jwm43obyvfz2t4lbn3ufrmsw7kogw3qe/lib64;;/opt/cray/pe/cce/21.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") + +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/cce-21.0.0/axom-develop-jwm43obyvfz2t4lbn3ufrmsw7kogw3qe/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/cce-21.0.0/axom-develop-jwm43obyvfz2t4lbn3ufrmsw7kogw3qe/lib64;;/opt/cray/pe/cce/21.0.0/cce/x86_64/lib;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") + +set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") + +#------------------------------------------------------------------------------ +# Compilers +#------------------------------------------------------------------------------ +# Compiler Spec: cce@21.0.0/nmaros4ushwf5auvt3asaaazfjerle4l +#------------------------------------------------------------------------------ +if(DEFINED ENV{SPACK_CC}) + + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/none-none/compiler-wrapper-1.1.0-yz3pcrs3u74ghvkyrkg5krf3a3y72be7/libexec/spack/cce/craycc" CACHE PATH "") + + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/none-none/compiler-wrapper-1.1.0-yz3pcrs3u74ghvkyrkg5krf3a3y72be7/libexec/spack/cce/case-insensitive/crayCC" CACHE PATH "") + + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/none-none/compiler-wrapper-1.1.0-yz3pcrs3u74ghvkyrkg5krf3a3y72be7/libexec/spack/cce/crayftn" CACHE PATH "") + +else() + + set(CMAKE_C_COMPILER "/usr/tce/packages/cce-tce/cce-21.0.0/bin/craycc" CACHE PATH "") + + set(CMAKE_CXX_COMPILER "/usr/tce/packages/cce-tce/cce-21.0.0/bin/crayCC" CACHE PATH "") + + set(CMAKE_Fortran_COMPILER "/usr/tce/packages/cce-tce/cce-21.0.0/bin/crayftn" CACHE PATH "") + +endif() + +set(CMAKE_C_FLAGS "-fPIC" CACHE STRING "") + +set(CMAKE_CXX_FLAGS "-fPIC" CACHE STRING "") + +set(CMAKE_Fortran_FLAGS "-fPIC -ef" CACHE STRING "") + +set(ENABLE_FORTRAN ON CACHE BOOL "") + +set(CMAKE_CXX_FLAGS_DEBUG "-O1 -g" CACHE STRING "") + +#------------------------------------------------------------------------------ +# MPI +#------------------------------------------------------------------------------ + +set(MPI_C_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-9.1.0-cce-21.0.0/bin/mpicc" CACHE PATH "") + +set(MPI_CXX_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-9.1.0-cce-21.0.0/bin/mpicxx" CACHE PATH "") + +set(MPI_Fortran_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-9.1.0-cce-21.0.0/bin/mpif90" CACHE PATH "") + +set(MPIEXEC_EXECUTABLE "/usr/global/tools/flux_wrappers/bin/srun" CACHE PATH "") + +set(MPIEXEC_NUMPROC_FLAG "-n" CACHE STRING "") + +set(ENABLE_MPI ON CACHE BOOL "") + +#------------------------------------------------------------------------------ +# Hardware +#------------------------------------------------------------------------------ + +#------------------------------------------------ +# ROCm +#------------------------------------------------ + +set(ROCM_PATH "/opt/rocm-7.2.1" CACHE PATH "") + +set(CMAKE_HIP_ARCHITECTURES "gfx90a;gfx942" CACHE STRING "") + +set(CMAKE_HIP_COMPILER "/opt/rocm-7.2.1/bin/amdclang++" CACHE FILEPATH "") + +#------------------------------------------------------------------------------ + +# Axom ROCm specifics + +#------------------------------------------------------------------------------ + + +set(ENABLE_HIP ON CACHE BOOL "") + +set(ROCM_ROOT_DIR "/opt/rocm-7.2.1" CACHE PATH "") + +set(BLT_CMAKE_IMPLICIT_LINK_LIBRARIES_EXCLUDE "unwind;ompstub" CACHE STRING "") + +set(BLT_CMAKE_IMPLICIT_LINK_DIRECTORIES_EXCLUDE "/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12;/opt/rh/gcc-toolset-12/root/usr/lib64" CACHE STRING "") + +set(CMAKE_EXE_LINKER_FLAGS "-lxpmem -L/opt/cray/pe/mpich/9.1.0/gtl/lib -Wl,-rpath,/opt/cray/pe/mpich/9.1.0/gtl/lib -lmpi_gtl_hsa -L/opt/rocm-7.2.1/lib/llvm/lib -Wl,-rpath,/opt/rocm-7.2.1/lib/llvm/lib -L/opt/rocm-7.2.1/lib -Wl,-rpath,/opt/rocm-7.2.1/lib -lpgmath -L/opt/cray/pe/cce/21.0.0/cce/x86_64/lib -Wl,-rpath,/opt/cray/pe/cce/21.0.0/cce/x86_64/lib-lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " CACHE STRING "") + +#------------------------------------------------ +# Hardware Specifics +#------------------------------------------------ + +set(ENABLE_OPENMP ON CACHE BOOL "") + +set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") + +#------------------------------------------------------------------------------ +# TPLs +#------------------------------------------------------------------------------ + +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/cce-21.0.0" CACHE PATH "") + +set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.7-vexctj4cctvlrc6kmkpkn2ay4qcdbwrj" CACHE PATH "") + +set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-elfizos6urooc46kuyg4gkxlm2k5xn54" CACHE PATH "") + +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-pk6k52bpyhqvlro22ivxspmnkaub5ws7" CACHE PATH "") + +set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-npwvi7rxvqyndhdmecm2lhf64qokekbm" CACHE PATH "") + +set(LUA_DIR "${TPL_ROOT}/lua-5.4.8-5eyihk35qulaupbroruuuax2uqjvq5a4" CACHE PATH "") + +set(RAJA_DIR "${TPL_ROOT}/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-5x26ho4hxwgcxcjryppsw5gbcxvvhhnz" CACHE PATH "") + +set(UMPIRE_DIR "${TPL_ROOT}/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-kzkhyazpya22yjeonk3yrp32ld3cbx54" CACHE PATH "") + +# OPENCASCADE not built + +set(ADIAK_DIR "${TPL_ROOT}/adiak-0.5.0-4iigkdlf2fzswe6dx3mh5turnlgflb5p" CACHE PATH "") + +set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-qnpa4ygyxq2jvwwgjmnb24eprakzuzio" CACHE PATH "") + +set(CAMP_DIR "${TPL_ROOT}/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-by47x6vtpcmgyi5nbxytaptyxeglkgds" CACHE PATH "") + +# scr not built + +#------------------------------------------------------------------------------ +# Devtools & Python +#------------------------------------------------------------------------------ + +set(DEVTOOLS_ROOT "/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25" CACHE PATH "") + +set(CLANGFORMAT_EXECUTABLE "/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm/bin/clang-format" CACHE PATH "") + +set(Python_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/python3" CACHE PATH "") + +set(JSONSCHEMA_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/jsonschema" CACHE PATH "") + +set(ENABLE_DOCS ON CACHE BOOL "") + +set(SPHINX_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/sphinx-build" CACHE PATH "") + +set(YAPF_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/yapf" CACHE PATH "") + +set(SHROUD_EXECUTABLE "/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0/bin/shroud" CACHE PATH "") + +set(CPPCHECK_EXECUTABLE "${DEVTOOLS_ROOT}/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27/bin/cppcheck" CACHE PATH "") + +set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/doxygen-1.15.0/bin/doxygen" CACHE PATH "") + +set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.12.0-he5xenbyflysayerkt6ovwhjktryej6j/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/none-none/py-pytest-9.0.3-4yefg77cpcyymgmjssuqsb7ojxs7aiuo/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.6-k7fnyomjx5pjsntja3yw7n2r3i7a45nh/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/none-none/py-pluggy-1.6.0-ient4fgfjoacxpdsxw5qw6vqya3gid4b/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/none-none/py-iniconfig-2.1.0-h3ficswruattwshoykrh4mp2ds6fwnjg/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-jbvd3mlwrl5y7vgyd2b3qxkbws3ah6qn/lib/python3.13/site-packages" CACHE PATH "") + + diff --git a/host-configs/tuolumne-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake b/host-configs/tuolumne-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake deleted file mode 100644 index 64fff8bd85..0000000000 --- a/host-configs/tuolumne-toss_4_x86_64_ib_cray-llvm-amdgpu@6.3.1_hip.cmake +++ /dev/null @@ -1,163 +0,0 @@ -#------------------------------------------------------------------------------ -# !!!! This is a generated file, edit at own risk !!!! -#------------------------------------------------------------------------------ -# CMake executable path: /usr/tce/packages/cmake/cmake-3.29.2/bin/cmake -#------------------------------------------------------------------------------ - -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1/adiak-0.4.0-c4srnrz5apjetx74dt6xjah63o3a7xcx;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1/blt-0.7.1-kzmxfjvxm4drr2qhspckvwee6mrlc3e5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1/c2c-1.8.0-lm64gc55hpxilmtsw7geirys3q4wacu5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1/conduit-0.9.5-wswspowegvp5byl5inc2cikljzkcqg64;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1/lua-5.4.8-j7ngytniswhoa536kqfzarcraarkicbm;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1/mfem-4.9.0-xwg5un3mlbeog3j4voeltekra43ix3ga;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1/py-nanobind-2.7.0-q2kwozph3gwgfyjnx43jp2gkbaghwxoj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1/hdf5-1.8.23-rb4cjthx4wz4wnpaizwmm2jycijdthqm;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1/parmetis-4.0.3-ng66tt5fmjyfoiuuxyl4rxqv4ompawmd;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1/py-mpi4py-4.1.1-s4a64moil7qmgczpelmmb344qxxavrzk;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1/py-numpy-2.4.2-zcxbrxijjc3miotq6s3kq446afrgycuu;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1/hypre-2.27.0-6m2jwyzqbksxgmdznvh54ytysxyme7jy;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-wcyqyggju4u2ornjjnamgdgfbqjrzsir;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1/umpire-2025.12.0-5w5v22edixuttxwueaagiobpjzn5mcgr;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1/zlib-1.3.1-5w5muvpvct6uruxrddscblsiyizl3uoc;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1/metis-5.1.0-4punmdkye6rovr3u7ph7bgj66qim77du;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-jnalmgzqx2nn2afq4gwhyjda3u6yub7y;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1/fmt-11.0.2-opsfnufdiedpfpob5wgbvhcrhyhzfkaq;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.3.1;/opt/rocm-6.3.1;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.3.1;/opt/rocm-6.3.1;/opt/rocm-6.3.1" CACHE STRING "") - -set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") - -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1/axom-develop-4srm7we3wjp7l4cfcshzov5nr52jna4r/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1/axom-develop-4srm7we3wjp7l4cfcshzov5nr52jna4r/lib64;;/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12" CACHE STRING "") - -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1/axom-develop-4srm7we3wjp7l4cfcshzov5nr52jna4r/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1/axom-develop-4srm7we3wjp7l4cfcshzov5nr52jna4r/lib64;;/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12" CACHE STRING "") - -set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") - -#------------------------------------------------------------------------------ -# Compilers -#------------------------------------------------------------------------------ -# Compiler Spec: llvm-amdgpu@6.3.1/ns2pv5bzkpjolxzmhgkckj6glqymgd7e -#------------------------------------------------------------------------------ -if(DEFINED ENV{SPACK_CC}) - - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdclang" CACHE PATH "") - - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdclang++" CACHE PATH "") - - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdflang" CACHE PATH "") - -else() - - set(CMAKE_C_COMPILER "/opt/rocm-6.3.1/llvm/bin/amdclang" CACHE PATH "") - - set(CMAKE_CXX_COMPILER "/opt/rocm-6.3.1/llvm/bin/amdclang++" CACHE PATH "") - - set(CMAKE_Fortran_COMPILER "/opt/rocm-6.3.1/llvm/bin/amdflang" CACHE PATH "") - -endif() - -set(CMAKE_Fortran_FLAGS "-Mfreeform" CACHE STRING "") - -set(ENABLE_FORTRAN ON CACHE BOOL "") - -#------------------------------------------------------------------------------ -# MPI -#------------------------------------------------------------------------------ - -set(MPI_C_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.3.1/bin/mpicc" CACHE PATH "") - -set(MPI_CXX_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.3.1/bin/mpicxx" CACHE PATH "") - -set(MPI_Fortran_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.3.1/bin/mpif90" CACHE PATH "") - -set(MPIEXEC_EXECUTABLE "/usr/global/tools/flux_wrappers/bin/srun" CACHE PATH "") - -set(MPIEXEC_NUMPROC_FLAG "-n" CACHE STRING "") - -set(ENABLE_MPI ON CACHE BOOL "") - -#------------------------------------------------------------------------------ -# Hardware -#------------------------------------------------------------------------------ - -#------------------------------------------------ -# ROCm -#------------------------------------------------ - -set(ROCM_PATH "/opt/rocm-6.3.1" CACHE PATH "") - -set(CMAKE_HIP_ARCHITECTURES "gfx90a;gfx942" CACHE STRING "") - -set(CMAKE_HIP_COMPILER "/opt/rocm-6.3.1/bin/amdclang++" CACHE FILEPATH "") - -#------------------------------------------------------------------------------ - -# Axom ROCm specifics - -#------------------------------------------------------------------------------ - - -set(ENABLE_HIP ON CACHE BOOL "") - -set(ROCM_ROOT_DIR "/opt/rocm-6.3.1" CACHE PATH "") - -set(BLT_CMAKE_IMPLICIT_LINK_LIBRARIES_EXCLUDE "ompstub" CACHE STRING "") - -set(CMAKE_EXE_LINKER_FLAGS "-lxpmem -L/opt/cray/pe/mpich/8.1.29/gtl/lib -Wl,-rpath,/opt/cray/pe/mpich/8.1.29/gtl/lib -lmpi_gtl_hsa -L/opt/rocm-6.3.1/lib/llvm/lib -Wl,-rpath,/opt/rocm-6.3.1/lib/llvm/lib -L/opt/rocm-6.3.1/lib -Wl,-rpath,/opt/rocm-6.3.1/lib -lpgmath -Wl,--disable-new-dtags -lflang -lflangrti -lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " CACHE STRING "") - -#------------------------------------------------ -# Hardware Specifics -#------------------------------------------------ - -set(ENABLE_OPENMP ON CACHE BOOL "") - -set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") - -#------------------------------------------------------------------------------ -# TPLs -#------------------------------------------------------------------------------ - -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.3.1" CACHE PATH "") - -set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-wswspowegvp5byl5inc2cikljzkcqg64" CACHE PATH "") - -set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-lm64gc55hpxilmtsw7geirys3q4wacu5" CACHE PATH "") - -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-xwg5un3mlbeog3j4voeltekra43ix3ga" CACHE PATH "") - -set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-rb4cjthx4wz4wnpaizwmm2jycijdthqm" CACHE PATH "") - -set(LUA_DIR "${TPL_ROOT}/lua-5.4.8-j7ngytniswhoa536kqfzarcraarkicbm" CACHE PATH "") - -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-wcyqyggju4u2ornjjnamgdgfbqjrzsir" CACHE PATH "") - -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-5w5v22edixuttxwueaagiobpjzn5mcgr" CACHE PATH "") - -# OPENCASCADE not built - -set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-c4srnrz5apjetx74dt6xjah63o3a7xcx" CACHE PATH "") - -# CALIPER not built - -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-jnalmgzqx2nn2afq4gwhyjda3u6yub7y" CACHE PATH "") - -# scr not built - -#------------------------------------------------------------------------------ -# Devtools & Python -#------------------------------------------------------------------------------ - -set(DEVTOOLS_ROOT "/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25" CACHE PATH "") - -set(CLANGFORMAT_EXECUTABLE "/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm/bin/clang-format" CACHE PATH "") - -set(Python_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/python3" CACHE PATH "") - -set(JSONSCHEMA_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/jsonschema" CACHE PATH "") - -set(ENABLE_DOCS ON CACHE BOOL "") - -set(SPHINX_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/sphinx-build" CACHE PATH "") - -set(YAPF_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/yapf" CACHE PATH "") - -set(SHROUD_EXECUTABLE "/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0/bin/shroud" CACHE PATH "") - -set(CPPCHECK_EXECUTABLE "${DEVTOOLS_ROOT}/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27/bin/cppcheck" CACHE PATH "") - -set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/doxygen-1.15.0/bin/doxygen" CACHE PATH "") - -set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-q2kwozph3gwgfyjnx43jp2gkbaghwxoj/lib/python3.13/site-packages" CACHE PATH "") - -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5/lib/python3.13/site-packages" CACHE PATH "") - -set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-zcxbrxijjc3miotq6s3kq446afrgycuu/lib/python3.13/site-packages" CACHE PATH "") - -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/py-pluggy-1.6.0-n4ojmtqyawxz6gipk64pup3ahwz3xqpu/lib/python3.13/site-packages" CACHE PATH "") - -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/py-iniconfig-2.1.0-lf3yvch65se22jr7b5unfoxtse4ugkrk/lib/python3.13/site-packages" CACHE PATH "") - -set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-s4a64moil7qmgczpelmmb344qxxavrzk/lib/python3.13/site-packages" CACHE PATH "") - - diff --git a/host-configs/tuolumne-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake b/host-configs/tuolumne-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake index e20913a2d6..d7839a8c9b 100644 --- a/host-configs/tuolumne-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake +++ b/host-configs/tuolumne-toss_4_x86_64_ib_cray-llvm-amdgpu@6.4.3_hip.cmake @@ -4,28 +4,28 @@ # CMake executable path: /usr/tce/packages/cmake/cmake-3.29.2/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/blt-0.7.1-4kxhk7nbuiv5oh2ymh7sdimnmor36hmo;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/c2c-1.8.0-h5kio7nseyazg4pzgvj4kxanb3plhbxm;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-rmlwpdacdtpzf4u72ctyroj72soqzf3f;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/conduit-0.9.5-zqywrasos2vbears5hvdwxhulm3xox6q;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/lua-5.4.8-gp4kdewsy4uiy6flsx342lgogcm7fym2;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/mfem-4.9.0-6cgokeizyrjzsjzwmtf35p5jvhnws7n5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/py-nanobind-2.7.0-2kp35jcpb73xuujvcz6kmg4q2npek4gg;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/adiak-0.4.0-cropyu42jr4q6k3zar6mkma6gc2vp6tk;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/libunwind-1.8.3-plszp43gkd75kzjcvge3qxrtb6j2budl;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/hdf5-1.8.23-mgic2kxojd5zeivhaz473zzmzrg3gdqe;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/parmetis-4.0.3-hd2qoczrh3vw3ejm6msitqdgecjk7xwe;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/py-mpi4py-4.1.1-wimx6qtzmouefjmhidpsho6iipv234ay;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/py-numpy-2.4.2-a3mo27shjtpliwewbm5cci2kf37crrab;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/hypre-2.27.0-4nrl5egspbxmnkpzfym5udby6uruhn3i;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-v74yskgcb7tsgho6bdush5jdlbigkttf;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/umpire-2025.12.0-jft5rocv5ev4b57f3664b2kagyfjra2e;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/zlib-1.3.1-slqujeue3l44x3pgqbu7e2pw3lh2mj5i;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/metis-5.1.0-puzeddu6ufhz5vpxnw7i3i2i6slsvuuj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-zadbjpethvnlhpqwx6vm6ahphk42a4gq;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/fmt-11.0.2-lpnox72rqcneew5hucwzq4nmms4at525;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/opt/rocm-6.4.3" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-6.4.3/blt-0.7.2-bzkumxze6js6wdthtsyl4ftpehsiaxio;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-6.4.3/c2c-1.8.0-ydvwtgebkhgntradrdhlngk2e2ins6ts;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-6.4.3/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-emtyurqbikhifn5wtfw474oe3kn64mfi;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-6.4.3/conduit-0.9.7-f6ii6fwopqrxtnu3wbzur6pp5toyxhii;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-6.4.3/lua-5.4.8-tje5nnei5fpukdicoscj7qxbzqjpe4m3;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-6.4.3/mfem-4.9.0-pjfp26ljzvrlffcxgse2jlc5g5n5246y;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-6.4.3/py-nanobind-2.12.0-numnrtajdqauya2idgshkccahuczfpkw;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/none-none/py-pytest-9.0.3-4yefg77cpcyymgmjssuqsb7ojxs7aiuo;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-6.4.3/adiak-0.5.0-y4dumyncsqyo6zpicmqathscj7ifygfz;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-6.4.3/libunwind-1.8.3-pi2bqmxzk2wzxc7bzen4e4prp6ne6kdt;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-6.4.3/hdf5-1.8.23-3hacwkv36ucg3rpihr47xas6hs5jsa4y;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-6.4.3/parmetis-4.0.3-4vksfdsbadamrzasmbyzj36urczamcob;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-6.4.3/py-mpi4py-4.1.1-6gainjtlacfzkhyqqz7urkdo3yj3236p;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-6.4.3/py-numpy-2.4.6-cajeg4ft7ph2grvmugdwnusk5qdn6ykl;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-6.4.3/hypre-3.1.0-icopylgqlovyr65krmmtz4grihalnwpa;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-6.4.3/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-3ov5ypd4f3gv7l4f4kb2ihulqjntt2d3;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-6.4.3/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-hiu6spvj4flzb626dg3hxhgp45jnnbt6;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-6.4.3/zlib-1.3.2-yrtzpfoi5ikkd74jkizit5jxnmb4rdxc;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-6.4.3/metis-5.1.0-i5u67yfxxq355s6bplogrdwr6mnynz4s;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-6.4.3/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-2jyb2ctyhlpuidy4z256v7j2ugzqfdpc;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-6.4.3/fmt-12.1.0-4obgphnlva42oeyokwqzkpmsooswpbux;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/none-none/compiler-wrapper-1.1.0-yz3pcrs3u74ghvkyrkg5krf3a3y72be7;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-8.1.29-rocmcc-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-6.4.3;/opt/rocm-6.4.3;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-6.4.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-6.4.3;/opt/rocm-7.2.1;/opt/rocm-6.4.3;/opt/rocm-7.2.1;/opt/rocm-6.4.3" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/axom-develop-iczg7quatuysr5e2ggwwzdsnmdzw7odk/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/axom-develop-iczg7quatuysr5e2ggwwzdsnmdzw7odk/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-6.4.3/axom-develop-p2clmjufguaontgknmbcahddhqtutvoo/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-6.4.3/axom-develop-p2clmjufguaontgknmbcahddhqtutvoo/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/axom-develop-iczg7quatuysr5e2ggwwzdsnmdzw7odk/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3/axom-develop-iczg7quatuysr5e2ggwwzdsnmdzw7odk/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-6.4.3/axom-develop-p2clmjufguaontgknmbcahddhqtutvoo/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-6.4.3/axom-develop-p2clmjufguaontgknmbcahddhqtutvoo/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ # Compilers #------------------------------------------------------------------------------ -# Compiler Spec: llvm-amdgpu@6.4.3/u67i6xutdthzl3yti7tv3klyhyzddbkv +# Compiler Spec: llvm-amdgpu@6.4.3/j3unaymyuiplqezbi6qng6k2nyia7raa #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdclang" CACHE PATH "") + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/none-none/compiler-wrapper-1.1.0-yz3pcrs3u74ghvkyrkg5krf3a3y72be7/libexec/spack/rocmcc/amdclang" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdclang++" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/none-none/compiler-wrapper-1.1.0-yz3pcrs3u74ghvkyrkg5krf3a3y72be7/libexec/spack/rocmcc/amdclang++" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/compiler-wrapper-1.0-pypgv2tridcfdliq3cjhfecu23umxcjd/libexec/spack/rocmcc/amdflang" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/none-none/compiler-wrapper-1.1.0-yz3pcrs3u74ghvkyrkg5krf3a3y72be7/libexec/spack/rocmcc/amdflang" CACHE PATH "") else() @@ -37,7 +37,11 @@ else() endif() -set(CMAKE_Fortran_FLAGS "-Mfreeform" CACHE STRING "") +set(CMAKE_C_FLAGS "-fPIC" CACHE STRING "") + +set(CMAKE_CXX_FLAGS "-fPIC" CACHE STRING "") + +set(CMAKE_Fortran_FLAGS "-fPIC -Mfreeform" CACHE STRING "") set(ENABLE_FORTRAN ON CACHE BOOL "") @@ -84,6 +88,8 @@ set(ROCM_ROOT_DIR "/opt/rocm-6.4.3" CACHE PATH "") set(BLT_CMAKE_IMPLICIT_LINK_LIBRARIES_EXCLUDE "ompstub" CACHE STRING "") +set(BLT_CMAKE_IMPLICIT_LINK_DIRECTORIES_EXCLUDE "/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12;/opt/rh/gcc-toolset-12/root/usr/lib64" CACHE STRING "") + set(CMAKE_EXE_LINKER_FLAGS "-lxpmem -L/opt/cray/pe/mpich/8.1.29/gtl/lib -Wl,-rpath,/opt/cray/pe/mpich/8.1.29/gtl/lib -lmpi_gtl_hsa -L/opt/rocm-6.4.3/lib/llvm/lib -Wl,-rpath,/opt/rocm-6.4.3/lib/llvm/lib -L/opt/rocm-6.4.3/lib -Wl,-rpath,/opt/rocm-6.4.3/lib -lpgmath -Wl,--disable-new-dtags -lflang -lflangrti -lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " CACHE STRING "") #------------------------------------------------ @@ -98,29 +104,29 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") # TPLs #------------------------------------------------------------------------------ -set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/llvm-amdgpu-6.4.3" CACHE PATH "") +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-6.4.3" CACHE PATH "") -set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-zqywrasos2vbears5hvdwxhulm3xox6q" CACHE PATH "") +set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.7-f6ii6fwopqrxtnu3wbzur6pp5toyxhii" CACHE PATH "") -set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-h5kio7nseyazg4pzgvj4kxanb3plhbxm" CACHE PATH "") +set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-ydvwtgebkhgntradrdhlngk2e2ins6ts" CACHE PATH "") -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-6cgokeizyrjzsjzwmtf35p5jvhnws7n5" CACHE PATH "") +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-pjfp26ljzvrlffcxgse2jlc5g5n5246y" CACHE PATH "") -set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-mgic2kxojd5zeivhaz473zzmzrg3gdqe" CACHE PATH "") +set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-3hacwkv36ucg3rpihr47xas6hs5jsa4y" CACHE PATH "") -set(LUA_DIR "${TPL_ROOT}/lua-5.4.8-gp4kdewsy4uiy6flsx342lgogcm7fym2" CACHE PATH "") +set(LUA_DIR "${TPL_ROOT}/lua-5.4.8-tje5nnei5fpukdicoscj7qxbzqjpe4m3" CACHE PATH "") -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-v74yskgcb7tsgho6bdush5jdlbigkttf" CACHE PATH "") +set(RAJA_DIR "${TPL_ROOT}/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-3ov5ypd4f3gv7l4f4kb2ihulqjntt2d3" CACHE PATH "") -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-jft5rocv5ev4b57f3664b2kagyfjra2e" CACHE PATH "") +set(UMPIRE_DIR "${TPL_ROOT}/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-hiu6spvj4flzb626dg3hxhgp45jnnbt6" CACHE PATH "") # OPENCASCADE not built -set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-cropyu42jr4q6k3zar6mkma6gc2vp6tk" CACHE PATH "") +set(ADIAK_DIR "${TPL_ROOT}/adiak-0.5.0-y4dumyncsqyo6zpicmqathscj7ifygfz" CACHE PATH "") -set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-rmlwpdacdtpzf4u72ctyroj72soqzf3f" CACHE PATH "") +set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-emtyurqbikhifn5wtfw474oe3kn64mfi" CACHE PATH "") -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-zadbjpethvnlhpqwx6vm6ahphk42a4gq" CACHE PATH "") +set(CAMP_DIR "${TPL_ROOT}/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-2jyb2ctyhlpuidy4z256v7j2ugzqfdpc" CACHE PATH "") # scr not built @@ -148,16 +154,16 @@ set(CPPCHECK_EXECUTABLE "${DEVTOOLS_ROOT}/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/doxygen-1.15.0/bin/doxygen" CACHE PATH "") -set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-2kp35jcpb73xuujvcz6kmg4q2npek4gg/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.12.0-numnrtajdqauya2idgshkccahuczfpkw/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/py-pytest-9.0.0-jfdwo62b3p36blpm54gvocrcj6afccv5/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/none-none/py-pytest-9.0.3-4yefg77cpcyymgmjssuqsb7ojxs7aiuo/lib/python3.13/site-packages" CACHE PATH "") -set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-a3mo27shjtpliwewbm5cci2kf37crrab/lib/python3.13/site-packages" CACHE PATH "") +set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.6-cajeg4ft7ph2grvmugdwnusk5qdn6ykl/lib/python3.13/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/py-pluggy-1.6.0-n4ojmtqyawxz6gipk64pup3ahwz3xqpu/lib/python3.13/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/none-none/py-pluggy-1.6.0-ient4fgfjoacxpdsxw5qw6vqya3gid4b/lib/python3.13/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_04_22_11_47_33/none-none/py-iniconfig-2.1.0-lf3yvch65se22jr7b5unfoxtse4ugkrk/lib/python3.13/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/none-none/py-iniconfig-2.1.0-h3ficswruattwshoykrh4mp2ds6fwnjg/lib/python3.13/site-packages" CACHE PATH "") -set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-wimx6qtzmouefjmhidpsho6iipv234ay/lib/python3.13/site-packages" CACHE PATH "") +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-6gainjtlacfzkhyqqz7urkdo3yj3236p/lib/python3.13/site-packages" CACHE PATH "") diff --git a/host-configs/tuolumne-toss_4_x86_64_ib_cray-llvm-amdgpu@7.2.1_hip.cmake b/host-configs/tuolumne-toss_4_x86_64_ib_cray-llvm-amdgpu@7.2.1_hip.cmake new file mode 100644 index 0000000000..2272820c77 --- /dev/null +++ b/host-configs/tuolumne-toss_4_x86_64_ib_cray-llvm-amdgpu@7.2.1_hip.cmake @@ -0,0 +1,169 @@ +#------------------------------------------------------------------------------ +# !!!! This is a generated file, edit at own risk !!!! +#------------------------------------------------------------------------------ +# CMake executable path: /usr/tce/packages/cmake/cmake-3.29.2/bin/cmake +#------------------------------------------------------------------------------ + +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-7.2.1/blt-0.7.2-nxjxxyf2y5djqa4vln5xupa6ew3ynot3;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-7.2.1/c2c-1.8.0-h4dewx3nxtgmrrh6lp6qan5prczqgfaj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-7.2.1/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-zbyb2mglk6bf5a3ximlcf6ku7k66zybh;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-7.2.1/conduit-0.9.7-kfuntpxuli4mn2pwj35inyxavtzc6cr7;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-7.2.1/lua-5.4.8-nxtvprvcyhamwfbtybkegxasjkzccxqy;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-7.2.1/mfem-4.9.0-bcwbljz6eiilxpjtkkdfda4kk3lfwazl;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-7.2.1/py-nanobind-2.12.0-zogbnvfusufkfpdv57rgb5tplbzlvklj;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/none-none/py-pytest-9.0.3-4yefg77cpcyymgmjssuqsb7ojxs7aiuo;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-7.2.1/adiak-0.5.0-dg7yk2rewn3kqsz7fqdgginoknzvxamg;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-7.2.1/libunwind-1.8.3-iz7k4qtg4do67ekisexgc2tfil5vrhv3;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-7.2.1/hdf5-1.8.23-evyfjqno2x77bsy7wjoxjw6xg5lz2ndv;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-7.2.1/parmetis-4.0.3-yskd6p7t2azzm3vyjvig42tfpi2rkcua;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-7.2.1/py-mpi4py-4.1.1-bnctpy3xteakdhzawno2xnpof7keqrhb;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-7.2.1/py-numpy-2.4.6-vtwtcnpk3cxghcggxaaohuxmvdtcqavf;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-7.2.1/hypre-3.1.0-mws6aupd6t33sb3c6prw3e2qa2be5glv;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-7.2.1/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-x3acixz33hp3o6zoob76rfwqx7vyy3rw;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-7.2.1/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-ypm44b4vaj27oi6drmdn5hvmggfo4oq4;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-7.2.1/zlib-1.3.2-iqcsdt34b6drsoycmdkejtnrwheup3s5;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-7.2.1/metis-5.1.0-jahfgmirxbyg3ljsuef4dm5jx27d23op;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-7.2.1/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-ipojuvuh27sjhbzzikt2trem3zlmaxkx;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-7.2.1/fmt-12.1.0-d7qzs4sxkrgsyhpdvpu33hcrzkxulav4;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/none-none/compiler-wrapper-1.1.0-yz3pcrs3u74ghvkyrkg5krf3a3y72be7;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/usr/tce/packages/cmake/cmake-3.29.2;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27;/usr/tce/packages/cray-mpich-tce/cray-mpich-9.1.0-rocmcc-7.2.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/doxygen-1.15.0;/opt/rocm-7.2.1;/opt/rocm-7.2.1;/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm;/opt/rocm-7.2.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25/view/python-3.13.11;/opt/rocm-7.2.1;/opt/rocm-7.2.1;/opt/rocm-7.2.1;/opt/rocm-7.2.1;/opt/rocm-7.2.1" CACHE STRING "") + +set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") + +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-7.2.1/axom-develop-fjfrq2tz3jcuqtkimgtmdg7lynoi55dx/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-7.2.1/axom-develop-fjfrq2tz3jcuqtkimgtmdg7lynoi55dx/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") + +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-7.2.1/axom-develop-fjfrq2tz3jcuqtkimgtmdg7lynoi55dx/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-7.2.1/axom-develop-fjfrq2tz3jcuqtkimgtmdg7lynoi55dx/lib64;;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") + +set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") + +#------------------------------------------------------------------------------ +# Compilers +#------------------------------------------------------------------------------ +# Compiler Spec: llvm-amdgpu@7.2.1/titlcdv5eonxuakrjl6bcxvgndtjk4xy +#------------------------------------------------------------------------------ +if(DEFINED ENV{SPACK_CC}) + + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/none-none/compiler-wrapper-1.1.0-yz3pcrs3u74ghvkyrkg5krf3a3y72be7/libexec/spack/rocmcc/amdclang" CACHE PATH "") + + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/none-none/compiler-wrapper-1.1.0-yz3pcrs3u74ghvkyrkg5krf3a3y72be7/libexec/spack/rocmcc/amdclang++" CACHE PATH "") + + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/none-none/compiler-wrapper-1.1.0-yz3pcrs3u74ghvkyrkg5krf3a3y72be7/libexec/spack/rocmcc/amdflang" CACHE PATH "") + +else() + + set(CMAKE_C_COMPILER "/opt/rocm-7.2.1/llvm/bin/amdclang" CACHE PATH "") + + set(CMAKE_CXX_COMPILER "/opt/rocm-7.2.1/llvm/bin/amdclang++" CACHE PATH "") + + set(CMAKE_Fortran_COMPILER "/opt/rocm-7.2.1/llvm/bin/amdflang" CACHE PATH "") + +endif() + +set(CMAKE_C_FLAGS "-fPIC" CACHE STRING "") + +set(CMAKE_CXX_FLAGS "-fPIC" CACHE STRING "") + +set(CMAKE_Fortran_FLAGS "-fPIC" CACHE STRING "") + +set(ENABLE_FORTRAN ON CACHE BOOL "") + +#------------------------------------------------------------------------------ +# MPI +#------------------------------------------------------------------------------ + +set(MPI_C_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-9.1.0-rocmcc-7.2.1/bin/mpicc" CACHE PATH "") + +set(MPI_CXX_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-9.1.0-rocmcc-7.2.1/bin/mpicxx" CACHE PATH "") + +set(MPI_Fortran_COMPILER "/usr/tce/packages/cray-mpich-tce/cray-mpich-9.1.0-rocmcc-7.2.1/bin/mpif90" CACHE PATH "") + +set(MPIEXEC_EXECUTABLE "/usr/global/tools/flux_wrappers/bin/srun" CACHE PATH "") + +set(MPIEXEC_NUMPROC_FLAG "-n" CACHE STRING "") + +set(ENABLE_MPI ON CACHE BOOL "") + +#------------------------------------------------------------------------------ +# Hardware +#------------------------------------------------------------------------------ + +#------------------------------------------------ +# ROCm +#------------------------------------------------ + +set(ROCM_PATH "/opt/rocm-7.2.1" CACHE PATH "") + +set(CMAKE_HIP_ARCHITECTURES "gfx90a;gfx942" CACHE STRING "") + +set(CMAKE_HIP_COMPILER "/opt/rocm-7.2.1/bin/amdclang++" CACHE FILEPATH "") + +#------------------------------------------------------------------------------ + +# Axom ROCm specifics + +#------------------------------------------------------------------------------ + + +set(ENABLE_HIP ON CACHE BOOL "") + +set(ROCM_ROOT_DIR "/opt/rocm-7.2.1" CACHE PATH "") + +set(BLT_CMAKE_IMPLICIT_LINK_LIBRARIES_EXCLUDE "ompstub" CACHE STRING "") + +set(BLT_CMAKE_IMPLICIT_LINK_DIRECTORIES_EXCLUDE "/opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12;/opt/rh/gcc-toolset-12/root/usr/lib64" CACHE STRING "") + +set(CMAKE_EXE_LINKER_FLAGS "-lxpmem -L/opt/cray/pe/mpich/9.1.0/gtl/lib -Wl,-rpath,/opt/cray/pe/mpich/9.1.0/gtl/lib -lmpi_gtl_hsa -L/opt/rocm-7.2.1/lib/llvm/lib -Wl,-rpath,/opt/rocm-7.2.1/lib/llvm/lib -L/opt/rocm-7.2.1/lib -Wl,-rpath,/opt/rocm-7.2.1/lib -lpgmath -Wl,--disable-new-dtags -lflang -lflangrti -lamdhip64 -lhsakmt -lhsa-runtime64 -lamd_comgr " CACHE STRING "") + +#------------------------------------------------ +# Hardware Specifics +#------------------------------------------------ + +set(ENABLE_OPENMP ON CACHE BOOL "") + +set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") + +#------------------------------------------------------------------------------ +# TPLs +#------------------------------------------------------------------------------ + +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/llvm-amdgpu-7.2.1" CACHE PATH "") + +set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.7-kfuntpxuli4mn2pwj35inyxavtzc6cr7" CACHE PATH "") + +set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-h4dewx3nxtgmrrh6lp6qan5prczqgfaj" CACHE PATH "") + +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-bcwbljz6eiilxpjtkkdfda4kk3lfwazl" CACHE PATH "") + +set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-evyfjqno2x77bsy7wjoxjw6xg5lz2ndv" CACHE PATH "") + +set(LUA_DIR "${TPL_ROOT}/lua-5.4.8-nxtvprvcyhamwfbtybkegxasjkzccxqy" CACHE PATH "") + +set(RAJA_DIR "${TPL_ROOT}/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-x3acixz33hp3o6zoob76rfwqx7vyy3rw" CACHE PATH "") + +set(UMPIRE_DIR "${TPL_ROOT}/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-ypm44b4vaj27oi6drmdn5hvmggfo4oq4" CACHE PATH "") + +# OPENCASCADE not built + +set(ADIAK_DIR "${TPL_ROOT}/adiak-0.5.0-dg7yk2rewn3kqsz7fqdgginoknzvxamg" CACHE PATH "") + +set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-zbyb2mglk6bf5a3ximlcf6ku7k66zybh" CACHE PATH "") + +set(CAMP_DIR "${TPL_ROOT}/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-ipojuvuh27sjhbzzikt2trem3zlmaxkx" CACHE PATH "") + +# scr not built + +#------------------------------------------------------------------------------ +# Devtools & Python +#------------------------------------------------------------------------------ + +set(DEVTOOLS_ROOT "/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib_cray/2026_02_17_15_20_25" CACHE PATH "") + +set(CLANGFORMAT_EXECUTABLE "/usr/tce/packages/rocmcc/rocmcc-6.4.3-magic/llvm/bin/clang-format" CACHE PATH "") + +set(Python_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/python3" CACHE PATH "") + +set(JSONSCHEMA_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/jsonschema" CACHE PATH "") + +set(ENABLE_DOCS ON CACHE BOOL "") + +set(SPHINX_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/sphinx-build" CACHE PATH "") + +set(YAPF_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/python-3.13.11/bin/yapf" CACHE PATH "") + +set(SHROUD_EXECUTABLE "/collab/usr/gapps/shroud/public/toss_4_x86_64_ib_cray/shroud-0.14.0/bin/shroud" CACHE PATH "") + +set(CPPCHECK_EXECUTABLE "${DEVTOOLS_ROOT}/gcc-13.3.1/cppcheck-2.18.0-n6kdcwtwlrc3u3t47t7gokpyd4h6mc27/bin/cppcheck" CACHE PATH "") + +set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/ywmag65dnysd7p4dhlmcoaaqqxfzgz5a/doxygen-1.15.0/bin/doxygen" CACHE PATH "") + +set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.12.0-zogbnvfusufkfpdv57rgb5tplbzlvklj/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_PYTEST_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/none-none/py-pytest-9.0.3-4yefg77cpcyymgmjssuqsb7ojxs7aiuo/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.6-vtwtcnpk3cxghcggxaaohuxmvdtcqavf/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_PLUGGY_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/none-none/py-pluggy-1.6.0-ient4fgfjoacxpdsxw5qw6vqya3gid4b/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_INICONFIG_DIR "/usr/WS1/axom/libs/toss_4_x86_64_ib_cray/2026_07_08_09_47_04/none-none/py-iniconfig-2.1.0-h3ficswruattwshoykrh4mp2ds6fwnjg/lib/python3.13/site-packages" CACHE PATH "") + +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-bnctpy3xteakdhzawno2xnpof7keqrhb/lib/python3.13/site-packages" CACHE PATH "") + + From ce5b70f70b060e6c5981f90d35fbd1745e77f587 Mon Sep 17 00:00:00 2001 From: chapman39 Date: Thu, 9 Jul 2026 16:15:56 -0700 Subject: [PATCH 676/986] improve error message when collection not registered as varient struct --- src/axom/inlet/Container.hpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/axom/inlet/Container.hpp b/src/axom/inlet/Container.hpp index b8a6c6342f..ed197794bd 100644 --- a/src/axom/inlet/Container.hpp +++ b/src/axom/inlet/Container.hpp @@ -1589,7 +1589,12 @@ const detail::VariantStructFactory& Container::variantStructFactory() c { const auto factory_iter = m_variant_struct_factories.find(std::type_index(typeid(Variant))); SLIC_ERROR_IF(factory_iter == m_variant_struct_factories.end(), - "[Inlet] Variant struct collection schema has not been registered"); + fmt::format("[Inlet] Collection '{0}' was read as a variant struct collection, " + "but no variant schema was registered. Define it with " + "addVariantStructArray() or addVariantStructDictionary(), then " + "register each alternative with addAlternative() before calling " + "get<...>().", + m_name)); auto* factory = dynamic_cast*>(factory_iter->second.get()); SLIC_ERROR_IF(factory == nullptr, "[Inlet] Variant struct factory type mismatch"); From 49343d0e3783cf79380aab38ff77cd6746b6e0b5 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Thu, 9 Jul 2026 16:28:40 -0700 Subject: [PATCH 677/986] Update Gitlab CI HIP configs --- .gitlab/build_tioga.yml | 10 +++++----- .gitlab/build_tuolumne.yml | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.gitlab/build_tioga.yml b/.gitlab/build_tioga.yml index 2f7d720d7a..6afd43d3ae 100644 --- a/.gitlab/build_tioga.yml +++ b/.gitlab/build_tioga.yml @@ -35,17 +35,17 @@ #### # PR Build jobs -tioga-llvm-amdgpu_6_3_1_hip-src: +tioga-llvm-amdgpu_6_4_3_hip-src: variables: - COMPILER: "llvm-amdgpu@6.3.1_hip" + COMPILER: "llvm-amdgpu@6.4.3_hip" HOST_CONFIG: "tioga-toss_4_x86_64_ib_cray-${COMPILER}.cmake" extends: .src_build_on_tioga #### # Full Build jobs -tioga-llvm-amdgpu_6_3_1_hip-full: +tioga-llvm-amdgpu_6_4_3_hip-full: variables: - COMPILER: "llvm-amdgpu@6.3.1_hip" + COMPILER: "llvm-amdgpu@6.4.3_hip" SPEC: "%${COMPILER}~openmp+rocm+mfem+c2c" - EXTRASPEC: "amdgpu_target=gfx90a ^hip@6.3.1 ^hsa-rocr-dev@6.3.1 ^raja~openmp+rocm ^umpire~openmp+rocm ^hdf5 cflags=-Wno-int-conversion" + EXTRASPEC: "amdgpu_target=gfx90a ^hip@6.4.3 ^hsa-rocr-dev@6.4.3 ^raja~openmp+rocm ^umpire~openmp+rocm ^hdf5 cflags=-Wno-int-conversion" extends: .full_build_on_tioga diff --git a/.gitlab/build_tuolumne.yml b/.gitlab/build_tuolumne.yml index 5111426daf..a034932ce2 100644 --- a/.gitlab/build_tuolumne.yml +++ b/.gitlab/build_tuolumne.yml @@ -37,18 +37,18 @@ #### # PR Build jobs # Disable OpenMP to avoid timeout -tuolumne-llvm-amdgpu_6_4_3_hip-src: +tuolumne-llvm-amdgpu_7_2_1_hip-src: variables: - COMPILER: "llvm-amdgpu@6.4.3_hip" + COMPILER: "llvm-amdgpu@7.2.1_hip" HOST_CONFIG: "tuolumne-toss_4_x86_64_ib_cray-${COMPILER}.cmake" EXTRA_CMAKE_OPTIONS: "-DENABLE_OPENMP:BOOL=OFF" extends: .src_build_on_tuolumne #### # Full Build jobs -tuolumne-llvm-amdgpu_6_4_3_hip-full: +tuolumne-llvm-amdgpu_7_2_1_hip-full: variables: - COMPILER: "llvm-amdgpu@6.4.3_hip" + COMPILER: "llvm-amdgpu@7.2.1_hip" SPEC: "%${COMPILER}~openmp+rocm+mfem+c2c" - EXTRASPEC: "amdgpu_target=gfx90a ^hip@6.4.3 ^hsa-rocr-dev@6.4.3 ^raja~openmp+rocm ^umpire~openmp+rocm ^hdf5 cflags=-Wno-int-conversion" + EXTRASPEC: "amdgpu_target=gfx90a ^hip@7.2.1 ^hsa-rocr-dev@7.2.1 ^raja~openmp+rocm ^umpire~openmp+rocm ^hdf5 cflags=-Wno-int-conversion" extends: .full_build_on_tuolumne From 321cb6e2d71d04ad68651dc215b2f66622748441 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Thu, 9 Jul 2026 16:55:25 -0700 Subject: [PATCH 678/986] Lambda capture warnings for C++20 gcc fix --- src/axom/quest/GWNMethods.hpp | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/axom/quest/GWNMethods.hpp b/src/axom/quest/GWNMethods.hpp index ccd0354948..1476da281d 100644 --- a/src/axom/quest/GWNMethods.hpp +++ b/src/axom/quest/GWNMethods.hpp @@ -209,21 +209,21 @@ class NURBSCurveGWNQuery const int ncurves = m_processed_curves_view.size(); axom::Array aabbs(ncurves, ncurves); auto aabbs_view = aabbs.view(); + const auto processed_curves_view = m_processed_curves_view; axom::for_all( ncurves, - AXOM_LAMBDA(axom::IndexType i) { - aabbs_view[i] = m_processed_curves_view[i].boundingBox(); - }); + AXOM_LAMBDA(axom::IndexType i) { aabbs_view[i] = processed_curves_view[i].boundingBox(); }); m_bvh.initialize(aabbs_view, ncurves); } { AXOM_ANNOTATE_SCOPE("moment_precomputation"); - auto compute_moments = [=](std::int32_t currentNode, - const std::int32_t* leafNodes) -> GWNMoments { + const auto processed_curves_view = m_processed_curves_view; + auto compute_moments = [processed_curves_view](std::int32_t currentNode, + const std::int32_t* leafNodes) -> GWNMoments { const auto idx = leafNodes[currentNode]; - return GWNMoments(m_processed_curves_view[idx]); + return GWNMoments(processed_curves_view[idx]); }; const auto traverser = m_bvh.getTraverser(); @@ -279,6 +279,7 @@ class NURBSCurveGWNQuery { AXOM_ANNOTATE_SCOPE("query"); const primal::WindingTolerances tol_copy = tol; + const auto processed_curves_view = m_processed_curves_view; // Use fast approximation if(m_bvh.isInitialized()) @@ -292,7 +293,7 @@ class NURBSCurveGWNQuery axom::for_all(num_query_points, [=, &winding, &inout](axom::IndexType index) { const double wn = axom::quest::fast_approximate_winding_number(query_point(index), traverser, - m_processed_curves_view, + processed_curves_view, internal_moments_view, tol_copy); winding[static_cast(index)] = wn; @@ -324,7 +325,7 @@ class NURBSCurveGWNQuery axom::for_all(num_query_points, [=, &winding, &inout](axom::IndexType nidx) { const auto q = query_point(static_cast(nidx)); double wn {}; - for(const auto& curve : m_processed_curves_view) + for(const auto& curve : processed_curves_view) { wn += axom::primal::winding_number(q, curve, tol_copy.edge_tol, tol_copy.EPS); } @@ -638,12 +639,11 @@ class NURBSPatchGWNQuery const int npatches = m_processed_patches_view.size(); axom::Array aabbs(npatches, npatches); auto aabbs_view = aabbs.view(); + const auto processed_patches_view = m_processed_patches_view; axom::for_all( npatches, - AXOM_LAMBDA(axom::IndexType i) { - aabbs_view[i] = m_processed_patches_view[i].boundingBox(); - }); + AXOM_LAMBDA(axom::IndexType i) { aabbs_view[i] = processed_patches_view[i].boundingBox(); }); m_bvh.initialize(aabbs_view, npatches); } @@ -655,10 +655,11 @@ class NURBSPatchGWNQuery auto normals_view = precomputed_normals.view(); auto surface_areas_view = precomputed_surface_areas.view(); + const auto processed_patches_view = m_processed_patches_view; auto compute_moments = [=](std::int32_t currentNode, const std::int32_t* leafNodes) -> GWNMoments { const auto idx = leafNodes[currentNode]; - const auto leaf_moments = GWNMoments(m_processed_patches_view[idx]); + const auto leaf_moments = GWNMoments(processed_patches_view[idx]); normals_view[idx] = leaf_moments.getNormal(); surface_areas_view[idx] = leaf_moments.getSurfaceArea(); @@ -735,6 +736,7 @@ class NURBSPatchGWNQuery { AXOM_ANNOTATE_SCOPE("query"); const primal::WindingTolerances tol_copy = tol; + const auto processed_patches_view = m_processed_patches_view; // Use fast approximation if(m_bvh.isInitialized()) @@ -750,7 +752,7 @@ class NURBSPatchGWNQuery axom::for_all(num_query_points, [=, &winding, &inout](axom::IndexType index) { const double wn = axom::quest::fast_approximate_winding_number(query_point(index), traverser, - m_processed_patches_view, + processed_patches_view, internal_moments_view, tol_copy); winding[static_cast(index)] = wn; @@ -784,7 +786,7 @@ class NURBSPatchGWNQuery axom::for_all(num_query_points, [=, &winding, &inout](axom::IndexType nidx) { const auto q = query_point(static_cast(nidx)); double wn {}; - for(const auto& patch : m_processed_patches_view) + for(const auto& patch : processed_patches_view) { wn += axom::primal::winding_number(q, patch, From d884b2f38c58879df6b6edd27a9a70eab06dc6ac Mon Sep 17 00:00:00 2001 From: Brian Han Date: Mon, 13 Jul 2026 07:33:00 -0700 Subject: [PATCH 679/986] Werror fixes for C++20 --- src/axom/sidre/tests/sidre_attribute.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/axom/sidre/tests/sidre_attribute.cpp b/src/axom/sidre/tests/sidre_attribute.cpp index 64ba94591b..306dd4ed47 100644 --- a/src/axom/sidre/tests/sidre_attribute.cpp +++ b/src/axom/sidre/tests/sidre_attribute.cpp @@ -33,7 +33,6 @@ const std::string g_color_blue("blue"); const std::string g_name_animal("animal"); const std::string g_animal_none("human"); -const std::string g_animal_cat("cat"); const std::string g_animal_dog("dog"); const std::string g_namea("a"); From 00cdd3d9d731cb0c9170fd4220bf3d7a655581c1 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Mon, 13 Jul 2026 07:39:04 -0700 Subject: [PATCH 680/986] Update docker tag and docker host-configs --- .github/workflows/ci-tests.yml | 4 +-- host-configs/docker/gcc@13.3.1.cmake | 50 +++++++++++++-------------- host-configs/docker/llvm@19.0.0.cmake | 50 +++++++++++++-------------- 3 files changed, 52 insertions(+), 52 deletions(-) diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index 0371952443..bb8773e2ef 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -12,8 +12,8 @@ concurrency: cancel-in-progress: ${{ github.ref != 'refs/heads/develop' }} env: - CLANG_DOCKER_IMAGE: axom/tpls:clang-19_05-19-26_17h-35m - GCC_DOCKER_IMAGE: axom/tpls:gcc-13_05-19-26_17h-32m + CLANG_DOCKER_IMAGE: axom/tpls:clang-19_07-09-26_22h-40m + GCC_DOCKER_IMAGE: axom/tpls:gcc-13_07-09-26_22h-39m jobs: # Hacky solution to reference env variables outside of `run` steps https://stackoverflow.com/a/74217028 diff --git a/host-configs/docker/gcc@13.3.1.cmake b/host-configs/docker/gcc@13.3.1.cmake index 9fda04ffda..b6f0026836 100644 --- a/host-configs/docker/gcc@13.3.1.cmake +++ b/host-configs/docker/gcc@13.3.1.cmake @@ -4,28 +4,28 @@ # CMake executable path: /usr/local/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/home/axom/axom_tpls/gcc-13.3.1/blt-0.7.1-tp6erawewp4l2ewllhglzso5fnjudoja;/home/axom/axom_tpls/gcc-13.3.1/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-qollf3jglv6wxat4uffls42u2t2biu43;/home/axom/axom_tpls/gcc-13.3.1/conduit-0.9.5-tov2czaq7ifefmpwa2gtqaqgelclqwge;/home/axom/axom_tpls/gcc-13.3.1/gmake-4.4.1-jclt3ixkhzk7gh4qz7bph3nqno2d2tan;/home/axom/axom_tpls/gcc-13.3.1/mfem-4.9.0-27y23akm3yqlnmn3y4ujuiif7ch3d42x;/home/axom/axom_tpls/gcc-13.3.1/py-nanobind-2.7.0-haxzzbmxto45ae43fjjdfmtgo4l5qhjx;/home/axom/axom_tpls/none-none/py-pytest-9.0.0-lzujihl4aaovgis2uoemzabjpymsjfuj;/home/axom/axom_tpls/gcc-13.3.1/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-xk3wk7pkfxfmczqsud2zgvoaynygae7x;/home/axom/axom_tpls/gcc-13.3.1/umpire-2025.12.0-eg7ottka4ejtla2sshmo5fbnz6ievmht;/home/axom/axom_tpls/gcc-13.3.1/adiak-0.4.0-7tvwkbkbsrz7pg7cmzjbgpakeqwxiwfn;/home/axom/axom_tpls/gcc-13.3.1/elfutils-0.193-yvyizihf3ovzcrz2xspbo3xqpcsgpciw;/home/axom/axom_tpls/gcc-13.3.1/libunwind-1.8.3-dms6jlqrs6yvgwf454ezco5mszpn4fny;/home/axom/axom_tpls/gcc-13.3.1/hdf5-1.8.23-c5fghc2avhw2ujuhxb32akojoz62ulww;/home/axom/axom_tpls/gcc-13.3.1/parmetis-4.0.3-fwggxdxmzfdyu4zuwzps2iy4ku5nlzbq;/home/axom/axom_tpls/gcc-13.3.1/py-mpi4py-4.1.1-n2ebx2enfluwannhglskxq3j5ntbasbp;/home/axom/axom_tpls/gcc-13.3.1/py-numpy-2.4.2-4z3hsqslzknft2priqptynbsotvbetca;/home/axom/axom_tpls/gcc-13.3.1/hypre-2.27.0-yukxydlpq2lrtkdzhpyvnyfxo6u62dqh;/home/axom/axom_tpls/gcc-13.3.1/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-gdtpys6nyl4kfgvwd5d46hh7mrx4c64f;/home/axom/axom_tpls/gcc-13.3.1/fmt-11.0.2-auzpmart4pgyllfizbgbrmi7vo5l7imm;/home/axom/axom_tpls/gcc-13.3.1/zstd-1.5.7-ynwtbrjy4fy7fmg4mnoq7inn6j36z2lo;/home/axom/axom_tpls/gcc-13.3.1/metis-5.1.0-ngjvzo5djlp4rtpsh2rro63istec4tks;/home/axom/axom_tpls/gcc-13.3.1/mpich-4.2.0-oafwifl7wossiwmdg3osevy3h42nehm5;/home/axom/axom_tpls/gcc-13.3.1/hwloc-2.12.2-r6jiv64detpwvvobvcpbxqsgbyz4an7b;/home/axom/axom_tpls/gcc-13.3.1/libfabric-2.4.0-glityu7f5kxxo72mbpnt2h54aowe2o7j;/home/axom/axom_tpls/gcc-13.3.1/yaksa-0.4-2nqzb7ap73wiacj77ktkz5qh33s3h7cj;/home/axom/axom_tpls/gcc-13.3.1/libpciaccess-0.17-e7jficwldulh2dbp7l742wedsof3d6h5;/home/axom/axom_tpls/gcc-13.3.1/libxml2-2.13.5-wj6jpg6gipn4cjol77o5bwppiymtifjj;/home/axom/axom_tpls/gcc-13.3.1/ncurses-6.5-20250705-izsjfkqb573w7lny6vvdbt57lctv2j22;/home/axom/axom_tpls/gcc-13.3.1/libiconv-1.18-nwihe6gonhf3rig4qghahxqrdzcpxzp2;/home/axom/axom_tpls/gcc-13.3.1/xz-5.6.3-t6r2wp2e2kybjuus2yzlqap6khgwvw73;/home/axom/axom_tpls/gcc-13.3.1/zlib-ng-2.3.2-xturc74asm73dunfzuwguafjrucw75ae;/home/axom/axom_tpls/none-none/gcc-runtime-13.3.1-ahhevkdxsqek4foiubajeiisj7ryali4;/home/axom/axom_tpls/none-none/compiler-wrapper-1.0-u5fjo4cce7cqt6425ipfxnafausfog7z" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/home/axom/axom_tpls/gcc-13.3.1/blt-0.7.2-5absc3jx6wakorcjsjkdynqwxnbnorh6;/home/axom/axom_tpls/gcc-13.3.1/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-inngzcnkamxvkx4tf63litjl37mvlhi3;/home/axom/axom_tpls/gcc-13.3.1/conduit-0.9.7-4u7iwy46n5rjodzl22mh7qpwoodwxv2x;/home/axom/axom_tpls/gcc-13.3.1/gmake-4.4.1-eo7xr5pepi4gjz4id45mww2wwoobqna4;/home/axom/axom_tpls/gcc-13.3.1/mfem-4.9.0-gkkrkhqk32u6czms2qlaengpmbvvonos;/home/axom/axom_tpls/gcc-13.3.1/py-nanobind-2.12.0-7hnfo23qy7drvs62irqa2gexzkhlvres;/home/axom/axom_tpls/none-none/py-pytest-9.0.3-wsk5ciiop2d2pscyghldid7tl63skcka;/home/axom/axom_tpls/gcc-13.3.1/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-xzintylixuvg4mx46mvsx4m2fqujzvzz;/home/axom/axom_tpls/gcc-13.3.1/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-m6kv3jw7n6tlzdgadoevpdbnaw3vkkyz;/home/axom/axom_tpls/gcc-13.3.1/adiak-0.5.0-qfz5sst753v27xdf55jjxcddepe6eken;/home/axom/axom_tpls/gcc-13.3.1/elfutils-0.194-24lcmosh4ix5dftamu6sl4krvd3qzcq7;/home/axom/axom_tpls/gcc-13.3.1/libunwind-1.8.3-lm4pigmiq5ev32in37mfo5pct2z4t7ka;/home/axom/axom_tpls/gcc-13.3.1/hdf5-1.8.23-vupxkvhhvrj5yllafuiydpgvnny5tdvf;/home/axom/axom_tpls/gcc-13.3.1/parmetis-4.0.3-hbcgadqq565zdm5sm7oeg43c2o25f4lp;/home/axom/axom_tpls/gcc-13.3.1/py-mpi4py-4.1.1-4raoq3qthuhepcfzlp3k4vuu2gx23ok7;/home/axom/axom_tpls/gcc-13.3.1/py-numpy-2.4.6-xnjkt4apjzihu342dmdtpyin3wq6xyfc;/home/axom/axom_tpls/gcc-13.3.1/hypre-3.1.0-qkjigq3mapfvdz2kjqz7dhmn4arpdxyw;/home/axom/axom_tpls/gcc-13.3.1/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-wcnztqffxlaxgoql4davxooormnfyv37;/home/axom/axom_tpls/gcc-13.3.1/fmt-12.1.0-gngayzothl7ztt3bauma6t44imsldtfn;/home/axom/axom_tpls/gcc-13.3.1/zstd-1.5.7-7g4seslqjeav25vhy3rox5s4n43btd6y;/home/axom/axom_tpls/gcc-13.3.1/metis-5.1.0-olrhfcwe2wisiz7in37tyq56lv5elmx3;/home/axom/axom_tpls/gcc-13.3.1/mpich-4.2.0-xvyqlju67s3lyqmitye6roi2aqnihskz;/home/axom/axom_tpls/gcc-13.3.1/hwloc-2.13.0-fam7eyle4i3ooqfrkgkrlfwpul4qn52e;/home/axom/axom_tpls/gcc-13.3.1/libfabric-2.5.1-op5snnnjhxudqbotmo3u7pdjo4xz3ozz;/home/axom/axom_tpls/gcc-13.3.1/yaksa-0.4-xaaaay5p6gx6yrgvrosbpul2edpevf7r;/home/axom/axom_tpls/gcc-13.3.1/libpciaccess-0.17-7kbxqu54pe26k5lsycdmofuy2zzoqsfe;/home/axom/axom_tpls/gcc-13.3.1/libxml2-2.15.3-i6bw5j722v27z6yuj4j6of6s4xstrcie;/home/axom/axom_tpls/gcc-13.3.1/ncurses-6.6-dkz2ddp537t4bxvqneirkupvmfzkxxcc;/home/axom/axom_tpls/gcc-13.3.1/libiconv-1.18-hbn7cpldeutre5dr6puqt4573djkr5vj;/home/axom/axom_tpls/gcc-13.3.1/xz-5.8.3-l2j7trx645gxzandrqmk7tnjh3patviy;/home/axom/axom_tpls/gcc-13.3.1/zlib-ng-2.3.3-ggnrpefs2kr7yleaxhhy4ugi6uebp6yy;/home/axom/axom_tpls/none-none/gcc-runtime-13.3.1-tmwwov6s5vhnf7ecrbz24zwhy4bclvox;/home/axom/axom_tpls/none-none/compiler-wrapper-1.1.0-m2hzi6ouuda4xfn6tiiaok5ekmsr62nh" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/home/axom/axom_tpls/gcc-13.3.1/axom-develop-hhm5dhgenh2t6dyv3rykj7vvnpmybt3p/lib;/home/axom/axom_tpls/gcc-13.3.1/axom-develop-hhm5dhgenh2t6dyv3rykj7vvnpmybt3p/lib64;;" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/home/axom/axom_tpls/gcc-13.3.1/axom-develop-inlbmd6uqchvg3t3efjhheult5m2vz6l/lib;/home/axom/axom_tpls/gcc-13.3.1/axom-develop-inlbmd6uqchvg3t3efjhheult5m2vz6l/lib64;;" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/home/axom/axom_tpls/gcc-13.3.1/axom-develop-hhm5dhgenh2t6dyv3rykj7vvnpmybt3p/lib;/home/axom/axom_tpls/gcc-13.3.1/axom-develop-hhm5dhgenh2t6dyv3rykj7vvnpmybt3p/lib64;;" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/home/axom/axom_tpls/gcc-13.3.1/axom-develop-inlbmd6uqchvg3t3efjhheult5m2vz6l/lib;/home/axom/axom_tpls/gcc-13.3.1/axom-develop-inlbmd6uqchvg3t3efjhheult5m2vz6l/lib64;;" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ # Compilers #------------------------------------------------------------------------------ -# Compiler Spec: gcc@13.3.1/3ueirnbsml6vw26zpzmeekt6nwal4lhw +# Compiler Spec: gcc@13.3.1/qheu24jrtqgtemkgehdgrqp6sxqnpu6k #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/home/axom/axom_tpls/none-none/compiler-wrapper-1.0-u5fjo4cce7cqt6425ipfxnafausfog7z/libexec/spack/gcc/gcc" CACHE PATH "") + set(CMAKE_C_COMPILER "/home/axom/axom_tpls/none-none/compiler-wrapper-1.1.0-m2hzi6ouuda4xfn6tiiaok5ekmsr62nh/libexec/spack/gcc/gcc" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/home/axom/axom_tpls/none-none/compiler-wrapper-1.0-u5fjo4cce7cqt6425ipfxnafausfog7z/libexec/spack/gcc/g++" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/home/axom/axom_tpls/none-none/compiler-wrapper-1.1.0-m2hzi6ouuda4xfn6tiiaok5ekmsr62nh/libexec/spack/gcc/g++" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/home/axom/axom_tpls/none-none/compiler-wrapper-1.0-u5fjo4cce7cqt6425ipfxnafausfog7z/libexec/spack/gcc/gfortran" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/home/axom/axom_tpls/none-none/compiler-wrapper-1.1.0-m2hzi6ouuda4xfn6tiiaok5ekmsr62nh/libexec/spack/gcc/gfortran" CACHE PATH "") else() @@ -47,13 +47,13 @@ set(ENABLE_FORTRAN ON CACHE BOOL "") # MPI #------------------------------------------------------------------------------ -set(MPI_C_COMPILER "/home/axom/axom_tpls/gcc-13.3.1/mpich-4.2.0-oafwifl7wossiwmdg3osevy3h42nehm5/bin/mpicc" CACHE PATH "") +set(MPI_C_COMPILER "/home/axom/axom_tpls/gcc-13.3.1/mpich-4.2.0-xvyqlju67s3lyqmitye6roi2aqnihskz/bin/mpicc" CACHE PATH "") -set(MPI_CXX_COMPILER "/home/axom/axom_tpls/gcc-13.3.1/mpich-4.2.0-oafwifl7wossiwmdg3osevy3h42nehm5/bin/mpicxx" CACHE PATH "") +set(MPI_CXX_COMPILER "/home/axom/axom_tpls/gcc-13.3.1/mpich-4.2.0-xvyqlju67s3lyqmitye6roi2aqnihskz/bin/mpicxx" CACHE PATH "") -set(MPI_Fortran_COMPILER "/home/axom/axom_tpls/gcc-13.3.1/mpich-4.2.0-oafwifl7wossiwmdg3osevy3h42nehm5/bin/mpif90" CACHE PATH "") +set(MPI_Fortran_COMPILER "/home/axom/axom_tpls/gcc-13.3.1/mpich-4.2.0-xvyqlju67s3lyqmitye6roi2aqnihskz/bin/mpif90" CACHE PATH "") -set(MPIEXEC_EXECUTABLE "/home/axom/axom_tpls/gcc-13.3.1/mpich-4.2.0-oafwifl7wossiwmdg3osevy3h42nehm5/bin/mpirun" CACHE PATH "") +set(MPIEXEC_EXECUTABLE "/home/axom/axom_tpls/gcc-13.3.1/mpich-4.2.0-xvyqlju67s3lyqmitye6roi2aqnihskz/bin/mpirun" CACHE PATH "") set(MPIEXEC_NUMPROC_FLAG "-np" CACHE STRING "") @@ -77,27 +77,27 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") set(TPL_ROOT "/home/axom/axom_tpls/gcc-13.3.1" CACHE PATH "") -set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-tov2czaq7ifefmpwa2gtqaqgelclqwge" CACHE PATH "") +set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.7-4u7iwy46n5rjodzl22mh7qpwoodwxv2x" CACHE PATH "") # C2C not built -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-27y23akm3yqlnmn3y4ujuiif7ch3d42x" CACHE PATH "") +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-gkkrkhqk32u6czms2qlaengpmbvvonos" CACHE PATH "") -set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-c5fghc2avhw2ujuhxb32akojoz62ulww" CACHE PATH "") +set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-vupxkvhhvrj5yllafuiydpgvnny5tdvf" CACHE PATH "") set(LUA_DIR "/usr" CACHE PATH "") -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-xk3wk7pkfxfmczqsud2zgvoaynygae7x" CACHE PATH "") +set(RAJA_DIR "${TPL_ROOT}/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-xzintylixuvg4mx46mvsx4m2fqujzvzz" CACHE PATH "") -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-eg7ottka4ejtla2sshmo5fbnz6ievmht" CACHE PATH "") +set(UMPIRE_DIR "${TPL_ROOT}/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-m6kv3jw7n6tlzdgadoevpdbnaw3vkkyz" CACHE PATH "") # OPENCASCADE not built -set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-7tvwkbkbsrz7pg7cmzjbgpakeqwxiwfn" CACHE PATH "") +set(ADIAK_DIR "${TPL_ROOT}/adiak-0.5.0-qfz5sst753v27xdf55jjxcddepe6eken" CACHE PATH "") -set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-qollf3jglv6wxat4uffls42u2t2biu43" CACHE PATH "") +set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-inngzcnkamxvkx4tf63litjl37mvlhi3" CACHE PATH "") -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-gdtpys6nyl4kfgvwd5d46hh7mrx4c64f" CACHE PATH "") +set(CAMP_DIR "${TPL_ROOT}/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-wcnztqffxlaxgoql4davxooormnfyv37" CACHE PATH "") # scr not built @@ -113,16 +113,16 @@ set(Python_EXECUTABLE "/usr/bin/python3" CACHE PATH "") set(ENABLE_DOCS OFF CACHE BOOL "") -set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-haxzzbmxto45ae43fjjdfmtgo4l5qhjx/lib/python3.12/site-packages" CACHE PATH "") +set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.12.0-7hnfo23qy7drvs62irqa2gexzkhlvres/lib/python3.12/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/home/axom/axom_tpls/none-none/py-pytest-9.0.0-lzujihl4aaovgis2uoemzabjpymsjfuj/lib/python3.12/site-packages" CACHE PATH "") +set(PY_PYTEST_DIR "/home/axom/axom_tpls/none-none/py-pytest-9.0.3-wsk5ciiop2d2pscyghldid7tl63skcka/lib/python3.12/site-packages" CACHE PATH "") -set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-4z3hsqslzknft2priqptynbsotvbetca/lib/python3.12/site-packages" CACHE PATH "") +set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.6-xnjkt4apjzihu342dmdtpyin3wq6xyfc/lib/python3.12/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/home/axom/axom_tpls/none-none/py-pluggy-1.6.0-mpbg4tbgwvzfntupp2kdega47dr2vxi6/lib/python3.12/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/home/axom/axom_tpls/none-none/py-pluggy-1.6.0-xgegorq25sifwjlfyjctzynclh575iuk/lib/python3.12/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/home/axom/axom_tpls/none-none/py-iniconfig-2.1.0-yhyncq6joox5xckyjtjnj6tuoc35s72m/lib/python3.12/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/home/axom/axom_tpls/none-none/py-iniconfig-2.1.0-nmh3ypew4zehi2ymgsflx65cfsiahf4y/lib/python3.12/site-packages" CACHE PATH "") -set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-n2ebx2enfluwannhglskxq3j5ntbasbp/lib/python3.12/site-packages" CACHE PATH "") +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-4raoq3qthuhepcfzlp3k4vuu2gx23ok7/lib/python3.12/site-packages" CACHE PATH "") diff --git a/host-configs/docker/llvm@19.0.0.cmake b/host-configs/docker/llvm@19.0.0.cmake index 783d9b41bc..40e7e29922 100644 --- a/host-configs/docker/llvm@19.0.0.cmake +++ b/host-configs/docker/llvm@19.0.0.cmake @@ -4,28 +4,28 @@ # CMake executable path: /usr/local/bin/cmake #------------------------------------------------------------------------------ -set(CMAKE_PREFIX_PATH "/home/axom/axom_tpls/llvm-19.0.0/blt-0.7.1-p7mm766jfnjcbcnon7lmbmjk52d7nnfy;/home/axom/axom_tpls/llvm-19.0.0/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-qv2wv4xikow5wxrhdem6c2dvkxt2cptf;/home/axom/axom_tpls/llvm-19.0.0/conduit-0.9.5-vzh2futlihcbvpypnehpznvnqgijdflr;/home/axom/axom_tpls/llvm-19.0.0/gmake-4.4.1-xsybuyd32plkd6jcojnvdqkbwnt44ij6;/home/axom/axom_tpls/llvm-19.0.0/mfem-4.9.0-g3kaipukjvqu677tqztygnik5kzcxhfj;/home/axom/axom_tpls/llvm-19.0.0/py-nanobind-2.7.0-wvrnl66utfn2pr23wovdcp4eko42usc2;/home/axom/axom_tpls/none-none/py-pytest-9.0.0-rrb5ddzqzb7gvgzm7kpaqkye6bdjwsic;/home/axom/axom_tpls/llvm-19.0.0/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-dkkk27oc7p2gsrq4cslpdsv5qz7g3sbd;/home/axom/axom_tpls/llvm-19.0.0/umpire-2025.12.0-kulqmeumvx2c36r6rzz7lwndhsv3ot4i;/home/axom/axom_tpls/llvm-19.0.0/adiak-0.4.0-idbd67m2y4b4gnuxxbivm7hvesbbl4q5;/home/axom/axom_tpls/llvm-19.0.0/elfutils-0.193-437imlexpxs7pztuf7vh33f4mrvwh2i2;/home/axom/axom_tpls/llvm-19.0.0/libunwind-1.8.3-3tkxrnad3g5t5zjiodqfqne3sqfq5pzn;/home/axom/axom_tpls/llvm-19.0.0/hdf5-1.8.23-mwa7fanurwtkpebryywcggusn4gd7yg2;/home/axom/axom_tpls/llvm-19.0.0/parmetis-4.0.3-oe7i7ur4mu5sqkoet576wwcezbbodolt;/home/axom/axom_tpls/llvm-19.0.0/py-mpi4py-4.1.1-vcihkzxcnca4aeslr53dqlijjwhcwuuh;/home/axom/axom_tpls/llvm-19.0.0/py-numpy-2.4.2-3lrivxf4cgsisw7njzzpyihmo7ok2b77;/home/axom/axom_tpls/llvm-19.0.0/hypre-2.27.0-phspcafi6ten26qxkokwwij44ygoctxz;/home/axom/axom_tpls/llvm-19.0.0/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-y2k23nuv2eya2soe3fznpcgsgt2k2dai;/home/axom/axom_tpls/llvm-19.0.0/fmt-11.0.2-eutx47cvctyr7hyulz4rv4q4nfg5apzm;/home/axom/axom_tpls/llvm-19.0.0/zstd-1.5.7-ereaou2vj32kdin5v4z23qtd36yk4euw;/home/axom/axom_tpls/llvm-19.0.0/metis-5.1.0-sq3zbc3dla7ya33x67xr2evinhxbb6ey;/home/axom/axom_tpls/llvm-19.0.0/mpich-4.2.0-wxhfezauopwtrdjmf6hx32l2jh4yyb25;/home/axom/axom_tpls/none-none/gcc-runtime-13.3.1-ahhevkdxsqek4foiubajeiisj7ryali4;/home/axom/axom_tpls/llvm-19.0.0/hwloc-2.12.2-mab72jducih2myearbldanjcfr4epqcz;/home/axom/axom_tpls/llvm-19.0.0/libfabric-2.4.0-n7vbijzh3ebt3lahb5hyk7e4smcfyoxz;/home/axom/axom_tpls/llvm-19.0.0/yaksa-0.4-o3gdzqkwwkch27u7cr4yxzzjuewlg327;/home/axom/axom_tpls/llvm-19.0.0/libpciaccess-0.17-2sndua5wqv2xd2jy3ef52ehian4tpd4i;/home/axom/axom_tpls/llvm-19.0.0/libxml2-2.13.5-j3go7vs6gxvkyl7xdh476o3h3aos6zxk;/home/axom/axom_tpls/llvm-19.0.0/ncurses-6.5-20250705-qjve66dkqppk4z75pct2zhcyso2jsjhl;/home/axom/axom_tpls/llvm-19.0.0/libiconv-1.18-wwggylv5rglt7x7teuq3m7egjl27vbp5;/home/axom/axom_tpls/llvm-19.0.0/xz-5.6.3-55t47rdkjzlx64icqzp5lalnd67nsvnm;/home/axom/axom_tpls/llvm-19.0.0/zlib-ng-2.3.2-h74mdgso4hyns3yjgipxrwh3q5ngbbyt;/home/axom/axom_tpls/none-none/compiler-wrapper-1.0-u5fjo4cce7cqt6425ipfxnafausfog7z;/usr/lib/llvm-19" CACHE STRING "") +set(CMAKE_PREFIX_PATH "/home/axom/axom_tpls/gcc-13.3.1/blt-0.7.2-5absc3jx6wakorcjsjkdynqwxnbnorh6;/home/axom/axom_tpls/llvm-19.0.0/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-7l6avuxfcuijunyqk2fxlqumtwdkyxb6;/home/axom/axom_tpls/llvm-19.0.0/conduit-0.9.7-l3aie4yfhoj4uyb27hina4n32geo3mgj;/home/axom/axom_tpls/llvm-19.0.0/gmake-4.4.1-yb4srpdllr3xe2ja5h6ininrk4w72lzl;/home/axom/axom_tpls/llvm-19.0.0/mfem-4.9.0-lkfxzxlsvuadfg5ydwsiycek5zfiivlm;/home/axom/axom_tpls/llvm-19.0.0/py-nanobind-2.12.0-tuzm4bi26bapgxvfevyrhmcw7yigr3cr;/home/axom/axom_tpls/none-none/py-pytest-9.0.3-ptdfhdimwrrpqf4wur2qqxqrulbgg3uw;/home/axom/axom_tpls/llvm-19.0.0/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-kug35zmdpy6lfj3t76hexqb6n7p75f2d;/home/axom/axom_tpls/llvm-19.0.0/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-add5kytrwuasrm4givvkcrlqel7oup4v;/home/axom/axom_tpls/llvm-19.0.0/adiak-0.5.0-p7pebmhlgrttctgpqavzzkua7acsw2xg;/home/axom/axom_tpls/llvm-19.0.0/elfutils-0.194-75a35l6tythtyzzbyewlaqy2pohxg7fh;/home/axom/axom_tpls/llvm-19.0.0/libunwind-1.8.3-dphpfuxgpaivkuzk7goxnrjscsxe6nz7;/home/axom/axom_tpls/llvm-19.0.0/hdf5-1.8.23-u4qdid5mjabnchbvrfckewojub6kwpod;/home/axom/axom_tpls/llvm-19.0.0/parmetis-4.0.3-4xp6lsbxuxtbqbm57lw6whnekzem2doz;/home/axom/axom_tpls/llvm-19.0.0/py-mpi4py-4.1.1-pc75mwip4clfns5pfqxw6mbv4iwiqxnw;/home/axom/axom_tpls/llvm-19.0.0/py-numpy-2.4.6-unrm5sqk3hvcqkzjmhynfnyccwajogpz;/home/axom/axom_tpls/llvm-19.0.0/hypre-3.1.0-h4aaaomwkehrgbikb5uqbydhw45xpqbj;/home/axom/axom_tpls/llvm-19.0.0/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-xbb726qz2hsgwfqwgsi2rux6t73gjzsh;/home/axom/axom_tpls/llvm-19.0.0/fmt-12.1.0-cfp3hu5rte67sr3swh7yrmyh2e2osqew;/home/axom/axom_tpls/llvm-19.0.0/zstd-1.5.7-mj2uxlxvqfdwel26zgj4o7zmm5wokixw;/home/axom/axom_tpls/llvm-19.0.0/metis-5.1.0-wqdxc6uiqd3um6ftpcoucbqrifbsyupa;/home/axom/axom_tpls/llvm-19.0.0/mpich-4.2.0-ivgdztgkzp5ou7e43yng7kwmncuorlbu;/home/axom/axom_tpls/none-none/gcc-runtime-13.3.1-tmwwov6s5vhnf7ecrbz24zwhy4bclvox;/home/axom/axom_tpls/llvm-19.0.0/hwloc-2.13.0-oysseifxkwdrx6x24z5bmbtauh72ckny;/home/axom/axom_tpls/llvm-19.0.0/libfabric-2.5.1-gddbyvmnubikebp4tnur4u5lxbth2ui5;/home/axom/axom_tpls/llvm-19.0.0/yaksa-0.4-jvbmky5ig45ojfwixfq3xifkveyeadq3;/home/axom/axom_tpls/llvm-19.0.0/libpciaccess-0.17-7lextl2emhxkoctrmsx6k74zwhfi47ye;/home/axom/axom_tpls/llvm-19.0.0/libxml2-2.15.3-3jxvbwny5h5xlagwjp4gipowfd67s3dr;/home/axom/axom_tpls/llvm-19.0.0/ncurses-6.6-pcmm3nvf2rrjehty4o7ir2s43viopuhy;/home/axom/axom_tpls/llvm-19.0.0/libiconv-1.18-jnaujzfhyoavjk6lxvjh3mbv5xan567i;/home/axom/axom_tpls/llvm-19.0.0/xz-5.8.3-uuaa7wnhfzqnitctcnm4fgqkab24kif5;/home/axom/axom_tpls/llvm-19.0.0/zlib-ng-2.3.3-yemxpxgxs6kr6ort6ofcyuc6c6ktp6xc;/home/axom/axom_tpls/none-none/compiler-wrapper-1.1.0-m2hzi6ouuda4xfn6tiiaok5ekmsr62nh;/usr/lib/llvm-19" CACHE STRING "") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") -set(CMAKE_BUILD_RPATH "/home/axom/axom_tpls/llvm-19.0.0/axom-develop-qzk4bzyik3p6ybwwp7djapznpldjxhvx/lib;/home/axom/axom_tpls/llvm-19.0.0/axom-develop-qzk4bzyik3p6ybwwp7djapznpldjxhvx/lib64;;" CACHE STRING "") +set(CMAKE_BUILD_RPATH "/home/axom/axom_tpls/llvm-19.0.0/axom-develop-7eped62hfd56iwgikanotwa6zhkq4lpl/lib;/home/axom/axom_tpls/llvm-19.0.0/axom-develop-7eped62hfd56iwgikanotwa6zhkq4lpl/lib64;;" CACHE STRING "") -set(CMAKE_INSTALL_RPATH "/home/axom/axom_tpls/llvm-19.0.0/axom-develop-qzk4bzyik3p6ybwwp7djapznpldjxhvx/lib;/home/axom/axom_tpls/llvm-19.0.0/axom-develop-qzk4bzyik3p6ybwwp7djapznpldjxhvx/lib64;;" CACHE STRING "") +set(CMAKE_INSTALL_RPATH "/home/axom/axom_tpls/llvm-19.0.0/axom-develop-7eped62hfd56iwgikanotwa6zhkq4lpl/lib;/home/axom/axom_tpls/llvm-19.0.0/axom-develop-7eped62hfd56iwgikanotwa6zhkq4lpl/lib64;;" CACHE STRING "") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") #------------------------------------------------------------------------------ # Compilers #------------------------------------------------------------------------------ -# Compiler Spec: llvm@19.0.0/hpp37w5qjo4zc4cgogknua6ull7tes74 +# Compiler Spec: llvm@19.0.0/ba5crpdef2intlzcl2gwmdnlj46vvlog #------------------------------------------------------------------------------ if(DEFINED ENV{SPACK_CC}) - set(CMAKE_C_COMPILER "/home/axom/axom_tpls/none-none/compiler-wrapper-1.0-u5fjo4cce7cqt6425ipfxnafausfog7z/libexec/spack/clang/clang" CACHE PATH "") + set(CMAKE_C_COMPILER "/home/axom/axom_tpls/none-none/compiler-wrapper-1.1.0-m2hzi6ouuda4xfn6tiiaok5ekmsr62nh/libexec/spack/clang/clang" CACHE PATH "") - set(CMAKE_CXX_COMPILER "/home/axom/axom_tpls/none-none/compiler-wrapper-1.0-u5fjo4cce7cqt6425ipfxnafausfog7z/libexec/spack/clang/clang++" CACHE PATH "") + set(CMAKE_CXX_COMPILER "/home/axom/axom_tpls/none-none/compiler-wrapper-1.1.0-m2hzi6ouuda4xfn6tiiaok5ekmsr62nh/libexec/spack/clang/clang++" CACHE PATH "") - set(CMAKE_Fortran_COMPILER "/home/axom/axom_tpls/none-none/compiler-wrapper-1.0-u5fjo4cce7cqt6425ipfxnafausfog7z/libexec/spack/gcc/gfortran" CACHE PATH "") + set(CMAKE_Fortran_COMPILER "/home/axom/axom_tpls/none-none/compiler-wrapper-1.1.0-m2hzi6ouuda4xfn6tiiaok5ekmsr62nh/libexec/spack/gcc/gfortran" CACHE PATH "") else() @@ -49,13 +49,13 @@ set(BLT_EXE_LINKER_FLAGS " -Wl,-rpath,/usr/lib/llvm-19/lib" CACHE STRING "Adds a # MPI #------------------------------------------------------------------------------ -set(MPI_C_COMPILER "/home/axom/axom_tpls/llvm-19.0.0/mpich-4.2.0-wxhfezauopwtrdjmf6hx32l2jh4yyb25/bin/mpicc" CACHE PATH "") +set(MPI_C_COMPILER "/home/axom/axom_tpls/llvm-19.0.0/mpich-4.2.0-ivgdztgkzp5ou7e43yng7kwmncuorlbu/bin/mpicc" CACHE PATH "") -set(MPI_CXX_COMPILER "/home/axom/axom_tpls/llvm-19.0.0/mpich-4.2.0-wxhfezauopwtrdjmf6hx32l2jh4yyb25/bin/mpicxx" CACHE PATH "") +set(MPI_CXX_COMPILER "/home/axom/axom_tpls/llvm-19.0.0/mpich-4.2.0-ivgdztgkzp5ou7e43yng7kwmncuorlbu/bin/mpicxx" CACHE PATH "") -set(MPI_Fortran_COMPILER "/home/axom/axom_tpls/llvm-19.0.0/mpich-4.2.0-wxhfezauopwtrdjmf6hx32l2jh4yyb25/bin/mpif90" CACHE PATH "") +set(MPI_Fortran_COMPILER "/home/axom/axom_tpls/llvm-19.0.0/mpich-4.2.0-ivgdztgkzp5ou7e43yng7kwmncuorlbu/bin/mpif90" CACHE PATH "") -set(MPIEXEC_EXECUTABLE "/home/axom/axom_tpls/llvm-19.0.0/mpich-4.2.0-wxhfezauopwtrdjmf6hx32l2jh4yyb25/bin/mpirun" CACHE PATH "") +set(MPIEXEC_EXECUTABLE "/home/axom/axom_tpls/llvm-19.0.0/mpich-4.2.0-ivgdztgkzp5ou7e43yng7kwmncuorlbu/bin/mpirun" CACHE PATH "") set(MPIEXEC_NUMPROC_FLAG "-np" CACHE STRING "") @@ -79,27 +79,27 @@ set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") set(TPL_ROOT "/home/axom/axom_tpls/llvm-19.0.0" CACHE PATH "") -set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-vzh2futlihcbvpypnehpznvnqgijdflr" CACHE PATH "") +set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.7-l3aie4yfhoj4uyb27hina4n32geo3mgj" CACHE PATH "") # C2C not built -set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-g3kaipukjvqu677tqztygnik5kzcxhfj" CACHE PATH "") +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-lkfxzxlsvuadfg5ydwsiycek5zfiivlm" CACHE PATH "") -set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-mwa7fanurwtkpebryywcggusn4gd7yg2" CACHE PATH "") +set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-u4qdid5mjabnchbvrfckewojub6kwpod" CACHE PATH "") set(LUA_DIR "/usr" CACHE PATH "") -set(RAJA_DIR "${TPL_ROOT}/raja-git.3b8b59a1e9be2e1066c0d77372b3bf5956e6d6e2_develop-dkkk27oc7p2gsrq4cslpdsv5qz7g3sbd" CACHE PATH "") +set(RAJA_DIR "${TPL_ROOT}/raja-git.6e4fe62d810711a0af9020d4e94c6c41c9a6117b_develop-kug35zmdpy6lfj3t76hexqb6n7p75f2d" CACHE PATH "") -set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.12.0-kulqmeumvx2c36r6rzz7lwndhsv3ot4i" CACHE PATH "") +set(UMPIRE_DIR "${TPL_ROOT}/umpire-git.f544027ef118133f1ecbc26e64c04ea77c3eb5ae_develop-add5kytrwuasrm4givvkcrlqel7oup4v" CACHE PATH "") # OPENCASCADE not built -set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-idbd67m2y4b4gnuxxbivm7hvesbbl4q5" CACHE PATH "") +set(ADIAK_DIR "${TPL_ROOT}/adiak-0.5.0-p7pebmhlgrttctgpqavzzkua7acsw2xg" CACHE PATH "") -set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-qv2wv4xikow5wxrhdem6c2dvkxt2cptf" CACHE PATH "") +set(CALIPER_DIR "${TPL_ROOT}/caliper-git.7e5b7a5c0eacc077f9b842abf41c9fc7b996ce0c_master-7l6avuxfcuijunyqk2fxlqumtwdkyxb6" CACHE PATH "") -set(CAMP_DIR "${TPL_ROOT}/camp-git.a8caefa9f4c811b1a114b4ed2c9b681d40f12325_main-y2k23nuv2eya2soe3fznpcgsgt2k2dai" CACHE PATH "") +set(CAMP_DIR "${TPL_ROOT}/camp-git.e75ab64c029aa27c80593715cb2a3ccad7453c8c_main-xbb726qz2hsgwfqwgsi2rux6t73gjzsh" CACHE PATH "") # scr not built @@ -115,16 +115,16 @@ set(Python_EXECUTABLE "/usr/bin/python3" CACHE PATH "") set(ENABLE_DOCS OFF CACHE BOOL "") -set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.7.0-wvrnl66utfn2pr23wovdcp4eko42usc2/lib/python3.12/site-packages" CACHE PATH "") +set(PY_NANOBIND_DIR "${TPL_ROOT}/py-nanobind-2.12.0-tuzm4bi26bapgxvfevyrhmcw7yigr3cr/lib/python3.12/site-packages" CACHE PATH "") -set(PY_PYTEST_DIR "/home/axom/axom_tpls/none-none/py-pytest-9.0.0-rrb5ddzqzb7gvgzm7kpaqkye6bdjwsic/lib/python3.12/site-packages" CACHE PATH "") +set(PY_PYTEST_DIR "/home/axom/axom_tpls/none-none/py-pytest-9.0.3-ptdfhdimwrrpqf4wur2qqxqrulbgg3uw/lib/python3.12/site-packages" CACHE PATH "") -set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.2-3lrivxf4cgsisw7njzzpyihmo7ok2b77/lib/python3.12/site-packages" CACHE PATH "") +set(PY_NUMPY_DIR "${TPL_ROOT}/py-numpy-2.4.6-unrm5sqk3hvcqkzjmhynfnyccwajogpz/lib/python3.12/site-packages" CACHE PATH "") -set(PY_PLUGGY_DIR "/home/axom/axom_tpls/none-none/py-pluggy-1.6.0-3rhfyqmyucyrezyg67awukgbpmagwkr4/lib/python3.12/site-packages" CACHE PATH "") +set(PY_PLUGGY_DIR "/home/axom/axom_tpls/none-none/py-pluggy-1.6.0-4jel2q6ulwszhusddvgwo7pmvfdxegyn/lib/python3.12/site-packages" CACHE PATH "") -set(PY_INICONFIG_DIR "/home/axom/axom_tpls/none-none/py-iniconfig-2.1.0-72fwkqdbtz4ngubc37txds5iujbw7keb/lib/python3.12/site-packages" CACHE PATH "") +set(PY_INICONFIG_DIR "/home/axom/axom_tpls/none-none/py-iniconfig-2.1.0-wtznmg6t3pr66rtrlxbqdwu6rbucbeyf/lib/python3.12/site-packages" CACHE PATH "") -set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-vcihkzxcnca4aeslr53dqlijjwhcwuuh/lib/python3.12/site-packages" CACHE PATH "") +set(PY_MPI4PY_DIR "${TPL_ROOT}/py-mpi4py-4.1.1-pc75mwip4clfns5pfqxw6mbv4iwiqxnw/lib/python3.12/site-packages" CACHE PATH "") From aa7d9fe314574c04145ce4eb41f01d5567962cff Mon Sep 17 00:00:00 2001 From: Brian Han Date: Mon, 13 Jul 2026 09:21:23 -0700 Subject: [PATCH 681/986] 7.2.1 libraries require openmp - reenable for gitlab CI --- .gitlab/build_tuolumne.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitlab/build_tuolumne.yml b/.gitlab/build_tuolumne.yml index a034932ce2..f00e9a0e00 100644 --- a/.gitlab/build_tuolumne.yml +++ b/.gitlab/build_tuolumne.yml @@ -36,12 +36,10 @@ #### # PR Build jobs -# Disable OpenMP to avoid timeout tuolumne-llvm-amdgpu_7_2_1_hip-src: variables: COMPILER: "llvm-amdgpu@7.2.1_hip" HOST_CONFIG: "tuolumne-toss_4_x86_64_ib_cray-${COMPILER}.cmake" - EXTRA_CMAKE_OPTIONS: "-DENABLE_OPENMP:BOOL=OFF" extends: .src_build_on_tuolumne #### From 491c8052bfe4f7e0f7544fddd15f26a9949bdfe4 Mon Sep 17 00:00:00 2001 From: Brian Han Date: Mon, 13 Jul 2026 09:24:02 -0700 Subject: [PATCH 682/986] Gitlab CI - Disable OpenMP for tioga 6.4.3, see if that helps with timeout --- .gitlab/build_tioga.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitlab/build_tioga.yml b/.gitlab/build_tioga.yml index 6afd43d3ae..fc917c0bc9 100644 --- a/.gitlab/build_tioga.yml +++ b/.gitlab/build_tioga.yml @@ -35,10 +35,12 @@ #### # PR Build jobs +# Disable OpenMP to avoid timeout tioga-llvm-amdgpu_6_4_3_hip-src: variables: COMPILER: "llvm-amdgpu@6.4.3_hip" HOST_CONFIG: "tioga-toss_4_x86_64_ib_cray-${COMPILER}.cmake" + EXTRA_CMAKE_OPTIONS: "-DENABLE_OPENMP:BOOL=OFF" extends: .src_build_on_tioga #### From 46f25d4f9ac969367936f20cb2da5afbcb24b654 Mon Sep 17 00:00:00 2001 From: Rebecca Haluska Date: Mon, 13 Jul 2026 09:41:52 -0700 Subject: [PATCH 683/986] Sina: add support for codes to send non-partial append curves --- src/axom/sina/core/Document.cpp | 30 +++++++++++++++++---------- src/axom/sina/core/Document.hpp | 11 ++++++++-- src/axom/sina/tests/sina_Document.cpp | 19 +++++++++-------- 3 files changed, 38 insertions(+), 22 deletions(-) diff --git a/src/axom/sina/core/Document.cpp b/src/axom/sina/core/Document.cpp index c8b837ad77..d0178ecd3d 100644 --- a/src/axom/sina/core/Document.cpp +++ b/src/axom/sina/core/Document.cpp @@ -817,7 +817,8 @@ void append_curveset(ConduitRelayLike &appendTo, conduit::Node &appendFrom, const std::string &endpoint, int record_num, - const std::string &original_file_path) + const std::string &original_file_path, + bool curvesAreFullLength) { for(const std::string &curve_cat : CURVE_CATEGORIES) { @@ -827,7 +828,7 @@ void append_curveset(ConduitRelayLike &appendTo, { conduit::Node &n = curveIter.next(); std::string curve_endpoint = endpoint + "/" + curve_cat + "/" + curveIter.name() + "/value"; - if(relayLikeHasPath(appendTo, curve_endpoint, record_num)) + if(relayLikeHasPath(appendTo, curve_endpoint, record_num) && !curvesAreFullLength) { relayLikeAppendCurve(appendTo, n["value"], curve_endpoint, record_num, original_file_path); } @@ -847,7 +848,8 @@ void append_recordlike_fields(ConduitRelayLike &appendTo, const int mergeProtocol, int record_num, const std::string &original_file_path, - bool isHDF5) + bool isHDF5, + bool curvesAreFullLength) { auto fieldsIter = appendFrom.children(); while(fieldsIter.has_next()) @@ -915,7 +917,8 @@ void append_recordlike_fields(ConduitRelayLike &appendTo, mergeProtocol, record_num, original_file_path, - isHDF5); + isHDF5, + curvesAreFullLength); } else { @@ -934,7 +937,8 @@ void append_recordlike_fields(ConduitRelayLike &appendTo, curveSetField, appendAtEndpoint + curveSetIter.name(), record_num, - original_file_path); + original_file_path, + curvesAreFullLength); } } break; @@ -1002,7 +1006,8 @@ conduit::Node append(ConduitRelayLike &appendTo, const int mergeProtocol, bool isHDF5, bool skipValidation, - const std::string &original_file_path) + const std::string &original_file_path, + bool curvesAreFullLength ) { conduit::Node msgNode = conduit::Node(conduit::DataType::list()); // We need to figure out where each record is in appendTo, since there's no guarantee in the order @@ -1067,7 +1072,8 @@ conduit::Node append(ConduitRelayLike &appendTo, mergeProtocol, rec_num->second, original_file_path, - isHDF5); + isHDF5, + curvesAreFullLength); } } append_relationships(appendTo, appendFrom["relationships"]); @@ -1077,13 +1083,14 @@ conduit::Node append(ConduitRelayLike &appendTo, conduit::Node appendDocumentToJson(const std::string &jsonFilePath, const Document &newData, const int mergeProtocol, - const bool skipValidation) + const bool skipValidation, + const bool curvesAreFullLength) { conduit::Node appendTo; appendTo.load(jsonFilePath, "json"); conduit::Node appendFrom = newData.toNode(); conduit::Node msgNode = - append(appendTo, appendFrom, mergeProtocol, false, skipValidation, jsonFilePath); + append(appendTo, appendFrom, mergeProtocol, false, skipValidation, jsonFilePath, curvesAreFullLength); conduit::relay::io::save(appendTo, jsonFilePath); return msgNode; } @@ -1091,7 +1098,8 @@ conduit::Node appendDocumentToJson(const std::string &jsonFilePath, conduit::Node appendDocumentToHDF5(const std::string &hdf5FilePath, const Document &newData, const int mergeProtocol, - const bool skipValidation) + const bool skipValidation, + const bool curvesAreFullLength) { #ifdef AXOM_USE_HDF5 conduit::relay::io::IOHandle appendTo; @@ -1099,7 +1107,7 @@ conduit::Node appendDocumentToHDF5(const std::string &hdf5FilePath, conduit::Node appendFrom; newData.toHDF5Node(appendFrom); conduit::Node msgNode = - append(appendTo, appendFrom, mergeProtocol, true, skipValidation, hdf5FilePath); + append(appendTo, appendFrom, mergeProtocol, true, skipValidation, hdf5FilePath, curvesAreFullLength); appendTo.close(); return msgNode; #else diff --git a/src/axom/sina/core/Document.hpp b/src/axom/sina/core/Document.hpp index e87eec8367..f75b8f80b0 100644 --- a/src/axom/sina/core/Document.hpp +++ b/src/axom/sina/core/Document.hpp @@ -326,12 +326,16 @@ Document loadDocument(std::string const &path, * Protocol 1 is the conduit default behavior. Ignored entirely when skipping validation. * \param skipValidation whether to skip the validation step entirely. Most useful for well-controlled cases, * ex: a code is appending values to every timeseries every N cycles. + * \param curvesAreFullLength Indicates whether the data source passes the full curve (from ex: t=0) instead + * of since last write. Causes the curve to overwrite. + * * \return a conduit Node containing a list of any errors encountered in appending. If empty, success. */ conduit::Node appendDocumentToJson(const std::string &jsonFilePath, const Document &newData, const int mergeProtocol = 1, - const bool skipValidation = false); + const bool skipValidation = false, + const bool curvesAreFullLength = false); /** * \brief Append the new records or, per-record, new data, user defined content, curves/curve sets, @@ -358,13 +362,16 @@ conduit::Node appendDocumentToJson(const std::string &jsonFilePath, * Protocol 1 is the conduit default behavior. Ignored entirely when skipping validation. * \param skipValidation whether to skip the validation step entirely. Most useful for well-controlled cases, * ex: a code is appending values to every timeseries every N cycles. + * \param curvesAreFullLength Indicates whether the data source passes the full curve (from ex: t=0) instead + * of since last write. Causes the curve to overwrite. * * \return a conduit Node containing a list of any errors encountered in appending. If empty, success! */ conduit::Node appendDocumentToHDF5(const std::string &hdf5FilePath, Document const &newData, const int mergeProtocol = 1, - const bool skipValidation = false); + const bool skipValidation = false, + const bool curvesAreFullLength = false); /** * @brief Append a Document to an existing file with automatic format detection diff --git a/src/axom/sina/tests/sina_Document.cpp b/src/axom/sina/tests/sina_Document.cpp index af336ae5d5..f4c77d532c 100644 --- a/src/axom/sina/tests/sina_Document.cpp +++ b/src/axom/sina/tests/sina_Document.cpp @@ -657,8 +657,9 @@ TEST(Document, test_validate_append_valid) void doEveryErrorTest( const std::string &protocol, - std::function appendDocumentFunc, - bool skipValidation = false) + std::function appendDocumentFunc, + bool skipValidation = false, + bool curvesAreFullLength = false) { std::string append_to_file = "test." + protocol; axom::sina::Document append_to_doc = @@ -671,7 +672,7 @@ void doEveryErrorTest( "curve_sets": {"set_1": {"independent": {"0": {"value": [4, 5, 6]}}}}, "library_data": {"my_lib": {"library_data": {"my_inner_lib": {"user_defined": {"foo/bar": "baz/qux"}}}}}}]})"); axom::sina::Document new_doc = Document(appendFrom, createRecordLoaderWithAllKnownTypes()); - conduit::Node resultMsg = appendDocumentFunc(append_to_file, new_doc, 3, skipValidation); + conduit::Node resultMsg = appendDocumentFunc(append_to_file, new_doc, 3, skipValidation, curvesAreFullLength); // Make sure no data changed conduit::Node root; conduit::relay::io::load(append_to_file, root); @@ -693,7 +694,7 @@ TEST(Document, test_appendErrorCodepathsHDF5) { doEveryErrorTest("hdf5", appendD // Appending into an empty document void doSimpleAppendTest( const std::string &protocol, - std::function appendDocumentFunc) + std::function appendDocumentFunc) { std::string empty_file = "test." + protocol; axom::sina::Document empty_doc = @@ -701,7 +702,7 @@ void doSimpleAppendTest( Protocol enum_protocol = (protocol == "hdf5") ? Protocol::HDF5 : Protocol::JSON; saveDocument(empty_doc, empty_file, enum_protocol); axom::sina::Document new_doc = Document(SIMPLE_DOCUMENT, createRecordLoaderWithAllKnownTypes()); - conduit::Node resultMsg = appendDocumentFunc(empty_file, new_doc, 3, true); // skip validation + conduit::Node resultMsg = appendDocumentFunc(empty_file, new_doc, 3, true, false); // skip validation EXPECT_EQ(resultMsg.number_of_children(), 0); conduit::Node root; conduit::relay::io::load(empty_file, root); @@ -725,7 +726,7 @@ TEST(Document, test_simpleAppendDocumentToHDF5) // One unchanged, one merged void doFullAppendTest( const std::string &protocol, - std::function appendDocumentFunc) + std::function appendDocumentFunc) { std::string filePath = "test." + protocol; sina::Document testDoc = Document(MULTI_REC_DOCUMENT, createRecordLoaderWithAllKnownTypes()); @@ -733,7 +734,7 @@ void doFullAppendTest( saveDocument(testDoc, filePath, enum_protocol); axom::sina::Document new_doc = Document(SIMPLE_DOCUMENT, createRecordLoaderWithAllKnownTypes()); - conduit::Node resultMsg = appendDocumentFunc(filePath, new_doc, 1, false); + conduit::Node resultMsg = appendDocumentFunc(filePath, new_doc, 1, false, false); EXPECT_EQ(resultMsg.number_of_children(), 0); conduit::Node root; @@ -790,7 +791,7 @@ TEST(Document, test_appendDocumentToHDF5) { doFullAppendTest("hdf5", appendDocum // Making sure we respect curve order (Records are in charge of ordering their curves, not documents) void doAppendOrderedCurveTest( const std::string &protocol, - std::function appendDocumentFunc) + std::function appendDocumentFunc) { std::string curvedump_file = "test_curve." + protocol; axom::sina::Document ordered_curves = @@ -799,7 +800,7 @@ void doAppendOrderedCurveTest( Document(CURVE_ORDERED_DOCUMENT_APPEND, createRecordLoaderWithAllKnownTypes()); Protocol enum_protocol = (protocol == "hdf5") ? Protocol::HDF5 : Protocol::JSON; saveDocument(ordered_curves, curvedump_file, enum_protocol); - conduit::Node resultMsg = appendDocumentFunc(curvedump_file, additional_curve, 3, false); + conduit::Node resultMsg = appendDocumentFunc(curvedump_file, additional_curve, 3, false, false); EXPECT_EQ(resultMsg.number_of_children(), 0); conduit::Node root; conduit::relay::io::load(curvedump_file, root); From 4cbfd9fa44f6c642598ee20a009d1df45a7b7765 Mon Sep 17 00:00:00 2001 From: Rich Hornung Date: Mon, 13 Jul 2026 10:43:52 -0700 Subject: [PATCH 684/986] Replace strcpy and sprintf with snprintf in spot causing compiler warnings --- src/axom/sidre/examples/sidre_shocktube.cpp | 10 ++++++---- .../slam/examples/lulesh2.0.3/lulesh-util.cpp | 5 +++-- .../slam/examples/lulesh2.0.3/lulesh-viz.cpp | 18 ++++++++++-------- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/axom/sidre/examples/sidre_shocktube.cpp b/src/axom/sidre/examples/sidre_shocktube.cpp index d5e5f73d77..92c2697d65 100644 --- a/src/axom/sidre/examples/sidre_shocktube.cpp +++ b/src/axom/sidre/examples/sidre_shocktube.cpp @@ -476,21 +476,23 @@ void UpdateElemInfo(Group* const prob) void DumpUltra(Group* const prob) { #if 1 + constexpr int fname_size = 100; FILE* fp; - char fname[100]; + char fname[fname_size]; char* tail; // VHashTraverse_t content ; - strcpy(fname, "problem"); + snprintf(fname, fname_size, "problem"); + int fname_rem = fname_size; /* Skip past the junk */ for(tail = fname; isalpha(*tail); ++tail) { - ; + fname_rem--; } - sprintf(tail, "_%04d.ult", prob->getView("cycle")->getData()); + snprintf(tail, fname_rem, "_%04d.ult", prob->getView("cycle")->getData()); if((fp = fopen(fname, "w")) == nullptr) { diff --git a/src/axom/slam/examples/lulesh2.0.3/lulesh-util.cpp b/src/axom/slam/examples/lulesh2.0.3/lulesh-util.cpp index 3a98414bd0..7cd39f964c 100644 --- a/src/axom/slam/examples/lulesh2.0.3/lulesh-util.cpp +++ b/src/axom/slam/examples/lulesh2.0.3/lulesh-util.cpp @@ -203,9 +203,10 @@ namespace slamLulesh { #endif } else { - char msg[80]; + constexpr int msg_size = 80; + char msg[msg_size]; PrintCommandLineOptions(argv[0], myRank); - sprintf(msg, "ERROR: Unknown command line argument: %s\n", argv[i]); + snprintf(msg, msg_size, "ERROR: Unknown command line argument: %s\n", argv[i]); ParseError(msg, myRank); } } diff --git a/src/axom/slam/examples/lulesh2.0.3/lulesh-viz.cpp b/src/axom/slam/examples/lulesh2.0.3/lulesh-viz.cpp index e18a9e21dd..0a3f9202c4 100644 --- a/src/axom/slam/examples/lulesh2.0.3/lulesh-viz.cpp +++ b/src/axom/slam/examples/lulesh2.0.3/lulesh-viz.cpp @@ -62,13 +62,14 @@ namespace slamLulesh { /**********************************************************************/ void DumpToVisit(Domain& domain, int numFiles, int myRank, int numRanks) { - char subdirName[32]; - char basename[32]; + constexpr int buff_size = 32; + + char subdirName[buff_size]; + char basename[buff_size]; DBfile *db; - - sprintf(basename, "lulesh_plot_c%d", domain.cycle()); - sprintf(subdirName, "data_%d", myRank); + snprintf(basename, buff_size, "lulesh_plot_c%d", domain.cycle()); + snprintf(subdirName, buff_size, "data_%d", myRank); #ifdef AXOM_USE_MPI @@ -83,12 +84,13 @@ namespace slamLulesh { int myiorank = PMPIO_GroupRank(bat, myRank); - char fileName[64]; + constexpr int fileName_size = 64; + char fileName[fileName_size]; if (myiorank == 0) - strcpy(fileName, basename); + snprintf(fileName, fileName_size, basename); else - sprintf(fileName, "%s.%03d", basename, myiorank); + snprintf(fileName, fileName_size, "%s.%03d", basename, myiorank); db = (DBfile*)PMPIO_WaitForBaton(bat, fileName, subdirName); From 4becf1118aab4ba87316a5a2847391d49f94a745 Mon Sep 17 00:00:00 2001 From: Chris White Date: Mon, 29 Jun 2026 12:36:11 -0700 Subject: [PATCH 685/986] convert to pragma once and make sure all headers are guarded --- src/axom/bump/BlendData.hpp | 5 +---- src/axom/bump/CoordsetBlender.hpp | 5 +---- src/axom/bump/CoordsetExtents.hpp | 5 +---- src/axom/bump/CoordsetSlicer.hpp | 5 +---- src/axom/bump/ExtractZones.hpp | 5 +---- src/axom/bump/ExtractZonesAndMatsetPolyhedral.hpp | 5 +---- src/axom/bump/ExtrudeMesh.hpp | 5 +---- src/axom/bump/FieldBlender.hpp | 5 +---- src/axom/bump/FieldSlicer.hpp | 5 +---- src/axom/bump/HashNaming.hpp | 5 +---- src/axom/bump/IndexingPolicies.hpp | 5 +---- src/axom/bump/MakePointMesh.hpp | 5 +---- src/axom/bump/MakePolyhedralTopology.hpp | 5 +---- src/axom/bump/MakeUnstructured.hpp | 5 +---- src/axom/bump/MakeZoneCenters.hpp | 5 +---- src/axom/bump/MakeZoneVolumes.hpp | 5 +---- src/axom/bump/MapBasedNaming.hpp | 5 +---- src/axom/bump/MatsetSlicer.hpp | 5 +---- src/axom/bump/MergeCoordsetPoints.hpp | 5 +---- src/axom/bump/MergeMeshes.hpp | 5 +---- src/axom/bump/MergePolyhedralFaces.hpp | 5 +---- src/axom/bump/MinMax.hpp | 5 +---- src/axom/bump/NodeToZoneRelationBuilder.hpp | 5 +---- src/axom/bump/Options.hpp | 5 +---- src/axom/bump/PrimalAdaptor.hpp | 5 +---- src/axom/bump/RecenterField.hpp | 5 +---- src/axom/bump/SelectedZones.hpp | 5 +---- src/axom/bump/TopologyMapper.hpp | 5 +---- src/axom/bump/Unique.hpp | 5 +---- src/axom/bump/VariableShape.hpp | 5 +---- src/axom/bump/ZoneListBuilder.hpp | 5 +---- src/axom/bump/data/MeshTester.hpp | 5 +---- src/axom/bump/extraction/BlendGroupBuilder.hpp | 5 +---- src/axom/bump/extraction/ClipField.hpp | 5 +---- src/axom/bump/extraction/ClipTableManager.hpp | 5 +---- src/axom/bump/extraction/CutField.hpp | 5 +---- src/axom/bump/extraction/CutTableManager.hpp | 5 +---- src/axom/bump/extraction/ExtractionConstants.hpp | 5 +---- src/axom/bump/extraction/ExtractorOptions.hpp | 5 +---- src/axom/bump/extraction/FieldIntersector.hpp | 5 +---- src/axom/bump/extraction/FieldOptions.hpp | 5 +---- src/axom/bump/extraction/PlaneIntersector.hpp | 5 +---- src/axom/bump/extraction/PlaneSlice.hpp | 5 +---- src/axom/bump/extraction/Table.hpp | 5 +---- src/axom/bump/extraction/TableBasedExtractor.hpp | 5 +---- src/axom/bump/extraction/TableManager.hpp | 5 +---- src/axom/bump/extraction/tables/clipping/ClipCases.h | 5 +---- src/axom/bump/extraction/tables/cutting/CutCases.hpp | 5 +---- src/axom/bump/io/save.hpp | 5 +---- src/axom/bump/tests/blueprint_testing_data_helpers.hpp | 5 +---- src/axom/bump/tests/blueprint_testing_helpers.hpp | 4 +--- src/axom/bump/utilities/blueprint_utilities.hpp | 5 +---- src/axom/bump/utilities/conduit_array_view.hpp | 5 +---- src/axom/bump/utilities/conduit_memory.hpp | 5 +---- src/axom/bump/utilities/conduit_traits.hpp | 5 +---- src/axom/bump/utilities/utilities.hpp | 5 +---- src/axom/bump/views/BasicIndexing.hpp | 5 +---- src/axom/bump/views/ExplicitCoordsetView.hpp | 5 +---- src/axom/bump/views/MaterialView.hpp | 5 +---- src/axom/bump/views/MixedFieldView.hpp | 4 +--- src/axom/bump/views/NodeArrayView.hpp | 5 +---- src/axom/bump/views/RectilinearCoordsetView.hpp | 5 +---- src/axom/bump/views/Shapes.hpp | 5 +---- src/axom/bump/views/StridedStructuredIndexing.hpp | 5 +---- src/axom/bump/views/StructuredIndexing.hpp | 5 +---- src/axom/bump/views/StructuredTopologyView.hpp | 5 +---- src/axom/bump/views/UniformCoordsetView.hpp | 5 +---- src/axom/bump/views/UnstructuredTopologyMixedShapeView.hpp | 5 +---- src/axom/bump/views/UnstructuredTopologyPolyhedralView.hpp | 5 +---- src/axom/bump/views/UnstructuredTopologySingleShapeView.hpp | 5 +---- src/axom/bump/views/dispatch_coordset.hpp | 5 +---- src/axom/bump/views/dispatch_material.hpp | 5 +---- src/axom/bump/views/dispatch_material_field.hpp | 5 +---- src/axom/bump/views/dispatch_rectilinear_topology.hpp | 5 +---- src/axom/bump/views/dispatch_structured_topology.hpp | 5 +---- src/axom/bump/views/dispatch_topology.hpp | 5 +---- src/axom/bump/views/dispatch_uniform_topology.hpp | 5 +---- src/axom/bump/views/dispatch_unstructured_topology.hpp | 5 +---- src/axom/bump/views/dispatch_utilities.hpp | 5 +---- src/axom/bump/views/view_traits.hpp | 5 +---- src/axom/core/AnnotationMacros.hpp | 5 +---- src/axom/core/Array.hpp | 5 +---- src/axom/core/ArrayBase.hpp | 5 +---- src/axom/core/ArrayIteratorBase.hpp | 5 +---- src/axom/core/ArrayView.hpp | 5 +---- src/axom/core/DeviceHash.hpp | 5 +---- src/axom/core/FlatMap.hpp | 5 +---- src/axom/core/FlatMapUtil.hpp | 5 +---- src/axom/core/FlatMapView.hpp | 5 +---- src/axom/core/IndexedCollection.hpp | 5 +---- src/axom/core/ItemCollection.hpp | 5 +---- src/axom/core/IteratorBase.hpp | 5 +---- src/axom/core/ListCollection.hpp | 5 +---- src/axom/core/MDMapping.hpp | 5 +---- src/axom/core/Macros.hpp | 4 +--- src/axom/core/Map.hpp | 5 +---- src/axom/core/MapCollection.hpp | 5 +---- src/axom/core/NumericArray.hpp | 5 +---- src/axom/core/NumericLimits.hpp | 5 +---- src/axom/core/Path.hpp | 5 +---- src/axom/core/RangeAdapter.hpp | 5 +---- src/axom/core/StackArray.hpp | 5 +---- src/axom/core/StaticArray.hpp | 5 +---- src/axom/core/Types.hpp | 5 +---- src/axom/core/detail/FlatMapOps.hpp | 5 +---- src/axom/core/detail/FlatTable.hpp | 5 +---- src/axom/core/execution/atomics.hpp | 5 +---- src/axom/core/execution/execution_space.hpp | 5 +---- src/axom/core/execution/for_all.hpp | 5 +---- src/axom/core/execution/internal/cuda_exec.hpp | 5 +---- src/axom/core/execution/internal/hip_exec.hpp | 5 +---- src/axom/core/execution/internal/omp_exec.hpp | 5 +---- src/axom/core/execution/internal/seq_exec.hpp | 5 +---- src/axom/core/execution/nested_for_exec.hpp | 5 +---- src/axom/core/execution/reductions.hpp | 5 +---- src/axom/core/execution/runtime_policy.hpp | 5 +---- src/axom/core/execution/scans.hpp | 5 +---- src/axom/core/execution/sorts.hpp | 4 +--- src/axom/core/execution/synchronize.hpp | 5 +---- src/axom/core/execution/timed_for_all.hpp | 5 +---- src/axom/core/memory_management.hpp | 5 +---- src/axom/core/numerics/Determinants.hpp | 5 +---- src/axom/core/numerics/LU.hpp | 5 +---- src/axom/core/numerics/Matrix.hpp | 5 +---- src/axom/core/numerics/eigen_solve.hpp | 5 +---- src/axom/core/numerics/eigen_sort.hpp | 5 +---- src/axom/core/numerics/floating_point_limits.hpp | 5 +---- src/axom/core/numerics/internal/matrix_norms.hpp | 5 +---- src/axom/core/numerics/jacobi_eigensolve.hpp | 5 +---- src/axom/core/numerics/linear_solve.hpp | 5 +---- src/axom/core/numerics/matvecops.hpp | 5 +---- src/axom/core/numerics/polynomial_solvers.hpp | 5 +---- src/axom/core/numerics/quadrature.hpp | 5 +---- src/axom/core/numerics/transforms.hpp | 5 +---- src/axom/core/tests/core_Path.hpp | 2 ++ src/axom/core/tests/core_about.hpp | 2 ++ src/axom/core/tests/core_array.hpp | 2 ++ src/axom/core/tests/core_array_for_all.hpp | 2 ++ src/axom/core/tests/core_array_mapping.hpp | 2 ++ src/axom/core/tests/core_bit_utilities.hpp | 2 ++ src/axom/core/tests/core_device_hash.hpp | 2 ++ src/axom/core/tests/core_execution_for_all.hpp | 2 ++ src/axom/core/tests/core_execution_scans.hpp | 2 ++ src/axom/core/tests/core_execution_space.hpp | 2 ++ src/axom/core/tests/core_flatmap.hpp | 2 ++ src/axom/core/tests/core_flatmap_for_all.hpp | 2 ++ src/axom/core/tests/core_map.hpp | 2 ++ src/axom/core/tests/core_memory_management.hpp | 2 ++ src/axom/core/tests/core_numeric_array.hpp | 2 ++ src/axom/core/tests/core_numeric_limits.hpp | 2 ++ src/axom/core/tests/core_openmp_map.hpp | 2 ++ src/axom/core/tests/core_shared_memory.hpp | 2 ++ src/axom/core/tests/core_stack_array.hpp | 2 ++ src/axom/core/tests/core_static_array.hpp | 2 ++ src/axom/core/tests/core_types.hpp | 2 ++ src/axom/core/tests/core_utilities.hpp | 2 ++ src/axom/core/tests/numerics_determinants.hpp | 2 ++ src/axom/core/tests/numerics_eigen_solve.hpp | 2 ++ src/axom/core/tests/numerics_eigen_sort.hpp | 2 ++ src/axom/core/tests/numerics_floating_point_limits.hpp | 2 ++ src/axom/core/tests/numerics_jacobi_eigensolve.hpp | 2 ++ src/axom/core/tests/numerics_linear_solve.hpp | 2 ++ src/axom/core/tests/numerics_lu.hpp | 2 ++ src/axom/core/tests/numerics_matrix.hpp | 2 ++ src/axom/core/tests/numerics_matvecops.hpp | 2 ++ src/axom/core/tests/numerics_polynomial_solvers.hpp | 2 ++ src/axom/core/tests/numerics_quadrature.hpp | 4 +++- src/axom/core/tests/numerics_transforms.hpp | 2 ++ src/axom/core/tests/utils_Timer.hpp | 2 ++ src/axom/core/tests/utils_endianness.hpp | 2 ++ src/axom/core/tests/utils_fileUtilities.hpp | 2 ++ src/axom/core/tests/utils_locale.hpp | 2 ++ src/axom/core/tests/utils_stringUtilities.hpp | 2 ++ src/axom/core/tests/utils_system.hpp | 2 ++ src/axom/core/tests/utils_utilities.hpp | 2 ++ src/axom/core/utilities/About.hpp | 5 +---- src/axom/core/utilities/Annotations.hpp | 5 +---- src/axom/core/utilities/BitUtilities.hpp | 5 +---- src/axom/core/utilities/CommandLineUtilities.hpp | 5 +---- src/axom/core/utilities/FileUtilities.hpp | 5 +---- src/axom/core/utilities/RAII.hpp | 5 +---- src/axom/core/utilities/Sorting.hpp | 5 +---- src/axom/core/utilities/StringUtilities.hpp | 5 +---- src/axom/core/utilities/System.hpp | 5 +---- src/axom/core/utilities/Timer.hpp | 5 +---- src/axom/core/utilities/Utilities.hpp | 5 +---- src/axom/inlet/ConduitReader.hpp | 5 +---- src/axom/inlet/Container.hpp | 5 +---- src/axom/inlet/Field.hpp | 5 +---- src/axom/inlet/Function.hpp | 5 +---- src/axom/inlet/Inlet.hpp | 5 +---- src/axom/inlet/InletVector.hpp | 5 +---- src/axom/inlet/JSONReader.hpp | 5 +---- src/axom/inlet/JSONSchemaWriter.hpp | 5 +---- src/axom/inlet/LuaReader.hpp | 5 +---- src/axom/inlet/Proxy.hpp | 5 +---- src/axom/inlet/Reader.hpp | 5 +---- src/axom/inlet/SphinxWriter.hpp | 5 +---- src/axom/inlet/VariantKey.hpp | 5 +---- src/axom/inlet/VariantValue.hpp | 5 +---- src/axom/inlet/Verifiable.hpp | 5 +---- src/axom/inlet/VerifiableScalar.hpp | 5 +---- src/axom/inlet/Writer.hpp | 5 +---- src/axom/inlet/YAMLReader.hpp | 5 +---- src/axom/inlet/inlet_utils.hpp | 5 +---- src/axom/inlet/tests/inlet_test_utils.hpp | 5 +---- src/axom/klee/AffineMatrixVisitor.hpp | 5 +---- src/axom/klee/Dimensions.hpp | 5 +---- src/axom/klee/Geometry.hpp | 5 +---- src/axom/klee/GeometryOperators.hpp | 5 +---- src/axom/klee/KleeError.hpp | 5 +---- src/axom/klee/Shape.hpp | 5 +---- src/axom/klee/ShapeSet.hpp | 5 +---- src/axom/klee/Units.hpp | 4 +--- src/axom/klee/io/GeometryOperatorsIO.hpp | 5 +---- src/axom/klee/io/IO.hpp | 5 +---- src/axom/klee/io/IOUtil.hpp | 5 +---- src/axom/klee/tests/KleeMatchers.hpp | 5 +---- src/axom/klee/tests/KleeTestUtils.hpp | 5 +---- src/axom/lumberjack/BinaryTreeCommunicator.hpp | 5 +---- src/axom/lumberjack/Combiner.hpp | 5 +---- src/axom/lumberjack/Communicator.hpp | 5 +---- src/axom/lumberjack/LineFileTagCombiner.hpp | 5 +---- src/axom/lumberjack/Lumberjack.hpp | 5 +---- src/axom/lumberjack/MPIUtility.hpp | 5 +---- src/axom/lumberjack/Message.hpp | 5 +---- src/axom/lumberjack/NonCollectiveRootCommunicator.hpp | 5 +---- src/axom/lumberjack/RootCommunicator.hpp | 5 +---- src/axom/lumberjack/TextEqualityCombiner.hpp | 5 +---- src/axom/lumberjack/TextTagCombiner.hpp | 5 +---- src/axom/lumberjack/tests/lumberjack_BinaryCommunicator.hpp | 2 ++ src/axom/lumberjack/tests/lumberjack_LineFileTagCombiner.hpp | 4 +++- src/axom/lumberjack/tests/lumberjack_Lumberjack.hpp | 2 ++ src/axom/lumberjack/tests/lumberjack_Message.hpp | 2 ++ .../tests/lumberjack_NonCollectiveRootCommunicator.hpp | 2 ++ src/axom/lumberjack/tests/lumberjack_RootCommunicator.hpp | 2 ++ .../lumberjack/tests/lumberjack_TextEqualityCombiner.hpp | 2 ++ src/axom/lumberjack/tests/lumberjack_TextTagCombiner.hpp | 2 ++ src/axom/mint/deprecated/MCArray.hpp | 5 +---- src/axom/mint/deprecated/SidreMCArray.hpp | 5 +---- src/axom/mint/execution/interface.hpp | 4 +--- src/axom/mint/execution/internal/for_all_cells.hpp | 5 +---- src/axom/mint/execution/internal/for_all_faces.hpp | 5 +---- src/axom/mint/execution/internal/for_all_nodes.hpp | 5 +---- src/axom/mint/execution/internal/helpers.hpp | 5 +---- src/axom/mint/execution/xargs.hpp | 5 +---- src/axom/mint/fem/FEBasis.hpp | 5 +---- src/axom/mint/fem/FEBasisTypes.hpp | 5 +---- src/axom/mint/fem/FiniteElement.hpp | 5 +---- src/axom/mint/fem/shape_functions/Lagrange.hpp | 5 +---- src/axom/mint/fem/shape_functions/ShapeFunction.hpp | 5 +---- .../mint/fem/shape_functions/lagrange/lagrange_hexa_27.hpp | 5 +---- .../mint/fem/shape_functions/lagrange/lagrange_hexa_8.hpp | 4 +--- .../mint/fem/shape_functions/lagrange/lagrange_prism_6.hpp | 4 +--- .../mint/fem/shape_functions/lagrange/lagrange_pyra_5.hpp | 5 +---- .../mint/fem/shape_functions/lagrange/lagrange_quad_4.hpp | 5 +---- .../mint/fem/shape_functions/lagrange/lagrange_quad_9.hpp | 5 +---- .../mint/fem/shape_functions/lagrange/lagrange_tetra_4.hpp | 4 +--- .../mint/fem/shape_functions/lagrange/lagrange_tri_3.hpp | 5 +---- src/axom/mint/mesh/CellTypes.hpp | 5 +---- src/axom/mint/mesh/ConnectivityArray.hpp | 5 +---- src/axom/mint/mesh/CurvilinearMesh.hpp | 5 +---- src/axom/mint/mesh/Field.hpp | 5 +---- src/axom/mint/mesh/FieldAssociation.hpp | 5 +---- src/axom/mint/mesh/FieldData.hpp | 5 +---- src/axom/mint/mesh/FieldTypes.hpp | 5 +---- src/axom/mint/mesh/FieldVariable.hpp | 5 +---- src/axom/mint/mesh/Mesh.hpp | 5 +---- src/axom/mint/mesh/MeshCoordinates.hpp | 5 +---- src/axom/mint/mesh/MeshTypes.hpp | 4 +--- src/axom/mint/mesh/ParticleMesh.hpp | 5 +---- src/axom/mint/mesh/RectilinearMesh.hpp | 5 +---- src/axom/mint/mesh/StructuredMesh.hpp | 5 +---- src/axom/mint/mesh/UniformMesh.hpp | 5 +---- src/axom/mint/mesh/UnstructuredMesh.hpp | 5 +---- src/axom/mint/mesh/blueprint.hpp | 5 +---- src/axom/mint/mesh/internal/ConnectivityArrayHelpers.hpp | 5 +---- .../mesh/internal/ConnectivityArray_typed_indirection.hpp | 5 +---- src/axom/mint/mesh/internal/MeshHelpers.hpp | 5 +---- src/axom/mint/tests/StructuredMesh_helpers.hpp | 5 +---- src/axom/mint/tests/mint_test_utilities.hpp | 3 +++ src/axom/mint/utils/ArrayWrapper.hpp | 5 +---- src/axom/mint/utils/ExternalArray.hpp | 5 +---- src/axom/mint/utils/su2_utils.hpp | 5 +---- src/axom/mint/utils/vtk_utils.hpp | 5 +---- src/axom/mir/ElviraAlgorithm.hpp | 5 +---- src/axom/mir/EquiZAlgorithm.hpp | 5 +---- src/axom/mir/MIRAlgorithm.hpp | 5 +---- src/axom/mir/detail/elvira_detail.hpp | 5 +---- src/axom/mir/detail/elvira_impl.hpp | 2 ++ src/axom/mir/detail/equiz_detail.hpp | 5 +---- src/axom/mir/examples/concentric_circles/MIRApplication.hpp | 5 +---- src/axom/mir/examples/concentric_circles/runMIR.hpp | 5 +---- src/axom/mir/examples/heavily_mixed/HMApplication.hpp | 5 +---- src/axom/mir/examples/heavily_mixed/runMIR.hpp | 5 +---- src/axom/mir/examples/tutorial_simple/runMIR.hpp | 5 +---- src/axom/mir/future/ClipFieldFilter.hpp | 5 +---- src/axom/mir/future/ClipFieldFilterDevice.hpp | 5 +---- src/axom/mir/reference/CellClipper.hpp | 5 +---- src/axom/mir/reference/CellData.hpp | 5 +---- src/axom/mir/reference/CellGenerator.hpp | 5 +---- src/axom/mir/reference/InterfaceReconstructor.hpp | 4 +--- src/axom/mir/reference/MIRMesh.hpp | 4 +--- src/axom/mir/reference/MIRMeshTypes.hpp | 4 +--- src/axom/mir/reference/MIRUtilities.hpp | 5 +---- src/axom/mir/reference/ZooClippingTables.hpp | 4 +--- src/axom/multimat/examples/helper.hpp | 2 ++ src/axom/multimat/mmfield.hpp | 5 +---- src/axom/multimat/mmsubfield.hpp | 5 +---- src/axom/multimat/multimat.hpp | 5 +---- src/axom/primal/constants.hpp | 5 +---- src/axom/primal/geometry/BezierCurve.hpp | 5 +---- src/axom/primal/geometry/BezierPatch.hpp | 5 +---- src/axom/primal/geometry/BezierTriangle.hpp | 5 +---- src/axom/primal/geometry/BoundingBox.hpp | 5 +---- src/axom/primal/geometry/Cone.hpp | 5 +---- src/axom/primal/geometry/CoordinateTransformer.hpp | 5 +---- src/axom/primal/geometry/CurvedPolygon.hpp | 5 +---- src/axom/primal/geometry/Hexahedron.hpp | 5 +---- src/axom/primal/geometry/KnotVector.hpp | 5 +---- src/axom/primal/geometry/Line.hpp | 5 +---- src/axom/primal/geometry/NURBSCurve.hpp | 5 +---- src/axom/primal/geometry/NURBSPatch.hpp | 5 +---- src/axom/primal/geometry/Octahedron.hpp | 5 +---- src/axom/primal/geometry/OrientationResult.hpp | 5 +---- src/axom/primal/geometry/OrientedBoundingBox.hpp | 5 +---- src/axom/primal/geometry/Plane.hpp | 5 +---- src/axom/primal/geometry/Point.hpp | 5 +---- src/axom/primal/geometry/Polygon.hpp | 5 +---- src/axom/primal/geometry/Polyhedron.hpp | 5 +---- src/axom/primal/geometry/Quadrilateral.hpp | 5 +---- src/axom/primal/geometry/Ray.hpp | 5 +---- src/axom/primal/geometry/Segment.hpp | 5 +---- src/axom/primal/geometry/Sphere.hpp | 5 +---- src/axom/primal/geometry/Tetrahedron.hpp | 5 +---- src/axom/primal/geometry/Triangle.hpp | 5 +---- src/axom/primal/geometry/Vector.hpp | 5 +---- src/axom/primal/geometry/construct.hpp | 5 +---- src/axom/primal/geometry/detail/analytic_test_surfaces.hpp | 5 +---- src/axom/primal/operators/clip.hpp | 5 +---- src/axom/primal/operators/closest_point.hpp | 5 +---- src/axom/primal/operators/compute_bounding_box.hpp | 5 +---- src/axom/primal/operators/compute_moments.hpp | 5 +---- src/axom/primal/operators/detail/clip_impl.hpp | 5 +---- src/axom/primal/operators/detail/compute_moments_impl.hpp | 5 +---- .../primal/operators/detail/evaluate_integral_curve_impl.hpp | 5 +---- src/axom/primal/operators/detail/evaluate_integral_impl.hpp | 5 +---- .../operators/detail/evaluate_integral_surface_impl.hpp | 5 +---- src/axom/primal/operators/detail/fuzzy_comparators.hpp | 5 +---- src/axom/primal/operators/detail/intersect_bezier_impl.hpp | 5 +---- .../primal/operators/detail/intersect_bounding_box_impl.hpp | 5 +---- src/axom/primal/operators/detail/intersect_impl.hpp | 5 +---- src/axom/primal/operators/detail/intersect_patch_impl.hpp | 5 +---- src/axom/primal/operators/detail/intersect_ray_impl.hpp | 5 +---- src/axom/primal/operators/detail/predicate_determinants.hpp | 5 +---- src/axom/primal/operators/detail/slice_impl.hpp | 4 +--- src/axom/primal/operators/detail/winding_number_2d_impl.hpp | 5 +---- .../operators/detail/winding_number_2d_memoization.hpp | 5 +---- src/axom/primal/operators/detail/winding_number_3d_impl.hpp | 5 +---- .../operators/detail/winding_number_3d_memoization.hpp | 5 +---- src/axom/primal/operators/evaluate_integral.hpp | 5 +---- src/axom/primal/operators/evaluate_integral_curve.hpp | 5 +---- src/axom/primal/operators/evaluate_integral_surface.hpp | 5 +---- src/axom/primal/operators/in_curved_polygon.hpp | 5 +---- src/axom/primal/operators/in_polygon.hpp | 5 +---- src/axom/primal/operators/in_polyhedron.hpp | 5 +---- src/axom/primal/operators/in_sphere.hpp | 5 +---- src/axom/primal/operators/intersect.hpp | 5 +---- src/axom/primal/operators/intersection_volume.hpp | 5 +---- src/axom/primal/operators/is_convex.hpp | 5 +---- src/axom/primal/operators/orientation.hpp | 5 +---- src/axom/primal/operators/slice.hpp | 4 +--- src/axom/primal/operators/split.hpp | 5 +---- src/axom/primal/operators/squared_distance.hpp | 5 +---- src/axom/primal/operators/winding_number.hpp | 5 +---- src/axom/primal/utils/ZipBoundingBox.hpp | 5 +---- src/axom/primal/utils/ZipIndexable.hpp | 5 +---- src/axom/primal/utils/ZipPoint.hpp | 5 +---- src/axom/primal/utils/ZipRay.hpp | 5 +---- src/axom/primal/utils/ZipVector.hpp | 5 +---- src/axom/quest/AllNearestNeighbors.hpp | 5 +---- src/axom/quest/Delaunay.hpp | 5 +---- src/axom/quest/DiscreteShape.hpp | 5 +---- src/axom/quest/Discretize.hpp | 5 +---- src/axom/quest/DistributedClosestPoint.hpp | 5 +---- src/axom/quest/FastApproximateGWN.hpp | 5 +---- src/axom/quest/GWNMethods.hpp | 4 +--- src/axom/quest/InOutOctree.hpp | 5 +---- src/axom/quest/IntersectionShaper.hpp | 5 +---- src/axom/quest/LinearizeCurves.hpp | 5 +---- src/axom/quest/MarchingCubes.hpp | 4 +--- src/axom/quest/MeshClipper.hpp | 5 +---- src/axom/quest/MeshClipperStrategy.hpp | 5 +---- src/axom/quest/MeshTester.hpp | 5 +---- src/axom/quest/MeshViewUtil.hpp | 4 +--- src/axom/quest/PointInCell.hpp | 5 +---- src/axom/quest/SamplingShaper.hpp | 5 +---- src/axom/quest/ScatteredInterpolation.hpp | 5 +---- src/axom/quest/ShapeMesh.hpp | 5 +---- src/axom/quest/Shaper.hpp | 5 +---- src/axom/quest/SignedDistance.hpp | 5 +---- src/axom/quest/detail/AllNearestNeighbors_detail.hpp | 5 +---- src/axom/quest/detail/DelaunayElementFinder.hpp | 5 +---- src/axom/quest/detail/DelaunayImpl.hpp | 5 +---- src/axom/quest/detail/DelaunayInsertionHelper.hpp | 5 +---- src/axom/quest/detail/DelaunayPointLocation.hpp | 5 +---- src/axom/quest/detail/DelaunayValidation.hpp | 5 +---- src/axom/quest/detail/Discretize_detail.hpp | 5 +---- src/axom/quest/detail/DistributedClosestPointImpl.hpp | 5 +---- src/axom/quest/detail/MarchingCubesImpl.hpp | 2 ++ src/axom/quest/detail/MarchingCubesSingleDomain.hpp | 4 +--- src/axom/quest/detail/MeshTester_detail.hpp | 5 +---- src/axom/quest/detail/PointFinder.hpp | 5 +---- src/axom/quest/detail/PointInCellMeshWrapper_mfem.hpp | 5 +---- src/axom/quest/detail/clipping/HexClipper.hpp | 5 +---- src/axom/quest/detail/clipping/MeshClipperImpl.hpp | 5 +---- src/axom/quest/detail/clipping/MonotonicZSORClipper.hpp | 5 +---- src/axom/quest/detail/clipping/Plane3DClipper.hpp | 5 +---- src/axom/quest/detail/clipping/SORClipper.hpp | 5 +---- src/axom/quest/detail/clipping/SphereClipper.hpp | 5 +---- src/axom/quest/detail/clipping/TetClipper.hpp | 5 +---- src/axom/quest/detail/clipping/TetMeshClipper.hpp | 5 +---- src/axom/quest/detail/inout/BlockData.hpp | 5 +---- src/axom/quest/detail/inout/InOutOctreeMeshDumper.hpp | 5 +---- src/axom/quest/detail/inout/InOutOctreeStats.hpp | 5 +---- src/axom/quest/detail/inout/InOutOctreeValidator.hpp | 5 +---- src/axom/quest/detail/inout/MeshWrapper.hpp | 5 +---- src/axom/quest/detail/marching_cubes_lookup.hpp | 2 ++ src/axom/quest/detail/shaping/InOutSampler.hpp | 5 +---- src/axom/quest/detail/shaping/PrimitiveSampler.hpp | 5 +---- src/axom/quest/detail/shaping/WindingNumberSampler.hpp | 5 +---- src/axom/quest/detail/shaping/shaping_helpers.hpp | 5 +---- src/axom/quest/interface/c_fortran/typesQUEST.h | 5 +---- src/axom/quest/interface/c_fortran/wrapQUEST.h | 5 +---- src/axom/quest/interface/inout.hpp | 5 +---- src/axom/quest/interface/internal/QuestHelpers.hpp | 5 +---- src/axom/quest/interface/internal/mpicomm_wrapper.hpp | 5 +---- src/axom/quest/interface/python/pyQUESTmodule.hpp | 5 +---- src/axom/quest/interface/signed_distance.hpp | 5 +---- src/axom/quest/io/C2CReader.hpp | 5 +---- src/axom/quest/io/MFEMReader.hpp | 5 +---- src/axom/quest/io/PC2CReader.hpp | 5 +---- src/axom/quest/io/PProEReader.hpp | 5 +---- src/axom/quest/io/PSTEPReader.hpp | 5 +---- src/axom/quest/io/PSTLReader.hpp | 5 +---- src/axom/quest/io/ProEReader.hpp | 5 +---- src/axom/quest/io/STEPReader.hpp | 5 +---- src/axom/quest/io/STLReader.hpp | 5 +---- src/axom/quest/io/STLWriter.hpp | 5 +---- src/axom/quest/tests/quest_intersection_shaper_utils.hpp | 5 +---- src/axom/quest/tests/quest_test_utilities.hpp | 5 +---- src/axom/quest/util/make_clipper_strategy.hpp | 4 +--- src/axom/quest/util/mesh_helpers.hpp | 5 +---- src/axom/sidre/core/Array.hpp | 5 +---- src/axom/sidre/core/AttrValues.hpp | 5 +---- src/axom/sidre/core/Attribute.hpp | 5 +---- src/axom/sidre/core/Buffer.hpp | 5 +---- src/axom/sidre/core/ConduitMemory.hpp | 5 +---- src/axom/sidre/core/DataStore.hpp | 5 +---- src/axom/sidre/core/Group.hpp | 5 +---- src/axom/sidre/core/MFEMSidreDataCollection.hpp | 5 +---- src/axom/sidre/core/SidreDataTypeIds.h | 5 +---- src/axom/sidre/core/SidreTypes.hpp | 5 +---- src/axom/sidre/core/View.hpp | 5 +---- src/axom/sidre/examples/lulesh2/lulesh.h | 2 ++ src/axom/sidre/examples/lulesh2/lulesh_tuple.h | 2 ++ src/axom/sidre/examples/spio/spio_scr.hpp | 2 ++ src/axom/sidre/interface/SidreTypes.h | 5 +---- src/axom/sidre/interface/c_fortran/typesSidre.h | 5 +---- src/axom/sidre/interface/c_fortran/wrapBuffer.h | 5 +---- src/axom/sidre/interface/c_fortran/wrapDataStore.h | 5 +---- src/axom/sidre/interface/c_fortran/wrapGroup.h | 5 +---- src/axom/sidre/interface/c_fortran/wrapSidre.h | 5 +---- src/axom/sidre/interface/c_fortran/wrapView.h | 5 +---- src/axom/sidre/interface/sidre.h | 5 +---- src/axom/sidre/spio/IOBaton.hpp | 5 +---- src/axom/sidre/spio/IOManager.hpp | 5 +---- src/axom/sidre/spio/interface/c_fortran/typesSPIO.h | 5 +---- src/axom/sidre/spio/interface/c_fortran/wrapIOManager.h | 5 +---- src/axom/sidre/tests/spio/spio_basic.hpp | 2 ++ src/axom/sidre/tests/spio/spio_parallel.hpp | 2 ++ src/axom/sidre/tests/spio/spio_serial.hpp | 2 ++ src/axom/sina/core/AdiakWriter.hpp | 5 +---- src/axom/sina/core/ConduitUtil.hpp | 5 +---- src/axom/sina/core/Curve.hpp | 5 +---- src/axom/sina/core/CurveSet.hpp | 5 +---- src/axom/sina/core/DataHolder.hpp | 5 +---- src/axom/sina/core/Datum.hpp | 5 +---- src/axom/sina/core/Document.hpp | 5 +---- src/axom/sina/core/File.hpp | 5 +---- src/axom/sina/core/ID.hpp | 5 +---- src/axom/sina/core/Record.hpp | 5 +---- src/axom/sina/core/Relationship.hpp | 5 +---- src/axom/sina/core/Run.hpp | 5 +---- src/axom/sina/interface/sina_fortran_interface.h | 2 ++ src/axom/sina/tests/SinaMatchers.hpp | 5 +---- src/axom/sina/tests/TestRecord.hpp | 5 +---- src/axom/slam/BitSet.hpp | 5 +---- src/axom/slam/BivariateMap.hpp | 5 +---- src/axom/slam/BivariateSet.hpp | 5 +---- src/axom/slam/DynamicConstantRelation.hpp | 5 +---- src/axom/slam/DynamicMap.hpp | 5 +---- src/axom/slam/DynamicSet.hpp | 5 +---- src/axom/slam/DynamicVariableRelation.hpp | 5 +---- src/axom/slam/FieldRegistry.hpp | 4 +--- src/axom/slam/IndirectionSet.hpp | 5 +---- src/axom/slam/Map.hpp | 5 +---- src/axom/slam/MapBase.hpp | 5 +---- src/axom/slam/ModularInt.hpp | 5 +---- src/axom/slam/NullSet.hpp | 5 +---- src/axom/slam/OrderedSet.hpp | 5 +---- src/axom/slam/ProductSet.hpp | 5 +---- src/axom/slam/RangeSet.hpp | 5 +---- src/axom/slam/Relation.hpp | 5 +---- src/axom/slam/RelationBuilders.hpp | 4 +--- src/axom/slam/RelationSet.hpp | 4 +--- src/axom/slam/Set.hpp | 5 +---- src/axom/slam/StaticRelation.hpp | 5 +---- src/axom/slam/SubMap.hpp | 5 +---- src/axom/slam/Utilities.hpp | 5 +---- src/axom/slam/examples/lulesh2.0.3/lulesh.hpp | 2 ++ src/axom/slam/examples/lulesh2.0.3/lulesh_tuple.hpp | 2 ++ src/axom/slam/examples/lulesh2.0.3_orig/lulesh.h | 2 ++ src/axom/slam/examples/lulesh2.0.3_orig/lulesh_tuple.h | 2 ++ src/axom/slam/examples/tinyHydro/HydroC.hpp | 2 ++ src/axom/slam/examples/tinyHydro/Part.hpp | 5 +---- src/axom/slam/examples/tinyHydro/PolygonMeshXY.hpp | 5 +---- src/axom/slam/examples/tinyHydro/State.hpp | 2 ++ src/axom/slam/examples/tinyHydro/TinyHydroTypes.hpp | 5 +---- src/axom/slam/examples/tinyHydro/VectorXY.hpp | 5 +---- src/axom/slam/mesh_struct/IA.hpp | 5 +---- src/axom/slam/mesh_struct/IA_impl.hpp | 5 +---- src/axom/slam/mesh_struct/detail/FacetPairingMap.hpp | 5 +---- src/axom/slam/policies/BivariateSetInterfacePolicies.hpp | 5 +---- src/axom/slam/policies/CardinalityPolicies.hpp | 5 +---- src/axom/slam/policies/IndirectionPolicies.hpp | 5 +---- src/axom/slam/policies/InterfacePolicies.hpp | 5 +---- src/axom/slam/policies/MapInterfacePolicies.hpp | 5 +---- src/axom/slam/policies/OffsetPolicies.hpp | 5 +---- src/axom/slam/policies/PolicyTraits.hpp | 5 +---- src/axom/slam/policies/SetInterfacePolicies.hpp | 5 +---- src/axom/slam/policies/SizePolicies.hpp | 5 +---- src/axom/slam/policies/StridePolicies.hpp | 5 +---- src/axom/slam/policies/SubsettingPolicies.hpp | 5 +---- src/axom/slic/core/LogStream.hpp | 5 +---- src/axom/slic/core/LogStreamStatusMonitor.hpp | 5 +---- src/axom/slic/core/Logger.hpp | 5 +---- src/axom/slic/core/MessageLevel.hpp | 5 +---- src/axom/slic/core/SimpleLogger.hpp | 5 +---- src/axom/slic/examples/multicode/physicsA.hpp | 5 +---- src/axom/slic/examples/multicode/physicsB.hpp | 5 +---- src/axom/slic/interface/c_fortran/typesSLIC.h | 5 +---- src/axom/slic/interface/c_fortran/wrapGenericOutputStream.h | 5 +---- src/axom/slic/interface/c_fortran/wrapSLIC.h | 5 +---- src/axom/slic/interface/slic.hpp | 5 +---- src/axom/slic/interface/slic_macros.hpp | 5 +---- src/axom/slic/internal/stacktrace.hpp | 2 ++ src/axom/slic/streams/GenericOutputStream.hpp | 5 +---- src/axom/slic/streams/LumberjackStream.hpp | 5 +---- src/axom/slic/streams/SynchronizedStream.hpp | 5 +---- src/axom/spin/BVH.hpp | 5 +---- src/axom/spin/Brood.hpp | 5 +---- src/axom/spin/DenseOctreeLevel.hpp | 5 +---- src/axom/spin/ImplicitGrid.hpp | 5 +---- src/axom/spin/MortonIndex.hpp | 5 +---- src/axom/spin/OctreeBase.hpp | 5 +---- src/axom/spin/OctreeLevel.hpp | 5 +---- src/axom/spin/RectangularLattice.hpp | 5 +---- src/axom/spin/SparseOctreeLevel.hpp | 5 +---- src/axom/spin/SpatialOctree.hpp | 5 +---- src/axom/spin/UniformGrid.hpp | 5 +---- src/axom/spin/internal/linear_bvh/RadixTree.hpp | 5 +---- src/axom/spin/internal/linear_bvh/build_radix_tree.hpp | 4 +--- src/axom/spin/internal/linear_bvh/bvh_traverse.hpp | 5 +---- src/axom/spin/internal/linear_bvh/bvh_vtkio.hpp | 5 +---- src/axom/spin/policy/LinearBVH.hpp | 4 +--- src/axom/spin/policy/UniformGridStorage.hpp | 4 +--- src/examples/radiuss_tutorial/patch/hip_patch.hpp | 5 +---- 578 files changed, 649 insertions(+), 2015 deletions(-) diff --git a/src/axom/bump/BlendData.hpp b/src/axom/bump/BlendData.hpp index 22ab0e2cf5..dbfed4f88a 100644 --- a/src/axom/bump/BlendData.hpp +++ b/src/axom/bump/BlendData.hpp @@ -3,8 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_BLEND_DATA_HPP_ -#define AXOM_BUMP_BLEND_DATA_HPP_ +#pragma once #include "axom/core.hpp" @@ -79,5 +78,3 @@ inline axom::IndexType numberOfValues(const BlendData &blend) } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/CoordsetBlender.hpp b/src/axom/bump/CoordsetBlender.hpp index 68c6c49442..e73ff5d349 100644 --- a/src/axom/bump/CoordsetBlender.hpp +++ b/src/axom/bump/CoordsetBlender.hpp @@ -3,8 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_COORDSET_BLENDER_HPP_ -#define AXOM_BUMP_COORDSET_BLENDER_HPP_ +#pragma once #include "axom/core.hpp" #include "axom/bump/utilities/conduit_memory.hpp" @@ -147,5 +146,3 @@ class CoordsetBlender } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/CoordsetExtents.hpp b/src/axom/bump/CoordsetExtents.hpp index 7102543f11..240e41a0a5 100644 --- a/src/axom/bump/CoordsetExtents.hpp +++ b/src/axom/bump/CoordsetExtents.hpp @@ -3,8 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_COORDSET_EXTENTS_HPP_ -#define AXOM_BUMP_COORDSET_EXTENTS_HPP_ +#pragma once #include "axom/core.hpp" #include "axom/slic.hpp" @@ -215,5 +214,3 @@ class CoordsetExtents } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/CoordsetSlicer.hpp b/src/axom/bump/CoordsetSlicer.hpp index 35cc3dfaac..0bdb0c0f56 100644 --- a/src/axom/bump/CoordsetSlicer.hpp +++ b/src/axom/bump/CoordsetSlicer.hpp @@ -3,8 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_COORDSET_SLICER_HPP_ -#define AXOM_BUMP_COORDSET_SLICER_HPP_ +#pragma once #include "axom/core.hpp" #include "axom/slic.hpp" @@ -128,5 +127,3 @@ class CoordsetSlicer } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/ExtractZones.hpp b/src/axom/bump/ExtractZones.hpp index d68f282bef..c18e86d43d 100644 --- a/src/axom/bump/ExtractZones.hpp +++ b/src/axom/bump/ExtractZones.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_EXTRACT_ZONES_HPP -#define AXOM_BUMP_EXTRACT_ZONES_HPP +#pragma once #include "axom/core.hpp" #include "axom/slic.hpp" @@ -863,5 +862,3 @@ class ExtractZonesAndMatset : public ExtractZones } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/MergePolyhedralFaces.hpp b/src/axom/bump/MergePolyhedralFaces.hpp index 2ad307957a..7c1169e477 100644 --- a/src/axom/bump/MergePolyhedralFaces.hpp +++ b/src/axom/bump/MergePolyhedralFaces.hpp @@ -3,8 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_MERGE_POLYHEDRAL_FACES_HPP_ -#define AXOM_BUMP_MERGE_POLYHEDRAL_FACES_HPP_ +#pragma once #include "axom/core.hpp" #include "axom/bump/utilities/blueprint_utilities.hpp" @@ -279,5 +278,3 @@ class MergePolyhedralFaces } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/MinMax.hpp b/src/axom/bump/MinMax.hpp index 673ab017d7..6669cecf54 100644 --- a/src/axom/bump/MinMax.hpp +++ b/src/axom/bump/MinMax.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_MINMAX_HPP_ -#define AXOM_BUMP_MINMAX_HPP_ +#pragma once #include "axom/core/execution/execution_space.hpp" #include "axom/core/execution/reductions.hpp" @@ -75,5 +74,3 @@ struct MinMax } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/NodeToZoneRelationBuilder.hpp b/src/axom/bump/NodeToZoneRelationBuilder.hpp index e5a7098e8f..c2a06abe31 100644 --- a/src/axom/bump/NodeToZoneRelationBuilder.hpp +++ b/src/axom/bump/NodeToZoneRelationBuilder.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_NODE_TO_ZONE_RELATION_BUILDER_HPP_ -#define AXOM_BUMP_NODE_TO_ZONE_RELATION_BUILDER_HPP_ +#pragma once #include "axom/core.hpp" #include "axom/slic.hpp" @@ -567,5 +566,3 @@ class NodeToZoneRelationBuilder } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/Options.hpp b/src/axom/bump/Options.hpp index 3ebcaf7988..33d5bdd862 100644 --- a/src/axom/bump/Options.hpp +++ b/src/axom/bump/Options.hpp @@ -3,8 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_OPTIONS_HPP_ -#define AXOM_BUMP_OPTIONS_HPP_ +#pragma once #include "axom/core.hpp" @@ -136,5 +135,3 @@ class Options } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/PrimalAdaptor.hpp b/src/axom/bump/PrimalAdaptor.hpp index 9c1a2bc222..f6eaf2542d 100644 --- a/src/axom/bump/PrimalAdaptor.hpp +++ b/src/axom/bump/PrimalAdaptor.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_UTILITIES_PRIMAL_ADAPTOR_HPP_ -#define AXOM_BUMP_UTILITIES_PRIMAL_ADAPTOR_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core.hpp" @@ -483,5 +482,3 @@ struct PrimalAdaptor } // namespace bump } // namespace axom - -#endif diff --git a/src/axom/bump/RecenterField.hpp b/src/axom/bump/RecenterField.hpp index 303a6d8679..3b4ce76d74 100644 --- a/src/axom/bump/RecenterField.hpp +++ b/src/axom/bump/RecenterField.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_RECENTER_FIELD_HPP_ -#define AXOM_BUMP_RECENTER_FIELD_HPP_ +#pragma once #include "axom/core.hpp" #include "axom/slic.hpp" @@ -165,5 +164,3 @@ class RecenterField } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/SelectedZones.hpp b/src/axom/bump/SelectedZones.hpp index 0cfd8a222b..46e2ffe92b 100644 --- a/src/axom/bump/SelectedZones.hpp +++ b/src/axom/bump/SelectedZones.hpp @@ -3,8 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_SELECTED_ZONES_HPP_ -#define AXOM_BUMP_SELECTED_ZONES_HPP_ +#pragma once #include "axom/core.hpp" @@ -179,5 +178,3 @@ class SelectedZones } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/TopologyMapper.hpp b/src/axom/bump/TopologyMapper.hpp index ebdbb2cb59..22d45060c4 100644 --- a/src/axom/bump/TopologyMapper.hpp +++ b/src/axom/bump/TopologyMapper.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_TOPOLOGY_MAPPER_HPP_ -#define AXOM_BUMP_TOPOLOGY_MAPPER_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core.hpp" @@ -878,5 +877,3 @@ class TopologyMapper } // namespace axom #undef AXOM_TM_ASSERT_OR_RETURN - -#endif diff --git a/src/axom/bump/Unique.hpp b/src/axom/bump/Unique.hpp index 2a5311cd67..c4a77f5aed 100644 --- a/src/axom/bump/Unique.hpp +++ b/src/axom/bump/Unique.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_UTILITIES_UNIQUE_HPP_ -#define AXOM_BUMP_UTILITIES_UNIQUE_HPP_ +#pragma once #include "axom/core.hpp" #include "axom/slic.hpp" @@ -256,5 +255,3 @@ struct Unique } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/VariableShape.hpp b/src/axom/bump/VariableShape.hpp index ff7cc5b607..c9dcd24504 100644 --- a/src/axom/bump/VariableShape.hpp +++ b/src/axom/bump/VariableShape.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_UTILITIES_VARIABLE_SHAPE_HPP_ -#define AXOM_BUMP_UTILITIES_VARIABLE_SHAPE_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core.hpp" @@ -156,5 +155,3 @@ std::ostream &operator<<(std::ostream &os, const VariableShape &obj } // namespace bump } // namespace axom - -#endif diff --git a/src/axom/bump/ZoneListBuilder.hpp b/src/axom/bump/ZoneListBuilder.hpp index 9f65e3bb6b..20108d48f3 100644 --- a/src/axom/bump/ZoneListBuilder.hpp +++ b/src/axom/bump/ZoneListBuilder.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_ZONELIST_BUILDER_HPP -#define AXOM_BUMP_ZONELIST_BUILDER_HPP +#pragma once #include "axom/core.hpp" #include "axom/slic.hpp" @@ -524,5 +523,3 @@ class ZoneListBuilder } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/data/MeshTester.hpp b/src/axom/bump/data/MeshTester.hpp index 5ff45095c7..5fbfaa23e4 100644 --- a/src/axom/bump/data/MeshTester.hpp +++ b/src/axom/bump/data/MeshTester.hpp @@ -11,8 +11,7 @@ * */ -#ifndef __AXOM_BUMP_MESH_TESTER_HPP__ -#define __AXOM_BUMP_MESH_TESTER_HPP__ +#pragma once #include "axom/core.hpp" // for axom macros #include "axom/primal.hpp" @@ -195,5 +194,3 @@ class MeshTester } // namespace data } // namespace bump } // namespace axom - -#endif diff --git a/src/axom/bump/extraction/BlendGroupBuilder.hpp b/src/axom/bump/extraction/BlendGroupBuilder.hpp index f953ede43f..12ad431982 100644 --- a/src/axom/bump/extraction/BlendGroupBuilder.hpp +++ b/src/axom/bump/extraction/BlendGroupBuilder.hpp @@ -3,8 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_BLEND_GROUP_BUILDER_HPP_ -#define AXOM_BUMP_BLEND_GROUP_BUILDER_HPP_ +#pragma once #include "axom/core.hpp" #include "axom/slic.hpp" @@ -534,5 +533,3 @@ class BlendGroupBuilder } // end namespace extraction } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/extraction/ClipField.hpp b/src/axom/bump/extraction/ClipField.hpp index 9c26cb6511..3a45799f55 100644 --- a/src/axom/bump/extraction/ClipField.hpp +++ b/src/axom/bump/extraction/ClipField.hpp @@ -3,8 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_CLIP_FIELD_HPP_ -#define AXOM_BUMP_CLIP_FIELD_HPP_ +#pragma once #include "axom/bump/extraction/TableBasedExtractor.hpp" #include "axom/bump/extraction/ClipTableManager.hpp" @@ -36,5 +35,3 @@ using ClipField = } // end namespace extraction } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/extraction/ClipTableManager.hpp b/src/axom/bump/extraction/ClipTableManager.hpp index 9a703cdc99..60517676f4 100644 --- a/src/axom/bump/extraction/ClipTableManager.hpp +++ b/src/axom/bump/extraction/ClipTableManager.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_EXTRACTION_CLIP_TABLE_MANAGER_HPP_ -#define AXOM_BUMP_EXTRACTION_CLIP_TABLE_MANAGER_HPP_ +#pragma once #include "axom/core.hpp" #include "axom/bump/extraction/TableManager.hpp" @@ -39,5 +38,3 @@ class ClipTableManager : public TableManager } // end namespace extraction } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/extraction/CutField.hpp b/src/axom/bump/extraction/CutField.hpp index 66e093e3de..d7559bf36a 100644 --- a/src/axom/bump/extraction/CutField.hpp +++ b/src/axom/bump/extraction/CutField.hpp @@ -3,8 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_CUT_FIELD_HPP_ -#define AXOM_BUMP_CUT_FIELD_HPP_ +#pragma once #include "axom/bump/extraction/TableBasedExtractor.hpp" #include "axom/bump/extraction/CutTableManager.hpp" @@ -36,5 +35,3 @@ using CutField = } // end namespace extraction } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/extraction/CutTableManager.hpp b/src/axom/bump/extraction/CutTableManager.hpp index 18beaaa82e..c11f6dfeff 100644 --- a/src/axom/bump/extraction/CutTableManager.hpp +++ b/src/axom/bump/extraction/CutTableManager.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_EXTRACTION_CUT_TABLE_MANAGER_HPP_ -#define AXOM_BUMP_EXTRACTION_CUT_TABLE_MANAGER_HPP_ +#pragma once #include "axom/core.hpp" #include "axom/bump/extraction/TableManager.hpp" @@ -39,5 +38,3 @@ class CutTableManager : public TableManager } // end namespace extraction } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/extraction/ExtractionConstants.hpp b/src/axom/bump/extraction/ExtractionConstants.hpp index fbef86fdb6..61da5dabb4 100644 --- a/src/axom/bump/extraction/ExtractionConstants.hpp +++ b/src/axom/bump/extraction/ExtractionConstants.hpp @@ -2,8 +2,7 @@ // Project developers. See the top-level LICENSE file for dates and other // details. No copyright assignment is required to contribute to VisIt. -#ifndef AXOM_BUMP_EXTRACTION_CONSTANTS_HPP -#define AXOM_BUMP_EXTRACTION_CONSTANTS_HPP +#pragma once #include "axom/export/bump.h" #include @@ -78,5 +77,3 @@ constexpr unsigned char NOCOLOR = 122; } // namespace axom // clang-format on //--------------------------------------------------------------------------- - -#endif diff --git a/src/axom/bump/extraction/ExtractorOptions.hpp b/src/axom/bump/extraction/ExtractorOptions.hpp index 64553664c0..a8c301e0ec 100644 --- a/src/axom/bump/extraction/ExtractorOptions.hpp +++ b/src/axom/bump/extraction/ExtractorOptions.hpp @@ -3,8 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_EXTRACTOR_OPTIONS_HPP_ -#define AXOM_BUMP_EXTRACTOR_OPTIONS_HPP_ +#pragma once #include "axom/bump/Options.hpp" @@ -105,5 +104,3 @@ class ExtractorOptions : public axom::bump::Options } // end namespace extraction } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/extraction/FieldIntersector.hpp b/src/axom/bump/extraction/FieldIntersector.hpp index 0404a4cb08..23104fdafc 100644 --- a/src/axom/bump/extraction/FieldIntersector.hpp +++ b/src/axom/bump/extraction/FieldIntersector.hpp @@ -3,8 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_FIELD_INTERSECTOR_HPP_ -#define AXOM_BUMP_FIELD_INTERSECTOR_HPP_ +#pragma once #include "axom/core.hpp" #include "axom/bump/extraction/FieldOptions.hpp" @@ -201,5 +200,3 @@ class FieldIntersector } // end namespace extraction } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/extraction/FieldOptions.hpp b/src/axom/bump/extraction/FieldOptions.hpp index d23d398ca0..368340b48d 100644 --- a/src/axom/bump/extraction/FieldOptions.hpp +++ b/src/axom/bump/extraction/FieldOptions.hpp @@ -3,8 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_FIELD_OPTIONS_HPP_ -#define AXOM_BUMP_FIELD_OPTIONS_HPP_ +#pragma once #include "axom/bump/extraction/ExtractorOptions.hpp" @@ -47,5 +46,3 @@ class FieldOptions : public axom::bump::extraction::ExtractorOptions } // end namespace extraction } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/extraction/PlaneIntersector.hpp b/src/axom/bump/extraction/PlaneIntersector.hpp index 125535f3b6..9c242a9780 100644 --- a/src/axom/bump/extraction/PlaneIntersector.hpp +++ b/src/axom/bump/extraction/PlaneIntersector.hpp @@ -3,8 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_PLANE_INTERSECTOR_HPP_ -#define AXOM_BUMP_PLANE_INTERSECTOR_HPP_ +#pragma once #include "axom/core.hpp" #include "axom/bump/utilities/blueprint_utilities.hpp" @@ -196,5 +195,3 @@ class PlaneIntersector } // end namespace extraction } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/extraction/PlaneSlice.hpp b/src/axom/bump/extraction/PlaneSlice.hpp index a3d708c341..4aafec0a4f 100644 --- a/src/axom/bump/extraction/PlaneSlice.hpp +++ b/src/axom/bump/extraction/PlaneSlice.hpp @@ -3,8 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_PLANE_SLICE_HPP_ -#define AXOM_BUMP_PLANE_SLICE_HPP_ +#pragma once #include "axom/bump/extraction/TableBasedExtractor.hpp" #include "axom/bump/extraction/CutTableManager.hpp" @@ -35,5 +34,3 @@ using PlaneSlice = } // end namespace extraction } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/extraction/Table.hpp b/src/axom/bump/extraction/Table.hpp index f890939606..b6cce052bf 100644 --- a/src/axom/bump/extraction/Table.hpp +++ b/src/axom/bump/extraction/Table.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_EXTRACTION_TABLE_HPP_ -#define AXOM_BUMP_EXTRACTION_TABLE_HPP_ +#pragma once #include "axom/core.hpp" #include "axom/slic.hpp" @@ -404,5 +403,3 @@ class Table } // end namespace extraction } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/extraction/TableBasedExtractor.hpp b/src/axom/bump/extraction/TableBasedExtractor.hpp index e453064291..a29eab6172 100644 --- a/src/axom/bump/extraction/TableBasedExtractor.hpp +++ b/src/axom/bump/extraction/TableBasedExtractor.hpp @@ -3,8 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_TABLE_BASED_EXTRACTOR_HPP_ -#define AXOM_BUMP_TABLE_BASED_EXTRACTOR_HPP_ +#pragma once #include "axom/core.hpp" #include "axom/bump/extraction/BlendGroupBuilder.hpp" @@ -2442,5 +2441,3 @@ class TableBasedExtractor } // end namespace extraction } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/extraction/TableManager.hpp b/src/axom/bump/extraction/TableManager.hpp index b10c56fd2b..cf0101884d 100644 --- a/src/axom/bump/extraction/TableManager.hpp +++ b/src/axom/bump/extraction/TableManager.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_EXTRACTION_TABLE_MANAGER_HPP_ -#define AXOM_BUMP_EXTRACTION_TABLE_MANAGER_HPP_ +#pragma once #include "axom/core.hpp" #include "axom/slic.hpp" @@ -80,5 +79,3 @@ class TableManager } // end namespace extraction } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/extraction/tables/clipping/ClipCases.h b/src/axom/bump/extraction/tables/clipping/ClipCases.h index e05fcdb3cb..277460d28c 100644 --- a/src/axom/bump/extraction/tables/clipping/ClipCases.h +++ b/src/axom/bump/extraction/tables/clipping/ClipCases.h @@ -2,8 +2,7 @@ // Project developers. See the top-level LICENSE file for dates and other // details. No copyright assignment is required to contribute to VisIt. -#ifndef AXOM_BUMP_EXTRACTION_CLIP_CASES_H -#define AXOM_BUMP_EXTRACTION_CLIP_CASES_H +#pragma once #include "axom/export/bump.h" #include "axom/bump/extraction/ExtractionConstants.hpp" #include @@ -106,5 +105,3 @@ extern AXOM_BUMP_EXPORT const size_t clipShapesHexSize; } // namespace extraction } // namespace bump } // namespace axom - -#endif diff --git a/src/axom/bump/extraction/tables/cutting/CutCases.hpp b/src/axom/bump/extraction/tables/cutting/CutCases.hpp index 2a1f087465..4db6d1d867 100644 --- a/src/axom/bump/extraction/tables/cutting/CutCases.hpp +++ b/src/axom/bump/extraction/tables/cutting/CutCases.hpp @@ -2,8 +2,7 @@ // Project developers. See the top-level LICENSE file for dates and other // details. No copyright assignment is required to contribute to VisIt. -#ifndef AXOM_BUMP_EXTRACTION_CUT_CASES_H -#define AXOM_BUMP_EXTRACTION_CUT_CASES_H +#pragma once #include "axom/export/bump.h" #include "axom/bump/extraction/ExtractionConstants.hpp" #include @@ -96,5 +95,3 @@ extern AXOM_BUMP_EXPORT const size_t cutShapesHexSize; } // namespace extraction } // namespace bump } // namespace axom - -#endif diff --git a/src/axom/bump/io/save.hpp b/src/axom/bump/io/save.hpp index c0fc0d8744..d1172097d7 100644 --- a/src/axom/bump/io/save.hpp +++ b/src/axom/bump/io/save.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_IO_SAVE_HPP_ -#define AXOM_BUMP_IO_SAVE_HPP_ +#pragma once #include #include @@ -30,5 +29,3 @@ void save_vtk(const conduit::Node &node, const std::string &path); } // end namespace io } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/tests/blueprint_testing_data_helpers.hpp b/src/axom/bump/tests/blueprint_testing_data_helpers.hpp index f77d1dd9f3..297a2f4bb1 100644 --- a/src/axom/bump/tests/blueprint_testing_data_helpers.hpp +++ b/src/axom/bump/tests/blueprint_testing_data_helpers.hpp @@ -3,8 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BLUEPRINT_TESTING_DATA_HELPERS_HPP_ -#define AXOM_BLUEPRINT_TESTING_DATA_HELPERS_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core.hpp" @@ -659,5 +658,3 @@ void strided_structured(conduit::Node &hostMesh) } // end namespace testing } // end namespace blueprint } // end namespace axom - -#endif diff --git a/src/axom/bump/tests/blueprint_testing_helpers.hpp b/src/axom/bump/tests/blueprint_testing_helpers.hpp index d23903a8af..3ce86fbf7e 100644 --- a/src/axom/bump/tests/blueprint_testing_helpers.hpp +++ b/src/axom/bump/tests/blueprint_testing_helpers.hpp @@ -3,8 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BLUEPRINT_TESTING_HELPERS_HPP_ -#define AXOM_BLUEPRINT_TESTING_HELPERS_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core.hpp" @@ -906,4 +905,3 @@ class TestApplication } // end namespace testing } // end namespace blueprint } // end namespace axom -#endif diff --git a/src/axom/bump/utilities/blueprint_utilities.hpp b/src/axom/bump/utilities/blueprint_utilities.hpp index 10d78abba9..cc1be16b7c 100644 --- a/src/axom/bump/utilities/blueprint_utilities.hpp +++ b/src/axom/bump/utilities/blueprint_utilities.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_BLUEPRINT_UTILITIES_HPP_ -#define AXOM_BUMP_BLUEPRINT_UTILITIES_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core.hpp" @@ -33,5 +32,3 @@ std::vector coordsetAxes(const conduit::Node &n_input); } // end namespace utilities } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/utilities/conduit_array_view.hpp b/src/axom/bump/utilities/conduit_array_view.hpp index 6ea360a48c..2897c742a3 100644 --- a/src/axom/bump/utilities/conduit_array_view.hpp +++ b/src/axom/bump/utilities/conduit_array_view.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_CONDUIT_ARRAY_VIEW_HPP_ -#define AXOM_BUMP_CONDUIT_ARRAY_VIEW_HPP_ +#pragma once #include "axom/bump/utilities/conduit_traits.hpp" #include "axom/core/ArrayView.hpp" @@ -76,5 +75,3 @@ inline axom::ArrayView make_conduit_array_view(const conduit::Node &n) } // namespace utilities } // namespace bump } // namespace axom - -#endif diff --git a/src/axom/bump/utilities/conduit_memory.hpp b/src/axom/bump/utilities/conduit_memory.hpp index 03099b19f4..287ab34324 100644 --- a/src/axom/bump/utilities/conduit_memory.hpp +++ b/src/axom/bump/utilities/conduit_memory.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_CONDUIT_MEMORY_HPP_ -#define AXOM_BUMP_CONDUIT_MEMORY_HPP_ +#pragma once #include "axom/bump/utilities/conduit_array_view.hpp" #include "axom/bump/utilities/conduit_traits.hpp" @@ -196,5 +195,3 @@ bool fillFromNode(const conduit::Node &n, const std::string &key, ArrayType &arr } // end namespace utilities } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/utilities/conduit_traits.hpp b/src/axom/bump/utilities/conduit_traits.hpp index bc3f5ca4a3..2713377e5b 100644 --- a/src/axom/bump/utilities/conduit_traits.hpp +++ b/src/axom/bump/utilities/conduit_traits.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_CONDUIT_TRAITS_HPP_ -#define AXOM_BUMP_CONDUIT_TRAITS_HPP_ +#pragma once #include "axom/export/bump.h" @@ -109,5 +108,3 @@ struct cpp2conduit } // end namespace utilities } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/utilities/utilities.hpp b/src/axom/bump/utilities/utilities.hpp index 795057a9fc..2d1f01b4d2 100644 --- a/src/axom/bump/utilities/utilities.hpp +++ b/src/axom/bump/utilities/utilities.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_UTILITIES_HPP_ -#define AXOM_BUMP_UTILITIES_HPP_ +#pragma once #include "axom/core.hpp" #include "axom/slic.hpp" @@ -112,5 +111,3 @@ struct ComputeShapeAmount<3> } // end namespace utilities } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/views/BasicIndexing.hpp b/src/axom/bump/views/BasicIndexing.hpp index bce2534787..598b680fac 100644 --- a/src/axom/bump/views/BasicIndexing.hpp +++ b/src/axom/bump/views/BasicIndexing.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_BASIC_INDEXING_HPP_ -#define AXOM_BUMP_BASIC_INDEXING_HPP_ +#pragma once namespace axom { @@ -84,5 +83,3 @@ class BasicIndexing } // end namespace views } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/views/ExplicitCoordsetView.hpp b/src/axom/bump/views/ExplicitCoordsetView.hpp index 69753f0ed0..60476d1536 100644 --- a/src/axom/bump/views/ExplicitCoordsetView.hpp +++ b/src/axom/bump/views/ExplicitCoordsetView.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_EXPLICIT_COORDSET_VIEW_HPP_ -#define AXOM_BUMP_EXPLICIT_COORDSET_VIEW_HPP_ +#pragma once #include "axom/core/ArrayView.hpp" #include "axom/slic.hpp" @@ -186,5 +185,3 @@ class ExplicitCoordsetView } // end namespace views } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/views/MaterialView.hpp b/src/axom/bump/views/MaterialView.hpp index cd80f87557..20a50335d2 100644 --- a/src/axom/bump/views/MaterialView.hpp +++ b/src/axom/bump/views/MaterialView.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_VIEWS_MATERIAL_VIEW_HPP_ -#define AXOM_BUMP_VIEWS_MATERIAL_VIEW_HPP_ +#pragma once #include "axom/core.hpp" #include "axom/slic.hpp" @@ -925,5 +924,3 @@ class MaterialDominantMaterialView } // end namespace views } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/views/MixedFieldView.hpp b/src/axom/bump/views/MixedFieldView.hpp index 17b152f771..1fea6f06e7 100644 --- a/src/axom/bump/views/MixedFieldView.hpp +++ b/src/axom/bump/views/MixedFieldView.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_VIEWS_MIXED_FIELD_VIEW_HPP_ -#define AXOM_BUMP_VIEWS_MIXED_FIELD_VIEW_HPP_ +#pragma once #include "axom/core/ArrayView.hpp" #include "axom/core/StaticArray.hpp" @@ -156,4 +155,3 @@ class MixedFieldView } // end namespace views } // end namespace bump } // end namespace axom -#endif diff --git a/src/axom/bump/views/NodeArrayView.hpp b/src/axom/bump/views/NodeArrayView.hpp index e0d76be8a6..99bbbbcd1e 100644 --- a/src/axom/bump/views/NodeArrayView.hpp +++ b/src/axom/bump/views/NodeArrayView.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_VIEWS_NODE_ARRAY_VIEW_HPP_ -#define AXOM_BUMP_VIEWS_NODE_ARRAY_VIEW_HPP_ +#pragma once #include "axom/bump/utilities/conduit_array_view.hpp" #include "axom/slic/interface/slic.hpp" @@ -325,5 +324,3 @@ void floatNodeToArrayViewSame(conduit::Node &first, Args &&...args) } // namespace views } // namespace bump } // namespace axom - -#endif diff --git a/src/axom/bump/views/RectilinearCoordsetView.hpp b/src/axom/bump/views/RectilinearCoordsetView.hpp index b6d42a5ee9..13f2d41155 100644 --- a/src/axom/bump/views/RectilinearCoordsetView.hpp +++ b/src/axom/bump/views/RectilinearCoordsetView.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_RECTILINEAR_COORDSET_VIEW_HPP_ -#define AXOM_BUMP_RECTILINEAR_COORDSET_VIEW_HPP_ +#pragma once #include "axom/core/StackArray.hpp" #include "axom/core/ArrayView.hpp" @@ -272,5 +271,3 @@ class RectilinearCoordsetView3 } // end namespace views } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/views/Shapes.hpp b/src/axom/bump/views/Shapes.hpp index 1efd6b5e8e..a1f4d127bb 100644 --- a/src/axom/bump/views/Shapes.hpp +++ b/src/axom/bump/views/Shapes.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_VIEWS_SHAPES_HPP_ -#define AXOM_BUMP_VIEWS_SHAPES_HPP_ +#pragma once #include "axom/core/ArrayView.hpp" #include "axom/slic.hpp" @@ -1212,5 +1211,3 @@ AXOM_HOST_DEVICE constexpr IndexType shapeDimension(int shapeId) } // end namespace views } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/views/StridedStructuredIndexing.hpp b/src/axom/bump/views/StridedStructuredIndexing.hpp index 91fc40c333..fcb8eafc6c 100644 --- a/src/axom/bump/views/StridedStructuredIndexing.hpp +++ b/src/axom/bump/views/StridedStructuredIndexing.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_STRIDED_STRUCTURED_INDEXING_HPP_ -#define AXOM_BUMP_STRIDED_STRUCTURED_INDEXING_HPP_ +#pragma once #include "axom/core/StackArray.hpp" #include "axom/core/ArrayView.hpp" @@ -419,5 +418,3 @@ struct StridedStructuredIndexing } // end namespace views } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/views/StructuredIndexing.hpp b/src/axom/bump/views/StructuredIndexing.hpp index 541088fc6f..7192800df3 100644 --- a/src/axom/bump/views/StructuredIndexing.hpp +++ b/src/axom/bump/views/StructuredIndexing.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_STRUCTURED_INDEXING_HPP_ -#define AXOM_BUMP_STRUCTURED_INDEXING_HPP_ +#pragma once #include "axom/core/StackArray.hpp" #include "axom/core/ArrayView.hpp" @@ -317,5 +316,3 @@ class StructuredIndexing } // end namespace views } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/views/StructuredTopologyView.hpp b/src/axom/bump/views/StructuredTopologyView.hpp index 83ed122820..9ab0ef2633 100644 --- a/src/axom/bump/views/StructuredTopologyView.hpp +++ b/src/axom/bump/views/StructuredTopologyView.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_VIEWS_STRUCTURED_TOPOLOGY_VIEW_HPP_ -#define AXOM_BUMP_VIEWS_STRUCTURED_TOPOLOGY_VIEW_HPP_ +#pragma once #include "axom/core.hpp" #include "axom/slic.hpp" @@ -206,5 +205,3 @@ class StructuredTopologyView } // end namespace views } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/views/UniformCoordsetView.hpp b/src/axom/bump/views/UniformCoordsetView.hpp index 422ff71c33..7b3fafe665 100644 --- a/src/axom/bump/views/UniformCoordsetView.hpp +++ b/src/axom/bump/views/UniformCoordsetView.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_UNIFORM_COORDSET_VIEW_HPP_ -#define AXOM_BUMP_UNIFORM_COORDSET_VIEW_HPP_ +#pragma once #include "axom/core/StackArray.hpp" #include "axom/core/ArrayView.hpp" @@ -139,5 +138,3 @@ class UniformCoordsetView } // end namespace views } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/views/UnstructuredTopologyMixedShapeView.hpp b/src/axom/bump/views/UnstructuredTopologyMixedShapeView.hpp index 4b96ea99c0..37272af989 100644 --- a/src/axom/bump/views/UnstructuredTopologyMixedShapeView.hpp +++ b/src/axom/bump/views/UnstructuredTopologyMixedShapeView.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_VIEWS_UNSTRUCTURED_TOPOLOGY_MIXED_SHAPE_VIEW_HPP_ -#define AXOM_BUMP_VIEWS_UNSTRUCTURED_TOPOLOGY_MIXED_SHAPE_VIEW_HPP_ +#pragma once #include "axom/core.hpp" #include "axom/slic.hpp" @@ -199,5 +198,3 @@ class UnstructuredTopologyMixedShapeView } // end namespace views } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/views/UnstructuredTopologyPolyhedralView.hpp b/src/axom/bump/views/UnstructuredTopologyPolyhedralView.hpp index 706b87ec1e..d18a80f7e9 100644 --- a/src/axom/bump/views/UnstructuredTopologyPolyhedralView.hpp +++ b/src/axom/bump/views/UnstructuredTopologyPolyhedralView.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_VIEWS_UNSTRUCTURED_TOPOLOGY_POLYHEDRAL_VIEW_HPP_ -#define AXOM_BUMP_VIEWS_UNSTRUCTURED_TOPOLOGY_POLYHEDRAL_VIEW_HPP_ +#pragma once #include "axom/core.hpp" #include "axom/slic.hpp" @@ -283,5 +282,3 @@ class UnstructuredTopologyPolyhedralView } // end namespace views } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/views/UnstructuredTopologySingleShapeView.hpp b/src/axom/bump/views/UnstructuredTopologySingleShapeView.hpp index f1389e9fbf..ba555274f1 100644 --- a/src/axom/bump/views/UnstructuredTopologySingleShapeView.hpp +++ b/src/axom/bump/views/UnstructuredTopologySingleShapeView.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_VIEWS_UNSTRUCTURED_TOPOLOGY_SINGLE_SHAPE_VIEW_HPP_ -#define AXOM_BUMP_VIEWS_UNSTRUCTURED_TOPOLOGY_SINGLE_SHAPE_VIEW_HPP_ +#pragma once #include "axom/core.hpp" #include "axom/slic.hpp" @@ -159,5 +158,3 @@ class UnstructuredTopologySingleShapeView } // end namespace views } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/views/dispatch_coordset.hpp b/src/axom/bump/views/dispatch_coordset.hpp index a08fdce653..97795fcce6 100644 --- a/src/axom/bump/views/dispatch_coordset.hpp +++ b/src/axom/bump/views/dispatch_coordset.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_DISPATCH_COORDSET_HPP_ -#define AXOM_BUMP_DISPATCH_COORDSET_HPP_ +#pragma once #include "axom/bump/utilities/conduit_memory.hpp" #include "axom/bump/views/dispatch_utilities.hpp" @@ -348,5 +347,3 @@ void dispatch_coordset(const conduit::Node &coordset, FuncType &&func) } // end namespace views } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/views/dispatch_material.hpp b/src/axom/bump/views/dispatch_material.hpp index c8187c6366..ab314b2461 100644 --- a/src/axom/bump/views/dispatch_material.hpp +++ b/src/axom/bump/views/dispatch_material.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_DISPATCH_MATERIAL_HPP_ -#define AXOM_BUMP_DISPATCH_MATERIAL_HPP_ +#pragma once #include "axom/bump/views/MaterialView.hpp" #include "axom/bump/views/NodeArrayView.hpp" @@ -362,5 +361,3 @@ bool dispatch_material(const conduit::Node &matset, FuncType &&func) } // end namespace views } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/views/dispatch_material_field.hpp b/src/axom/bump/views/dispatch_material_field.hpp index 3cabd66750..da4daea4be 100644 --- a/src/axom/bump/views/dispatch_material_field.hpp +++ b/src/axom/bump/views/dispatch_material_field.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_DISPATCH_MATERIAL_FIELD_HPP_ -#define AXOM_BUMP_DISPATCH_MATERIAL_FIELD_HPP_ +#pragma once #include "axom/bump/views/dispatch_material.hpp" #include "axom/bump/views/MixedFieldView.hpp" @@ -228,5 +227,3 @@ bool dispatch_material_field(const conduit::Node &matset, const conduit::Node &n } // end namespace views } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/views/dispatch_rectilinear_topology.hpp b/src/axom/bump/views/dispatch_rectilinear_topology.hpp index e5d900a87b..d4285a10cc 100644 --- a/src/axom/bump/views/dispatch_rectilinear_topology.hpp +++ b/src/axom/bump/views/dispatch_rectilinear_topology.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_DISPATCH_RECTILINEAR_TOPOLOGY_HPP_ -#define AXOM_BUMP_DISPATCH_RECTILINEAR_TOPOLOGY_HPP_ +#pragma once #include "axom/bump/views/StructuredTopologyView.hpp" #include "axom/bump/views/StructuredIndexing.hpp" @@ -255,5 +254,3 @@ void dispatch_rectilinear_topology(const conduit::Node &topo, FuncType &&func) } // end namespace views } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/views/dispatch_structured_topology.hpp b/src/axom/bump/views/dispatch_structured_topology.hpp index a84573d17e..d87c736595 100644 --- a/src/axom/bump/views/dispatch_structured_topology.hpp +++ b/src/axom/bump/views/dispatch_structured_topology.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_DISPATCH_STRUCTURED_TOPOLOGY_HPP_ -#define AXOM_BUMP_DISPATCH_STRUCTURED_TOPOLOGY_HPP_ +#pragma once #include "axom/core.hpp" #include "axom/bump/views/StructuredTopologyView.hpp" @@ -607,5 +606,3 @@ void dispatch_structured_topologies(const conduit::Node &topo, FuncType &&func) } // end namespace views } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/views/dispatch_topology.hpp b/src/axom/bump/views/dispatch_topology.hpp index 013e3d9c2e..a9a208917d 100644 --- a/src/axom/bump/views/dispatch_topology.hpp +++ b/src/axom/bump/views/dispatch_topology.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_DISPATCH_TOPOLOGY_HPP_ -#define AXOM_BUMP_DISPATCH_TOPOLOGY_HPP_ +#pragma once #include "axom/bump/views/StructuredTopologyView.hpp" #include "axom/bump/views/dispatch_uniform_topology.hpp" @@ -52,5 +51,3 @@ void dispatch_topology(const conduit::Node &topo, FuncType &&func) } // end namespace views } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/views/dispatch_uniform_topology.hpp b/src/axom/bump/views/dispatch_uniform_topology.hpp index 24f441d98c..acb9ee361b 100644 --- a/src/axom/bump/views/dispatch_uniform_topology.hpp +++ b/src/axom/bump/views/dispatch_uniform_topology.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_DISPATCH_UNIFORM_TOPOLOGY_HPP_ -#define AXOM_BUMP_DISPATCH_UNIFORM_TOPOLOGY_HPP_ +#pragma once #include "axom/bump/views/StructuredTopologyView.hpp" #include "axom/bump/views/dispatch_utilities.hpp" @@ -254,5 +253,3 @@ void dispatch_uniform_topology(const conduit::Node &topo, FuncType &&func) } // end namespace views } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/views/dispatch_unstructured_topology.hpp b/src/axom/bump/views/dispatch_unstructured_topology.hpp index a9c5610748..b03bbfe10a 100644 --- a/src/axom/bump/views/dispatch_unstructured_topology.hpp +++ b/src/axom/bump/views/dispatch_unstructured_topology.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_DISPATCH_UNSTRUCTURED_TOPOLOGY_HPP_ -#define AXOM_BUMP_DISPATCH_UNSTRUCTURED_TOPOLOGY_HPP_ +#pragma once #include "axom/core.hpp" #include "axom/bump/views/UnstructuredTopologySingleShapeView.hpp" @@ -607,5 +606,3 @@ void dispatch_unstructured_topology(const conduit::Node &topo, FuncType &&func) } // end namespace views } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/views/dispatch_utilities.hpp b/src/axom/bump/views/dispatch_utilities.hpp index 46330490f5..cf7ef5c019 100644 --- a/src/axom/bump/views/dispatch_utilities.hpp +++ b/src/axom/bump/views/dispatch_utilities.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_DISPATCH_UTILITIES_HPP_ -#define AXOM_BUMP_DISPATCH_UTILITIES_HPP_ +#pragma once #include @@ -42,5 +41,3 @@ void verify(const conduit::Node &obj, const std::string &protocol = std::string( } // end namespace views } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/views/view_traits.hpp b/src/axom/bump/views/view_traits.hpp index 7ce79e762d..b8d3903135 100644 --- a/src/axom/bump/views/view_traits.hpp +++ b/src/axom/bump/views/view_traits.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_VIEW_TRAITS_HPP_ -#define AXOM_BUMP_VIEW_TRAITS_HPP_ +#pragma once #include "axom/core/utilities/BitUtilities.hpp" #include "axom/bump/views/StructuredTopologyView.hpp" @@ -120,5 +119,3 @@ AXOM_MAKE_TRAIT(double) } // end namespace views } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/core/AnnotationMacros.hpp b/src/axom/core/AnnotationMacros.hpp index 79f9bebff5..50e78cea12 100644 --- a/src/axom/core/AnnotationMacros.hpp +++ b/src/axom/core/AnnotationMacros.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_ANNOTATION_MACROS_HPP_ -#define AXOM_ANNOTATION_MACROS_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core/utilities/Annotations.hpp" @@ -56,5 +55,3 @@ */ #define AXOM_ANNOTATE_METADATA(name, value, category) \ axom::utilities::annotations::declare_metadata(name, value, category) - -#endif // AXOM_ANNOTATION_MACROS_HPP_ diff --git a/src/axom/core/Array.hpp b/src/axom/core/Array.hpp index 09597099f8..9e1732b660 100644 --- a/src/axom/core/Array.hpp +++ b/src/axom/core/Array.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_ARRAY_HPP_ -#define AXOM_ARRAY_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core/MDMapping.hpp" @@ -1891,5 +1890,3 @@ inline void Array::dynamicRealloc(IndexType new_nu } } /* namespace axom */ - -#endif /* AXOM_ARRAY_HPP_ */ diff --git a/src/axom/core/ArrayBase.hpp b/src/axom/core/ArrayBase.hpp index 387a76d65d..b724fd3e46 100644 --- a/src/axom/core/ArrayBase.hpp +++ b/src/axom/core/ArrayBase.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_ARRAYBASE_HPP_ -#define AXOM_ARRAYBASE_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core/Macros.hpp" @@ -1393,5 +1392,3 @@ class ArraySubslice : public ArrayBase::assign(std::initializer_list elems) } } /* namespace axom */ - -#endif /* AXOM_ARRAYVIEW_HPP_ */ diff --git a/src/axom/core/DeviceHash.hpp b/src/axom/core/DeviceHash.hpp index 0dc1bb4caa..ff24e5cb41 100644 --- a/src/axom/core/DeviceHash.hpp +++ b/src/axom/core/DeviceHash.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef Axom_Core_DeviceHash_Hpp -#define Axom_Core_DeviceHash_Hpp +#pragma once #include "axom/config.hpp" #include "axom/core/Macros.hpp" @@ -125,5 +124,3 @@ struct DeviceHash : public detail::DeviceHashHelper }; } // namespace axom - -#endif diff --git a/src/axom/core/FlatMap.hpp b/src/axom/core/FlatMap.hpp index be1958a9bf..9dea0c875d 100644 --- a/src/axom/core/FlatMap.hpp +++ b/src/axom/core/FlatMap.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef Axom_Core_FlatMap_HPP -#define Axom_Core_FlatMap_HPP +#pragma once #include #include @@ -992,5 +991,3 @@ auto FlatMap::erase(const_iterator pos) -> iterator } // namespace axom #include "FlatMapUtil.hpp" - -#endif // Axom_Core_FlatMap_HPP diff --git a/src/axom/core/FlatMapUtil.hpp b/src/axom/core/FlatMapUtil.hpp index 24cb9e38d6..a235c84268 100644 --- a/src/axom/core/FlatMapUtil.hpp +++ b/src/axom/core/FlatMapUtil.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef Axom_Core_FlatMap_Util_HPP -#define Axom_Core_FlatMap_Util_HPP +#pragma once #include "axom/config.hpp" #include "axom/core/FlatMap.hpp" @@ -516,5 +515,3 @@ void FlatMap::insert(InputIt kv_begin, InputIt kv_end) } } // namespace axom - -#endif diff --git a/src/axom/core/FlatMapView.hpp b/src/axom/core/FlatMapView.hpp index 4d7ea5730f..03f6d3b219 100644 --- a/src/axom/core/FlatMapView.hpp +++ b/src/axom/core/FlatMapView.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef Axom_Core_FlatMap_View_HPP -#define Axom_Core_FlatMap_View_HPP +#pragma once #include "axom/core/FlatMap.hpp" @@ -292,5 +291,3 @@ auto FlatMap::view() const -> ConstView } } // namespace axom - -#endif diff --git a/src/axom/core/IndexedCollection.hpp b/src/axom/core/IndexedCollection.hpp index ea3399069f..0022066489 100644 --- a/src/axom/core/IndexedCollection.hpp +++ b/src/axom/core/IndexedCollection.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef SIDRE_INDEXED_COLLECTION_HPP_ -#define SIDRE_INDEXED_COLLECTION_HPP_ +#pragma once // Standard C++ headers #include @@ -280,5 +279,3 @@ T* IndexedCollection::removeItem(IndexType idx) } } // end namespace axom - -#endif // SIDRE_INDEXED_COLLECTION_HPP_ diff --git a/src/axom/core/ItemCollection.hpp b/src/axom/core/ItemCollection.hpp index cf61856baf..2a65ab49bf 100644 --- a/src/axom/core/ItemCollection.hpp +++ b/src/axom/core/ItemCollection.hpp @@ -78,8 +78,7 @@ ****************************************************************************** */ -#ifndef AXOM_ITEMCOLLECTIONS_HPP_ -#define AXOM_ITEMCOLLECTIONS_HPP_ +#pragma once #include @@ -322,5 +321,3 @@ class ItemCollection::const_iterator_adaptor }; } /* end namespace axom */ - -#endif /* AXOM_ITEMCOLLECTIONS_HPP_ */ diff --git a/src/axom/core/IteratorBase.hpp b/src/axom/core/IteratorBase.hpp index 56aa107b54..c4d647f58b 100644 --- a/src/axom/core/IteratorBase.hpp +++ b/src/axom/core/IteratorBase.hpp @@ -10,8 +10,7 @@ * \brief Contains iterator base classes */ -#ifndef AXOM_ITERBASE_HPP_ -#define AXOM_ITERBASE_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core/Macros.hpp" @@ -204,5 +203,3 @@ class IteratorBase }; } // end namespace axom - -#endif // AXOM_ITERBASE_HPP_ diff --git a/src/axom/core/ListCollection.hpp b/src/axom/core/ListCollection.hpp index ce53b36fc9..213e7f1c85 100644 --- a/src/axom/core/ListCollection.hpp +++ b/src/axom/core/ListCollection.hpp @@ -76,8 +76,7 @@ ****************************************************************************** */ -#ifndef AXOM_LISTCOLLECTIONS_HPP_ -#define AXOM_LISTCOLLECTIONS_HPP_ +#pragma once // Standard C++ headers #include @@ -277,5 +276,3 @@ T* ListCollection::removeItem(IndexType idx) } } /* end namespace axom */ - -#endif /* AXOM_LIST_COLLECTIONS_HPP_ */ diff --git a/src/axom/core/MDMapping.hpp b/src/axom/core/MDMapping.hpp index 164dad72b6..538c2aff80 100644 --- a/src/axom/core/MDMapping.hpp +++ b/src/axom/core/MDMapping.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_MDMAPPING_HPP_ -#define AXOM_MDMAPPING_HPP_ +#pragma once #include "axom/core/StackArray.hpp" #include "axom/core/numerics/matvecops.hpp" @@ -393,5 +392,3 @@ class MDMapping }; } // end namespace axom - -#endif // AXOM_MDMAPPING_HPP_ diff --git a/src/axom/core/Macros.hpp b/src/axom/core/Macros.hpp index c6e94138b7..011a0bf9c0 100644 --- a/src/axom/core/Macros.hpp +++ b/src/axom/core/Macros.hpp @@ -10,8 +10,7 @@ * \brief Contains several useful macros for the axom project */ -#ifndef AXOM_MACROS_HPP_ -#define AXOM_MACROS_HPP_ +#pragma once #include "axom/config.hpp" #include // for assert() @@ -425,4 +424,3 @@ */ #define AXOM_CONSTEXPR_ASSERT(EXP) ::axom::detail::constexprAssert((EXP), #EXP, __FILE__, __LINE__) -#endif // AXOM_MACROS_HPP_ diff --git a/src/axom/core/Map.hpp b/src/axom/core/Map.hpp index 0b5dca93db..c6d6c04d57 100644 --- a/src/axom/core/Map.hpp +++ b/src/axom/core/Map.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_MAP_HPP_ -#define AXOM_MAP_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core/Macros.hpp" @@ -926,5 +925,3 @@ class Map }; } /* namespace experimental */ } /* namespace axom */ - -#endif /* AXOM_MAP_HPP_ */ diff --git a/src/axom/core/MapCollection.hpp b/src/axom/core/MapCollection.hpp index 1342daba7b..7d69ca2e8c 100644 --- a/src/axom/core/MapCollection.hpp +++ b/src/axom/core/MapCollection.hpp @@ -106,8 +106,7 @@ ****************************************************************************** */ -#ifndef AXOM_MAP_COLLECTIONS_HPP_ -#define AXOM_MAP_COLLECTIONS_HPP_ +#pragma once // Standard C++ headers #include @@ -429,5 +428,3 @@ T* MapCollection::removeItem(IndexType idx) } } // namespace axom - -#endif // AXOM_MAP_COLLECTIONS_HPP_ diff --git a/src/axom/core/NumericArray.hpp b/src/axom/core/NumericArray.hpp index 8f25767f2e..c8da6ab75f 100644 --- a/src/axom/core/NumericArray.hpp +++ b/src/axom/core/NumericArray.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_PRIMAL_NUMERIC_ARRAY_HPP_ -#define AXOM_PRIMAL_NUMERIC_ARRAY_HPP_ +#pragma once #include "axom/core/Macros.hpp" #include "axom/core/utilities/Utilities.hpp" @@ -805,5 +804,3 @@ AXOM_HOST_DEVICE inline NumericArray abs(const NumericArray& a template struct axom::fmt::formatter> : ostream_formatter { }; - -#endif // AXOM_PRIMAL_NUMERIC_ARRAY_HPP_ diff --git a/src/axom/core/NumericLimits.hpp b/src/axom/core/NumericLimits.hpp index 513c372c54..0bb0460ddb 100644 --- a/src/axom/core/NumericLimits.hpp +++ b/src/axom/core/NumericLimits.hpp @@ -13,8 +13,7 @@ * */ -#ifndef AXOM_NUMERICLIMITS_HPP_ -#define AXOM_NUMERICLIMITS_HPP_ +#pragma once #include "axom/config.hpp" // for compile-time definitions @@ -38,5 +37,3 @@ using numeric_limits = std::numeric_limits; #endif } // namespace axom - -#endif // AXOM_NUMERICLIMITS_HPP_ diff --git a/src/axom/core/Path.hpp b/src/axom/core/Path.hpp index 8106fdd31b..b27b579c29 100644 --- a/src/axom/core/Path.hpp +++ b/src/axom/core/Path.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_CORE_PATH_H_ -#define AXOM_CORE_PATH_H_ +#pragma once #include #include @@ -117,5 +116,3 @@ bool operator==(const Path& lhs, const Path& rhs); inline bool operator!=(const Path& lhs, const Path& rhs) { return !(lhs == rhs); } } // end namespace axom - -#endif // AXOM_CORE_PATH_H_ diff --git a/src/axom/core/RangeAdapter.hpp b/src/axom/core/RangeAdapter.hpp index bc3e604b7e..db8f2594c2 100644 --- a/src/axom/core/RangeAdapter.hpp +++ b/src/axom/core/RangeAdapter.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef Axom_Core_RangeAdapter_HPP -#define Axom_Core_RangeAdapter_HPP +#pragma once namespace axom { @@ -31,5 +30,3 @@ class RangeAdapter }; } // namespace axom - -#endif diff --git a/src/axom/core/StackArray.hpp b/src/axom/core/StackArray.hpp index 24aa534264..a41bce9747 100644 --- a/src/axom/core/StackArray.hpp +++ b/src/axom/core/StackArray.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_STACKARRAY_HPP_ -#define AXOM_STACKARRAY_HPP_ +#pragma once #include "axom/config.hpp" // for compile-time defines #include "axom/core/Macros.hpp" // for axom macros @@ -166,5 +165,3 @@ std::ostream& operator<<(std::ostream& os, const StackArray& obj) } } /* namespace axom */ - -#endif /* AXOM_STACKARRAY_HPP_ */ diff --git a/src/axom/core/StaticArray.hpp b/src/axom/core/StaticArray.hpp index 303bd6f10d..9205886500 100644 --- a/src/axom/core/StaticArray.hpp +++ b/src/axom/core/StaticArray.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_STATICARRAY_HPP_ -#define AXOM_STATICARRAY_HPP_ +#pragma once #include "axom/config.hpp" // for compile-time defines #include "axom/core/Macros.hpp" // for axom macros @@ -163,5 +162,3 @@ class StaticArray : public StackArray }; } /* namespace axom */ - -#endif /* AXOM_STATICARRAY_HPP_ */ diff --git a/src/axom/core/Types.hpp b/src/axom/core/Types.hpp index 45320e288c..06c102c71f 100644 --- a/src/axom/core/Types.hpp +++ b/src/axom/core/Types.hpp @@ -10,8 +10,7 @@ * \brief Exposes some common types used by axom components. */ -#ifndef AXOM_TYPES_HPP_ -#define AXOM_TYPES_HPP_ +#pragma once // Axom includes #include "axom/config.hpp" @@ -212,5 +211,3 @@ struct mpi_traits #endif // AXOM_USE_MPI } // end namespace axom - -#endif // AXOM_TYPES_HPP_ diff --git a/src/axom/core/detail/FlatMapOps.hpp b/src/axom/core/detail/FlatMapOps.hpp index d28d289e82..3ae2dea360 100644 --- a/src/axom/core/detail/FlatMapOps.hpp +++ b/src/axom/core/detail/FlatMapOps.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef Axom_Core_Detail_FlatMapOps_Hpp -#define Axom_Core_Detail_FlatMapOps_Hpp +#pragma once #include "axom/core/detail/FlatTable.hpp" @@ -137,5 +136,3 @@ inline void copyBuckets(axom::ArrayView metadata, } // namespace flat_map } // namespace detail } // namespace axom - -#endif diff --git a/src/axom/core/detail/FlatTable.hpp b/src/axom/core/detail/FlatTable.hpp index 34ec9c4167..72fef04ead 100644 --- a/src/axom/core/detail/FlatTable.hpp +++ b/src/axom/core/detail/FlatTable.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef Axom_Core_Detail_FlatTable_Hpp -#define Axom_Core_Detail_FlatTable_Hpp +#pragma once #include @@ -582,5 +581,3 @@ struct alignas(T) TypeErasedStorage } // namespace axom #undef _AXOM_CORE_HAVE_SSE2 - -#endif // Axom_Core_Detail_FlatTable_Hpp diff --git a/src/axom/core/execution/atomics.hpp b/src/axom/core/execution/atomics.hpp index e342d3374d..83babbb87e 100644 --- a/src/axom/core/execution/atomics.hpp +++ b/src/axom/core/execution/atomics.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_CORE_EXECUTION_ATOMICS_HPP_ -#define AXOM_CORE_EXECUTION_ATOMICS_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core/execution/execution_space.hpp" @@ -239,5 +238,3 @@ inline AXOM_HOST_DEVICE void atomicStore(T* address, T value) } // namespace axom #endif // AXOM_HAVE_RAJA - -#endif diff --git a/src/axom/core/execution/execution_space.hpp b/src/axom/core/execution/execution_space.hpp index 554bb9c132..70ccd80a6e 100644 --- a/src/axom/core/execution/execution_space.hpp +++ b/src/axom/core/execution/execution_space.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_EXECUTIONSPACE_HPP_ -#define AXOM_EXECUTIONSPACE_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core/memory_management.hpp" @@ -152,5 +151,3 @@ inline int policyToDefaultAllocatorID(axom::runtime_policy::Policy policy) } } // namespace axom - -#endif // AXOM_EXECUTIONSPACE_HPP_ diff --git a/src/axom/core/execution/for_all.hpp b/src/axom/core/execution/for_all.hpp index d6a629feae..68c52bf9ac 100644 --- a/src/axom/core/execution/for_all.hpp +++ b/src/axom/core/execution/for_all.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_CORE_EXECUTION_FOR_ALL_HPP_ -#define AXOM_CORE_EXECUTION_FOR_ALL_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core/execution/execution_space.hpp" @@ -306,5 +305,3 @@ inline void for_all(const StackArray &shape, KernelType &&kernel) /// @} } // namespace axom - -#endif // AXOM_CORE_EXECUTION_FOR_ALL_HPP_ diff --git a/src/axom/core/execution/internal/cuda_exec.hpp b/src/axom/core/execution/internal/cuda_exec.hpp index 8e8b7dca35..b539bcfdaf 100644 --- a/src/axom/core/execution/internal/cuda_exec.hpp +++ b/src/axom/core/execution/internal/cuda_exec.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_CUDA_EXEC_HPP_ -#define AXOM_CUDA_EXEC_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core/memory_management.hpp" @@ -123,5 +122,3 @@ struct execution_space> } }; } // namespace axom - -#endif // AXOM_CUDA_EXEC_HPP_ diff --git a/src/axom/core/execution/internal/hip_exec.hpp b/src/axom/core/execution/internal/hip_exec.hpp index c5e30ce9cd..c552c89a81 100644 --- a/src/axom/core/execution/internal/hip_exec.hpp +++ b/src/axom/core/execution/internal/hip_exec.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_HIP_EXEC_HPP_ -#define AXOM_HIP_EXEC_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core/memory_management.hpp" @@ -121,5 +120,3 @@ struct execution_space> } }; } // namespace axom - -#endif // AXOM_HIP_EXEC_HPP_ diff --git a/src/axom/core/execution/internal/omp_exec.hpp b/src/axom/core/execution/internal/omp_exec.hpp index 6b52ecdbce..094322d95c 100644 --- a/src/axom/core/execution/internal/omp_exec.hpp +++ b/src/axom/core/execution/internal/omp_exec.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_OMP_EXEC_HPP_ -#define AXOM_OMP_EXEC_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core/memory_management.hpp" @@ -82,5 +81,3 @@ struct execution_space }; } // namespace axom - -#endif // AXOM_OMP_EXEC_HPP_ diff --git a/src/axom/core/execution/internal/seq_exec.hpp b/src/axom/core/execution/internal/seq_exec.hpp index ceedaffdea..e4179ce20e 100644 --- a/src/axom/core/execution/internal/seq_exec.hpp +++ b/src/axom/core/execution/internal/seq_exec.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_SEQ_EXEC_HPP_ -#define AXOM_SEQ_EXEC_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core/memory_management.hpp" @@ -91,5 +90,3 @@ struct execution_space }; } // namespace axom - -#endif // AXOM_SEQ_EXEC_HPP_ diff --git a/src/axom/core/execution/nested_for_exec.hpp b/src/axom/core/execution/nested_for_exec.hpp index cb0f7fb9b6..a72163f322 100644 --- a/src/axom/core/execution/nested_for_exec.hpp +++ b/src/axom/core/execution/nested_for_exec.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_CORE_NESTED_FOR_EXEC_HPP_ -#define AXOM_CORE_NESTED_FOR_EXEC_HPP_ +#pragma once #include "axom/core/execution/execution_space.hpp" @@ -307,5 +306,3 @@ struct nested_for_exec> } /* namespace internal */ } /* namespace axom */ - -#endif /* AXOM_CORE_NESTED_FOR_EXEC_HPP_ */ diff --git a/src/axom/core/execution/reductions.hpp b/src/axom/core/execution/reductions.hpp index 696a123711..fedd16bdf5 100644 --- a/src/axom/core/execution/reductions.hpp +++ b/src/axom/core/execution/reductions.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_CORE_EXECUTION_REDUCTIONS_HPP_ -#define AXOM_CORE_EXECUTION_REDUCTIONS_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core/execution/execution_space.hpp" @@ -351,5 +350,3 @@ using ReduceBitOr = axom::serial::reductions::ReduceBitOr; } // namespace axom #endif // AXOM_HAVE_RAJA - -#endif diff --git a/src/axom/core/execution/runtime_policy.hpp b/src/axom/core/execution/runtime_policy.hpp index bb63f0f9e1..67f154c15a 100644 --- a/src/axom/core/execution/runtime_policy.hpp +++ b/src/axom/core/execution/runtime_policy.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_CORE_EXECUTION_RUNTIME_POLICY_HPP_ -#define AXOM_CORE_EXECUTION_RUNTIME_POLICY_HPP_ +#pragma once #include "axom/config.hpp" /* for compile time defs. */ #include @@ -119,5 +118,3 @@ static inline auto format_as(Policy pol) { return axom::fmt::underlying(pol); } } // end namespace runtime_policy } // end namespace axom - -#endif /* AXOM_CORE_EXECUTION_RUNTIME_POLICY_HPP_ */ diff --git a/src/axom/core/execution/scans.hpp b/src/axom/core/execution/scans.hpp index 442677be98..ce9d1be99d 100644 --- a/src/axom/core/execution/scans.hpp +++ b/src/axom/core/execution/scans.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_CORE_EXECUTION_SCANS_HPP_ -#define AXOM_CORE_EXECUTION_SCANS_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core/execution/execution_space.hpp" @@ -229,5 +228,3 @@ inline void inclusive_scan_inplace(Container &&input) /// @} } // namespace axom - -#endif // AXOM_CORE_EXECUTION_FOR_ALL_HPP_ diff --git a/src/axom/core/execution/sorts.hpp b/src/axom/core/execution/sorts.hpp index 0727786262..a36dc29e23 100644 --- a/src/axom/core/execution/sorts.hpp +++ b/src/axom/core/execution/sorts.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_CORE_EXECUTION_SORTS_HPP_ -#define AXOM_CORE_EXECUTION_SORTS_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core/execution/execution_space.hpp" @@ -154,4 +153,3 @@ inline void stable_sort_pairs(Container1 &input1, Container2 &input2) } } // namespace axom -#endif diff --git a/src/axom/core/execution/synchronize.hpp b/src/axom/core/execution/synchronize.hpp index bad595d2c2..1e12417f98 100644 --- a/src/axom/core/execution/synchronize.hpp +++ b/src/axom/core/execution/synchronize.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_CORE_EXECUTION_SYNCHRONIZE_HPP_ -#define AXOM_CORE_EXECUTION_SYNCHRONIZE_HPP_ +#pragma once #include "axom/config.hpp" /* for compile time defs. */ #include "axom/core/Macros.hpp" /* for AXOM_STATIC_ASSERT */ @@ -37,5 +36,3 @@ inline void synchronize() noexcept { } } // namespace axom - -#endif /* AXOM_CORE_EXECUTION_SYNCHRONIZE_HPP_ */ diff --git a/src/axom/core/execution/timed_for_all.hpp b/src/axom/core/execution/timed_for_all.hpp index 0ea013264e..af49674de3 100644 --- a/src/axom/core/execution/timed_for_all.hpp +++ b/src/axom/core/execution/timed_for_all.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_CORE_EXECUTION_TIMED_FOR_ALL_HPP_ -#define AXOM_CORE_EXECUTION_TIMED_FOR_ALL_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core/execution/for_all.hpp" @@ -129,5 +128,3 @@ void timed_for_all(const std::string &name, axom::IndexType n, KernelType &&kern /// @} } // namespace axom - -#endif // AXOM_CORE_EXECUTION_TIMED_FOR_ALL_HPP_ diff --git a/src/axom/core/memory_management.hpp b/src/axom/core/memory_management.hpp index c382c337b0..5f6fd8bd41 100644 --- a/src/axom/core/memory_management.hpp +++ b/src/axom/core/memory_management.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_MEMORYMANAGEMENT_HPP_ -#define AXOM_MEMORYMANAGEMENT_HPP_ +#pragma once // Axom includes #include "axom/config.hpp" @@ -692,5 +691,3 @@ inline bool isDeviceAllocator(int AXOM_UNUSED_PARAM(allocator_id)) { return fals inline MemorySpace Allocator::getSpace() const { return axom::detail::getAllocatorSpace(m_id); } } // namespace axom - -#endif /* AXOM_MEMORYMANAGEMENT_HPP_ */ diff --git a/src/axom/core/numerics/Determinants.hpp b/src/axom/core/numerics/Determinants.hpp index 8d242416fa..2a7bf918ec 100644 --- a/src/axom/core/numerics/Determinants.hpp +++ b/src/axom/core/numerics/Determinants.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_NUMERICS_DETERMINANTS_HPP_ -#define AXOM_NUMERICS_DETERMINANTS_HPP_ +#pragma once #include "axom/core/numerics/LU.hpp" // for lu_decompose() #include "axom/core/numerics/Matrix.hpp" // for Matrix @@ -199,5 +198,3 @@ real determinant(const Matrix& A) } /* namespace numerics */ } /* namespace axom */ - -#endif diff --git a/src/axom/core/numerics/LU.hpp b/src/axom/core/numerics/LU.hpp index 582226bc74..084a5e26a2 100644 --- a/src/axom/core/numerics/LU.hpp +++ b/src/axom/core/numerics/LU.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_NUMERICS_LU_HPP_ -#define AXOM_NUMERICS_LU_HPP_ +#pragma once #include "axom/core/utilities/Utilities.hpp" // NearlyEqual(), swap() and abs() #include "axom/core/memory_management.hpp" // alloc() and free() @@ -201,5 +200,3 @@ int lu_solve(const Matrix& A, const int* pivots, const T* b, T* x) } /* end namespace numerics */ } /* end namespace axom */ - -#endif /* AXOM_NUMERICS_LU_HPP_ */ diff --git a/src/axom/core/numerics/Matrix.hpp b/src/axom/core/numerics/Matrix.hpp index e2cf28b14a..eddcfc3af3 100644 --- a/src/axom/core/numerics/Matrix.hpp +++ b/src/axom/core/numerics/Matrix.hpp @@ -15,8 +15,7 @@ #include #include -#ifndef AXOM_MATRIX_HPP_ - #define AXOM_MATRIX_HPP_ +#pragma once namespace axom { @@ -1095,5 +1094,3 @@ std::ostream& operator<<(std::ostream& os, const Matrix& M) template struct axom::fmt::formatter> : ostream_formatter { }; - -#endif /* AXOM_MATRIX_HPP_ */ diff --git a/src/axom/core/numerics/eigen_solve.hpp b/src/axom/core/numerics/eigen_solve.hpp index b02f8b198f..a864be1130 100644 --- a/src/axom/core/numerics/eigen_solve.hpp +++ b/src/axom/core/numerics/eigen_solve.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_NUMERICS_EIGEN_SOLVE_HPP_ -#define AXOM_NUMERICS_EIGEN_SOLVE_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core/numerics/matvecops.hpp" // for matrix vector operators @@ -169,5 +168,3 @@ int eigen_solve(Matrix& A, int k, T* u, T* lambdas, int numIterations) } /* end namespace numerics */ } /* end namespace axom */ - -#endif diff --git a/src/axom/core/numerics/eigen_sort.hpp b/src/axom/core/numerics/eigen_sort.hpp index bad9478999..99cca99890 100644 --- a/src/axom/core/numerics/eigen_sort.hpp +++ b/src/axom/core/numerics/eigen_sort.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_EIGEN_SORT_HPP_ -#define AXOM_EIGEN_SORT_HPP_ +#pragma once // Axom includes #include "axom/core/utilities/Utilities.hpp" // for utilities::swap() @@ -75,5 +74,3 @@ bool eigen_sort(T* lambdas, Matrix& eigen_vectors) } /* end namespace numerics */ } /* end namespace axom */ - -#endif /* AXOM_EIGEN_SORT_HPP_ */ diff --git a/src/axom/core/numerics/floating_point_limits.hpp b/src/axom/core/numerics/floating_point_limits.hpp index b4c1ca97e6..80920db7a3 100644 --- a/src/axom/core/numerics/floating_point_limits.hpp +++ b/src/axom/core/numerics/floating_point_limits.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_NUMERICS_FLOATING_POINT_LIMITS_HPP_ -#define AXOM_NUMERICS_FLOATING_POINT_LIMITS_HPP_ +#pragma once #include "axom/core/Macros.hpp" // for Axom macros @@ -90,5 +89,3 @@ struct floating_point_limits } /* namespace numerics */ } /* namespace axom */ - -#endif /* AXOM_NUMERICS_FLOATING_POINT_LIMITS_HPP_ */ diff --git a/src/axom/core/numerics/internal/matrix_norms.hpp b/src/axom/core/numerics/internal/matrix_norms.hpp index 85d31abf16..9fb87483af 100644 --- a/src/axom/core/numerics/internal/matrix_norms.hpp +++ b/src/axom/core/numerics/internal/matrix_norms.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_MATRIX_NORMS_HPP_ -#define AXOM_MATRIX_NORMS_HPP_ +#pragma once #include "axom/core/numerics/Matrix.hpp" // for numerics::Matrix #include "axom/core/utilities/Utilities.hpp" // for utilities::abs() @@ -156,5 +155,3 @@ inline T matrix_frobenious_norm(const Matrix& A) } /* end namespace internal */ } /* end namespace numerics */ } /* end namespace axom */ - -#endif /* AXOM_MATRIX_NORMS_HPP_ */ diff --git a/src/axom/core/numerics/jacobi_eigensolve.hpp b/src/axom/core/numerics/jacobi_eigensolve.hpp index d1c35c5eaa..97ed61f7be 100644 --- a/src/axom/core/numerics/jacobi_eigensolve.hpp +++ b/src/axom/core/numerics/jacobi_eigensolve.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_JACOBI_EIGENSOLVE_HPP_ -#define AXOM_JACOBI_EIGENSOLVE_HPP_ +#pragma once #include "axom/core/Macros.hpp" // for AXOM_STATIC_ASSERT @@ -242,5 +241,3 @@ int jacobi_eigensolve(Matrix A, Matrix& V, T* lambdas, int maxIterations, } /* end namespace numerics */ } /* end namespace axom */ - -#endif /* AXOM_JACOBI_EIGENSOLVE_HPP_ */ diff --git a/src/axom/core/numerics/linear_solve.hpp b/src/axom/core/numerics/linear_solve.hpp index 56dadcffb0..ed9bb4aa55 100644 --- a/src/axom/core/numerics/linear_solve.hpp +++ b/src/axom/core/numerics/linear_solve.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_NUMERICS_LINEAR_SOLVE_HPP_ -#define AXOM_NUMERICS_LINEAR_SOLVE_HPP_ +#pragma once #include "axom/core/numerics/Determinants.hpp" // for Determinants #include "axom/core/numerics/LU.hpp" // for lu_decompose()/lu_solve() @@ -105,5 +104,3 @@ int linear_solve(Matrix& A, const T* b, T* x) } /* end namespace numerics */ } /* end namespace axom */ - -#endif diff --git a/src/axom/core/numerics/matvecops.hpp b/src/axom/core/numerics/matvecops.hpp index b619905e0b..ed1d26a109 100644 --- a/src/axom/core/numerics/matvecops.hpp +++ b/src/axom/core/numerics/matvecops.hpp @@ -12,8 +12,7 @@ * */ -#ifndef AXOM_NUMERICS_MATVECOPS_HPP_ -#define AXOM_NUMERICS_MATVECOPS_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core/numerics/Determinants.hpp" // numerics::determinant() @@ -661,5 +660,3 @@ inline AXOM_HOST_DEVICE bool normalize(T* v, int dim, double eps) } /* end namespace numerics */ } /* end namespace axom */ - -#endif /* AXOM_NUMERICS_VECTOR_UTILITIES_HPP_ */ diff --git a/src/axom/core/numerics/polynomial_solvers.hpp b/src/axom/core/numerics/polynomial_solvers.hpp index d8226e632b..1077c0b9df 100644 --- a/src/axom/core/numerics/polynomial_solvers.hpp +++ b/src/axom/core/numerics/polynomial_solvers.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_NUMERICS_POLY_SOLVE_HPP_ -#define AXOM_NUMERICS_POLY_SOLVE_HPP_ +#pragma once #include "axom/core/Array.hpp" #include "axom/core/ArrayView.hpp" @@ -256,5 +255,3 @@ int solve_cubic(const double* coeff, double* roots, int& numRoots); } // namespace numerics } // namespace axom - -#endif // AXOM_NUMERICS_POLY_SOLVE_HPP_ diff --git a/src/axom/core/numerics/quadrature.hpp b/src/axom/core/numerics/quadrature.hpp index b8db612e11..2d892e9d70 100644 --- a/src/axom/core/numerics/quadrature.hpp +++ b/src/axom/core/numerics/quadrature.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_NUMERICS_QUADRATURE_HPP_ -#define AXOM_NUMERICS_QUADRATURE_HPP_ +#pragma once #include "axom/core/Array.hpp" #include "axom/core/memory_management.hpp" @@ -99,5 +98,3 @@ QuadratureRule get_gauss_legendre(int npts, int allocatorID = axom::getDefaultAl } /* end namespace numerics */ } /* end namespace axom */ - -#endif // AXOM_NUMERICS_QUADRATURE_HPP_ diff --git a/src/axom/core/numerics/transforms.hpp b/src/axom/core/numerics/transforms.hpp index efa9d80d1b..5e9a72f849 100644 --- a/src/axom/core/numerics/transforms.hpp +++ b/src/axom/core/numerics/transforms.hpp @@ -3,8 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_NUMERICS_TRANSFORMS_HPP_ -#define AXOM_NUMERICS_TRANSFORMS_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core/numerics/Matrix.hpp" @@ -287,5 +286,3 @@ Matrix scale(T sx, T sy, T sz, const axom::ArrayView ¢er) } // end namespace transforms } // end namespace numerics } // end namespace axom - -#endif diff --git a/src/axom/core/tests/core_Path.hpp b/src/axom/core/tests/core_Path.hpp index 9fd12818df..7d7df28a91 100644 --- a/src/axom/core/tests/core_Path.hpp +++ b/src/axom/core/tests/core_Path.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "gtest/gtest.h" #include "axom/core/Path.hpp" diff --git a/src/axom/core/tests/core_about.hpp b/src/axom/core/tests/core_about.hpp index a3743eb448..285e7d12a7 100644 --- a/src/axom/core/tests/core_about.hpp +++ b/src/axom/core/tests/core_about.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "gtest/gtest.h" #include "axom/config.hpp" diff --git a/src/axom/core/tests/core_array.hpp b/src/axom/core/tests/core_array.hpp index 84f1520c6e..a7b8339a24 100644 --- a/src/axom/core/tests/core_array.hpp +++ b/src/axom/core/tests/core_array.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "axom/core/Array.hpp" #include "axom/core/ArrayView.hpp" #include "axom/core/memory_management.hpp" diff --git a/src/axom/core/tests/core_array_for_all.hpp b/src/axom/core/tests/core_array_for_all.hpp index d63d91f005..608204d66e 100644 --- a/src/axom/core/tests/core_array_for_all.hpp +++ b/src/axom/core/tests/core_array_for_all.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + // Axom includes #include "axom/config.hpp" #include "axom/core/Macros.hpp" diff --git a/src/axom/core/tests/core_array_mapping.hpp b/src/axom/core/tests/core_array_mapping.hpp index d42b948986..9fcba269fe 100644 --- a/src/axom/core/tests/core_array_mapping.hpp +++ b/src/axom/core/tests/core_array_mapping.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + // Axom includes #include "axom/core/Array.hpp" #include "axom/core/MDMapping.hpp" diff --git a/src/axom/core/tests/core_bit_utilities.hpp b/src/axom/core/tests/core_bit_utilities.hpp index 62be17960f..781fa2a5e3 100644 --- a/src/axom/core/tests/core_bit_utilities.hpp +++ b/src/axom/core/tests/core_bit_utilities.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "gtest/gtest.h" #include "axom/config.hpp" diff --git a/src/axom/core/tests/core_device_hash.hpp b/src/axom/core/tests/core_device_hash.hpp index dcdb278c37..b76b2e2fca 100644 --- a/src/axom/core/tests/core_device_hash.hpp +++ b/src/axom/core/tests/core_device_hash.hpp @@ -3,6 +3,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + // Axom includes #include "axom/config.hpp" #include "axom/core/Macros.hpp" diff --git a/src/axom/core/tests/core_execution_for_all.hpp b/src/axom/core/tests/core_execution_for_all.hpp index 1ad2b178fd..a73a4c4b4f 100644 --- a/src/axom/core/tests/core_execution_for_all.hpp +++ b/src/axom/core/tests/core_execution_for_all.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + // Axom includes #include "axom/config.hpp" /* for compile time defs */ #include "axom/core/Macros.hpp" /* for axom macros */ diff --git a/src/axom/core/tests/core_execution_scans.hpp b/src/axom/core/tests/core_execution_scans.hpp index c2c941e799..a4a95582f8 100644 --- a/src/axom/core/tests/core_execution_scans.hpp +++ b/src/axom/core/tests/core_execution_scans.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "axom/core/execution/scans.hpp" #include "axom/core/execution/execution_space.hpp" #include "axom/core/Array.hpp" diff --git a/src/axom/core/tests/core_execution_space.hpp b/src/axom/core/tests/core_execution_space.hpp index 164f412821..26385c8f57 100644 --- a/src/axom/core/tests/core_execution_space.hpp +++ b/src/axom/core/tests/core_execution_space.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "axom/config.hpp" // for compile time definitions // spin includes diff --git a/src/axom/core/tests/core_flatmap.hpp b/src/axom/core/tests/core_flatmap.hpp index c0665ce21b..e3f67c8486 100644 --- a/src/axom/core/tests/core_flatmap.hpp +++ b/src/axom/core/tests/core_flatmap.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + // Axom includes #include "axom/config.hpp" #include "axom/core/Macros.hpp" diff --git a/src/axom/core/tests/core_flatmap_for_all.hpp b/src/axom/core/tests/core_flatmap_for_all.hpp index 7215996467..027e67083b 100644 --- a/src/axom/core/tests/core_flatmap_for_all.hpp +++ b/src/axom/core/tests/core_flatmap_for_all.hpp @@ -3,6 +3,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + // Axom includes #include "axom/config.hpp" #include "axom/core/Macros.hpp" diff --git a/src/axom/core/tests/core_map.hpp b/src/axom/core/tests/core_map.hpp index 76238de19a..d6e947828c 100644 --- a/src/axom/core/tests/core_map.hpp +++ b/src/axom/core/tests/core_map.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "axom/core/StackArray.hpp" #include "axom/core/Map.hpp" #include "gtest/gtest.h" diff --git a/src/axom/core/tests/core_memory_management.hpp b/src/axom/core/tests/core_memory_management.hpp index 8a671a82fc..cc38325711 100644 --- a/src/axom/core/tests/core_memory_management.hpp +++ b/src/axom/core/tests/core_memory_management.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "gtest/gtest.h" #include "axom/core/memory_management.hpp" diff --git a/src/axom/core/tests/core_numeric_array.hpp b/src/axom/core/tests/core_numeric_array.hpp index 5a3f2d7a36..12e4b1a33b 100644 --- a/src/axom/core/tests/core_numeric_array.hpp +++ b/src/axom/core/tests/core_numeric_array.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "axom/core/NumericArray.hpp" #include "axom/core/execution/execution_space.hpp" #include "axom/core/execution/for_all.hpp" diff --git a/src/axom/core/tests/core_numeric_limits.hpp b/src/axom/core/tests/core_numeric_limits.hpp index 0d962543f9..6fe917078d 100644 --- a/src/axom/core/tests/core_numeric_limits.hpp +++ b/src/axom/core/tests/core_numeric_limits.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "axom/config.hpp" // for compile time definitions #include "axom/core/NumericLimits.hpp" diff --git a/src/axom/core/tests/core_openmp_map.hpp b/src/axom/core/tests/core_openmp_map.hpp index 160d661064..87a08cf45c 100644 --- a/src/axom/core/tests/core_openmp_map.hpp +++ b/src/axom/core/tests/core_openmp_map.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "axom/core/execution/execution_space.hpp" #include "axom/core/execution/for_all.hpp" #include "axom/core/Map.hpp" diff --git a/src/axom/core/tests/core_shared_memory.hpp b/src/axom/core/tests/core_shared_memory.hpp index 774e4d3677..3d564d5baa 100644 --- a/src/axom/core/tests/core_shared_memory.hpp +++ b/src/axom/core/tests/core_shared_memory.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + // Axom includes #include "axom/config.hpp" #include "axom/core/memory_management.hpp" diff --git a/src/axom/core/tests/core_stack_array.hpp b/src/axom/core/tests/core_stack_array.hpp index a6e0bf842b..f2467d68cd 100644 --- a/src/axom/core/tests/core_stack_array.hpp +++ b/src/axom/core/tests/core_stack_array.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "axom/core/StackArray.hpp" #include "gtest/gtest.h" #include diff --git a/src/axom/core/tests/core_static_array.hpp b/src/axom/core/tests/core_static_array.hpp index 58e7b9d119..fc139e70f9 100644 --- a/src/axom/core/tests/core_static_array.hpp +++ b/src/axom/core/tests/core_static_array.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "axom/config.hpp" // for compile time definitions #include "axom/core/StaticArray.hpp" diff --git a/src/axom/core/tests/core_types.hpp b/src/axom/core/tests/core_types.hpp index 930e038abb..fdb79583e6 100644 --- a/src/axom/core/tests/core_types.hpp +++ b/src/axom/core/tests/core_types.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + // Axom includes #include "axom/config.hpp" #include "axom/core/Types.hpp" diff --git a/src/axom/core/tests/core_utilities.hpp b/src/axom/core/tests/core_utilities.hpp index 4ef69db492..8be7a68839 100644 --- a/src/axom/core/tests/core_utilities.hpp +++ b/src/axom/core/tests/core_utilities.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "gtest/gtest.h" #include "axom/config.hpp" diff --git a/src/axom/core/tests/numerics_determinants.hpp b/src/axom/core/tests/numerics_determinants.hpp index c57ace5704..7cb54533c9 100644 --- a/src/axom/core/tests/numerics_determinants.hpp +++ b/src/axom/core/tests/numerics_determinants.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "gtest/gtest.h" #include "axom/core/numerics/Matrix.hpp" diff --git a/src/axom/core/tests/numerics_eigen_solve.hpp b/src/axom/core/tests/numerics_eigen_solve.hpp index e5147e8cae..a879139ae6 100644 --- a/src/axom/core/tests/numerics_eigen_solve.hpp +++ b/src/axom/core/tests/numerics_eigen_solve.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "gtest/gtest.h" #include "axom/core/numerics/Matrix.hpp" diff --git a/src/axom/core/tests/numerics_eigen_sort.hpp b/src/axom/core/tests/numerics_eigen_sort.hpp index cdcf4badb7..f9ac309d9f 100644 --- a/src/axom/core/tests/numerics_eigen_sort.hpp +++ b/src/axom/core/tests/numerics_eigen_sort.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + // Axom includes #include "axom/core/numerics/Matrix.hpp" // for numerics::Matrix #include "axom/core/numerics/eigen_sort.hpp" // for eigen_sort() diff --git a/src/axom/core/tests/numerics_floating_point_limits.hpp b/src/axom/core/tests/numerics_floating_point_limits.hpp index 0c360b0922..38ce99dc48 100644 --- a/src/axom/core/tests/numerics_floating_point_limits.hpp +++ b/src/axom/core/tests/numerics_floating_point_limits.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + // axom includes #include "axom/core/Macros.hpp" #include "axom/core/utilities/Utilities.hpp" diff --git a/src/axom/core/tests/numerics_jacobi_eigensolve.hpp b/src/axom/core/tests/numerics_jacobi_eigensolve.hpp index 9907e98ef5..69a71c5010 100644 --- a/src/axom/core/tests/numerics_jacobi_eigensolve.hpp +++ b/src/axom/core/tests/numerics_jacobi_eigensolve.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + // Axom includes #include "axom/core/numerics/Matrix.hpp" // for numerics::Matrix #include "axom/core/utilities/Utilities.hpp" // random_real()/isNearlyEqual diff --git a/src/axom/core/tests/numerics_linear_solve.hpp b/src/axom/core/tests/numerics_linear_solve.hpp index efc9296529..5b9db60d9d 100644 --- a/src/axom/core/tests/numerics_linear_solve.hpp +++ b/src/axom/core/tests/numerics_linear_solve.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "gtest/gtest.h" #include "axom/core/numerics/Matrix.hpp" diff --git a/src/axom/core/tests/numerics_lu.hpp b/src/axom/core/tests/numerics_lu.hpp index 9996efc161..2f3a63e0d1 100644 --- a/src/axom/core/tests/numerics_lu.hpp +++ b/src/axom/core/tests/numerics_lu.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "gtest/gtest.h" #include "axom/core/numerics/LU.hpp" diff --git a/src/axom/core/tests/numerics_matrix.hpp b/src/axom/core/tests/numerics_matrix.hpp index 06f7fa12bd..b4dbfe0dbb 100644 --- a/src/axom/core/tests/numerics_matrix.hpp +++ b/src/axom/core/tests/numerics_matrix.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "gtest/gtest.h" #include "axom/core/numerics/Matrix.hpp" diff --git a/src/axom/core/tests/numerics_matvecops.hpp b/src/axom/core/tests/numerics_matvecops.hpp index e884347d4a..d6176f2b59 100644 --- a/src/axom/core/tests/numerics_matvecops.hpp +++ b/src/axom/core/tests/numerics_matvecops.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "gtest/gtest.h" #include "axom/core/numerics/matvecops.hpp" diff --git a/src/axom/core/tests/numerics_polynomial_solvers.hpp b/src/axom/core/tests/numerics_polynomial_solvers.hpp index 73f939277f..0bc4bb1c53 100644 --- a/src/axom/core/tests/numerics_polynomial_solvers.hpp +++ b/src/axom/core/tests/numerics_polynomial_solvers.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + // Axom includes #include "axom/core/Array.hpp" #include "axom/core/numerics/polynomial_solvers.hpp" diff --git a/src/axom/core/tests/numerics_quadrature.hpp b/src/axom/core/tests/numerics_quadrature.hpp index 163e83e9a1..68fffeb80c 100644 --- a/src/axom/core/tests/numerics_quadrature.hpp +++ b/src/axom/core/tests/numerics_quadrature.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "gtest/gtest.h" #include "axom/config.hpp" @@ -135,4 +137,4 @@ TEST(numerics_quadrature, get_nodes_cuda) { test_device_quadrature>::test(); } -#endif \ No newline at end of file +#endif diff --git a/src/axom/core/tests/numerics_transforms.hpp b/src/axom/core/tests/numerics_transforms.hpp index 7905613b30..efcc926c94 100644 --- a/src/axom/core/tests/numerics_transforms.hpp +++ b/src/axom/core/tests/numerics_transforms.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "gtest/gtest.h" #include "axom/core/ArrayView.hpp" diff --git a/src/axom/core/tests/utils_Timer.hpp b/src/axom/core/tests/utils_Timer.hpp index faad085334..cfa4316783 100644 --- a/src/axom/core/tests/utils_Timer.hpp +++ b/src/axom/core/tests/utils_Timer.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "gtest/gtest.h" #include "axom/core/utilities/Timer.hpp" diff --git a/src/axom/core/tests/utils_endianness.hpp b/src/axom/core/tests/utils_endianness.hpp index 527c74787c..ae7cb819f3 100644 --- a/src/axom/core/tests/utils_endianness.hpp +++ b/src/axom/core/tests/utils_endianness.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "gtest/gtest.h" #include "axom/config.hpp" diff --git a/src/axom/core/tests/utils_fileUtilities.hpp b/src/axom/core/tests/utils_fileUtilities.hpp index 594f7ecef6..5f886ee533 100644 --- a/src/axom/core/tests/utils_fileUtilities.hpp +++ b/src/axom/core/tests/utils_fileUtilities.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "gtest/gtest.h" #include #include diff --git a/src/axom/core/tests/utils_locale.hpp b/src/axom/core/tests/utils_locale.hpp index 07ca56369e..296fe2c2e7 100644 --- a/src/axom/core/tests/utils_locale.hpp +++ b/src/axom/core/tests/utils_locale.hpp @@ -11,6 +11,8 @@ // //----------------------------------------------------------------------------- +#pragma once + #include "axom/core/utilities/StringUtilities.hpp" #include "axom/core/utilities/System.hpp" diff --git a/src/axom/core/tests/utils_stringUtilities.hpp b/src/axom/core/tests/utils_stringUtilities.hpp index ec6aa58635..557214ffd5 100644 --- a/src/axom/core/tests/utils_stringUtilities.hpp +++ b/src/axom/core/tests/utils_stringUtilities.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "gtest/gtest.h" #include "axom/core/utilities/StringUtilities.hpp" diff --git a/src/axom/core/tests/utils_system.hpp b/src/axom/core/tests/utils_system.hpp index ff85dd4ddf..f5ba29dc41 100644 --- a/src/axom/core/tests/utils_system.hpp +++ b/src/axom/core/tests/utils_system.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "gtest/gtest.h" #include "axom/core/utilities/System.hpp" diff --git a/src/axom/core/tests/utils_utilities.hpp b/src/axom/core/tests/utils_utilities.hpp index 98f0724134..70c0f386aa 100644 --- a/src/axom/core/tests/utils_utilities.hpp +++ b/src/axom/core/tests/utils_utilities.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "gtest/gtest.h" #include "axom/core/utilities/Utilities.hpp" diff --git a/src/axom/core/utilities/About.hpp b/src/axom/core/utilities/About.hpp index 5f2e1c306b..c73625039c 100644 --- a/src/axom/core/utilities/About.hpp +++ b/src/axom/core/utilities/About.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_UTILITIES_ABOUT_H_ -#define AXOM_UTILITIES_ABOUT_H_ +#pragma once #include #include @@ -39,5 +38,3 @@ void about(std::ostream &oss); std::string getVersion(); } // end namespace axom - -#endif // AXOM_UTILITIES_ABOUT_H_ diff --git a/src/axom/core/utilities/Annotations.hpp b/src/axom/core/utilities/Annotations.hpp index 6cacc889f2..8c5bdfad27 100644 --- a/src/axom/core/utilities/Annotations.hpp +++ b/src/axom/core/utilities/Annotations.hpp @@ -13,8 +13,7 @@ * unless axom is built with caliper and adiak support */ -#ifndef AXOM_CORE_ANNOTATIONS_HPP_ -#define AXOM_CORE_ANNOTATIONS_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core/Macros.hpp" @@ -100,5 +99,3 @@ std::map retrieve_metadata(); } // namespace annotations } // namespace utilities } // namespace axom - -#endif // AXOM_CORE_ANNOTATIONS_HPP_ diff --git a/src/axom/core/utilities/BitUtilities.hpp b/src/axom/core/utilities/BitUtilities.hpp index 7869b838e3..e16f115500 100644 --- a/src/axom/core/utilities/BitUtilities.hpp +++ b/src/axom/core/utilities/BitUtilities.hpp @@ -12,8 +12,7 @@ * */ -#ifndef AXOM_BIT_UTILITIES_HPP -#define AXOM_BIT_UTILITIES_HPP +#pragma once #include "axom/config.hpp" #include "axom/core/Macros.hpp" @@ -296,5 +295,3 @@ constexpr void setBitOn(FlagType &flags, BitType bit) #undef _AXOM_CORE_USE_INTRINSICS_MSVC #undef _AXOM_CORE_USE_INTRINSICS_GCC #undef _AXOM_CORE_USE_INTRINSICS_PPC - -#endif // AXOM_BIT_UTILITIES_HPP diff --git a/src/axom/core/utilities/CommandLineUtilities.hpp b/src/axom/core/utilities/CommandLineUtilities.hpp index a9a432e536..218101190d 100644 --- a/src/axom/core/utilities/CommandLineUtilities.hpp +++ b/src/axom/core/utilities/CommandLineUtilities.hpp @@ -10,8 +10,7 @@ * \brief Defines utilities in support of validating command line input */ -#ifndef AXOM_CORE_COMMANDLINE_UTILITIES_HPP_ -#define AXOM_CORE_COMMANDLINE_UTILITIES_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core/utilities/Annotations.hpp" @@ -55,5 +54,3 @@ const static CaliperModeValidator ValidCaliperMode; } // namespace utilities } // namespace axom - -#endif // AXOM_CORE_COMMANDLINE_UTILITIES_HPP_ \ No newline at end of file diff --git a/src/axom/core/utilities/FileUtilities.hpp b/src/axom/core/utilities/FileUtilities.hpp index 9cec140ab3..414a92cd06 100644 --- a/src/axom/core/utilities/FileUtilities.hpp +++ b/src/axom/core/utilities/FileUtilities.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef COMMON_FILE_UTILITIES_H_ -#define COMMON_FILE_UTILITIES_H_ +#pragma once #include #include @@ -248,5 +247,3 @@ class TempFile } // end namespace filesystem } // end namespace utilities } // end namespace axom - -#endif // COMMON_FILE_UTILITIES_H_ diff --git a/src/axom/core/utilities/RAII.hpp b/src/axom/core/utilities/RAII.hpp index 1ca509bc6d..8198063a61 100644 --- a/src/axom/core/utilities/RAII.hpp +++ b/src/axom/core/utilities/RAII.hpp @@ -13,8 +13,7 @@ * For more information about RAII, see: https://en.cppreference.com/w/cpp/language/raii */ -#ifndef AXOM_CORE_RAII_HPP_ -#define AXOM_CORE_RAII_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core/Macros.hpp" @@ -119,5 +118,3 @@ class AnnotationsWrapper } // namespace raii } // namespace utilities } // namespace axom - -#endif // AXOM_CORE_RAII_HPP_ diff --git a/src/axom/core/utilities/Sorting.hpp b/src/axom/core/utilities/Sorting.hpp index c86e3a6e96..901619e273 100644 --- a/src/axom/core/utilities/Sorting.hpp +++ b/src/axom/core/utilities/Sorting.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_CORE_UTILITIES_SORTING_HPP -#define AXOM_CORE_UTILITIES_SORTING_HPP +#pragma once #include #include @@ -490,5 +489,3 @@ struct Sorting } // end namespace utilities } // end namespace axom - -#endif diff --git a/src/axom/core/utilities/StringUtilities.hpp b/src/axom/core/utilities/StringUtilities.hpp index 1c324ae3b9..950e5b0606 100644 --- a/src/axom/core/utilities/StringUtilities.hpp +++ b/src/axom/core/utilities/StringUtilities.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef CORE_STRING_UTILITIES_H_ -#define CORE_STRING_UTILITIES_H_ +#pragma once #include #include @@ -196,5 +195,3 @@ std::string replaceAllInstances(const std::string& target, } // end namespace string } // end namespace utilities } // end namespace axom - -#endif // CORE_STRING_UTILITIES_H_ diff --git a/src/axom/core/utilities/System.hpp b/src/axom/core/utilities/System.hpp index 60a565f084..c0829ea7ce 100644 --- a/src/axom/core/utilities/System.hpp +++ b/src/axom/core/utilities/System.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef CORE_SYSTEM_UTILITIES_H_ -#define CORE_SYSTEM_UTILITIES_H_ +#pragma once #include #include @@ -37,5 +36,3 @@ std::locale locale(const std::string& name = "en_US.UTF-8"); } // end namespace utilities } // end namespace axom - -#endif // CORE_SYSTEM_UTILITIES_H_ diff --git a/src/axom/core/utilities/Timer.hpp b/src/axom/core/utilities/Timer.hpp index 60ab57766b..9444792d2c 100644 --- a/src/axom/core/utilities/Timer.hpp +++ b/src/axom/core/utilities/Timer.hpp @@ -13,8 +13,7 @@ ****************************************************************************** */ -#ifndef TIMER_HPP_ -#define TIMER_HPP_ +#pragma once #include "axom/config.hpp" @@ -176,5 +175,3 @@ class Timer } // namespace utilities } // namespace axom - -#endif // TIMER_HPP_ diff --git a/src/axom/core/utilities/Utilities.hpp b/src/axom/core/utilities/Utilities.hpp index cf58b68c1f..6f9b86ae72 100644 --- a/src/axom/core/utilities/Utilities.hpp +++ b/src/axom/core/utilities/Utilities.hpp @@ -12,8 +12,7 @@ * */ -#ifndef AXOM_UTILITIES_HPP_ -#define AXOM_UTILITIES_HPP_ +#pragma once #include "axom/config.hpp" // for compile-time definitions #include "axom/core/Types.hpp" @@ -569,5 +568,3 @@ inline std::uint64_t hash_bytes(const std::uint8_t* data, std::uint32_t length) } // namespace utilities } // namespace axom - -#endif // AXOM_UTILITIES_HPP_ diff --git a/src/axom/inlet/ConduitReader.hpp b/src/axom/inlet/ConduitReader.hpp index b9593e83d9..7617ef9648 100644 --- a/src/axom/inlet/ConduitReader.hpp +++ b/src/axom/inlet/ConduitReader.hpp @@ -12,8 +12,7 @@ ******************************************************************************* */ -#ifndef INLET_CONDUITREADER_HPP -#define INLET_CONDUITREADER_HPP +#pragma once #include "axom/inlet/Reader.hpp" @@ -129,5 +128,3 @@ class ConduitReader : public Reader } // end namespace inlet } // end namespace axom - -#endif diff --git a/src/axom/inlet/Container.hpp b/src/axom/inlet/Container.hpp index ed197794bd..40a23897de 100644 --- a/src/axom/inlet/Container.hpp +++ b/src/axom/inlet/Container.hpp @@ -12,8 +12,7 @@ ******************************************************************************* */ -#ifndef INLET_CONTAINER_HPP -#define INLET_CONTAINER_HPP +#pragma once #include #include @@ -1603,5 +1602,3 @@ const detail::VariantStructFactory& Container::variantStructFactory() c } // namespace inlet } // namespace axom - -#endif diff --git a/src/axom/inlet/Field.hpp b/src/axom/inlet/Field.hpp index 2d93506d3f..db62ace476 100644 --- a/src/axom/inlet/Field.hpp +++ b/src/axom/inlet/Field.hpp @@ -12,8 +12,7 @@ ******************************************************************************* */ -#ifndef INLET_FIELD_HPP -#define INLET_FIELD_HPP +#pragma once #include "axom/sidre.hpp" #include "axom/inlet/VariantKey.hpp" @@ -333,5 +332,3 @@ class AggregateField : public VerifiableScalar } // end namespace inlet } // end namespace axom - -#endif diff --git a/src/axom/inlet/Function.hpp b/src/axom/inlet/Function.hpp index 7f907331b8..af19270edd 100644 --- a/src/axom/inlet/Function.hpp +++ b/src/axom/inlet/Function.hpp @@ -12,8 +12,7 @@ ******************************************************************************* */ -#ifndef INLET_FUNCTION_HPP -#define INLET_FUNCTION_HPP +#pragma once #include #include @@ -349,5 +348,3 @@ class Function : public Verifiable } // end namespace inlet } // end namespace axom - -#endif // INLET_FUNCTION_HPP diff --git a/src/axom/inlet/Inlet.hpp b/src/axom/inlet/Inlet.hpp index a0c15c206e..eef25080bf 100644 --- a/src/axom/inlet/Inlet.hpp +++ b/src/axom/inlet/Inlet.hpp @@ -13,8 +13,7 @@ ******************************************************************************* */ -#ifndef INLET_INLET_HPP -#define INLET_INLET_HPP +#pragma once #include #include @@ -567,5 +566,3 @@ class Inlet } // end namespace inlet } // end namespace axom - -#endif diff --git a/src/axom/inlet/InletVector.hpp b/src/axom/inlet/InletVector.hpp index 3d789d8500..48a8890a73 100644 --- a/src/axom/inlet/InletVector.hpp +++ b/src/axom/inlet/InletVector.hpp @@ -13,8 +13,7 @@ ******************************************************************************* */ -#ifndef INLET_INLETVECTOR_HPP -#define INLET_INLETVECTOR_HPP +#pragma once #include "axom/primal/geometry/Vector.hpp" #include "axom/fmt.hpp" @@ -145,5 +144,3 @@ inline std::ostream& operator<<(std::ostream& os, const InletVector& v) template <> struct axom::fmt::formatter : ostream_formatter { }; - -#endif // INLET_INLETVECTOR_HPP diff --git a/src/axom/inlet/JSONReader.hpp b/src/axom/inlet/JSONReader.hpp index 4ccc933213..36a612ea1b 100644 --- a/src/axom/inlet/JSONReader.hpp +++ b/src/axom/inlet/JSONReader.hpp @@ -12,8 +12,7 @@ ******************************************************************************* */ -#ifndef INLET_JSONREADER_HPP -#define INLET_JSONREADER_HPP +#pragma once #include "axom/inlet/ConduitReader.hpp" @@ -40,5 +39,3 @@ class JSONReader : public ConduitReader } // end namespace inlet } // end namespace axom - -#endif diff --git a/src/axom/inlet/JSONSchemaWriter.hpp b/src/axom/inlet/JSONSchemaWriter.hpp index 1ea8d015ba..ea00b30a8a 100644 --- a/src/axom/inlet/JSONSchemaWriter.hpp +++ b/src/axom/inlet/JSONSchemaWriter.hpp @@ -12,8 +12,7 @@ ******************************************************************************* */ -#ifndef INLET_JSONSCHEMAWRITER_HPP -#define INLET_JSONSCHEMAWRITER_HPP +#pragma once #include #include @@ -65,5 +64,3 @@ class JSONSchemaWriter : public Writer } // namespace inlet } // namespace axom - -#endif diff --git a/src/axom/inlet/LuaReader.hpp b/src/axom/inlet/LuaReader.hpp index b835d7f819..fa3193c8ca 100644 --- a/src/axom/inlet/LuaReader.hpp +++ b/src/axom/inlet/LuaReader.hpp @@ -12,8 +12,7 @@ ******************************************************************************* */ -#ifndef INLET_LUAMAP_HPP -#define INLET_LUAMAP_HPP +#pragma once #include "axom/inlet/Reader.hpp" #include "axom/sol_forward.hpp" @@ -170,5 +169,3 @@ class LuaReader : public Reader } // end namespace inlet } // end namespace axom - -#endif diff --git a/src/axom/inlet/Proxy.hpp b/src/axom/inlet/Proxy.hpp index 1b02022750..abe648c760 100644 --- a/src/axom/inlet/Proxy.hpp +++ b/src/axom/inlet/Proxy.hpp @@ -12,8 +12,7 @@ ******************************************************************************* */ -#ifndef INLET_PROXY_HPP -#define INLET_PROXY_HPP +#pragma once #include #include @@ -264,5 +263,3 @@ class Proxy } // end namespace inlet } // end namespace axom - -#endif // INLET_PROXY_HPP diff --git a/src/axom/inlet/Reader.hpp b/src/axom/inlet/Reader.hpp index 6e1a6aacc0..d0fa4ee6a8 100644 --- a/src/axom/inlet/Reader.hpp +++ b/src/axom/inlet/Reader.hpp @@ -12,8 +12,7 @@ ******************************************************************************* */ -#ifndef INLET_READER_HPP -#define INLET_READER_HPP +#pragma once #include #include @@ -276,5 +275,3 @@ class Reader } // end namespace inlet } // end namespace axom - -#endif diff --git a/src/axom/inlet/SphinxWriter.hpp b/src/axom/inlet/SphinxWriter.hpp index d5cae75c24..c1bd01b66b 100644 --- a/src/axom/inlet/SphinxWriter.hpp +++ b/src/axom/inlet/SphinxWriter.hpp @@ -12,8 +12,7 @@ ******************************************************************************* */ -#ifndef INLET_SPHINXWRITER_HPP -#define INLET_SPHINXWRITER_HPP +#pragma once #include #include @@ -251,5 +250,3 @@ class SphinxWriter : public Writer } // namespace inlet } // namespace axom - -#endif diff --git a/src/axom/inlet/VariantKey.hpp b/src/axom/inlet/VariantKey.hpp index 8a31b35e5d..d240db467d 100644 --- a/src/axom/inlet/VariantKey.hpp +++ b/src/axom/inlet/VariantKey.hpp @@ -13,8 +13,7 @@ ******************************************************************************* */ -#ifndef INLET_KEY_HPP -#define INLET_KEY_HPP +#pragma once #include #include @@ -175,5 +174,3 @@ struct hash template <> struct axom::fmt::formatter : ostream_formatter { }; - -#endif // INLET_KEY_HPP diff --git a/src/axom/inlet/VariantValue.hpp b/src/axom/inlet/VariantValue.hpp index b4e03006b0..078948553e 100644 --- a/src/axom/inlet/VariantValue.hpp +++ b/src/axom/inlet/VariantValue.hpp @@ -13,8 +13,7 @@ ******************************************************************************* */ -#ifndef INLET_VARIANT_VALUE_HPP -#define INLET_VARIANT_VALUE_HPP +#pragma once #include #include @@ -28,5 +27,3 @@ using VariantValue = std::variant; } // namespace inlet } // namespace axom - -#endif diff --git a/src/axom/inlet/Verifiable.hpp b/src/axom/inlet/Verifiable.hpp index 1644dac8c5..95acfc922b 100644 --- a/src/axom/inlet/Verifiable.hpp +++ b/src/axom/inlet/Verifiable.hpp @@ -12,8 +12,7 @@ ******************************************************************************* */ -#ifndef INLET_VERIFIABLE_HPP -#define INLET_VERIFIABLE_HPP +#pragma once #include @@ -181,5 +180,3 @@ class AggregateVerifiable : public Verifiable } // namespace inlet } // namespace axom - -#endif // INLET_VERIFIABLE_HPP diff --git a/src/axom/inlet/VerifiableScalar.hpp b/src/axom/inlet/VerifiableScalar.hpp index 21161c201e..7aaf1efb31 100644 --- a/src/axom/inlet/VerifiableScalar.hpp +++ b/src/axom/inlet/VerifiableScalar.hpp @@ -12,8 +12,7 @@ ******************************************************************************* */ -#ifndef INLET_VERIFIABLE_SCALAR_HPP -#define INLET_VERIFIABLE_SCALAR_HPP +#pragma once #include #include @@ -282,5 +281,3 @@ class VerifiableScalar } // namespace inlet } // namespace axom - -#endif // INLET_VERIFIABLE_SCALAR_HPP diff --git a/src/axom/inlet/Writer.hpp b/src/axom/inlet/Writer.hpp index 714ef8be30..3373005a4a 100644 --- a/src/axom/inlet/Writer.hpp +++ b/src/axom/inlet/Writer.hpp @@ -12,8 +12,7 @@ ******************************************************************************* */ -#ifndef INLET_WRITER_HPP -#define INLET_WRITER_HPP +#pragma once namespace axom { @@ -63,5 +62,3 @@ class Writer } // namespace inlet } // namespace axom - -#endif diff --git a/src/axom/inlet/YAMLReader.hpp b/src/axom/inlet/YAMLReader.hpp index e6099f50e7..471f5866d4 100644 --- a/src/axom/inlet/YAMLReader.hpp +++ b/src/axom/inlet/YAMLReader.hpp @@ -12,8 +12,7 @@ ******************************************************************************* */ -#ifndef INLET_YAMLREADER_HPP -#define INLET_YAMLREADER_HPP +#pragma once #include "axom/inlet/ConduitReader.hpp" @@ -40,5 +39,3 @@ class YAMLReader : public ConduitReader } // end namespace inlet } // end namespace axom - -#endif diff --git a/src/axom/inlet/inlet_utils.hpp b/src/axom/inlet/inlet_utils.hpp index 114197a58c..c5b107e0bc 100644 --- a/src/axom/inlet/inlet_utils.hpp +++ b/src/axom/inlet/inlet_utils.hpp @@ -12,8 +12,7 @@ #include "axom/core/utilities/StringUtilities.hpp" #include "axom/core/Path.hpp" -#ifndef INLET_UTILS_HPP - #define INLET_UTILS_HPP +#pragma once namespace axom { @@ -191,5 +190,3 @@ ReaderResult collectionRetrievalResult(const bool contains_other_type, } // namespace inlet } // namespace axom - -#endif diff --git a/src/axom/inlet/tests/inlet_test_utils.hpp b/src/axom/inlet/tests/inlet_test_utils.hpp index fa2892a206..ce88c94142 100644 --- a/src/axom/inlet/tests/inlet_test_utils.hpp +++ b/src/axom/inlet/tests/inlet_test_utils.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef INLET_TEST_UTILS_HPP -#define INLET_TEST_UTILS_HPP +#pragma once #include #include @@ -116,5 +115,3 @@ using ReaderTypes = ::testing::Types : ostream_formatter } } }; - -#endif // AXOM_KLEE_DIMENSIONS_HPP_ diff --git a/src/axom/klee/Geometry.hpp b/src/axom/klee/Geometry.hpp index 03a1c22898..df040d64af 100644 --- a/src/axom/klee/Geometry.hpp +++ b/src/axom/klee/Geometry.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_KLEE_GEOMETRY_HPP_ -#define AXOM_KLEE_GEOMETRY_HPP_ +#pragma once #include "axom/klee/Dimensions.hpp" #include "axom/klee/Units.hpp" @@ -386,5 +385,3 @@ class Geometry } // namespace klee } // namespace axom - -#endif // AXOM_KLEE_GEOMETRY_HPP_ diff --git a/src/axom/klee/GeometryOperators.hpp b/src/axom/klee/GeometryOperators.hpp index 9bf58049c4..d92e58f2c3 100644 --- a/src/axom/klee/GeometryOperators.hpp +++ b/src/axom/klee/GeometryOperators.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_KLEE_GEOMETRYOPERATOR_HPP_ -#define AXOM_KLEE_GEOMETRYOPERATOR_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core/numerics/Matrix.hpp" @@ -412,5 +411,3 @@ class GeometryOperatorVisitor } // namespace klee } // namespace axom - -#endif // AXOM_KLEE_GEOMETRYOPERATOR_HPP_ diff --git a/src/axom/klee/KleeError.hpp b/src/axom/klee/KleeError.hpp index 9b9dd26e49..e7b2f13b64 100644 --- a/src/axom/klee/KleeError.hpp +++ b/src/axom/klee/KleeError.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_KLEE_ERROR_HPP_ -#define AXOM_KLEE_ERROR_HPP_ +#pragma once #include "axom/inlet/inlet_utils.hpp" @@ -54,5 +53,3 @@ class KleeError : public std::exception } // namespace klee } // namespace axom - -#endif // AXOM_KLEE_ERROR_HPP_ diff --git a/src/axom/klee/Shape.hpp b/src/axom/klee/Shape.hpp index 3d0039da51..fe26aca692 100644 --- a/src/axom/klee/Shape.hpp +++ b/src/axom/klee/Shape.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_KLEE_SHAPE_HPP_ -#define AXOM_KLEE_SHAPE_HPP_ +#pragma once #include "axom/klee/Geometry.hpp" @@ -91,5 +90,3 @@ class Shape } // namespace klee } // namespace axom - -#endif // AXOM_KLEE_SHAPE_HPP_ diff --git a/src/axom/klee/ShapeSet.hpp b/src/axom/klee/ShapeSet.hpp index fb9ff42505..641bbf4c87 100644 --- a/src/axom/klee/ShapeSet.hpp +++ b/src/axom/klee/ShapeSet.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_KLEE_SHAPESET_HPP_ -#define AXOM_KLEE_SHAPESET_HPP_ +#pragma once #include "axom/klee/Dimensions.hpp" #include "axom/klee/Shape.hpp" @@ -74,5 +73,3 @@ class ShapeSet } // namespace klee } // namespace axom - -#endif // AXOM_KLEE_SHAPESET_HPP_ diff --git a/src/axom/klee/Units.hpp b/src/axom/klee/Units.hpp index bd75775b4d..e698da8553 100644 --- a/src/axom/klee/Units.hpp +++ b/src/axom/klee/Units.hpp @@ -3,8 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_KLEE_UNITS_HPP -#define AXOM_KLEE_UNITS_HPP +#pragma once #include "axom/core/utilities/Units.hpp" @@ -38,4 +37,3 @@ LengthUnit parseLengthUnits(const inlet::Proxy &unitsAsProxy); } // namespace klee } // namespace axom -#endif // AXOM_KLEE_UNITS_HPP diff --git a/src/axom/klee/io/GeometryOperatorsIO.hpp b/src/axom/klee/io/GeometryOperatorsIO.hpp index bb304933aa..ea9be87440 100644 --- a/src/axom/klee/io/GeometryOperatorsIO.hpp +++ b/src/axom/klee/io/GeometryOperatorsIO.hpp @@ -3,8 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_KLEE_GEOMETRYOPERATORSIO_HPP_ -#define AXOM_KLEE_GEOMETRYOPERATORSIO_HPP_ +#pragma once #include "axom/inlet.hpp" #include "axom/klee/Dimensions.hpp" @@ -156,5 +155,3 @@ struct FromInlet { axom::klee::internal::NamedOperatorMapData operator()(const axom::inlet::Container &base); }; - -#endif // AXOM_KLEE_GEOMETRYOPERATORSIO_HPP_ diff --git a/src/axom/klee/io/IO.hpp b/src/axom/klee/io/IO.hpp index 8f9bf32bdd..913a2567ea 100644 --- a/src/axom/klee/io/IO.hpp +++ b/src/axom/klee/io/IO.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_KLEE_IO_HPP_ -#define AXOM_KLEE_IO_HPP_ +#pragma once #include "axom/klee/ShapeSet.hpp" @@ -35,5 +34,3 @@ ShapeSet readShapeSet(const std::string &filePath); } // namespace klee } // namespace axom - -#endif // AXOM_KLEE_IO_HPP_ diff --git a/src/axom/klee/io/IOUtil.hpp b/src/axom/klee/io/IOUtil.hpp index 1d205be283..037fec7bfc 100644 --- a/src/axom/klee/io/IOUtil.hpp +++ b/src/axom/klee/io/IOUtil.hpp @@ -3,8 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_KLEE_IO_UTIL_HPP_ -#define AXOM_KLEE_IO_UTIL_HPP_ +#pragma once #include "axom/klee/Dimensions.hpp" #include "axom/klee/Units.hpp" @@ -162,5 +161,3 @@ Dimensions toDimensions(const inlet::Proxy &dimProxy); } // namespace internal } // namespace klee } // namespace axom - -#endif // AXOM_KLEE_IO_UTIL_HPP_ diff --git a/src/axom/klee/tests/KleeMatchers.hpp b/src/axom/klee/tests/KleeMatchers.hpp index 8f0a4fbbc7..9e8ef28dd0 100644 --- a/src/axom/klee/tests/KleeMatchers.hpp +++ b/src/axom/klee/tests/KleeMatchers.hpp @@ -3,8 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_KLEE_MATCHERS_HPP_ -#define AXOM_KLEE_MATCHERS_HPP_ +#pragma once #include "axom/core.hpp" #include "axom/primal.hpp" @@ -171,5 +170,3 @@ inline auto AlmostEqSlice(const klee::SliceOperator& slice) { return AlmostEqSli } // namespace test } // namespace klee } // namespace axom - -#endif // AXOM_KLEE_MATCHERS_HPP_ diff --git a/src/axom/klee/tests/KleeTestUtils.hpp b/src/axom/klee/tests/KleeTestUtils.hpp index 99cd14adbe..884304f154 100644 --- a/src/axom/klee/tests/KleeTestUtils.hpp +++ b/src/axom/klee/tests/KleeTestUtils.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_KLEE_TESTUTILS_HPP_ -#define AXOM_KLEE_TESTUTILS_HPP_ +#pragma once #include "axom/klee/GeometryOperators.hpp" @@ -46,5 +45,3 @@ class MockOperator : public GeometryOperator } // namespace test } // namespace klee } // namespace axom - -#endif // AXOM_KLEE_TESTUTILS_HPP_ diff --git a/src/axom/lumberjack/BinaryTreeCommunicator.hpp b/src/axom/lumberjack/BinaryTreeCommunicator.hpp index f2591c78c9..2ab567983c 100644 --- a/src/axom/lumberjack/BinaryTreeCommunicator.hpp +++ b/src/axom/lumberjack/BinaryTreeCommunicator.hpp @@ -12,8 +12,7 @@ ******************************************************************************* */ -#ifndef BINARYTREECOMMUNICATOR_HPP -#define BINARYTREECOMMUNICATOR_HPP +#pragma once #include "mpi.h" @@ -165,5 +164,3 @@ class BinaryTreeCommunicator : public Communicator } // end namespace lumberjack } // end namespace axom - -#endif diff --git a/src/axom/lumberjack/Combiner.hpp b/src/axom/lumberjack/Combiner.hpp index 81ca34e348..fe2be386fa 100644 --- a/src/axom/lumberjack/Combiner.hpp +++ b/src/axom/lumberjack/Combiner.hpp @@ -13,8 +13,7 @@ ******************************************************************************* */ -#ifndef COMBINER_HPP -#define COMBINER_HPP +#pragma once #include "axom/lumberjack/Message.hpp" @@ -89,5 +88,3 @@ class Combiner } // end namespace lumberjack } // end namespace axom - -#endif diff --git a/src/axom/lumberjack/Communicator.hpp b/src/axom/lumberjack/Communicator.hpp index 3d1c610a13..daa611a0e4 100644 --- a/src/axom/lumberjack/Communicator.hpp +++ b/src/axom/lumberjack/Communicator.hpp @@ -13,8 +13,7 @@ ******************************************************************************* */ -#ifndef COMMUNICATOR_HPP -#define COMMUNICATOR_HPP +#pragma once #include #include @@ -165,5 +164,3 @@ class Communicator } // end namespace lumberjack } // end namespace axom - -#endif diff --git a/src/axom/lumberjack/LineFileTagCombiner.hpp b/src/axom/lumberjack/LineFileTagCombiner.hpp index bf8a4e09e1..b3e50d3f27 100644 --- a/src/axom/lumberjack/LineFileTagCombiner.hpp +++ b/src/axom/lumberjack/LineFileTagCombiner.hpp @@ -13,8 +13,7 @@ ******************************************************************************* */ -#ifndef LINEFILETAGCOMBINER_HPP -#define LINEFILETAGCOMBINER_HPP +#pragma once #include "axom/lumberjack/Combiner.hpp" #include "axom/lumberjack/Message.hpp" @@ -103,5 +102,3 @@ class LineFileTagCombiner : public axom::lumberjack::Combiner } // end namespace lumberjack } // end namespace axom - -#endif \ No newline at end of file diff --git a/src/axom/lumberjack/Lumberjack.hpp b/src/axom/lumberjack/Lumberjack.hpp index edd595cfb3..64c36c2df2 100644 --- a/src/axom/lumberjack/Lumberjack.hpp +++ b/src/axom/lumberjack/Lumberjack.hpp @@ -13,8 +13,7 @@ ******************************************************************************* */ -#ifndef LUMBERJACK_HPP -#define LUMBERJACK_HPP +#pragma once #include @@ -307,5 +306,3 @@ class Lumberjack } // end namespace lumberjack } // end namespace axom - -#endif diff --git a/src/axom/lumberjack/MPIUtility.hpp b/src/axom/lumberjack/MPIUtility.hpp index 166b9503f2..cf742b3947 100644 --- a/src/axom/lumberjack/MPIUtility.hpp +++ b/src/axom/lumberjack/MPIUtility.hpp @@ -12,8 +12,7 @@ ******************************************************************************* */ -#ifndef MPIUTILITY_HPP -#define MPIUTILITY_HPP +#pragma once #include "mpi.h" @@ -63,5 +62,3 @@ void mpiNonBlockingSendMessages(MPI_Comm comm, int destinationRank, const char* } // end namespace lumberjack } // end namespace axom - -#endif diff --git a/src/axom/lumberjack/Message.hpp b/src/axom/lumberjack/Message.hpp index 0c2335cfef..6958aafbd4 100644 --- a/src/axom/lumberjack/Message.hpp +++ b/src/axom/lumberjack/Message.hpp @@ -12,8 +12,7 @@ ******************************************************************************* */ -#ifndef MESSAGE_HPP -#define MESSAGE_HPP +#pragma once #include #include @@ -387,5 +386,3 @@ inline bool isPackedMessagesEmpty(const char* packedMessages) } // end namespace lumberjack } // end namespace axom - -#endif diff --git a/src/axom/lumberjack/NonCollectiveRootCommunicator.hpp b/src/axom/lumberjack/NonCollectiveRootCommunicator.hpp index 1c0345b1d8..c2711f8b68 100644 --- a/src/axom/lumberjack/NonCollectiveRootCommunicator.hpp +++ b/src/axom/lumberjack/NonCollectiveRootCommunicator.hpp @@ -13,8 +13,7 @@ ******************************************************************************* */ -#ifndef NONCOLLECTIVEROOTCOMMUNICATOR_HPP -#define NONCOLLECTIVEROOTCOMMUNICATOR_HPP +#pragma once #include "axom/lumberjack/Lumberjack.hpp" #include "axom/lumberjack/Communicator.hpp" @@ -149,5 +148,3 @@ class NonCollectiveRootCommunicator : public axom::lumberjack::Communicator } // end namespace lumberjack } // end namespace axom - -#endif diff --git a/src/axom/lumberjack/RootCommunicator.hpp b/src/axom/lumberjack/RootCommunicator.hpp index 5f2607ddec..8d3a45072c 100644 --- a/src/axom/lumberjack/RootCommunicator.hpp +++ b/src/axom/lumberjack/RootCommunicator.hpp @@ -12,8 +12,7 @@ ******************************************************************************* */ -#ifndef ROOTCOMMUNICATOR_HPP -#define ROOTCOMMUNICATOR_HPP +#pragma once #include @@ -156,5 +155,3 @@ class RootCommunicator : public Communicator } // end namespace lumberjack } // end namespace axom - -#endif diff --git a/src/axom/lumberjack/TextEqualityCombiner.hpp b/src/axom/lumberjack/TextEqualityCombiner.hpp index 1796709890..506653d749 100644 --- a/src/axom/lumberjack/TextEqualityCombiner.hpp +++ b/src/axom/lumberjack/TextEqualityCombiner.hpp @@ -13,8 +13,7 @@ ******************************************************************************* */ -#ifndef TEXTEQUALITYCOMBINER_HPP -#define TEXTEQUALITYCOMBINER_HPP +#pragma once #include "axom/lumberjack/Combiner.hpp" #include "axom/lumberjack/Message.hpp" @@ -103,5 +102,3 @@ class TextEqualityCombiner : public Combiner } // end namespace lumberjack } // end namespace axom - -#endif diff --git a/src/axom/lumberjack/TextTagCombiner.hpp b/src/axom/lumberjack/TextTagCombiner.hpp index 50d11b36ce..6883c48e2b 100644 --- a/src/axom/lumberjack/TextTagCombiner.hpp +++ b/src/axom/lumberjack/TextTagCombiner.hpp @@ -13,8 +13,7 @@ ******************************************************************************* */ -#ifndef TEXTTAGCOMBINER_HPP -#define TEXTTAGCOMBINER_HPP +#pragma once #include "axom/lumberjack/Combiner.hpp" #include "axom/lumberjack/Message.hpp" @@ -102,5 +101,3 @@ class TextTagCombiner : public Combiner } // end namespace lumberjack } // end namespace axom - -#endif diff --git a/src/axom/lumberjack/tests/lumberjack_BinaryCommunicator.hpp b/src/axom/lumberjack/tests/lumberjack_BinaryCommunicator.hpp index f1d8c2ac9d..41c5ba4f04 100644 --- a/src/axom/lumberjack/tests/lumberjack_BinaryCommunicator.hpp +++ b/src/axom/lumberjack/tests/lumberjack_BinaryCommunicator.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "gtest/gtest.h" #include diff --git a/src/axom/lumberjack/tests/lumberjack_LineFileTagCombiner.hpp b/src/axom/lumberjack/tests/lumberjack_LineFileTagCombiner.hpp index 8a3fb184c3..2d432d36bd 100644 --- a/src/axom/lumberjack/tests/lumberjack_LineFileTagCombiner.hpp +++ b/src/axom/lumberjack/tests/lumberjack_LineFileTagCombiner.hpp @@ -1,3 +1,5 @@ +#pragma once + #include #include #include @@ -130,4 +132,4 @@ INSTANTIATE_TEST_SUITE_P( // Negative case: Different line numbers, different filenames, different tags TestParams {"case8", 12000, 154, "foo.cpp", "bar.cpp", "myTag", "myOtherTag", false}), CustomNameGenerator // Use the custom name generator -); \ No newline at end of file +); diff --git a/src/axom/lumberjack/tests/lumberjack_Lumberjack.hpp b/src/axom/lumberjack/tests/lumberjack_Lumberjack.hpp index eb1ca1b33a..483b25b54f 100644 --- a/src/axom/lumberjack/tests/lumberjack_Lumberjack.hpp +++ b/src/axom/lumberjack/tests/lumberjack_Lumberjack.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "gtest/gtest.h" #include "axom/core/Macros.hpp" diff --git a/src/axom/lumberjack/tests/lumberjack_Message.hpp b/src/axom/lumberjack/tests/lumberjack_Message.hpp index 796fc2f837..3780314787 100644 --- a/src/axom/lumberjack/tests/lumberjack_Message.hpp +++ b/src/axom/lumberjack/tests/lumberjack_Message.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "gtest/gtest.h" #include diff --git a/src/axom/lumberjack/tests/lumberjack_NonCollectiveRootCommunicator.hpp b/src/axom/lumberjack/tests/lumberjack_NonCollectiveRootCommunicator.hpp index b24a0887b8..935fc5836a 100644 --- a/src/axom/lumberjack/tests/lumberjack_NonCollectiveRootCommunicator.hpp +++ b/src/axom/lumberjack/tests/lumberjack_NonCollectiveRootCommunicator.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "axom/lumberjack/NonCollectiveRootCommunicator.hpp" #include "gtest/gtest.h" #include "mpi.h" diff --git a/src/axom/lumberjack/tests/lumberjack_RootCommunicator.hpp b/src/axom/lumberjack/tests/lumberjack_RootCommunicator.hpp index 289639ed6c..3fd0a7481f 100644 --- a/src/axom/lumberjack/tests/lumberjack_RootCommunicator.hpp +++ b/src/axom/lumberjack/tests/lumberjack_RootCommunicator.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "gtest/gtest.h" #include diff --git a/src/axom/lumberjack/tests/lumberjack_TextEqualityCombiner.hpp b/src/axom/lumberjack/tests/lumberjack_TextEqualityCombiner.hpp index 77baae9c1a..6c0c114edb 100644 --- a/src/axom/lumberjack/tests/lumberjack_TextEqualityCombiner.hpp +++ b/src/axom/lumberjack/tests/lumberjack_TextEqualityCombiner.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "gtest/gtest.h" #include "axom/lumberjack/TextEqualityCombiner.hpp" diff --git a/src/axom/lumberjack/tests/lumberjack_TextTagCombiner.hpp b/src/axom/lumberjack/tests/lumberjack_TextTagCombiner.hpp index 9a85f84647..07ad90cc87 100644 --- a/src/axom/lumberjack/tests/lumberjack_TextTagCombiner.hpp +++ b/src/axom/lumberjack/tests/lumberjack_TextTagCombiner.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "gtest/gtest.h" #include "axom/lumberjack/TextTagCombiner.hpp" diff --git a/src/axom/mint/deprecated/MCArray.hpp b/src/axom/mint/deprecated/MCArray.hpp index 7308a7303c..afe80a1093 100644 --- a/src/axom/mint/deprecated/MCArray.hpp +++ b/src/axom/mint/deprecated/MCArray.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_DEPRECATED_MCARRAY_HPP_ -#define AXOM_DEPRECATED_MCARRAY_HPP_ +#pragma once #include "axom/config.hpp" // for compile-time defines #include "axom/core/Macros.hpp" // for axom macros @@ -747,5 +746,3 @@ inline void MCArray::dynamicRealloc(IndexType new_num_tuples) } /* namespace deprecated */ } /* namespace axom */ - -#endif /* AXOM_DEPRECATED_MCARRAY_HPP_ */ diff --git a/src/axom/mint/deprecated/SidreMCArray.hpp b/src/axom/mint/deprecated/SidreMCArray.hpp index 64d9b83575..63549bb61d 100644 --- a/src/axom/mint/deprecated/SidreMCArray.hpp +++ b/src/axom/mint/deprecated/SidreMCArray.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef SIDRE_DEPRECATED_MCArray_HPP_ -#define SIDRE_DEPRECATED_MCArray_HPP_ +#pragma once #include "axom/core/Macros.hpp" // for disable copy/assignment macro #include "axom/core/utilities/Utilities.hpp" // for memory allocation functions @@ -403,5 +402,3 @@ inline void MCArray::reallocViewData(IndexType new_capacity) } /* namespace deprecated */ } /* namespace sidre */ } /* namespace axom */ - -#endif /* SIDRE_DEPRECATED_MCArray_HPP_ */ diff --git a/src/axom/mint/execution/interface.hpp b/src/axom/mint/execution/interface.hpp index adeb867edf..5641c091d8 100644 --- a/src/axom/mint/execution/interface.hpp +++ b/src/axom/mint/execution/interface.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MINT_EXECUTION_INTERFACE_HPP_ -#define MINT_EXECUTION_INTERFACE_HPP_ +#pragma once #include "axom/config.hpp" // compile-time definitions #include "axom/core/Macros.hpp" // for AXOM_STATIC_ASSERT @@ -342,4 +341,3 @@ inline void for_all_faces(const Mesh* m, KernelType&& kernel) } // namespace mint } // namespace axom -#endif /* MINT_EXECUTION_INTERFACE_HPP_ */ diff --git a/src/axom/mint/execution/internal/for_all_cells.hpp b/src/axom/mint/execution/internal/for_all_cells.hpp index 8f09738ef0..869d3f118c 100644 --- a/src/axom/mint/execution/internal/for_all_cells.hpp +++ b/src/axom/mint/execution/internal/for_all_cells.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MINT_FOR_ALL_CELLS_HPP_ -#define MINT_FOR_ALL_CELLS_HPP_ +#pragma once // Axom core includes #include "axom/config.hpp" // compile time definitions @@ -785,5 +784,3 @@ inline void for_all_cells(xargs::coords, const Mesh& m, KernelType&& kernel) } /* namespace internal */ } /* namespace mint */ } /* namespace axom */ - -#endif /* MINT_FOR_ALL_CELLS_HPP_ */ diff --git a/src/axom/mint/execution/internal/for_all_faces.hpp b/src/axom/mint/execution/internal/for_all_faces.hpp index cea69250d1..c24963e73e 100644 --- a/src/axom/mint/execution/internal/for_all_faces.hpp +++ b/src/axom/mint/execution/internal/for_all_faces.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MINT_FOR_ALL_FACES_HPP_ -#define MINT_FOR_ALL_FACES_HPP_ +#pragma once // Axom core includes #include "axom/config.hpp" // compile time definitions @@ -945,5 +944,3 @@ inline void for_all_faces(xargs::coords, const Mesh& m, KernelType&& kernel) } /* namespace internal */ } /* namespace mint */ } /* namespace axom */ - -#endif /* MINT_FOR_ALL_FACES_HPP_ */ diff --git a/src/axom/mint/execution/internal/for_all_nodes.hpp b/src/axom/mint/execution/internal/for_all_nodes.hpp index 50e90f0481..bae3807dca 100644 --- a/src/axom/mint/execution/internal/for_all_nodes.hpp +++ b/src/axom/mint/execution/internal/for_all_nodes.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MINT_FOR_ALL_NODES_HPP_ -#define MINT_FOR_ALL_NODES_HPP_ +#pragma once // Axom core includes #include "axom/config.hpp" // compile time definitions @@ -438,5 +437,3 @@ inline void for_all_nodes(xargs::xyz, const mint::Mesh& m, KernelType&& kernel) } /* namespace internal */ } /* namespace mint */ } /* namespace axom */ - -#endif /* MINTFOR_ALL_NODES_STRUCTURED_HPP_ */ diff --git a/src/axom/mint/execution/internal/helpers.hpp b/src/axom/mint/execution/internal/helpers.hpp index 9d0126907d..af65d10b04 100644 --- a/src/axom/mint/execution/internal/helpers.hpp +++ b/src/axom/mint/execution/internal/helpers.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MINT_EXECUTION_HELPERS_HPP_ -#define MINT_EXECUTION_HELPERS_HPP_ +#pragma once // mint includes #include "axom/mint/config.hpp" // for compile-time definitions @@ -113,5 +112,3 @@ inline void for_all_coords(const FOR_ALL_FUNCTOR& for_all_nodes, const MeshType& } /* namespace internal */ } /* namespace mint */ } /* namespace axom */ - -#endif /* MINT_EXECUTION_HELPERS_HPP_ */ \ No newline at end of file diff --git a/src/axom/mint/execution/xargs.hpp b/src/axom/mint/execution/xargs.hpp index fb7b1ddcbe..c67d5c29dd 100644 --- a/src/axom/mint/execution/xargs.hpp +++ b/src/axom/mint/execution/xargs.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MINT_EXECUTION_ARGS_HPP_ -#define MINT_EXECUTION_ARGS_HPP_ +#pragma once /*! * \file @@ -204,5 +203,3 @@ struct xargs_traits } /* namespace mint */ } /* namespace axom */ - -#endif /* MINT_EXEC_ARGS_HPP_ */ diff --git a/src/axom/mint/fem/FEBasis.hpp b/src/axom/mint/fem/FEBasis.hpp index 2d5985dc91..dcfd54f472 100644 --- a/src/axom/mint/fem/FEBasis.hpp +++ b/src/axom/mint/fem/FEBasis.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MINT_FEM_BASIS_HPP_ -#define MINT_FEM_BASIS_HPP_ +#pragma once // Mint includes #include "axom/mint/mesh/CellTypes.hpp" @@ -61,5 +60,3 @@ REGISTER_LAGRANGE_BASIS(mint::HEX27); // undef internal macros #undef REGISTER_LAGRANGE_BASIS - -#endif /* MINT_FEM_BASIS_HPP_ */ diff --git a/src/axom/mint/fem/FEBasisTypes.hpp b/src/axom/mint/fem/FEBasisTypes.hpp index dbaf6c58bf..736ada7509 100644 --- a/src/axom/mint/fem/FEBasisTypes.hpp +++ b/src/axom/mint/fem/FEBasisTypes.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MINT_FEM_BASIS_TYPES_HPP_ -#define MINT_FEM_BASIS_TYPES_HPP_ +#pragma once #include @@ -42,5 +41,3 @@ static const std::string basis_name[] = { } // namespace mint } // namespace axom - -#endif diff --git a/src/axom/mint/fem/FiniteElement.hpp b/src/axom/mint/fem/FiniteElement.hpp index d4bc0c2a10..3d3daece5f 100644 --- a/src/axom/mint/fem/FiniteElement.hpp +++ b/src/axom/mint/fem/FiniteElement.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MINT_FINITEELEMENT_HPP_ -#define MINT_FINITEELEMENT_HPP_ +#pragma once #include "axom/core/Macros.hpp" // for disable copy/assignment macros #include "axom/core/Types.hpp" // for nullptr definition @@ -559,5 +558,3 @@ void bind_basis(FiniteElement& fe) } /* namespace mint */ } /* namespace axom */ - -#endif /* MINT_FINITEELEMENT_HPP_ */ diff --git a/src/axom/mint/fem/shape_functions/Lagrange.hpp b/src/axom/mint/fem/shape_functions/Lagrange.hpp index 916007d3b9..f527da77fc 100644 --- a/src/axom/mint/fem/shape_functions/Lagrange.hpp +++ b/src/axom/mint/fem/shape_functions/Lagrange.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MINT_LAGRANGE_SHAPEFUNCTION_HPP_ -#define MINT_LAGRANGE_SHAPEFUNCTION_HPP_ +#pragma once // Axom includes #include "axom/core/Macros.hpp" @@ -236,5 +235,3 @@ class Lagrange : public ShapeFunction> #include "axom/mint/fem/shape_functions/lagrange/lagrange_quad_9.hpp" #include "axom/mint/fem/shape_functions/lagrange/lagrange_tetra_4.hpp" #include "axom/mint/fem/shape_functions/lagrange/lagrange_tri_3.hpp" - -#endif /* MINT_LAGRANGE_SHAPEFUNCTION_HPP_ */ diff --git a/src/axom/mint/fem/shape_functions/ShapeFunction.hpp b/src/axom/mint/fem/shape_functions/ShapeFunction.hpp index 55d9a25839..243ef3c622 100644 --- a/src/axom/mint/fem/shape_functions/ShapeFunction.hpp +++ b/src/axom/mint/fem/shape_functions/ShapeFunction.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MINT_SHAPEFUNCTION_HPP_ -#define MINT_SHAPEFUNCTION_HPP_ +#pragma once namespace axom { @@ -124,5 +123,3 @@ class ShapeFunction } /* namespace mint */ } /* namespace axom */ - -#endif /* MINT_SHAPEFUNCTION_HPP_ */ diff --git a/src/axom/mint/fem/shape_functions/lagrange/lagrange_hexa_27.hpp b/src/axom/mint/fem/shape_functions/lagrange/lagrange_hexa_27.hpp index dfe6ec2447..adcef46f91 100644 --- a/src/axom/mint/fem/shape_functions/lagrange/lagrange_hexa_27.hpp +++ b/src/axom/mint/fem/shape_functions/lagrange/lagrange_hexa_27.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MINT_LAGRANGE_HEXA_27_HPP_ -#define MINT_LAGRANGE_HEXA_27_HPP_ +#pragma once // Mint includes #include "axom/mint/mesh/CellTypes.hpp" @@ -381,5 +380,3 @@ class Lagrange : public ShapeFunction> } /* namespace mint */ } // namespace axom - -#endif /* MINT_LAGRANGE_HEXA_27_HPP_ */ diff --git a/src/axom/mint/fem/shape_functions/lagrange/lagrange_hexa_8.hpp b/src/axom/mint/fem/shape_functions/lagrange/lagrange_hexa_8.hpp index 028f070a57..3a2b27d815 100644 --- a/src/axom/mint/fem/shape_functions/lagrange/lagrange_hexa_8.hpp +++ b/src/axom/mint/fem/shape_functions/lagrange/lagrange_hexa_8.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MINT_HEXA_8_HPP_ -#define MINT_HEXA_8_HPP_ +#pragma once // Mint includes #include "axom/mint/mesh/CellTypes.hpp" @@ -188,4 +187,3 @@ class Lagrange : public ShapeFunction> } /* namespace mint */ } /* namespace axom */ -#endif /* MINT_HEXA_8_HPP_ */ diff --git a/src/axom/mint/fem/shape_functions/lagrange/lagrange_prism_6.hpp b/src/axom/mint/fem/shape_functions/lagrange/lagrange_prism_6.hpp index b1ee66ddc5..64392908d4 100644 --- a/src/axom/mint/fem/shape_functions/lagrange/lagrange_prism_6.hpp +++ b/src/axom/mint/fem/shape_functions/lagrange/lagrange_prism_6.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MINT_PRISM_6_HPP_ -#define MINT_PRISM_6_HPP_ +#pragma once // Mint includes #include "axom/mint/mesh/CellTypes.hpp" @@ -156,4 +155,3 @@ class Lagrange : public ShapeFunction> } /* namespace mint */ } /* namespace axom */ -#endif /* MINT_PRISM_6_HPP_ */ diff --git a/src/axom/mint/fem/shape_functions/lagrange/lagrange_pyra_5.hpp b/src/axom/mint/fem/shape_functions/lagrange/lagrange_pyra_5.hpp index 4a16118770..17df1c8eb9 100644 --- a/src/axom/mint/fem/shape_functions/lagrange/lagrange_pyra_5.hpp +++ b/src/axom/mint/fem/shape_functions/lagrange/lagrange_pyra_5.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MINT_LAGRANGE_PYRA_5_HPP_ -#define MINT_LAGRANGE_PYRA_5_HPP_ +#pragma once // Mint includes #include "axom/mint/mesh/CellTypes.hpp" @@ -153,5 +152,3 @@ class Lagrange : public ShapeFunction> } /* namespace mint */ } /* namespace axom */ - -#endif /* MINT_LAGRANGE_PYRA_5_HPP_ */ diff --git a/src/axom/mint/fem/shape_functions/lagrange/lagrange_quad_4.hpp b/src/axom/mint/fem/shape_functions/lagrange/lagrange_quad_4.hpp index 252675ecc7..faa8bde7f9 100644 --- a/src/axom/mint/fem/shape_functions/lagrange/lagrange_quad_4.hpp +++ b/src/axom/mint/fem/shape_functions/lagrange/lagrange_quad_4.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MINT_QUAD4_HPP_ -#define MINT_QUAD4_HPP_ +#pragma once // Mint includes #include "axom/mint/mesh/CellTypes.hpp" @@ -126,5 +125,3 @@ class Lagrange : public ShapeFunction> } /* namespace mint */ } /* namespace axom */ - -#endif /* MINT_QUAD4_HPP_ */ diff --git a/src/axom/mint/fem/shape_functions/lagrange/lagrange_quad_9.hpp b/src/axom/mint/fem/shape_functions/lagrange/lagrange_quad_9.hpp index 4710119ef4..0cd7a02062 100644 --- a/src/axom/mint/fem/shape_functions/lagrange/lagrange_quad_9.hpp +++ b/src/axom/mint/fem/shape_functions/lagrange/lagrange_quad_9.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MINT_QUAD9_HPP_ -#define MINT_QUAD9_HPP_ +#pragma once // Mint includes #include "axom/mint/mesh/CellTypes.hpp" @@ -194,5 +193,3 @@ class Lagrange : public ShapeFunction> } /* namespace mint */ } // namespace axom - -#endif /* MINT_QUAD_9_HPP_ */ diff --git a/src/axom/mint/fem/shape_functions/lagrange/lagrange_tetra_4.hpp b/src/axom/mint/fem/shape_functions/lagrange/lagrange_tetra_4.hpp index fe69413626..0d64da0d11 100644 --- a/src/axom/mint/fem/shape_functions/lagrange/lagrange_tetra_4.hpp +++ b/src/axom/mint/fem/shape_functions/lagrange/lagrange_tetra_4.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MINT_TETRA_4_HPP_ -#define MINT_TETRA_4_HPP_ +#pragma once // Mint includes #include "axom/mint/mesh/CellTypes.hpp" @@ -133,4 +132,3 @@ class Lagrange : public ShapeFunction> } /* namespace mint */ } /* namespace axom */ -#endif /* MINT_TETRA_4_HPP_ */ diff --git a/src/axom/mint/fem/shape_functions/lagrange/lagrange_tri_3.hpp b/src/axom/mint/fem/shape_functions/lagrange/lagrange_tri_3.hpp index 9b7e65b966..32e1921ec6 100644 --- a/src/axom/mint/fem/shape_functions/lagrange/lagrange_tri_3.hpp +++ b/src/axom/mint/fem/shape_functions/lagrange/lagrange_tri_3.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MINT_TRI3_HPP_ -#define MINT_TRI3_HPP_ +#pragma once // Mint includes #include "axom/mint/mesh/CellTypes.hpp" @@ -112,5 +111,3 @@ class Lagrange : public ShapeFunction> } /* namespace mint */ } /* namespace axom */ - -#endif /* MINT_TRI3_HPP_ */ diff --git a/src/axom/mint/mesh/CellTypes.hpp b/src/axom/mint/mesh/CellTypes.hpp index 8e51e862aa..7bd0758c57 100644 --- a/src/axom/mint/mesh/CellTypes.hpp +++ b/src/axom/mint/mesh/CellTypes.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MINT_CELLTYPES_HPP_ -#define MINT_CELLTYPES_HPP_ +#pragma once #include "axom/mint/config.hpp" @@ -345,5 +344,3 @@ inline constexpr const CellInfo& getCellInfo(CellType type) } /* namespace mint */ } /* namespace axom */ - -#endif /* MINT_CellTypes_HPP_ */ diff --git a/src/axom/mint/mesh/ConnectivityArray.hpp b/src/axom/mint/mesh/ConnectivityArray.hpp index 038f1cd6bd..0071560dc9 100644 --- a/src/axom/mint/mesh/ConnectivityArray.hpp +++ b/src/axom/mint/mesh/ConnectivityArray.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MINT_ConnectivityArray_HPP_ -#define MINT_ConnectivityArray_HPP_ +#pragma once // Axom includes #include "axom/core/Macros.hpp" @@ -717,5 +716,3 @@ class ConnectivityArray } /* namespace axom */ #include "axom/mint/mesh/internal/ConnectivityArray_typed_indirection.hpp" - -#endif /* MINT_ConnectivityArray_HPP_ */ diff --git a/src/axom/mint/mesh/CurvilinearMesh.hpp b/src/axom/mint/mesh/CurvilinearMesh.hpp index 945317728f..07eaf45507 100644 --- a/src/axom/mint/mesh/CurvilinearMesh.hpp +++ b/src/axom/mint/mesh/CurvilinearMesh.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MINT_CURVILINEARMESH_HPP_ -#define MINT_CURVILINEARMESH_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/mint/mesh/StructuredMesh.hpp" @@ -287,5 +286,3 @@ class CurvilinearMesh : public StructuredMesh } /* namespace mint */ } /* namespace axom */ - -#endif /* MINT_CURVILINEARMESH_HPP_ */ diff --git a/src/axom/mint/mesh/Field.hpp b/src/axom/mint/mesh/Field.hpp index 8df398af35..38b1693c24 100644 --- a/src/axom/mint/mesh/Field.hpp +++ b/src/axom/mint/mesh/Field.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MINT_FIELD_HPP_ -#define MINT_FIELD_HPP_ +#pragma once // axom includes #include "axom/core/Macros.hpp" // for axom Macros @@ -275,5 +274,3 @@ inline const T* Field::getDataPtr(const Field* field) } /* namespace mint */ } /* namespace axom */ - -#endif /* FIELD_HPP_ */ diff --git a/src/axom/mint/mesh/FieldAssociation.hpp b/src/axom/mint/mesh/FieldAssociation.hpp index 217c94cdd5..ffd35bebdd 100644 --- a/src/axom/mint/mesh/FieldAssociation.hpp +++ b/src/axom/mint/mesh/FieldAssociation.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MINT_FIELDASSOCIATION_HPP_ -#define MINT_FIELDASSOCIATION_HPP_ +#pragma once namespace axom { @@ -28,5 +27,3 @@ enum FieldAssociation } /* namespace mint */ } /* namespace axom */ - -#endif /* MINT_FIELDASSOCIATION_HPP_ */ diff --git a/src/axom/mint/mesh/FieldData.hpp b/src/axom/mint/mesh/FieldData.hpp index 2461af57f8..2bc6f40406 100644 --- a/src/axom/mint/mesh/FieldData.hpp +++ b/src/axom/mint/mesh/FieldData.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MINT_FIELDDATA_HPP_ -#define MINT_FIELDDATA_HPP_ +#pragma once // Axom includes #include "axom/core/Macros.hpp" // for Axom macros @@ -683,5 +682,3 @@ inline T* FieldData::createField(const std::string& name, } /* namespace mint */ } /* namespace axom */ - -#endif /* FIELDDATA_HPP_ */ diff --git a/src/axom/mint/mesh/FieldTypes.hpp b/src/axom/mint/mesh/FieldTypes.hpp index 3237ad20a9..51636ca0e5 100644 --- a/src/axom/mint/mesh/FieldTypes.hpp +++ b/src/axom/mint/mesh/FieldTypes.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MINT_FIELDTYPES_HPP_ -#define MINT_FIELDTYPES_HPP_ +#pragma once #include "axom/core/Types.hpp" // for axom type definitions @@ -72,5 +71,3 @@ struct field_traits } /* namespace mint */ } /* namespace axom */ - -#endif /* MINT_FIELDTYPES_HPP_ */ diff --git a/src/axom/mint/mesh/FieldVariable.hpp b/src/axom/mint/mesh/FieldVariable.hpp index 64a1093c11..5759df41cb 100644 --- a/src/axom/mint/mesh/FieldVariable.hpp +++ b/src/axom/mint/mesh/FieldVariable.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MINT_FIELDVARIABLE_HPP_ -#define MINT_FIELDVARIABLE_HPP_ +#pragma once #include "axom/mint/mesh/Field.hpp" @@ -396,5 +395,3 @@ FieldVariable::FieldVariable(const std::string& name, } /* namespace mint */ } /* namespace axom */ - -#endif /* FIELDVARIABLE_HPP_ */ diff --git a/src/axom/mint/mesh/Mesh.hpp b/src/axom/mint/mesh/Mesh.hpp index 5b2cc2f553..edb2cb7e07 100644 --- a/src/axom/mint/mesh/Mesh.hpp +++ b/src/axom/mint/mesh/Mesh.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MINT_MESH_HPP_ -#define MINT_MESH_HPP_ +#pragma once #include "axom/core/Macros.hpp" // for Axom macros @@ -1025,5 +1024,3 @@ inline void Mesh::getFieldInfo(int association, IndexType& num_tuples, IndexType } /* namespace mint */ } /* namespace axom */ - -#endif /* MINT_MESH_HPP_ */ diff --git a/src/axom/mint/mesh/MeshCoordinates.hpp b/src/axom/mint/mesh/MeshCoordinates.hpp index 7aabb1d64e..19aeabf0e2 100644 --- a/src/axom/mint/mesh/MeshCoordinates.hpp +++ b/src/axom/mint/mesh/MeshCoordinates.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MINT_MESHCOORDINATES_HPP_ -#define MINT_MESHCOORDINATES_HPP_ +#pragma once #include "axom/core/Macros.hpp" // for Axom macros and definitions #include "axom/mint/deprecated/MCArray.hpp" @@ -1011,5 +1010,3 @@ inline void MeshCoordinates::initialize(IndexType numNodes, IndexType maxCapacit } /* namespace mint */ } /* namespace axom */ - -#endif /* MINT_MESHCOORDINATES_HPP_ */ diff --git a/src/axom/mint/mesh/MeshTypes.hpp b/src/axom/mint/mesh/MeshTypes.hpp index 80e95f14eb..2cdf5ad6e2 100644 --- a/src/axom/mint/mesh/MeshTypes.hpp +++ b/src/axom/mint/mesh/MeshTypes.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MINT_MESHTYPES_HPP_ -#define MINT_MESHTYPES_HPP_ +#pragma once namespace axom { @@ -31,4 +30,3 @@ enum MeshTypes } /* namespace mint */ } /* namespace axom */ -#endif /* MINT_MESHTYPE_HPP_ */ diff --git a/src/axom/mint/mesh/ParticleMesh.hpp b/src/axom/mint/mesh/ParticleMesh.hpp index 23130306f2..ac77c0e2ec 100644 --- a/src/axom/mint/mesh/ParticleMesh.hpp +++ b/src/axom/mint/mesh/ParticleMesh.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MINT_PARTICLEMESH_HPP_ -#define MINT_PARTICLEMESH_HPP_ +#pragma once #include "axom/core/Macros.hpp" // for axom macros @@ -575,5 +574,3 @@ inline void ParticleMesh::shrink() } /* namespace mint */ } /* namespace axom */ - -#endif /* MINT_PARTICLEMESH_HPP_ */ diff --git a/src/axom/mint/mesh/RectilinearMesh.hpp b/src/axom/mint/mesh/RectilinearMesh.hpp index 86c503c9f6..7c24a143d6 100644 --- a/src/axom/mint/mesh/RectilinearMesh.hpp +++ b/src/axom/mint/mesh/RectilinearMesh.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MINT_RECTILINEARMESH_HPP_ -#define MINT_RECTILINEARMESH_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/mint/mesh/StructuredMesh.hpp" @@ -293,5 +292,3 @@ class RectilinearMesh : public StructuredMesh } /* namespace mint */ } /* namespace axom */ - -#endif /* MINT_RECTILINEARMESH_HPP_ */ diff --git a/src/axom/mint/mesh/StructuredMesh.hpp b/src/axom/mint/mesh/StructuredMesh.hpp index 01aacf21db..99d912bc58 100644 --- a/src/axom/mint/mesh/StructuredMesh.hpp +++ b/src/axom/mint/mesh/StructuredMesh.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MINT_STRUCTUREDMESH_HPP_ -#define MINT_STRUCTUREDMESH_HPP_ +#pragma once #include "axom/core/Types.hpp" // for axom types #include "axom/core/Macros.hpp" // for axom macros @@ -1229,5 +1228,3 @@ inline IndexType StructuredMesh::getCellFaceIDsInternal(IndexType cellID, } /* namespace mint */ } /* namespace axom */ - -#endif /* MINT_STRUCTUREDMESH_HPP_ */ diff --git a/src/axom/mint/mesh/UniformMesh.hpp b/src/axom/mint/mesh/UniformMesh.hpp index 763e27c544..873338645f 100644 --- a/src/axom/mint/mesh/UniformMesh.hpp +++ b/src/axom/mint/mesh/UniformMesh.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MINT_UNIFORMMESH_HPP_ -#define MINT_UNIFORMMESH_HPP_ +#pragma once #include "axom/core/StackArray.hpp" #include "axom/mint/config.hpp" @@ -291,5 +290,3 @@ class UniformMesh : public StructuredMesh } /* namespace mint */ } /* namespace axom */ - -#endif /* MINT_UNIFORMMESH_HPP_ */ diff --git a/src/axom/mint/mesh/UnstructuredMesh.hpp b/src/axom/mint/mesh/UnstructuredMesh.hpp index 3119aa0347..2c51f874a9 100644 --- a/src/axom/mint/mesh/UnstructuredMesh.hpp +++ b/src/axom/mint/mesh/UnstructuredMesh.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MINT_UNSTRUCTUREDMESH_HPP_ -#define MINT_UNSTRUCTUREDMESH_HPP_ +#pragma once // Axom includes #include "axom/core/Macros.hpp" @@ -1913,5 +1912,3 @@ inline void UnstructuredMesh::updateFaceRelations(IndexType numFace } /* namespace mint */ } /* namespace axom */ - -#endif /* MINT_UNSTRUCTUREDMESH_HPP_ */ diff --git a/src/axom/mint/mesh/blueprint.hpp b/src/axom/mint/mesh/blueprint.hpp index 558d4a425f..a00ebe3817 100644 --- a/src/axom/mint/mesh/blueprint.hpp +++ b/src/axom/mint/mesh/blueprint.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MINT_MESH_BLUEPRINT_HPP_ -#define MINT_MESH_BLUEPRINT_HPP_ +#pragma once // mint includes #include "axom/mint/config.hpp" // for compile-time definitions @@ -301,5 +300,3 @@ void setUniformMeshProperties(int dim, } /* namespace mint */ } /* namespace axom */ - -#endif /* MINT_MESH_BLUEPRINT_HPP_ */ diff --git a/src/axom/mint/mesh/internal/ConnectivityArrayHelpers.hpp b/src/axom/mint/mesh/internal/ConnectivityArrayHelpers.hpp index ab330aa6c4..51e1cebe07 100644 --- a/src/axom/mint/mesh/internal/ConnectivityArrayHelpers.hpp +++ b/src/axom/mint/mesh/internal/ConnectivityArrayHelpers.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MINT_ConnectivityArrayHelpers_HPP_ -#define MINT_ConnectivityArrayHelpers_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core/Array.hpp" @@ -452,5 +451,3 @@ inline IndexType calcValueCapacity(IndexType n_IDs, } /* namespace internal */ } /* namespace mint */ } /* namespace axom */ - -#endif /* MINT_ConnectivityArrayHelpers_HPP_ */ diff --git a/src/axom/mint/mesh/internal/ConnectivityArray_typed_indirection.hpp b/src/axom/mint/mesh/internal/ConnectivityArray_typed_indirection.hpp index d52ba392b7..a7ae71c8e7 100644 --- a/src/axom/mint/mesh/internal/ConnectivityArray_typed_indirection.hpp +++ b/src/axom/mint/mesh/internal/ConnectivityArray_typed_indirection.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MINT_ConnectivityArray_typed_indirection_HPP_ -#define MINT_ConnectivityArray_typed_indirection_HPP_ +#pragma once #include @@ -585,5 +584,3 @@ class ConnectivityArray } /* namespace mint */ } /* namespace axom */ - -#endif /* MINT_ConnectivityArray_typed_indirection_HPP_ */ diff --git a/src/axom/mint/mesh/internal/MeshHelpers.hpp b/src/axom/mint/mesh/internal/MeshHelpers.hpp index 6522070f9d..ba4c055557 100644 --- a/src/axom/mint/mesh/internal/MeshHelpers.hpp +++ b/src/axom/mint/mesh/internal/MeshHelpers.hpp @@ -3,8 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MINT_MESH_HELPERS_HPP_ -#define MINT_MESH_HELPERS_HPP_ +#pragma once #include "axom/core/Macros.hpp" // for AXOM_UNUSED_PARAM #include "axom/core/Types.hpp" // for nullptr @@ -78,5 +77,3 @@ bool initFaces(Mesh* m, } /* namespace internal */ } /* namespace mint */ } /* namespace axom */ - -#endif /* MINT_MESH_HELPERS_HPP_ */ diff --git a/src/axom/mint/tests/StructuredMesh_helpers.hpp b/src/axom/mint/tests/StructuredMesh_helpers.hpp index 7a86a20283..46799b9f91 100644 --- a/src/axom/mint/tests/StructuredMesh_helpers.hpp +++ b/src/axom/mint/tests/StructuredMesh_helpers.hpp @@ -3,8 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MINT_STRUCTURED_MESH_TEST_HELPERS_HPP_ -#define MINT_STRUCTURED_MESH_TEST_HELPERS_HPP_ +#pragma once #include "axom/mint/config.hpp" // for compile-time definitions @@ -982,5 +981,3 @@ inline void check_node_extent(const StructuredMesh* m, const int64* extent) } /* namespace internal */ } /* namespace mint */ } /* namespace axom */ - -#endif /* MINT_STRUCTURED_MESH_TEST_HELPERS_HPP_ */ diff --git a/src/axom/mint/tests/mint_test_utilities.hpp b/src/axom/mint/tests/mint_test_utilities.hpp index 581ab23696..9fb5f12371 100644 --- a/src/axom/mint/tests/mint_test_utilities.hpp +++ b/src/axom/mint/tests/mint_test_utilities.hpp @@ -9,7 +9,10 @@ * * \brief Consists of utility functions to facilitate in test development. */ +#pragma once + // Axom includes + #include "axom/core/Macros.hpp" // Mint includes diff --git a/src/axom/mint/utils/ArrayWrapper.hpp b/src/axom/mint/utils/ArrayWrapper.hpp index 9380e6db73..6ecd67a0b5 100644 --- a/src/axom/mint/utils/ArrayWrapper.hpp +++ b/src/axom/mint/utils/ArrayWrapper.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef Axom_Mint_ArrayWrapper_HPP -#define Axom_Mint_ArrayWrapper_HPP +#pragma once #include "axom/core/Array.hpp" // to inherit #include "axom/core/Types.hpp" @@ -278,5 +277,3 @@ class ArrayWrapper } // namespace detail } // namespace mint } // namespace axom - -#endif diff --git a/src/axom/mint/utils/ExternalArray.hpp b/src/axom/mint/utils/ExternalArray.hpp index e8037e4409..2b7df047d7 100644 --- a/src/axom/mint/utils/ExternalArray.hpp +++ b/src/axom/mint/utils/ExternalArray.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MINT_EXTERNALARRAY_HPP_ -#define MINT_EXTERNALARRAY_HPP_ +#pragma once #include "axom/core/Array.hpp" // to inherit #include "axom/core/Types.hpp" @@ -179,5 +178,3 @@ class ExternalArray } // namespace mint } // namespace axom - -#endif diff --git a/src/axom/mint/utils/su2_utils.hpp b/src/axom/mint/utils/su2_utils.hpp index 7b7585c679..0a05ac54e1 100644 --- a/src/axom/mint/utils/su2_utils.hpp +++ b/src/axom/mint/utils/su2_utils.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MINT_UTILS_SU2_UTILS_HPP_ -#define MINT_UTILS_SU2_UTILS_HPP_ +#pragma once #include // for std::string @@ -62,5 +61,3 @@ int write_su2(const mint::Mesh* mesh, const std::string& file); } /* namespace mint */ } /* namespace axom */ - -#endif /* MINT_UTILS_SU2_UTILS_HPP_ */ diff --git a/src/axom/mint/utils/vtk_utils.hpp b/src/axom/mint/utils/vtk_utils.hpp index 15e99ad2cb..f25f8bb5bc 100644 --- a/src/axom/mint/utils/vtk_utils.hpp +++ b/src/axom/mint/utils/vtk_utils.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MINT_SRC_UTILS_VTK_UTILS_HPP -#define MINT_SRC_UTILS_VTK_UTILS_HPP +#pragma once #include // for std::string @@ -42,5 +41,3 @@ int write_vtk(mint::FiniteElement& fe, const std::string& file_path); } /* namespace mint */ } /* namespace axom */ - -#endif /* MINT_SRC_UTILS_VTK_UTILS_HPP */ diff --git a/src/axom/mir/ElviraAlgorithm.hpp b/src/axom/mir/ElviraAlgorithm.hpp index 13a9731086..7c1a59c6f9 100644 --- a/src/axom/mir/ElviraAlgorithm.hpp +++ b/src/axom/mir/ElviraAlgorithm.hpp @@ -3,8 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_MIR_ELVIRA_ALGORITHM_HPP_ -#define AXOM_MIR_ELVIRA_ALGORITHM_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core.hpp" @@ -1134,5 +1133,3 @@ class ElviraAlgorithm : public axom::mir::MIRAlgorithm } // end namespace mir } // end namespace axom - -#endif diff --git a/src/axom/mir/EquiZAlgorithm.hpp b/src/axom/mir/EquiZAlgorithm.hpp index f0ca1f7e9c..0a36344b43 100644 --- a/src/axom/mir/EquiZAlgorithm.hpp +++ b/src/axom/mir/EquiZAlgorithm.hpp @@ -3,8 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_MIR_EQUIZ_ALGORITHM_HPP_ -#define AXOM_MIR_EQUIZ_ALGORITHM_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core.hpp" @@ -1232,5 +1231,3 @@ class EquiZAlgorithm : public axom::mir::MIRAlgorithm } // end namespace mir } // end namespace axom - -#endif diff --git a/src/axom/mir/MIRAlgorithm.hpp b/src/axom/mir/MIRAlgorithm.hpp index 51d829a3a8..4a50579eaa 100644 --- a/src/axom/mir/MIRAlgorithm.hpp +++ b/src/axom/mir/MIRAlgorithm.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_MIR_ALGORITHM_HPP_ -#define AXOM_MIR_ALGORITHM_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core.hpp" @@ -166,5 +165,3 @@ class MIRAlgorithm } // end namespace mir } // end namespace axom - -#endif diff --git a/src/axom/mir/detail/elvira_detail.hpp b/src/axom/mir/detail/elvira_detail.hpp index 3c31280ead..d40f1b6128 100644 --- a/src/axom/mir/detail/elvira_detail.hpp +++ b/src/axom/mir/detail/elvira_detail.hpp @@ -3,8 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_MIR_ELVIRA_ALGORITHM_DETAIL_HPP_ -#define AXOM_MIR_ELVIRA_ALGORITHM_DETAIL_HPP_ +#pragma once // Most includes happen in the ElviraAlgorithm.hpp header file that includes this file. @@ -1105,5 +1104,3 @@ struct MakeCleanZones } // end namespace detail } // end namespace mir } // end namespace axom - -#endif diff --git a/src/axom/mir/detail/elvira_impl.hpp b/src/axom/mir/detail/elvira_impl.hpp index 8af899de79..e4aa50bfa7 100644 --- a/src/axom/mir/detail/elvira_impl.hpp +++ b/src/axom/mir/detail/elvira_impl.hpp @@ -7,6 +7,8 @@ // NOTE: This file is meant to be included by ElviraAlgorithm.hpp after its // other includes so we do not include much here. +#pragma once + namespace axom { namespace mir diff --git a/src/axom/mir/detail/equiz_detail.hpp b/src/axom/mir/detail/equiz_detail.hpp index 1d2e19e8ba..3f9ef1088c 100644 --- a/src/axom/mir/detail/equiz_detail.hpp +++ b/src/axom/mir/detail/equiz_detail.hpp @@ -3,8 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_MIR_EQUIZ_ALGORITHM_DETAIL_HPP_ -#define AXOM_MIR_EQUIZ_ALGORITHM_DETAIL_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core.hpp" @@ -246,5 +245,3 @@ class MaterialIntersector } // end namespace detail } // end namespace mir } // end namespace axom - -#endif diff --git a/src/axom/mir/examples/concentric_circles/MIRApplication.hpp b/src/axom/mir/examples/concentric_circles/MIRApplication.hpp index 1ded039a0a..4540bb645d 100644 --- a/src/axom/mir/examples/concentric_circles/MIRApplication.hpp +++ b/src/axom/mir/examples/concentric_circles/MIRApplication.hpp @@ -3,8 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_MIR_EXAMPLES_MIR_APPLICATION_HPP -#define AXOM_MIR_EXAMPLES_MIR_APPLICATION_HPP +#pragma once #include "axom/config.hpp" #include "axom/core.hpp" // for axom macros @@ -83,5 +82,3 @@ class MIRApplication std::string annotationMode; std::string protocol; }; - -#endif diff --git a/src/axom/mir/examples/concentric_circles/runMIR.hpp b/src/axom/mir/examples/concentric_circles/runMIR.hpp index 9ece5401f5..d04e12d59f 100644 --- a/src/axom/mir/examples/concentric_circles/runMIR.hpp +++ b/src/axom/mir/examples/concentric_circles/runMIR.hpp @@ -3,8 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_MIR_EXAMPLES_CONCENTRIC_CIRCLES_RUNMIR_HPP -#define AXOM_MIR_EXAMPLES_CONCENTRIC_CIRCLES_RUNMIR_HPP +#pragma once #include "axom/config.hpp" #include "axom/core.hpp" // for axom macros #include "axom/slic.hpp" @@ -245,5 +244,3 @@ int runMIR_hip(int dimension, const conduit::Node &mesh, const conduit::Node &options, conduit::Node &result); - -#endif diff --git a/src/axom/mir/examples/heavily_mixed/HMApplication.hpp b/src/axom/mir/examples/heavily_mixed/HMApplication.hpp index f374a24f31..78111c7352 100644 --- a/src/axom/mir/examples/heavily_mixed/HMApplication.hpp +++ b/src/axom/mir/examples/heavily_mixed/HMApplication.hpp @@ -3,8 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_MIR_EXAMPLES_HEAVILY_MIXED_APPLICATION_HPP -#define AXOM_MIR_EXAMPLES_HEAVILY_MIXED_APPLICATION_HPP +#pragma once #include "axom/config.hpp" #include "axom/core.hpp" @@ -77,5 +76,3 @@ class HMApplication std::string m_annotationMode; std::string m_protocol; }; - -#endif diff --git a/src/axom/mir/examples/heavily_mixed/runMIR.hpp b/src/axom/mir/examples/heavily_mixed/runMIR.hpp index 74e64e0ba1..372a5d3f2d 100644 --- a/src/axom/mir/examples/heavily_mixed/runMIR.hpp +++ b/src/axom/mir/examples/heavily_mixed/runMIR.hpp @@ -3,8 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_MIR_EXAMPLES_HEAVILY_MIXED_RUNMIR_HPP -#define AXOM_MIR_EXAMPLES_HEAVILY_MIXED_RUNMIR_HPP +#pragma once #include "axom/config.hpp" #include "axom/core.hpp" #include "axom/slic.hpp" @@ -175,5 +174,3 @@ int runMIR_hip(int dimension, const conduit::Node &mesh, const conduit::Node &options, conduit::Node &result); - -#endif diff --git a/src/axom/mir/examples/tutorial_simple/runMIR.hpp b/src/axom/mir/examples/tutorial_simple/runMIR.hpp index a1f5a4973a..a05c1df1b0 100644 --- a/src/axom/mir/examples/tutorial_simple/runMIR.hpp +++ b/src/axom/mir/examples/tutorial_simple/runMIR.hpp @@ -3,8 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_MIR_EXAMPLES_TUTORIAL_SIMPLE_RUNMIR_HPP -#define AXOM_MIR_EXAMPLES_TUTORIAL_SIMPLE_RUNMIR_HPP +#pragma once #include "axom/config.hpp" #include "axom/core.hpp" // for axom macros #include "axom/slic.hpp" @@ -198,5 +197,3 @@ int runMIR_seq(const conduit::Node &mesh, const conduit::Node &options, conduit: int runMIR_omp(const conduit::Node &mesh, const conduit::Node &options, conduit::Node &result); int runMIR_cuda(const conduit::Node &mesh, const conduit::Node &options, conduit::Node &result); int runMIR_hip(const conduit::Node &mesh, const conduit::Node &options, conduit::Node &result); - -#endif diff --git a/src/axom/mir/future/ClipFieldFilter.hpp b/src/axom/mir/future/ClipFieldFilter.hpp index 334d13d8ce..f65577d236 100644 --- a/src/axom/mir/future/ClipFieldFilter.hpp +++ b/src/axom/mir/future/ClipFieldFilter.hpp @@ -3,8 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_MIR_CLIP_FIELD_FILTER_HPP_ -#define AXOM_MIR_CLIP_FIELD_FILTER_HPP_ +#pragma once #include "axom/core.hpp" #include "axom/mir.hpp" @@ -76,5 +75,3 @@ class ClipFieldFilter } // end namespace clipping } // end namespace mir } // end namespace axom - -#endif diff --git a/src/axom/mir/future/ClipFieldFilterDevice.hpp b/src/axom/mir/future/ClipFieldFilterDevice.hpp index 1c92297235..e3ba0832ca 100644 --- a/src/axom/mir/future/ClipFieldFilterDevice.hpp +++ b/src/axom/mir/future/ClipFieldFilterDevice.hpp @@ -3,8 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_MIR_CLIP_FIELD_FILTER_DEVICE_HPP_ -#define AXOM_MIR_CLIP_FIELD_FILTER_DEVICE_HPP_ +#pragma once #include "axom/core.hpp" #include "axom/mir.hpp" @@ -143,5 +142,3 @@ class ClipFieldFilterDevice } // end namespace clipping } // end namespace mir } // end namespace axom - -#endif diff --git a/src/axom/mir/reference/CellClipper.hpp b/src/axom/mir/reference/CellClipper.hpp index 1fd3f1230e..71510693bb 100644 --- a/src/axom/mir/reference/CellClipper.hpp +++ b/src/axom/mir/reference/CellClipper.hpp @@ -11,8 +11,7 @@ * */ -#ifndef __CELL_CLIPPER_H -#define __CELL_CLIPPER_H +#pragma once #include "axom/core.hpp" // for axom macros #include "axom/slam.hpp" // unified header for slam classes and functions @@ -113,5 +112,3 @@ class CellClipper } // namespace mir } // namespace axom - -#endif diff --git a/src/axom/mir/reference/CellData.hpp b/src/axom/mir/reference/CellData.hpp index 9413e05527..3203bf4eb4 100644 --- a/src/axom/mir/reference/CellData.hpp +++ b/src/axom/mir/reference/CellData.hpp @@ -11,8 +11,7 @@ * and CellTopologyData and CellMapData structs. */ -#ifndef __CELL_DATA_H__ -#define __CELL_DATA_H__ +#pragma once #include "axom/core.hpp" // for axom macros #include "axom/slam.hpp" @@ -94,5 +93,3 @@ class CellData }; } // namespace mir } // namespace axom - -#endif diff --git a/src/axom/mir/reference/CellGenerator.hpp b/src/axom/mir/reference/CellGenerator.hpp index 2c46b4a11d..5144bde63a 100644 --- a/src/axom/mir/reference/CellGenerator.hpp +++ b/src/axom/mir/reference/CellGenerator.hpp @@ -11,8 +11,7 @@ * */ -#ifndef __CELL_GENERATOR_H__ -#define __CELL_GENERATOR_H__ +#pragma once #include "axom/core.hpp" // for axom macros #include "axom/slam.hpp" // unified header for slam classes and functions @@ -125,5 +124,3 @@ class CellGenerator } // namespace mir } // namespace axom - -#endif diff --git a/src/axom/mir/reference/InterfaceReconstructor.hpp b/src/axom/mir/reference/InterfaceReconstructor.hpp index 124776d713..c4242008ef 100644 --- a/src/axom/mir/reference/InterfaceReconstructor.hpp +++ b/src/axom/mir/reference/InterfaceReconstructor.hpp @@ -11,8 +11,7 @@ * */ -#ifndef __INTERFACE_RECONSTRUCTOR_H__ -#define __INTERFACE_RECONSTRUCTOR_H__ +#pragma once #include "axom/core.hpp" // for axom macros #include "axom/slam.hpp" @@ -105,4 +104,3 @@ class InterfaceReconstructor }; } // namespace mir } // namespace axom -#endif diff --git a/src/axom/mir/reference/MIRMesh.hpp b/src/axom/mir/reference/MIRMesh.hpp index 67b506000f..2b5f589849 100644 --- a/src/axom/mir/reference/MIRMesh.hpp +++ b/src/axom/mir/reference/MIRMesh.hpp @@ -11,8 +11,7 @@ * */ -#ifndef __MIR_MESH_H__ -#define __MIR_MESH_H__ +#pragma once #include "axom/core.hpp" // for axom macros #include "axom/slam.hpp" // unified header for slam classes and functions @@ -228,4 +227,3 @@ class MIRMesh //-------------------------------------------------------------------------------- } // namespace mir } // namespace axom -#endif diff --git a/src/axom/mir/reference/MIRMeshTypes.hpp b/src/axom/mir/reference/MIRMeshTypes.hpp index 06754bf942..74e70a7748 100644 --- a/src/axom/mir/reference/MIRMeshTypes.hpp +++ b/src/axom/mir/reference/MIRMeshTypes.hpp @@ -10,8 +10,7 @@ * \brief Contains the specifications for types aliases used throughout the MIR component. */ -#ifndef __MIR_MESH_TYPES_H__ -#define __MIR_MESH_TYPES_H__ +#pragma once #include "axom/core.hpp" // for axom macros #include "axom/slam.hpp" @@ -60,4 +59,3 @@ using PointMap = slam::Map; using IntMap = slam::Map; } // namespace mir } // namespace axom -#endif diff --git a/src/axom/mir/reference/MIRUtilities.hpp b/src/axom/mir/reference/MIRUtilities.hpp index e55b48f394..9b96421d9b 100644 --- a/src/axom/mir/reference/MIRUtilities.hpp +++ b/src/axom/mir/reference/MIRUtilities.hpp @@ -12,8 +12,7 @@ * */ -#ifndef __MIR_UTILITIES_HPP__ -#define __MIR_UTILITIES_HPP__ +#pragma once #include "axom/mir/reference/ZooClippingTables.hpp" @@ -655,5 +654,3 @@ inline mir::Shape determineElementShapeType(const Shape parentShapeType, const i } // namespace utilities } // namespace mir } // namespace axom - -#endif diff --git a/src/axom/mir/reference/ZooClippingTables.hpp b/src/axom/mir/reference/ZooClippingTables.hpp index 16cdcceb20..085541bb68 100644 --- a/src/axom/mir/reference/ZooClippingTables.hpp +++ b/src/axom/mir/reference/ZooClippingTables.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef __ZOO_CLIPPING_TABLES_H__ -#define __ZOO_CLIPPING_TABLES_H__ +#pragma once /** * \file ZooClippingTables.hpp @@ -31,4 +30,3 @@ extern const std::vector> triangularPrismClipTableVec; extern const std::vector> hexahedronClipTableVec; } // namespace mir } // namespace axom -#endif diff --git a/src/axom/multimat/examples/helper.hpp b/src/axom/multimat/examples/helper.hpp index 5a9d7fad8c..a9d7aadb55 100644 --- a/src/axom/multimat/examples/helper.hpp +++ b/src/axom/multimat/examples/helper.hpp @@ -12,6 +12,8 @@ * Also defines some helper struct-classes. */ +#pragma once + #include "axom/core.hpp" #include "axom/slam.hpp" #include "axom/fmt.hpp" diff --git a/src/axom/multimat/mmfield.hpp b/src/axom/multimat/mmfield.hpp index 3779a6251c..4e76d29d88 100644 --- a/src/axom/multimat/mmfield.hpp +++ b/src/axom/multimat/mmfield.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MMFIELD_H_ -#define MMFIELD_H_ +#pragma once #include "axom/multimat/multimat.hpp" #include "axom/multimat/mmsubfield.hpp" @@ -168,5 +167,3 @@ class MMField2DTemplated : public MMField2D } //end namespace multimat } //end namespace axom - -#endif diff --git a/src/axom/multimat/mmsubfield.hpp b/src/axom/multimat/mmsubfield.hpp index b179d89da7..28bcc7c053 100644 --- a/src/axom/multimat/mmsubfield.hpp +++ b/src/axom/multimat/mmsubfield.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MMSUBFIELD_H_ -#define MMSUBFIELD_H_ +#pragma once #include "axom/multimat/multimat.hpp" @@ -90,5 +89,3 @@ class MMSubField2DWrap : public MMSubField2D& bCurve) template struct axom::fmt::formatter> : ostream_formatter { }; - -#endif // AXOM_PRIMAL_BEZIERCURVE_HPP_ diff --git a/src/axom/primal/geometry/BezierPatch.hpp b/src/axom/primal/geometry/BezierPatch.hpp index f5511224c8..9ad0c26a1b 100644 --- a/src/axom/primal/geometry/BezierPatch.hpp +++ b/src/axom/primal/geometry/BezierPatch.hpp @@ -10,8 +10,7 @@ * \brief A BezierPatch primitive */ -#ifndef AXOM_PRIMAL_BEZIERPATCH_HPP_ -#define AXOM_PRIMAL_BEZIERPATCH_HPP_ +#pragma once #include "axom/core.hpp" #include "axom/slic.hpp" @@ -2091,5 +2090,3 @@ std::ostream& operator<<(std::ostream& os, const BezierPatch& bPatch) } // namespace primal } // namespace axom - -#endif // AXOM_PRIMAL_BEZIERPATCH_HPP_ diff --git a/src/axom/primal/geometry/BezierTriangle.hpp b/src/axom/primal/geometry/BezierTriangle.hpp index 6431eb8bbc..27d04f0a2f 100644 --- a/src/axom/primal/geometry/BezierTriangle.hpp +++ b/src/axom/primal/geometry/BezierTriangle.hpp @@ -10,8 +10,7 @@ * \brief A BezierTriangle primitive */ -#ifndef AXOM_PRIMAL_BEZIERTRIANGLE_HPP_ -#define AXOM_PRIMAL_BEZIERTRIANGLE_HPP_ +#pragma once #include "axom/core.hpp" #include "axom/slic.hpp" @@ -1857,5 +1856,3 @@ std::ostream& operator<<(std::ostream& os, const BezierTriangle& bTri) } // namespace primal } // namespace axom - -#endif // AXOM_PRIMAL_BEZIERTRIANGLE_HPP_ diff --git a/src/axom/primal/geometry/BoundingBox.hpp b/src/axom/primal/geometry/BoundingBox.hpp index 6a4f2b9e44..2537a45fdb 100644 --- a/src/axom/primal/geometry/BoundingBox.hpp +++ b/src/axom/primal/geometry/BoundingBox.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_PRIMAL_BOUNDINGBOX_HPP_ -#define AXOM_PRIMAL_BOUNDINGBOX_HPP_ +#pragma once #include "axom/config.hpp" @@ -726,5 +725,3 @@ std::ostream& operator<<(std::ostream& os, const BoundingBox& bb) template struct axom::fmt::formatter> : ostream_formatter { }; - -#endif // AXOM_PRIMAL_BOUNDINGBOX_HPP_ diff --git a/src/axom/primal/geometry/Cone.hpp b/src/axom/primal/geometry/Cone.hpp index 24abe89982..137cdee24d 100644 --- a/src/axom/primal/geometry/Cone.hpp +++ b/src/axom/primal/geometry/Cone.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_PRIMAL_CONE_HPP_ -#define AXOM_PRIMAL_CONE_HPP_ +#pragma once #include "axom/core.hpp" @@ -188,5 +187,3 @@ std::ostream& operator<<(std::ostream& os, const Cone& Cone) template struct axom::fmt::formatter> : ostream_formatter { }; - -#endif // AXOM_PRIMAL_CONE_HPP_ diff --git a/src/axom/primal/geometry/CoordinateTransformer.hpp b/src/axom/primal/geometry/CoordinateTransformer.hpp index a07f3fdc2e..7c3b0e2910 100644 --- a/src/axom/primal/geometry/CoordinateTransformer.hpp +++ b/src/axom/primal/geometry/CoordinateTransformer.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_PRIMAL_COORDINATE_TRANSFORMER_HPP -#define AXOM_PRIMAL_COORDINATE_TRANSFORMER_HPP +#pragma once #include "axom/core/numerics/Matrix.hpp" #include "axom/core/utilities/Utilities.hpp" @@ -458,5 +457,3 @@ class CoordinateTransformer } // namespace experimental } // namespace primal } // namespace axom - -#endif diff --git a/src/axom/primal/geometry/CurvedPolygon.hpp b/src/axom/primal/geometry/CurvedPolygon.hpp index f543d3106c..e308926d08 100644 --- a/src/axom/primal/geometry/CurvedPolygon.hpp +++ b/src/axom/primal/geometry/CurvedPolygon.hpp @@ -10,8 +10,7 @@ * \brief A polygon primitive whose edges are Bezier curves */ -#ifndef AXOM_PRIMAL_CURVEDPOLYGON_HPP_ -#define AXOM_PRIMAL_CURVEDPOLYGON_HPP_ +#pragma once #include "axom/slic.hpp" @@ -290,5 +289,3 @@ std::ostream& operator<<(std::ostream& os, const CurvedPolygon& poly) } // namespace primal } // namespace axom - -#endif // AXOM_PRIMAL_CURVEDPOLYGON_HPP_ diff --git a/src/axom/primal/geometry/Hexahedron.hpp b/src/axom/primal/geometry/Hexahedron.hpp index a0a08d010a..679f7cba98 100644 --- a/src/axom/primal/geometry/Hexahedron.hpp +++ b/src/axom/primal/geometry/Hexahedron.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef HEXAHEDRON_HPP_ -#define HEXAHEDRON_HPP_ +#pragma once #include "axom/core/StackArray.hpp" @@ -385,5 +384,3 @@ std::ostream& operator<<(std::ostream& os, const Hexahedron& hex) } /* namespace primal */ } /* namespace axom */ - -#endif /* HEXAHEDRON_HPP_ */ diff --git a/src/axom/primal/geometry/KnotVector.hpp b/src/axom/primal/geometry/KnotVector.hpp index 399b50f1c7..708247318d 100644 --- a/src/axom/primal/geometry/KnotVector.hpp +++ b/src/axom/primal/geometry/KnotVector.hpp @@ -10,8 +10,7 @@ * \brief A class to represent knot vectors for NURBS */ -#ifndef AXOM_PRIMAL_KNOTVECTOR_HPP -#define AXOM_PRIMAL_KNOTVECTOR_HPP +#pragma once #include "axom/core.hpp" #include "axom/slic.hpp" @@ -1321,5 +1320,3 @@ std::ostream& operator<<(std::ostream& os, const KnotVector& kvector) template struct axom::fmt::formatter> : ostream_formatter { }; - -#endif // AXOM_PRIMAL_KNOTVECTOR_HPP diff --git a/src/axom/primal/geometry/Line.hpp b/src/axom/primal/geometry/Line.hpp index a228b514a0..9db67ecddc 100644 --- a/src/axom/primal/geometry/Line.hpp +++ b/src/axom/primal/geometry/Line.hpp @@ -3,8 +3,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_PRIMAL_LINE_HPP_ -#define AXOM_PRIMAL_LINE_HPP_ +#pragma once #include "axom/primal/geometry/Point.hpp" #include "axom/primal/geometry/Segment.hpp" @@ -164,5 +163,3 @@ std::ostream& operator<<(std::ostream& os, const Line& line) template struct axom::fmt::formatter> : ostream_formatter { }; - -#endif // AXOM_PRIMAL_LINE_HPP_ \ No newline at end of file diff --git a/src/axom/primal/geometry/NURBSCurve.hpp b/src/axom/primal/geometry/NURBSCurve.hpp index d2c80d961e..cfbad07af9 100644 --- a/src/axom/primal/geometry/NURBSCurve.hpp +++ b/src/axom/primal/geometry/NURBSCurve.hpp @@ -10,8 +10,7 @@ * \brief A NURBS curve primitive */ -#ifndef AXOM_PRIMAL_NURBSCURVE_HPP_ -#define AXOM_PRIMAL_NURBSCURVE_HPP_ +#pragma once #include "axom/core.hpp" #include "axom/slic.hpp" @@ -1538,5 +1537,3 @@ std::ostream& operator<<(std::ostream& os, const NURBSCurve& nCurve) template struct axom::fmt::formatter> : ostream_formatter { }; - -#endif // AXOM_PRIMAL_NURBSCURVE_HPP_ diff --git a/src/axom/primal/geometry/NURBSPatch.hpp b/src/axom/primal/geometry/NURBSPatch.hpp index fefe70ae80..764971c4de 100644 --- a/src/axom/primal/geometry/NURBSPatch.hpp +++ b/src/axom/primal/geometry/NURBSPatch.hpp @@ -10,8 +10,7 @@ * \brief A (trimmed) NURBSPatch primitive */ -#ifndef AXOM_PRIMAL_NURBSPATCH_HPP_ -#define AXOM_PRIMAL_NURBSPATCH_HPP_ +#pragma once #include "axom/core.hpp" #include "axom/slic.hpp" @@ -4711,5 +4710,3 @@ std::ostream& operator<<(std::ostream& os, const NURBSPatch& nPatch) template struct axom::fmt::formatter> : ostream_formatter { }; - -#endif // AXOM_PRIMAL_NURBSPATCH_HPP_ diff --git a/src/axom/primal/geometry/Octahedron.hpp b/src/axom/primal/geometry/Octahedron.hpp index d006fa3292..748de10a3f 100644 --- a/src/axom/primal/geometry/Octahedron.hpp +++ b/src/axom/primal/geometry/Octahedron.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef OCTAHEDRON_HPP_ -#define OCTAHEDRON_HPP_ +#pragma once #include "axom/primal/geometry/Point.hpp" #include "axom/primal/geometry/Vector.hpp" @@ -250,5 +249,3 @@ std::ostream& operator<<(std::ostream& os, const Octahedron& oct) } /* namespace primal */ } /* namespace axom */ - -#endif /* OCTAHEDRON_HPP_ */ diff --git a/src/axom/primal/geometry/OrientationResult.hpp b/src/axom/primal/geometry/OrientationResult.hpp index 2e73213521..40a44822d9 100644 --- a/src/axom/primal/geometry/OrientationResult.hpp +++ b/src/axom/primal/geometry/OrientationResult.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_PRIMAL_ORIENTATIONRESULT_HPP_ -#define AXOM_PRIMAL_ORIENTATIONRESULT_HPP_ +#pragma once /*! * \file @@ -30,5 +29,3 @@ enum OrientationResult } // namespace primal } // namespace axom - -#endif // AXOM_PRIMAL_ORIENTATIONRESULT_HPP_ diff --git a/src/axom/primal/geometry/OrientedBoundingBox.hpp b/src/axom/primal/geometry/OrientedBoundingBox.hpp index 29da4ef9d1..c832944f8d 100644 --- a/src/axom/primal/geometry/OrientedBoundingBox.hpp +++ b/src/axom/primal/geometry/OrientedBoundingBox.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_PRIMAL_ORIENTEDBOUNDINGBOX_HPP_ -#define AXOM_PRIMAL_ORIENTEDBOUNDINGBOX_HPP_ +#pragma once #include @@ -795,5 +794,3 @@ std::ostream& operator<<(std::ostream& os, const OrientedBoundingBox& } // namespace primal } // namespace axom - -#endif // AXOM_PRIMAL_ORIENTEDBOUNDINGBOX_HPP_ diff --git a/src/axom/primal/geometry/Plane.hpp b/src/axom/primal/geometry/Plane.hpp index b1b9c57831..6f1bb72025 100644 --- a/src/axom/primal/geometry/Plane.hpp +++ b/src/axom/primal/geometry/Plane.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_PRIMAL_PLANE_HPP_ -#define AXOM_PRIMAL_PLANE_HPP_ +#pragma once #include "axom/core/Macros.hpp" #include "axom/core/numerics/matvecops.hpp" @@ -392,5 +391,3 @@ AXOM_HOST_DEVICE Plane make_plane(const Point& x1, } // namespace primal } // namespace axom - -#endif // AXOM_PRIMAL_PLANE_HPP_ diff --git a/src/axom/primal/geometry/Point.hpp b/src/axom/primal/geometry/Point.hpp index 0cb6d40f0b..2fdbcee57b 100644 --- a/src/axom/primal/geometry/Point.hpp +++ b/src/axom/primal/geometry/Point.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_PRIMAL_POINT_HPP_ -#define AXOM_PRIMAL_POINT_HPP_ +#pragma once #include "axom/core/NumericArray.hpp" #include "axom/core/Macros.hpp" @@ -416,5 +415,3 @@ Point transform_point( template struct axom::fmt::formatter> : ostream_formatter { }; - -#endif // AXOM_PRIMAL_POINT_HPP_ diff --git a/src/axom/primal/geometry/Polygon.hpp b/src/axom/primal/geometry/Polygon.hpp index 237a995556..6fbd3b1486 100644 --- a/src/axom/primal/geometry/Polygon.hpp +++ b/src/axom/primal/geometry/Polygon.hpp @@ -10,8 +10,7 @@ * \brief A Polygon primitive for primal */ -#ifndef AXOM_PRIMAL_POLYGON_HPP_ -#define AXOM_PRIMAL_POLYGON_HPP_ +#pragma once #include "axom/core/Array.hpp" #include "axom/core/StaticArray.hpp" @@ -447,5 +446,3 @@ std::ostream& operator<<(std::ostream& os, const Polygon struct axom::fmt::formatter> : ostream_formatter { }; - -#endif // AXOM_PRIMAL_POLYGON_HPP_ diff --git a/src/axom/primal/geometry/Polyhedron.hpp b/src/axom/primal/geometry/Polyhedron.hpp index a4cb3bbbcd..b618cc0124 100644 --- a/src/axom/primal/geometry/Polyhedron.hpp +++ b/src/axom/primal/geometry/Polyhedron.hpp @@ -10,8 +10,7 @@ * \brief A Polyhedron primitive for primal */ -#ifndef AXOM_PRIMAL_POLYHEDRON_HPP_ -#define AXOM_PRIMAL_POLYHEDRON_HPP_ +#pragma once #include "axom/core/StackArray.hpp" @@ -1045,5 +1044,3 @@ std::ostream& operator<<(std::ostream& os, const Polyhedron& poly) template struct axom::fmt::formatter> : ostream_formatter { }; - -#endif // AXOM_PRIMAL_POLYHEDRON_HPP_ diff --git a/src/axom/primal/geometry/Quadrilateral.hpp b/src/axom/primal/geometry/Quadrilateral.hpp index a424096921..9189e5f5b8 100644 --- a/src/axom/primal/geometry/Quadrilateral.hpp +++ b/src/axom/primal/geometry/Quadrilateral.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_PRIMAL_QUADRILATERAL_HPP_ -#define AXOM_PRIMAL_QUADRILATERAL_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core.hpp" @@ -238,5 +237,3 @@ std::ostream& operator<<(std::ostream& os, const Quadrilateral& quad) template struct axom::fmt::formatter> : ostream_formatter { }; - -#endif // AXOM_PRIMAL_QUADRILATERAL_HPP_ diff --git a/src/axom/primal/geometry/Ray.hpp b/src/axom/primal/geometry/Ray.hpp index fa4e78e962..d8d4f6d725 100644 --- a/src/axom/primal/geometry/Ray.hpp +++ b/src/axom/primal/geometry/Ray.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_PRIMAL_RAY_HPP_ -#define AXOM_PRIMAL_RAY_HPP_ +#pragma once #include "axom/primal/geometry/Point.hpp" #include "axom/primal/geometry/Segment.hpp" @@ -167,5 +166,3 @@ std::ostream& operator<<(std::ostream& os, const Ray& ray) template struct axom::fmt::formatter> : ostream_formatter { }; - -#endif // AXOM_PRIMAL_RAY_HPP_ diff --git a/src/axom/primal/geometry/Segment.hpp b/src/axom/primal/geometry/Segment.hpp index 0e3ad28032..160d8b605d 100644 --- a/src/axom/primal/geometry/Segment.hpp +++ b/src/axom/primal/geometry/Segment.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_PRIMAL_SEGMENT_HPP_ -#define AXOM_PRIMAL_SEGMENT_HPP_ +#pragma once #include "axom/core/Macros.hpp" // for Axom macros #include "axom/slic.hpp" @@ -205,5 +204,3 @@ std::ostream& operator<<(std::ostream& os, const Segment& seg) template struct axom::fmt::formatter> : ostream_formatter { }; - -#endif // AXOM_PRIMAL_SEGMENT_HPP_ diff --git a/src/axom/primal/geometry/Sphere.hpp b/src/axom/primal/geometry/Sphere.hpp index 530abb8a94..740514144f 100644 --- a/src/axom/primal/geometry/Sphere.hpp +++ b/src/axom/primal/geometry/Sphere.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_PRIMAL_SPHERE_HPP_ -#define AXOM_PRIMAL_SPHERE_HPP_ +#pragma once #include "axom/core/Macros.hpp" @@ -301,5 +300,3 @@ std::ostream& operator<<(std::ostream& os, const Sphere& s) template struct axom::fmt::formatter> : ostream_formatter { }; - -#endif // AXOM_PRIMAL_SPHERE_HPP_ diff --git a/src/axom/primal/geometry/Tetrahedron.hpp b/src/axom/primal/geometry/Tetrahedron.hpp index 862ca53d38..35f19a0681 100644 --- a/src/axom/primal/geometry/Tetrahedron.hpp +++ b/src/axom/primal/geometry/Tetrahedron.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_PRIMAL_TETRAHEDRON_HPP_ -#define AXOM_PRIMAL_TETRAHEDRON_HPP_ +#pragma once #include "axom/core.hpp" @@ -366,5 +365,3 @@ std::ostream& operator<<(std::ostream& os, const Tetrahedron& tet) template struct axom::fmt::formatter> : ostream_formatter { }; - -#endif // AXOM_PRIMAL_TETRAHEDRON_HPP_ diff --git a/src/axom/primal/geometry/Triangle.hpp b/src/axom/primal/geometry/Triangle.hpp index c419840939..1cdd56030c 100644 --- a/src/axom/primal/geometry/Triangle.hpp +++ b/src/axom/primal/geometry/Triangle.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_PRIMAL_TRIANGLE_HPP_ -#define AXOM_PRIMAL_TRIANGLE_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core.hpp" @@ -447,5 +446,3 @@ std::ostream& operator<<(std::ostream& os, const Triangle& tri) template struct axom::fmt::formatter> : ostream_formatter { }; - -#endif // AXOM_PRIMAL_TRIANGLE_HPP_ diff --git a/src/axom/primal/geometry/Vector.hpp b/src/axom/primal/geometry/Vector.hpp index b8e8558ed7..47731df69a 100644 --- a/src/axom/primal/geometry/Vector.hpp +++ b/src/axom/primal/geometry/Vector.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_PRIMAL_VECTOR_HPP_ -#define AXOM_PRIMAL_VECTOR_HPP_ +#pragma once // axom_utils includes #include "axom/core/Macros.hpp" @@ -693,5 +692,3 @@ AXOM_HOST_DEVICE inline Vector Vector::make_vector(const T& template struct axom::fmt::formatter> : ostream_formatter { }; - -#endif // AXOM_PRIMAL_VECTOR_HPP_ diff --git a/src/axom/primal/geometry/construct.hpp b/src/axom/primal/geometry/construct.hpp index ac35c5008a..fd0e452aaf 100644 --- a/src/axom/primal/geometry/construct.hpp +++ b/src/axom/primal/geometry/construct.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_PRIMAL_CONSTRUCT_HPP_ -#define AXOM_PRIMAL_CONSTRUCT_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/slic.hpp" @@ -130,5 +129,3 @@ Polyhedron regular_prism( } // namespace primal } // namespace axom - -#endif // AXOM_PRIMAL_CONSTRUCT_HPP_ diff --git a/src/axom/primal/geometry/detail/analytic_test_surfaces.hpp b/src/axom/primal/geometry/detail/analytic_test_surfaces.hpp index eb409fadce..8311203497 100644 --- a/src/axom/primal/geometry/detail/analytic_test_surfaces.hpp +++ b/src/axom/primal/geometry/detail/analytic_test_surfaces.hpp @@ -14,8 +14,7 @@ * \sa primal_solid_angle.cpp */ -#ifndef AXOM_PRIMAL_ANALYTIC_TEST_SURFACES_HPP -#define AXOM_PRIMAL_ANALYTIC_TEST_SURFACES_HPP +#pragma once #include "axom/config.hpp" #include "axom/primal.hpp" @@ -321,5 +320,3 @@ axom::Array> make_teardrop() } // namespace detail } // namespace primal } // namespace axom - -#endif // AXOM_PRIMAL_ANALYTIC_TEST_SURFACES_HPP diff --git a/src/axom/primal/operators/clip.hpp b/src/axom/primal/operators/clip.hpp index d1208b0847..dab5447b40 100644 --- a/src/axom/primal/operators/clip.hpp +++ b/src/axom/primal/operators/clip.hpp @@ -11,8 +11,7 @@ * another primal primitive */ -#ifndef AXOM_PRIMAL_CLIP_HPP_ -#define AXOM_PRIMAL_CLIP_HPP_ +#pragma once #include "axom/core/utilities/Utilities.hpp" @@ -863,5 +862,3 @@ AXOM_HOST_DEVICE Polyhedron clip(const Hexahedron& hex, } // namespace primal } // namespace axom - -#endif // AXOM_PRIMAL_CLIP_HPP_ diff --git a/src/axom/primal/operators/closest_point.hpp b/src/axom/primal/operators/closest_point.hpp index 8a8a96f7c9..fab271e115 100644 --- a/src/axom/primal/operators/closest_point.hpp +++ b/src/axom/primal/operators/closest_point.hpp @@ -12,8 +12,7 @@ * */ -#ifndef AXOM_PRIMAL_CLOSEST_POINT_HPP_ -#define AXOM_PRIMAL_CLOSEST_POINT_HPP_ +#pragma once #include "axom/primal/geometry/Point.hpp" #include "axom/primal/geometry/Segment.hpp" @@ -351,5 +350,3 @@ AXOM_HOST_DEVICE inline Point closest_point(const Point& P, } // namespace primal } // namespace axom - -#endif // AXOM_PRIMAL_CLOSEST_POINT_HPP_ diff --git a/src/axom/primal/operators/compute_bounding_box.hpp b/src/axom/primal/operators/compute_bounding_box.hpp index 5bc1730e5d..0b9573cd36 100644 --- a/src/axom/primal/operators/compute_bounding_box.hpp +++ b/src/axom/primal/operators/compute_bounding_box.hpp @@ -10,8 +10,7 @@ * \brief Consists of functions to create bounding boxes. */ -#ifndef AXOM_PRIMAL_COMPUTE_BOUNDING_BOX_HPP_ -#define AXOM_PRIMAL_COMPUTE_BOUNDING_BOX_HPP_ +#pragma once #include "axom/core/numerics/Matrix.hpp" // for Matrix #include "axom/core/Macros.hpp" // for AXOM_HOST__DEVICE @@ -193,5 +192,3 @@ AXOM_HOST_DEVICE BoundingBox compute_bounding_box( } // namespace primal } // namespace axom - -#endif // AXOM_PRIMAL_COMPUTE_BOUNDING_BOX_HPP_ diff --git a/src/axom/primal/operators/compute_moments.hpp b/src/axom/primal/operators/compute_moments.hpp index ce96622578..2f674317a2 100644 --- a/src/axom/primal/operators/compute_moments.hpp +++ b/src/axom/primal/operators/compute_moments.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_PRIMAL_COMPUTE_MOMENTS_HPP_ -#define AXOM_PRIMAL_COMPUTE_MOMENTS_HPP_ +#pragma once /*! * \file compute_moments.hpp @@ -154,5 +153,3 @@ primal::Point centroid(const primal::CurvedPolygon>& pol } // namespace primal } // namespace axom - -#endif // AXOM_PRIMAL_COMPUTE_MOMENTS_HPP_ diff --git a/src/axom/primal/operators/detail/clip_impl.hpp b/src/axom/primal/operators/detail/clip_impl.hpp index 4a1539cd7f..ebb36d77d9 100644 --- a/src/axom/primal/operators/detail/clip_impl.hpp +++ b/src/axom/primal/operators/detail/clip_impl.hpp @@ -10,8 +10,7 @@ * \brief Helper functions for the primal clipping operators */ -#ifndef AXOM_PRIMAL_CLIP_IMPL_HPP_ -#define AXOM_PRIMAL_CLIP_IMPL_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core/Macros.hpp" @@ -1213,5 +1212,3 @@ AXOM_HOST_DEVICE Polygon clipPolygonPlane( } // namespace detail } // namespace primal } // namespace axom - -#endif // AXOM_PRIMAL_CLIP_IMPL_HPP_ diff --git a/src/axom/primal/operators/detail/compute_moments_impl.hpp b/src/axom/primal/operators/detail/compute_moments_impl.hpp index 79a970cbf1..fd72710186 100644 --- a/src/axom/primal/operators/detail/compute_moments_impl.hpp +++ b/src/axom/primal/operators/detail/compute_moments_impl.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_PRIMAL_COMPUTE_MOMENTS_IMPL_HPP_ -#define AXOM_PRIMAL_COMPUTE_MOMENTS_IMPL_HPP_ +#pragma once /*! * \file compute_moments_impl.hpp @@ -196,5 +195,3 @@ class MemoizedSectorCentroidWeights } // namespace detail } // namespace primal } // namespace axom - -#endif // AXOM_PRIMAL_COMPUTE_MOMENTS_IMPL_HPP_ \ No newline at end of file diff --git a/src/axom/primal/operators/detail/evaluate_integral_curve_impl.hpp b/src/axom/primal/operators/detail/evaluate_integral_curve_impl.hpp index 69b7ddf0e9..3c3e22d324 100644 --- a/src/axom/primal/operators/detail/evaluate_integral_curve_impl.hpp +++ b/src/axom/primal/operators/detail/evaluate_integral_curve_impl.hpp @@ -13,8 +13,7 @@ * dependencies to prevent circular include chains (e.g. with NURBSPatch). */ -#ifndef PRIMAL_EVAL_INTEGRAL_CURVE_IMPL_HPP_ -#define PRIMAL_EVAL_INTEGRAL_CURVE_IMPL_HPP_ +#pragma once // Axom includes #include "axom/core.hpp" @@ -292,5 +291,3 @@ inline typename CurveType::NumericType curve_array_lower_bound_y(const axom::Arr } // end namespace detail } // end namespace primal } // end namespace axom - -#endif diff --git a/src/axom/primal/operators/detail/evaluate_integral_impl.hpp b/src/axom/primal/operators/detail/evaluate_integral_impl.hpp index 99e69eebbb..caa10c9fea 100644 --- a/src/axom/primal/operators/detail/evaluate_integral_impl.hpp +++ b/src/axom/primal/operators/detail/evaluate_integral_impl.hpp @@ -13,10 +13,7 @@ * internally evaluate curve-based integrals (e.g. via trimming curves). */ -#ifndef PRIMAL_EVAL_INTEGRAL_IMPL_HPP_ -#define PRIMAL_EVAL_INTEGRAL_IMPL_HPP_ +#pragma once #include "axom/primal/operators/detail/evaluate_integral_curve_impl.hpp" #include "axom/primal/operators/detail/evaluate_integral_surface_impl.hpp" - -#endif diff --git a/src/axom/primal/operators/detail/evaluate_integral_surface_impl.hpp b/src/axom/primal/operators/detail/evaluate_integral_surface_impl.hpp index 78f9268de9..f3934c6fbb 100644 --- a/src/axom/primal/operators/detail/evaluate_integral_surface_impl.hpp +++ b/src/axom/primal/operators/detail/evaluate_integral_surface_impl.hpp @@ -10,8 +10,7 @@ * \brief Implementation helpers for surface/volume integral evaluation. */ -#ifndef PRIMAL_EVAL_INTEGRAL_SURFACE_IMPL_HPP_ -#define PRIMAL_EVAL_INTEGRAL_SURFACE_IMPL_HPP_ +#pragma once // Axom includes #include "axom/core.hpp" @@ -212,5 +211,3 @@ inline LambdaRetType evaluate_volume_integral_component(const primal::NURBSPatch } // end namespace detail } // end namespace primal } // end namespace axom - -#endif diff --git a/src/axom/primal/operators/detail/fuzzy_comparators.hpp b/src/axom/primal/operators/detail/fuzzy_comparators.hpp index ff065c9998..fc3c915696 100644 --- a/src/axom/primal/operators/detail/fuzzy_comparators.hpp +++ b/src/axom/primal/operators/detail/fuzzy_comparators.hpp @@ -10,8 +10,7 @@ * This file provides helper functions for fuzzy comparisons */ -#ifndef AXOM_PRIMAL_FUZZY_COMPARATORS_HPP_ -#define AXOM_PRIMAL_FUZZY_COMPARATORS_HPP_ +#pragma once #include "axom/core/Macros.hpp" #include "axom/core/utilities/Utilities.hpp" @@ -87,5 +86,3 @@ inline bool isGpeq(double x, double y, bool includeEqual = false, double EPS = 1 } // namespace detail } // namespace primal } // namespace axom - -#endif // AXOM_PRIMAL_FUZZY_COMPARATORS_HPP_ \ No newline at end of file diff --git a/src/axom/primal/operators/detail/intersect_bezier_impl.hpp b/src/axom/primal/operators/detail/intersect_bezier_impl.hpp index 443e75a029..867523e89b 100644 --- a/src/axom/primal/operators/detail/intersect_bezier_impl.hpp +++ b/src/axom/primal/operators/detail/intersect_bezier_impl.hpp @@ -11,8 +11,7 @@ * of Bezier curves with other Bezier curves and other geometric objects */ -#ifndef AXOM_PRIMAL_INTERSECT_BEZIER_IMPL_HPP_ -#define AXOM_PRIMAL_INTERSECT_BEZIER_IMPL_HPP_ +#pragma once #include "axom/primal/geometry/Point.hpp" #include "axom/primal/geometry/BoundingBox.hpp" @@ -603,5 +602,3 @@ bool intersect_nurbscurves(const NURBSCurve &n1, } // end namespace detail } // end namespace primal } // end namespace axom - -#endif // AXOM_PRIMAL_INTERSECT_BEZIER_IMPL_HPP_ diff --git a/src/axom/primal/operators/detail/intersect_bounding_box_impl.hpp b/src/axom/primal/operators/detail/intersect_bounding_box_impl.hpp index 0e453c0efd..6d971a788b 100644 --- a/src/axom/primal/operators/detail/intersect_bounding_box_impl.hpp +++ b/src/axom/primal/operators/detail/intersect_bounding_box_impl.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_PRIMAL_INTERSECT_BOUNDING_BOX_IMPL_HPP_ -#define AXOM_PRIMAL_INTERSECT_BOUNDING_BOX_IMPL_HPP_ +#pragma once #include "axom/core/Macros.hpp" @@ -115,5 +114,3 @@ AXOM_HOST_DEVICE inline bool intersect_bounding_box(const T& xmin1, } // namespace detail } // namespace primal } // namespace axom - -#endif // AXOM_PRIMAL_INTERSECT_BOUNDING_BOX_IMPL_HPP_ diff --git a/src/axom/primal/operators/detail/intersect_impl.hpp b/src/axom/primal/operators/detail/intersect_impl.hpp index 3bcbaa31de..ce3bcd3d94 100644 --- a/src/axom/primal/operators/detail/intersect_impl.hpp +++ b/src/axom/primal/operators/detail/intersect_impl.hpp @@ -11,8 +11,7 @@ * geometric primitives intersect */ -#ifndef AXOM_PRIMAL_INTERSECT_IMPL_HPP_ -#define AXOM_PRIMAL_INTERSECT_IMPL_HPP_ +#pragma once #include "axom/core/Macros.hpp" #include "axom/core/numerics/Determinants.hpp" @@ -2200,5 +2199,3 @@ bool select_candidates(const CandidateArrayType& tc, } // end namespace detail } // end namespace primal } // end namespace axom - -#endif // AXOM_PRIMAL_INTERSECT_IMPL_HPP_ diff --git a/src/axom/primal/operators/detail/intersect_patch_impl.hpp b/src/axom/primal/operators/detail/intersect_patch_impl.hpp index 59478a90c6..752cb5a565 100644 --- a/src/axom/primal/operators/detail/intersect_patch_impl.hpp +++ b/src/axom/primal/operators/detail/intersect_patch_impl.hpp @@ -10,8 +10,7 @@ * of rays and Bezier patches */ -#ifndef AXOM_PRIMAL_INTERSECT_PATCH_IMPL_HPP_ -#define AXOM_PRIMAL_INTERSECT_PATCH_IMPL_HPP_ +#pragma once #include "axom/primal/geometry/Point.hpp" #include "axom/primal/geometry/Polygon.hpp" @@ -202,5 +201,3 @@ bool intersect_line_patch(const Line &line, } // end namespace detail } // end namespace primal } // end namespace axom - -#endif // AXOM_PRIMAL_INTERSECT_PATCH_IMPL_HPP_ diff --git a/src/axom/primal/operators/detail/intersect_ray_impl.hpp b/src/axom/primal/operators/detail/intersect_ray_impl.hpp index fe1f1ed137..b201e2d986 100644 --- a/src/axom/primal/operators/detail/intersect_ray_impl.hpp +++ b/src/axom/primal/operators/detail/intersect_ray_impl.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_PRIMAL_INTERSECT_RAY_IMPL_HPP_ -#define AXOM_PRIMAL_INTERSECT_RAY_IMPL_HPP_ +#pragma once // core includes #include "axom/core/numerics/floating_point_limits.hpp" @@ -409,5 +408,3 @@ AXOM_HOST_DEVICE inline bool intersect_line(const primal::Line& L, } // namespace detail } // namespace primal } // namespace axom - -#endif // AXOM_PRIMAL_INTERSECT_RAY_IMPL_HPP_ diff --git a/src/axom/primal/operators/detail/predicate_determinants.hpp b/src/axom/primal/operators/detail/predicate_determinants.hpp index 2ad6479726..29db51b7d7 100644 --- a/src/axom/primal/operators/detail/predicate_determinants.hpp +++ b/src/axom/primal/operators/detail/predicate_determinants.hpp @@ -27,8 +27,7 @@ * characterizing where the double-precision sign is and is not reliable. */ -#ifndef AXOM_PRIMAL_PREDICATE_DETERMINANTS_HPP_ -#define AXOM_PRIMAL_PREDICATE_DETERMINANTS_HPP_ +#pragma once #include "axom/core/numerics/Determinants.hpp" @@ -132,5 +131,3 @@ inline double in_sphere_determinant(const Point& q, } // namespace detail } // namespace primal } // namespace axom - -#endif // AXOM_PRIMAL_PREDICATE_DETERMINANTS_HPP_ diff --git a/src/axom/primal/operators/detail/slice_impl.hpp b/src/axom/primal/operators/detail/slice_impl.hpp index 6d54a255ba..e830c3d1b0 100644 --- a/src/axom/primal/operators/detail/slice_impl.hpp +++ b/src/axom/primal/operators/detail/slice_impl.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_PRIMAL_SLICE_IMPL_HPP_ -#define AXOM_PRIMAL_SLICE_IMPL_HPP_ +#pragma once namespace axom { @@ -134,4 +133,3 @@ AXOM_HOST_DEVICE primal::Polygon slice_tet_plane( } // namespace detail } // namespace primal } // namespace axom -#endif diff --git a/src/axom/primal/operators/detail/winding_number_2d_impl.hpp b/src/axom/primal/operators/detail/winding_number_2d_impl.hpp index 52fdede57a..6c1875ef24 100644 --- a/src/axom/primal/operators/detail/winding_number_2d_impl.hpp +++ b/src/axom/primal/operators/detail/winding_number_2d_impl.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef PRIMAL_WINDING_NUMBER_2D_IMPL_HPP_ -#define PRIMAL_WINDING_NUMBER_2D_IMPL_HPP_ +#pragma once // Axom includes #include "axom/config.hpp" @@ -602,5 +601,3 @@ double nurbs_winding_number(const Point& q, } // end namespace detail } // end namespace primal } // end namespace axom - -#endif diff --git a/src/axom/primal/operators/detail/winding_number_2d_memoization.hpp b/src/axom/primal/operators/detail/winding_number_2d_memoization.hpp index 50daa33b0e..16ea941642 100644 --- a/src/axom/primal/operators/detail/winding_number_2d_memoization.hpp +++ b/src/axom/primal/operators/detail/winding_number_2d_memoization.hpp @@ -11,8 +11,7 @@ * i.e. dynamically caching and reusing intermediate curve subdivisions. */ -#ifndef AXOM_PRIMAL_WINDING_NUMBER_2D_MEMOIZATION_HPP_ -#define AXOM_PRIMAL_WINDING_NUMBER_2D_MEMOIZATION_HPP_ +#pragma once #include "axom/core.hpp" #include "axom/slic.hpp" @@ -369,5 +368,3 @@ struct nurbs_cache_2d_traits } // namespace primal } // namespace axom - -#endif // AXOM_PRIMAL_WINDING_NUMBER_2D_MEMOIZATION_HPP_ diff --git a/src/axom/primal/operators/detail/winding_number_3d_impl.hpp b/src/axom/primal/operators/detail/winding_number_3d_impl.hpp index 9b1e9e48f1..ebc33e0ef7 100644 --- a/src/axom/primal/operators/detail/winding_number_3d_impl.hpp +++ b/src/axom/primal/operators/detail/winding_number_3d_impl.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef PRIMAL_WINDING_NUMBER_3D_IMPL_HPP_ -#define PRIMAL_WINDING_NUMBER_3D_IMPL_HPP_ +#pragma once // Axom includes #include "axom/config.hpp" @@ -923,5 +922,3 @@ double nurbs_winding_number(const Point& query, } // end namespace detail } // end namespace primal } // end namespace axom - -#endif diff --git a/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp b/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp index bafbd18b6b..5446b86431 100644 --- a/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp +++ b/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp @@ -11,8 +11,7 @@ * dynamically caching and reusing patch surface evaluations and tangents at quadrature points. */ -#ifndef AXOM_PRIMAL_WINDING_NUMBER_3D_MEMOIZATION_HPP_ -#define AXOM_PRIMAL_WINDING_NUMBER_3D_MEMOIZATION_HPP_ +#pragma once #include "axom/core.hpp" #include "axom/slic.hpp" @@ -494,5 +493,3 @@ struct nurbs_cache_3d_traits } // namespace primal } // namespace axom - -#endif // AXOM_PRIMAL_WINDING_NUMBER_3D_MEMOIZATION_HPP_ diff --git a/src/axom/primal/operators/evaluate_integral.hpp b/src/axom/primal/operators/evaluate_integral.hpp index 1bc47377a4..7e5e583c54 100644 --- a/src/axom/primal/operators/evaluate_integral.hpp +++ b/src/axom/primal/operators/evaluate_integral.hpp @@ -17,10 +17,7 @@ * evaluate curve-based integrals (e.g. via trimming curves). */ -#ifndef PRIMAL_EVAL_INTEGRAL_HPP_ -#define PRIMAL_EVAL_INTEGRAL_HPP_ +#pragma once #include "axom/primal/operators/evaluate_integral_curve.hpp" #include "axom/primal/operators/evaluate_integral_surface.hpp" - -#endif diff --git a/src/axom/primal/operators/evaluate_integral_curve.hpp b/src/axom/primal/operators/evaluate_integral_curve.hpp index 0bbd2be4d0..9e6d312be4 100644 --- a/src/axom/primal/operators/evaluate_integral_curve.hpp +++ b/src/axom/primal/operators/evaluate_integral_curve.hpp @@ -23,8 +23,7 @@ * https://doi.org/10.1016/j.cad.2020.102944 */ -#ifndef PRIMAL_EVAL_INTEGRAL_CURVE_HPP_ -#define PRIMAL_EVAL_INTEGRAL_CURVE_HPP_ +#pragma once // Axom includes #include "axom/core.hpp" @@ -363,5 +362,3 @@ LambdaRetType evaluate_area_integral(const axom::Array& carray, } // namespace primal } // end namespace axom - -#endif diff --git a/src/axom/primal/operators/evaluate_integral_surface.hpp b/src/axom/primal/operators/evaluate_integral_surface.hpp index 8ade0b431e..d7c9fcc74c 100644 --- a/src/axom/primal/operators/evaluate_integral_surface.hpp +++ b/src/axom/primal/operators/evaluate_integral_surface.hpp @@ -17,8 +17,7 @@ * https://doi.org/10.1016/j.cad.2021.103093 */ -#ifndef PRIMAL_EVAL_INTEGRAL_SURFACE_HPP_ -#define PRIMAL_EVAL_INTEGRAL_SURFACE_HPP_ +#pragma once // Axom includes #include "axom/core.hpp" @@ -405,5 +404,3 @@ LambdaRetType evaluate_volume_integral(const axom::Array>& patc } // namespace primal } // end namespace axom - -#endif diff --git a/src/axom/primal/operators/in_curved_polygon.hpp b/src/axom/primal/operators/in_curved_polygon.hpp index f129d20574..18c3dc7bc3 100644 --- a/src/axom/primal/operators/in_curved_polygon.hpp +++ b/src/axom/primal/operators/in_curved_polygon.hpp @@ -13,8 +13,7 @@ * Uses an adaptive winding number calculation */ -#ifndef AXOM_PRIMAL_IN_CURVED_POLYGON_HPP_ -#define AXOM_PRIMAL_IN_CURVED_POLYGON_HPP_ +#pragma once // Axom includes #include "axom/config.hpp" @@ -61,5 +60,3 @@ bool in_curved_polygon(const Point& query, } // namespace primal } // namespace axom - -#endif // AXOM_PRIMAL_IN_CURVED_POLYGON_H_ diff --git a/src/axom/primal/operators/in_polygon.hpp b/src/axom/primal/operators/in_polygon.hpp index 9adb607857..ae485c3677 100644 --- a/src/axom/primal/operators/in_polygon.hpp +++ b/src/axom/primal/operators/in_polygon.hpp @@ -13,8 +13,7 @@ * Uses a ray casting algorithm */ -#ifndef AXOM_PRIMAL_IN_POLYGON_HPP_ -#define AXOM_PRIMAL_IN_POLYGON_HPP_ +#pragma once // Axom includes #include "axom/config.hpp" @@ -62,5 +61,3 @@ bool in_polygon(const Point& query, } // namespace primal } // namespace axom - -#endif // AXOM_PRIMAL_IN_CURVED_POLYGON_H_ diff --git a/src/axom/primal/operators/in_polyhedron.hpp b/src/axom/primal/operators/in_polyhedron.hpp index 01aab9bd9c..98d138cd55 100644 --- a/src/axom/primal/operators/in_polyhedron.hpp +++ b/src/axom/primal/operators/in_polyhedron.hpp @@ -13,8 +13,7 @@ * Uses a winding number algorithm */ -#ifndef AXOM_PRIMAL_IN_POLYHEDRON_HPP_ -#define AXOM_PRIMAL_IN_POLYHEDRON_HPP_ +#pragma once // Axom includes #include "axom/config.hpp" @@ -64,5 +63,3 @@ bool in_polyhedron(const Point& query, } // namespace primal } // namespace axom - -#endif // AXOM_PRIMAL_IN_POLYHEDRON_H_ diff --git a/src/axom/primal/operators/in_sphere.hpp b/src/axom/primal/operators/in_sphere.hpp index 192575aace..85c5ea8126 100644 --- a/src/axom/primal/operators/in_sphere.hpp +++ b/src/axom/primal/operators/in_sphere.hpp @@ -19,8 +19,7 @@ * and in_sphere_orientation() for the tolerance (EPS) scaling caveat. */ -#ifndef AXOM_PRIMAL_IN_SPHERE_H_ -#define AXOM_PRIMAL_IN_SPHERE_H_ +#pragma once #include "axom/core.hpp" #include "axom/primal/geometry/Point.hpp" @@ -289,5 +288,3 @@ inline bool in_sphere(const BoundingBox& bb, const Sphere& circle) } // namespace primal } // namespace axom - -#endif // AXOM_PRIMAL_IN_SPHERE_H_ diff --git a/src/axom/primal/operators/intersect.hpp b/src/axom/primal/operators/intersect.hpp index 3feb27c8d3..6ce637c286 100644 --- a/src/axom/primal/operators/intersect.hpp +++ b/src/axom/primal/operators/intersect.hpp @@ -10,8 +10,7 @@ * \brief Consists of functions to test intersection among geometric primitives. */ -#ifndef AXOM_PRIMAL_INTERSECT_HPP_ -#define AXOM_PRIMAL_INTERSECT_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core/Macros.hpp" @@ -1484,5 +1483,3 @@ bool intersect(const NURBSCurve& n1, } // namespace primal } // namespace axom - -#endif // AXOM_PRIMAL_INTERSECT_HPP_ diff --git a/src/axom/primal/operators/intersection_volume.hpp b/src/axom/primal/operators/intersection_volume.hpp index bd79c4e040..6decba7b4e 100644 --- a/src/axom/primal/operators/intersection_volume.hpp +++ b/src/axom/primal/operators/intersection_volume.hpp @@ -12,8 +12,7 @@ * another primal primitive */ -#ifndef AXOM_PRIMAL_INTERSECTION_VOLUME_HPP_ -#define AXOM_PRIMAL_INTERSECTION_VOLUME_HPP_ +#pragma once #include "axom/primal/geometry/Tetrahedron.hpp" #include "axom/primal/geometry/Octahedron.hpp" @@ -198,5 +197,3 @@ AXOM_HOST_DEVICE T intersection_volume(const Tetrahedron& tet1, } // namespace primal } // namespace axom - -#endif // AXOM_PRIMAL_INTERSECTION_VOLUME_HPP_ diff --git a/src/axom/primal/operators/is_convex.hpp b/src/axom/primal/operators/is_convex.hpp index 54486eac0c..113d8059cc 100644 --- a/src/axom/primal/operators/is_convex.hpp +++ b/src/axom/primal/operators/is_convex.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_PRIMAL_IS_CONVEX_HPP_ -#define AXOM_PRIMAL_IS_CONVEX_HPP_ +#pragma once #include "axom/primal/geometry/Segment.hpp" #include "axom/primal/geometry/Polygon.hpp" @@ -68,5 +67,3 @@ bool is_convex(const Polygon& poly, double EPS = 1e-8) } // namespace primal } // namespace axom - -#endif diff --git a/src/axom/primal/operators/orientation.hpp b/src/axom/primal/operators/orientation.hpp index eb4cdc8913..a9c1450dd0 100644 --- a/src/axom/primal/operators/orientation.hpp +++ b/src/axom/primal/operators/orientation.hpp @@ -15,8 +15,7 @@ * See detail/predicate_determinants.hpp for the precision/robustness discussion. */ -#ifndef AXOM_PRIMAL_ORIENTATION_HPP_ -#define AXOM_PRIMAL_ORIENTATION_HPP_ +#pragma once #include "axom/core/numerics/Determinants.hpp" #include "axom/core/utilities/Utilities.hpp" @@ -126,5 +125,3 @@ inline int orientation(const Point& p, const Segment& seg, double EP } // namespace primal } // namespace axom - -#endif // AXOM_PRIMAL_ORIENTATION_HPP_ diff --git a/src/axom/primal/operators/slice.hpp b/src/axom/primal/operators/slice.hpp index 70ba91c03d..55ade5ab84 100644 --- a/src/axom/primal/operators/slice.hpp +++ b/src/axom/primal/operators/slice.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_PRIMAL_SLICE_HPP_ -#define AXOM_PRIMAL_SLICE_HPP_ +#pragma once #include "axom/core/utilities/Utilities.hpp" #include "axom/primal/geometry/Point.hpp" @@ -48,4 +47,3 @@ AXOM_HOST_DEVICE primal::Polygon slice(const primal } // namespace primal } // namespace axom -#endif diff --git a/src/axom/primal/operators/split.hpp b/src/axom/primal/operators/split.hpp index e7c960b99d..36bf068689 100644 --- a/src/axom/primal/operators/split.hpp +++ b/src/axom/primal/operators/split.hpp @@ -11,8 +11,7 @@ * (a collection of) another primal primitive */ -#ifndef AXOM_PRIMAL_SPLIT_HPP_ -#define AXOM_PRIMAL_SPLIT_HPP_ +#pragma once #include "axom/core/Array.hpp" #include "axom/primal/geometry/Octahedron.hpp" @@ -118,5 +117,3 @@ AXOM_HOST_DEVICE void split(const Octahedron& oct, Tetrahedron* ou } // namespace primal } // namespace axom - -#endif // AXOM_PRIMAL_SPLIT_HPP_ diff --git a/src/axom/primal/operators/squared_distance.hpp b/src/axom/primal/operators/squared_distance.hpp index 9951148896..b2d3af627b 100644 --- a/src/axom/primal/operators/squared_distance.hpp +++ b/src/axom/primal/operators/squared_distance.hpp @@ -11,8 +11,7 @@ * the squared distance between two geometric entities. */ -#ifndef AXOM_PRIMAL_SQUAREDDISTANCE_HPP_ -#define AXOM_PRIMAL_SQUAREDDISTANCE_HPP_ +#pragma once #include "axom/primal/geometry/BoundingBox.hpp" #include "axom/primal/geometry/Point.hpp" @@ -164,5 +163,3 @@ AXOM_HOST_DEVICE inline double squared_distance(const Point& P, } // namespace primal } // namespace axom - -#endif // AXOM_PRIMAL_SQUAREDDISTANCE_HPP_ diff --git a/src/axom/primal/operators/winding_number.hpp b/src/axom/primal/operators/winding_number.hpp index 0e60184395..95203be499 100644 --- a/src/axom/primal/operators/winding_number.hpp +++ b/src/axom/primal/operators/winding_number.hpp @@ -11,8 +11,7 @@ * for points with respect to various geometric objects. */ -#ifndef AXOM_PRIMAL_WINDING_NUMBER_HPP_ -#define AXOM_PRIMAL_WINDING_NUMBER_HPP_ +#pragma once // Axom includes #include "axom/core.hpp" @@ -936,5 +935,3 @@ axom::Array winding_number(const axom::Array>& query_arr, } // namespace primal } // namespace axom - -#endif // AXOM_PRIMAL_WINDING_NUMBER_H_ diff --git a/src/axom/primal/utils/ZipBoundingBox.hpp b/src/axom/primal/utils/ZipBoundingBox.hpp index 81e8e03ccb..ab81e54322 100644 --- a/src/axom/primal/utils/ZipBoundingBox.hpp +++ b/src/axom/primal/utils/ZipBoundingBox.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_PRIMAL_ZIP_BOUNDINGBOX_HPP_ -#define AXOM_PRIMAL_ZIP_BOUNDINGBOX_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core/StackArray.hpp" @@ -84,5 +83,3 @@ struct ZipBase> } // namespace detail } // namespace primal } // namespace axom - -#endif // AXOM_PRIMAL_ZIP_BOUNDINGBOX_HPP_ diff --git a/src/axom/primal/utils/ZipIndexable.hpp b/src/axom/primal/utils/ZipIndexable.hpp index 7394ebc5d9..2af7efa8ac 100644 --- a/src/axom/primal/utils/ZipIndexable.hpp +++ b/src/axom/primal/utils/ZipIndexable.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_PRIMAL_ZIP_INDEXABLE_HPP_ -#define AXOM_PRIMAL_ZIP_INDEXABLE_HPP_ +#pragma once #include "axom/config.hpp" @@ -55,5 +54,3 @@ class ZipIndexable : detail::ZipBase } // namespace primal } // namespace axom - -#endif // AXOM_PRIMAL_ZIP_INDEXABLE_HPP diff --git a/src/axom/primal/utils/ZipPoint.hpp b/src/axom/primal/utils/ZipPoint.hpp index 09dd84ab96..3090125476 100644 --- a/src/axom/primal/utils/ZipPoint.hpp +++ b/src/axom/primal/utils/ZipPoint.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_PRIMAL_ZIP_POINT_HPP_ -#define AXOM_PRIMAL_ZIP_POINT_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core/StackArray.hpp" @@ -76,5 +75,3 @@ struct ZipBase> } // namespace detail } // namespace primal } // namespace axom - -#endif // AXOM_PRIMAL_ZIP_POINT_HPP_ diff --git a/src/axom/primal/utils/ZipRay.hpp b/src/axom/primal/utils/ZipRay.hpp index cc3e8527b3..7b2209ae68 100644 --- a/src/axom/primal/utils/ZipRay.hpp +++ b/src/axom/primal/utils/ZipRay.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_PRIMAL_ZIP_RAY_HPP_ -#define AXOM_PRIMAL_ZIP_RAY_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core/StackArray.hpp" @@ -87,5 +86,3 @@ struct ZipBase> } // namespace detail } // namespace primal } // namespace axom - -#endif // AXOM_PRIMAL_ZIP_RAY_HPP_ diff --git a/src/axom/primal/utils/ZipVector.hpp b/src/axom/primal/utils/ZipVector.hpp index 800df9c01a..7d0f1119aa 100644 --- a/src/axom/primal/utils/ZipVector.hpp +++ b/src/axom/primal/utils/ZipVector.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_PRIMAL_ZIP_VECTOR_HPP_ -#define AXOM_PRIMAL_ZIP_VECTOR_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core/StackArray.hpp" @@ -73,5 +72,3 @@ struct ZipBase> } // namespace detail } // namespace primal } // namespace axom - -#endif // AXOM_PRIMAL_ZIP_VECTOR_HPP_ diff --git a/src/axom/quest/AllNearestNeighbors.hpp b/src/axom/quest/AllNearestNeighbors.hpp index bcd51e1546..48e353372c 100644 --- a/src/axom/quest/AllNearestNeighbors.hpp +++ b/src/axom/quest/AllNearestNeighbors.hpp @@ -9,8 +9,7 @@ * \brief Defines all-nearest-neighbor queries */ -#ifndef AXOM_QUEST_ALL_NEAREST_NEIGHBORS_HPP_ -#define AXOM_QUEST_ALL_NEAREST_NEIGHBORS_HPP_ +#pragma once namespace axom { @@ -62,5 +61,3 @@ void all_nearest_neighbors(const double* x, } // end namespace quest } // end namespace axom - -#endif // AXOM_QUEST_ALL_NEAREST_NEIGHBORS_HPP_ diff --git a/src/axom/quest/Delaunay.hpp b/src/axom/quest/Delaunay.hpp index 5b2c82cbe3..89b3644a48 100644 --- a/src/axom/quest/Delaunay.hpp +++ b/src/axom/quest/Delaunay.hpp @@ -10,8 +10,7 @@ * \brief Declares the public `quest::Delaunay` incremental 2D/3D triangulation API. */ -#ifndef QUEST_DELAUNAY_H_ -#define QUEST_DELAUNAY_H_ +#pragma once #include "axom/core.hpp" #include "axom/slic.hpp" @@ -734,5 +733,3 @@ constexpr typename Delaunay::IndexType Delaunay::INVALID_INDEX; #include "detail/DelaunayImpl.hpp" #undef AXOM_QUEST_DELAUNAY_FORCE_INLINE - -#endif // QUEST_DELAUNAY_H_ diff --git a/src/axom/quest/DiscreteShape.hpp b/src/axom/quest/DiscreteShape.hpp index 6f2cde1808..8147e09a63 100644 --- a/src/axom/quest/DiscreteShape.hpp +++ b/src/axom/quest/DiscreteShape.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_QUEST_DISCRETE_SHAPE_HPP -#define AXOM_QUEST_DISCRETE_SHAPE_HPP +#pragma once #include #include @@ -191,5 +190,3 @@ class DiscreteShape } // namespace quest } // namespace axom - -#endif diff --git a/src/axom/quest/Discretize.hpp b/src/axom/quest/Discretize.hpp index b0a9ddb987..bf06bb8f94 100644 --- a/src/axom/quest/Discretize.hpp +++ b/src/axom/quest/Discretize.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef QUEST_DISCRETIZE_HPP_ -#define QUEST_DISCRETIZE_HPP_ +#pragma once // Axom includes #include "axom/core/Macros.hpp" @@ -117,5 +116,3 @@ int mesh_from_discretized_polyline(const axom::ArrayView& octs, } // end namespace axom #include "detail/Discretize_detail.hpp" - -#endif // QUEST_DISCRETIZE_HPP_ diff --git a/src/axom/quest/DistributedClosestPoint.hpp b/src/axom/quest/DistributedClosestPoint.hpp index af7227d799..0151a843c4 100644 --- a/src/axom/quest/DistributedClosestPoint.hpp +++ b/src/axom/quest/DistributedClosestPoint.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef QUEST_DISTRIBUTED_CLOSEST_POINT_H_ -#define QUEST_DISTRIBUTED_CLOSEST_POINT_H_ +#pragma once #include "axom/config.hpp" #include "axom/core/execution/runtime_policy.hpp" @@ -212,5 +211,3 @@ class DistributedClosestPoint } // end namespace quest } // end namespace axom - -#endif // QUEST_DISTRIBUTED_CLOSEST_POINT_H_ diff --git a/src/axom/quest/FastApproximateGWN.hpp b/src/axom/quest/FastApproximateGWN.hpp index 7ba60a4961..5f6cf7c881 100644 --- a/src/axom/quest/FastApproximateGWN.hpp +++ b/src/axom/quest/FastApproximateGWN.hpp @@ -2,8 +2,7 @@ // other Axom Project Developers. See the top-level LICENSE file for details. // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_QUEST_FAST_APPROXIMATE_GWN_HPP -#define AXOM_QUEST_FAST_APPROXIMATE_GWN_HPP +#pragma once #include "axom/primal.hpp" #include @@ -704,5 +703,3 @@ axom::Array> subdivide_patches( } // end namespace quest } // end namespace axom - -#endif diff --git a/src/axom/quest/GWNMethods.hpp b/src/axom/quest/GWNMethods.hpp index ccd0354948..a79efcfd88 100644 --- a/src/axom/quest/GWNMethods.hpp +++ b/src/axom/quest/GWNMethods.hpp @@ -10,8 +10,7 @@ * \brief Helper classes and type traits for GWN Evaluation methods */ -#ifndef AXOM_QUEST_GWN_METHODS_HPP_ -#define AXOM_QUEST_GWN_METHODS_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core.hpp" @@ -1155,4 +1154,3 @@ IntegralStats compute_integrals(const mfem::GridFunction& gf) } // namespace quest } // namespace axom -#endif diff --git a/src/axom/quest/InOutOctree.hpp b/src/axom/quest/InOutOctree.hpp index 6af23a9f3f..f3b08fc52c 100644 --- a/src/axom/quest/InOutOctree.hpp +++ b/src/axom/quest/InOutOctree.hpp @@ -10,8 +10,7 @@ * \brief Defines an InOutOctree for containment queries on a surface. */ -#ifndef AXOM_QUEST_INOUT_OCTREE__HPP_ -#define AXOM_QUEST_INOUT_OCTREE__HPP_ +#pragma once #include "axom/core.hpp" #include "axom/core/NumericLimits.hpp" @@ -1598,5 +1597,3 @@ void InOutOctree::dumpDifferentColoredNeighborsMeshVTK(const std::string& n // Note: The following needs to be included after InOutOctree is defined #include "detail/inout/InOutOctreeMeshDumper.hpp" - -#endif // AXOM_QUEST_INOUT_OCTREE__HPP_ diff --git a/src/axom/quest/IntersectionShaper.hpp b/src/axom/quest/IntersectionShaper.hpp index ea12ad1499..465f3451e4 100644 --- a/src/axom/quest/IntersectionShaper.hpp +++ b/src/axom/quest/IntersectionShaper.hpp @@ -10,8 +10,7 @@ * \brief Helper class for intersection-based shaping queries */ -#ifndef AXOM_QUEST_INTERSECTION_SHAPER__HPP_ -#define AXOM_QUEST_INTERSECTION_SHAPER__HPP_ +#pragma once #include "axom/config.hpp" @@ -3026,5 +3025,3 @@ class IntersectionShaper : public Shaper } // end namespace quest } // end namespace axom - -#endif // AXOM_QUEST_INTERSECTION_SHAPER__HPP_ diff --git a/src/axom/quest/LinearizeCurves.hpp b/src/axom/quest/LinearizeCurves.hpp index 9a6393c99e..a69ac76850 100644 --- a/src/axom/quest/LinearizeCurves.hpp +++ b/src/axom/quest/LinearizeCurves.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef QUEST_LINEARIZE_CURVES_HPP_ -#define QUEST_LINEARIZE_CURVES_HPP_ +#pragma once // Axom includes #include "axom/config.hpp" @@ -91,5 +90,3 @@ class LinearizeCurves } // end namespace quest } // end namespace axom - -#endif diff --git a/src/axom/quest/MarchingCubes.hpp b/src/axom/quest/MarchingCubes.hpp index cdd8270766..db4c1b2c0d 100644 --- a/src/axom/quest/MarchingCubes.hpp +++ b/src/axom/quest/MarchingCubes.hpp @@ -11,8 +11,7 @@ * compute isocontour from a scalar field in a blueprint mesh. */ -#ifndef AXOM_QUEST_MARCHINGCUBES_H_ -#define AXOM_QUEST_MARCHINGCUBES_H_ +#pragma once #include "axom/config.hpp" @@ -364,4 +363,3 @@ class MarchingCubes } // namespace axom #endif // AXOM_USE_CONDUIT -#endif // AXOM_QUEST_MARCHINGCUBES_H_ diff --git a/src/axom/quest/MeshClipper.hpp b/src/axom/quest/MeshClipper.hpp index e1c68851f0..e4b945e595 100644 --- a/src/axom/quest/MeshClipper.hpp +++ b/src/axom/quest/MeshClipper.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_QUEST_MESHCLIPPER_HPP -#define AXOM_QUEST_MESHCLIPPER_HPP +#pragma once #include "axom/config.hpp" @@ -275,5 +274,3 @@ class MeshClipper } // namespace experimental } // namespace quest } // namespace axom - -#endif // AXOM_QUEST_MESHCLIPPER_HPP diff --git a/src/axom/quest/MeshClipperStrategy.hpp b/src/axom/quest/MeshClipperStrategy.hpp index a8b8ebc394..b321c1ba69 100644 --- a/src/axom/quest/MeshClipperStrategy.hpp +++ b/src/axom/quest/MeshClipperStrategy.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_QUEST_MESHCLIPPERSTRATEGY_HPP -#define AXOM_QUEST_MESHCLIPPERSTRATEGY_HPP +#pragma once #include "axom/config.hpp" @@ -436,5 +435,3 @@ class MeshClipperStrategy } // namespace experimental } // namespace quest } // namespace axom - -#endif // AXOM_QUEST_MESHCLIPPERSTRATEGY_HPP diff --git a/src/axom/quest/MeshTester.hpp b/src/axom/quest/MeshTester.hpp index 2a992a17e9..4c0f5cd0cf 100644 --- a/src/axom/quest/MeshTester.hpp +++ b/src/axom/quest/MeshTester.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_QUEST_MESH_TESTER_HPP_ -#define AXOM_QUEST_MESH_TESTER_HPP_ +#pragma once // Axom includes #include "axom/config.hpp" @@ -276,5 +275,3 @@ void weldTriMeshVertices(mint::UnstructuredMesh** surface_me } // namespace quest } // namespace axom - -#endif // AXOM_QUEST_MESH_TESTER_HPP_ diff --git a/src/axom/quest/MeshViewUtil.hpp b/src/axom/quest/MeshViewUtil.hpp index d488924a6a..f6a2d2214f 100644 --- a/src/axom/quest/MeshViewUtil.hpp +++ b/src/axom/quest/MeshViewUtil.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef QUEST_MESH_VIEW_UTIL_H_ -#define QUEST_MESH_VIEW_UTIL_H_ +#pragma once #include "axom/config.hpp" @@ -848,4 +847,3 @@ class MeshViewUtil } // end namespace axom #endif // AXOM_USE_CONDUIT -#endif // QUEST_MESH_VIEW_UTIL_H_ diff --git a/src/axom/quest/PointInCell.hpp b/src/axom/quest/PointInCell.hpp index 729068dcfa..5023596c7f 100644 --- a/src/axom/quest/PointInCell.hpp +++ b/src/axom/quest/PointInCell.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_QUEST_POINT_IN_CELL_HPP_ -#define AXOM_QUEST_POINT_IN_CELL_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core/Macros.hpp" @@ -373,5 +372,3 @@ class PointInCell } // end namespace quest } // end namespace axom - -#endif // AXOM_QUEST_POINT_IN_CELL_HPP_ diff --git a/src/axom/quest/SamplingShaper.hpp b/src/axom/quest/SamplingShaper.hpp index 7ac291c477..4859d8cd51 100644 --- a/src/axom/quest/SamplingShaper.hpp +++ b/src/axom/quest/SamplingShaper.hpp @@ -10,8 +10,7 @@ * \brief Helper class for sampling-based shaping queries */ -#ifndef AXOM_QUEST_SAMPLING_SHAPER__HPP_ -#define AXOM_QUEST_SAMPLING_SHAPER__HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core.hpp" @@ -1176,5 +1175,3 @@ class SamplingShaper : public Shaper } // namespace quest } // namespace axom - -#endif // AXOM_QUEST_SAMPLING_SHAPER__HPP_ diff --git a/src/axom/quest/ScatteredInterpolation.hpp b/src/axom/quest/ScatteredInterpolation.hpp index f396609a01..1308858317 100644 --- a/src/axom/quest/ScatteredInterpolation.hpp +++ b/src/axom/quest/ScatteredInterpolation.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef QUEST_SCATTERED_INTERPOLATION_H_ -#define QUEST_SCATTERED_INTERPOLATION_H_ +#pragma once #include "axom/core.hpp" #include "axom/core/NumericLimits.hpp" @@ -753,5 +752,3 @@ constexpr int ScatteredInterpolation::DIM; } // namespace quest } // namespace axom - -#endif // QUEST_SCATTERED_INTERPOLATION_H_ diff --git a/src/axom/quest/ShapeMesh.hpp b/src/axom/quest/ShapeMesh.hpp index ed5fc0d807..1f535bcb58 100644 --- a/src/axom/quest/ShapeMesh.hpp +++ b/src/axom/quest/ShapeMesh.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_QUEST_SHAPEMESH_HPP -#define AXOM_QUEST_SHAPEMESH_HPP +#pragma once #include "axom/config.hpp" @@ -507,5 +506,3 @@ AXOM_HOST_DEVICE inline void ShapeMesh::hexToTets(const HexahedronType& hex, Tet } // namespace experimental } // namespace quest } // namespace axom - -#endif // AXOM_QUEST_SHAPEMESH_HPP diff --git a/src/axom/quest/Shaper.hpp b/src/axom/quest/Shaper.hpp index a6de63f9eb..c6e3520734 100644 --- a/src/axom/quest/Shaper.hpp +++ b/src/axom/quest/Shaper.hpp @@ -10,8 +10,7 @@ * \brief Helper class for shaping queries */ -#ifndef AXOM_QUEST_SHAPER__HPP_ -#define AXOM_QUEST_SHAPER__HPP_ +#pragma once #include "axom/config.hpp" #ifndef AXOM_USE_KLEE @@ -274,5 +273,3 @@ class Shaper } // end namespace quest } // end namespace axom - -#endif // AXOM_QUEST_SHAPER__HPP_ diff --git a/src/axom/quest/SignedDistance.hpp b/src/axom/quest/SignedDistance.hpp index 1e7b59e83c..56806f700b 100644 --- a/src/axom/quest/SignedDistance.hpp +++ b/src/axom/quest/SignedDistance.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_QUEST_SIGNED_DISTANCE_HPP_ -#define AXOM_QUEST_SIGNED_DISTANCE_HPP_ +#pragma once // axom includes #include "axom/config.hpp" @@ -764,5 +763,3 @@ AXOM_HOST_DEVICE inline double SignedDistance::computeSign(con } // end namespace quest } // end namespace axom - -#endif // AXOM_QUEST_SIGNED_DISTANCE_HPP_ diff --git a/src/axom/quest/detail/AllNearestNeighbors_detail.hpp b/src/axom/quest/detail/AllNearestNeighbors_detail.hpp index b2353502d3..608f0bd037 100644 --- a/src/axom/quest/detail/AllNearestNeighbors_detail.hpp +++ b/src/axom/quest/detail/AllNearestNeighbors_detail.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_QUEST_ALL_NEAREST_NEIGHBORS_DETAIL_HPP_ -#define AXOM_QUEST_ALL_NEAREST_NEIGHBORS_DETAIL_HPP_ +#pragma once namespace axom { @@ -26,5 +25,3 @@ inline double squared_distance(double x1, double y1, double z1, double x2, doubl } // end namespace detail } // end namespace quest } // end namespace axom - -#endif // AXOM_QUEST_ALL_NEAREST_NEIGHBORS_DETAIL_HPP_ diff --git a/src/axom/quest/detail/DelaunayElementFinder.hpp b/src/axom/quest/detail/DelaunayElementFinder.hpp index d874cf9aab..7b6d501387 100644 --- a/src/axom/quest/detail/DelaunayElementFinder.hpp +++ b/src/axom/quest/detail/DelaunayElementFinder.hpp @@ -11,8 +11,7 @@ * location walks from nearby inserted vertices. */ -#ifndef AXOM_QUEST_DETAIL_DELAUNAY_ELEMENT_FINDER_HPP_ -#define AXOM_QUEST_DETAIL_DELAUNAY_ELEMENT_FINDER_HPP_ +#pragma once #include "axom/core.hpp" #include "axom/primal.hpp" @@ -287,5 +286,3 @@ class DelaunayElementFinder } // namespace detail } // namespace quest } // namespace axom - -#endif diff --git a/src/axom/quest/detail/DelaunayImpl.hpp b/src/axom/quest/detail/DelaunayImpl.hpp index 63f960674c..63c531eeee 100644 --- a/src/axom/quest/detail/DelaunayImpl.hpp +++ b/src/axom/quest/detail/DelaunayImpl.hpp @@ -19,8 +19,7 @@ * - VTK export for visualization */ -#ifndef AXOM_QUEST_DETAIL_DELAUNAY_IMPL_HPP_ -#define AXOM_QUEST_DETAIL_DELAUNAY_IMPL_HPP_ +#pragma once namespace axom { @@ -430,5 +429,3 @@ inline void Delaunay::generateInitialMesh(std::vector& points, } // namespace quest } // namespace axom - -#endif diff --git a/src/axom/quest/detail/DelaunayInsertionHelper.hpp b/src/axom/quest/detail/DelaunayInsertionHelper.hpp index 526e24bcea..52730737c5 100644 --- a/src/axom/quest/detail/DelaunayInsertionHelper.hpp +++ b/src/axom/quest/detail/DelaunayInsertionHelper.hpp @@ -18,8 +18,7 @@ * The helper is reused across insertions to avoid repeated allocations. */ -#ifndef AXOM_QUEST_DETAIL_DELAUNAY_INSERTION_HELPER_HPP_ -#define AXOM_QUEST_DETAIL_DELAUNAY_INSERTION_HELPER_HPP_ +#pragma once #include "axom/core.hpp" #include "axom/primal.hpp" @@ -336,5 +335,3 @@ class DelaunayInsertionHelper } // namespace detail } // namespace quest } // namespace axom - -#endif diff --git a/src/axom/quest/detail/DelaunayPointLocation.hpp b/src/axom/quest/detail/DelaunayPointLocation.hpp index a1eb882a49..c83b738136 100644 --- a/src/axom/quest/detail/DelaunayPointLocation.hpp +++ b/src/axom/quest/detail/DelaunayPointLocation.hpp @@ -15,8 +15,7 @@ * Fallback strategies handle edge cases (cycles, numerical issues). */ -#ifndef AXOM_QUEST_DETAIL_DELAUNAY_POINT_LOCATION_HPP_ -#define AXOM_QUEST_DETAIL_DELAUNAY_POINT_LOCATION_HPP_ +#pragma once namespace axom { @@ -457,5 +456,3 @@ inline typename Delaunay::IndexType Delaunay::findContainingElement(co } // namespace quest } // namespace axom - -#endif diff --git a/src/axom/quest/detail/DelaunayValidation.hpp b/src/axom/quest/detail/DelaunayValidation.hpp index 45916870bb..df034e48ba 100644 --- a/src/axom/quest/detail/DelaunayValidation.hpp +++ b/src/axom/quest/detail/DelaunayValidation.hpp @@ -22,8 +22,7 @@ * - Boundary coordinate tolerance computation */ -#ifndef AXOM_QUEST_DETAIL_DELAUNAY_VALIDATION_HPP_ -#define AXOM_QUEST_DETAIL_DELAUNAY_VALIDATION_HPP_ +#pragma once namespace axom { @@ -948,5 +947,3 @@ inline void Delaunay::validateInsertedBall(IndexType new_pt_i, } // namespace quest } // namespace axom - -#endif diff --git a/src/axom/quest/detail/Discretize_detail.hpp b/src/axom/quest/detail/Discretize_detail.hpp index 0272420317..5d0b158174 100644 --- a/src/axom/quest/detail/Discretize_detail.hpp +++ b/src/axom/quest/detail/Discretize_detail.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_QUEST_DISCRETIZE_DETAIL_ -#define AXOM_QUEST_DISCRETIZE_DETAIL_ +#pragma once #include "axom/primal/constants.hpp" #include "math.h" @@ -313,5 +312,3 @@ bool discretize(const axom::ArrayView &polyline, } // end namespace quest } // end namespace axom - -#endif // AXOM_QUEST_DISCRETIZE_DETAIL_ diff --git a/src/axom/quest/detail/DistributedClosestPointImpl.hpp b/src/axom/quest/detail/DistributedClosestPointImpl.hpp index c3d70854ab..d932a92583 100644 --- a/src/axom/quest/detail/DistributedClosestPointImpl.hpp +++ b/src/axom/quest/detail/DistributedClosestPointImpl.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef QUEST_DISTRIBUTED_CLOSEST_POINT_IMPL_H_ -#define QUEST_DISTRIBUTED_CLOSEST_POINT_IMPL_H_ +#pragma once #include "axom/config.hpp" #include "axom/core.hpp" @@ -1101,5 +1100,3 @@ class DistributedClosestPointExec : public DistributedClosestPointImpl } // end namespace quest } // end namespace axom - -#endif // QUEST_DISTRIBUTED_CLOSEST_POINT_IMPL_H_ diff --git a/src/axom/quest/detail/MarchingCubesImpl.hpp b/src/axom/quest/detail/MarchingCubesImpl.hpp index a920d32234..48d0b98955 100644 --- a/src/axom/quest/detail/MarchingCubesImpl.hpp +++ b/src/axom/quest/detail/MarchingCubesImpl.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "axom/config.hpp" // Implementation requires Conduit. diff --git a/src/axom/quest/detail/MarchingCubesSingleDomain.hpp b/src/axom/quest/detail/MarchingCubesSingleDomain.hpp index 3ebde30ed0..64adec944a 100644 --- a/src/axom/quest/detail/MarchingCubesSingleDomain.hpp +++ b/src/axom/quest/detail/MarchingCubesSingleDomain.hpp @@ -11,8 +11,7 @@ * compute isocontour from a scalar field in a blueprint mesh. */ -#ifndef AXOM_QUEST_MARCHINGCUBESSINGLEDOMAIN_H_ -#define AXOM_QUEST_MARCHINGCUBESSINGLEDOMAIN_H_ +#pragma once #include "axom/config.hpp" @@ -263,4 +262,3 @@ class MarchingCubesSingleDomain } // namespace axom #endif // AXOM_USE_CONDUIT -#endif // AXOM_QUEST_MARCHINGCUBES_H_ diff --git a/src/axom/quest/detail/MeshTester_detail.hpp b/src/axom/quest/detail/MeshTester_detail.hpp index f9921d444d..bcf7d93284 100644 --- a/src/axom/quest/detail/MeshTester_detail.hpp +++ b/src/axom/quest/detail/MeshTester_detail.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_QUEST_MESH_TESTER_DETAIL_HPP_ -#define AXOM_QUEST_MESH_TESTER_DETAIL_HPP_ +#pragma once // Axom includes #include "axom/config.hpp" @@ -470,5 +469,3 @@ struct CandidateFinder } // namespace axom #undef MESH_TESTER_MUTABLE_LAMBDA - -#endif // AXOM_QUEST_MESH_TESTER_DETAIL_HPP_ diff --git a/src/axom/quest/detail/PointFinder.hpp b/src/axom/quest/detail/PointFinder.hpp index 234695f91e..1329f0e946 100644 --- a/src/axom/quest/detail/PointFinder.hpp +++ b/src/axom/quest/detail/PointFinder.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_QUEST_POINT_IN_CELL_POINT_FINDER_HPP_ -#define AXOM_QUEST_POINT_IN_CELL_POINT_FINDER_HPP_ +#pragma once #include "axom/spin/ImplicitGrid.hpp" #include "axom/primal/geometry/BoundingBox.hpp" @@ -347,5 +346,3 @@ class PointFinder } // end namespace detail } // end namespace quest } // end namespace axom - -#endif // AXOM_QUEST_POINT_IN_CELL_POINT_FINDER_HPP_ diff --git a/src/axom/quest/detail/PointInCellMeshWrapper_mfem.hpp b/src/axom/quest/detail/PointInCellMeshWrapper_mfem.hpp index 5946d1aefb..4651f0e8fe 100644 --- a/src/axom/quest/detail/PointInCellMeshWrapper_mfem.hpp +++ b/src/axom/quest/detail/PointInCellMeshWrapper_mfem.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_QUEST_POINT_IN_CELL_MFEM_IMPL_HPP_ -#define AXOM_QUEST_POINT_IN_CELL_MFEM_IMPL_HPP_ +#pragma once /*! * \file PointInCellMeshWrapper_mfem.hpp @@ -502,5 +501,3 @@ class PointInCellMeshWrapper } // end namespace detail } // end namespace quest } // end namespace axom - -#endif // AXOM_QUEST_POINT_IN_CELL_MFEM_IMPL_HPP_ diff --git a/src/axom/quest/detail/clipping/HexClipper.hpp b/src/axom/quest/detail/clipping/HexClipper.hpp index 8465426694..9f0eb03c05 100644 --- a/src/axom/quest/detail/clipping/HexClipper.hpp +++ b/src/axom/quest/detail/clipping/HexClipper.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_QUEST_HEXCLIPPER_HPP -#define AXOM_QUEST_HEXCLIPPER_HPP +#pragma once #include "axom/klee/Geometry.hpp" #include "axom/quest/MeshClipperStrategy.hpp" @@ -116,5 +115,3 @@ class HexClipper : public MeshClipperStrategy } // namespace experimental } // namespace quest } // namespace axom - -#endif // AXOM_QUEST_HEXCLIPPER_HPP diff --git a/src/axom/quest/detail/clipping/MeshClipperImpl.hpp b/src/axom/quest/detail/clipping/MeshClipperImpl.hpp index 242258fa03..622e6aa290 100644 --- a/src/axom/quest/detail/clipping/MeshClipperImpl.hpp +++ b/src/axom/quest/detail/clipping/MeshClipperImpl.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_MESHCLIPPERIMPL_HPP_ -#define AXOM_MESHCLIPPERIMPL_HPP_ +#pragma once #include "axom/config.hpp" @@ -931,5 +930,3 @@ class MeshClipperImpl : public MeshClipper::Impl } // namespace experimental } // end namespace quest } // end namespace axom - -#endif // AXOM_MESHCLIPPERIMPL_HPP_ diff --git a/src/axom/quest/detail/clipping/MonotonicZSORClipper.hpp b/src/axom/quest/detail/clipping/MonotonicZSORClipper.hpp index a0e5b93306..aba0e98d42 100644 --- a/src/axom/quest/detail/clipping/MonotonicZSORClipper.hpp +++ b/src/axom/quest/detail/clipping/MonotonicZSORClipper.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_QUEST_MONOTONICZSORCLIPPER_HPP -#define AXOM_QUEST_MONOTONICZSORCLIPPER_HPP +#pragma once #include "axom/klee/Geometry.hpp" #include "axom/quest/MeshClipperStrategy.hpp" @@ -219,5 +218,3 @@ class MonotonicZSORClipper : public MeshClipperStrategy } // namespace experimental } // namespace quest } // namespace axom - -#endif // AXOM_QUEST_FSORCLIPPER_HPP diff --git a/src/axom/quest/detail/clipping/Plane3DClipper.hpp b/src/axom/quest/detail/clipping/Plane3DClipper.hpp index 7b9ac092f4..74a04917f8 100644 --- a/src/axom/quest/detail/clipping/Plane3DClipper.hpp +++ b/src/axom/quest/detail/clipping/Plane3DClipper.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_QUEST_PLANE3DCLIPPER_HPP -#define AXOM_QUEST_PLANE3DCLIPPER_HPP +#pragma once #include "axom/klee/Geometry.hpp" #include "axom/quest/MeshClipperStrategy.hpp" @@ -113,5 +112,3 @@ class Plane3DClipper : public MeshClipperStrategy } // namespace experimental } // namespace quest } // namespace axom - -#endif // AXOM_QUEST_PLANE3DCLIPPER_HPP diff --git a/src/axom/quest/detail/clipping/SORClipper.hpp b/src/axom/quest/detail/clipping/SORClipper.hpp index a63202ecd9..d0556ef2a8 100644 --- a/src/axom/quest/detail/clipping/SORClipper.hpp +++ b/src/axom/quest/detail/clipping/SORClipper.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_QUEST_SORCLIPPER_HPP -#define AXOM_QUEST_SORCLIPPER_HPP +#pragma once #include "axom/klee/Geometry.hpp" #include "axom/quest/MeshClipperStrategy.hpp" @@ -115,5 +114,3 @@ class SORClipper : public MeshClipperStrategy } // namespace experimental } // namespace quest } // namespace axom - -#endif // AXOM_QUEST_SORCLIPPER_HPP diff --git a/src/axom/quest/detail/clipping/SphereClipper.hpp b/src/axom/quest/detail/clipping/SphereClipper.hpp index 3b96391fb2..e43bf4b40d 100644 --- a/src/axom/quest/detail/clipping/SphereClipper.hpp +++ b/src/axom/quest/detail/clipping/SphereClipper.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_QUEST_SPHERECLIPPER_HPP -#define AXOM_QUEST_SPHERECLIPPER_HPP +#pragma once #include "axom/klee/Geometry.hpp" #include "axom/quest/MeshClipperStrategy.hpp" @@ -95,5 +94,3 @@ class SphereClipper : public MeshClipperStrategy } // namespace experimental } // namespace quest } // namespace axom - -#endif // AXOM_QUEST_SPHERECLIPPER_HPP diff --git a/src/axom/quest/detail/clipping/TetClipper.hpp b/src/axom/quest/detail/clipping/TetClipper.hpp index cbb4b03d92..56f3d7cb35 100644 --- a/src/axom/quest/detail/clipping/TetClipper.hpp +++ b/src/axom/quest/detail/clipping/TetClipper.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_QUEST_TETCLIPPER_HPP -#define AXOM_QUEST_TETCLIPPER_HPP +#pragma once #include "axom/klee/Geometry.hpp" #include "axom/quest/MeshClipperStrategy.hpp" @@ -93,5 +92,3 @@ class TetClipper : public MeshClipperStrategy } // namespace experimental } // namespace quest } // namespace axom - -#endif // AXOM_QUEST_TETCLIPPER_HPP diff --git a/src/axom/quest/detail/clipping/TetMeshClipper.hpp b/src/axom/quest/detail/clipping/TetMeshClipper.hpp index a901930054..2c83238029 100644 --- a/src/axom/quest/detail/clipping/TetMeshClipper.hpp +++ b/src/axom/quest/detail/clipping/TetMeshClipper.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_QUEST_TETMESHCLIPPER_HPP -#define AXOM_QUEST_TETMESHCLIPPER_HPP +#pragma once #include "axom/klee/Geometry.hpp" #include "axom/quest/MeshClipperStrategy.hpp" @@ -180,5 +179,3 @@ class TetMeshClipper : public MeshClipperStrategy } // namespace experimental } // namespace quest } // namespace axom - -#endif // AXOM_QUEST_TETMESHCLIPPER_HPP diff --git a/src/axom/quest/detail/inout/BlockData.hpp b/src/axom/quest/detail/inout/BlockData.hpp index 38797105da..5a42759db6 100644 --- a/src/axom/quest/detail/inout/BlockData.hpp +++ b/src/axom/quest/detail/inout/BlockData.hpp @@ -10,8 +10,7 @@ * \brief Defines helper classes for data associated with InOutOctree blocks. */ -#ifndef AXOM_QUEST_INOUT_OCTREE_BLOCKDATA__HPP_ -#define AXOM_QUEST_INOUT_OCTREE_BLOCKDATA__HPP_ +#pragma once #include "axom/core.hpp" #include "axom/slic.hpp" @@ -422,5 +421,3 @@ struct axom::fmt::formatter : ostream_formatter template <> struct axom::fmt::formatter : ostream_formatter { }; - -#endif // AXOM_QUEST_INOUT_OCTREE_BLOCKDATA__HPP_ diff --git a/src/axom/quest/detail/inout/InOutOctreeMeshDumper.hpp b/src/axom/quest/detail/inout/InOutOctreeMeshDumper.hpp index 1dac5794a8..d38071c5ca 100644 --- a/src/axom/quest/detail/inout/InOutOctreeMeshDumper.hpp +++ b/src/axom/quest/detail/inout/InOutOctreeMeshDumper.hpp @@ -10,8 +10,7 @@ * \brief Defines helper class to write meshes for InOutOctree instances */ -#ifndef AXOM_QUEST_INOUT_OCTREE_MESHDUMPER__HPP_ -#define AXOM_QUEST_INOUT_OCTREE_MESHDUMPER__HPP_ +#pragma once #include "axom/core.hpp" #include "axom/slic.hpp" @@ -832,5 +831,3 @@ class InOutOctreeMeshDumper<3> : public InOutOctreeMeshDumperBase<3, InOutOctree } // namespace detail } // namespace quest } // namespace axom - -#endif // AXOM_QUEST_INOUT_OCTREE_MESHDUMPER__HPP_ diff --git a/src/axom/quest/detail/inout/InOutOctreeStats.hpp b/src/axom/quest/detail/inout/InOutOctreeStats.hpp index 4c12d067d2..95989db7e2 100644 --- a/src/axom/quest/detail/inout/InOutOctreeStats.hpp +++ b/src/axom/quest/detail/inout/InOutOctreeStats.hpp @@ -10,8 +10,7 @@ * \brief Defines helper class to generate statistics about an InOutOctree. */ -#ifndef AXOM_QUEST_INOUT_OCTREE_STATS__HPP_ -#define AXOM_QUEST_INOUT_OCTREE_STATS__HPP_ +#pragma once #include "axom/core.hpp" #include "axom/slic.hpp" @@ -336,5 +335,3 @@ class InOutOctreeStats } // namespace detail } // namespace quest } // namespace axom - -#endif // AXOM_QUEST_INOUT_OCTREE_STATS__HPP_ diff --git a/src/axom/quest/detail/inout/InOutOctreeValidator.hpp b/src/axom/quest/detail/inout/InOutOctreeValidator.hpp index da3cce2e7c..6613d61fdb 100644 --- a/src/axom/quest/detail/inout/InOutOctreeValidator.hpp +++ b/src/axom/quest/detail/inout/InOutOctreeValidator.hpp @@ -10,8 +10,7 @@ * \brief Defines helper class to validate an InOutOctree instance */ -#ifndef AXOM_QUEST_INOUT_OCTREE_VALIDATOR__HPP_ -#define AXOM_QUEST_INOUT_OCTREE_VALIDATOR__HPP_ +#pragma once #include "axom/core.hpp" #include "axom/slic.hpp" @@ -336,5 +335,3 @@ class InOutOctreeValidator } // namespace detail } // namespace quest } // namespace axom - -#endif // AXOM_QUEST_INOUT_OCTREE_VALIDATOR__HPP_ diff --git a/src/axom/quest/detail/inout/MeshWrapper.hpp b/src/axom/quest/detail/inout/MeshWrapper.hpp index 86a879162f..b3e8e404d8 100644 --- a/src/axom/quest/detail/inout/MeshWrapper.hpp +++ b/src/axom/quest/detail/inout/MeshWrapper.hpp @@ -10,8 +10,7 @@ * \brief Defines a templated mesh wrapper class for the InOutOctree. */ -#ifndef AXOM_QUEST_INOUT_OCTREE_MESH_WRAPPER__HPP_ -#define AXOM_QUEST_INOUT_OCTREE_MESH_WRAPPER__HPP_ +#pragma once #include "axom/core.hpp" #include "axom/slic.hpp" @@ -592,5 +591,3 @@ class MeshWrapper<3> : public SimplexMeshWrapper<3, MeshWrapper<3>> } // namespace quest } // namespace axom - -#endif // AXOM_QUEST_INOUT_OCTREE_MESH_WRAPPER__HPP_ diff --git a/src/axom/quest/detail/marching_cubes_lookup.hpp b/src/axom/quest/detail/marching_cubes_lookup.hpp index 4ede71da13..5c068183d7 100644 --- a/src/axom/quest/detail/marching_cubes_lookup.hpp +++ b/src/axom/quest/detail/marching_cubes_lookup.hpp @@ -10,6 +10,8 @@ // 2D case table // clang-format off +#pragma once + #ifdef _MC_LOOKUP_CASES2D /*! @brief Look-up table in 2D. diff --git a/src/axom/quest/detail/shaping/InOutSampler.hpp b/src/axom/quest/detail/shaping/InOutSampler.hpp index 45d8458b0b..f48873ad2d 100644 --- a/src/axom/quest/detail/shaping/InOutSampler.hpp +++ b/src/axom/quest/detail/shaping/InOutSampler.hpp @@ -10,8 +10,7 @@ * \brief Helper class for sampling-based shaping queries using the InOutOctree */ -#ifndef AXOM_QUEST_INOUT_SAMPLER__HPP_ -#define AXOM_QUEST_INOUT_SAMPLER__HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core.hpp" @@ -198,5 +197,3 @@ class InOutSampler } // namespace shaping } // namespace quest } // namespace axom - -#endif // AXOM_QUEST_INOUT_SAMPLER__HPP_ diff --git a/src/axom/quest/detail/shaping/PrimitiveSampler.hpp b/src/axom/quest/detail/shaping/PrimitiveSampler.hpp index db010e0658..4736a02068 100644 --- a/src/axom/quest/detail/shaping/PrimitiveSampler.hpp +++ b/src/axom/quest/detail/shaping/PrimitiveSampler.hpp @@ -10,8 +10,7 @@ * \brief Helper class for sampling-based shaping queries using primal geometric primitives */ -#ifndef AXOM_QUEST_PRIMITIVE_SAMPLER__HPP_ -#define AXOM_QUEST_PRIMITIVE_SAMPLER__HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core.hpp" @@ -334,5 +333,3 @@ class PrimitiveSampler } // namespace shaping } // namespace quest } // namespace axom - -#endif // AXOM_QUEST_PRIMITIVE_SAMPLER__HPP_ diff --git a/src/axom/quest/detail/shaping/WindingNumberSampler.hpp b/src/axom/quest/detail/shaping/WindingNumberSampler.hpp index 5b34be6788..23aff52ccd 100644 --- a/src/axom/quest/detail/shaping/WindingNumberSampler.hpp +++ b/src/axom/quest/detail/shaping/WindingNumberSampler.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_QUEST_WINDING_NUMBER_SAMPLER__HPP_ -#define AXOM_QUEST_WINDING_NUMBER_SAMPLER__HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core.hpp" @@ -333,5 +332,3 @@ class WindingNumberSampler } // namespace shaping } // namespace quest } // namespace axom - -#endif // AXOM_QUEST_WINDING_NUMBER_SAMPLER__HPP_ diff --git a/src/axom/quest/detail/shaping/shaping_helpers.hpp b/src/axom/quest/detail/shaping/shaping_helpers.hpp index fbfd05ec63..04d22f595f 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers.hpp +++ b/src/axom/quest/detail/shaping/shaping_helpers.hpp @@ -10,8 +10,7 @@ * \brief Free-standing helper functions in support of shaping query */ -#ifndef AXOM_QUEST_SHAPING_HELPERS__HPP_ -#define AXOM_QUEST_SHAPING_HELPERS__HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core.hpp" @@ -474,5 +473,3 @@ void computeVolumeFractionsBaseline(const std::string& shapeName, } // end namespace shaping } // end namespace quest } // end namespace axom - -#endif // AXOM_QUEST_SHAPING_HELPERS__HPP_ diff --git a/src/axom/quest/interface/c_fortran/typesQUEST.h b/src/axom/quest/interface/c_fortran/typesQUEST.h index cf789b8d07..8bc56e1772 100644 --- a/src/axom/quest/interface/c_fortran/typesQUEST.h +++ b/src/axom/quest/interface/c_fortran/typesQUEST.h @@ -8,8 +8,7 @@ // SPDX-License-Identifier: (BSD-3-Clause) // For C users and C++ implementation -#ifndef TYPESQUEST_H -#define TYPESQUEST_H +#pragma once // Shared with other Shroud wrapped libraries #ifndef SHROUD_SHARED_H @@ -47,5 +46,3 @@ void QUEST_SHROUD_memory_destructor(QUEST_SHROUD_capsule_data *cap); #ifdef __cplusplus } #endif - -#endif // TYPESQUEST_H diff --git a/src/axom/quest/interface/c_fortran/wrapQUEST.h b/src/axom/quest/interface/c_fortran/wrapQUEST.h index f3602b2205..c554127b79 100644 --- a/src/axom/quest/interface/c_fortran/wrapQUEST.h +++ b/src/axom/quest/interface/c_fortran/wrapQUEST.h @@ -12,8 +12,7 @@ */ // For C users and C++ implementation -#ifndef WRAPQUEST_H -#define WRAPQUEST_H +#pragma once #ifdef AXOM_USE_MPI #include "mpi.h" @@ -142,5 +141,3 @@ void QUEST_signed_distance_finalize(void); #ifdef __cplusplus } #endif - -#endif // WRAPQUEST_H diff --git a/src/axom/quest/interface/inout.hpp b/src/axom/quest/interface/inout.hpp index f0823e8103..df2d19b8ec 100644 --- a/src/axom/quest/interface/inout.hpp +++ b/src/axom/quest/interface/inout.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef QUEST_INOUT_INTERFACE_HPP_ -#define QUEST_INOUT_INTERFACE_HPP_ +#pragma once // Axom includes #include "axom/config.hpp" @@ -253,5 +252,3 @@ int inout_set_segments_per_knot_span(int segmentsPerKnotSpan); } // end namespace quest } // end namespace axom - -#endif // QUEST_INOUT_INTERFACE_HPP_ diff --git a/src/axom/quest/interface/internal/QuestHelpers.hpp b/src/axom/quest/interface/internal/QuestHelpers.hpp index 651c1cdf65..1f308544dd 100644 --- a/src/axom/quest/interface/internal/QuestHelpers.hpp +++ b/src/axom/quest/interface/internal/QuestHelpers.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef QUEST_HELPERS_HPP_ -#define QUEST_HELPERS_HPP_ +#pragma once // Axom includes #include "axom/config.hpp" @@ -307,5 +306,3 @@ void logger_finalize(bool mustFinalize); } /* end namespace internal */ } /* end namespace quest */ } /* end namespace axom */ - -#endif /* QUEST_HELPERS_HPP_ */ diff --git a/src/axom/quest/interface/internal/mpicomm_wrapper.hpp b/src/axom/quest/interface/internal/mpicomm_wrapper.hpp index a5af13af85..9471190171 100644 --- a/src/axom/quest/interface/internal/mpicomm_wrapper.hpp +++ b/src/axom/quest/interface/internal/mpicomm_wrapper.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef QUEST_MPICOMM_WRAPPER_HPP_ -#define QUEST_MPICOMM_WRAPPER_HPP_ +#pragma once #include "axom/config.hpp" // for Axom compile-time definitions @@ -25,5 +24,3 @@ using MPI_Comm = int; constexpr int MPI_COMM_SELF = -1; #endif - -#endif /* QUEST_MPICOMM_WRAPPER_HPP_ */ diff --git a/src/axom/quest/interface/python/pyQUESTmodule.hpp b/src/axom/quest/interface/python/pyQUESTmodule.hpp index 7edcf72da4..bd4fc05cd2 100644 --- a/src/axom/quest/interface/python/pyQUESTmodule.hpp +++ b/src/axom/quest/interface/python/pyQUESTmodule.hpp @@ -6,8 +6,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef PYQUESTMODULE_HPP -#define PYQUESTMODULE_HPP +#pragma once #define PY_SSIZE_T_CLEAN #include @@ -28,5 +27,3 @@ extern "C" PyMODINIT_FUNC PyInit_quest(void); #else extern "C" PyMODINIT_FUNC initquest(void); #endif - -#endif /* PYQUESTMODULE_HPP */ diff --git a/src/axom/quest/interface/signed_distance.hpp b/src/axom/quest/interface/signed_distance.hpp index c8c1fa80fa..1f7cb8cc2e 100644 --- a/src/axom/quest/interface/signed_distance.hpp +++ b/src/axom/quest/interface/signed_distance.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef QUEST_SIGNED_DISTANCE_INTERFACE_HPP_ -#define QUEST_SIGNED_DISTANCE_INTERFACE_HPP_ +#pragma once // Axom includes #include "axom/config.hpp" @@ -341,5 +340,3 @@ void signed_distance_finalize(); } // end namespace quest } // end namespace axom - -#endif /* QUEST_SIGNED_DISTANCE_INTERFACE_HPP_ */ diff --git a/src/axom/quest/io/C2CReader.hpp b/src/axom/quest/io/C2CReader.hpp index 09e99ec441..7accfaa3e3 100644 --- a/src/axom/quest/io/C2CReader.hpp +++ b/src/axom/quest/io/C2CReader.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef QUEST_C2CREADER_HPP_ -#define QUEST_C2CREADER_HPP_ +#pragma once #include "axom/config.hpp" @@ -82,5 +81,3 @@ class C2CReader } // namespace quest } // namespace axom - -#endif // QUEST_C2CREADER_HPP_ diff --git a/src/axom/quest/io/MFEMReader.hpp b/src/axom/quest/io/MFEMReader.hpp index e3699c0d98..2b981bcd5a 100644 --- a/src/axom/quest/io/MFEMReader.hpp +++ b/src/axom/quest/io/MFEMReader.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef QUEST_MFEMREADER_HPP_ -#define QUEST_MFEMREADER_HPP_ +#pragma once #include "axom/config.hpp" @@ -99,5 +98,3 @@ class MFEMReader } // namespace quest } // namespace axom - -#endif diff --git a/src/axom/quest/io/PC2CReader.hpp b/src/axom/quest/io/PC2CReader.hpp index 179f4ecf12..1c17a9d103 100644 --- a/src/axom/quest/io/PC2CReader.hpp +++ b/src/axom/quest/io/PC2CReader.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef QUEST_PC2CREADER_HPP_ -#define QUEST_PC2CREADER_HPP_ +#pragma once #include "axom/config.hpp" @@ -95,5 +94,3 @@ class PC2CReader : public C2CReader } // namespace quest } // namespace axom - -#endif // QUEST_PC2CREADER_HPP_ diff --git a/src/axom/quest/io/PProEReader.hpp b/src/axom/quest/io/PProEReader.hpp index 390e8f07f7..2a7241f7a1 100644 --- a/src/axom/quest/io/PProEReader.hpp +++ b/src/axom/quest/io/PProEReader.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef QUEST_PPROEREADER_HPP_ -#define QUEST_PPROEREADER_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core/Macros.hpp" @@ -43,5 +42,3 @@ class PProEReader : public ProEReader } // namespace quest } // namespace axom - -#endif /* QUEST_PPROEREADER_HPP_ */ diff --git a/src/axom/quest/io/PSTEPReader.hpp b/src/axom/quest/io/PSTEPReader.hpp index 76082acf4b..5954cf9b50 100644 --- a/src/axom/quest/io/PSTEPReader.hpp +++ b/src/axom/quest/io/PSTEPReader.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef QUEST_PSTEPREADER_HPP_ -#define QUEST_PSTEPREADER_HPP_ +#pragma once #include "axom/config.hpp" @@ -190,5 +189,3 @@ class PSTEPReader : public STEPReader } // namespace quest } // namespace axom - -#endif // QUEST_PSTEPREADER_HPP_ diff --git a/src/axom/quest/io/PSTLReader.hpp b/src/axom/quest/io/PSTLReader.hpp index 35ba8df52d..58c6c377b7 100644 --- a/src/axom/quest/io/PSTLReader.hpp +++ b/src/axom/quest/io/PSTLReader.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef QUEST_PSTLREADER_HPP_ -#define QUEST_PSTLREADER_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core/Macros.hpp" @@ -43,5 +42,3 @@ class PSTLReader : public STLReader } // namespace quest } // namespace axom - -#endif /* QUEST_PSTLREADER_HPP_ */ diff --git a/src/axom/quest/io/ProEReader.hpp b/src/axom/quest/io/ProEReader.hpp index 0de16fc9ed..d1f0c0421f 100644 --- a/src/axom/quest/io/ProEReader.hpp +++ b/src/axom/quest/io/ProEReader.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef QUEST_PROEREADER_HPP_ -#define QUEST_PROEREADER_HPP_ +#pragma once // Axom includes #include "axom/config.hpp" @@ -164,5 +163,3 @@ class ProEReader } // namespace quest } // namespace axom - -#endif // QUEST_PROEREADER_HPP_ diff --git a/src/axom/quest/io/STEPReader.hpp b/src/axom/quest/io/STEPReader.hpp index f3d91f8400..6cf4890f32 100644 --- a/src/axom/quest/io/STEPReader.hpp +++ b/src/axom/quest/io/STEPReader.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef QUEST_STEPREADER_HPP_ -#define QUEST_STEPREADER_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/mint.hpp" @@ -136,5 +135,3 @@ class STEPReader } // namespace quest } // namespace axom - -#endif diff --git a/src/axom/quest/io/STLReader.hpp b/src/axom/quest/io/STLReader.hpp index ce6a5f3213..dc56deafb4 100644 --- a/src/axom/quest/io/STLReader.hpp +++ b/src/axom/quest/io/STLReader.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef QUEST_STLREADER_HPP_ -#define QUEST_STLREADER_HPP_ +#pragma once // Axom includes #include "axom/config.hpp" @@ -120,5 +119,3 @@ class STLReader } // namespace quest } // namespace axom - -#endif // QUEST_STLREADER_HPP_ diff --git a/src/axom/quest/io/STLWriter.hpp b/src/axom/quest/io/STLWriter.hpp index 2f9bbe653e..416efa3e32 100644 --- a/src/axom/quest/io/STLWriter.hpp +++ b/src/axom/quest/io/STLWriter.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef QUEST_STLWRITER_HPP_ -#define QUEST_STLWRITER_HPP_ +#pragma once // Axom includes #include "axom/config.hpp" @@ -110,5 +109,3 @@ int write_stl(const mint::Mesh* mesh, const std::string& filename, bool binary = } // namespace quest } // namespace axom - -#endif // QUEST_STLWRITER_HPP_ diff --git a/src/axom/quest/tests/quest_intersection_shaper_utils.hpp b/src/axom/quest/tests/quest_intersection_shaper_utils.hpp index 46d902bd55..853f7b525b 100644 --- a/src/axom/quest/tests/quest_intersection_shaper_utils.hpp +++ b/src/axom/quest/tests/quest_intersection_shaper_utils.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef QUEST_TESTS_INTERSECTION_SHAPER_UTILS_HPP -#define QUEST_TESTS_INTERSECTION_SHAPER_UTILS_HPP +#pragma once #include "axom/config.hpp" #include "axom/core.hpp" @@ -536,5 +535,3 @@ class ShapingTestApplication std::string m_policy; int m_caseNumber; }; - -#endif diff --git a/src/axom/quest/tests/quest_test_utilities.hpp b/src/axom/quest/tests/quest_test_utilities.hpp index c7d7acd274..67c6c487d0 100644 --- a/src/axom/quest/tests/quest_test_utilities.hpp +++ b/src/axom/quest/tests/quest_test_utilities.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef QUEST_TEST_UTILITIES_HPP_ -#define QUEST_TEST_UTILITIES_HPP_ +#pragma once #include "axom/core.hpp" #include "axom/slic.hpp" @@ -471,5 +470,3 @@ mint::Mesh* make_circle_mesh_2d(double radius, int num_segments) } // end namespace utilities } // end namespace quest } // end namespace axom - -#endif // QUEST_TEST_UTILITIES_HPP_ diff --git a/src/axom/quest/util/make_clipper_strategy.hpp b/src/axom/quest/util/make_clipper_strategy.hpp index 754662cbb8..aa9988063f 100644 --- a/src/axom/quest/util/make_clipper_strategy.hpp +++ b/src/axom/quest/util/make_clipper_strategy.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_MAKE_CLIPPER_STRATEGY_HPP -#define AXOM_MAKE_CLIPPER_STRATEGY_HPP +#pragma once #include "axom/config.hpp" @@ -47,4 +46,3 @@ std::shared_ptr make_clipper_strategy(const axom::klee::Geo } // namespace axom #endif // AXOM_USE_SIDRE -#endif // AXOM_MAKE_CLIPPER_STRATEGY_HPP diff --git a/src/axom/quest/util/mesh_helpers.hpp b/src/axom/quest/util/mesh_helpers.hpp index d0ebc9ebe1..8aa80f9c62 100644 --- a/src/axom/quest/util/mesh_helpers.hpp +++ b/src/axom/quest/util/mesh_helpers.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_QUEST_MESH_HELPERS__HPP_ -#define AXOM_QUEST_MESH_HELPERS__HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core.hpp" @@ -251,5 +250,3 @@ void fill_cartesian_coords_2d_impl(const primal::BoundingBox& domainB } // namespace util } // namespace quest } // namespace axom - -#endif // AXOM_QUEST_MESH_HELPERS__HPP_ diff --git a/src/axom/sidre/core/Array.hpp b/src/axom/sidre/core/Array.hpp index ef524bd648..e863376d7b 100644 --- a/src/axom/sidre/core/Array.hpp +++ b/src/axom/sidre/core/Array.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef SIDRE_ARRAY_HPP_ -#define SIDRE_ARRAY_HPP_ +#pragma once #include "axom/core/utilities/Utilities.hpp" // for memory allocation functions #include "axom/core/Array.hpp" // to inherit @@ -569,5 +568,3 @@ inline axom::IndexType Array::getViewShape(int dim) const } /* namespace sidre */ } /* namespace axom */ - -#endif /* SIDRE_ARRAY_HPP_ */ diff --git a/src/axom/sidre/core/AttrValues.hpp b/src/axom/sidre/core/AttrValues.hpp index 03b83f943c..ab2df74669 100644 --- a/src/axom/sidre/core/AttrValues.hpp +++ b/src/axom/sidre/core/AttrValues.hpp @@ -27,8 +27,7 @@ #include "axom/sidre/core/Attribute.hpp" #include "axom/sidre/core/SidreTypes.hpp" -#ifndef SIDRE_ATTRVALUES_HPP_ - #define SIDRE_ATTRVALUES_HPP_ +#pragma once namespace axom { @@ -247,5 +246,3 @@ class AttrValues } /* end namespace sidre */ } /* end namespace axom */ - -#endif /* SIDRE_ATTRVALUES_HPP_ */ diff --git a/src/axom/sidre/core/Attribute.hpp b/src/axom/sidre/core/Attribute.hpp index cc549aa8e4..22745f38f6 100644 --- a/src/axom/sidre/core/Attribute.hpp +++ b/src/axom/sidre/core/Attribute.hpp @@ -14,8 +14,7 @@ ****************************************************************************** */ -#ifndef SIDRE_ATTRIBUTE_HPP_ -#define SIDRE_ATTRIBUTE_HPP_ +#pragma once // Standard C++ headers #include @@ -161,5 +160,3 @@ class Attribute } /* end namespace sidre */ } /* end namespace axom */ - -#endif /* SIDRE_ATTRIBUTE_HPP_ */ diff --git a/src/axom/sidre/core/Buffer.hpp b/src/axom/sidre/core/Buffer.hpp index e5cf9bdd50..3b389930c5 100644 --- a/src/axom/sidre/core/Buffer.hpp +++ b/src/axom/sidre/core/Buffer.hpp @@ -14,8 +14,7 @@ ****************************************************************************** */ -#ifndef SIDRE_BUFFER_HPP_ -#define SIDRE_BUFFER_HPP_ +#pragma once // Standard C++ headers #include @@ -347,5 +346,3 @@ class Buffer } /* end namespace sidre */ } /* end namespace axom */ - -#endif /* SIDRE_BUFFER_HPP_ */ diff --git a/src/axom/sidre/core/ConduitMemory.hpp b/src/axom/sidre/core/ConduitMemory.hpp index 08ed2f4791..8d3eed971e 100644 --- a/src/axom/sidre/core/ConduitMemory.hpp +++ b/src/axom/sidre/core/ConduitMemory.hpp @@ -14,8 +14,7 @@ ****************************************************************************** */ -#ifndef SIDRE_CONDUITMEMORY_HPP_ -#define SIDRE_CONDUITMEMORY_HPP_ +#pragma once // Standard C++ headers #include @@ -190,5 +189,3 @@ axom::utilities::CheckSum checksum(const conduit::Node& n, } /* end namespace sidre */ } /* end namespace axom */ - -#endif // AXOM_USE_CONDUIT diff --git a/src/axom/sidre/core/DataStore.hpp b/src/axom/sidre/core/DataStore.hpp index fbcbf08c30..a2b4af7758 100644 --- a/src/axom/sidre/core/DataStore.hpp +++ b/src/axom/sidre/core/DataStore.hpp @@ -14,8 +14,7 @@ ****************************************************************************** */ -#ifndef SIDRE_DATASTORE_HPP_ -#define SIDRE_DATASTORE_HPP_ +#pragma once // Standard C++ headers #include @@ -548,5 +547,3 @@ class DataStore } /* end namespace sidre */ } /* end namespace axom */ - -#endif /* SIDRE_DATASTORE_HPP_ */ diff --git a/src/axom/sidre/core/Group.hpp b/src/axom/sidre/core/Group.hpp index 13e8ab8874..065c9b070f 100644 --- a/src/axom/sidre/core/Group.hpp +++ b/src/axom/sidre/core/Group.hpp @@ -14,8 +14,7 @@ ****************************************************************************** */ -#ifndef SIDRE_GROUP_HPP_ -#define SIDRE_GROUP_HPP_ +#pragma once // axom headers #include "axom/config.hpp" @@ -2140,5 +2139,3 @@ class Group } /* end namespace sidre */ } /* end namespace axom */ - -#endif /* SIDRE_GROUP_HPP_ */ diff --git a/src/axom/sidre/core/MFEMSidreDataCollection.hpp b/src/axom/sidre/core/MFEMSidreDataCollection.hpp index 50cb2cb9b1..d9a48da611 100644 --- a/src/axom/sidre/core/MFEMSidreDataCollection.hpp +++ b/src/axom/sidre/core/MFEMSidreDataCollection.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MFEM_SIDRE_DATACOLLECTION_HPP_ -#define MFEM_SIDRE_DATACOLLECTION_HPP_ +#pragma once #include "axom/config.hpp" #include "DataStore.hpp" @@ -775,5 +774,3 @@ class MFEMSidreDataCollection : public mfem::DataCollection } /* namespace axom */ #endif // AXOM_USE_MFEM - -#endif diff --git a/src/axom/sidre/core/SidreDataTypeIds.h b/src/axom/sidre/core/SidreDataTypeIds.h index 2aadda7159..c4281d8203 100644 --- a/src/axom/sidre/core/SidreDataTypeIds.h +++ b/src/axom/sidre/core/SidreDataTypeIds.h @@ -17,8 +17,7 @@ * since it will be included from a C file. */ -#ifndef SIDRE_DATATYPEIDS_H_ -#define SIDRE_DATATYPEIDS_H_ +#pragma once // Libraries and other axom headers #include "conduit.h" @@ -44,5 +43,3 @@ #define SIDRE_ULONG_ID CONDUIT_NATIVE_UNSIGNED_LONG_ID #define SIDRE_FLOAT_ID CONDUIT_NATIVE_FLOAT_ID #define SIDRE_DOUBLE_ID CONDUIT_NATIVE_DOUBLE_ID - -#endif /* SIDRE_DATATYPEIDS_H_ */ diff --git a/src/axom/sidre/core/SidreTypes.hpp b/src/axom/sidre/core/SidreTypes.hpp index 42dee8f176..80d4fec6f3 100644 --- a/src/axom/sidre/core/SidreTypes.hpp +++ b/src/axom/sidre/core/SidreTypes.hpp @@ -11,8 +11,7 @@ * */ -#ifndef SIDRE_TYPES_HPP_ -#define SIDRE_TYPES_HPP_ +#pragma once #include "SidreDataTypeIds.h" #include "conduit.hpp" @@ -271,5 +270,3 @@ struct formatter }; } // namespace fmt } // namespace axom - -#endif // SIDRE_TYPES_HPP_ diff --git a/src/axom/sidre/core/View.hpp b/src/axom/sidre/core/View.hpp index 74e7bd0909..336dc27531 100644 --- a/src/axom/sidre/core/View.hpp +++ b/src/axom/sidre/core/View.hpp @@ -14,8 +14,7 @@ ****************************************************************************** */ -#ifndef SIDRE_VIEW_HPP_ -#define SIDRE_VIEW_HPP_ +#pragma once // Standard C++ headers #include @@ -1736,5 +1735,3 @@ class View } /* end namespace sidre */ } /* end namespace axom */ - -#endif /* SIDRE_VIEW_HPP_ */ diff --git a/src/axom/sidre/examples/lulesh2/lulesh.h b/src/axom/sidre/examples/lulesh2/lulesh.h index 23b26957a7..e5d0e56b81 100644 --- a/src/axom/sidre/examples/lulesh2/lulesh.h +++ b/src/axom/sidre/examples/lulesh2/lulesh.h @@ -7,6 +7,8 @@ // OpenMP will be compiled in if this flag is set to 1 AND the compiler beging // used supports it (i.e. the _OPENMP symbol is defined) +#pragma once + #define USE_OMP 1 #include "axom/sidre.hpp" diff --git a/src/axom/sidre/examples/lulesh2/lulesh_tuple.h b/src/axom/sidre/examples/lulesh2/lulesh_tuple.h index df959eee0c..c210f9a5bb 100644 --- a/src/axom/sidre/examples/lulesh2/lulesh_tuple.h +++ b/src/axom/sidre/examples/lulesh2/lulesh_tuple.h @@ -5,6 +5,8 @@ // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "axom/config.hpp" // OpenMP will be compiled in if this flag is set to 1 AND the compiler beging diff --git a/src/axom/sidre/examples/spio/spio_scr.hpp b/src/axom/sidre/examples/spio/spio_scr.hpp index 9f91c38f19..e0b8e82f7b 100644 --- a/src/axom/sidre/examples/spio/spio_scr.hpp +++ b/src/axom/sidre/examples/spio/spio_scr.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "gtest/gtest.h" #include "axom/config.hpp" diff --git a/src/axom/sidre/interface/SidreTypes.h b/src/axom/sidre/interface/SidreTypes.h index 0e598bcad2..8ee546bfbe 100644 --- a/src/axom/sidre/interface/SidreTypes.h +++ b/src/axom/sidre/interface/SidreTypes.h @@ -17,8 +17,7 @@ * It is part of the C wrapper. */ -#ifndef SIDRETYPES_H -#define SIDRETYPES_H +#pragma once // Axom includes #include "axom/config.hpp" @@ -37,5 +36,3 @@ typedef short SIDRE_TypeID; typedef int SIDRE_TypeIDint; #define SIDRE_InvalidName NULL - -#endif // SIDRETYPES_H diff --git a/src/axom/sidre/interface/c_fortran/typesSidre.h b/src/axom/sidre/interface/c_fortran/typesSidre.h index 5a124dae3a..7f3854af65 100644 --- a/src/axom/sidre/interface/c_fortran/typesSidre.h +++ b/src/axom/sidre/interface/c_fortran/typesSidre.h @@ -8,8 +8,7 @@ // SPDX-License-Identifier: (BSD-3-Clause) // For C users and C++ implementation -#ifndef TYPESSIDRE_H -#define TYPESSIDRE_H +#pragma once // Shared with other Shroud wrapped libraries #ifndef SHROUD_SHARED_H @@ -118,5 +117,3 @@ void SIDRE_SHROUD_memory_destructor(SIDRE_SHROUD_capsule_data *cap); #ifdef __cplusplus } #endif - -#endif // TYPESSIDRE_H diff --git a/src/axom/sidre/interface/c_fortran/wrapBuffer.h b/src/axom/sidre/interface/c_fortran/wrapBuffer.h index 5ad93b3d19..6db6c00405 100644 --- a/src/axom/sidre/interface/c_fortran/wrapBuffer.h +++ b/src/axom/sidre/interface/c_fortran/wrapBuffer.h @@ -12,8 +12,7 @@ */ // For C users and C++ implementation -#ifndef WRAPBUFFER_H -#define WRAPBUFFER_H +#pragma once #include "wrapSidre.h" #include "axom/sidre/interface/SidreTypes.h" @@ -62,5 +61,3 @@ void SIDRE_Buffer_print(const SIDRE_Buffer* self); #ifdef __cplusplus } #endif - -#endif // WRAPBUFFER_H diff --git a/src/axom/sidre/interface/c_fortran/wrapDataStore.h b/src/axom/sidre/interface/c_fortran/wrapDataStore.h index eb8b1c543c..2e592be068 100644 --- a/src/axom/sidre/interface/c_fortran/wrapDataStore.h +++ b/src/axom/sidre/interface/c_fortran/wrapDataStore.h @@ -12,8 +12,7 @@ */ // For C users and C++ implementation -#ifndef WRAPDATASTORE_H -#define WRAPDATASTORE_H +#pragma once #include "wrapSidre.h" #include "axom/sidre/interface/SidreTypes.h" @@ -114,5 +113,3 @@ void SIDRE_DataStore_print(const SIDRE_DataStore *self); #ifdef __cplusplus } #endif - -#endif // WRAPDATASTORE_H diff --git a/src/axom/sidre/interface/c_fortran/wrapGroup.h b/src/axom/sidre/interface/c_fortran/wrapGroup.h index 0de20008e5..614fcd71e9 100644 --- a/src/axom/sidre/interface/c_fortran/wrapGroup.h +++ b/src/axom/sidre/interface/c_fortran/wrapGroup.h @@ -12,8 +12,7 @@ */ // For C users and C++ implementation -#ifndef WRAPGROUP_H -#define WRAPGROUP_H +#pragma once #include "wrapSidre.h" #include "axom/sidre/interface/SidreTypes.h" @@ -494,5 +493,3 @@ bool SIDRE_Group_rename_bufferify(SIDRE_Group *self, char *new_name, int SHT_new #ifdef __cplusplus } #endif - -#endif // WRAPGROUP_H diff --git a/src/axom/sidre/interface/c_fortran/wrapSidre.h b/src/axom/sidre/interface/c_fortran/wrapSidre.h index 183c9fbcde..18785d7c6d 100644 --- a/src/axom/sidre/interface/c_fortran/wrapSidre.h +++ b/src/axom/sidre/interface/c_fortran/wrapSidre.h @@ -12,8 +12,7 @@ */ // For C users and C++ implementation -#ifndef WRAPSIDRE_H -#define WRAPSIDRE_H +#pragma once #ifndef __cplusplus #include @@ -65,5 +64,3 @@ int SIDRE_get_malloc_allocator_id(void); #ifdef __cplusplus } #endif - -#endif // WRAPSIDRE_H diff --git a/src/axom/sidre/interface/c_fortran/wrapView.h b/src/axom/sidre/interface/c_fortran/wrapView.h index 28fdf1b6fb..384d9f5e5c 100644 --- a/src/axom/sidre/interface/c_fortran/wrapView.h +++ b/src/axom/sidre/interface/c_fortran/wrapView.h @@ -12,8 +12,7 @@ */ // For C users and C++ implementation -#ifndef WRAPVIEW_H -#define WRAPVIEW_H +#pragma once #include "wrapSidre.h" #include "axom/sidre/interface/SidreTypes.h" @@ -193,5 +192,3 @@ bool SIDRE_View_rename_bufferify(SIDRE_View *self, char *new_name, int SHT_new_n #ifdef __cplusplus } #endif - -#endif // WRAPVIEW_H diff --git a/src/axom/sidre/interface/sidre.h b/src/axom/sidre/interface/sidre.h index 168ff41082..f51860d124 100644 --- a/src/axom/sidre/interface/sidre.h +++ b/src/axom/sidre/interface/sidre.h @@ -10,8 +10,7 @@ * C header file */ -#ifndef SIDRE_H_ -#define SIDRE_H_ +#pragma once #include "axom/sidre/interface/SidreTypes.h" #include "axom/sidre/interface/c_fortran/wrapSidre.h" @@ -19,5 +18,3 @@ #include "axom/sidre/interface/c_fortran/wrapBuffer.h" #include "axom/sidre/interface/c_fortran/wrapGroup.h" #include "axom/sidre/interface/c_fortran/wrapView.h" - -#endif diff --git a/src/axom/sidre/spio/IOBaton.hpp b/src/axom/sidre/spio/IOBaton.hpp index 2f463a6a18..e60d04accd 100644 --- a/src/axom/sidre/spio/IOBaton.hpp +++ b/src/axom/sidre/spio/IOBaton.hpp @@ -14,8 +14,7 @@ ****************************************************************************** */ -#ifndef SIDRE_IOBATON_HPP_ -#define SIDRE_IOBATON_HPP_ +#pragma once #include "mpi.h" @@ -121,5 +120,3 @@ class IOBaton } /* end namespace sidre */ } /* end namespace axom */ - -#endif /* SIDRE_IOBATON_HPP_ */ diff --git a/src/axom/sidre/spio/IOManager.hpp b/src/axom/sidre/spio/IOManager.hpp index 66ba31a2bc..3089a46598 100644 --- a/src/axom/sidre/spio/IOManager.hpp +++ b/src/axom/sidre/spio/IOManager.hpp @@ -14,8 +14,7 @@ ****************************************************************************** */ -#ifndef SIDRE_IOMANAGER_HPP_ -#define SIDRE_IOMANAGER_HPP_ +#pragma once // Other axom headers #include "axom/config.hpp" @@ -424,5 +423,3 @@ class IOManager } /* end namespace sidre */ } /* end namespace axom */ - -#endif /* SIDRE_IOMANAGER_HPP_ */ diff --git a/src/axom/sidre/spio/interface/c_fortran/typesSPIO.h b/src/axom/sidre/spio/interface/c_fortran/typesSPIO.h index d1b1e6470c..86532da94c 100644 --- a/src/axom/sidre/spio/interface/c_fortran/typesSPIO.h +++ b/src/axom/sidre/spio/interface/c_fortran/typesSPIO.h @@ -8,8 +8,7 @@ // SPDX-License-Identifier: (BSD-3-Clause) // For C users and C++ implementation -#ifndef TYPESSPIO_H -#define TYPESSPIO_H +#pragma once // Shared with other Shroud wrapped libraries #ifndef SHROUD_SHARED_H @@ -66,5 +65,3 @@ void SPIO_SHROUD_memory_destructor(SPIO_SHROUD_capsule_data *cap); #ifdef __cplusplus } #endif - -#endif // TYPESSPIO_H diff --git a/src/axom/sidre/spio/interface/c_fortran/wrapIOManager.h b/src/axom/sidre/spio/interface/c_fortran/wrapIOManager.h index 076738961f..455b5be769 100644 --- a/src/axom/sidre/spio/interface/c_fortran/wrapIOManager.h +++ b/src/axom/sidre/spio/interface/c_fortran/wrapIOManager.h @@ -12,8 +12,7 @@ */ // For C users and C++ implementation -#ifndef WRAPIOMANAGER_H -#define WRAPIOMANAGER_H +#pragma once #include "axom/sidre/interface/c_fortran/wrapGroup.h" #include "axom/sidre/interface/c_fortran/wrapDataStore.h" @@ -152,5 +151,3 @@ void SPIO_IOManager_loadExternalData_bufferify(SPIO_IOManager *self, #ifdef __cplusplus } #endif - -#endif // WRAPIOMANAGER_H diff --git a/src/axom/sidre/tests/spio/spio_basic.hpp b/src/axom/sidre/tests/spio/spio_basic.hpp index 12695514d9..9055a6b5f0 100644 --- a/src/axom/sidre/tests/spio/spio_basic.hpp +++ b/src/axom/sidre/tests/spio/spio_basic.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "gtest/gtest.h" #include "axom/sidre/spio/IOManager.hpp" diff --git a/src/axom/sidre/tests/spio/spio_parallel.hpp b/src/axom/sidre/tests/spio/spio_parallel.hpp index 2cf624e51d..9196e9fbd9 100644 --- a/src/axom/sidre/tests/spio/spio_parallel.hpp +++ b/src/axom/sidre/tests/spio/spio_parallel.hpp @@ -17,6 +17,8 @@ * prepended with an underscore. */ +#pragma once + #include "gtest/gtest.h" // _parallel_io_headers_start diff --git a/src/axom/sidre/tests/spio/spio_serial.hpp b/src/axom/sidre/tests/spio/spio_serial.hpp index 0ccb22462f..3e2ee97cc7 100644 --- a/src/axom/sidre/tests/spio/spio_serial.hpp +++ b/src/axom/sidre/tests/spio/spio_serial.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "gtest/gtest.h" #include "axom/config.hpp" diff --git a/src/axom/sina/core/AdiakWriter.hpp b/src/axom/sina/core/AdiakWriter.hpp index d54112d193..87d37c75d7 100644 --- a/src/axom/sina/core/AdiakWriter.hpp +++ b/src/axom/sina/core/AdiakWriter.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef SINA_ADIAK_HPP -#define SINA_ADIAK_HPP +#pragma once /*! ****************************************************************************** @@ -66,5 +65,3 @@ void adiakSinaCallback(const char *name, } // namespace axom #endif // AXOM_USE_ADIAK - -#endif // SINA_ADIAK_HPP diff --git a/src/axom/sina/core/ConduitUtil.hpp b/src/axom/sina/core/ConduitUtil.hpp index 2132cd0545..3878d6e024 100644 --- a/src/axom/sina/core/ConduitUtil.hpp +++ b/src/axom/sina/core/ConduitUtil.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef SINA_JSONUTIL_HPP -#define SINA_JSONUTIL_HPP +#pragma once /*! ****************************************************************************** @@ -121,5 +120,3 @@ void addStringsToNode(conduit::Node &parent, } // end namespace sina } // end namespace axom - -#endif //SINA_JSONUTIL_HPP diff --git a/src/axom/sina/core/Curve.hpp b/src/axom/sina/core/Curve.hpp index 4f46f13f5f..192aef92ad 100644 --- a/src/axom/sina/core/Curve.hpp +++ b/src/axom/sina/core/Curve.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef SINA_CURVE_HPP -#define SINA_CURVE_HPP +#pragma once /*! ****************************************************************************** @@ -116,5 +115,3 @@ class Curve } // namespace sina } // namespace axom - -#endif //SINA_CURVE_HPP diff --git a/src/axom/sina/core/CurveSet.hpp b/src/axom/sina/core/CurveSet.hpp index 25cdeea122..aca95ea13d 100644 --- a/src/axom/sina/core/CurveSet.hpp +++ b/src/axom/sina/core/CurveSet.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef SINA_CURVESET_HPP -#define SINA_CURVESET_HPP +#pragma once /*! ****************************************************************************** @@ -187,5 +186,3 @@ void setDefaultCurveOrder(CurveSet::CurveOrder order); } // namespace sina } // namespace axom - -#endif //SINA_CURVESET_HPP diff --git a/src/axom/sina/core/DataHolder.hpp b/src/axom/sina/core/DataHolder.hpp index dec0ac8659..9bc5bfe287 100644 --- a/src/axom/sina/core/DataHolder.hpp +++ b/src/axom/sina/core/DataHolder.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef SINA_DATAHOLDER_HPP -#define SINA_DATAHOLDER_HPP +#pragma once /*! ****************************************************************************** @@ -201,5 +200,3 @@ class DataHolder } // namespace sina } // namespace axom - -#endif //SINA_DATAHOLDER_HPP diff --git a/src/axom/sina/core/Datum.hpp b/src/axom/sina/core/Datum.hpp index 3944a22754..25bf26349a 100644 --- a/src/axom/sina/core/Datum.hpp +++ b/src/axom/sina/core/Datum.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef SINA_DATUM_HPP -#define SINA_DATUM_HPP +#pragma once /*! ****************************************************************************** @@ -197,5 +196,3 @@ class Datum } // namespace sina } // namespace axom - -#endif //SINA_DATUM_HPP diff --git a/src/axom/sina/core/Document.hpp b/src/axom/sina/core/Document.hpp index e87eec8367..745b1f33f3 100644 --- a/src/axom/sina/core/Document.hpp +++ b/src/axom/sina/core/Document.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef SINA_DOCUMENT_HPP -#define SINA_DOCUMENT_HPP +#pragma once /*! ****************************************************************************** @@ -407,5 +406,3 @@ conduit::Node validateAppendDocument(ConduitRelayLike &appendTo, } // namespace sina } // namespace axom - -#endif //SINA_DOCUMENT_HPP diff --git a/src/axom/sina/core/File.hpp b/src/axom/sina/core/File.hpp index 618603291c..fd1c8f53fb 100644 --- a/src/axom/sina/core/File.hpp +++ b/src/axom/sina/core/File.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef SINA_FILE_HPP -#define SINA_FILE_HPP +#pragma once /*! ****************************************************************************** @@ -111,5 +110,3 @@ class File } // namespace sina } // namespace axom - -#endif //SINA_FILE_HPP diff --git a/src/axom/sina/core/ID.hpp b/src/axom/sina/core/ID.hpp index b403609a84..20aa864b2a 100644 --- a/src/axom/sina/core/ID.hpp +++ b/src/axom/sina/core/ID.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef SINA_ID_HPP -#define SINA_ID_HPP +#pragma once /*! ****************************************************************************** @@ -146,5 +145,3 @@ class IDField } // namespace internal } // namespace sina } // namespace axom - -#endif //SINA_ID_HPP diff --git a/src/axom/sina/core/Record.hpp b/src/axom/sina/core/Record.hpp index 7527010cc0..1511eb7e1e 100644 --- a/src/axom/sina/core/Record.hpp +++ b/src/axom/sina/core/Record.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef SINA_RECORD_HPP -#define SINA_RECORD_HPP +#pragma once /*! ****************************************************************************** @@ -267,5 +266,3 @@ RecordLoader createRecordLoaderWithAllKnownTypes(); } // namespace sina } // namespace axom - -#endif //SINA_RECORD_HPP diff --git a/src/axom/sina/core/Relationship.hpp b/src/axom/sina/core/Relationship.hpp index 4978c4eb43..f8bc21558b 100644 --- a/src/axom/sina/core/Relationship.hpp +++ b/src/axom/sina/core/Relationship.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef SINA_RELATIONSHIP_HPP -#define SINA_RELATIONSHIP_HPP +#pragma once /*! ****************************************************************************** @@ -137,5 +136,3 @@ class Relationship } // namespace sina } // namespace axom - -#endif //SINA_RELATIONSHIP_HPP diff --git a/src/axom/sina/core/Run.hpp b/src/axom/sina/core/Run.hpp index f58a4d756a..a4a842f442 100644 --- a/src/axom/sina/core/Run.hpp +++ b/src/axom/sina/core/Run.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef SINA_RUN_HPP -#define SINA_RUN_HPP +#pragma once /*! ****************************************************************************** @@ -104,5 +103,3 @@ void addRunLoader(RecordLoader &loader); } // namespace sina } // namespace axom - -#endif //SINA_RUN_HPP diff --git a/src/axom/sina/interface/sina_fortran_interface.h b/src/axom/sina/interface/sina_fortran_interface.h index ad05644229..6f748899bf 100644 --- a/src/axom/sina/interface/sina_fortran_interface.h +++ b/src/axom/sina/interface/sina_fortran_interface.h @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "axom/sina.hpp" extern "C" void sina_set_default_record_type_(char *); diff --git a/src/axom/sina/tests/SinaMatchers.hpp b/src/axom/sina/tests/SinaMatchers.hpp index e5f531e6ae..a6e1293140 100644 --- a/src/axom/sina/tests/SinaMatchers.hpp +++ b/src/axom/sina/tests/SinaMatchers.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_SINAMATCHERS_HPP -#define AXOM_SINAMATCHERS_HPP +#pragma once #include #include "conduit.hpp" @@ -48,5 +47,3 @@ inline ::testing::PolymorphicMatcher MatchesJsonMatcher(const std:: } // namespace testing } // namespace sina } // namespace axom - -#endif //AXOM_SINAMATCHERS_HPP \ No newline at end of file diff --git a/src/axom/sina/tests/TestRecord.hpp b/src/axom/sina/tests/TestRecord.hpp index 5b3ecd8f51..e186a9b85d 100644 --- a/src/axom/sina/tests/TestRecord.hpp +++ b/src/axom/sina/tests/TestRecord.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef SINA_TESTRECORD_HPP -#define SINA_TESTRECORD_HPP +#pragma once #include "axom/sina/core/ConduitUtil.hpp" #include "axom/sina/core/Record.hpp" @@ -83,5 +82,3 @@ conduit::Node TestRecord::toNode(CurveSet::CurveOrder curveOrder) const } // namespace testing } // namespace sina } // namespace axom - -#endif //SINA_TESTRECORD_HPP diff --git a/src/axom/slam/BitSet.hpp b/src/axom/slam/BitSet.hpp index 13ef45e1aa..b945f78340 100644 --- a/src/axom/slam/BitSet.hpp +++ b/src/axom/slam/BitSet.hpp @@ -10,8 +10,7 @@ * \brief Contains a BitSet class for manipulating ordered sequences of bits. */ -#ifndef SLAM_BITSET_H_ -#define SLAM_BITSET_H_ +#pragma once #include "axom/config.hpp" #include "axom/core/Array.hpp" @@ -416,5 +415,3 @@ class BitSet } // end namespace slam } // end namespace axom - -#endif // SLAM_BITSET_H_ diff --git a/src/axom/slam/BivariateMap.hpp b/src/axom/slam/BivariateMap.hpp index 34eec57d09..fb10aa0ce2 100644 --- a/src/axom/slam/BivariateMap.hpp +++ b/src/axom/slam/BivariateMap.hpp @@ -11,8 +11,7 @@ * */ -#ifndef SLAM_BIVARIATE_MAP_HPP_ -#define SLAM_BIVARIATE_MAP_HPP_ +#pragma once #include "axom/slam/Map.hpp" #include "axom/slam/Relation.hpp" @@ -745,5 +744,3 @@ class BivariateMap::RangeIterator } // end namespace slam } // end namespace axom - -#endif // SLAM_BIVARIATE_MAP_HPP_ diff --git a/src/axom/slam/BivariateSet.hpp b/src/axom/slam/BivariateSet.hpp index 3a815534b5..3cf7b4a545 100644 --- a/src/axom/slam/BivariateSet.hpp +++ b/src/axom/slam/BivariateSet.hpp @@ -11,8 +11,7 @@ * */ -#ifndef SLAM_BIVARIATE_SET_H_ -#define SLAM_BIVARIATE_SET_H_ +#pragma once #include "axom/slic.hpp" @@ -454,5 +453,3 @@ class NullBivariateSet : public BivariateSet } // end namespace slam } // end namespace axom - -#endif // SLAM_BIVARIATE_SET_H_ diff --git a/src/axom/slam/DynamicConstantRelation.hpp b/src/axom/slam/DynamicConstantRelation.hpp index 58d3b922c0..302bed59fc 100644 --- a/src/axom/slam/DynamicConstantRelation.hpp +++ b/src/axom/slam/DynamicConstantRelation.hpp @@ -15,8 +15,7 @@ * This relation is dynamic; the related entities can change at runtime. */ -#ifndef SLAM_DYNAMIC_CONSTANT_RELATION_HPP_ -#define SLAM_DYNAMIC_CONSTANT_RELATION_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/slic.hpp" @@ -572,5 +571,3 @@ bool DynamicConstantRelation::isValid(bool } // end namespace slam } // end namespace axom - -#endif // SLAM_DYNAMIC_CONSTANT_RELATION_HPP_ diff --git a/src/axom/slam/DynamicMap.hpp b/src/axom/slam/DynamicMap.hpp index 89efabafee..9fdf406cab 100644 --- a/src/axom/slam/DynamicMap.hpp +++ b/src/axom/slam/DynamicMap.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef SLAM_DYNAMIC_MAP_HPP_ -#define SLAM_DYNAMIC_MAP_HPP_ +#pragma once #include #include @@ -249,5 +248,3 @@ bool DynamicMap::isValid(bool verboseOutput) const } // end namespace slam } // end namespace axom - -#endif // SLAM_DYNAMIC_MAP_HPP_ diff --git a/src/axom/slam/DynamicSet.hpp b/src/axom/slam/DynamicSet.hpp index 748251bae7..59235c5970 100644 --- a/src/axom/slam/DynamicSet.hpp +++ b/src/axom/slam/DynamicSet.hpp @@ -11,8 +11,7 @@ * at runtime */ -#ifndef SLAM_DYNAMIC_SET_H_ -#define SLAM_DYNAMIC_SET_H_ +#pragma once #include "axom/config.hpp" #include "axom/core/IteratorBase.hpp" @@ -488,5 +487,3 @@ constexpr typename DynamicSet::ElementType DynamicSet::INVALID } // end namespace slam } // end namespace axom - -#endif // SLAM_DYNAMIC_SET_H_ diff --git a/src/axom/slam/DynamicVariableRelation.hpp b/src/axom/slam/DynamicVariableRelation.hpp index c2b615ce66..2ecf52aa5e 100644 --- a/src/axom/slam/DynamicVariableRelation.hpp +++ b/src/axom/slam/DynamicVariableRelation.hpp @@ -13,8 +13,7 @@ * at runtime. */ -#ifndef SLAM_DYNAMIC_VARIABLE_RELATION_HPP_ -#define SLAM_DYNAMIC_VARIABLE_RELATION_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/slic.hpp" @@ -310,5 +309,3 @@ bool DynamicVariableRelation::isValid(bool verboseO } // end namespace slam } // end namespace axom - -#endif // SLAM_DYNAMIC_VARIABLE_RELATION_HPP_ diff --git a/src/axom/slam/FieldRegistry.hpp b/src/axom/slam/FieldRegistry.hpp index 30ed52bb0a..d0ac330773 100644 --- a/src/axom/slam/FieldRegistry.hpp +++ b/src/axom/slam/FieldRegistry.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef SLAM_FIELD_REGISTRY_H_ -#define SLAM_FIELD_REGISTRY_H_ +#pragma once #include "axom/slic.hpp" #include "axom/fmt.hpp" @@ -655,4 +654,3 @@ class FieldRegistry } // end namespace axom::slam -#endif // SLAM_FIELD_REGISTRY_H_ diff --git a/src/axom/slam/IndirectionSet.hpp b/src/axom/slam/IndirectionSet.hpp index aed452d667..0923494a18 100644 --- a/src/axom/slam/IndirectionSet.hpp +++ b/src/axom/slam/IndirectionSet.hpp @@ -10,8 +10,7 @@ * \brief Defines some alias templates for OrderedSets with indirection */ -#ifndef SLAM_INDIRECTION_SET_H_ -#define SLAM_INDIRECTION_SET_H_ +#pragma once #include #include @@ -84,5 +83,3 @@ using ArrayViewIndirectionSet = OrderedSet #include @@ -855,5 +854,3 @@ void Map::print() const } // end namespace slam } // end namespace axom - -#endif // SLAM_MAP_HPP_ diff --git a/src/axom/slam/MapBase.hpp b/src/axom/slam/MapBase.hpp index 302ae14f47..0ea37d5785 100644 --- a/src/axom/slam/MapBase.hpp +++ b/src/axom/slam/MapBase.hpp @@ -11,8 +11,7 @@ * */ -#ifndef SLAM_MAPBASE_HPP_ -#define SLAM_MAPBASE_HPP_ +#pragma once #include "axom/core/Macros.hpp" #include "axom/core/Types.hpp" @@ -64,5 +63,3 @@ class MapBase } // end namespace slam } // end namespace axom - -#endif // SLAM_MAPBASE_HPP_ diff --git a/src/axom/slam/ModularInt.hpp b/src/axom/slam/ModularInt.hpp index 1cd0f0adc3..433891e1b4 100644 --- a/src/axom/slam/ModularInt.hpp +++ b/src/axom/slam/ModularInt.hpp @@ -14,8 +14,7 @@ * */ -#ifndef SLAM_MODULAR_INT_H_ -#define SLAM_MODULAR_INT_H_ +#pragma once #include "axom/slic/interface/slic.hpp" #include "axom/slam/policies/SizePolicies.hpp" @@ -288,5 +287,3 @@ constexpr ModularInt operator*(const int n, const ModularInt; } // end namespace slam } // end namespace axom - -#endif // SLAM_RANGE_SET_H_ diff --git a/src/axom/slam/Relation.hpp b/src/axom/slam/Relation.hpp index f259fe2ec6..39247f83fd 100644 --- a/src/axom/slam/Relation.hpp +++ b/src/axom/slam/Relation.hpp @@ -11,8 +11,7 @@ * */ -#ifndef SLAM_RELATION_HPP_ -#define SLAM_RELATION_HPP_ +#pragma once #include @@ -86,5 +85,3 @@ NullSet Relation::s_nullSet; } // end namespace slam } // end namespace axom - -#endif // SLAM_RELATION_HPP_ diff --git a/src/axom/slam/RelationBuilders.hpp b/src/axom/slam/RelationBuilders.hpp index 9e1003ec9b..103ea9b73d 100644 --- a/src/axom/slam/RelationBuilders.hpp +++ b/src/axom/slam/RelationBuilders.hpp @@ -34,8 +34,7 @@ * deduction guides (the builder argument is a non-deduced nested-name context). */ -#ifndef SLAM_RELATION_BUILDERS_H_ -#define SLAM_RELATION_BUILDERS_H_ +#pragma once #include "axom/slam/StaticRelation.hpp" #include "axom/slam/policies/CardinalityPolicies.hpp" @@ -642,4 +641,3 @@ auto make_constant_relation_ct(FromSet& fromSet, ToSet& toSet, axom::Array #include @@ -170,5 +169,3 @@ inline bool operator!=(const Set& set1, const Set& set2) } // end namespace slam } // end namespace axom - -#endif // SLAM_SET_H_ diff --git a/src/axom/slam/StaticRelation.hpp b/src/axom/slam/StaticRelation.hpp index 737c5eb05b..9529e2c670 100644 --- a/src/axom/slam/StaticRelation.hpp +++ b/src/axom/slam/StaticRelation.hpp @@ -12,8 +12,7 @@ * */ -#ifndef SLAM_STATIC_RELATION_HPP_ -#define SLAM_STATIC_RELATION_HPP_ +#pragma once #include "axom/config.hpp" @@ -350,5 +349,3 @@ bool StaticRelation::RangeIterator } // end namespace slam } // end namespace axom - -#endif // SLAM_SUBMAP_HPP_ diff --git a/src/axom/slam/Utilities.hpp b/src/axom/slam/Utilities.hpp index 284636fb4c..32aecfc27c 100644 --- a/src/axom/slam/Utilities.hpp +++ b/src/axom/slam/Utilities.hpp @@ -8,8 +8,7 @@ * \file * \brief A few utility functions used by the SLAM component. */ -#ifndef SLAM_UTILITIES_H_ -#define SLAM_UTILITIES_H_ +#pragma once #include "axom/core.hpp" #include "axom/fmt.hpp" @@ -139,5 +138,3 @@ T distance(const Point3& pt1, const Point3& pt2) template struct axom::fmt::formatter> : ostream_formatter { }; - -#endif // SLAM_UTILITIES_H_ diff --git a/src/axom/slam/examples/lulesh2.0.3/lulesh.hpp b/src/axom/slam/examples/lulesh2.0.3/lulesh.hpp index 14885ef93f..acf2a5953f 100644 --- a/src/axom/slam/examples/lulesh2.0.3/lulesh.hpp +++ b/src/axom/slam/examples/lulesh2.0.3/lulesh.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "axom/slic.hpp" #include "axom/slam.hpp" diff --git a/src/axom/slam/examples/lulesh2.0.3/lulesh_tuple.hpp b/src/axom/slam/examples/lulesh2.0.3/lulesh_tuple.hpp index af90507011..b6ff9bd1b9 100644 --- a/src/axom/slam/examples/lulesh2.0.3/lulesh_tuple.hpp +++ b/src/axom/slam/examples/lulesh2.0.3/lulesh_tuple.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "axom/config.hpp" // defines AXOM_USE_MPI and AXOM_USE_OPENMP #ifdef AXOM_USE_MPI diff --git a/src/axom/slam/examples/lulesh2.0.3_orig/lulesh.h b/src/axom/slam/examples/lulesh2.0.3_orig/lulesh.h index fc1ee74b97..62a45128e3 100644 --- a/src/axom/slam/examples/lulesh2.0.3_orig/lulesh.h +++ b/src/axom/slam/examples/lulesh2.0.3_orig/lulesh.h @@ -1,3 +1,5 @@ +#pragma once + #if !defined(USE_MPI) # error "You should specify USE_MPI=0 or USE_MPI=1 on the compile line" #endif diff --git a/src/axom/slam/examples/lulesh2.0.3_orig/lulesh_tuple.h b/src/axom/slam/examples/lulesh2.0.3_orig/lulesh_tuple.h index 9ad7412f49..20ee582e16 100644 --- a/src/axom/slam/examples/lulesh2.0.3_orig/lulesh_tuple.h +++ b/src/axom/slam/examples/lulesh2.0.3_orig/lulesh_tuple.h @@ -1,3 +1,5 @@ +#pragma once + #if !defined(USE_MPI) # error "You should specify USE_MPI=0 or USE_MPI=1 on the compile line" #endif diff --git a/src/axom/slam/examples/tinyHydro/HydroC.hpp b/src/axom/slam/examples/tinyHydro/HydroC.hpp index bc8fec9aee..a52be895ad 100644 --- a/src/axom/slam/examples/tinyHydro/HydroC.hpp +++ b/src/axom/slam/examples/tinyHydro/HydroC.hpp @@ -16,6 +16,8 @@ // allocation of all the memory we ever need at problem start, and // explicit deletes when the hydro object is destroyed. +#pragma once + #include "State.hpp" #include "TinyHydroTypes.hpp" diff --git a/src/axom/slam/examples/tinyHydro/Part.hpp b/src/axom/slam/examples/tinyHydro/Part.hpp index 21d68a8ead..dfb799869f 100644 --- a/src/axom/slam/examples/tinyHydro/Part.hpp +++ b/src/axom/slam/examples/tinyHydro/Part.hpp @@ -7,8 +7,7 @@ // Part class holds the material data for a single material. -#ifndef __Part__ -#define __Part__ +#pragma once #include @@ -59,5 +58,3 @@ namespace tinyHydro { }; } // end namespace tinyHydro - -#endif diff --git a/src/axom/slam/examples/tinyHydro/PolygonMeshXY.hpp b/src/axom/slam/examples/tinyHydro/PolygonMeshXY.hpp index e45325109c..6c7770a318 100644 --- a/src/axom/slam/examples/tinyHydro/PolygonMeshXY.hpp +++ b/src/axom/slam/examples/tinyHydro/PolygonMeshXY.hpp @@ -7,8 +7,7 @@ // arbitrary mesh of polygons in XY geom // Thu Mar 26 09:38:50 PDT 2015 -#ifndef __PolygonMeshXY_hh__ -#define __PolygonMeshXY_hh__ +#pragma once #include "VectorXY.hpp" #include "TinyHydroTypes.hpp" @@ -77,5 +76,3 @@ namespace tinyHydro { }; } // end namespace tinyHydro - -#endif // __PolygonMeshXY_hh__ diff --git a/src/axom/slam/examples/tinyHydro/State.hpp b/src/axom/slam/examples/tinyHydro/State.hpp index d4284ef5f9..f2cb41a5ab 100644 --- a/src/axom/slam/examples/tinyHydro/State.hpp +++ b/src/axom/slam/examples/tinyHydro/State.hpp @@ -9,6 +9,8 @@ // that hold the data, plus a few functions to do mesh sums and // averages of quantities. +#pragma once + #include "axom/slam.hpp" #include "VectorXY.hpp" diff --git a/src/axom/slam/examples/tinyHydro/TinyHydroTypes.hpp b/src/axom/slam/examples/tinyHydro/TinyHydroTypes.hpp index bd0504499a..59230a8f2b 100644 --- a/src/axom/slam/examples/tinyHydro/TinyHydroTypes.hpp +++ b/src/axom/slam/examples/tinyHydro/TinyHydroTypes.hpp @@ -6,8 +6,7 @@ // Part class holds the material data for a single material. -#ifndef __TINY_HYDRO_TYPES_H__ -#define __TINY_HYDRO_TYPES_H__ +#pragma once #include "VectorXY.hpp" @@ -86,5 +85,3 @@ namespace tinyHydro { }; } // end namespace tinyHydro - -#endif diff --git a/src/axom/slam/examples/tinyHydro/VectorXY.hpp b/src/axom/slam/examples/tinyHydro/VectorXY.hpp index 40c2328bac..8fc90668c2 100644 --- a/src/axom/slam/examples/tinyHydro/VectorXY.hpp +++ b/src/axom/slam/examples/tinyHydro/VectorXY.hpp @@ -8,8 +8,7 @@ // Fri Nov 21 10:50:53 PST 2014 #include -#ifndef _VECTORXY_H -#define _VECTORXY_H 1 +#pragma once namespace tinyHydro { @@ -153,5 +152,3 @@ namespace tinyHydro { } // end namespace tinyHydro - -#endif diff --git a/src/axom/slam/mesh_struct/IA.hpp b/src/axom/slam/mesh_struct/IA.hpp index 4566a8a974..520e0c38a6 100644 --- a/src/axom/slam/mesh_struct/IA.hpp +++ b/src/axom/slam/mesh_struct/IA.hpp @@ -10,8 +10,7 @@ * \brief Contains the header information of IA class */ -#ifndef SLAM_IA_H_ -#define SLAM_IA_H_ +#pragma once #include "axom/config.hpp" #include "axom/core.hpp" @@ -448,5 +447,3 @@ constexpr int IAMesh::VERTS_PER_ELEM; } // end namespace axom #include "axom/slam/mesh_struct/IA_impl.hpp" - -#endif // SLAM_IA_H_ diff --git a/src/axom/slam/mesh_struct/IA_impl.hpp b/src/axom/slam/mesh_struct/IA_impl.hpp index 5cf17c5ccd..b27b1f54d0 100644 --- a/src/axom/slam/mesh_struct/IA_impl.hpp +++ b/src/axom/slam/mesh_struct/IA_impl.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef SLAM_IA_IMPL_H_ -#define SLAM_IA_IMPL_H_ +#pragma once /* * \file IA_impl.hpp @@ -1284,5 +1283,3 @@ bool IAMesh::isConforming(bool verboseOutput) const } // end namespace slam } // end namespace axom - -#endif // SLAM_IA_IMPL_H_ diff --git a/src/axom/slam/mesh_struct/detail/FacetPairingMap.hpp b/src/axom/slam/mesh_struct/detail/FacetPairingMap.hpp index 76102d6a58..b07caa2c83 100644 --- a/src/axom/slam/mesh_struct/detail/FacetPairingMap.hpp +++ b/src/axom/slam/mesh_struct/detail/FacetPairingMap.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_SLAM_MESH_STRUCT_DETAIL_FACET_PAIRING_MAP_HPP_ -#define AXOM_SLAM_MESH_STRUCT_DETAIL_FACET_PAIRING_MAP_HPP_ +#pragma once /** * \file FacetPairingMap.hpp @@ -465,5 +464,3 @@ thread_local unsigned int FacetPairingMap::s_generation = 0; } // namespace detail } // namespace slam } // namespace axom - -#endif // AXOM_SLAM_MESH_STRUCT_DETAIL_FACET_PAIRING_MAP_HPP_ diff --git a/src/axom/slam/policies/BivariateSetInterfacePolicies.hpp b/src/axom/slam/policies/BivariateSetInterfacePolicies.hpp index 27a1d13876..b550efdd2c 100644 --- a/src/axom/slam/policies/BivariateSetInterfacePolicies.hpp +++ b/src/axom/slam/policies/BivariateSetInterfacePolicies.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef SLAM_BivarSetIfacePolicies_HPP -#define SLAM_BivarSetIfacePolicies_HPP +#pragma once #include "axom/slam/BivariateSet.hpp" #include "axom/slam/policies/InterfacePolicies.hpp" @@ -142,5 +141,3 @@ using BivariateSetInterface = } // end namespace policies } // end namespace slam } // end namespace axom - -#endif // SLAM_BivarSetIfacePolicies_HPP diff --git a/src/axom/slam/policies/CardinalityPolicies.hpp b/src/axom/slam/policies/CardinalityPolicies.hpp index 03ceeb107a..0a37294cde 100644 --- a/src/axom/slam/policies/CardinalityPolicies.hpp +++ b/src/axom/slam/policies/CardinalityPolicies.hpp @@ -41,8 +41,7 @@ * */ -#ifndef SLAM_POLICIES_CARDINALITY_H_ -#define SLAM_POLICIES_CARDINALITY_H_ +#pragma once #include "axom/config.hpp" #include "axom/core/Macros.hpp" @@ -314,5 +313,3 @@ struct MappedVariableCardinality } // end namespace slam } // end namespace axom - -#endif // SLAM_POLICIES_CARDINALITY_H_ diff --git a/src/axom/slam/policies/IndirectionPolicies.hpp b/src/axom/slam/policies/IndirectionPolicies.hpp index aaced1fa4c..754c10c7d6 100644 --- a/src/axom/slam/policies/IndirectionPolicies.hpp +++ b/src/axom/slam/policies/IndirectionPolicies.hpp @@ -30,8 +30,7 @@ * allocating/deallocating their own memory */ -#ifndef SLAM_POLICIES_INDIRECTION_H_ -#define SLAM_POLICIES_INDIRECTION_H_ +#pragma once #include "axom/core/Macros.hpp" #include "axom/core/Array.hpp" @@ -462,5 +461,3 @@ using ArrayViewIndirection = /// \} } // end namespace axom::slam::policies - -#endif // SLAM_POLICIES_INDIRECTION_H_ diff --git a/src/axom/slam/policies/InterfacePolicies.hpp b/src/axom/slam/policies/InterfacePolicies.hpp index d615ce6bae..40d3d2abe3 100644 --- a/src/axom/slam/policies/InterfacePolicies.hpp +++ b/src/axom/slam/policies/InterfacePolicies.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef SLAM_InterfacePolicies_HPP -#define SLAM_InterfacePolicies_HPP +#pragma once /** * \file InterfacePolicies.hpp @@ -58,5 +57,3 @@ struct ConcreteInterface } // namespace policies } // namespace slam } // namespace axom - -#endif // SLAM_InterfacePolicies_HPP diff --git a/src/axom/slam/policies/MapInterfacePolicies.hpp b/src/axom/slam/policies/MapInterfacePolicies.hpp index 9c00e3b650..723ed0c2f4 100644 --- a/src/axom/slam/policies/MapInterfacePolicies.hpp +++ b/src/axom/slam/policies/MapInterfacePolicies.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef SLAM_MapIfacePolicies_HPP -#define SLAM_MapIfacePolicies_HPP +#pragma once #include @@ -43,5 +42,3 @@ using MapInterface = typename detail::MapInterfaceSelector; /// \} } // end namespace axom::slam::policies - -#endif // SLAM_POLICIES_OFFSET_H_ diff --git a/src/axom/slam/policies/PolicyTraits.hpp b/src/axom/slam/policies/PolicyTraits.hpp index 501800f490..0844e6cf64 100644 --- a/src/axom/slam/policies/PolicyTraits.hpp +++ b/src/axom/slam/policies/PolicyTraits.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef SLAM_POLICY_TRAITS_H_ -#define SLAM_POLICY_TRAITS_H_ +#pragma once /** * \file PolicyTraits.hpp @@ -125,5 +124,3 @@ struct indices_use_indirection> : std ///@} } // end namespace axom::slam::traits - -#endif // SLAM_POLICY_TRAITS_H_ diff --git a/src/axom/slam/policies/SetInterfacePolicies.hpp b/src/axom/slam/policies/SetInterfacePolicies.hpp index 2d14bb861f..4566275e26 100644 --- a/src/axom/slam/policies/SetInterfacePolicies.hpp +++ b/src/axom/slam/policies/SetInterfacePolicies.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef SLAM_SetIfacePolicies_HPP -#define SLAM_SetIfacePolicies_HPP +#pragma once #include @@ -60,5 +59,3 @@ using SetInterface = typename detail::SetInterfaceSelector::DEFAULT_VALUE = IntType {}; /// \} } // end namespace axom::slam::policies - -#endif // SLAM_POLICIES_SIZE_H_ diff --git a/src/axom/slam/policies/StridePolicies.hpp b/src/axom/slam/policies/StridePolicies.hpp index 5a99df072c..d2b03af077 100644 --- a/src/axom/slam/policies/StridePolicies.hpp +++ b/src/axom/slam/policies/StridePolicies.hpp @@ -29,8 +29,7 @@ * MultiDimStride is a separate, inherently multi-dimensional policy and is unaffected. */ -#ifndef SLAM_POLICIES_STRIDE_H_ -#define SLAM_POLICIES_STRIDE_H_ +#pragma once #include "axom/core/Macros.hpp" #include "axom/core/StackArray.hpp" @@ -167,5 +166,3 @@ struct MultiDimStride /// \} } // end namespace axom::slam::policies - -#endif // SLAM_POLICIES_STRIDE_H_ diff --git a/src/axom/slam/policies/SubsettingPolicies.hpp b/src/axom/slam/policies/SubsettingPolicies.hpp index f85520a9b7..87de28b587 100644 --- a/src/axom/slam/policies/SubsettingPolicies.hpp +++ b/src/axom/slam/policies/SubsettingPolicies.hpp @@ -20,8 +20,7 @@ * * operator(): IntType -- alternate accessor for indirection */ -#ifndef SLAM_POLICIES_SUBSET_H_ -#define SLAM_POLICIES_SUBSET_H_ +#pragma once #include "axom/config.hpp" #include "axom/core/Macros.hpp" @@ -199,5 +198,3 @@ struct ConcreteParentSubset /// \} } // end namespace axom::slam::policies - -#endif // SLAM_POLICIES_SUBSET_H_ diff --git a/src/axom/slic/core/LogStream.hpp b/src/axom/slic/core/LogStream.hpp index 15c793afe7..0412c49313 100644 --- a/src/axom/slic/core/LogStream.hpp +++ b/src/axom/slic/core/LogStream.hpp @@ -9,8 +9,7 @@ * */ -#ifndef LOGSTREAM_HPP_ -#define LOGSTREAM_HPP_ +#pragma once #include "axom/slic/core/MessageLevel.hpp" #include "axom/core/Macros.hpp" @@ -228,5 +227,3 @@ class LogStream } /* namespace slic */ } /* namespace axom */ - -#endif /* LOGSTREAM_HPP_ */ diff --git a/src/axom/slic/core/LogStreamStatusMonitor.hpp b/src/axom/slic/core/LogStreamStatusMonitor.hpp index a52f93237a..c19da7309d 100644 --- a/src/axom/slic/core/LogStreamStatusMonitor.hpp +++ b/src/axom/slic/core/LogStreamStatusMonitor.hpp @@ -9,8 +9,7 @@ * */ -#ifndef LOGSTREAMSTATUS_MONITOR_HPP_ -#define LOGSTREAMSTATUS_MONITOR_HPP_ +#pragma once #include #include "axom/slic/core/LogStream.hpp" @@ -66,5 +65,3 @@ class LogStreamStatusMonitor } /* namespace slic */ } /* namespace axom */ - -#endif /* LOGSTREAMSTATUSMONITOR_HPP_ */ diff --git a/src/axom/slic/core/Logger.hpp b/src/axom/slic/core/Logger.hpp index 06223c33bd..16a5e0dff8 100644 --- a/src/axom/slic/core/Logger.hpp +++ b/src/axom/slic/core/Logger.hpp @@ -8,8 +8,7 @@ * \file Logger.hpp */ -#ifndef LOGGER_HPP_ -#define LOGGER_HPP_ +#pragma once #include "axom/slic/core/LogStreamStatusMonitor.hpp" #include "axom/slic/core/MessageLevel.hpp" @@ -488,5 +487,3 @@ class Logger } /* namespace slic */ } /* namespace axom */ - -#endif /* LOGGER_HPP_ */ diff --git a/src/axom/slic/core/MessageLevel.hpp b/src/axom/slic/core/MessageLevel.hpp index 5dbe06c241..e114b55a8a 100644 --- a/src/axom/slic/core/MessageLevel.hpp +++ b/src/axom/slic/core/MessageLevel.hpp @@ -9,8 +9,7 @@ * */ -#ifndef MESSAGELEVEL_H_ -#define MESSAGELEVEL_H_ +#pragma once #include @@ -97,5 +96,3 @@ static const flags masks[message::Num_Levels] = {error, warning, info, debug}; } /* namespace slic */ } /* namespace axom */ - -#endif /* MESSAGELEVEL_H_ */ diff --git a/src/axom/slic/core/SimpleLogger.hpp b/src/axom/slic/core/SimpleLogger.hpp index 1223c070a8..10e4ec2e70 100644 --- a/src/axom/slic/core/SimpleLogger.hpp +++ b/src/axom/slic/core/SimpleLogger.hpp @@ -11,8 +11,7 @@ * */ -#ifndef SLIC_SIMPLELOGGER_HPP_ -#define SLIC_SIMPLELOGGER_HPP_ +#pragma once // Other axom headers #include "axom/config.hpp" @@ -103,5 +102,3 @@ class SimpleLogger } // namespace slic } // namespace axom - -#endif // SLIC_SIMPLELOGGER_HPP_ diff --git a/src/axom/slic/examples/multicode/physicsA.hpp b/src/axom/slic/examples/multicode/physicsA.hpp index 551baa195d..3fffe92de5 100644 --- a/src/axom/slic/examples/multicode/physicsA.hpp +++ b/src/axom/slic/examples/multicode/physicsA.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef PHYSICSA_HPP_ -#define PHYSICSA_HPP_ +#pragma once // SLIC includes #include "axom/slic/interface/slic.hpp" @@ -74,5 +73,3 @@ void timestep(int step, int n) inline void finalize() { physicsA_log.close(); } } /* namespace physicsA */ - -#endif /* PHYSICSA_HPP_ */ diff --git a/src/axom/slic/examples/multicode/physicsB.hpp b/src/axom/slic/examples/multicode/physicsB.hpp index 6a9ec59e2a..2a5b4e769d 100644 --- a/src/axom/slic/examples/multicode/physicsB.hpp +++ b/src/axom/slic/examples/multicode/physicsB.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef PHYSICSB_HPP_ -#define PHYSICSB_HPP_ +#pragma once // SLIC includes #include "axom/slic/interface/slic.hpp" @@ -76,5 +75,3 @@ void timestep(int step, int n) inline void finalize() { physicsB_log.close(); } } /* namespace physicsB */ - -#endif /* PHYSICSB_HPP_ */ diff --git a/src/axom/slic/interface/c_fortran/typesSLIC.h b/src/axom/slic/interface/c_fortran/typesSLIC.h index 45193987ad..777e5a6a35 100644 --- a/src/axom/slic/interface/c_fortran/typesSLIC.h +++ b/src/axom/slic/interface/c_fortran/typesSLIC.h @@ -8,8 +8,7 @@ // SPDX-License-Identifier: (BSD-3-Clause) // For C users and C++ implementation -#ifndef TYPESSLIC_H -#define TYPESSLIC_H +#pragma once #include @@ -121,5 +120,3 @@ void SLIC_SHROUD_memory_destructor(SLIC_SHROUD_capsule_data *cap); #ifdef __cplusplus } #endif - -#endif // TYPESSLIC_H diff --git a/src/axom/slic/interface/c_fortran/wrapGenericOutputStream.h b/src/axom/slic/interface/c_fortran/wrapGenericOutputStream.h index 307fa07aec..98d18948b9 100644 --- a/src/axom/slic/interface/c_fortran/wrapGenericOutputStream.h +++ b/src/axom/slic/interface/c_fortran/wrapGenericOutputStream.h @@ -12,8 +12,7 @@ */ // For C users and C++ implementation -#ifndef WRAPGENERICOUTPUTSTREAM_H -#define WRAPGENERICOUTPUTSTREAM_H +#pragma once #include "typesSLIC.h" @@ -49,5 +48,3 @@ void SLIC_GenericOutputStream_delete(SLIC_GenericOutputStream *self); #ifdef __cplusplus } #endif - -#endif // WRAPGENERICOUTPUTSTREAM_H diff --git a/src/axom/slic/interface/c_fortran/wrapSLIC.h b/src/axom/slic/interface/c_fortran/wrapSLIC.h index b4389c1824..493faf851e 100644 --- a/src/axom/slic/interface/c_fortran/wrapSLIC.h +++ b/src/axom/slic/interface/c_fortran/wrapSLIC.h @@ -12,8 +12,7 @@ */ // For C users and C++ implementation -#ifndef WRAPSLIC_H -#define WRAPSLIC_H +#pragma once #include "wrapSLIC.h" #ifndef __cplusplus @@ -134,5 +133,3 @@ void SLIC_finalize(void); #ifdef __cplusplus } #endif - -#endif // WRAPSLIC_H diff --git a/src/axom/slic/interface/slic.hpp b/src/axom/slic/interface/slic.hpp index ca7155a111..a2ccf33bf2 100644 --- a/src/axom/slic/interface/slic.hpp +++ b/src/axom/slic/interface/slic.hpp @@ -8,8 +8,7 @@ * \file slic.hpp */ -#ifndef SLIC_HPP_ -#define SLIC_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core/memory_management.hpp" @@ -546,5 +545,3 @@ void printContainer(std::ostream& os, const std::string& name, const ContainerTy } /* namespace slic */ } /* namespace axom */ - -#endif /* SLIC_HPP_ */ diff --git a/src/axom/slic/interface/slic_macros.hpp b/src/axom/slic/interface/slic_macros.hpp index 4cda60dc01..0d2ed6a7cc 100644 --- a/src/axom/slic/interface/slic_macros.hpp +++ b/src/axom/slic/interface/slic_macros.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_SLIC_MACROS_HPP_ -#define AXOM_SLIC_MACROS_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core/Macros.hpp" @@ -924,5 +923,3 @@ static const FalseType false_value; } /* namespace detail */ } /* namespace slic */ } /* namespace axom */ - -#endif /* AXOM_SLIC_MACROS_HPP_ */ diff --git a/src/axom/slic/internal/stacktrace.hpp b/src/axom/slic/internal/stacktrace.hpp index 72e99c4379..94d2f72f1c 100644 --- a/src/axom/slic/internal/stacktrace.hpp +++ b/src/axom/slic/internal/stacktrace.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include namespace axom diff --git a/src/axom/slic/streams/GenericOutputStream.hpp b/src/axom/slic/streams/GenericOutputStream.hpp index c04e66cfe1..375b70b98a 100644 --- a/src/axom/slic/streams/GenericOutputStream.hpp +++ b/src/axom/slic/streams/GenericOutputStream.hpp @@ -9,8 +9,7 @@ * */ -#ifndef GENERICOUTPUTSTREAM_HPP_ -#define GENERICOUTPUTSTREAM_HPP_ +#pragma once #include "axom/slic/core/LogStream.hpp" @@ -135,5 +134,3 @@ class GenericOutputStream : public LogStream } /* namespace slic */ } /* namespace axom */ - -#endif /* GENERICOUTPUTSTREAM_HPP_ */ diff --git a/src/axom/slic/streams/LumberjackStream.hpp b/src/axom/slic/streams/LumberjackStream.hpp index bb592d47be..f298e7de39 100644 --- a/src/axom/slic/streams/LumberjackStream.hpp +++ b/src/axom/slic/streams/LumberjackStream.hpp @@ -9,8 +9,7 @@ * */ -#ifndef LUMBERJACKSTREAM_HPP_ -#define LUMBERJACKSTREAM_HPP_ +#pragma once #include "axom/slic/core/LogStream.hpp" @@ -288,5 +287,3 @@ class LumberjackStream : public LogStream } /* namespace slic */ } /* namespace axom */ - -#endif /* LUMBERJACKSTREAM_HPP_ */ diff --git a/src/axom/slic/streams/SynchronizedStream.hpp b/src/axom/slic/streams/SynchronizedStream.hpp index 29e040c2df..1dda1b77ae 100644 --- a/src/axom/slic/streams/SynchronizedStream.hpp +++ b/src/axom/slic/streams/SynchronizedStream.hpp @@ -9,8 +9,7 @@ * */ -#ifndef SYNCHRONIZEDSTREAM_HPP_ -#define SYNCHRONIZEDSTREAM_HPP_ +#pragma once #include "axom/slic/core/LogStream.hpp" @@ -204,5 +203,3 @@ class SynchronizedStream : public LogStream } /* namespace slic */ } /* namespace axom */ - -#endif /* SYNCHRONIZEDSTREAM_HPP_ */ diff --git a/src/axom/spin/BVH.hpp b/src/axom/spin/BVH.hpp index 8371dba9a7..8cd7552c44 100644 --- a/src/axom/spin/BVH.hpp +++ b/src/axom/spin/BVH.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_SPIN_BVH_H_ -#define AXOM_SPIN_BVH_H_ +#pragma once // axom core includes #include "axom/config.hpp" // for Axom compile-time definitions @@ -579,5 +578,3 @@ void BVH::writeVtkFile(const std::string& fil } // namespace spin } // namespace axom - -#endif // AXOM_SPIN_BVH_H_ diff --git a/src/axom/spin/Brood.hpp b/src/axom/spin/Brood.hpp index 2223daa722..7386894cb4 100644 --- a/src/axom/spin/Brood.hpp +++ b/src/axom/spin/Brood.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_SPIN_BROOD__HPP_ -#define AXOM_SPIN_BROOD__HPP_ +#pragma once #include "axom/config.hpp" #include "axom/spin/MortonIndex.hpp" @@ -123,5 +122,3 @@ struct Brood } // end namespace spin } // end namespace axom - -#endif // AXOM_SPIN_BROOD__HPP_ diff --git a/src/axom/spin/DenseOctreeLevel.hpp b/src/axom/spin/DenseOctreeLevel.hpp index ab45c91488..ef646d14e9 100644 --- a/src/axom/spin/DenseOctreeLevel.hpp +++ b/src/axom/spin/DenseOctreeLevel.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_SPIN_DENSE_OCTREE_LEVEL__HPP_ -#define AXOM_SPIN_DENSE_OCTREE_LEVEL__HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core/Types.hpp" @@ -320,5 +319,3 @@ class DenseOctreeLevel : public OctreeLevel } // end namespace spin } // end namespace axom - -#endif // AXOM_SPIN_DENSE_OCTREE_LEVEL__HPP_ diff --git a/src/axom/spin/ImplicitGrid.hpp b/src/axom/spin/ImplicitGrid.hpp index a5337e4fbb..53e05c6e6f 100644 --- a/src/axom/spin/ImplicitGrid.hpp +++ b/src/axom/spin/ImplicitGrid.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_SPIN_IMPLICIT_GRID__HPP_ -#define AXOM_SPIN_IMPLICIT_GRID__HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core.hpp" @@ -1094,5 +1093,3 @@ AXOM_HOST_DEVICE void ImplicitGrid::QueryObject::vi } // namespace spin } // namespace axom - -#endif // AXOM_SPIN_IMPLICIT_GRID__HPP_ diff --git a/src/axom/spin/MortonIndex.hpp b/src/axom/spin/MortonIndex.hpp index c6a566363e..63083277ba 100644 --- a/src/axom/spin/MortonIndex.hpp +++ b/src/axom/spin/MortonIndex.hpp @@ -14,8 +14,7 @@ * functor class that can be used as a std::hash for unordered_maps and axom::FlatMap */ -#ifndef AXOM_SPIN_MORTON_INDEX_HPP_ -#define AXOM_SPIN_MORTON_INDEX_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core/Types.hpp" @@ -580,5 +579,3 @@ struct PointHash } // end namespace spin } // end namespace axom - -#endif // AXOM_SPIN_MORTON_INDEX_HPP_ diff --git a/src/axom/spin/OctreeBase.hpp b/src/axom/spin/OctreeBase.hpp index 4704e3a9ae..9e03a966cf 100644 --- a/src/axom/spin/OctreeBase.hpp +++ b/src/axom/spin/OctreeBase.hpp @@ -9,8 +9,7 @@ * \brief Defines templated OctreeBase class and its inner class BlockIndex */ -#ifndef AXOM_SPIN_OCTREE_BASE__HPP_ -#define AXOM_SPIN_OCTREE_BASE__HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core/NumericLimits.hpp" @@ -914,5 +913,3 @@ class OctreeBase } // end namespace spin } // end namespace axom - -#endif // AXOM_SPIN_OCTREE_BASE__HPP_ diff --git a/src/axom/spin/OctreeLevel.hpp b/src/axom/spin/OctreeLevel.hpp index 83cc985e94..d448c0fc41 100644 --- a/src/axom/spin/OctreeLevel.hpp +++ b/src/axom/spin/OctreeLevel.hpp @@ -17,8 +17,7 @@ * hash key for its octree blocks. */ -#ifndef AXOM_SPIN_OCTREE_LEVEL__HPP_ -#define AXOM_SPIN_OCTREE_LEVEL__HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core.hpp" @@ -344,5 +343,3 @@ class OctreeLevel } // end namespace spin } // end namespace axom - -#endif // AXOM_SPIN_OCTREE_LEVEL__HPP_ diff --git a/src/axom/spin/RectangularLattice.hpp b/src/axom/spin/RectangularLattice.hpp index 3e83ebd599..61814af4bb 100644 --- a/src/axom/spin/RectangularLattice.hpp +++ b/src/axom/spin/RectangularLattice.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_SPIN_RECTANGULAR_LATTICE_HPP_ -#define AXOM_SPIN_RECTANGULAR_LATTICE_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core/NumericArray.hpp" @@ -294,5 +293,3 @@ std::ostream& operator<<(std::ostream& os, } // end namespace spin } // end namespace axom - -#endif // AXOM_SPIN_RECTANGULAR_LATTICE_HPP_ diff --git a/src/axom/spin/SparseOctreeLevel.hpp b/src/axom/spin/SparseOctreeLevel.hpp index c76401a82a..b58007d808 100644 --- a/src/axom/spin/SparseOctreeLevel.hpp +++ b/src/axom/spin/SparseOctreeLevel.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_SPIN_SPARSE_OCTREE_LEVEL__HPP_ -#define AXOM_SPIN_SPARSE_OCTREE_LEVEL__HPP_ +#pragma once #include "axom/config.hpp" @@ -350,5 +349,3 @@ class SparseOctreeLevel : public OctreeLevel } // end namespace spin } // end namespace axom - -#endif // AXOM_SPIN_SPARSE_OCTREE_LEVEL__HPP_ diff --git a/src/axom/spin/SpatialOctree.hpp b/src/axom/spin/SpatialOctree.hpp index 95502ee638..864635a3ce 100644 --- a/src/axom/spin/SpatialOctree.hpp +++ b/src/axom/spin/SpatialOctree.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_SPIN_SPATIAL_OCTREE__HPP_ -#define AXOM_SPIN_SPATIAL_OCTREE__HPP_ +#pragma once #include "axom/core.hpp" #include "axom/slic.hpp" @@ -196,5 +195,3 @@ class SpatialOctree : public OctreeBase } // end namespace spin } // end namespace axom - -#endif // AXOM_SPIN_SPATIAL_OCTREE__HPP_ diff --git a/src/axom/spin/UniformGrid.hpp b/src/axom/spin/UniformGrid.hpp index 765a689f91..c3b6a90c11 100644 --- a/src/axom/spin/UniformGrid.hpp +++ b/src/axom/spin/UniformGrid.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_SPIN_UNIFORMGRID_HPP_ -#define AXOM_SPIN_UNIFORMGRID_HPP_ +#pragma once #include "axom/core/utilities/Utilities.hpp" #include "axom/core/execution/for_all.hpp" @@ -872,5 +871,3 @@ UniformGrid::getClampedGridCell( } // end namespace spin } // end namespace axom - -#endif // AXOM_SPIN_UNIFORMGRID_HPP_ diff --git a/src/axom/spin/internal/linear_bvh/RadixTree.hpp b/src/axom/spin/internal/linear_bvh/RadixTree.hpp index 41153ee365..c553427ae7 100644 --- a/src/axom/spin/internal/linear_bvh/RadixTree.hpp +++ b/src/axom/spin/internal/linear_bvh/RadixTree.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_SPIN_RADIXTREE_HPP_ -#define AXOM_SPIN_RADIXTREE_HPP_ +#pragma once #include "axom/core/Array.hpp" #include "axom/core/AnnotationMacros.hpp" @@ -67,5 +66,3 @@ struct RadixTree } /* namespace internal */ } /* namespace spin */ } /* namespace axom */ - -#endif /* AXOM_RADIXTREE_HPP_ */ diff --git a/src/axom/spin/internal/linear_bvh/build_radix_tree.hpp b/src/axom/spin/internal/linear_bvh/build_radix_tree.hpp index 1cbbbdf4ab..63df3a133d 100644 --- a/src/axom/spin/internal/linear_bvh/build_radix_tree.hpp +++ b/src/axom/spin/internal/linear_bvh/build_radix_tree.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_SPIN_BUILD_RADIX_TREE_H_ -#define AXOM_SPIN_BUILD_RADIX_TREE_H_ +#pragma once #include "axom/config.hpp" @@ -628,4 +627,3 @@ void build_radix_tree(const BoxIndexable boxes, } /* namespace internal */ } /* namespace spin */ } /* namespace axom */ -#endif diff --git a/src/axom/spin/internal/linear_bvh/bvh_traverse.hpp b/src/axom/spin/internal/linear_bvh/bvh_traverse.hpp index 0efbc0eaff..9de97b7962 100644 --- a/src/axom/spin/internal/linear_bvh/bvh_traverse.hpp +++ b/src/axom/spin/internal/linear_bvh/bvh_traverse.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_SPIN_BVH_TRAVERSE_HPP_ -#define AXOM_SPIN_BVH_TRAVERSE_HPP_ +#pragma once #include "axom/config.hpp" // compile-time definitions #include "axom/core/Macros.hpp" // for AXOM_HOST_DEVICE @@ -202,5 +201,3 @@ AXOM_HOST_DEVICE inline void bvh_traverse( } /* namespace internal */ } /* namespace spin */ } /* namespace axom */ - -#endif /* AXOM_SPIN_BVH_TRAVERSE_HPP_ */ diff --git a/src/axom/spin/internal/linear_bvh/bvh_vtkio.hpp b/src/axom/spin/internal/linear_bvh/bvh_vtkio.hpp index ee53c54f3e..1b745d9e66 100644 --- a/src/axom/spin/internal/linear_bvh/bvh_vtkio.hpp +++ b/src/axom/spin/internal/linear_bvh/bvh_vtkio.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_SPIN_LINEAR_BVH_VTKIO_HPP_ -#define AXOM_SPIN_LINEAR_BVH_VTKIO_HPP_ +#pragma once #include "axom/primal/geometry/BoundingBox.hpp" @@ -209,5 +208,3 @@ void write_recursive(ArrayView> inne } /* namespace internal */ } /* namespace spin */ } /* namespace axom */ - -#endif /* AXOM_SPIN_LINEAR_BVH_VTKIO_HPP_ */ diff --git a/src/axom/spin/policy/LinearBVH.hpp b/src/axom/spin/policy/LinearBVH.hpp index fa4a17ef4a..438d4a9d9d 100644 --- a/src/axom/spin/policy/LinearBVH.hpp +++ b/src/axom/spin/policy/LinearBVH.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_SPIN_POLICY_LINEARBVH_HPP_ -#define AXOM_SPIN_POLICY_LINEARBVH_HPP_ +#pragma once // axom core includes #include "axom/core/Types.hpp" @@ -563,4 +562,3 @@ void LinearBVH::writeVtkFileImpl(const std::string& } // namespace policy } // namespace spin } // namespace axom -#endif // AXOM_SPIN_POLICY_LINEARBVH_HPP_ diff --git a/src/axom/spin/policy/UniformGridStorage.hpp b/src/axom/spin/policy/UniformGridStorage.hpp index 618990193a..e5740488cf 100644 --- a/src/axom/spin/policy/UniformGridStorage.hpp +++ b/src/axom/spin/policy/UniformGridStorage.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_SPIN_POLICY_UGRID_STORAGE_HPP -#define AXOM_SPIN_POLICY_UGRID_STORAGE_HPP +#pragma once #include "axom/core/Array.hpp" #include "axom/core/execution/for_all.hpp" @@ -279,4 +278,3 @@ struct FlatGridView } // namespace policy } // namespace spin } // namespace axom -#endif // AXOM_SPIN_POLICY_UGRID_STORAGE_HPP diff --git a/src/examples/radiuss_tutorial/patch/hip_patch.hpp b/src/examples/radiuss_tutorial/patch/hip_patch.hpp index e779079daf..ba054fd2c3 100644 --- a/src/examples/radiuss_tutorial/patch/hip_patch.hpp +++ b/src/examples/radiuss_tutorial/patch/hip_patch.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_RADIUSS_TUTORIAL_HIP_PATCH -#define AXOM_RADIUSS_TUTORIAL_HIP_PATCH +#pragma once #include "axom/config.hpp" @@ -31,5 +30,3 @@ constexpr unsigned int } // namespace rocprim #endif - -#endif // AXOM_RADIUSS_TUTORIAL_HIP_PATCH \ No newline at end of file From a60f62f8c323667dd8ce981abe05d3884df01e6d Mon Sep 17 00:00:00 2001 From: Chris White Date: Mon, 29 Jun 2026 13:55:11 -0700 Subject: [PATCH 686/986] style --- src/axom/core/Types.hpp | 2 +- src/axom/core/execution/nested_for_exec.hpp | 4 ++-- src/axom/core/numerics/Matrix.hpp | 12 ++++++------ src/axom/core/utilities/BitUtilities.hpp | 2 +- src/axom/inlet/inlet_utils.hpp | 18 +++++++++--------- src/axom/mint/mesh/CurvilinearMesh.hpp | 2 +- src/axom/mint/mesh/ParticleMesh.hpp | 4 ++-- src/axom/mint/mesh/RectilinearMesh.hpp | 2 +- src/axom/mint/mesh/UniformMesh.hpp | 2 +- src/axom/mint/mesh/UnstructuredMesh.hpp | 2 +- src/axom/quest/IntersectionShaper.hpp | 2 +- src/axom/quest/ShapeMesh.hpp | 6 +++--- .../internal/linear_bvh/build_radix_tree.hpp | 18 +++++++++--------- 13 files changed, 38 insertions(+), 38 deletions(-) diff --git a/src/axom/core/Types.hpp b/src/axom/core/Types.hpp index 06c102c71f..73352767af 100644 --- a/src/axom/core/Types.hpp +++ b/src/axom/core/Types.hpp @@ -206,7 +206,7 @@ struct mpi_traits }; #endif // AXOM_NO_INT64_T - /// @} +/// @} #endif // AXOM_USE_MPI diff --git a/src/axom/core/execution/nested_for_exec.hpp b/src/axom/core/execution/nested_for_exec.hpp index a72163f322..0d8f87dbcc 100644 --- a/src/axom/core/execution/nested_for_exec.hpp +++ b/src/axom/core/execution/nested_for_exec.hpp @@ -23,7 +23,7 @@ using tile_fixed = ::RAJA::statement::tile_fixed< SIZE >; } // namespace RAJA - /* clang-format on */ + /* clang-format on */ #endif @@ -74,7 +74,7 @@ struct nested_for_exec > // END j > // END k >; // END kernel - /* clang-format on */ + /* clang-format on */ #else using loop2d_policy = void; diff --git a/src/axom/core/numerics/Matrix.hpp b/src/axom/core/numerics/Matrix.hpp index eddcfc3af3..451cde9f16 100644 --- a/src/axom/core/numerics/Matrix.hpp +++ b/src/axom/core/numerics/Matrix.hpp @@ -625,13 +625,13 @@ AXOM_HOST_DEVICE Matrix::Matrix(int rows, int cols, T* data, bool external) } else { - #if defined(AXOM_DEVICE_CODE) +#if defined(AXOM_DEVICE_CODE) assert(false); - #else +#else const int nitems = m_rows * m_cols; m_data = allocate(nitems); memcpy(m_data, data, nitems * sizeof(T)); - #endif +#endif } } @@ -950,14 +950,14 @@ void Matrix::copy(const Matrix& rhs) template AXOM_HOST_DEVICE void Matrix::clear() { - #if defined(AXOM_DEVICE_CODE) +#if defined(AXOM_DEVICE_CODE) assert(m_usingExternal); - #else +#else if(!m_usingExternal) { deallocate(m_data); } - #endif +#endif m_rows = m_cols = 0; } diff --git a/src/axom/core/utilities/BitUtilities.hpp b/src/axom/core/utilities/BitUtilities.hpp index e16f115500..71c0cd6c7b 100644 --- a/src/axom/core/utilities/BitUtilities.hpp +++ b/src/axom/core/utilities/BitUtilities.hpp @@ -24,7 +24,7 @@ // Check for and setup defines for platform-specific intrinsics // Note: `__GNUC__` is defined for the gnu, clang and intel compilers #if defined(AXOM_USE_CUDA) - // Intrinsics included implicitly +// Intrinsics included implicitly #elif defined(AXOM_USE_HIP) #include diff --git a/src/axom/inlet/inlet_utils.hpp b/src/axom/inlet/inlet_utils.hpp index c5b107e0bc..8f96470050 100644 --- a/src/axom/inlet/inlet_utils.hpp +++ b/src/axom/inlet/inlet_utils.hpp @@ -53,15 +53,15 @@ struct VerificationError * \param errs The list of errors, must be of type \p std::vector* ***************************************************************************** */ - #define INLET_VERIFICATION_WARNING(path, msg, errs) \ - if(errs) \ - { \ - errs->push_back({axom::Path {path}, msg}); \ - } \ - else \ - { \ - SLIC_WARNING(msg); \ - } +#define INLET_VERIFICATION_WARNING(path, msg, errs) \ + if(errs) \ + { \ + errs->push_back({axom::Path {path}, msg}); \ + } \ + else \ + { \ + SLIC_WARNING(msg); \ + } /*! ***************************************************************************** diff --git a/src/axom/mint/mesh/CurvilinearMesh.hpp b/src/axom/mint/mesh/CurvilinearMesh.hpp index 07eaf45507..beb05ee826 100644 --- a/src/axom/mint/mesh/CurvilinearMesh.hpp +++ b/src/axom/mint/mesh/CurvilinearMesh.hpp @@ -205,7 +205,7 @@ class CurvilinearMesh : public StructuredMesh : CurvilinearMesh(group, "", "", Ni, Nj, Nk) { } - /// @} + /// @} /// @} #endif diff --git a/src/axom/mint/mesh/ParticleMesh.hpp b/src/axom/mint/mesh/ParticleMesh.hpp index ac77c0e2ec..030ac30e34 100644 --- a/src/axom/mint/mesh/ParticleMesh.hpp +++ b/src/axom/mint/mesh/ParticleMesh.hpp @@ -196,9 +196,9 @@ class ParticleMesh : public Mesh IndexType numParticles, sidre::Group* group, IndexType capacity = USE_DEFAULT); - /// @} + /// @} - /// @} + /// @} #endif /* AXOM_MINT_USE_SIDRE */ diff --git a/src/axom/mint/mesh/RectilinearMesh.hpp b/src/axom/mint/mesh/RectilinearMesh.hpp index 7c24a143d6..61404ae7eb 100644 --- a/src/axom/mint/mesh/RectilinearMesh.hpp +++ b/src/axom/mint/mesh/RectilinearMesh.hpp @@ -205,7 +205,7 @@ class RectilinearMesh : public StructuredMesh : RectilinearMesh(group, "", "", Ni, Nj, Nk) { } - /// @} + /// @} /// @} #endif diff --git a/src/axom/mint/mesh/UniformMesh.hpp b/src/axom/mint/mesh/UniformMesh.hpp index 873338645f..e2e35c3f16 100644 --- a/src/axom/mint/mesh/UniformMesh.hpp +++ b/src/axom/mint/mesh/UniformMesh.hpp @@ -184,7 +184,7 @@ class UniformMesh : public StructuredMesh IndexType Nk = -1) : UniformMesh(group, "", "", lower_bound, upper_bound, Ni, Nj, Nk) { } - /// @} + /// @} /// @} #endif diff --git a/src/axom/mint/mesh/UnstructuredMesh.hpp b/src/axom/mint/mesh/UnstructuredMesh.hpp index 2c51f874a9..d319ae0c75 100644 --- a/src/axom/mint/mesh/UnstructuredMesh.hpp +++ b/src/axom/mint/mesh/UnstructuredMesh.hpp @@ -607,7 +607,7 @@ class UnstructuredMesh : public Mesh : UnstructuredMesh(ndims, group, "", "", node_capacity, cell_capacity, connectivity_capacity) { } - /// @} + /// @} #endif /* AXOM_MINT_USE_SIDRE */ diff --git a/src/axom/quest/IntersectionShaper.hpp b/src/axom/quest/IntersectionShaper.hpp index 465f3451e4..61bb2e2107 100644 --- a/src/axom/quest/IntersectionShaper.hpp +++ b/src/axom/quest/IntersectionShaper.hpp @@ -2609,7 +2609,7 @@ class IntersectionShaper : public Shaper #if defined(__CUDACC__) public: - // These methods should be private, but NVCC complains unless they're public. + // These methods should be private, but NVCC complains unless they're public. #endif template diff --git a/src/axom/quest/ShapeMesh.hpp b/src/axom/quest/ShapeMesh.hpp index 1f535bcb58..e91be8272a 100644 --- a/src/axom/quest/ShapeMesh.hpp +++ b/src/axom/quest/ShapeMesh.hpp @@ -14,9 +14,9 @@ #ifndef AXOM_USE_SIDRE #error "ShapeMesh requires sidre" - // Note: We guard sidre use for mesh stored in sidre, but sidre::ConduitMemory - // is required even when the mesh is stored in Conduit. Hence the dependence - // on sidre. +// Note: We guard sidre use for mesh stored in sidre, but sidre::ConduitMemory +// is required even when the mesh is stored in Conduit. Hence the dependence +// on sidre. #endif #include "axom/core.hpp" diff --git a/src/axom/spin/internal/linear_bvh/build_radix_tree.hpp b/src/axom/spin/internal/linear_bvh/build_radix_tree.hpp index 63df3a133d..3e81db7f33 100644 --- a/src/axom/spin/internal/linear_bvh/build_radix_tree.hpp +++ b/src/axom/spin/internal/linear_bvh/build_radix_tree.hpp @@ -406,15 +406,15 @@ AXOM_HOST_DEVICE static inline BBoxType sync_load(const BBoxType& box) volatile const FloatType& max_dim = reinterpret_cast(box.getMax()[dim]); - // NOTE: There is a possibility for a read-after-write hazard, where the - // uncached store of an AABB on one thread isn't visible when another - // thread calls this method to read the value. However, this doesn't seem to - // be an issue on Volta; the atomicAdd used to terminate the first thread - // seems to correctly synchronize the prior atomic store operations for the - // bounding box data. - // - // Just in case this changes, we poll for a non-sentinel value to be read - // out. Naturally, this assumes that reads of sizeof(FloatType) don't tear. + // NOTE: There is a possibility for a read-after-write hazard, where the + // uncached store of an AABB on one thread isn't visible when another + // thread calls this method to read the value. However, this doesn't seem to + // be an issue on Volta; the atomicAdd used to terminate the first thread + // seems to correctly synchronize the prior atomic store operations for the + // bounding box data. + // + // Just in case this changes, we poll for a non-sentinel value to be read + // out. Naturally, this assumes that reads of sizeof(FloatType) don't tear. #ifdef SPIN_BVH_DEBUG_MEMORY_HAZARD while((min_pt[dim] = min_dim) == BBoxType::InvalidMin) { From ae8e48ff19ab413a11ed0fcd74c4ebf23b2a0da9 Mon Sep 17 00:00:00 2001 From: Chris White Date: Mon, 29 Jun 2026 14:16:24 -0700 Subject: [PATCH 687/986] be consistent with pragma placement --- src/axom/bump/BlendData.hpp | 1 + src/axom/bump/CoordsetBlender.hpp | 1 + src/axom/bump/CoordsetExtents.hpp | 1 + src/axom/bump/CoordsetSlicer.hpp | 1 + src/axom/bump/FieldBlender.hpp | 1 + src/axom/bump/FieldSlicer.hpp | 1 + src/axom/bump/MakePointMesh.hpp | 1 + src/axom/bump/MakePolyhedralTopology.hpp | 1 + src/axom/bump/MakeUnstructured.hpp | 1 + src/axom/bump/MakeZoneCenters.hpp | 1 + src/axom/bump/MakeZoneVolumes.hpp | 1 + src/axom/bump/MergeCoordsetPoints.hpp | 1 + src/axom/bump/MergeMeshes.hpp | 1 + src/axom/bump/MergePolyhedralFaces.hpp | 1 + src/axom/bump/Options.hpp | 1 + src/axom/bump/SelectedZones.hpp | 1 + src/axom/bump/data/MeshTester.hpp | 4 ++-- src/axom/bump/extraction/BlendGroupBuilder.hpp | 1 + src/axom/bump/extraction/ClipField.hpp | 1 + src/axom/bump/extraction/CutField.hpp | 1 + src/axom/bump/extraction/ExtractorOptions.hpp | 1 + src/axom/bump/extraction/FieldIntersector.hpp | 1 + src/axom/bump/extraction/FieldOptions.hpp | 1 + src/axom/bump/extraction/PlaneIntersector.hpp | 1 + src/axom/bump/extraction/PlaneSlice.hpp | 1 + src/axom/bump/extraction/TableBasedExtractor.hpp | 1 + src/axom/bump/tests/blueprint_testing_data_helpers.hpp | 1 + src/axom/bump/tests/blueprint_testing_helpers.hpp | 1 + src/axom/bump/views/dispatch_material_field.hpp | 1 + src/axom/core/ItemCollection.hpp | 4 ++-- src/axom/core/IteratorBase.hpp | 4 ++-- src/axom/core/ListCollection.hpp | 4 ++-- src/axom/core/Macros.hpp | 4 ++-- src/axom/core/MapCollection.hpp | 4 ++-- src/axom/core/NumericLimits.hpp | 4 ++-- src/axom/core/Types.hpp | 4 ++-- src/axom/core/numerics/Matrix.hpp | 4 ++-- src/axom/core/numerics/matvecops.hpp | 4 ++-- src/axom/core/numerics/transforms.hpp | 1 + src/axom/core/tests/utils_locale.hpp | 4 ++-- src/axom/core/utilities/Annotations.hpp | 4 ++-- src/axom/core/utilities/BitUtilities.hpp | 4 ++-- src/axom/core/utilities/CommandLineUtilities.hpp | 4 ++-- src/axom/core/utilities/RAII.hpp | 4 ++-- src/axom/core/utilities/Sorting.hpp | 1 + src/axom/core/utilities/Timer.hpp | 4 ++-- src/axom/core/utilities/Utilities.hpp | 4 ++-- src/axom/inlet/ConduitReader.hpp | 4 ++-- src/axom/inlet/Container.hpp | 4 ++-- src/axom/inlet/Field.hpp | 4 ++-- src/axom/inlet/Function.hpp | 4 ++-- src/axom/inlet/Inlet.hpp | 4 ++-- src/axom/inlet/InletVector.hpp | 4 ++-- src/axom/inlet/JSONReader.hpp | 4 ++-- src/axom/inlet/JSONSchemaWriter.hpp | 4 ++-- src/axom/inlet/LuaReader.hpp | 4 ++-- src/axom/inlet/Proxy.hpp | 4 ++-- src/axom/inlet/Reader.hpp | 4 ++-- src/axom/inlet/SphinxWriter.hpp | 4 ++-- src/axom/inlet/VariantKey.hpp | 4 ++-- src/axom/inlet/VariantValue.hpp | 4 ++-- src/axom/inlet/Verifiable.hpp | 4 ++-- src/axom/inlet/VerifiableScalar.hpp | 4 ++-- src/axom/inlet/Writer.hpp | 4 ++-- src/axom/inlet/YAMLReader.hpp | 4 ++-- src/axom/inlet/inlet_utils.hpp | 4 ++-- src/axom/klee/AffineMatrixVisitor.hpp | 2 ++ src/axom/klee/Dimensions.hpp | 1 + src/axom/klee/Units.hpp | 1 + src/axom/klee/io/GeometryOperatorsIO.hpp | 1 + src/axom/klee/io/IOUtil.hpp | 1 + src/axom/klee/tests/KleeMatchers.hpp | 1 + src/axom/lumberjack/BinaryTreeCommunicator.hpp | 4 ++-- src/axom/lumberjack/Combiner.hpp | 4 ++-- src/axom/lumberjack/Communicator.hpp | 4 ++-- src/axom/lumberjack/LineFileTagCombiner.hpp | 4 ++-- src/axom/lumberjack/Lumberjack.hpp | 4 ++-- src/axom/lumberjack/MPIUtility.hpp | 4 ++-- src/axom/lumberjack/Message.hpp | 4 ++-- src/axom/lumberjack/NonCollectiveRootCommunicator.hpp | 4 ++-- src/axom/lumberjack/RootCommunicator.hpp | 4 ++-- src/axom/lumberjack/TextEqualityCombiner.hpp | 4 ++-- src/axom/lumberjack/TextTagCombiner.hpp | 4 ++-- src/axom/mint/mesh/internal/MeshHelpers.hpp | 1 + src/axom/mint/tests/StructuredMesh_helpers.hpp | 1 + src/axom/mint/tests/mint_test_utilities.hpp | 4 ++-- src/axom/mir/ElviraAlgorithm.hpp | 1 + src/axom/mir/EquiZAlgorithm.hpp | 1 + src/axom/mir/detail/elvira_detail.hpp | 1 + src/axom/mir/detail/elvira_impl.hpp | 4 ++-- src/axom/mir/detail/equiz_detail.hpp | 1 + src/axom/mir/examples/concentric_circles/MIRApplication.hpp | 2 ++ src/axom/mir/examples/concentric_circles/runMIR.hpp | 2 ++ src/axom/mir/examples/heavily_mixed/HMApplication.hpp | 2 ++ src/axom/mir/examples/heavily_mixed/runMIR.hpp | 2 ++ src/axom/mir/examples/tutorial_simple/runMIR.hpp | 2 ++ src/axom/mir/future/ClipFieldFilter.hpp | 1 + src/axom/mir/future/ClipFieldFilterDevice.hpp | 1 + src/axom/mir/reference/CellClipper.hpp | 4 ++-- src/axom/mir/reference/CellData.hpp | 4 ++-- src/axom/mir/reference/CellGenerator.hpp | 4 ++-- src/axom/mir/reference/InterfaceReconstructor.hpp | 4 ++-- src/axom/mir/reference/MIRMesh.hpp | 4 ++-- src/axom/mir/reference/MIRMeshTypes.hpp | 4 ++-- src/axom/mir/reference/MIRUtilities.hpp | 4 ++-- src/axom/multimat/examples/helper.hpp | 4 ++-- src/axom/multimat/multimat.hpp | 4 ++-- src/axom/primal/geometry/BezierCurve.hpp | 4 ++-- src/axom/primal/geometry/BezierPatch.hpp | 4 ++-- src/axom/primal/geometry/BezierTriangle.hpp | 4 ++-- src/axom/primal/geometry/CurvedPolygon.hpp | 4 ++-- src/axom/primal/geometry/KnotVector.hpp | 4 ++-- src/axom/primal/geometry/NURBSCurve.hpp | 4 ++-- src/axom/primal/geometry/NURBSPatch.hpp | 4 ++-- src/axom/primal/geometry/Polygon.hpp | 4 ++-- src/axom/primal/geometry/Polyhedron.hpp | 4 ++-- src/axom/primal/geometry/detail/analytic_test_surfaces.hpp | 4 ++-- src/axom/primal/operators/clip.hpp | 4 ++-- src/axom/primal/operators/closest_point.hpp | 4 ++-- src/axom/primal/operators/compute_bounding_box.hpp | 4 ++-- src/axom/primal/operators/detail/clip_impl.hpp | 4 ++-- .../primal/operators/detail/evaluate_integral_curve_impl.hpp | 4 ++-- src/axom/primal/operators/detail/evaluate_integral_impl.hpp | 4 ++-- .../operators/detail/evaluate_integral_surface_impl.hpp | 4 ++-- src/axom/primal/operators/detail/fuzzy_comparators.hpp | 4 ++-- src/axom/primal/operators/detail/intersect_bezier_impl.hpp | 4 ++-- src/axom/primal/operators/detail/intersect_impl.hpp | 4 ++-- src/axom/primal/operators/detail/intersect_patch_impl.hpp | 4 ++-- src/axom/primal/operators/detail/predicate_determinants.hpp | 4 ++-- .../operators/detail/winding_number_2d_memoization.hpp | 4 ++-- .../operators/detail/winding_number_3d_memoization.hpp | 4 ++-- src/axom/primal/operators/evaluate_integral.hpp | 4 ++-- src/axom/primal/operators/evaluate_integral_curve.hpp | 4 ++-- src/axom/primal/operators/evaluate_integral_surface.hpp | 4 ++-- src/axom/primal/operators/in_curved_polygon.hpp | 4 ++-- src/axom/primal/operators/in_polygon.hpp | 4 ++-- src/axom/primal/operators/in_polyhedron.hpp | 4 ++-- src/axom/primal/operators/in_sphere.hpp | 4 ++-- src/axom/primal/operators/intersect.hpp | 4 ++-- src/axom/primal/operators/intersection_volume.hpp | 4 ++-- src/axom/primal/operators/orientation.hpp | 4 ++-- src/axom/primal/operators/slice.hpp | 1 + src/axom/primal/operators/split.hpp | 4 ++-- src/axom/primal/operators/squared_distance.hpp | 4 ++-- src/axom/primal/operators/winding_number.hpp | 4 ++-- src/axom/quest/AllNearestNeighbors.hpp | 4 ++-- src/axom/quest/Delaunay.hpp | 4 ++-- src/axom/quest/FastApproximateGWN.hpp | 2 ++ src/axom/quest/GWNMethods.hpp | 4 ++-- src/axom/quest/InOutOctree.hpp | 4 ++-- src/axom/quest/IntersectionShaper.hpp | 4 ++-- src/axom/quest/MarchingCubes.hpp | 4 ++-- src/axom/quest/SamplingShaper.hpp | 4 ++-- src/axom/quest/Shaper.hpp | 4 ++-- src/axom/quest/detail/DelaunayElementFinder.hpp | 4 ++-- src/axom/quest/detail/DelaunayImpl.hpp | 4 ++-- src/axom/quest/detail/DelaunayInsertionHelper.hpp | 4 ++-- src/axom/quest/detail/DelaunayPointLocation.hpp | 4 ++-- src/axom/quest/detail/DelaunayValidation.hpp | 4 ++-- src/axom/quest/detail/MarchingCubesSingleDomain.hpp | 4 ++-- src/axom/quest/detail/inout/BlockData.hpp | 4 ++-- src/axom/quest/detail/inout/InOutOctreeMeshDumper.hpp | 4 ++-- src/axom/quest/detail/inout/InOutOctreeStats.hpp | 4 ++-- src/axom/quest/detail/inout/InOutOctreeValidator.hpp | 4 ++-- src/axom/quest/detail/inout/MeshWrapper.hpp | 4 ++-- src/axom/quest/detail/marching_cubes_lookup.hpp | 4 ++-- src/axom/quest/detail/shaping/InOutSampler.hpp | 4 ++-- src/axom/quest/detail/shaping/PrimitiveSampler.hpp | 4 ++-- src/axom/quest/detail/shaping/shaping_helpers.hpp | 4 ++-- src/axom/quest/interface/c_fortran/typesQUEST.h | 3 ++- src/axom/quest/interface/c_fortran/wrapQUEST.h | 5 +++-- src/axom/quest/interface/python/pyQUESTmodule.hpp | 1 + src/axom/sidre/core/AttrValues.hpp | 4 ++-- src/axom/sidre/core/Attribute.hpp | 4 ++-- src/axom/sidre/core/Buffer.hpp | 4 ++-- src/axom/sidre/core/ConduitMemory.hpp | 4 ++-- src/axom/sidre/core/DataStore.hpp | 4 ++-- src/axom/sidre/core/Group.hpp | 4 ++-- src/axom/sidre/core/SidreDataTypeIds.h | 4 ++-- src/axom/sidre/core/SidreTypes.hpp | 4 ++-- src/axom/sidre/core/View.hpp | 4 ++-- src/axom/sidre/examples/lulesh2/lulesh.h | 3 +-- src/axom/sidre/examples/lulesh2/lulesh_tuple.h | 1 - src/axom/sidre/interface/SidreTypes.h | 4 ++-- src/axom/sidre/interface/c_fortran/typesSidre.h | 3 ++- src/axom/sidre/interface/c_fortran/wrapBuffer.h | 5 +++-- src/axom/sidre/interface/c_fortran/wrapDataStore.h | 5 +++-- src/axom/sidre/interface/c_fortran/wrapGroup.h | 5 +++-- src/axom/sidre/interface/c_fortran/wrapSidre.h | 5 +++-- src/axom/sidre/interface/c_fortran/wrapView.h | 5 +++-- src/axom/sidre/interface/sidre.h | 4 ++-- src/axom/sidre/spio/IOBaton.hpp | 4 ++-- src/axom/sidre/spio/IOManager.hpp | 4 ++-- src/axom/sidre/spio/interface/c_fortran/typesSPIO.h | 3 ++- src/axom/sidre/spio/interface/c_fortran/wrapIOManager.h | 5 +++-- src/axom/sidre/tests/spio/spio_parallel.hpp | 4 ++-- src/axom/slam/BitSet.hpp | 4 ++-- src/axom/slam/BivariateMap.hpp | 4 ++-- src/axom/slam/DynamicConstantRelation.hpp | 4 ++-- src/axom/slam/DynamicSet.hpp | 4 ++-- src/axom/slam/DynamicVariableRelation.hpp | 4 ++-- src/axom/slam/IndirectionSet.hpp | 4 ++-- src/axom/slam/Map.hpp | 4 ++-- src/axom/slam/MapBase.hpp | 4 ++-- src/axom/slam/ModularInt.hpp | 4 ++-- src/axom/slam/NullSet.hpp | 4 ++-- src/axom/slam/OrderedSet.hpp | 4 ++-- src/axom/slam/ProductSet.hpp | 4 ++-- src/axom/slam/RangeSet.hpp | 4 ++-- src/axom/slam/Relation.hpp | 4 ++-- src/axom/slam/Set.hpp | 4 ++-- src/axom/slam/StaticRelation.hpp | 4 ++-- src/axom/slam/SubMap.hpp | 4 ++-- src/axom/slam/Utilities.hpp | 4 ++-- src/axom/slam/examples/tinyHydro/HydroC.hpp | 3 +-- src/axom/slam/examples/tinyHydro/Part.hpp | 3 +-- src/axom/slam/examples/tinyHydro/PolygonMeshXY.hpp | 4 ++-- src/axom/slam/examples/tinyHydro/State.hpp | 4 ++-- src/axom/slam/examples/tinyHydro/TinyHydroTypes.hpp | 3 +-- src/axom/slam/examples/tinyHydro/VectorXY.hpp | 4 ++-- src/axom/slam/mesh_struct/IA.hpp | 4 ++-- src/axom/slam/policies/CardinalityPolicies.hpp | 4 ++-- src/axom/slam/policies/IndirectionPolicies.hpp | 4 ++-- src/axom/slam/policies/OffsetPolicies.hpp | 4 ++-- src/axom/slam/policies/SizePolicies.hpp | 4 ++-- src/axom/slam/policies/StridePolicies.hpp | 4 ++-- src/axom/slam/policies/SubsettingPolicies.hpp | 4 ++-- src/axom/slic/core/LogStream.hpp | 4 ++-- src/axom/slic/core/LogStreamStatusMonitor.hpp | 4 ++-- src/axom/slic/core/Logger.hpp | 4 ++-- src/axom/slic/core/MessageLevel.hpp | 4 ++-- src/axom/slic/core/SimpleLogger.hpp | 4 ++-- src/axom/slic/interface/c_fortran/typesSLIC.h | 3 ++- src/axom/slic/interface/c_fortran/wrapGenericOutputStream.h | 5 +++-- src/axom/slic/interface/c_fortran/wrapSLIC.h | 5 +++-- src/axom/slic/interface/slic.hpp | 4 ++-- src/axom/slic/streams/GenericOutputStream.hpp | 4 ++-- src/axom/slic/streams/LumberjackStream.hpp | 4 ++-- src/axom/slic/streams/SynchronizedStream.hpp | 4 ++-- src/axom/spin/MortonIndex.hpp | 4 ++-- src/axom/spin/OctreeBase.hpp | 4 ++-- src/axom/spin/OctreeLevel.hpp | 4 ++-- 242 files changed, 442 insertions(+), 375 deletions(-) diff --git a/src/axom/bump/BlendData.hpp b/src/axom/bump/BlendData.hpp index dbfed4f88a..b1d8cbf0c0 100644 --- a/src/axom/bump/BlendData.hpp +++ b/src/axom/bump/BlendData.hpp @@ -3,6 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once #include "axom/core.hpp" diff --git a/src/axom/bump/CoordsetBlender.hpp b/src/axom/bump/CoordsetBlender.hpp index e73ff5d349..288cee341d 100644 --- a/src/axom/bump/CoordsetBlender.hpp +++ b/src/axom/bump/CoordsetBlender.hpp @@ -3,6 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once #include "axom/core.hpp" diff --git a/src/axom/bump/CoordsetExtents.hpp b/src/axom/bump/CoordsetExtents.hpp index 240e41a0a5..a04eaf0966 100644 --- a/src/axom/bump/CoordsetExtents.hpp +++ b/src/axom/bump/CoordsetExtents.hpp @@ -3,6 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once #include "axom/core.hpp" diff --git a/src/axom/bump/CoordsetSlicer.hpp b/src/axom/bump/CoordsetSlicer.hpp index 0bdb0c0f56..5a312df50c 100644 --- a/src/axom/bump/CoordsetSlicer.hpp +++ b/src/axom/bump/CoordsetSlicer.hpp @@ -3,6 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once #include "axom/core.hpp" diff --git a/src/axom/bump/FieldBlender.hpp b/src/axom/bump/FieldBlender.hpp index 85a2bc5853..ebf60656e6 100644 --- a/src/axom/bump/FieldBlender.hpp +++ b/src/axom/bump/FieldBlender.hpp @@ -3,6 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once #include "axom/core.hpp" diff --git a/src/axom/bump/FieldSlicer.hpp b/src/axom/bump/FieldSlicer.hpp index 4816018b40..17d7eb0db7 100644 --- a/src/axom/bump/FieldSlicer.hpp +++ b/src/axom/bump/FieldSlicer.hpp @@ -3,6 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once #include "axom/core.hpp" diff --git a/src/axom/bump/MakePointMesh.hpp b/src/axom/bump/MakePointMesh.hpp index 8620b772ca..b5f6d8775d 100644 --- a/src/axom/bump/MakePointMesh.hpp +++ b/src/axom/bump/MakePointMesh.hpp @@ -3,6 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once #include "axom/core.hpp" diff --git a/src/axom/bump/MakePolyhedralTopology.hpp b/src/axom/bump/MakePolyhedralTopology.hpp index 644d08cbda..51405a4a78 100644 --- a/src/axom/bump/MakePolyhedralTopology.hpp +++ b/src/axom/bump/MakePolyhedralTopology.hpp @@ -3,6 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once #include "axom/core.hpp" diff --git a/src/axom/bump/MakeUnstructured.hpp b/src/axom/bump/MakeUnstructured.hpp index 78264525c5..ca5ff072a0 100644 --- a/src/axom/bump/MakeUnstructured.hpp +++ b/src/axom/bump/MakeUnstructured.hpp @@ -3,6 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once #include "axom/core.hpp" diff --git a/src/axom/bump/MakeZoneCenters.hpp b/src/axom/bump/MakeZoneCenters.hpp index 496c0df32d..61c57634f7 100644 --- a/src/axom/bump/MakeZoneCenters.hpp +++ b/src/axom/bump/MakeZoneCenters.hpp @@ -3,6 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once #include "axom/core.hpp" diff --git a/src/axom/bump/MakeZoneVolumes.hpp b/src/axom/bump/MakeZoneVolumes.hpp index a74c2d8304..480a4e99eb 100644 --- a/src/axom/bump/MakeZoneVolumes.hpp +++ b/src/axom/bump/MakeZoneVolumes.hpp @@ -3,6 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once #include "axom/core.hpp" diff --git a/src/axom/bump/MergeCoordsetPoints.hpp b/src/axom/bump/MergeCoordsetPoints.hpp index e5ea6818cf..489ea065db 100644 --- a/src/axom/bump/MergeCoordsetPoints.hpp +++ b/src/axom/bump/MergeCoordsetPoints.hpp @@ -3,6 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once #include "axom/core.hpp" diff --git a/src/axom/bump/MergeMeshes.hpp b/src/axom/bump/MergeMeshes.hpp index 00294ea8ac..b089152ae4 100644 --- a/src/axom/bump/MergeMeshes.hpp +++ b/src/axom/bump/MergeMeshes.hpp @@ -3,6 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once #include "axom/config.hpp" diff --git a/src/axom/bump/MergePolyhedralFaces.hpp b/src/axom/bump/MergePolyhedralFaces.hpp index 7c1169e477..b0cd6b81bd 100644 --- a/src/axom/bump/MergePolyhedralFaces.hpp +++ b/src/axom/bump/MergePolyhedralFaces.hpp @@ -3,6 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once #include "axom/core.hpp" diff --git a/src/axom/bump/Options.hpp b/src/axom/bump/Options.hpp index 33d5bdd862..1230f3000f 100644 --- a/src/axom/bump/Options.hpp +++ b/src/axom/bump/Options.hpp @@ -3,6 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once #include "axom/core.hpp" diff --git a/src/axom/bump/SelectedZones.hpp b/src/axom/bump/SelectedZones.hpp index 46e2ffe92b..f42afbcf13 100644 --- a/src/axom/bump/SelectedZones.hpp +++ b/src/axom/bump/SelectedZones.hpp @@ -3,6 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once #include "axom/core.hpp" diff --git a/src/axom/bump/data/MeshTester.hpp b/src/axom/bump/data/MeshTester.hpp index 5fbfaa23e4..e486f9bc33 100644 --- a/src/axom/bump/data/MeshTester.hpp +++ b/src/axom/bump/data/MeshTester.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file MeshTester.hpp * @@ -11,8 +13,6 @@ * */ -#pragma once - #include "axom/core.hpp" // for axom macros #include "axom/primal.hpp" diff --git a/src/axom/bump/extraction/BlendGroupBuilder.hpp b/src/axom/bump/extraction/BlendGroupBuilder.hpp index 12ad431982..41e3fc0bc0 100644 --- a/src/axom/bump/extraction/BlendGroupBuilder.hpp +++ b/src/axom/bump/extraction/BlendGroupBuilder.hpp @@ -3,6 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once #include "axom/core.hpp" diff --git a/src/axom/bump/extraction/ClipField.hpp b/src/axom/bump/extraction/ClipField.hpp index 3a45799f55..2e53ab5bed 100644 --- a/src/axom/bump/extraction/ClipField.hpp +++ b/src/axom/bump/extraction/ClipField.hpp @@ -3,6 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once #include "axom/bump/extraction/TableBasedExtractor.hpp" diff --git a/src/axom/bump/extraction/CutField.hpp b/src/axom/bump/extraction/CutField.hpp index d7559bf36a..cd95071a08 100644 --- a/src/axom/bump/extraction/CutField.hpp +++ b/src/axom/bump/extraction/CutField.hpp @@ -3,6 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once #include "axom/bump/extraction/TableBasedExtractor.hpp" diff --git a/src/axom/bump/extraction/ExtractorOptions.hpp b/src/axom/bump/extraction/ExtractorOptions.hpp index a8c301e0ec..c476463adc 100644 --- a/src/axom/bump/extraction/ExtractorOptions.hpp +++ b/src/axom/bump/extraction/ExtractorOptions.hpp @@ -3,6 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once #include "axom/bump/Options.hpp" diff --git a/src/axom/bump/extraction/FieldIntersector.hpp b/src/axom/bump/extraction/FieldIntersector.hpp index 23104fdafc..ef71a6bee5 100644 --- a/src/axom/bump/extraction/FieldIntersector.hpp +++ b/src/axom/bump/extraction/FieldIntersector.hpp @@ -3,6 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once #include "axom/core.hpp" diff --git a/src/axom/bump/extraction/FieldOptions.hpp b/src/axom/bump/extraction/FieldOptions.hpp index 368340b48d..2ccd71ea68 100644 --- a/src/axom/bump/extraction/FieldOptions.hpp +++ b/src/axom/bump/extraction/FieldOptions.hpp @@ -3,6 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once #include "axom/bump/extraction/ExtractorOptions.hpp" diff --git a/src/axom/bump/extraction/PlaneIntersector.hpp b/src/axom/bump/extraction/PlaneIntersector.hpp index 9c242a9780..6324f26a34 100644 --- a/src/axom/bump/extraction/PlaneIntersector.hpp +++ b/src/axom/bump/extraction/PlaneIntersector.hpp @@ -3,6 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once #include "axom/core.hpp" diff --git a/src/axom/bump/extraction/PlaneSlice.hpp b/src/axom/bump/extraction/PlaneSlice.hpp index 4aafec0a4f..d8052bb3da 100644 --- a/src/axom/bump/extraction/PlaneSlice.hpp +++ b/src/axom/bump/extraction/PlaneSlice.hpp @@ -3,6 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once #include "axom/bump/extraction/TableBasedExtractor.hpp" diff --git a/src/axom/bump/extraction/TableBasedExtractor.hpp b/src/axom/bump/extraction/TableBasedExtractor.hpp index a29eab6172..3dc1a18329 100644 --- a/src/axom/bump/extraction/TableBasedExtractor.hpp +++ b/src/axom/bump/extraction/TableBasedExtractor.hpp @@ -3,6 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once #include "axom/core.hpp" diff --git a/src/axom/bump/tests/blueprint_testing_data_helpers.hpp b/src/axom/bump/tests/blueprint_testing_data_helpers.hpp index 297a2f4bb1..4518093b88 100644 --- a/src/axom/bump/tests/blueprint_testing_data_helpers.hpp +++ b/src/axom/bump/tests/blueprint_testing_data_helpers.hpp @@ -3,6 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once #include "axom/config.hpp" diff --git a/src/axom/bump/tests/blueprint_testing_helpers.hpp b/src/axom/bump/tests/blueprint_testing_helpers.hpp index 3ce86fbf7e..0ea7ffd715 100644 --- a/src/axom/bump/tests/blueprint_testing_helpers.hpp +++ b/src/axom/bump/tests/blueprint_testing_helpers.hpp @@ -3,6 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once #include "axom/config.hpp" diff --git a/src/axom/bump/views/dispatch_material_field.hpp b/src/axom/bump/views/dispatch_material_field.hpp index da4daea4be..690aa4354e 100644 --- a/src/axom/bump/views/dispatch_material_field.hpp +++ b/src/axom/bump/views/dispatch_material_field.hpp @@ -5,6 +5,7 @@ // SPDX-License-Identifier: (BSD-3-Clause) #pragma once + #include "axom/bump/views/dispatch_material.hpp" #include "axom/bump/views/MixedFieldView.hpp" diff --git a/src/axom/core/ItemCollection.hpp b/src/axom/core/ItemCollection.hpp index 2a65ab49bf..88da36b09f 100644 --- a/src/axom/core/ItemCollection.hpp +++ b/src/axom/core/ItemCollection.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! ****************************************************************************** * @@ -78,8 +80,6 @@ ****************************************************************************** */ -#pragma once - #include // Other axom headers diff --git a/src/axom/core/IteratorBase.hpp b/src/axom/core/IteratorBase.hpp index c4d647f58b..36f2b49b3a 100644 --- a/src/axom/core/IteratorBase.hpp +++ b/src/axom/core/IteratorBase.hpp @@ -4,14 +4,14 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file IteratorBase.hpp * * \brief Contains iterator base classes */ -#pragma once - #include "axom/config.hpp" #include "axom/core/Macros.hpp" diff --git a/src/axom/core/ListCollection.hpp b/src/axom/core/ListCollection.hpp index 213e7f1c85..dcf062c7d5 100644 --- a/src/axom/core/ListCollection.hpp +++ b/src/axom/core/ListCollection.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! ****************************************************************************** * @@ -76,8 +78,6 @@ ****************************************************************************** */ -#pragma once - // Standard C++ headers #include #include diff --git a/src/axom/core/Macros.hpp b/src/axom/core/Macros.hpp index 011a0bf9c0..6f3604d506 100644 --- a/src/axom/core/Macros.hpp +++ b/src/axom/core/Macros.hpp @@ -4,14 +4,14 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file AxomMacros.hpp * * \brief Contains several useful macros for the axom project */ -#pragma once - #include "axom/config.hpp" #include // for assert() diff --git a/src/axom/core/MapCollection.hpp b/src/axom/core/MapCollection.hpp index 7d69ca2e8c..01565b5f19 100644 --- a/src/axom/core/MapCollection.hpp +++ b/src/axom/core/MapCollection.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! ****************************************************************************** * @@ -106,8 +108,6 @@ ****************************************************************************** */ -#pragma once - // Standard C++ headers #include #include diff --git a/src/axom/core/NumericLimits.hpp b/src/axom/core/NumericLimits.hpp index 0bb0460ddb..242f04f62e 100644 --- a/src/axom/core/NumericLimits.hpp +++ b/src/axom/core/NumericLimits.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * * \file NumericLimits.hpp @@ -13,8 +15,6 @@ * */ -#pragma once - #include "axom/config.hpp" // for compile-time definitions #include diff --git a/src/axom/core/Types.hpp b/src/axom/core/Types.hpp index 73352767af..5c2c109d9a 100644 --- a/src/axom/core/Types.hpp +++ b/src/axom/core/Types.hpp @@ -4,14 +4,14 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file Types.hpp * * \brief Exposes some common types used by axom components. */ -#pragma once - // Axom includes #include "axom/config.hpp" diff --git a/src/axom/core/numerics/Matrix.hpp b/src/axom/core/numerics/Matrix.hpp index 451cde9f16..29f06f13d5 100644 --- a/src/axom/core/numerics/Matrix.hpp +++ b/src/axom/core/numerics/Matrix.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "axom/config.hpp" #include "axom/core/utilities/Utilities.hpp" #include "axom/core/memory_management.hpp" @@ -15,8 +17,6 @@ #include #include -#pragma once - namespace axom { namespace numerics diff --git a/src/axom/core/numerics/matvecops.hpp b/src/axom/core/numerics/matvecops.hpp index ed1d26a109..e644fd639c 100644 --- a/src/axom/core/numerics/matvecops.hpp +++ b/src/axom/core/numerics/matvecops.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * * \file matvecops.hpp @@ -12,8 +14,6 @@ * */ -#pragma once - #include "axom/config.hpp" #include "axom/core/numerics/Determinants.hpp" // numerics::determinant() #include "axom/core/numerics/Matrix.hpp" // for numerics::Matrix diff --git a/src/axom/core/numerics/transforms.hpp b/src/axom/core/numerics/transforms.hpp index 5e9a72f849..18149db4d9 100644 --- a/src/axom/core/numerics/transforms.hpp +++ b/src/axom/core/numerics/transforms.hpp @@ -3,6 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once #include "axom/config.hpp" diff --git a/src/axom/core/tests/utils_locale.hpp b/src/axom/core/tests/utils_locale.hpp index 296fe2c2e7..da06d66570 100644 --- a/src/axom/core/tests/utils_locale.hpp +++ b/src/axom/core/tests/utils_locale.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + //----------------------------------------------------------------------------- // // file: utils_locale.hpp @@ -11,8 +13,6 @@ // //----------------------------------------------------------------------------- -#pragma once - #include "axom/core/utilities/StringUtilities.hpp" #include "axom/core/utilities/System.hpp" diff --git a/src/axom/core/utilities/Annotations.hpp b/src/axom/core/utilities/Annotations.hpp index 8c5bdfad27..130421241d 100644 --- a/src/axom/core/utilities/Annotations.hpp +++ b/src/axom/core/utilities/Annotations.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file Annotations.hpp * @@ -13,8 +15,6 @@ * unless axom is built with caliper and adiak support */ -#pragma once - #include "axom/config.hpp" #include "axom/core/Macros.hpp" diff --git a/src/axom/core/utilities/BitUtilities.hpp b/src/axom/core/utilities/BitUtilities.hpp index 71c0cd6c7b..e7de622962 100644 --- a/src/axom/core/utilities/BitUtilities.hpp +++ b/src/axom/core/utilities/BitUtilities.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * * \file BitUtilities.hpp @@ -12,8 +14,6 @@ * */ -#pragma once - #include "axom/config.hpp" #include "axom/core/Macros.hpp" #include "axom/core/Types.hpp" diff --git a/src/axom/core/utilities/CommandLineUtilities.hpp b/src/axom/core/utilities/CommandLineUtilities.hpp index 218101190d..735779998c 100644 --- a/src/axom/core/utilities/CommandLineUtilities.hpp +++ b/src/axom/core/utilities/CommandLineUtilities.hpp @@ -4,14 +4,14 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file CommandLineUtilities.hpp * * \brief Defines utilities in support of validating command line input */ -#pragma once - #include "axom/config.hpp" #include "axom/core/utilities/Annotations.hpp" diff --git a/src/axom/core/utilities/RAII.hpp b/src/axom/core/utilities/RAII.hpp index 8198063a61..06457026ca 100644 --- a/src/axom/core/utilities/RAII.hpp +++ b/src/axom/core/utilities/RAII.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file RAII.hpp * @@ -13,8 +15,6 @@ * For more information about RAII, see: https://en.cppreference.com/w/cpp/language/raii */ -#pragma once - #include "axom/config.hpp" #include "axom/core/Macros.hpp" #include "axom/core/utilities/Annotations.hpp" diff --git a/src/axom/core/utilities/Sorting.hpp b/src/axom/core/utilities/Sorting.hpp index 901619e273..8456d97490 100644 --- a/src/axom/core/utilities/Sorting.hpp +++ b/src/axom/core/utilities/Sorting.hpp @@ -5,6 +5,7 @@ // SPDX-License-Identifier: (BSD-3-Clause) #pragma once + #include #include diff --git a/src/axom/core/utilities/Timer.hpp b/src/axom/core/utilities/Timer.hpp index 9444792d2c..2395140fb9 100644 --- a/src/axom/core/utilities/Timer.hpp +++ b/src/axom/core/utilities/Timer.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! ****************************************************************************** * \file Timer.hpp @@ -13,8 +15,6 @@ ****************************************************************************** */ -#pragma once - #include "axom/config.hpp" #include diff --git a/src/axom/core/utilities/Utilities.hpp b/src/axom/core/utilities/Utilities.hpp index 6f9b86ae72..231a8243ef 100644 --- a/src/axom/core/utilities/Utilities.hpp +++ b/src/axom/core/utilities/Utilities.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * * \file Utilities.hpp @@ -12,8 +14,6 @@ * */ -#pragma once - #include "axom/config.hpp" // for compile-time definitions #include "axom/core/Types.hpp" #include "axom/core/Macros.hpp" // for AXOM_STATIC_ASSERT diff --git a/src/axom/inlet/ConduitReader.hpp b/src/axom/inlet/ConduitReader.hpp index 7617ef9648..4d624af2c2 100644 --- a/src/axom/inlet/ConduitReader.hpp +++ b/src/axom/inlet/ConduitReader.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! ******************************************************************************* * \file ConduitReader.hpp @@ -12,8 +14,6 @@ ******************************************************************************* */ -#pragma once - #include "axom/inlet/Reader.hpp" #include "conduit.hpp" diff --git a/src/axom/inlet/Container.hpp b/src/axom/inlet/Container.hpp index 40a23897de..d2387e40c4 100644 --- a/src/axom/inlet/Container.hpp +++ b/src/axom/inlet/Container.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! ******************************************************************************* * \file Container.hpp @@ -12,8 +14,6 @@ ******************************************************************************* */ -#pragma once - #include #include #include diff --git a/src/axom/inlet/Field.hpp b/src/axom/inlet/Field.hpp index db62ace476..1e0dbf0bd0 100644 --- a/src/axom/inlet/Field.hpp +++ b/src/axom/inlet/Field.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! ******************************************************************************* * \file Field.hpp @@ -12,8 +14,6 @@ ******************************************************************************* */ -#pragma once - #include "axom/sidre.hpp" #include "axom/inlet/VariantKey.hpp" #include "axom/inlet/VerifiableScalar.hpp" diff --git a/src/axom/inlet/Function.hpp b/src/axom/inlet/Function.hpp index af19270edd..bd7dd9b4e7 100644 --- a/src/axom/inlet/Function.hpp +++ b/src/axom/inlet/Function.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! ******************************************************************************* * \file Function.hpp @@ -12,8 +14,6 @@ ******************************************************************************* */ -#pragma once - #include #include #include diff --git a/src/axom/inlet/Inlet.hpp b/src/axom/inlet/Inlet.hpp index eef25080bf..7168e9c177 100644 --- a/src/axom/inlet/Inlet.hpp +++ b/src/axom/inlet/Inlet.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! ******************************************************************************* * \file Inlet.hpp @@ -13,8 +15,6 @@ ******************************************************************************* */ -#pragma once - #include #include #include diff --git a/src/axom/inlet/InletVector.hpp b/src/axom/inlet/InletVector.hpp index 48a8890a73..4412b0b403 100644 --- a/src/axom/inlet/InletVector.hpp +++ b/src/axom/inlet/InletVector.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! ******************************************************************************* * \file InletVector.hpp @@ -13,8 +15,6 @@ ******************************************************************************* */ -#pragma once - #include "axom/primal/geometry/Vector.hpp" #include "axom/fmt.hpp" diff --git a/src/axom/inlet/JSONReader.hpp b/src/axom/inlet/JSONReader.hpp index 36a612ea1b..b143624c76 100644 --- a/src/axom/inlet/JSONReader.hpp +++ b/src/axom/inlet/JSONReader.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! ******************************************************************************* * \file JSONReader.hpp @@ -12,8 +14,6 @@ ******************************************************************************* */ -#pragma once - #include "axom/inlet/ConduitReader.hpp" #include "conduit.hpp" diff --git a/src/axom/inlet/JSONSchemaWriter.hpp b/src/axom/inlet/JSONSchemaWriter.hpp index ea00b30a8a..56b8e30bf5 100644 --- a/src/axom/inlet/JSONSchemaWriter.hpp +++ b/src/axom/inlet/JSONSchemaWriter.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! ******************************************************************************* * \file JSONSchemaWriter.hpp @@ -12,8 +14,6 @@ ******************************************************************************* */ -#pragma once - #include #include #include diff --git a/src/axom/inlet/LuaReader.hpp b/src/axom/inlet/LuaReader.hpp index fa3193c8ca..a9f88418d9 100644 --- a/src/axom/inlet/LuaReader.hpp +++ b/src/axom/inlet/LuaReader.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! ******************************************************************************* * \file LuaReader.hpp @@ -12,8 +14,6 @@ ******************************************************************************* */ -#pragma once - #include "axom/inlet/Reader.hpp" #include "axom/sol_forward.hpp" diff --git a/src/axom/inlet/Proxy.hpp b/src/axom/inlet/Proxy.hpp index abe648c760..9d12714165 100644 --- a/src/axom/inlet/Proxy.hpp +++ b/src/axom/inlet/Proxy.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! ******************************************************************************* * \file Proxy.hpp @@ -12,8 +14,6 @@ ******************************************************************************* */ -#pragma once - #include #include diff --git a/src/axom/inlet/Reader.hpp b/src/axom/inlet/Reader.hpp index d0fa4ee6a8..68643b5f10 100644 --- a/src/axom/inlet/Reader.hpp +++ b/src/axom/inlet/Reader.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! ******************************************************************************* * \file Reader.hpp @@ -12,8 +14,6 @@ ******************************************************************************* */ -#pragma once - #include #include #include diff --git a/src/axom/inlet/SphinxWriter.hpp b/src/axom/inlet/SphinxWriter.hpp index c1bd01b66b..ad657fc5c8 100644 --- a/src/axom/inlet/SphinxWriter.hpp +++ b/src/axom/inlet/SphinxWriter.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! ******************************************************************************* * \file SphinxWriter.hpp @@ -12,8 +14,6 @@ ******************************************************************************* */ -#pragma once - #include #include #include diff --git a/src/axom/inlet/VariantKey.hpp b/src/axom/inlet/VariantKey.hpp index d240db467d..d83fa2e735 100644 --- a/src/axom/inlet/VariantKey.hpp +++ b/src/axom/inlet/VariantKey.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! ******************************************************************************* * \file VariantKey.hpp @@ -13,8 +15,6 @@ ******************************************************************************* */ -#pragma once - #include #include diff --git a/src/axom/inlet/VariantValue.hpp b/src/axom/inlet/VariantValue.hpp index 078948553e..e25a601e1c 100644 --- a/src/axom/inlet/VariantValue.hpp +++ b/src/axom/inlet/VariantValue.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! ******************************************************************************* * \file VariantValue.hpp @@ -13,8 +15,6 @@ ******************************************************************************* */ -#pragma once - #include #include diff --git a/src/axom/inlet/Verifiable.hpp b/src/axom/inlet/Verifiable.hpp index 95acfc922b..1d985129d2 100644 --- a/src/axom/inlet/Verifiable.hpp +++ b/src/axom/inlet/Verifiable.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! ******************************************************************************* * \file Verifiable.hpp @@ -12,8 +14,6 @@ ******************************************************************************* */ -#pragma once - #include #include "axom/inlet/inlet_utils.hpp" diff --git a/src/axom/inlet/VerifiableScalar.hpp b/src/axom/inlet/VerifiableScalar.hpp index 7aaf1efb31..9582f5e748 100644 --- a/src/axom/inlet/VerifiableScalar.hpp +++ b/src/axom/inlet/VerifiableScalar.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! ******************************************************************************* * \file VerifiableScalar.hpp @@ -12,8 +14,6 @@ ******************************************************************************* */ -#pragma once - #include #include #include diff --git a/src/axom/inlet/Writer.hpp b/src/axom/inlet/Writer.hpp index 3373005a4a..1ee3ad83cd 100644 --- a/src/axom/inlet/Writer.hpp +++ b/src/axom/inlet/Writer.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! ******************************************************************************* * \file Writer.hpp @@ -12,8 +14,6 @@ ******************************************************************************* */ -#pragma once - namespace axom { namespace inlet diff --git a/src/axom/inlet/YAMLReader.hpp b/src/axom/inlet/YAMLReader.hpp index 471f5866d4..971da85e76 100644 --- a/src/axom/inlet/YAMLReader.hpp +++ b/src/axom/inlet/YAMLReader.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! ******************************************************************************* * \file YAMLReader.hpp @@ -12,8 +14,6 @@ ******************************************************************************* */ -#pragma once - #include "axom/inlet/ConduitReader.hpp" #include "conduit.hpp" diff --git a/src/axom/inlet/inlet_utils.hpp b/src/axom/inlet/inlet_utils.hpp index 8f96470050..fcafb8781c 100644 --- a/src/axom/inlet/inlet_utils.hpp +++ b/src/axom/inlet/inlet_utils.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include #include @@ -12,8 +14,6 @@ #include "axom/core/utilities/StringUtilities.hpp" #include "axom/core/Path.hpp" -#pragma once - namespace axom { namespace inlet diff --git a/src/axom/klee/AffineMatrixVisitor.hpp b/src/axom/klee/AffineMatrixVisitor.hpp index 6a5a0e3efb..a7a3026a55 100644 --- a/src/axom/klee/AffineMatrixVisitor.hpp +++ b/src/axom/klee/AffineMatrixVisitor.hpp @@ -3,7 +3,9 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once + #include "axom/klee/GeometryOperators.hpp" namespace axom::klee diff --git a/src/axom/klee/Dimensions.hpp b/src/axom/klee/Dimensions.hpp index 09622b339f..b7e86979bf 100644 --- a/src/axom/klee/Dimensions.hpp +++ b/src/axom/klee/Dimensions.hpp @@ -3,6 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once #include "axom/fmt.hpp" diff --git a/src/axom/klee/Units.hpp b/src/axom/klee/Units.hpp index e698da8553..458879eda1 100644 --- a/src/axom/klee/Units.hpp +++ b/src/axom/klee/Units.hpp @@ -3,6 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once #include "axom/core/utilities/Units.hpp" diff --git a/src/axom/klee/io/GeometryOperatorsIO.hpp b/src/axom/klee/io/GeometryOperatorsIO.hpp index ea9be87440..8d728ed94f 100644 --- a/src/axom/klee/io/GeometryOperatorsIO.hpp +++ b/src/axom/klee/io/GeometryOperatorsIO.hpp @@ -3,6 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once #include "axom/inlet.hpp" diff --git a/src/axom/klee/io/IOUtil.hpp b/src/axom/klee/io/IOUtil.hpp index 037fec7bfc..87e2eb8f75 100644 --- a/src/axom/klee/io/IOUtil.hpp +++ b/src/axom/klee/io/IOUtil.hpp @@ -3,6 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once #include "axom/klee/Dimensions.hpp" diff --git a/src/axom/klee/tests/KleeMatchers.hpp b/src/axom/klee/tests/KleeMatchers.hpp index 9e8ef28dd0..b3a14b78d0 100644 --- a/src/axom/klee/tests/KleeMatchers.hpp +++ b/src/axom/klee/tests/KleeMatchers.hpp @@ -3,6 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once #include "axom/core.hpp" diff --git a/src/axom/lumberjack/BinaryTreeCommunicator.hpp b/src/axom/lumberjack/BinaryTreeCommunicator.hpp index 2ab567983c..01544ef8b1 100644 --- a/src/axom/lumberjack/BinaryTreeCommunicator.hpp +++ b/src/axom/lumberjack/BinaryTreeCommunicator.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! ******************************************************************************* * \file BinaryTreeCommunicator.hpp @@ -12,8 +14,6 @@ ******************************************************************************* */ -#pragma once - #include "mpi.h" #include "axom/lumberjack/Communicator.hpp" diff --git a/src/axom/lumberjack/Combiner.hpp b/src/axom/lumberjack/Combiner.hpp index fe2be386fa..7d6a64522c 100644 --- a/src/axom/lumberjack/Combiner.hpp +++ b/src/axom/lumberjack/Combiner.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! ******************************************************************************* * \file Combiner.hpp @@ -13,8 +15,6 @@ ******************************************************************************* */ -#pragma once - #include "axom/lumberjack/Message.hpp" namespace axom diff --git a/src/axom/lumberjack/Communicator.hpp b/src/axom/lumberjack/Communicator.hpp index daa611a0e4..5e796fe3d1 100644 --- a/src/axom/lumberjack/Communicator.hpp +++ b/src/axom/lumberjack/Communicator.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! ******************************************************************************* * \file Communicator.hpp @@ -13,8 +15,6 @@ ******************************************************************************* */ -#pragma once - #include #include diff --git a/src/axom/lumberjack/LineFileTagCombiner.hpp b/src/axom/lumberjack/LineFileTagCombiner.hpp index b3e50d3f27..589b0b8111 100644 --- a/src/axom/lumberjack/LineFileTagCombiner.hpp +++ b/src/axom/lumberjack/LineFileTagCombiner.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! ******************************************************************************* * \file LineFileTagCombiner.hpp @@ -13,8 +15,6 @@ ******************************************************************************* */ -#pragma once - #include "axom/lumberjack/Combiner.hpp" #include "axom/lumberjack/Message.hpp" diff --git a/src/axom/lumberjack/Lumberjack.hpp b/src/axom/lumberjack/Lumberjack.hpp index 64c36c2df2..95ef56a1a1 100644 --- a/src/axom/lumberjack/Lumberjack.hpp +++ b/src/axom/lumberjack/Lumberjack.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! ******************************************************************************* * \file Lumberjack.hpp @@ -13,8 +15,6 @@ ******************************************************************************* */ -#pragma once - #include #include "mpi.h" diff --git a/src/axom/lumberjack/MPIUtility.hpp b/src/axom/lumberjack/MPIUtility.hpp index cf742b3947..25420e40a1 100644 --- a/src/axom/lumberjack/MPIUtility.hpp +++ b/src/axom/lumberjack/MPIUtility.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! ******************************************************************************* * \file MPIUtility.hpp @@ -12,8 +14,6 @@ ******************************************************************************* */ -#pragma once - #include "mpi.h" namespace axom diff --git a/src/axom/lumberjack/Message.hpp b/src/axom/lumberjack/Message.hpp index 6958aafbd4..1e10c3e69d 100644 --- a/src/axom/lumberjack/Message.hpp +++ b/src/axom/lumberjack/Message.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! ******************************************************************************* * \file Message.hpp @@ -12,8 +14,6 @@ ******************************************************************************* */ -#pragma once - #include #include #include diff --git a/src/axom/lumberjack/NonCollectiveRootCommunicator.hpp b/src/axom/lumberjack/NonCollectiveRootCommunicator.hpp index c2711f8b68..53cbf50d2d 100644 --- a/src/axom/lumberjack/NonCollectiveRootCommunicator.hpp +++ b/src/axom/lumberjack/NonCollectiveRootCommunicator.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! ******************************************************************************* * \file NonCollectiveRootCommunicator.hpp @@ -13,8 +15,6 @@ ******************************************************************************* */ -#pragma once - #include "axom/lumberjack/Lumberjack.hpp" #include "axom/lumberjack/Communicator.hpp" diff --git a/src/axom/lumberjack/RootCommunicator.hpp b/src/axom/lumberjack/RootCommunicator.hpp index 8d3a45072c..65c7d35948 100644 --- a/src/axom/lumberjack/RootCommunicator.hpp +++ b/src/axom/lumberjack/RootCommunicator.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! ******************************************************************************* * \file RootCommunicator.hpp @@ -12,8 +14,6 @@ ******************************************************************************* */ -#pragma once - #include #include "mpi.h" diff --git a/src/axom/lumberjack/TextEqualityCombiner.hpp b/src/axom/lumberjack/TextEqualityCombiner.hpp index 506653d749..f82aece901 100644 --- a/src/axom/lumberjack/TextEqualityCombiner.hpp +++ b/src/axom/lumberjack/TextEqualityCombiner.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! ******************************************************************************* * \file TextEqualityCombiner.hpp @@ -13,8 +15,6 @@ ******************************************************************************* */ -#pragma once - #include "axom/lumberjack/Combiner.hpp" #include "axom/lumberjack/Message.hpp" diff --git a/src/axom/lumberjack/TextTagCombiner.hpp b/src/axom/lumberjack/TextTagCombiner.hpp index 6883c48e2b..040d6fa785 100644 --- a/src/axom/lumberjack/TextTagCombiner.hpp +++ b/src/axom/lumberjack/TextTagCombiner.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! ******************************************************************************* * \file TextTagCombiner.hpp @@ -13,8 +15,6 @@ ******************************************************************************* */ -#pragma once - #include "axom/lumberjack/Combiner.hpp" #include "axom/lumberjack/Message.hpp" diff --git a/src/axom/mint/mesh/internal/MeshHelpers.hpp b/src/axom/mint/mesh/internal/MeshHelpers.hpp index ba4c055557..0419d726d2 100644 --- a/src/axom/mint/mesh/internal/MeshHelpers.hpp +++ b/src/axom/mint/mesh/internal/MeshHelpers.hpp @@ -3,6 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once #include "axom/core/Macros.hpp" // for AXOM_UNUSED_PARAM diff --git a/src/axom/mint/tests/StructuredMesh_helpers.hpp b/src/axom/mint/tests/StructuredMesh_helpers.hpp index 46799b9f91..5bfbed719e 100644 --- a/src/axom/mint/tests/StructuredMesh_helpers.hpp +++ b/src/axom/mint/tests/StructuredMesh_helpers.hpp @@ -3,6 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once #include "axom/mint/config.hpp" // for compile-time definitions diff --git a/src/axom/mint/tests/mint_test_utilities.hpp b/src/axom/mint/tests/mint_test_utilities.hpp index 9fb5f12371..4a023f4284 100644 --- a/src/axom/mint/tests/mint_test_utilities.hpp +++ b/src/axom/mint/tests/mint_test_utilities.hpp @@ -4,13 +4,13 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file * * \brief Consists of utility functions to facilitate in test development. */ -#pragma once - // Axom includes #include "axom/core/Macros.hpp" diff --git a/src/axom/mir/ElviraAlgorithm.hpp b/src/axom/mir/ElviraAlgorithm.hpp index 7c1a59c6f9..579e82aeb4 100644 --- a/src/axom/mir/ElviraAlgorithm.hpp +++ b/src/axom/mir/ElviraAlgorithm.hpp @@ -3,6 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once #include "axom/config.hpp" diff --git a/src/axom/mir/EquiZAlgorithm.hpp b/src/axom/mir/EquiZAlgorithm.hpp index 0a36344b43..c32f12a8c5 100644 --- a/src/axom/mir/EquiZAlgorithm.hpp +++ b/src/axom/mir/EquiZAlgorithm.hpp @@ -3,6 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once #include "axom/config.hpp" diff --git a/src/axom/mir/detail/elvira_detail.hpp b/src/axom/mir/detail/elvira_detail.hpp index d40f1b6128..141864a3de 100644 --- a/src/axom/mir/detail/elvira_detail.hpp +++ b/src/axom/mir/detail/elvira_detail.hpp @@ -3,6 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once // Most includes happen in the ElviraAlgorithm.hpp header file that includes this file. diff --git a/src/axom/mir/detail/elvira_impl.hpp b/src/axom/mir/detail/elvira_impl.hpp index e4aa50bfa7..bb9767aab3 100644 --- a/src/axom/mir/detail/elvira_impl.hpp +++ b/src/axom/mir/detail/elvira_impl.hpp @@ -4,11 +4,11 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + // NOTE: This file is meant to be included by ElviraAlgorithm.hpp after its // other includes so we do not include much here. -#pragma once - namespace axom { namespace mir diff --git a/src/axom/mir/detail/equiz_detail.hpp b/src/axom/mir/detail/equiz_detail.hpp index 3f9ef1088c..0dbb05987d 100644 --- a/src/axom/mir/detail/equiz_detail.hpp +++ b/src/axom/mir/detail/equiz_detail.hpp @@ -3,6 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once #include "axom/config.hpp" diff --git a/src/axom/mir/examples/concentric_circles/MIRApplication.hpp b/src/axom/mir/examples/concentric_circles/MIRApplication.hpp index 4540bb645d..c88c34a9ee 100644 --- a/src/axom/mir/examples/concentric_circles/MIRApplication.hpp +++ b/src/axom/mir/examples/concentric_circles/MIRApplication.hpp @@ -3,7 +3,9 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once + #include "axom/config.hpp" #include "axom/core.hpp" // for axom macros diff --git a/src/axom/mir/examples/concentric_circles/runMIR.hpp b/src/axom/mir/examples/concentric_circles/runMIR.hpp index d04e12d59f..650b650b3e 100644 --- a/src/axom/mir/examples/concentric_circles/runMIR.hpp +++ b/src/axom/mir/examples/concentric_circles/runMIR.hpp @@ -3,7 +3,9 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once + #include "axom/config.hpp" #include "axom/core.hpp" // for axom macros #include "axom/slic.hpp" diff --git a/src/axom/mir/examples/heavily_mixed/HMApplication.hpp b/src/axom/mir/examples/heavily_mixed/HMApplication.hpp index 78111c7352..1f1fa2316b 100644 --- a/src/axom/mir/examples/heavily_mixed/HMApplication.hpp +++ b/src/axom/mir/examples/heavily_mixed/HMApplication.hpp @@ -3,7 +3,9 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once + #include "axom/config.hpp" #include "axom/core.hpp" diff --git a/src/axom/mir/examples/heavily_mixed/runMIR.hpp b/src/axom/mir/examples/heavily_mixed/runMIR.hpp index 372a5d3f2d..9424cb8da3 100644 --- a/src/axom/mir/examples/heavily_mixed/runMIR.hpp +++ b/src/axom/mir/examples/heavily_mixed/runMIR.hpp @@ -3,7 +3,9 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once + #include "axom/config.hpp" #include "axom/core.hpp" #include "axom/slic.hpp" diff --git a/src/axom/mir/examples/tutorial_simple/runMIR.hpp b/src/axom/mir/examples/tutorial_simple/runMIR.hpp index a05c1df1b0..db0f50bd09 100644 --- a/src/axom/mir/examples/tutorial_simple/runMIR.hpp +++ b/src/axom/mir/examples/tutorial_simple/runMIR.hpp @@ -3,7 +3,9 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once + #include "axom/config.hpp" #include "axom/core.hpp" // for axom macros #include "axom/slic.hpp" diff --git a/src/axom/mir/future/ClipFieldFilter.hpp b/src/axom/mir/future/ClipFieldFilter.hpp index f65577d236..c26f37a1a9 100644 --- a/src/axom/mir/future/ClipFieldFilter.hpp +++ b/src/axom/mir/future/ClipFieldFilter.hpp @@ -3,6 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once #include "axom/core.hpp" diff --git a/src/axom/mir/future/ClipFieldFilterDevice.hpp b/src/axom/mir/future/ClipFieldFilterDevice.hpp index e3ba0832ca..79a00d0f56 100644 --- a/src/axom/mir/future/ClipFieldFilterDevice.hpp +++ b/src/axom/mir/future/ClipFieldFilterDevice.hpp @@ -3,6 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once #include "axom/core.hpp" diff --git a/src/axom/mir/reference/CellClipper.hpp b/src/axom/mir/reference/CellClipper.hpp index 71510693bb..0d0f9383e2 100644 --- a/src/axom/mir/reference/CellClipper.hpp +++ b/src/axom/mir/reference/CellClipper.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file CellClipper.hpp * @@ -11,8 +13,6 @@ * */ -#pragma once - #include "axom/core.hpp" // for axom macros #include "axom/slam.hpp" // unified header for slam classes and functions diff --git a/src/axom/mir/reference/CellData.hpp b/src/axom/mir/reference/CellData.hpp index 3203bf4eb4..ffd8a8e3be 100644 --- a/src/axom/mir/reference/CellData.hpp +++ b/src/axom/mir/reference/CellData.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file CellData.hpp * @@ -11,8 +13,6 @@ * and CellTopologyData and CellMapData structs. */ -#pragma once - #include "axom/core.hpp" // for axom macros #include "axom/slam.hpp" diff --git a/src/axom/mir/reference/CellGenerator.hpp b/src/axom/mir/reference/CellGenerator.hpp index 5144bde63a..6e48d46882 100644 --- a/src/axom/mir/reference/CellGenerator.hpp +++ b/src/axom/mir/reference/CellGenerator.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file CellGenerator.hpp * @@ -11,8 +13,6 @@ * */ -#pragma once - #include "axom/core.hpp" // for axom macros #include "axom/slam.hpp" // unified header for slam classes and functions diff --git a/src/axom/mir/reference/InterfaceReconstructor.hpp b/src/axom/mir/reference/InterfaceReconstructor.hpp index c4242008ef..8b786c97fb 100644 --- a/src/axom/mir/reference/InterfaceReconstructor.hpp +++ b/src/axom/mir/reference/InterfaceReconstructor.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file InterfaceReconstructor.hpp * @@ -11,8 +13,6 @@ * */ -#pragma once - #include "axom/core.hpp" // for axom macros #include "axom/slam.hpp" diff --git a/src/axom/mir/reference/MIRMesh.hpp b/src/axom/mir/reference/MIRMesh.hpp index 2b5f589849..f25975d541 100644 --- a/src/axom/mir/reference/MIRMesh.hpp +++ b/src/axom/mir/reference/MIRMesh.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file MIRMesh.hpp * @@ -11,8 +13,6 @@ * */ -#pragma once - #include "axom/core.hpp" // for axom macros #include "axom/slam.hpp" // unified header for slam classes and functions diff --git a/src/axom/mir/reference/MIRMeshTypes.hpp b/src/axom/mir/reference/MIRMeshTypes.hpp index 74e70a7748..cbb95b7139 100644 --- a/src/axom/mir/reference/MIRMeshTypes.hpp +++ b/src/axom/mir/reference/MIRMeshTypes.hpp @@ -4,14 +4,14 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file MIRMeshTypes.hpp * * \brief Contains the specifications for types aliases used throughout the MIR component. */ -#pragma once - #include "axom/core.hpp" // for axom macros #include "axom/slam.hpp" #include "axom/primal.hpp" diff --git a/src/axom/mir/reference/MIRUtilities.hpp b/src/axom/mir/reference/MIRUtilities.hpp index 9b96421d9b..37fb0a7bd1 100644 --- a/src/axom/mir/reference/MIRUtilities.hpp +++ b/src/axom/mir/reference/MIRUtilities.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file MIRUtilities.hpp * @@ -12,8 +14,6 @@ * */ -#pragma once - #include "axom/mir/reference/ZooClippingTables.hpp" //-------------------------------------------------------------------------------- diff --git a/src/axom/multimat/examples/helper.hpp b/src/axom/multimat/examples/helper.hpp index a9d7aadb55..c0327761a8 100644 --- a/src/axom/multimat/examples/helper.hpp +++ b/src/axom/multimat/examples/helper.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * Set-up multi-material data for examples * @@ -12,8 +14,6 @@ * Also defines some helper struct-classes. */ -#pragma once - #include "axom/core.hpp" #include "axom/slam.hpp" #include "axom/fmt.hpp" diff --git a/src/axom/multimat/multimat.hpp b/src/axom/multimat/multimat.hpp index 1a5e03ba09..ee67f965a7 100644 --- a/src/axom/multimat/multimat.hpp +++ b/src/axom/multimat/multimat.hpp @@ -4,14 +4,14 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file multimat.hpp * * \brief Contains the MultiMat library header and its template implementation * */ -#pragma once - #include "axom/slam.hpp" #include diff --git a/src/axom/primal/geometry/BezierCurve.hpp b/src/axom/primal/geometry/BezierCurve.hpp index 8e83a0b524..26616a1513 100644 --- a/src/axom/primal/geometry/BezierCurve.hpp +++ b/src/axom/primal/geometry/BezierCurve.hpp @@ -4,14 +4,14 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file BezierCurve.hpp * * \brief A BezierCurve primitive */ -#pragma once - #include "axom/core.hpp" #include "axom/slic.hpp" diff --git a/src/axom/primal/geometry/BezierPatch.hpp b/src/axom/primal/geometry/BezierPatch.hpp index 9ad0c26a1b..c892285baf 100644 --- a/src/axom/primal/geometry/BezierPatch.hpp +++ b/src/axom/primal/geometry/BezierPatch.hpp @@ -4,14 +4,14 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file BezierPatch.hpp * * \brief A BezierPatch primitive */ -#pragma once - #include "axom/core.hpp" #include "axom/slic.hpp" diff --git a/src/axom/primal/geometry/BezierTriangle.hpp b/src/axom/primal/geometry/BezierTriangle.hpp index 27d04f0a2f..fd4d5abf11 100644 --- a/src/axom/primal/geometry/BezierTriangle.hpp +++ b/src/axom/primal/geometry/BezierTriangle.hpp @@ -4,14 +4,14 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file BezierTriangle.hpp * * \brief A BezierTriangle primitive */ -#pragma once - #include "axom/core.hpp" #include "axom/slic.hpp" diff --git a/src/axom/primal/geometry/CurvedPolygon.hpp b/src/axom/primal/geometry/CurvedPolygon.hpp index e308926d08..ed564992ab 100644 --- a/src/axom/primal/geometry/CurvedPolygon.hpp +++ b/src/axom/primal/geometry/CurvedPolygon.hpp @@ -4,14 +4,14 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file CurvedPolygon.hpp * * \brief A polygon primitive whose edges are Bezier curves */ -#pragma once - #include "axom/slic.hpp" #include "axom/core/NumericArray.hpp" diff --git a/src/axom/primal/geometry/KnotVector.hpp b/src/axom/primal/geometry/KnotVector.hpp index 708247318d..6781430aac 100644 --- a/src/axom/primal/geometry/KnotVector.hpp +++ b/src/axom/primal/geometry/KnotVector.hpp @@ -4,14 +4,14 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file KnotVector.hpp * * \brief A class to represent knot vectors for NURBS */ -#pragma once - #include "axom/core.hpp" #include "axom/slic.hpp" diff --git a/src/axom/primal/geometry/NURBSCurve.hpp b/src/axom/primal/geometry/NURBSCurve.hpp index cfbad07af9..b5ecc60b3e 100644 --- a/src/axom/primal/geometry/NURBSCurve.hpp +++ b/src/axom/primal/geometry/NURBSCurve.hpp @@ -4,14 +4,14 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file NURBSCurve.hpp * * \brief A NURBS curve primitive */ -#pragma once - #include "axom/core.hpp" #include "axom/slic.hpp" diff --git a/src/axom/primal/geometry/NURBSPatch.hpp b/src/axom/primal/geometry/NURBSPatch.hpp index 764971c4de..eba9544377 100644 --- a/src/axom/primal/geometry/NURBSPatch.hpp +++ b/src/axom/primal/geometry/NURBSPatch.hpp @@ -4,14 +4,14 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file NURBSPatch.hpp * * \brief A (trimmed) NURBSPatch primitive */ -#pragma once - #include "axom/core.hpp" #include "axom/slic.hpp" diff --git a/src/axom/primal/geometry/Polygon.hpp b/src/axom/primal/geometry/Polygon.hpp index 6fbd3b1486..6350d459c1 100644 --- a/src/axom/primal/geometry/Polygon.hpp +++ b/src/axom/primal/geometry/Polygon.hpp @@ -4,14 +4,14 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file Polygon.hpp * * \brief A Polygon primitive for primal */ -#pragma once - #include "axom/core/Array.hpp" #include "axom/core/StaticArray.hpp" #include "axom/primal/geometry/Point.hpp" diff --git a/src/axom/primal/geometry/Polyhedron.hpp b/src/axom/primal/geometry/Polyhedron.hpp index b618cc0124..33330076d3 100644 --- a/src/axom/primal/geometry/Polyhedron.hpp +++ b/src/axom/primal/geometry/Polyhedron.hpp @@ -4,14 +4,14 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file Polyhedron.hpp * * \brief A Polyhedron primitive for primal */ -#pragma once - #include "axom/core/StackArray.hpp" #include "axom/primal/geometry/Hexahedron.hpp" diff --git a/src/axom/primal/geometry/detail/analytic_test_surfaces.hpp b/src/axom/primal/geometry/detail/analytic_test_surfaces.hpp index 8311203497..e56d643bda 100644 --- a/src/axom/primal/geometry/detail/analytic_test_surfaces.hpp +++ b/src/axom/primal/geometry/detail/analytic_test_surfaces.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file analytic_test_surfaces.hpp * @@ -14,8 +16,6 @@ * \sa primal_solid_angle.cpp */ -#pragma once - #include "axom/config.hpp" #include "axom/primal.hpp" diff --git a/src/axom/primal/operators/clip.hpp b/src/axom/primal/operators/clip.hpp index dab5447b40..f3a7ec4178 100644 --- a/src/axom/primal/operators/clip.hpp +++ b/src/axom/primal/operators/clip.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file clip.hpp * @@ -11,8 +13,6 @@ * another primal primitive */ -#pragma once - #include "axom/core/utilities/Utilities.hpp" #include "axom/primal/geometry/Point.hpp" diff --git a/src/axom/primal/operators/closest_point.hpp b/src/axom/primal/operators/closest_point.hpp index fab271e115..bdd79bce66 100644 --- a/src/axom/primal/operators/closest_point.hpp +++ b/src/axom/primal/operators/closest_point.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file closest_point.hpp * @@ -12,8 +14,6 @@ * */ -#pragma once - #include "axom/primal/geometry/Point.hpp" #include "axom/primal/geometry/Segment.hpp" #include "axom/primal/geometry/Triangle.hpp" diff --git a/src/axom/primal/operators/compute_bounding_box.hpp b/src/axom/primal/operators/compute_bounding_box.hpp index 0b9573cd36..dcf03c3847 100644 --- a/src/axom/primal/operators/compute_bounding_box.hpp +++ b/src/axom/primal/operators/compute_bounding_box.hpp @@ -4,14 +4,14 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file compute_bounding_box.hpp * * \brief Consists of functions to create bounding boxes. */ -#pragma once - #include "axom/core/numerics/Matrix.hpp" // for Matrix #include "axom/core/Macros.hpp" // for AXOM_HOST__DEVICE #include "axom/core/numerics/eigen_solve.hpp" // for eigen_solve diff --git a/src/axom/primal/operators/detail/clip_impl.hpp b/src/axom/primal/operators/detail/clip_impl.hpp index ebb36d77d9..443d5b4734 100644 --- a/src/axom/primal/operators/detail/clip_impl.hpp +++ b/src/axom/primal/operators/detail/clip_impl.hpp @@ -4,14 +4,14 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file clip_impl.hpp * * \brief Helper functions for the primal clipping operators */ -#pragma once - #include "axom/config.hpp" #include "axom/core/Macros.hpp" diff --git a/src/axom/primal/operators/detail/evaluate_integral_curve_impl.hpp b/src/axom/primal/operators/detail/evaluate_integral_curve_impl.hpp index 3c3e22d324..b0d8b9a318 100644 --- a/src/axom/primal/operators/detail/evaluate_integral_curve_impl.hpp +++ b/src/axom/primal/operators/detail/evaluate_integral_curve_impl.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file evaluate_integral_curve_impl.hpp * @@ -13,8 +15,6 @@ * dependencies to prevent circular include chains (e.g. with NURBSPatch). */ -#pragma once - // Axom includes #include "axom/core.hpp" #include "axom/config.hpp" diff --git a/src/axom/primal/operators/detail/evaluate_integral_impl.hpp b/src/axom/primal/operators/detail/evaluate_integral_impl.hpp index caa10c9fea..af645436d8 100644 --- a/src/axom/primal/operators/detail/evaluate_integral_impl.hpp +++ b/src/axom/primal/operators/detail/evaluate_integral_impl.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file evaluate_integral_impl.hpp * @@ -13,7 +15,5 @@ * internally evaluate curve-based integrals (e.g. via trimming curves). */ -#pragma once - #include "axom/primal/operators/detail/evaluate_integral_curve_impl.hpp" #include "axom/primal/operators/detail/evaluate_integral_surface_impl.hpp" diff --git a/src/axom/primal/operators/detail/evaluate_integral_surface_impl.hpp b/src/axom/primal/operators/detail/evaluate_integral_surface_impl.hpp index f3934c6fbb..ca661f8726 100644 --- a/src/axom/primal/operators/detail/evaluate_integral_surface_impl.hpp +++ b/src/axom/primal/operators/detail/evaluate_integral_surface_impl.hpp @@ -4,14 +4,14 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file evaluate_integral_surface_impl.hpp * * \brief Implementation helpers for surface/volume integral evaluation. */ -#pragma once - // Axom includes #include "axom/core.hpp" #include "axom/config.hpp" diff --git a/src/axom/primal/operators/detail/fuzzy_comparators.hpp b/src/axom/primal/operators/detail/fuzzy_comparators.hpp index fc3c915696..b47b1c5e1e 100644 --- a/src/axom/primal/operators/detail/fuzzy_comparators.hpp +++ b/src/axom/primal/operators/detail/fuzzy_comparators.hpp @@ -4,14 +4,14 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file fuzzy_comparators.hpp * * This file provides helper functions for fuzzy comparisons */ -#pragma once - #include "axom/core/Macros.hpp" #include "axom/core/utilities/Utilities.hpp" diff --git a/src/axom/primal/operators/detail/intersect_bezier_impl.hpp b/src/axom/primal/operators/detail/intersect_bezier_impl.hpp index 867523e89b..84e640821b 100644 --- a/src/axom/primal/operators/detail/intersect_bezier_impl.hpp +++ b/src/axom/primal/operators/detail/intersect_bezier_impl.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file intersect_bezier_impl.hpp * @@ -11,8 +13,6 @@ * of Bezier curves with other Bezier curves and other geometric objects */ -#pragma once - #include "axom/primal/geometry/Point.hpp" #include "axom/primal/geometry/BoundingBox.hpp" #include "axom/primal/geometry/BezierCurve.hpp" diff --git a/src/axom/primal/operators/detail/intersect_impl.hpp b/src/axom/primal/operators/detail/intersect_impl.hpp index ce3bcd3d94..1227816aed 100644 --- a/src/axom/primal/operators/detail/intersect_impl.hpp +++ b/src/axom/primal/operators/detail/intersect_impl.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file intersect_impl.hpp * @@ -11,8 +13,6 @@ * geometric primitives intersect */ -#pragma once - #include "axom/core/Macros.hpp" #include "axom/core/numerics/Determinants.hpp" #include "axom/core/utilities/Utilities.hpp" diff --git a/src/axom/primal/operators/detail/intersect_patch_impl.hpp b/src/axom/primal/operators/detail/intersect_patch_impl.hpp index 752cb5a565..9c21a69bc2 100644 --- a/src/axom/primal/operators/detail/intersect_patch_impl.hpp +++ b/src/axom/primal/operators/detail/intersect_patch_impl.hpp @@ -3,6 +3,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file intersect_patch_impl.hpp * @@ -10,8 +12,6 @@ * of rays and Bezier patches */ -#pragma once - #include "axom/primal/geometry/Point.hpp" #include "axom/primal/geometry/Polygon.hpp" #include "axom/primal/geometry/BoundingBox.hpp" diff --git a/src/axom/primal/operators/detail/predicate_determinants.hpp b/src/axom/primal/operators/detail/predicate_determinants.hpp index 29db51b7d7..1beaa18dd5 100644 --- a/src/axom/primal/operators/detail/predicate_determinants.hpp +++ b/src/axom/primal/operators/detail/predicate_determinants.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file predicate_determinants.hpp * @@ -27,8 +29,6 @@ * characterizing where the double-precision sign is and is not reliable. */ -#pragma once - #include "axom/core/numerics/Determinants.hpp" #include "axom/primal/geometry/Point.hpp" diff --git a/src/axom/primal/operators/detail/winding_number_2d_memoization.hpp b/src/axom/primal/operators/detail/winding_number_2d_memoization.hpp index 16ea941642..be24ae7837 100644 --- a/src/axom/primal/operators/detail/winding_number_2d_memoization.hpp +++ b/src/axom/primal/operators/detail/winding_number_2d_memoization.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file winding_number_2d_memoization.hpp * @@ -11,8 +13,6 @@ * i.e. dynamically caching and reusing intermediate curve subdivisions. */ -#pragma once - #include "axom/core.hpp" #include "axom/slic.hpp" diff --git a/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp b/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp index 5446b86431..44938542d2 100644 --- a/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp +++ b/src/axom/primal/operators/detail/winding_number_3d_memoization.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file winding_number_3d_memoization.hpp * @@ -11,8 +13,6 @@ * dynamically caching and reusing patch surface evaluations and tangents at quadrature points. */ -#pragma once - #include "axom/core.hpp" #include "axom/slic.hpp" diff --git a/src/axom/primal/operators/evaluate_integral.hpp b/src/axom/primal/operators/evaluate_integral.hpp index 7e5e583c54..fbc4539cdd 100644 --- a/src/axom/primal/operators/evaluate_integral.hpp +++ b/src/axom/primal/operators/evaluate_integral.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file evaluate_integral.hpp * @@ -17,7 +19,5 @@ * evaluate curve-based integrals (e.g. via trimming curves). */ -#pragma once - #include "axom/primal/operators/evaluate_integral_curve.hpp" #include "axom/primal/operators/evaluate_integral_surface.hpp" diff --git a/src/axom/primal/operators/evaluate_integral_curve.hpp b/src/axom/primal/operators/evaluate_integral_curve.hpp index 9e6d312be4..18339db7d7 100644 --- a/src/axom/primal/operators/evaluate_integral_curve.hpp +++ b/src/axom/primal/operators/evaluate_integral_curve.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file evaluate_integral_curve.hpp * @@ -23,8 +25,6 @@ * https://doi.org/10.1016/j.cad.2020.102944 */ -#pragma once - // Axom includes #include "axom/core.hpp" #include "axom/config.hpp" diff --git a/src/axom/primal/operators/evaluate_integral_surface.hpp b/src/axom/primal/operators/evaluate_integral_surface.hpp index d7c9fcc74c..372c2644c9 100644 --- a/src/axom/primal/operators/evaluate_integral_surface.hpp +++ b/src/axom/primal/operators/evaluate_integral_surface.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file evaluate_integral_surface.hpp * @@ -17,8 +19,6 @@ * https://doi.org/10.1016/j.cad.2021.103093 */ -#pragma once - // Axom includes #include "axom/core.hpp" #include "axom/config.hpp" diff --git a/src/axom/primal/operators/in_curved_polygon.hpp b/src/axom/primal/operators/in_curved_polygon.hpp index 18c3dc7bc3..ee94193ed2 100644 --- a/src/axom/primal/operators/in_curved_polygon.hpp +++ b/src/axom/primal/operators/in_curved_polygon.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file in_curved_polygon.hpp * @@ -13,8 +15,6 @@ * Uses an adaptive winding number calculation */ -#pragma once - // Axom includes #include "axom/config.hpp" diff --git a/src/axom/primal/operators/in_polygon.hpp b/src/axom/primal/operators/in_polygon.hpp index ae485c3677..c196b670a8 100644 --- a/src/axom/primal/operators/in_polygon.hpp +++ b/src/axom/primal/operators/in_polygon.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file in_polygon.hpp * @@ -13,8 +15,6 @@ * Uses a ray casting algorithm */ -#pragma once - // Axom includes #include "axom/config.hpp" diff --git a/src/axom/primal/operators/in_polyhedron.hpp b/src/axom/primal/operators/in_polyhedron.hpp index 98d138cd55..325f51fa8b 100644 --- a/src/axom/primal/operators/in_polyhedron.hpp +++ b/src/axom/primal/operators/in_polyhedron.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file in_polyhedron.hpp * @@ -13,8 +15,6 @@ * Uses a winding number algorithm */ -#pragma once - // Axom includes #include "axom/config.hpp" diff --git a/src/axom/primal/operators/in_sphere.hpp b/src/axom/primal/operators/in_sphere.hpp index 85c5ea8126..4d4e03933a 100644 --- a/src/axom/primal/operators/in_sphere.hpp +++ b/src/axom/primal/operators/in_sphere.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file in_sphere.hpp * @@ -19,8 +21,6 @@ * and in_sphere_orientation() for the tolerance (EPS) scaling caveat. */ -#pragma once - #include "axom/core.hpp" #include "axom/primal/geometry/Point.hpp" #include "axom/primal/geometry/Triangle.hpp" diff --git a/src/axom/primal/operators/intersect.hpp b/src/axom/primal/operators/intersect.hpp index 6ce637c286..432c0d95ca 100644 --- a/src/axom/primal/operators/intersect.hpp +++ b/src/axom/primal/operators/intersect.hpp @@ -4,14 +4,14 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file intersect.hpp * * \brief Consists of functions to test intersection among geometric primitives. */ -#pragma once - #include "axom/config.hpp" #include "axom/core/Macros.hpp" #include "axom/core/utilities/Utilities.hpp" diff --git a/src/axom/primal/operators/intersection_volume.hpp b/src/axom/primal/operators/intersection_volume.hpp index 6decba7b4e..b7050e3a5a 100644 --- a/src/axom/primal/operators/intersection_volume.hpp +++ b/src/axom/primal/operators/intersection_volume.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file intersection_volume.hpp * @@ -12,8 +14,6 @@ * another primal primitive */ -#pragma once - #include "axom/primal/geometry/Tetrahedron.hpp" #include "axom/primal/geometry/Octahedron.hpp" #include "axom/primal/geometry/Polyhedron.hpp" diff --git a/src/axom/primal/operators/orientation.hpp b/src/axom/primal/operators/orientation.hpp index a9c1450dd0..76b02e2cca 100644 --- a/src/axom/primal/operators/orientation.hpp +++ b/src/axom/primal/operators/orientation.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file orientation.hpp * @@ -15,8 +17,6 @@ * See detail/predicate_determinants.hpp for the precision/robustness discussion. */ -#pragma once - #include "axom/core/numerics/Determinants.hpp" #include "axom/core/utilities/Utilities.hpp" diff --git a/src/axom/primal/operators/slice.hpp b/src/axom/primal/operators/slice.hpp index 55ade5ab84..80f6df855c 100644 --- a/src/axom/primal/operators/slice.hpp +++ b/src/axom/primal/operators/slice.hpp @@ -5,6 +5,7 @@ // SPDX-License-Identifier: (BSD-3-Clause) #pragma once + #include "axom/core/utilities/Utilities.hpp" #include "axom/primal/geometry/Point.hpp" diff --git a/src/axom/primal/operators/split.hpp b/src/axom/primal/operators/split.hpp index 36bf068689..930ad00005 100644 --- a/src/axom/primal/operators/split.hpp +++ b/src/axom/primal/operators/split.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file split.hpp * @@ -11,8 +13,6 @@ * (a collection of) another primal primitive */ -#pragma once - #include "axom/core/Array.hpp" #include "axom/primal/geometry/Octahedron.hpp" #include "axom/primal/geometry/Tetrahedron.hpp" diff --git a/src/axom/primal/operators/squared_distance.hpp b/src/axom/primal/operators/squared_distance.hpp index b2d3af627b..e2efd9fbc1 100644 --- a/src/axom/primal/operators/squared_distance.hpp +++ b/src/axom/primal/operators/squared_distance.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file squared_distance.hpp * @@ -11,8 +13,6 @@ * the squared distance between two geometric entities. */ -#pragma once - #include "axom/primal/geometry/BoundingBox.hpp" #include "axom/primal/geometry/Point.hpp" #include "axom/primal/geometry/Segment.hpp" diff --git a/src/axom/primal/operators/winding_number.hpp b/src/axom/primal/operators/winding_number.hpp index 95203be499..55e330a9db 100644 --- a/src/axom/primal/operators/winding_number.hpp +++ b/src/axom/primal/operators/winding_number.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file winding_number.hpp * @@ -11,8 +13,6 @@ * for points with respect to various geometric objects. */ -#pragma once - // Axom includes #include "axom/core.hpp" #include "axom/config.hpp" diff --git a/src/axom/quest/AllNearestNeighbors.hpp b/src/axom/quest/AllNearestNeighbors.hpp index 48e353372c..cc90d26973 100644 --- a/src/axom/quest/AllNearestNeighbors.hpp +++ b/src/axom/quest/AllNearestNeighbors.hpp @@ -4,13 +4,13 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file AllNearestNeighbors.hpp * \brief Defines all-nearest-neighbor queries */ -#pragma once - namespace axom { namespace quest diff --git a/src/axom/quest/Delaunay.hpp b/src/axom/quest/Delaunay.hpp index 89b3644a48..136a76019c 100644 --- a/src/axom/quest/Delaunay.hpp +++ b/src/axom/quest/Delaunay.hpp @@ -4,14 +4,14 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file Delaunay.hpp * * \brief Declares the public `quest::Delaunay` incremental 2D/3D triangulation API. */ -#pragma once - #include "axom/core.hpp" #include "axom/slic.hpp" #include "axom/slam.hpp" diff --git a/src/axom/quest/FastApproximateGWN.hpp b/src/axom/quest/FastApproximateGWN.hpp index 5f6cf7c881..8c80df1552 100644 --- a/src/axom/quest/FastApproximateGWN.hpp +++ b/src/axom/quest/FastApproximateGWN.hpp @@ -2,7 +2,9 @@ // other Axom Project Developers. See the top-level LICENSE file for details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once + #include "axom/primal.hpp" #include diff --git a/src/axom/quest/GWNMethods.hpp b/src/axom/quest/GWNMethods.hpp index a79efcfd88..f775387f98 100644 --- a/src/axom/quest/GWNMethods.hpp +++ b/src/axom/quest/GWNMethods.hpp @@ -4,14 +4,14 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file GWNMethods.hpp * * \brief Helper classes and type traits for GWN Evaluation methods */ -#pragma once - #include "axom/config.hpp" #include "axom/core.hpp" #include "axom/slic.hpp" diff --git a/src/axom/quest/InOutOctree.hpp b/src/axom/quest/InOutOctree.hpp index f3b08fc52c..cba1e977b2 100644 --- a/src/axom/quest/InOutOctree.hpp +++ b/src/axom/quest/InOutOctree.hpp @@ -4,14 +4,14 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file InOutOctree.hpp * * \brief Defines an InOutOctree for containment queries on a surface. */ -#pragma once - #include "axom/core.hpp" #include "axom/core/NumericLimits.hpp" #include "axom/slic.hpp" diff --git a/src/axom/quest/IntersectionShaper.hpp b/src/axom/quest/IntersectionShaper.hpp index 61bb2e2107..ee70481b59 100644 --- a/src/axom/quest/IntersectionShaper.hpp +++ b/src/axom/quest/IntersectionShaper.hpp @@ -4,14 +4,14 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file IntersectionShaper.hpp * * \brief Helper class for intersection-based shaping queries */ -#pragma once - #include "axom/config.hpp" #include "axom/core.hpp" diff --git a/src/axom/quest/MarchingCubes.hpp b/src/axom/quest/MarchingCubes.hpp index db4c1b2c0d..4b663a0cbe 100644 --- a/src/axom/quest/MarchingCubes.hpp +++ b/src/axom/quest/MarchingCubes.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * @file MarchingCubes.hpp * @@ -11,8 +13,6 @@ * compute isocontour from a scalar field in a blueprint mesh. */ -#pragma once - #include "axom/config.hpp" // Implementation requires Conduit. diff --git a/src/axom/quest/SamplingShaper.hpp b/src/axom/quest/SamplingShaper.hpp index 4859d8cd51..fdb8c91642 100644 --- a/src/axom/quest/SamplingShaper.hpp +++ b/src/axom/quest/SamplingShaper.hpp @@ -4,14 +4,14 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file SamplingShaper.hpp * * \brief Helper class for sampling-based shaping queries */ -#pragma once - #include "axom/config.hpp" #include "axom/core.hpp" #include "axom/slic.hpp" diff --git a/src/axom/quest/Shaper.hpp b/src/axom/quest/Shaper.hpp index c6e3520734..caadea8300 100644 --- a/src/axom/quest/Shaper.hpp +++ b/src/axom/quest/Shaper.hpp @@ -4,14 +4,14 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file Shaper.hpp * * \brief Helper class for shaping queries */ -#pragma once - #include "axom/config.hpp" #ifndef AXOM_USE_KLEE #error Shaping functionality requires Axom to be configured with the Klee component diff --git a/src/axom/quest/detail/DelaunayElementFinder.hpp b/src/axom/quest/detail/DelaunayElementFinder.hpp index 7b6d501387..44cd5f1d20 100644 --- a/src/axom/quest/detail/DelaunayElementFinder.hpp +++ b/src/axom/quest/detail/DelaunayElementFinder.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file DelaunayElementFinder.hpp * @@ -11,8 +13,6 @@ * location walks from nearby inserted vertices. */ -#pragma once - #include "axom/core.hpp" #include "axom/primal.hpp" #include "axom/spin.hpp" diff --git a/src/axom/quest/detail/DelaunayImpl.hpp b/src/axom/quest/detail/DelaunayImpl.hpp index 63c531eeee..ebc7574333 100644 --- a/src/axom/quest/detail/DelaunayImpl.hpp +++ b/src/axom/quest/detail/DelaunayImpl.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file DelaunayImpl.hpp * @@ -19,8 +21,6 @@ * - VTK export for visualization */ -#pragma once - namespace axom { namespace quest diff --git a/src/axom/quest/detail/DelaunayInsertionHelper.hpp b/src/axom/quest/detail/DelaunayInsertionHelper.hpp index 52730737c5..c85c2d0442 100644 --- a/src/axom/quest/detail/DelaunayInsertionHelper.hpp +++ b/src/axom/quest/detail/DelaunayInsertionHelper.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file DelaunayInsertionHelper.hpp * @@ -18,8 +20,6 @@ * The helper is reused across insertions to avoid repeated allocations. */ -#pragma once - #include "axom/core.hpp" #include "axom/primal.hpp" #include "axom/slam.hpp" diff --git a/src/axom/quest/detail/DelaunayPointLocation.hpp b/src/axom/quest/detail/DelaunayPointLocation.hpp index c83b738136..836d3b5966 100644 --- a/src/axom/quest/detail/DelaunayPointLocation.hpp +++ b/src/axom/quest/detail/DelaunayPointLocation.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file DelaunayPointLocation.hpp * @@ -15,8 +17,6 @@ * Fallback strategies handle edge cases (cycles, numerical issues). */ -#pragma once - namespace axom { namespace quest diff --git a/src/axom/quest/detail/DelaunayValidation.hpp b/src/axom/quest/detail/DelaunayValidation.hpp index df034e48ba..043ba0ebcf 100644 --- a/src/axom/quest/detail/DelaunayValidation.hpp +++ b/src/axom/quest/detail/DelaunayValidation.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file DelaunayValidation.hpp * @@ -22,8 +24,6 @@ * - Boundary coordinate tolerance computation */ -#pragma once - namespace axom { namespace quest diff --git a/src/axom/quest/detail/MarchingCubesSingleDomain.hpp b/src/axom/quest/detail/MarchingCubesSingleDomain.hpp index 64adec944a..7ecb9d4b32 100644 --- a/src/axom/quest/detail/MarchingCubesSingleDomain.hpp +++ b/src/axom/quest/detail/MarchingCubesSingleDomain.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file MarchingCubesSingleDomain.hpp * @@ -11,8 +13,6 @@ * compute isocontour from a scalar field in a blueprint mesh. */ -#pragma once - #include "axom/config.hpp" // Implementation requires Conduit. diff --git a/src/axom/quest/detail/inout/BlockData.hpp b/src/axom/quest/detail/inout/BlockData.hpp index 5a42759db6..b766989577 100644 --- a/src/axom/quest/detail/inout/BlockData.hpp +++ b/src/axom/quest/detail/inout/BlockData.hpp @@ -4,14 +4,14 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file BlockData.hpp * * \brief Defines helper classes for data associated with InOutOctree blocks. */ -#pragma once - #include "axom/core.hpp" #include "axom/slic.hpp" diff --git a/src/axom/quest/detail/inout/InOutOctreeMeshDumper.hpp b/src/axom/quest/detail/inout/InOutOctreeMeshDumper.hpp index d38071c5ca..52ed4b0fe8 100644 --- a/src/axom/quest/detail/inout/InOutOctreeMeshDumper.hpp +++ b/src/axom/quest/detail/inout/InOutOctreeMeshDumper.hpp @@ -4,14 +4,14 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file InOutOctreeMeshDumper.hpp * * \brief Defines helper class to write meshes for InOutOctree instances */ -#pragma once - #include "axom/core.hpp" #include "axom/slic.hpp" #include "axom/slam.hpp" diff --git a/src/axom/quest/detail/inout/InOutOctreeStats.hpp b/src/axom/quest/detail/inout/InOutOctreeStats.hpp index 95989db7e2..8fa0f3c7c4 100644 --- a/src/axom/quest/detail/inout/InOutOctreeStats.hpp +++ b/src/axom/quest/detail/inout/InOutOctreeStats.hpp @@ -4,14 +4,14 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file InOutOctreeStats.hpp * * \brief Defines helper class to generate statistics about an InOutOctree. */ -#pragma once - #include "axom/core.hpp" #include "axom/slic.hpp" #include "axom/slam.hpp" diff --git a/src/axom/quest/detail/inout/InOutOctreeValidator.hpp b/src/axom/quest/detail/inout/InOutOctreeValidator.hpp index 6613d61fdb..c2bc123ef8 100644 --- a/src/axom/quest/detail/inout/InOutOctreeValidator.hpp +++ b/src/axom/quest/detail/inout/InOutOctreeValidator.hpp @@ -4,14 +4,14 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file InOutOctreeValidator.hpp * * \brief Defines helper class to validate an InOutOctree instance */ -#pragma once - #include "axom/core.hpp" #include "axom/slic.hpp" #include "axom/slam.hpp" diff --git a/src/axom/quest/detail/inout/MeshWrapper.hpp b/src/axom/quest/detail/inout/MeshWrapper.hpp index b3e8e404d8..d252f7b4ac 100644 --- a/src/axom/quest/detail/inout/MeshWrapper.hpp +++ b/src/axom/quest/detail/inout/MeshWrapper.hpp @@ -4,14 +4,14 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file MeshWrapper.hpp * * \brief Defines a templated mesh wrapper class for the InOutOctree. */ -#pragma once - #include "axom/core.hpp" #include "axom/slic.hpp" #include "axom/slam.hpp" diff --git a/src/axom/quest/detail/marching_cubes_lookup.hpp b/src/axom/quest/detail/marching_cubes_lookup.hpp index 5c068183d7..f68418f0d7 100644 --- a/src/axom/quest/detail/marching_cubes_lookup.hpp +++ b/src/axom/quest/detail/marching_cubes_lookup.hpp @@ -4,14 +4,14 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! @file Static look-up tables for MarchingCubesImpl. */ // 2D case table // clang-format off -#pragma once - #ifdef _MC_LOOKUP_CASES2D /*! @brief Look-up table in 2D. diff --git a/src/axom/quest/detail/shaping/InOutSampler.hpp b/src/axom/quest/detail/shaping/InOutSampler.hpp index f48873ad2d..a673e3ce3e 100644 --- a/src/axom/quest/detail/shaping/InOutSampler.hpp +++ b/src/axom/quest/detail/shaping/InOutSampler.hpp @@ -4,14 +4,14 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file InOutSampler.hpp * * \brief Helper class for sampling-based shaping queries using the InOutOctree */ -#pragma once - #include "axom/config.hpp" #include "axom/core.hpp" #include "axom/slic.hpp" diff --git a/src/axom/quest/detail/shaping/PrimitiveSampler.hpp b/src/axom/quest/detail/shaping/PrimitiveSampler.hpp index 4736a02068..1e220fc230 100644 --- a/src/axom/quest/detail/shaping/PrimitiveSampler.hpp +++ b/src/axom/quest/detail/shaping/PrimitiveSampler.hpp @@ -4,14 +4,14 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file PrimitiveSampler.hpp * * \brief Helper class for sampling-based shaping queries using primal geometric primitives */ -#pragma once - #include "axom/config.hpp" #include "axom/core.hpp" #include "axom/slic.hpp" diff --git a/src/axom/quest/detail/shaping/shaping_helpers.hpp b/src/axom/quest/detail/shaping/shaping_helpers.hpp index 04d22f595f..e39f40e42d 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers.hpp +++ b/src/axom/quest/detail/shaping/shaping_helpers.hpp @@ -4,14 +4,14 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file shaping_helpers.hpp * * \brief Free-standing helper functions in support of shaping query */ -#pragma once - #include "axom/config.hpp" #include "axom/core.hpp" #include "axom/primal.hpp" diff --git a/src/axom/quest/interface/c_fortran/typesQUEST.h b/src/axom/quest/interface/c_fortran/typesQUEST.h index 8bc56e1772..16faa1ca43 100644 --- a/src/axom/quest/interface/c_fortran/typesQUEST.h +++ b/src/axom/quest/interface/c_fortran/typesQUEST.h @@ -6,10 +6,11 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -// For C users and C++ implementation #pragma once +// For C users and C++ implementation + // Shared with other Shroud wrapped libraries #ifndef SHROUD_SHARED_H #define SHROUD_SHARED_H diff --git a/src/axom/quest/interface/c_fortran/wrapQUEST.h b/src/axom/quest/interface/c_fortran/wrapQUEST.h index c554127b79..58e1eb9a54 100644 --- a/src/axom/quest/interface/c_fortran/wrapQUEST.h +++ b/src/axom/quest/interface/c_fortran/wrapQUEST.h @@ -6,14 +6,15 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + +#pragma once + /** * \file wrapQUEST.h * \brief Shroud generated wrapper for quest namespace */ // For C users and C++ implementation -#pragma once - #ifdef AXOM_USE_MPI #include "mpi.h" #endif diff --git a/src/axom/quest/interface/python/pyQUESTmodule.hpp b/src/axom/quest/interface/python/pyQUESTmodule.hpp index bd4fc05cd2..483cc09564 100644 --- a/src/axom/quest/interface/python/pyQUESTmodule.hpp +++ b/src/axom/quest/interface/python/pyQUESTmodule.hpp @@ -6,6 +6,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + #pragma once #define PY_SSIZE_T_CLEAN diff --git a/src/axom/sidre/core/AttrValues.hpp b/src/axom/sidre/core/AttrValues.hpp index ab2df74669..99f43ff71b 100644 --- a/src/axom/sidre/core/AttrValues.hpp +++ b/src/axom/sidre/core/AttrValues.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! ****************************************************************************** * @@ -27,8 +29,6 @@ #include "axom/sidre/core/Attribute.hpp" #include "axom/sidre/core/SidreTypes.hpp" -#pragma once - namespace axom { namespace sidre diff --git a/src/axom/sidre/core/Attribute.hpp b/src/axom/sidre/core/Attribute.hpp index 22745f38f6..ffce90e48b 100644 --- a/src/axom/sidre/core/Attribute.hpp +++ b/src/axom/sidre/core/Attribute.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! ****************************************************************************** * @@ -14,8 +16,6 @@ ****************************************************************************** */ -#pragma once - // Standard C++ headers #include diff --git a/src/axom/sidre/core/Buffer.hpp b/src/axom/sidre/core/Buffer.hpp index 3b389930c5..9284e4221e 100644 --- a/src/axom/sidre/core/Buffer.hpp +++ b/src/axom/sidre/core/Buffer.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! ****************************************************************************** * @@ -14,8 +16,6 @@ ****************************************************************************** */ -#pragma once - // Standard C++ headers #include diff --git a/src/axom/sidre/core/ConduitMemory.hpp b/src/axom/sidre/core/ConduitMemory.hpp index 8d3eed971e..723b05110c 100644 --- a/src/axom/sidre/core/ConduitMemory.hpp +++ b/src/axom/sidre/core/ConduitMemory.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! ****************************************************************************** * @@ -14,8 +16,6 @@ ****************************************************************************** */ -#pragma once - // Standard C++ headers #include #include diff --git a/src/axom/sidre/core/DataStore.hpp b/src/axom/sidre/core/DataStore.hpp index a2b4af7758..91ddd844bc 100644 --- a/src/axom/sidre/core/DataStore.hpp +++ b/src/axom/sidre/core/DataStore.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! ****************************************************************************** * @@ -14,8 +16,6 @@ ****************************************************************************** */ -#pragma once - // Standard C++ headers #include #include diff --git a/src/axom/sidre/core/Group.hpp b/src/axom/sidre/core/Group.hpp index 065c9b070f..7c5a07f76e 100644 --- a/src/axom/sidre/core/Group.hpp +++ b/src/axom/sidre/core/Group.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! ****************************************************************************** * @@ -14,8 +16,6 @@ ****************************************************************************** */ -#pragma once - // axom headers #include "axom/config.hpp" #include "axom/core/Array.hpp" diff --git a/src/axom/sidre/core/SidreDataTypeIds.h b/src/axom/sidre/core/SidreDataTypeIds.h index c4281d8203..160bf7c873 100644 --- a/src/axom/sidre/core/SidreDataTypeIds.h +++ b/src/axom/sidre/core/SidreDataTypeIds.h @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file SiderDataTypeIds.h * @@ -17,8 +19,6 @@ * since it will be included from a C file. */ -#pragma once - // Libraries and other axom headers #include "conduit.h" diff --git a/src/axom/sidre/core/SidreTypes.hpp b/src/axom/sidre/core/SidreTypes.hpp index 80d4fec6f3..79d5d235f4 100644 --- a/src/axom/sidre/core/SidreTypes.hpp +++ b/src/axom/sidre/core/SidreTypes.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file SidreTypes.hpp * @@ -11,8 +13,6 @@ * */ -#pragma once - #include "SidreDataTypeIds.h" #include "conduit.hpp" #include "axom/core/Types.hpp" diff --git a/src/axom/sidre/core/View.hpp b/src/axom/sidre/core/View.hpp index 336dc27531..1cff7eaac5 100644 --- a/src/axom/sidre/core/View.hpp +++ b/src/axom/sidre/core/View.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! ****************************************************************************** * @@ -14,8 +16,6 @@ ****************************************************************************** */ -#pragma once - // Standard C++ headers #include #include diff --git a/src/axom/sidre/examples/lulesh2/lulesh.h b/src/axom/sidre/examples/lulesh2/lulesh.h index e5d0e56b81..26dd1caec4 100644 --- a/src/axom/sidre/examples/lulesh2/lulesh.h +++ b/src/axom/sidre/examples/lulesh2/lulesh.h @@ -4,11 +4,10 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once // OpenMP will be compiled in if this flag is set to 1 AND the compiler beging // used supports it (i.e. the _OPENMP symbol is defined) -#pragma once - #define USE_OMP 1 #include "axom/sidre.hpp" diff --git a/src/axom/sidre/examples/lulesh2/lulesh_tuple.h b/src/axom/sidre/examples/lulesh2/lulesh_tuple.h index c210f9a5bb..a454298be2 100644 --- a/src/axom/sidre/examples/lulesh2/lulesh_tuple.h +++ b/src/axom/sidre/examples/lulesh2/lulesh_tuple.h @@ -4,7 +4,6 @@ // // SPDX-License-Identifier: (BSD-3-Clause) - #pragma once #include "axom/config.hpp" diff --git a/src/axom/sidre/interface/SidreTypes.h b/src/axom/sidre/interface/SidreTypes.h index 8ee546bfbe..2865badf08 100644 --- a/src/axom/sidre/interface/SidreTypes.h +++ b/src/axom/sidre/interface/SidreTypes.h @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file SidreTypes.h * @@ -17,8 +19,6 @@ * It is part of the C wrapper. */ -#pragma once - // Axom includes #include "axom/config.hpp" #include "axom/sidre/core/SidreDataTypeIds.h" diff --git a/src/axom/sidre/interface/c_fortran/typesSidre.h b/src/axom/sidre/interface/c_fortran/typesSidre.h index 7f3854af65..bb194062c8 100644 --- a/src/axom/sidre/interface/c_fortran/typesSidre.h +++ b/src/axom/sidre/interface/c_fortran/typesSidre.h @@ -6,10 +6,11 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -// For C users and C++ implementation #pragma once +// For C users and C++ implementation + // Shared with other Shroud wrapped libraries #ifndef SHROUD_SHARED_H #define SHROUD_SHARED_H diff --git a/src/axom/sidre/interface/c_fortran/wrapBuffer.h b/src/axom/sidre/interface/c_fortran/wrapBuffer.h index 6db6c00405..caed34241b 100644 --- a/src/axom/sidre/interface/c_fortran/wrapBuffer.h +++ b/src/axom/sidre/interface/c_fortran/wrapBuffer.h @@ -6,14 +6,15 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + +#pragma once + /** * \file wrapBuffer.h * \brief Shroud generated wrapper for Buffer class */ // For C users and C++ implementation -#pragma once - #include "wrapSidre.h" #include "axom/sidre/interface/SidreTypes.h" #ifdef __cplusplus diff --git a/src/axom/sidre/interface/c_fortran/wrapDataStore.h b/src/axom/sidre/interface/c_fortran/wrapDataStore.h index 2e592be068..ba5f9d72b7 100644 --- a/src/axom/sidre/interface/c_fortran/wrapDataStore.h +++ b/src/axom/sidre/interface/c_fortran/wrapDataStore.h @@ -6,14 +6,15 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + +#pragma once + /** * \file wrapDataStore.h * \brief Shroud generated wrapper for DataStore class */ // For C users and C++ implementation -#pragma once - #include "wrapSidre.h" #include "axom/sidre/interface/SidreTypes.h" #ifdef AXOM_USE_MPI diff --git a/src/axom/sidre/interface/c_fortran/wrapGroup.h b/src/axom/sidre/interface/c_fortran/wrapGroup.h index 614fcd71e9..295bebfcb4 100644 --- a/src/axom/sidre/interface/c_fortran/wrapGroup.h +++ b/src/axom/sidre/interface/c_fortran/wrapGroup.h @@ -6,14 +6,15 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + +#pragma once + /** * \file wrapGroup.h * \brief Shroud generated wrapper for Group class */ // For C users and C++ implementation -#pragma once - #include "wrapSidre.h" #include "axom/sidre/interface/SidreTypes.h" #ifdef __cplusplus diff --git a/src/axom/sidre/interface/c_fortran/wrapSidre.h b/src/axom/sidre/interface/c_fortran/wrapSidre.h index 18785d7c6d..744ec2f58b 100644 --- a/src/axom/sidre/interface/c_fortran/wrapSidre.h +++ b/src/axom/sidre/interface/c_fortran/wrapSidre.h @@ -6,14 +6,15 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + +#pragma once + /** * \file wrapSidre.h * \brief Shroud generated wrapper for sidre namespace */ // For C users and C++ implementation -#pragma once - #ifndef __cplusplus #include #endif diff --git a/src/axom/sidre/interface/c_fortran/wrapView.h b/src/axom/sidre/interface/c_fortran/wrapView.h index 384d9f5e5c..8a55ae52b4 100644 --- a/src/axom/sidre/interface/c_fortran/wrapView.h +++ b/src/axom/sidre/interface/c_fortran/wrapView.h @@ -6,14 +6,15 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + +#pragma once + /** * \file wrapView.h * \brief Shroud generated wrapper for View class */ // For C users and C++ implementation -#pragma once - #include "wrapSidre.h" #include "axom/sidre/interface/SidreTypes.h" #ifdef __cplusplus diff --git a/src/axom/sidre/interface/sidre.h b/src/axom/sidre/interface/sidre.h index f51860d124..f23228976a 100644 --- a/src/axom/sidre/interface/sidre.h +++ b/src/axom/sidre/interface/sidre.h @@ -4,14 +4,14 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * @name sidre.h * * C header file */ -#pragma once - #include "axom/sidre/interface/SidreTypes.h" #include "axom/sidre/interface/c_fortran/wrapSidre.h" #include "axom/sidre/interface/c_fortran/wrapDataStore.h" diff --git a/src/axom/sidre/spio/IOBaton.hpp b/src/axom/sidre/spio/IOBaton.hpp index e60d04accd..5c079543c1 100644 --- a/src/axom/sidre/spio/IOBaton.hpp +++ b/src/axom/sidre/spio/IOBaton.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! ****************************************************************************** * @@ -14,8 +16,6 @@ ****************************************************************************** */ -#pragma once - #include "mpi.h" // Other axom headers diff --git a/src/axom/sidre/spio/IOManager.hpp b/src/axom/sidre/spio/IOManager.hpp index 3089a46598..54514be0ff 100644 --- a/src/axom/sidre/spio/IOManager.hpp +++ b/src/axom/sidre/spio/IOManager.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! ****************************************************************************** * @@ -14,8 +16,6 @@ ****************************************************************************** */ -#pragma once - // Other axom headers #include "axom/config.hpp" #include "axom/core/Macros.hpp" diff --git a/src/axom/sidre/spio/interface/c_fortran/typesSPIO.h b/src/axom/sidre/spio/interface/c_fortran/typesSPIO.h index 86532da94c..bfcf84686f 100644 --- a/src/axom/sidre/spio/interface/c_fortran/typesSPIO.h +++ b/src/axom/sidre/spio/interface/c_fortran/typesSPIO.h @@ -6,10 +6,11 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -// For C users and C++ implementation #pragma once +// For C users and C++ implementation + // Shared with other Shroud wrapped libraries #ifndef SHROUD_SHARED_H #define SHROUD_SHARED_H diff --git a/src/axom/sidre/spio/interface/c_fortran/wrapIOManager.h b/src/axom/sidre/spio/interface/c_fortran/wrapIOManager.h index 455b5be769..2698c6279c 100644 --- a/src/axom/sidre/spio/interface/c_fortran/wrapIOManager.h +++ b/src/axom/sidre/spio/interface/c_fortran/wrapIOManager.h @@ -6,14 +6,15 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + +#pragma once + /** * \file wrapIOManager.h * \brief Shroud generated wrapper for IOManager class */ // For C users and C++ implementation -#pragma once - #include "axom/sidre/interface/c_fortran/wrapGroup.h" #include "axom/sidre/interface/c_fortran/wrapDataStore.h" #include "mpi.h" diff --git a/src/axom/sidre/tests/spio/spio_parallel.hpp b/src/axom/sidre/tests/spio/spio_parallel.hpp index 9196e9fbd9..f703f9b239 100644 --- a/src/axom/sidre/tests/spio/spio_parallel.hpp +++ b/src/axom/sidre/tests/spio/spio_parallel.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /* An excerpt from this test file is used in the Sidre Sphinx documentation, * denoted by the comment strings * @@ -17,8 +19,6 @@ * prepended with an underscore. */ -#pragma once - #include "gtest/gtest.h" // _parallel_io_headers_start diff --git a/src/axom/slam/BitSet.hpp b/src/axom/slam/BitSet.hpp index b945f78340..f11068b538 100644 --- a/src/axom/slam/BitSet.hpp +++ b/src/axom/slam/BitSet.hpp @@ -4,14 +4,14 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file BitSet.hpp * * \brief Contains a BitSet class for manipulating ordered sequences of bits. */ -#pragma once - #include "axom/config.hpp" #include "axom/core/Array.hpp" #include "axom/core/execution/atomics.hpp" diff --git a/src/axom/slam/BivariateMap.hpp b/src/axom/slam/BivariateMap.hpp index fb10aa0ce2..981490007c 100644 --- a/src/axom/slam/BivariateMap.hpp +++ b/src/axom/slam/BivariateMap.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file BivariateMap.hpp * @@ -11,8 +13,6 @@ * */ -#pragma once - #include "axom/slam/Map.hpp" #include "axom/slam/Relation.hpp" #include "axom/slam/BivariateSet.hpp" diff --git a/src/axom/slam/DynamicConstantRelation.hpp b/src/axom/slam/DynamicConstantRelation.hpp index 302bed59fc..c25b693ddd 100644 --- a/src/axom/slam/DynamicConstantRelation.hpp +++ b/src/axom/slam/DynamicConstantRelation.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file DynamicConstantRelation.hpp * @@ -15,8 +17,6 @@ * This relation is dynamic; the related entities can change at runtime. */ -#pragma once - #include "axom/config.hpp" #include "axom/slic.hpp" diff --git a/src/axom/slam/DynamicSet.hpp b/src/axom/slam/DynamicSet.hpp index 59235c5970..7daa3f1319 100644 --- a/src/axom/slam/DynamicSet.hpp +++ b/src/axom/slam/DynamicSet.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file DynamicSet.hpp * @@ -11,8 +13,6 @@ * at runtime */ -#pragma once - #include "axom/config.hpp" #include "axom/core/IteratorBase.hpp" #include "axom/slam/OrderedSet.hpp" diff --git a/src/axom/slam/DynamicVariableRelation.hpp b/src/axom/slam/DynamicVariableRelation.hpp index 2ecf52aa5e..f91817f9fb 100644 --- a/src/axom/slam/DynamicVariableRelation.hpp +++ b/src/axom/slam/DynamicVariableRelation.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file DynamicVariableRelation.hpp * @@ -13,8 +15,6 @@ * at runtime. */ -#pragma once - #include "axom/config.hpp" #include "axom/slic.hpp" diff --git a/src/axom/slam/IndirectionSet.hpp b/src/axom/slam/IndirectionSet.hpp index 0923494a18..e2f13908b5 100644 --- a/src/axom/slam/IndirectionSet.hpp +++ b/src/axom/slam/IndirectionSet.hpp @@ -4,14 +4,14 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file IndirectionSet.hpp * * \brief Defines some alias templates for OrderedSets with indirection */ -#pragma once - #include #include diff --git a/src/axom/slam/Map.hpp b/src/axom/slam/Map.hpp index 2cf95f93c2..37975a5212 100644 --- a/src/axom/slam/Map.hpp +++ b/src/axom/slam/Map.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file Map.hpp * @@ -11,8 +13,6 @@ * */ -#pragma once - #include #include #include diff --git a/src/axom/slam/MapBase.hpp b/src/axom/slam/MapBase.hpp index 0ea37d5785..fed691beb5 100644 --- a/src/axom/slam/MapBase.hpp +++ b/src/axom/slam/MapBase.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file MapBase.hpp * @@ -11,8 +13,6 @@ * */ -#pragma once - #include "axom/core/Macros.hpp" #include "axom/core/Types.hpp" diff --git a/src/axom/slam/ModularInt.hpp b/src/axom/slam/ModularInt.hpp index 433891e1b4..a96f74ea79 100644 --- a/src/axom/slam/ModularInt.hpp +++ b/src/axom/slam/ModularInt.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file ModularInt.hpp * @@ -14,8 +16,6 @@ * */ -#pragma once - #include "axom/slic/interface/slic.hpp" #include "axom/slam/policies/SizePolicies.hpp" #include "axom/core/Macros.hpp" diff --git a/src/axom/slam/NullSet.hpp b/src/axom/slam/NullSet.hpp index c75d98f5b9..8ec8f65bd8 100644 --- a/src/axom/slam/NullSet.hpp +++ b/src/axom/slam/NullSet.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file NullSet.hpp * @@ -11,8 +13,6 @@ * */ -#pragma once - #include "axom/slic.hpp" #include "axom/slam/Set.hpp" diff --git a/src/axom/slam/OrderedSet.hpp b/src/axom/slam/OrderedSet.hpp index 99e62ee1d2..fef6113047 100644 --- a/src/axom/slam/OrderedSet.hpp +++ b/src/axom/slam/OrderedSet.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file OrderedSet.hpp * @@ -13,8 +15,6 @@ * */ -#pragma once - #include "axom/config.hpp" #include "axom/core/utilities/Utilities.hpp" #include "axom/slic.hpp" diff --git a/src/axom/slam/ProductSet.hpp b/src/axom/slam/ProductSet.hpp index 04d15889bb..609d0130dd 100644 --- a/src/axom/slam/ProductSet.hpp +++ b/src/axom/slam/ProductSet.hpp @@ -4,14 +4,14 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file ProductSet.hpp * * \brief Basic API for a SLAM Cartesian product set */ -#pragma once - #include "axom/core/IteratorBase.hpp" #include "axom/slam/BivariateSet.hpp" #include "axom/slam/RangeSet.hpp" diff --git a/src/axom/slam/RangeSet.hpp b/src/axom/slam/RangeSet.hpp index 46dfd017f7..132963c668 100644 --- a/src/axom/slam/RangeSet.hpp +++ b/src/axom/slam/RangeSet.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file RangeSet.hpp * @@ -11,8 +13,6 @@ * */ -#pragma once - #include "axom/slam/OrderedSet.hpp" namespace axom diff --git a/src/axom/slam/Relation.hpp b/src/axom/slam/Relation.hpp index 39247f83fd..00d2a1a4f1 100644 --- a/src/axom/slam/Relation.hpp +++ b/src/axom/slam/Relation.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file Relation.hpp * @@ -11,8 +13,6 @@ * */ -#pragma once - #include #include "axom/slam/Set.hpp" diff --git a/src/axom/slam/Set.hpp b/src/axom/slam/Set.hpp index 94642a48e7..c5ff3e3f4a 100644 --- a/src/axom/slam/Set.hpp +++ b/src/axom/slam/Set.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file Set.hpp * @@ -11,8 +13,6 @@ * */ -#pragma once - #include #include #include // for std::common_type diff --git a/src/axom/slam/StaticRelation.hpp b/src/axom/slam/StaticRelation.hpp index 9529e2c670..60ce773493 100644 --- a/src/axom/slam/StaticRelation.hpp +++ b/src/axom/slam/StaticRelation.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file StaticRelation.hpp * @@ -12,8 +14,6 @@ * */ -#pragma once - #include "axom/config.hpp" #include "axom/slam/policies/SizePolicies.hpp" diff --git a/src/axom/slam/SubMap.hpp b/src/axom/slam/SubMap.hpp index 50b2805ac6..656c2a8baa 100644 --- a/src/axom/slam/SubMap.hpp +++ b/src/axom/slam/SubMap.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file SubMap.hpp * @@ -11,8 +13,6 @@ * */ -#pragma once - #include "axom/slic.hpp" #include "axom/slam/Map.hpp" diff --git a/src/axom/slam/Utilities.hpp b/src/axom/slam/Utilities.hpp index 32aecfc27c..bd1b51cc97 100644 --- a/src/axom/slam/Utilities.hpp +++ b/src/axom/slam/Utilities.hpp @@ -4,12 +4,12 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file * \brief A few utility functions used by the SLAM component. */ -#pragma once - #include "axom/core.hpp" #include "axom/fmt.hpp" diff --git a/src/axom/slam/examples/tinyHydro/HydroC.hpp b/src/axom/slam/examples/tinyHydro/HydroC.hpp index a52be895ad..4316884d07 100644 --- a/src/axom/slam/examples/tinyHydro/HydroC.hpp +++ b/src/axom/slam/examples/tinyHydro/HydroC.hpp @@ -4,6 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once // Hydro driver // Fri Mar 27 14:14:28 PDT 2015 @@ -16,8 +17,6 @@ // allocation of all the memory we ever need at problem start, and // explicit deletes when the hydro object is destroyed. -#pragma once - #include "State.hpp" #include "TinyHydroTypes.hpp" diff --git a/src/axom/slam/examples/tinyHydro/Part.hpp b/src/axom/slam/examples/tinyHydro/Part.hpp index dfb799869f..31e4ee12d7 100644 --- a/src/axom/slam/examples/tinyHydro/Part.hpp +++ b/src/axom/slam/examples/tinyHydro/Part.hpp @@ -4,11 +4,10 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once // Part class holds the material data for a single material. -#pragma once - #include #include "TinyHydroTypes.hpp" diff --git a/src/axom/slam/examples/tinyHydro/PolygonMeshXY.hpp b/src/axom/slam/examples/tinyHydro/PolygonMeshXY.hpp index 6c7770a318..602ca6afd2 100644 --- a/src/axom/slam/examples/tinyHydro/PolygonMeshXY.hpp +++ b/src/axom/slam/examples/tinyHydro/PolygonMeshXY.hpp @@ -4,11 +4,11 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + // arbitrary mesh of polygons in XY geom // Thu Mar 26 09:38:50 PDT 2015 -#pragma once - #include "VectorXY.hpp" #include "TinyHydroTypes.hpp" diff --git a/src/axom/slam/examples/tinyHydro/State.hpp b/src/axom/slam/examples/tinyHydro/State.hpp index f2cb41a5ab..c4fc37b5e9 100644 --- a/src/axom/slam/examples/tinyHydro/State.hpp +++ b/src/axom/slam/examples/tinyHydro/State.hpp @@ -4,13 +4,13 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + // State class that holds all the material data. In the // multi-material world, this will mostly be a vector of Part structs // that hold the data, plus a few functions to do mesh sums and // averages of quantities. -#pragma once - #include "axom/slam.hpp" #include "VectorXY.hpp" diff --git a/src/axom/slam/examples/tinyHydro/TinyHydroTypes.hpp b/src/axom/slam/examples/tinyHydro/TinyHydroTypes.hpp index 59230a8f2b..5125058d30 100644 --- a/src/axom/slam/examples/tinyHydro/TinyHydroTypes.hpp +++ b/src/axom/slam/examples/tinyHydro/TinyHydroTypes.hpp @@ -4,10 +4,9 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -// Part class holds the material data for a single material. - #pragma once +// Part class holds the material data for a single material. #include "VectorXY.hpp" diff --git a/src/axom/slam/examples/tinyHydro/VectorXY.hpp b/src/axom/slam/examples/tinyHydro/VectorXY.hpp index 8fc90668c2..494a525bf6 100644 --- a/src/axom/slam/examples/tinyHydro/VectorXY.hpp +++ b/src/axom/slam/examples/tinyHydro/VectorXY.hpp @@ -4,12 +4,12 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + // 2D vector for XY geometry // Fri Nov 21 10:50:53 PST 2014 #include -#pragma once - namespace tinyHydro { class VectorXY diff --git a/src/axom/slam/mesh_struct/IA.hpp b/src/axom/slam/mesh_struct/IA.hpp index 520e0c38a6..79c0ef1f5a 100644 --- a/src/axom/slam/mesh_struct/IA.hpp +++ b/src/axom/slam/mesh_struct/IA.hpp @@ -4,14 +4,14 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file IA.hpp * * \brief Contains the header information of IA class */ -#pragma once - #include "axom/config.hpp" #include "axom/core.hpp" #include "axom/slic.hpp" diff --git a/src/axom/slam/policies/CardinalityPolicies.hpp b/src/axom/slam/policies/CardinalityPolicies.hpp index 0a37294cde..548cff02b7 100644 --- a/src/axom/slam/policies/CardinalityPolicies.hpp +++ b/src/axom/slam/policies/CardinalityPolicies.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file CardinalityPolicies.hpp * @@ -41,8 +43,6 @@ * */ -#pragma once - #include "axom/config.hpp" #include "axom/core/Macros.hpp" diff --git a/src/axom/slam/policies/IndirectionPolicies.hpp b/src/axom/slam/policies/IndirectionPolicies.hpp index 754c10c7d6..c63a9eb1cb 100644 --- a/src/axom/slam/policies/IndirectionPolicies.hpp +++ b/src/axom/slam/policies/IndirectionPolicies.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file IndirectionPolicies.hpp * @@ -30,8 +32,6 @@ * allocating/deallocating their own memory */ -#pragma once - #include "axom/core/Macros.hpp" #include "axom/core/Array.hpp" #include "axom/core/NumericLimits.hpp" diff --git a/src/axom/slam/policies/OffsetPolicies.hpp b/src/axom/slam/policies/OffsetPolicies.hpp index aabd336287..43306e3ac0 100644 --- a/src/axom/slam/policies/OffsetPolicies.hpp +++ b/src/axom/slam/policies/OffsetPolicies.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file OffsetPolicies.hpp * @@ -25,8 +27,6 @@ * The policies below add only the named `offset()` accessor and the DEFAULT_VALUE member. */ -#pragma once - #include "axom/core/Macros.hpp" #include "axom/slam/policies/ValuePolicies.hpp" diff --git a/src/axom/slam/policies/SizePolicies.hpp b/src/axom/slam/policies/SizePolicies.hpp index 97970a0360..bc500e74e3 100644 --- a/src/axom/slam/policies/SizePolicies.hpp +++ b/src/axom/slam/policies/SizePolicies.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file SizePolicies.hpp * @@ -24,8 +26,6 @@ * The scalar policies below add only the named `size()` accessor, `empty()`, and the DEFAULT_VALUE member. */ -#pragma once - #include "axom/core/Macros.hpp" #include "axom/slic.hpp" #include "axom/slam/policies/ValuePolicies.hpp" diff --git a/src/axom/slam/policies/StridePolicies.hpp b/src/axom/slam/policies/StridePolicies.hpp index d2b03af077..3b69a1c8a1 100644 --- a/src/axom/slam/policies/StridePolicies.hpp +++ b/src/axom/slam/policies/StridePolicies.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file StridePolicies.hpp * @@ -29,8 +31,6 @@ * MultiDimStride is a separate, inherently multi-dimensional policy and is unaffected. */ -#pragma once - #include "axom/core/Macros.hpp" #include "axom/core/StackArray.hpp" #include "axom/slam/policies/ValuePolicies.hpp" diff --git a/src/axom/slam/policies/SubsettingPolicies.hpp b/src/axom/slam/policies/SubsettingPolicies.hpp index 87de28b587..8eeabcb6bf 100644 --- a/src/axom/slam/policies/SubsettingPolicies.hpp +++ b/src/axom/slam/policies/SubsettingPolicies.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file SubsettingPolicies.hpp * @@ -20,8 +22,6 @@ * * operator(): IntType -- alternate accessor for indirection */ -#pragma once - #include "axom/config.hpp" #include "axom/core/Macros.hpp" diff --git a/src/axom/slic/core/LogStream.hpp b/src/axom/slic/core/LogStream.hpp index 0412c49313..3cbdbbbc0f 100644 --- a/src/axom/slic/core/LogStream.hpp +++ b/src/axom/slic/core/LogStream.hpp @@ -4,13 +4,13 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file LogStream.hpp * */ -#pragma once - #include "axom/slic/core/MessageLevel.hpp" #include "axom/core/Macros.hpp" diff --git a/src/axom/slic/core/LogStreamStatusMonitor.hpp b/src/axom/slic/core/LogStreamStatusMonitor.hpp index c19da7309d..62791ddfba 100644 --- a/src/axom/slic/core/LogStreamStatusMonitor.hpp +++ b/src/axom/slic/core/LogStreamStatusMonitor.hpp @@ -4,13 +4,13 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file LogStreamStatusMonitor.hpp * */ -#pragma once - #include #include "axom/slic/core/LogStream.hpp" diff --git a/src/axom/slic/core/Logger.hpp b/src/axom/slic/core/Logger.hpp index 16a5e0dff8..18a3d4ce2a 100644 --- a/src/axom/slic/core/Logger.hpp +++ b/src/axom/slic/core/Logger.hpp @@ -4,12 +4,12 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file Logger.hpp */ -#pragma once - #include "axom/slic/core/LogStreamStatusMonitor.hpp" #include "axom/slic/core/MessageLevel.hpp" diff --git a/src/axom/slic/core/MessageLevel.hpp b/src/axom/slic/core/MessageLevel.hpp index e114b55a8a..915251dbbc 100644 --- a/src/axom/slic/core/MessageLevel.hpp +++ b/src/axom/slic/core/MessageLevel.hpp @@ -4,13 +4,13 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file MessageLevel.h * */ -#pragma once - #include namespace axom diff --git a/src/axom/slic/core/SimpleLogger.hpp b/src/axom/slic/core/SimpleLogger.hpp index 10e4ec2e70..3433f145b4 100644 --- a/src/axom/slic/core/SimpleLogger.hpp +++ b/src/axom/slic/core/SimpleLogger.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file SimpleLogger.hpp * @@ -11,8 +13,6 @@ * */ -#pragma once - // Other axom headers #include "axom/config.hpp" #include "axom/core/Macros.hpp" diff --git a/src/axom/slic/interface/c_fortran/typesSLIC.h b/src/axom/slic/interface/c_fortran/typesSLIC.h index 777e5a6a35..ed7b2dba57 100644 --- a/src/axom/slic/interface/c_fortran/typesSLIC.h +++ b/src/axom/slic/interface/c_fortran/typesSLIC.h @@ -6,10 +6,11 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -// For C users and C++ implementation #pragma once +// For C users and C++ implementation + #include // Shared with other Shroud wrapped libraries diff --git a/src/axom/slic/interface/c_fortran/wrapGenericOutputStream.h b/src/axom/slic/interface/c_fortran/wrapGenericOutputStream.h index 98d18948b9..20e926121d 100644 --- a/src/axom/slic/interface/c_fortran/wrapGenericOutputStream.h +++ b/src/axom/slic/interface/c_fortran/wrapGenericOutputStream.h @@ -6,14 +6,15 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + +#pragma once + /** * \file wrapGenericOutputStream.h * \brief Shroud generated wrapper for GenericOutputStream class */ // For C users and C++ implementation -#pragma once - #include "typesSLIC.h" // splicer begin class.GenericOutputStream.CXX_declarations diff --git a/src/axom/slic/interface/c_fortran/wrapSLIC.h b/src/axom/slic/interface/c_fortran/wrapSLIC.h index 493faf851e..116e711704 100644 --- a/src/axom/slic/interface/c_fortran/wrapSLIC.h +++ b/src/axom/slic/interface/c_fortran/wrapSLIC.h @@ -6,14 +6,15 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) + +#pragma once + /** * \file wrapSLIC.h * \brief Shroud generated wrapper for slic namespace */ // For C users and C++ implementation -#pragma once - #include "wrapSLIC.h" #ifndef __cplusplus #include diff --git a/src/axom/slic/interface/slic.hpp b/src/axom/slic/interface/slic.hpp index a2ccf33bf2..73cd8fbdb0 100644 --- a/src/axom/slic/interface/slic.hpp +++ b/src/axom/slic/interface/slic.hpp @@ -4,12 +4,12 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file slic.hpp */ -#pragma once - #include "axom/config.hpp" #include "axom/core/memory_management.hpp" #include "axom/slic/core/Logger.hpp" diff --git a/src/axom/slic/streams/GenericOutputStream.hpp b/src/axom/slic/streams/GenericOutputStream.hpp index 375b70b98a..133f717ebe 100644 --- a/src/axom/slic/streams/GenericOutputStream.hpp +++ b/src/axom/slic/streams/GenericOutputStream.hpp @@ -4,13 +4,13 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file GenericOutputStream.hpp * */ -#pragma once - #include "axom/slic/core/LogStream.hpp" #include "axom/core/Macros.hpp" diff --git a/src/axom/slic/streams/LumberjackStream.hpp b/src/axom/slic/streams/LumberjackStream.hpp index f298e7de39..1336af6922 100644 --- a/src/axom/slic/streams/LumberjackStream.hpp +++ b/src/axom/slic/streams/LumberjackStream.hpp @@ -4,13 +4,13 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file LumberjackStream.hpp * */ -#pragma once - #include "axom/slic/core/LogStream.hpp" #include "axom/core/Macros.hpp" diff --git a/src/axom/slic/streams/SynchronizedStream.hpp b/src/axom/slic/streams/SynchronizedStream.hpp index 1dda1b77ae..9e2dd0b01a 100644 --- a/src/axom/slic/streams/SynchronizedStream.hpp +++ b/src/axom/slic/streams/SynchronizedStream.hpp @@ -4,13 +4,13 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file SynchronizedStream.hpp * */ -#pragma once - #include "axom/slic/core/LogStream.hpp" #include "axom/core/Macros.hpp" diff --git a/src/axom/spin/MortonIndex.hpp b/src/axom/spin/MortonIndex.hpp index 63083277ba..134dc988e9 100644 --- a/src/axom/spin/MortonIndex.hpp +++ b/src/axom/spin/MortonIndex.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /*! * \file MortonIndex * @@ -14,8 +16,6 @@ * functor class that can be used as a std::hash for unordered_maps and axom::FlatMap */ -#pragma once - #include "axom/config.hpp" #include "axom/core/Types.hpp" #include "axom/core/Macros.hpp" diff --git a/src/axom/spin/OctreeBase.hpp b/src/axom/spin/OctreeBase.hpp index 9e03a966cf..65e3f2d298 100644 --- a/src/axom/spin/OctreeBase.hpp +++ b/src/axom/spin/OctreeBase.hpp @@ -4,13 +4,13 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file OctreeBase.hpp * \brief Defines templated OctreeBase class and its inner class BlockIndex */ -#pragma once - #include "axom/config.hpp" #include "axom/core/NumericLimits.hpp" #include "axom/slic.hpp" diff --git a/src/axom/spin/OctreeLevel.hpp b/src/axom/spin/OctreeLevel.hpp index d448c0fc41..63365b2562 100644 --- a/src/axom/spin/OctreeLevel.hpp +++ b/src/axom/spin/OctreeLevel.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + /** * \file OctreeLevel.hpp * \brief Defines templated OctreeLevel class @@ -17,8 +19,6 @@ * hash key for its octree blocks. */ -#pragma once - #include "axom/config.hpp" #include "axom/core.hpp" #include "axom/slic.hpp" From b8a524deb18ae00e050ee5442aec3c20b9f847c6 Mon Sep 17 00:00:00 2001 From: Chris White Date: Mon, 29 Jun 2026 14:23:56 -0700 Subject: [PATCH 688/986] put shroud style guards back --- src/axom/quest/interface/c_fortran/typesQUEST.h | 5 ++++- src/axom/quest/interface/c_fortran/wrapQUEST.h | 5 ++++- src/axom/sidre/interface/c_fortran/typesSidre.h | 5 ++++- src/axom/sidre/interface/c_fortran/wrapBuffer.h | 5 ++++- src/axom/sidre/interface/c_fortran/wrapDataStore.h | 5 ++++- src/axom/sidre/interface/c_fortran/wrapGroup.h | 5 ++++- src/axom/sidre/interface/c_fortran/wrapSidre.h | 5 ++++- src/axom/sidre/interface/c_fortran/wrapView.h | 5 ++++- src/axom/sidre/spio/interface/c_fortran/typesSPIO.h | 5 ++++- src/axom/sidre/spio/interface/c_fortran/wrapIOManager.h | 5 ++++- src/axom/slic/interface/c_fortran/typesSLIC.h | 5 ++++- src/axom/slic/interface/c_fortran/wrapGenericOutputStream.h | 5 ++++- src/axom/slic/interface/c_fortran/wrapSLIC.h | 5 ++++- 13 files changed, 52 insertions(+), 13 deletions(-) diff --git a/src/axom/quest/interface/c_fortran/typesQUEST.h b/src/axom/quest/interface/c_fortran/typesQUEST.h index 16faa1ca43..9aae0b530a 100644 --- a/src/axom/quest/interface/c_fortran/typesQUEST.h +++ b/src/axom/quest/interface/c_fortran/typesQUEST.h @@ -7,7 +7,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#pragma once +#ifndef TYPESQUEST_H +#define TYPESQUEST_H // For C users and C++ implementation @@ -47,3 +48,5 @@ void QUEST_SHROUD_memory_destructor(QUEST_SHROUD_capsule_data *cap); #ifdef __cplusplus } #endif + +#endif // TYPESQUEST_H diff --git a/src/axom/quest/interface/c_fortran/wrapQUEST.h b/src/axom/quest/interface/c_fortran/wrapQUEST.h index 58e1eb9a54..ce744d04fb 100644 --- a/src/axom/quest/interface/c_fortran/wrapQUEST.h +++ b/src/axom/quest/interface/c_fortran/wrapQUEST.h @@ -7,7 +7,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#pragma once +#ifndef WRAPQUEST_H +#define WRAPQUEST_H /** * \file wrapQUEST.h @@ -142,3 +143,5 @@ void QUEST_signed_distance_finalize(void); #ifdef __cplusplus } #endif + +#endif // WRAPQUEST_H diff --git a/src/axom/sidre/interface/c_fortran/typesSidre.h b/src/axom/sidre/interface/c_fortran/typesSidre.h index bb194062c8..aa2f2fe77e 100644 --- a/src/axom/sidre/interface/c_fortran/typesSidre.h +++ b/src/axom/sidre/interface/c_fortran/typesSidre.h @@ -7,7 +7,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#pragma once +#ifndef TYPESSIDRE_H +#define TYPESSIDRE_H // For C users and C++ implementation @@ -118,3 +119,5 @@ void SIDRE_SHROUD_memory_destructor(SIDRE_SHROUD_capsule_data *cap); #ifdef __cplusplus } #endif + +#endif // TYPESSIDRE_H diff --git a/src/axom/sidre/interface/c_fortran/wrapBuffer.h b/src/axom/sidre/interface/c_fortran/wrapBuffer.h index caed34241b..8a94292b91 100644 --- a/src/axom/sidre/interface/c_fortran/wrapBuffer.h +++ b/src/axom/sidre/interface/c_fortran/wrapBuffer.h @@ -7,7 +7,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#pragma once +#ifndef WRAPBUFFER_H +#define WRAPBUFFER_H /** * \file wrapBuffer.h @@ -62,3 +63,5 @@ void SIDRE_Buffer_print(const SIDRE_Buffer* self); #ifdef __cplusplus } #endif + +#endif // WRAPBUFFER_H diff --git a/src/axom/sidre/interface/c_fortran/wrapDataStore.h b/src/axom/sidre/interface/c_fortran/wrapDataStore.h index ba5f9d72b7..e7f342af38 100644 --- a/src/axom/sidre/interface/c_fortran/wrapDataStore.h +++ b/src/axom/sidre/interface/c_fortran/wrapDataStore.h @@ -7,7 +7,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#pragma once +#ifndef WRAPDATASTORE_H +#define WRAPDATASTORE_H /** * \file wrapDataStore.h @@ -114,3 +115,5 @@ void SIDRE_DataStore_print(const SIDRE_DataStore *self); #ifdef __cplusplus } #endif + +#endif // WRAPDATASTORE_H diff --git a/src/axom/sidre/interface/c_fortran/wrapGroup.h b/src/axom/sidre/interface/c_fortran/wrapGroup.h index 295bebfcb4..b92c176405 100644 --- a/src/axom/sidre/interface/c_fortran/wrapGroup.h +++ b/src/axom/sidre/interface/c_fortran/wrapGroup.h @@ -7,7 +7,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#pragma once +#ifndef WRAPGROUP_H +#define WRAPGROUP_H /** * \file wrapGroup.h @@ -494,3 +495,5 @@ bool SIDRE_Group_rename_bufferify(SIDRE_Group *self, char *new_name, int SHT_new #ifdef __cplusplus } #endif + +#endif // WRAPGROUP_H diff --git a/src/axom/sidre/interface/c_fortran/wrapSidre.h b/src/axom/sidre/interface/c_fortran/wrapSidre.h index 744ec2f58b..48d97a9b8b 100644 --- a/src/axom/sidre/interface/c_fortran/wrapSidre.h +++ b/src/axom/sidre/interface/c_fortran/wrapSidre.h @@ -7,7 +7,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#pragma once +#ifndef WRAPSIDRE_H +#define WRAPSIDRE_H /** * \file wrapSidre.h @@ -65,3 +66,5 @@ int SIDRE_get_malloc_allocator_id(void); #ifdef __cplusplus } #endif + +#endif // WRAPSIDRE_H diff --git a/src/axom/sidre/interface/c_fortran/wrapView.h b/src/axom/sidre/interface/c_fortran/wrapView.h index 8a55ae52b4..174285649a 100644 --- a/src/axom/sidre/interface/c_fortran/wrapView.h +++ b/src/axom/sidre/interface/c_fortran/wrapView.h @@ -7,7 +7,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#pragma once +#ifndef WRAPVIEW_H +#define WRAPVIEW_H /** * \file wrapView.h @@ -193,3 +194,5 @@ bool SIDRE_View_rename_bufferify(SIDRE_View *self, char *new_name, int SHT_new_n #ifdef __cplusplus } #endif + +#endif // WRAPVIEW_H diff --git a/src/axom/sidre/spio/interface/c_fortran/typesSPIO.h b/src/axom/sidre/spio/interface/c_fortran/typesSPIO.h index bfcf84686f..ec533ea694 100644 --- a/src/axom/sidre/spio/interface/c_fortran/typesSPIO.h +++ b/src/axom/sidre/spio/interface/c_fortran/typesSPIO.h @@ -7,7 +7,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#pragma once +#ifndef TYPESSPIO_H +#define TYPESSPIO_H // For C users and C++ implementation @@ -66,3 +67,5 @@ void SPIO_SHROUD_memory_destructor(SPIO_SHROUD_capsule_data *cap); #ifdef __cplusplus } #endif + +#endif // TYPESSPIO_H diff --git a/src/axom/sidre/spio/interface/c_fortran/wrapIOManager.h b/src/axom/sidre/spio/interface/c_fortran/wrapIOManager.h index 2698c6279c..9a94d1c93f 100644 --- a/src/axom/sidre/spio/interface/c_fortran/wrapIOManager.h +++ b/src/axom/sidre/spio/interface/c_fortran/wrapIOManager.h @@ -7,7 +7,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#pragma once +#ifndef WRAPIOMANAGER_H +#define WRAPIOMANAGER_H /** * \file wrapIOManager.h @@ -152,3 +153,5 @@ void SPIO_IOManager_loadExternalData_bufferify(SPIO_IOManager *self, #ifdef __cplusplus } #endif + +#endif // WRAPIOMANAGER_H diff --git a/src/axom/slic/interface/c_fortran/typesSLIC.h b/src/axom/slic/interface/c_fortran/typesSLIC.h index ed7b2dba57..666620d5bb 100644 --- a/src/axom/slic/interface/c_fortran/typesSLIC.h +++ b/src/axom/slic/interface/c_fortran/typesSLIC.h @@ -7,7 +7,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#pragma once +#ifndef TYPESSLIC_H +#define TYPESSLIC_H // For C users and C++ implementation @@ -121,3 +122,5 @@ void SLIC_SHROUD_memory_destructor(SLIC_SHROUD_capsule_data *cap); #ifdef __cplusplus } #endif + +#endif // TYPESSLIC_H diff --git a/src/axom/slic/interface/c_fortran/wrapGenericOutputStream.h b/src/axom/slic/interface/c_fortran/wrapGenericOutputStream.h index 20e926121d..8958a919cb 100644 --- a/src/axom/slic/interface/c_fortran/wrapGenericOutputStream.h +++ b/src/axom/slic/interface/c_fortran/wrapGenericOutputStream.h @@ -7,7 +7,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#pragma once +#ifndef WRAPGENERICOUTPUTSTREAM_H +#define WRAPGENERICOUTPUTSTREAM_H /** * \file wrapGenericOutputStream.h @@ -49,3 +50,5 @@ void SLIC_GenericOutputStream_delete(SLIC_GenericOutputStream *self); #ifdef __cplusplus } #endif + +#endif // WRAPGENERICOUTPUTSTREAM_H diff --git a/src/axom/slic/interface/c_fortran/wrapSLIC.h b/src/axom/slic/interface/c_fortran/wrapSLIC.h index 116e711704..48da44b1b0 100644 --- a/src/axom/slic/interface/c_fortran/wrapSLIC.h +++ b/src/axom/slic/interface/c_fortran/wrapSLIC.h @@ -7,7 +7,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#pragma once +#ifndef WRAPSLIC_H +#define WRAPSLIC_H /** * \file wrapSLIC.h @@ -134,3 +135,5 @@ void SLIC_finalize(void); #ifdef __cplusplus } #endif + +#endif // WRAPSLIC_H From 1e196ca7b8a41f7eadda825f5ad2e8f8ee5fa477 Mon Sep 17 00:00:00 2001 From: Chris White Date: Wed, 8 Jul 2026 10:06:01 -0700 Subject: [PATCH 689/986] convert more --- src/axom/core/execution/doc.hpp | 2 ++ src/axom/core/tests/core_checksum.hpp | 2 ++ src/axom/core/tests/core_constexpr_assert.hpp | 5 +---- src/axom/core/utilities/Abort.hpp | 5 +---- src/axom/core/utilities/Checksum.hpp | 5 +---- src/axom/core/utilities/ConstexprAssert.hpp | 5 +---- src/axom/slam/MapBuilders.hpp | 5 +---- src/axom/slam/SetBuilders.hpp | 5 +---- src/axom/slam/policies/ValuePolicies.hpp | 5 +---- src/thirdparty/axom/fmt.hpp | 5 +---- 10 files changed, 12 insertions(+), 32 deletions(-) diff --git a/src/axom/core/execution/doc.hpp b/src/axom/core/execution/doc.hpp index b2579d28c7..329fc27c52 100644 --- a/src/axom/core/execution/doc.hpp +++ b/src/axom/core/execution/doc.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + // This file exists only to enable documentation of accelerated code within Axom. Do not // delete this file, nor add any lines of code. diff --git a/src/axom/core/tests/core_checksum.hpp b/src/axom/core/tests/core_checksum.hpp index 285dba09d3..13df9c9033 100644 --- a/src/axom/core/tests/core_checksum.hpp +++ b/src/axom/core/tests/core_checksum.hpp @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +#pragma once + #include "gtest/gtest.h" #include "axom/core/ArrayView.hpp" diff --git a/src/axom/core/tests/core_constexpr_assert.hpp b/src/axom/core/tests/core_constexpr_assert.hpp index 439cbbd3ee..7ddc16b9ea 100644 --- a/src/axom/core/tests/core_constexpr_assert.hpp +++ b/src/axom/core/tests/core_constexpr_assert.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_CORE_TESTS_CORE_CONSTEXPR_ASSERT_HPP_ -#define AXOM_CORE_TESTS_CORE_CONSTEXPR_ASSERT_HPP_ +#pragma once #include "gtest/gtest.h" @@ -41,5 +40,3 @@ TEST(core_constexpr_assert, runtime_false_death) EXPECT_DEATH_IF_SUPPORTED([]() { AXOM_CONSTEXPR_ASSERT(false); }(), ".*"); } #endif - -#endif // AXOM_CORE_TESTS_CORE_CONSTEXPR_ASSERT_HPP_ diff --git a/src/axom/core/utilities/Abort.hpp b/src/axom/core/utilities/Abort.hpp index b3790a5732..a202c9b457 100644 --- a/src/axom/core/utilities/Abort.hpp +++ b/src/axom/core/utilities/Abort.hpp @@ -13,8 +13,7 @@ * in low-level facilities that cannot depend on higher-level utilities headers. */ -#ifndef AXOM_CORE_UTILITIES_ABORT_HPP_ -#define AXOM_CORE_UTILITIES_ABORT_HPP_ +#pragma once #include "axom/config.hpp" @@ -25,5 +24,3 @@ namespace axom::utilities */ [[noreturn]] void processAbort(); } // namespace axom::utilities - -#endif // AXOM_CORE_UTILITIES_ABORT_HPP_ diff --git a/src/axom/core/utilities/Checksum.hpp b/src/axom/core/utilities/Checksum.hpp index 259f87ea7d..9c402ac91e 100644 --- a/src/axom/core/utilities/Checksum.hpp +++ b/src/axom/core/utilities/Checksum.hpp @@ -3,8 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_UTILITIES_CHECKSUM_HPP_ -#define AXOM_UTILITIES_CHECKSUM_HPP_ +#pragma once #include @@ -84,5 +83,3 @@ inline CheckSum checksum(axom::ArrayView view, const ScaleFactor scaleFactor } // namespace utilities } // namespace axom - -#endif // AXOM_UTILITIES_CHECKSUM_HPP_ diff --git a/src/axom/core/utilities/ConstexprAssert.hpp b/src/axom/core/utilities/ConstexprAssert.hpp index 44faac1030..4576ee9101 100644 --- a/src/axom/core/utilities/ConstexprAssert.hpp +++ b/src/axom/core/utilities/ConstexprAssert.hpp @@ -28,8 +28,7 @@ * - In device compilation, it is a no-op (kernels cannot throw/abort portably). */ -#ifndef AXOM_CORE_UTILITIES_CONSTEXPR_ASSERT_HPP_ -#define AXOM_CORE_UTILITIES_CONSTEXPR_ASSERT_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core/utilities/Abort.hpp" @@ -123,5 +122,3 @@ AXOM_DETAIL_CONSTEXPR_ASSERT_HOST_DEVICE constexpr void constexprAssert(bool, #undef AXOM_DETAIL_CONSTEXPR_ASSERT_HOST_DEVICE #undef AXOM_DETAIL_IS_CONSTANT_EVALUATED #undef AXOM_DETAIL_HAS_IS_CONSTANT_EVALUATED - -#endif // AXOM_CORE_UTILITIES_CONSTEXPR_ASSERT_HPP_ diff --git a/src/axom/slam/MapBuilders.hpp b/src/axom/slam/MapBuilders.hpp index 381c115c9e..275cd4a739 100644 --- a/src/axom/slam/MapBuilders.hpp +++ b/src/axom/slam/MapBuilders.hpp @@ -11,8 +11,7 @@ * from a set, stride, and backing buffer. */ -#ifndef SLAM_MAP_BUILDERS_HPP_ -#define SLAM_MAP_BUILDERS_HPP_ +#pragma once #include "axom/core/ArrayView.hpp" #include "axom/slic.hpp" @@ -152,5 +151,3 @@ auto make_map_ct(const SetType* set, T* data) } } // namespace axom::slam - -#endif // SLAM_MAP_BUILDERS_HPP_ diff --git a/src/axom/slam/SetBuilders.hpp b/src/axom/slam/SetBuilders.hpp index a7bccc013f..9e31a14c04 100644 --- a/src/axom/slam/SetBuilders.hpp +++ b/src/axom/slam/SetBuilders.hpp @@ -34,8 +34,7 @@ * functions are therefore the portable way to get stack-deducing construction in C++17. */ -#ifndef SLAM_SET_BUILDERS_H_ -#define SLAM_SET_BUILDERS_H_ +#pragma once #include "axom/core/Array.hpp" #include "axom/core/Types.hpp" @@ -153,5 +152,3 @@ CArrayIndirectionSet make_indirection_set(T* data, axom::type_identi /// \} } // end namespace axom::slam - -#endif // SLAM_SET_BUILDERS_H_ diff --git a/src/axom/slam/policies/ValuePolicies.hpp b/src/axom/slam/policies/ValuePolicies.hpp index 9f1671da7b..18650b7ebd 100644 --- a/src/axom/slam/policies/ValuePolicies.hpp +++ b/src/axom/slam/policies/ValuePolicies.hpp @@ -35,8 +35,7 @@ * this scalar substrate and remain defined alongside their families. */ -#ifndef SLAM_POLICIES_VALUE_H_ -#define SLAM_POLICIES_VALUE_H_ +#pragma once #include "axom/core/Macros.hpp" @@ -155,5 +154,3 @@ struct CompileTimeValue }; } // end namespace axom::slam::policies - -#endif // SLAM_POLICIES_VALUE_H_ diff --git a/src/thirdparty/axom/fmt.hpp b/src/thirdparty/axom/fmt.hpp index 8693d33e6a..6c96df662c 100644 --- a/src/thirdparty/axom/fmt.hpp +++ b/src/thirdparty/axom/fmt.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_FMT_H_ -#define AXOM_FMT_H_ +#pragma once // Axom's config.hpp sets up fmt defines #include "axom/config.hpp" @@ -26,5 +25,3 @@ #include "axom/fmt/std.h" #include "axom/fmt/xchar.h" -#endif // AXOM_FMT_H_ - From 19505498e7a4a5e742796cefb3d44e60b13e10a2 Mon Sep 17 00:00:00 2001 From: Chris White Date: Thu, 9 Jul 2026 13:33:43 -0700 Subject: [PATCH 690/986] remove header guard and put note why it shouldnt be there --- src/axom/quest/detail/marching_cubes_lookup.hpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/axom/quest/detail/marching_cubes_lookup.hpp b/src/axom/quest/detail/marching_cubes_lookup.hpp index f68418f0d7..5c6c0ef6fa 100644 --- a/src/axom/quest/detail/marching_cubes_lookup.hpp +++ b/src/axom/quest/detail/marching_cubes_lookup.hpp @@ -4,10 +4,12 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#pragma once - /*! @file Static look-up tables for MarchingCubesImpl. + + This file is intentionally included multiple times with different + _MC_LOOKUP_* macros to make selected lookup tables available in + host/device functions. Do not add an include guard or #pragma once. */ // 2D case table From 5c913f8c80f870bca13d5e9dc107e551c853cc68 Mon Sep 17 00:00:00 2001 From: Chris White Date: Thu, 9 Jul 2026 13:34:27 -0700 Subject: [PATCH 691/986] style --- src/axom/core/Macros.hpp | 1 - src/axom/slam/FieldRegistry.hpp | 1 - src/axom/slam/RelationBuilders.hpp | 1 - src/axom/slam/RelationSet.hpp | 1 - 4 files changed, 4 deletions(-) diff --git a/src/axom/core/Macros.hpp b/src/axom/core/Macros.hpp index 6f3604d506..8cc67b6869 100644 --- a/src/axom/core/Macros.hpp +++ b/src/axom/core/Macros.hpp @@ -423,4 +423,3 @@ * \brief Assert \a EXP in a way that is valid inside constexpr functions. */ #define AXOM_CONSTEXPR_ASSERT(EXP) ::axom::detail::constexprAssert((EXP), #EXP, __FILE__, __LINE__) - diff --git a/src/axom/slam/FieldRegistry.hpp b/src/axom/slam/FieldRegistry.hpp index d0ac330773..8f44036b89 100644 --- a/src/axom/slam/FieldRegistry.hpp +++ b/src/axom/slam/FieldRegistry.hpp @@ -653,4 +653,3 @@ class FieldRegistry }; } // end namespace axom::slam - diff --git a/src/axom/slam/RelationBuilders.hpp b/src/axom/slam/RelationBuilders.hpp index 103ea9b73d..7bd7aed4d4 100644 --- a/src/axom/slam/RelationBuilders.hpp +++ b/src/axom/slam/RelationBuilders.hpp @@ -640,4 +640,3 @@ auto make_constant_relation_ct(FromSet& fromSet, ToSet& toSet, axom::Array Date: Thu, 9 Jul 2026 13:39:55 -0700 Subject: [PATCH 692/986] more styling --- src/axom/klee/Units.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/axom/klee/Units.hpp b/src/axom/klee/Units.hpp index 458879eda1..4a3ad79cd9 100644 --- a/src/axom/klee/Units.hpp +++ b/src/axom/klee/Units.hpp @@ -37,4 +37,3 @@ LengthUnit parseLengthUnits(const inlet::Proxy &unitsAsProxy); } // namespace internal } // namespace klee } // namespace axom - From faf935045d49aa5498ab204728f98df1cd29d00d Mon Sep 17 00:00:00 2001 From: Chris White Date: Thu, 9 Jul 2026 15:25:27 -0700 Subject: [PATCH 693/986] change to run pipeline --- RELEASE-NOTES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 69a523fb0b..6001ef47d1 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -64,6 +64,7 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ - Quest: Updated `STEPReader` to use centralized length unit parsing and conversion logic. - Core: Optimization for axom::Array indirection -- since the stride is always 1, we can remove the runtime multiplication - Python: Removes build and test dependencies from `run_python_with_axom.sh` wrapper script +- Changed to `#pragma once` instead of unique header guard defines ### Fixed - Primal: Fixes signs of `compute_moments` to match orientation convention in `primal::evaluate_area_integral` From 7eee71b0c15f20edebb4bb1344c5111572437ad1 Mon Sep 17 00:00:00 2001 From: Rebecca Haluska Date: Tue, 14 Jul 2026 09:51:10 -0700 Subject: [PATCH 694/986] Sina: add tests for non-partial append curves --- src/axom/sina/tests/sina_Document.cpp | 43 +++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/axom/sina/tests/sina_Document.cpp b/src/axom/sina/tests/sina_Document.cpp index f4c77d532c..f7655fc0cb 100644 --- a/src/axom/sina/tests/sina_Document.cpp +++ b/src/axom/sina/tests/sina_Document.cpp @@ -829,6 +829,49 @@ TEST(Document, test_appendOrderedCurvesToHDF5) { doAppendOrderedCurveTest("hdf5", appendDocumentToHDF5); } +#endif + +// Making sure that we overwrite instead of appending when receiving "full" curves +void doAppendOverwriteCurveTest( + const std::string &protocol, + std::function appendDocumentFunc) +{ + std::string overwrite_file = "test_overwrite." + protocol; + axom::sina::Document overwritten_doc = Document(SIMPLE_DOCUMENT, createRecordLoaderWithAllKnownTypes()); + const std::string overwrite_str = R"( + { "records": [ { "type": "run", "application": "test", "local_id": "bar1", "curve_sets": { "set_1": { + "dependent": { "0": {"value": [1.0, 2.0, 3.0, 4.0]}, "1": {"value": [-1.0, -2.0, -3.0, -4.0]} }, + "independent": { "0": {"value": [4.0, 5.0, 6.0, 7.0]}, "1": {"value": [-4.0, -5.0, -6.0, -7.0]} } } } } ] })"; + axom::sina::Document overwrite_doc = + Document(overwrite_str, createRecordLoaderWithAllKnownTypes()); + Protocol enum_protocol = (protocol == "hdf5") ? Protocol::HDF5 : Protocol::JSON; + saveDocument(overwritten_doc, overwrite_file, enum_protocol); + conduit::Node resultMsg = appendDocumentFunc(overwrite_file, overwrite_doc, 3, false, true); + EXPECT_EQ(resultMsg.number_of_children(), 0); + conduit::Node root; + conduit::relay::io::load(overwrite_file, root); + conduit::Node expected_dependents = + parseJsonValue(overwrite_str)["records"].child(0)["curve_sets"]["set_1"]["dependent"]; + conduit::Node actual_dependents = root["records"].child(0)["curve_sets"]["set_1"]["dependent"]; + auto curveIter = actual_dependents.children(); + for(int i = 0; i < expected_dependents.number_of_children(); i++) + { + EXPECT_EQ(actual_dependents.child(i).name(), expected_dependents.child(i).name()); + EXPECT_EQ(node_to_double_vector(actual_dependents.child(i)["value"]), + node_to_double_vector(expected_dependents.child(i)["value"])); + } +} + +TEST(Document, test_appendFullLengthCurvesToJson) +{ + doAppendOverwriteCurveTest("json", appendDocumentToJson); +} + +#ifdef AXOM_USE_HDF5 +TEST(Document, test_appendFullLengthCurvesToHDF5) +{ + doAppendOverwriteCurveTest("hdf5", appendDocumentToHDF5); +} TEST(Document, create_fromJson_roundtrip_hdf5) { From b28dbe7779bf07ea0c690fc0751e5fa3c69e3aaa Mon Sep 17 00:00:00 2001 From: Rebecca Haluska Date: Tue, 14 Jul 2026 10:06:26 -0700 Subject: [PATCH 695/986] Sina: update confusing non-partial curve naming to simply 'overwrite' --- src/axom/sina/core/Document.cpp | 22 +++++++++++----------- src/axom/sina/core/Document.hpp | 14 ++++++++------ 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/axom/sina/core/Document.cpp b/src/axom/sina/core/Document.cpp index d0178ecd3d..6b7554afb3 100644 --- a/src/axom/sina/core/Document.cpp +++ b/src/axom/sina/core/Document.cpp @@ -818,7 +818,7 @@ void append_curveset(ConduitRelayLike &appendTo, const std::string &endpoint, int record_num, const std::string &original_file_path, - bool curvesAreFullLength) + bool overwriteCurves) { for(const std::string &curve_cat : CURVE_CATEGORIES) { @@ -828,7 +828,7 @@ void append_curveset(ConduitRelayLike &appendTo, { conduit::Node &n = curveIter.next(); std::string curve_endpoint = endpoint + "/" + curve_cat + "/" + curveIter.name() + "/value"; - if(relayLikeHasPath(appendTo, curve_endpoint, record_num) && !curvesAreFullLength) + if(relayLikeHasPath(appendTo, curve_endpoint, record_num) && !overwriteCurves) { relayLikeAppendCurve(appendTo, n["value"], curve_endpoint, record_num, original_file_path); } @@ -849,7 +849,7 @@ void append_recordlike_fields(ConduitRelayLike &appendTo, int record_num, const std::string &original_file_path, bool isHDF5, - bool curvesAreFullLength) + bool overwriteCurves) { auto fieldsIter = appendFrom.children(); while(fieldsIter.has_next()) @@ -918,7 +918,7 @@ void append_recordlike_fields(ConduitRelayLike &appendTo, record_num, original_file_path, isHDF5, - curvesAreFullLength); + overwriteCurves); } else { @@ -938,7 +938,7 @@ void append_recordlike_fields(ConduitRelayLike &appendTo, appendAtEndpoint + curveSetIter.name(), record_num, original_file_path, - curvesAreFullLength); + overwriteCurves); } } break; @@ -1007,7 +1007,7 @@ conduit::Node append(ConduitRelayLike &appendTo, bool isHDF5, bool skipValidation, const std::string &original_file_path, - bool curvesAreFullLength ) + bool overwriteCurves ) { conduit::Node msgNode = conduit::Node(conduit::DataType::list()); // We need to figure out where each record is in appendTo, since there's no guarantee in the order @@ -1073,7 +1073,7 @@ conduit::Node append(ConduitRelayLike &appendTo, rec_num->second, original_file_path, isHDF5, - curvesAreFullLength); + overwriteCurves); } } append_relationships(appendTo, appendFrom["relationships"]); @@ -1084,13 +1084,13 @@ conduit::Node appendDocumentToJson(const std::string &jsonFilePath, const Document &newData, const int mergeProtocol, const bool skipValidation, - const bool curvesAreFullLength) + const bool overwriteCurves) { conduit::Node appendTo; appendTo.load(jsonFilePath, "json"); conduit::Node appendFrom = newData.toNode(); conduit::Node msgNode = - append(appendTo, appendFrom, mergeProtocol, false, skipValidation, jsonFilePath, curvesAreFullLength); + append(appendTo, appendFrom, mergeProtocol, false, skipValidation, jsonFilePath, overwriteCurves); conduit::relay::io::save(appendTo, jsonFilePath); return msgNode; } @@ -1099,7 +1099,7 @@ conduit::Node appendDocumentToHDF5(const std::string &hdf5FilePath, const Document &newData, const int mergeProtocol, const bool skipValidation, - const bool curvesAreFullLength) + const bool overwriteCurves) { #ifdef AXOM_USE_HDF5 conduit::relay::io::IOHandle appendTo; @@ -1107,7 +1107,7 @@ conduit::Node appendDocumentToHDF5(const std::string &hdf5FilePath, conduit::Node appendFrom; newData.toHDF5Node(appendFrom); conduit::Node msgNode = - append(appendTo, appendFrom, mergeProtocol, true, skipValidation, hdf5FilePath, curvesAreFullLength); + append(appendTo, appendFrom, mergeProtocol, true, skipValidation, hdf5FilePath, overwriteCurves); appendTo.close(); return msgNode; #else diff --git a/src/axom/sina/core/Document.hpp b/src/axom/sina/core/Document.hpp index f75b8f80b0..569a3adedb 100644 --- a/src/axom/sina/core/Document.hpp +++ b/src/axom/sina/core/Document.hpp @@ -326,8 +326,9 @@ Document loadDocument(std::string const &path, * Protocol 1 is the conduit default behavior. Ignored entirely when skipping validation. * \param skipValidation whether to skip the validation step entirely. Most useful for well-controlled cases, * ex: a code is appending values to every timeseries every N cycles. - * \param curvesAreFullLength Indicates whether the data source passes the full curve (from ex: t=0) instead - * of since last write. Causes the curve to overwrite. + * \param overwriteCurves Indicates that curves should be overwritten instead of appended to. Useful for cases + * where a data source must pass the full curve each time. Note: the type of the data + * can't change on overwrite, don't use ints for floats etc. * * \return a conduit Node containing a list of any errors encountered in appending. If empty, success. */ @@ -335,7 +336,7 @@ conduit::Node appendDocumentToJson(const std::string &jsonFilePath, const Document &newData, const int mergeProtocol = 1, const bool skipValidation = false, - const bool curvesAreFullLength = false); + const bool overwriteCurves = false); /** * \brief Append the new records or, per-record, new data, user defined content, curves/curve sets, @@ -362,8 +363,9 @@ conduit::Node appendDocumentToJson(const std::string &jsonFilePath, * Protocol 1 is the conduit default behavior. Ignored entirely when skipping validation. * \param skipValidation whether to skip the validation step entirely. Most useful for well-controlled cases, * ex: a code is appending values to every timeseries every N cycles. - * \param curvesAreFullLength Indicates whether the data source passes the full curve (from ex: t=0) instead - * of since last write. Causes the curve to overwrite. + * \param overwriteCurves Indicates that curves should be overwritten instead of appended to. Useful for cases + * where a data source must pass the full curve each time. Note: the type of the data + * can't change on overwrite, don't use ints for floats etc. * * \return a conduit Node containing a list of any errors encountered in appending. If empty, success! */ @@ -371,7 +373,7 @@ conduit::Node appendDocumentToHDF5(const std::string &hdf5FilePath, Document const &newData, const int mergeProtocol = 1, const bool skipValidation = false, - const bool curvesAreFullLength = false); + const bool overwriteCurves = false); /** * @brief Append a Document to an existing file with automatic format detection From fde13402eae177aa089a63593bbb9eb390af39d4 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 14 Jul 2026 10:23:20 -0700 Subject: [PATCH 696/986] 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 697/986] 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 0d6447dc3b9ae36ebe1d68f583f77d6624fd9e7f Mon Sep 17 00:00:00 2001 From: Chris White Date: Tue, 14 Jul 2026 11:24:24 -0700 Subject: [PATCH 698/986] quiet warnings --- src/axom/quest/CMakeLists.txt | 1 - src/axom/slam/examples/tinyHydro/CMakeLists.txt | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/axom/quest/CMakeLists.txt b/src/axom/quest/CMakeLists.txt index 63fbeb364e..98f76640c3 100644 --- a/src/axom/quest/CMakeLists.txt +++ b/src/axom/quest/CMakeLists.txt @@ -189,7 +189,6 @@ if(AXOM_ENABLE_KLEE AND AXOM_ENABLE_SIDRE) detail/clipping/SphereClipper.cpp detail/clipping/MonotonicZSORClipper.cpp detail/clipping/SORClipper.cpp - detail/clipping/MeshClipperImpl.hpp util/make_clipper_strategy.cpp) # MeshClipperImpl uses RAJA algorithms directly. diff --git a/src/axom/slam/examples/tinyHydro/CMakeLists.txt b/src/axom/slam/examples/tinyHydro/CMakeLists.txt index 970142a2dd..6f1bbd764c 100644 --- a/src/axom/slam/examples/tinyHydro/CMakeLists.txt +++ b/src/axom/slam/examples/tinyHydro/CMakeLists.txt @@ -33,7 +33,7 @@ set(slam_tiny_hydro_lib_depends_on slam fmt) axom_add_library( NAME slam_tinyHydro_ex SOURCES ${tinyHydro_lib_sources} - ${tinyHydro_lib_headers} + HEADERS ${tinyHydro_lib_headers} DEPENDS_ON ${slam_tiny_hydro_lib_depends_on} FOLDER axom/slam/examples OBJECT TRUE ) From 3dbd9b8bacb66fd806950a83bd3f62f4aa89e16e Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 8 Jul 2026 13:18:29 -0700 Subject: [PATCH 699/986] quest: Bugfix for points on surface in 2D InOutOctree By convention, such points should be categorized as "Inside". --- src/axom/quest/InOutOctree.hpp | 14 +++ src/axom/quest/tests/quest_inout_quadtree.cpp | 112 +++++++++++++++++- 2 files changed, 125 insertions(+), 1 deletion(-) diff --git a/src/axom/quest/InOutOctree.hpp b/src/axom/quest/InOutOctree.hpp index cba1e977b2..619fb2e08a 100644 --- a/src/axom/quest/InOutOctree.hpp +++ b/src/axom/quest/InOutOctree.hpp @@ -1125,6 +1125,7 @@ typename std::enable_if::type InOutOctree::withinGrayBlock CellIndexSet triSet = leafCells(leafBlk, leafData); const int numTris = triSet.size(); + for(int i = 0; i < numTris; ++i) { /// Get the triangle @@ -1252,6 +1253,19 @@ typename std::enable_if::type InOutOctree::withinGrayBlock CellIndexSet segmentSet = leafCells(leafBlk, leafData); const int numSegments = segmentSet.size(); + + // First, handle the case where the query point lies on (or within the vertex welding tolerance of) + // one of the block's segments. Such points are 'within' the surface by convention, + // but the ray-orientation test below is degenerate for them. + for(int i = 0; i < numSegments; ++i) + { + if(primal::squared_distance(queryPt, m_meshWrapper.cellPositions(segmentSet[i])) <= + m_vertexWeldThresholdSquared) + { + return true; + } + } + for(int i = 0; i < numSegments; ++i) { /// Get the segment diff --git a/src/axom/quest/tests/quest_inout_quadtree.cpp b/src/axom/quest/tests/quest_inout_quadtree.cpp index fb80565b37..04e0be17ed 100644 --- a/src/axom/quest/tests/quest_inout_quadtree.cpp +++ b/src/axom/quest/tests/quest_inout_quadtree.cpp @@ -189,12 +189,122 @@ TEST(quest_inout_quadtree, circle_mesh) } } +TEST(quest_inout_quadtree, on_surface_points) +{ + // Regression test for https://github.com/LLNL/axom/issues/611 (2D) + // Query points on the surface should be marked as inside the surface. + // This test also checks for consistency with the winding number results. + + namespace mint = axom::mint; + namespace quest = axom::quest; + namespace primal = axom::primal; + + using Polygon2D = primal::Polygon; + + // lambda to linearly interpolate a point on an edge of a polygon at parameter 0 <= t <= 1 + auto lerp_edge = [](const Polygon2D& poly, int edge, double t) { + SLIC_ASSERT(edge >= 0 && edge <= poly.numVertices()); + SLIC_ASSERT(t >= 0. && t <= 1.); + return SpacePt::lerp(poly[edge], poly[(edge + 1) == poly.numVertices() ? 0 : edge + 1], t); + }; + + const Polygon2D unitSquare({SpacePt {0., 0.}, SpacePt {1., 0.}, SpacePt {1., 1.}, SpacePt {0., 1.}}); + + // Create mesh of unit square, with several edge refinements + for(int segsPerSide : {1, 2, 3, 5}) + { + // Build the perimeter vertices (CCW; corners not duplicated). + axom::Array verts; + for(int edge = 0; edge < unitSquare.numVertices(); ++edge) + { + for(int s = 0; s < segsPerSide; ++s) + { + const double t = static_cast(s) / segsPerSide; + verts.push_back(lerp_edge(unitSquare, edge, t)); + } + } + const int nverts = static_cast(verts.size()); + + // Segment mesh for the quadtree. + std::shared_ptr mesh = [&]() { + auto m = std::make_shared>(DIM, mint::SEGMENT); + for(const auto& v : verts) + { + m->appendNode(v[0], v[1]); + } + for(int i = 0; i < nverts; ++i) + { + axom::IndexType cell[2] = {i, (i + 1) % nverts}; + m->appendCell(cell); + } + return m; + }(); + + // Build quadtree over the linearized unit square + GeometricBoundingBox bbox = computeBoundingBox(*mesh); + Octree2D octree(bbox, mesh); + octree.generateIndex(); + + // sanity check on some interior and exterior points + EXPECT_TRUE(octree.within(SpacePt {0.5, 0.5})); + EXPECT_TRUE(octree.within(SpacePt {0.25, 0.75})); + EXPECT_FALSE(octree.within(SpacePt {0.5, 1.5})); + EXPECT_FALSE(octree.within(SpacePt {1.5, 0.5})); + EXPECT_FALSE(octree.within(SpacePt {2.0, 2.0})); + + // We will use the winding number over the polygon as an oracle + Polygon2D poly; + for(const auto& v : verts) + { + poly.addVertex(v); + } + + // Create a set of query points on the boundary, starting from unit square vertices and edge midpoints + axom::Array queryPoints {SpacePt {0.5, 1.0}, + SpacePt {0.5, 0.0}, + SpacePt {0.0, 0.5}, + SpacePt {1.0, 0.5}, + SpacePt {0.0, 0.0}, + SpacePt {1.0, 1.0}}; + + // Add regression case from the issue + queryPoints.push_back(SpacePt {0.370667, 1.0}); + + // Add a dense set of points along the edges + const int edgeSamples = 500 / poly.numVertices(); + for(int edge = 0; edge < poly.numVertices(); ++edge) + { + for(int sample = 0; sample < edgeSamples; ++sample) + { + queryPoints.push_back(lerp_edge(poly, edge, static_cast(sample) / edgeSamples)); + } + } + + // Run the tests + for(const auto& q : queryPoints) + { + bool isOnEdge {}; + const int wn = primal::winding_number(q, poly, isOnEdge, /*includeBoundary=*/true); + + // Sanity check on the oracle: these points really are on the boundary. + EXPECT_TRUE(isOnEdge) << axom::fmt::format("Oracle: point {} should be on the boundary", q); + EXPECT_NE(0, wn) << axom::fmt::format("Oracle: point {} should have nonzero winding number", q); + + // The actual regression assertion: on-surface points are 'within'. + EXPECT_TRUE(octree.within(q)) + << axom::fmt::format("Boundary point {} should be within the surface (segsPerSide={})", + q, + segsPerSide); + } + } +} + //---------------------------------------------------------------------- int main(int argc, char* argv[]) { ::testing::InitGoogleTest(&argc, argv); - axom::slic::SimpleLogger logger; // create & initialize test logger, + axom::slic::SimpleLogger logger; #ifdef INOUT_OCTREE_TESTER_SHOULD_SEED std::srand(std::time(0)); From 86788ce79de23c2fd5369ba769cd33b4a5c38b92 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 8 Jul 2026 14:43:47 -0700 Subject: [PATCH 700/986] quest: Bugfix for points on surface in 3D InOutOctree By conention, such points should be categorized as "Inside". --- src/axom/quest/InOutOctree.hpp | 12 ++ src/axom/quest/tests/quest_inout_octree.cpp | 140 ++++++++++++++++++++ 2 files changed, 152 insertions(+) diff --git a/src/axom/quest/InOutOctree.hpp b/src/axom/quest/InOutOctree.hpp index 619fb2e08a..7b7db00b42 100644 --- a/src/axom/quest/InOutOctree.hpp +++ b/src/axom/quest/InOutOctree.hpp @@ -1126,6 +1126,18 @@ typename std::enable_if::type InOutOctree::withinGrayBlock CellIndexSet triSet = leafCells(leafBlk, leafData); const int numTris = triSet.size(); + // First, handle the case where the query point lies on (or within the vertex welding tolerance of) + // one of the block's triangles. Such points are 'within' the surface by convention, + // but the ray-orientation test below is degenerate for them. + for(int i = 0; i < numTris; ++i) + { + if(primal::squared_distance(queryPt, m_meshWrapper.cellPositions(triSet[i])) <= + m_vertexWeldThresholdSquared) + { + return true; + } + } + for(int i = 0; i < numTris; ++i) { /// Get the triangle diff --git a/src/axom/quest/tests/quest_inout_octree.cpp b/src/axom/quest/tests/quest_inout_octree.cpp index 678a4d4991..533209eedb 100644 --- a/src/axom/quest/tests/quest_inout_octree.cpp +++ b/src/axom/quest/tests/quest_inout_octree.cpp @@ -234,6 +234,146 @@ TEST(quest_inout_octree, tetrahedron_mesh) } } +//---------------------------------------------------------------------- +TEST(quest_inout_octree, on_surface_points) +{ + // Regression test for https://github.com/LLNL/axom/issues/611 (3D) + // + // Query point lying exactly on the surface should be marked as inside the surface. + // This test builds a unit cube as a closed triangle surface (12 triangles) and + // checks that points sampled exactly on the surface are reported as 'within'. + // It compares against generalized winding number summed over the 12 triangles. + + namespace mint = axom::mint; + namespace quest = axom::quest; + namespace primal = axom::primal; + + using Point3D = primal::Point; + using Triangle3D = primal::Triangle; + + // 8 corners of the unit cube. + axom::Array V {Point3D {0., 0., 0.}, + Point3D {1., 0., 0.}, + Point3D {1., 1., 0.}, + Point3D {0., 1., 0.}, + Point3D {0., 0., 1.}, + Point3D {1., 0., 1.}, + Point3D {1., 1., 1.}, + Point3D {0., 1., 1.}}; + + // 12 triangles (2 per face), wound CCW as seen from outside (outward normals). + axom::Array> TRI {{0, 3, 2}, + {0, 2, 1}, // z=0 bottom + {4, 5, 6}, + {4, 6, 7}, // z=1 top + {0, 1, 5}, + {0, 5, 4}, // y=0 front + {3, 7, 6}, + {3, 6, 2}, // y=1 back + {0, 4, 7}, + {0, 7, 3}, // x=0 left + {1, 2, 6}, + {1, 6, 5}}; // x=1 right + + // Build a mesh over the triangles and an octree over the mesh + std::shared_ptr mesh = [&V, &TRI]() { + auto m = std::make_shared>(DIM, mint::TRIANGLE); + for(const auto& v : V) + { + m->appendNode(v[0], v[1], v[2]); + } + for(const auto& [t0, t1, t2] : TRI) + { + axom::IndexType cell[3] = {t0, t1, t2}; + m->appendCell(cell); + } + return m; + }(); + + GeometricBoundingBox bbox = computeBoundingBox(*mesh); + Octree3D octree(bbox, mesh); + octree.generateIndex(); + + // Matching triangle list for the winding-number oracle. + axom::Array tris; + for(const auto& [t0, t1, t2] : TRI) + { + tris.emplace_back(V[t0], V[t1], V[t2]); + } + + // Oracle: on-surface (by distance) => within; else sign of rounded GWN sum. + auto expectedWithin = [&tris](const Point3D& q, double edge_tol = 1e-8) -> bool { + const double edge_tol_2 = edge_tol * edge_tol; + double wn = 0.0; + for(const auto& tri : tris) + { + bool onThis {}; + wn += primal::winding_number(q, tri, onThis, edge_tol, edge_tol); + if(onThis || primal::squared_distance(q, tri) <= edge_tol_2) + { + return true; // on the surface + } + } + return std::lround(wn) != 0; + }; + + // Adds some hand-picked on-surface points: face centers, edge midpoints, corners, + // and points on the shared diagonal edge between the two triangles of a face. + axom::Array onSurface { + SpacePt {0.5, 0.5, 0.0}, + SpacePt {0.5, 0.5, 1.0}, // bottom/top face centers + SpacePt {0.5, 0.0, 0.5}, + SpacePt {0.5, 1.0, 0.5}, // front/back face centers + SpacePt {0.0, 0.5, 0.5}, + SpacePt {1.0, 0.5, 0.5}, // left/right face centers + SpacePt {0.5, 0.0, 0.0}, + SpacePt {0.0, 0.5, 0.0}, + SpacePt {1.0, 1.0, 0.5}, // edge midpoints + SpacePt {0.0, 0.0, 0.0}, + SpacePt {1.0, 1.0, 1.0}, + SpacePt {1.0, 0.0, 1.0}, // corners + SpacePt {0.3, 0.7, 1.0}, + SpacePt {1.0, 0.25, 0.6}, // off-center on faces + SpacePt {0.4, 0.4, 0.0} // on a face diagonal edge + }; + + // Also add dense set of samples on each triangle + const int bres = 8; + for(const auto& tri : tris) + { + for(int a = 0; a <= bres; ++a) + { + const double u = static_cast(a) / bres; + for(int b = 0; a + b <= bres; ++b) + { + const double v = static_cast(b) / bres; + onSurface.push_back(tri.baryToPhysical(SpacePt {u, v, 1. - u - v})); + } + } + } + + // Run the on-surface comparisons + for(const auto& q : onSurface) + { + EXPECT_TRUE(expectedWithin(q)) << "Oracle: point " << q << " should be on/within the surface"; + EXPECT_TRUE(octree.within(q)) << "On-surface point " << q << " should be within the surface"; + } + + // Sanity check for several interior and exterior query points + for(const auto& q_interior : {SpacePt {0.5, 0.5, 0.5}, SpacePt {0.25, 0.75, 0.5}}) + { + EXPECT_TRUE(expectedWithin(q_interior)); + EXPECT_TRUE(octree.within(q_interior)); + } + + for(const auto& q_exterior : + {SpacePt {0.5, 0.5, 1.5}, SpacePt {1.5, 0.5, 0.5}, SpacePt {2.0, 2.0, 2.0}}) + { + EXPECT_FALSE(expectedWithin(q_exterior)); + EXPECT_FALSE(octree.within(q_exterior)); + } +} + //---------------------------------------------------------------------- int main(int argc, char* argv[]) From 4898af021a1b4cf6a06c1449f4a31f3320ac17b6 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 8 Jul 2026 15:16:39 -0700 Subject: [PATCH 701/986] quest: Adds regression test for query points on unit square --- src/axom/quest/examples/CMakeLists.txt | 33 ++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/src/axom/quest/examples/CMakeLists.txt b/src/axom/quest/examples/CMakeLists.txt index bb88387758..89153b7f07 100644 --- a/src/axom/quest/examples/CMakeLists.txt +++ b/src/axom/quest/examples/CMakeLists.txt @@ -23,17 +23,40 @@ axom_add_executable( FOLDER axom/quest/examples ) -if(AXOM_ENABLE_TESTS AND AXOM_DATA_DIR AND CALIPER_FOUND) - set(_input_file "${AXOM_DATA_DIR}/quest/sphere_binary.stl") - +if(AXOM_ENABLE_TESTS AND AXOM_DATA_DIR) + + set(_optional_caliper) + if(CALIPER_FOUND) + set(_optional_caliper "--caliper" "report") + endif() + # this test requires MPI when available (but only 1 rank) + set(_input_file "${AXOM_DATA_DIR}/quest/sphere_binary.stl") axom_add_test( NAME quest_containment_sphere_ex COMMAND quest_containment_driver_ex - --input ${_input_file} - --caliper report + --input ${_input_file} + ${_optional_caliper} NUM_MPI_TASKS 1 ) + + if(C2C_FOUND) + set(_input_file "${AXOM_DATA_DIR}/contours/unit_square.contour") + axom_add_test( + NAME quest_containment_on_surface_regression_ex + COMMAND quest_containment_driver_ex + --input ${_input_file} + --min -0.234 -0.43 + --max 1.58 1.715 + -n3 + -l3 + ${_optional_caliper} + NUM_MPI_TASKS 1 + ) + endif() + + unset(_input_file) + unset(_optional_caliper) endif() # BVH two pass example -------------------------------------------------------- From e5b01e51be648dd363d2465320f06365204a4ba2 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 8 Jul 2026 16:06:57 -0700 Subject: [PATCH 702/986] quest: Adds runtime knob to InOutOctree to dump visualization meshes ... and exposes this as an option in the quest containment and shaping examples. This was previously exposed through a compile-time ifdef. --- src/axom/quest/InOutOctree.hpp | 106 ++++++++++-------- src/axom/quest/SamplingShaper.hpp | 15 ++- .../quest/detail/shaping/InOutSampler.hpp | 10 +- .../quest/examples/containment_driver.cpp | 64 ++++++----- src/axom/quest/examples/shaping_driver.cpp | 9 ++ 5 files changed, 130 insertions(+), 74 deletions(-) diff --git a/src/axom/quest/InOutOctree.hpp b/src/axom/quest/InOutOctree.hpp index 7b7db00b42..62d9fd9c03 100644 --- a/src/axom/quest/InOutOctree.hpp +++ b/src/axom/quest/InOutOctree.hpp @@ -14,6 +14,7 @@ #include "axom/core.hpp" #include "axom/core/NumericLimits.hpp" +#include "axom/core/utilities/FileUtilities.hpp" #include "axom/slic.hpp" #include "axom/slam.hpp" #include "axom/primal.hpp" @@ -30,14 +31,6 @@ #include #include -#ifndef DUMP_VTK_MESH -// #define DUMP_VTK_MESH -#endif - -#ifndef DUMP_OCTREE_INFO -// #define DUMP_OCTREE_INFO 1 -#endif - #ifndef DEBUG_OCTREE_ACTIVE // #define DEBUG_OCTREE_ACTIVE #endif @@ -215,6 +208,23 @@ class InOutOctree : public spin::SpatialOctree m_vertexWeldThresholdSquared = thresh * thresh; } + /*! + * \brief Controls whether VTK visualization dumps are written during octree generation. + * + * \param [in] enabled If true, writes the surface mesh and octree visualization + * dumps during generateIndex(). + */ + void setVtkOutputEnabled(bool enabled) { m_vtkOutputEnabled = enabled; } + + /// \brief Returns whether VTK visualization dumps are enabled. + bool isVtkOutputEnabled() const { return m_vtkOutputEnabled; } + + /// \brief Sets an optional prefix for VTK visualization dump filenames. + void setVtkOutputPrefix(const std::string& prefix) { m_vtkOutputPrefix = prefix; } + + /// \brief Sets the output directory for VTK visualization dump files. + void setVtkOutputDirectory(const std::string& directory) { m_vtkOutputDirectory = directory; } + private: /** * \brief Helper function to insert a vertex into the octree @@ -368,6 +378,7 @@ class InOutOctree : public spin::SpatialOctree void dumpOctreeMeshVTK(const std::string& name) const; void dumpSurfaceMeshVTK(const std::string& name) const; + std::string vtkOutputPath(const std::string& name) const; /** * \brief Utility function to dump any Inside blocks whose neighbors are @@ -397,6 +408,10 @@ class InOutOctree : public spin::SpatialOctree /// Bounding box scaling factor for dealing with grazing triangles double m_boundingBoxScaleFactor {DEFAULT_BOUNDING_BOX_SCALE_FACTOR}; + + bool m_vtkOutputEnabled {false}; + std::string m_vtkOutputDirectory; + std::string m_vtkOutputPrefix; }; template @@ -474,16 +489,24 @@ void InOutOctree::generateIndex() m_meshWrapper.numMeshVertices(), m_meshWrapper.numMeshCells())); -#ifdef DUMP_OCTREE_INFO - // -- Print some stats about the octree - SLIC_INFO_ROOT("** Octree stats after inserting vertices"); + if(m_vtkOutputEnabled) { + if(!m_vtkOutputDirectory.empty() && !axom::utilities::filesystem::pathExists(m_vtkOutputDirectory)) + { + const int rc = axom::utilities::filesystem::makeDirsForPath(m_vtkOutputDirectory); + SLIC_ERROR_IF( + rc != 0, + axom::fmt::format("Failed to create VTK output directory '{}'", m_vtkOutputDirectory)); + } + + // -- Print some stats about the octree + SLIC_INFO_ROOT("** Octree stats after inserting vertices"); AXOM_ANNOTATE_SCOPE("dump stats after inserting vertices"); - dumpSurfaceMeshVTK("surfaceMesh"); - dumpOctreeMeshVTK("prOctree"); + dumpSurfaceMeshVTK(m_vtkOutputPrefix + "surfaceMesh"); + dumpOctreeMeshVTK(m_vtkOutputPrefix + "prOctree"); printOctreeStats(); } -#endif + { AXOM_ANNOTATE_SCOPE("validate after inserting vertices"); checkValid(); @@ -517,16 +540,16 @@ void InOutOctree::generateIndex() "\t--Coloring octree leaves took {:.3Lf} seconds.", timer.elapsed())); -#ifdef DUMP_OCTREE_INFO - // -- Print some stats about the octree - SLIC_INFO_ROOT("** Octree stats after inserting cells"); + if(m_vtkOutputEnabled) { + // -- Print some stats about the octree + SLIC_INFO_ROOT("** Octree stats after inserting cells"); AXOM_ANNOTATE_SCOPE("dump stats after inserting cells"); - dumpOctreeMeshVTK("pmOctree"); - dumpDifferentColoredNeighborsMeshVTK("differentNeighbors"); + dumpOctreeMeshVTK(m_vtkOutputPrefix + "pmOctree"); + dumpDifferentColoredNeighborsMeshVTK(m_vtkOutputPrefix + "differentNeighbors"); printOctreeStats(); } -#endif + { AXOM_ANNOTATE_SCOPE("validate after inserting cells"); checkValid(); @@ -1527,30 +1550,29 @@ void InOutOctree::printOctreeStats() const detail::InOutOctreeStats octreeStats(*this); SLIC_INFO_ROOT(octreeStats.summaryStats()); -#ifdef DUMP_VTK_MESH - // Print out some debug meshes for vertex, triangle and/or blocks defined in - // DEBUG_XXX macros +#ifdef DEBUG_OCTREE_ACTIVE + // Print out some debug meshes for vertex, triangle and/or blocks defined in DEBUG_XXX macros if(m_generationState >= INOUTOCTREE_ELEMENTS_INSERTED) { detail::InOutOctreeMeshDumper meshDumper(*this); if(DEBUG_VERT_IDX >= 0 && DEBUG_VERT_IDX < m_meshWrapper.numMeshVertices()) { - meshDumper.dumpLocalOctreeMeshesForCell("debug_", DEBUG_VERT_IDX); + meshDumper.dumpLocalOctreeMeshesForCell(vtkOutputPath("debug_"), DEBUG_VERT_IDX); } if(DEBUG_TRI_IDX >= 0 && DEBUG_TRI_IDX < m_meshWrapper.numMeshCells()) { - meshDumper.dumpLocalOctreeMeshesForCell("debug_", DEBUG_TRI_IDX); + meshDumper.dumpLocalOctreeMeshesForCell(vtkOutputPath("debug_"), DEBUG_TRI_IDX); } if(DEBUG_BLOCK_1 != BlockIndex::invalid_index() && this->hasBlock(DEBUG_BLOCK_1)) { - meshDumper.dumpLocalOctreeMeshesForBlock("debug_", DEBUG_BLOCK_1); + meshDumper.dumpLocalOctreeMeshesForBlock(vtkOutputPath("debug_"), DEBUG_BLOCK_1); } if(DEBUG_BLOCK_2 != BlockIndex::invalid_index() && this->hasBlock(DEBUG_BLOCK_2)) { - meshDumper.dumpLocalOctreeMeshesForBlock("debug_", DEBUG_BLOCK_2); + meshDumper.dumpLocalOctreeMeshesForBlock(vtkOutputPath("debug_"), DEBUG_BLOCK_2); } } #endif @@ -1582,40 +1604,30 @@ void InOutOctree::checkValid() const template void InOutOctree::dumpSurfaceMeshVTK(const std::string& name) const { -#ifdef DUMP_VTK_MESH - detail::InOutOctreeMeshDumper meshDumper(*this); - meshDumper.dumpSurfaceMeshVTK(name); - -#else - AXOM_UNUSED_VAR(name); -#endif + meshDumper.dumpSurfaceMeshVTK(vtkOutputPath(name)); } template void InOutOctree::dumpOctreeMeshVTK(const std::string& name) const { -#ifdef DUMP_VTK_MESH - detail::InOutOctreeMeshDumper meshDumper(*this); - meshDumper.dumpOctreeMeshVTK(name); - -#else - AXOM_UNUSED_VAR(name); -#endif + meshDumper.dumpOctreeMeshVTK(vtkOutputPath(name)); } template void InOutOctree::dumpDifferentColoredNeighborsMeshVTK(const std::string& name) const { -#ifdef DUMP_VTK_MESH - detail::InOutOctreeMeshDumper meshDumper(*this); - meshDumper.dumpDifferentColoredNeighborsMeshVTK(name); + meshDumper.dumpDifferentColoredNeighborsMeshVTK(vtkOutputPath(name)); +} -#else - AXOM_UNUSED_VAR(name); -#endif +template +std::string InOutOctree::vtkOutputPath(const std::string& name) const +{ + return m_vtkOutputDirectory.empty() + ? name + : axom::utilities::filesystem::joinPath(m_vtkOutputDirectory, name); } } // end namespace quest diff --git a/src/axom/quest/SamplingShaper.hpp b/src/axom/quest/SamplingShaper.hpp index fdb8c91642..e2b6d6c8bb 100644 --- a/src/axom/quest/SamplingShaper.hpp +++ b/src/axom/quest/SamplingShaper.hpp @@ -165,6 +165,15 @@ class SamplingShaper : public Shaper void setSamplingMethod(SamplingMethod samplingMethod) { m_samplingMethod = samplingMethod; } + /// \brief Controls whether InOutOctree VTK visualization dumps are written during sampling. + void setInOutOctreeVtkOutputEnabled(bool enabled) { m_inoutOctreeVtkOutputEnabled = enabled; } + + /// \brief Sets the directory for InOutOctree VTK visualization dumps during sampling. + void setInOutOctreeVtkOutputDirectory(const std::string& directory) + { + m_inoutOctreeVtkOutputDirectory = directory; + } + /*! * \brief Sets the 1D quadrature family used to generate custom sample points. * @@ -422,7 +431,9 @@ class SamplingShaper : public Shaper else if constexpr(is_inoutsampler_v) { sampler->computeBounds(); - sampler->initSpatialIndex(this->m_vertexWeldThreshold); + sampler->initSpatialIndex(this->m_vertexWeldThreshold, + m_inoutOctreeVtkOutputEnabled, + m_inoutOctreeVtkOutputDirectory); } else if constexpr(is_primitivesampler_v) { @@ -1171,6 +1182,8 @@ class SamplingShaper : public Shaper axom::Array m_samplingResolution {}; int m_volfracOrder {2}; SamplingMethod m_samplingMethod {SamplingMethod::InOut}; + bool m_inoutOctreeVtkOutputEnabled {false}; + std::string m_inoutOctreeVtkOutputDirectory; }; } // namespace quest diff --git a/src/axom/quest/detail/shaping/InOutSampler.hpp b/src/axom/quest/detail/shaping/InOutSampler.hpp index a673e3ce3e..24f5deee33 100644 --- a/src/axom/quest/detail/shaping/InOutSampler.hpp +++ b/src/axom/quest/detail/shaping/InOutSampler.hpp @@ -85,12 +85,20 @@ class InOutSampler SLIC_INFO_ROOT("Mesh bounding box: " << m_bbox); } - void initSpatialIndex(double vertexWeldThreshold) + void initSpatialIndex(double vertexWeldThreshold, + bool shouldOutputVtk = false, + const std::string& vtkOutputDirectory = "") { AXOM_ANNOTATE_SCOPE("generate InOutOctree"); // Create octree over mesh's bounding box m_octree = new InOutOctreeType(m_bbox, m_surfaceMesh); m_octree->setVertexWeldThreshold(vertexWeldThreshold); + m_octree->setVtkOutputEnabled(shouldOutputVtk); + if(shouldOutputVtk) + { + m_octree->setVtkOutputDirectory(vtkOutputDirectory); + m_octree->setVtkOutputPrefix(axom::fmt::format("{}_", m_shapeName)); + } m_octree->generateIndex(); } diff --git a/src/axom/quest/examples/containment_driver.cpp b/src/axom/quest/examples/containment_driver.cpp index 4f0c073d04..5067a99b18 100644 --- a/src/axom/quest/examples/containment_driver.cpp +++ b/src/axom/quest/examples/containment_driver.cpp @@ -154,10 +154,15 @@ class ContainmentDriver SLIC_INFO("Bounding box for query points: " << m_queryBB); } - void initializeInOutOctree() + void initializeInOutOctree(bool shouldOutputVtk, const std::string& vtkOutputDirectory) { AXOM_ANNOTATE_SCOPE("generate octree"); m_octree = new InOutOctreeType(m_meshBB, m_surfaceMesh); + m_octree->setVtkOutputEnabled(shouldOutputVtk); + if(shouldOutputVtk) + { + m_octree->setVtkOutputDirectory(vtkOutputDirectory); + } m_octree->generateIndex(); } @@ -488,6 +493,7 @@ struct Input private: bool m_verboseOutput {false}; bool m_use_batched_query {false}; + bool m_output_octree_vtk {false}; public: Input() @@ -517,22 +523,20 @@ struct Input bool useBatchedQuery() const { return m_use_batched_query; } + bool outputOctreeVtk() const { return m_output_octree_vtk; } + void parse(int argc, char** argv, axom::CLI::App& app) { app.add_option("-i,--input", inputFile, "Path to input file")->check(axom::CLI::ExistingFile); - app - .add_flag("-v,--verbose", - m_verboseOutput, - "Enable/disable verbose output, " - "including outputting generated containment grids.") + app.add_flag("-v,--verbose", m_verboseOutput) + ->description( + "Enable/disable verbose output, including outputting generated containment grids.") ->capture_default_str(); - app - .add_option("-l,--levels", - maxQueryLevel, - "Max query resolution. \n" - "Will query uniform grids at levels 1 through the provided level") + app.add_option("-l,--levels", maxQueryLevel) + ->description( + "Max query resolution.\n Will query uniform grids at levels 1 through the provided level") ->capture_default_str() ->check(axom::CLI::PositiveNumber); @@ -544,17 +548,16 @@ struct Input minbb->needs(maxbb); maxbb->needs(minbb); - app - .add_flag("--batched", - m_use_batched_query, - "uses a single batched query on all points instead of many " - "individual queries") + app.add_flag("--batched", m_use_batched_query) + ->description("uses a single batched query on all points instead of many individual queries") ->capture_default_str(); - app - .add_option("-n,--segments-per-knot-span", - samplesPerKnotSpan, - "(2D only) Number of linear segments to generate per NURBS knot span") + app.add_flag("--vis", m_output_octree_vtk) + ->description("writes InOutOctree visualization VTK files during octree generation") + ->capture_default_str(); + + app.add_option("-n,--segments-per-knot-span", samplesPerKnotSpan) + ->description("(2D only) Number of linear segments to generate per NURBS knot span") ->capture_default_str() ->check(axom::CLI::PositiveNumber); @@ -567,7 +570,7 @@ struct Input ->check(axom::utilities::ValidCaliperMode); #endif - app.get_formatter()->column_width(48); + app.get_formatter()->column_width(50); // could throw an exception app.parse(argc, argv); @@ -639,19 +642,30 @@ int main(int argc, char** argv) /// Create octree over mesh's bounding box SLIC_INFO(axom::fmt::format("{:-^80}", " Generating the octree ")); + const std::string vtkOutputDirectory {"vis"}; if(is2D) { - driver2D.initializeInOutOctree(); + driver2D.initializeInOutOctree(params.outputOctreeVtk(), vtkOutputDirectory); driver2D.printSurfaceStats(); - mint::write_vtk(driver2D.getSurfaceMesh(), "meldedSegmentMesh.vtk"); + if(params.outputOctreeVtk()) + { + mint::write_vtk( + driver2D.getSurfaceMesh(), + axom::utilities::filesystem::joinPath(vtkOutputDirectory, "meldedSegmentMesh.vtk")); + } } else { - driver3D.initializeInOutOctree(); + driver3D.initializeInOutOctree(params.outputOctreeVtk(), vtkOutputDirectory); driver3D.printSurfaceStats(); - mint::write_vtk(driver3D.getSurfaceMesh(), "meldedTriMesh.vtk"); + if(params.outputOctreeVtk()) + { + mint::write_vtk( + driver3D.getSurfaceMesh(), + axom::utilities::filesystem::joinPath(vtkOutputDirectory, "meldedTriMesh.vtk")); + } } AXOM_ANNOTATE_END("init"); diff --git a/src/axom/quest/examples/shaping_driver.cpp b/src/axom/quest/examples/shaping_driver.cpp index 283b31f26d..01db86a683 100644 --- a/src/axom/quest/examples/shaping_driver.cpp +++ b/src/axom/quest/examples/shaping_driver.cpp @@ -122,10 +122,13 @@ struct Input private: bool m_verboseOutput {false}; + bool m_dumpOctreeVtk {false}; public: bool isVerbose() const { return m_verboseOutput; } + bool dumpOctreeVtk() const { return m_dumpOctreeVtk; } + /// Generate an mfem Cartesian mesh, scaled to the bounding box range mfem::Mesh* createBoxMesh() { @@ -342,6 +345,10 @@ struct Input "Selects the type of quadrature that determines point placement within elements.") ->capture_default_str() ->transform(axom::CLI::CheckedTransformer(quadTypeMap, axom::CLI::ignore_case)); + + sampling_options->add_flag("--dump-octree-vtk", m_dumpOctreeVtk) + ->description("Writes InOutOctree visualization VTK files when using inout sampling") + ->capture_default_str(); } // parameters that only apply to the intersection method @@ -669,6 +676,8 @@ int main(int argc, char** argv) samplingShaper->setQuadratureType(params.quadratureType); samplingShaper->setVolumeFractionOrder(params.outputOrder); samplingShaper->setSamplingMethod(params.samplingMethod); + samplingShaper->setInOutOctreeVtkOutputEnabled(params.dumpOctreeVtk()); + samplingShaper->setInOutOctreeVtkOutputDirectory("vis"); // register point projectors if(shapingDC.GetMesh()->Dimension() == 3) From 387f080585bcabbbbf173bf13f8d26184dd89541 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 8 Jul 2026 17:27:04 -0700 Subject: [PATCH 703/986] quest: Cleans up containment_drive by using a variant for the 2D and 3D cases --- .../quest/examples/containment_driver.cpp | 122 ++++++++---------- 1 file changed, 57 insertions(+), 65 deletions(-) diff --git a/src/axom/quest/examples/containment_driver.cpp b/src/axom/quest/examples/containment_driver.cpp index 5067a99b18..3da3c4e1fc 100644 --- a/src/axom/quest/examples/containment_driver.cpp +++ b/src/axom/quest/examples/containment_driver.cpp @@ -28,6 +28,8 @@ #include #include #include +#include +#include namespace mint = axom::mint; namespace primal = axom::primal; @@ -42,6 +44,8 @@ template class ContainmentDriver { public: + static constexpr int Dimension = DIM; + using CellVertIndices = primal::Point; using InOutOctreeType = quest::InOutOctree; @@ -606,8 +610,12 @@ int main(int argc, char** argv) const bool is2D = params.isInput2D(); - ContainmentDriver<2> driver2D; - ContainmentDriver<3> driver3D; + using DriverVariant = std::variant, ContainmentDriver<3>>; + DriverVariant driver; + if(!is2D) + { + driver.emplace>(); + } /// Load mesh file SLIC_INFO(axom::fmt::format("{:-^80}", " Loading the mesh ")); @@ -616,85 +624,69 @@ int main(int argc, char** argv) AXOM_ANNOTATE_METADATA("dimension", is2D ? 2 : 3, ""); AXOM_ANNOTATE_BEGIN("init"); - if(is2D) - { - if(!driver2D.loadContourMesh(params.inputFile, params.samplesPerKnotSpan)) - { - return 1; - } - } - else + const bool loadedMesh = std::visit( + [¶ms](auto& activeDriver) -> bool { + using DriverType = std::decay_t; + if constexpr(DriverType::Dimension == 2) + { + return activeDriver.loadContourMesh(params.inputFile, params.samplesPerKnotSpan); + } + else + { + activeDriver.loadSTLMesh(params.inputFile); + return true; + } + }, + driver); + if(!loadedMesh) { - driver3D.loadSTLMesh(params.inputFile); + return 1; } /// Compute mesh bounding box and log some stats about the surface - if(is2D) - { - driver2D.computeBounds(); - driver2D.printSurfaceStats(); - } - else - { - driver3D.computeBounds(); - driver3D.printSurfaceStats(); - } + std::visit( + [](auto& activeDriver) { + activeDriver.computeBounds(); + activeDriver.printSurfaceStats(); + }, + driver); /// Create octree over mesh's bounding box SLIC_INFO(axom::fmt::format("{:-^80}", " Generating the octree ")); const std::string vtkOutputDirectory {"vis"}; - if(is2D) - { - driver2D.initializeInOutOctree(params.outputOctreeVtk(), vtkOutputDirectory); - driver2D.printSurfaceStats(); - - if(params.outputOctreeVtk()) - { - mint::write_vtk( - driver2D.getSurfaceMesh(), - axom::utilities::filesystem::joinPath(vtkOutputDirectory, "meldedSegmentMesh.vtk")); - } - } - else - { - driver3D.initializeInOutOctree(params.outputOctreeVtk(), vtkOutputDirectory); - driver3D.printSurfaceStats(); + std::visit( + [¶ms, &vtkOutputDirectory](auto& activeDriver) { + activeDriver.initializeInOutOctree(params.outputOctreeVtk(), vtkOutputDirectory); + activeDriver.printSurfaceStats(); - if(params.outputOctreeVtk()) - { - mint::write_vtk( - driver3D.getSurfaceMesh(), - axom::utilities::filesystem::joinPath(vtkOutputDirectory, "meldedTriMesh.vtk")); - } - } + if(params.outputOctreeVtk()) + { + using DriverType = std::decay_t; + const std::string meldedMeshName = + (DriverType::Dimension == 2) ? "meldedSegmentMesh.vtk" : "meldedTriMesh.vtk"; + mint::write_vtk(activeDriver.getSurfaceMesh(), + axom::utilities::filesystem::joinPath(vtkOutputDirectory, meldedMeshName)); + } + }, + driver); AXOM_ANNOTATE_END("init"); AXOM_ANNOTATE_BEGIN("query"); /// Query the octree over mesh's bounding box SLIC_INFO(axom::fmt::format("{:-^80}", " Querying the octree ")); - if(is2D) - { - driver2D.initializeQueryBox(params.queryBoxMins, params.queryBoxMaxs); + std::visit( + [¶ms](auto& activeDriver) { + activeDriver.initializeQueryBox(params.queryBoxMins, params.queryBoxMaxs); - // Query the mesh - for(int i = 1; i < params.maxQueryLevel; ++i) - { - const int res = 1 << i; - driver2D.testContainmentOnRegularGrid(res, params.useBatchedQuery(), params.isVerbose()); - } - } - else - { - driver3D.initializeQueryBox(params.queryBoxMins, params.queryBoxMaxs); - - // Query the mesh - for(int i = 1; i < params.maxQueryLevel; ++i) - { - const int res = 1 << i; - driver3D.testContainmentOnRegularGrid(res, params.useBatchedQuery(), params.isVerbose()); - } - } + // Query the mesh + for(int i = 1; i < params.maxQueryLevel; ++i) + { + const int res = 1 << i; + activeDriver.testContainmentOnRegularGrid(res, params.useBatchedQuery(), params.isVerbose()); + } + }, + driver); AXOM_ANNOTATE_END("query"); SLIC_INFO(axom::fmt::format("{:-^80}", "")); From 19cc7507f3a069e9a4e1fd4fbc6bbadb297aeffb Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 8 Jul 2026 17:41:06 -0700 Subject: [PATCH 704/986] quest: Updates heroic_roses baseline for sample-based shaping w/ InOutOctree After our bugfix, the volume for the "black" material increased from 36,595 to 36,662.53, which better matches the computed volume from the winding-based approach (36,653.78). The difference between the two is now 8.74 (~0.024%). --- src/axom/quest/examples/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/axom/quest/examples/CMakeLists.txt b/src/axom/quest/examples/CMakeLists.txt index 89153b7f07..55703654f7 100644 --- a/src/axom/quest/examples/CMakeLists.txt +++ b/src/axom/quest/examples/CMakeLists.txt @@ -285,7 +285,7 @@ if(AXOM_ENABLE_MPI AND MFEM_FOUND AND MFEM_USE_MPI inline_mesh --min 0 0 --max 300 400 --resolution 150 200 -d 2 NUM_MPI_TASKS ${_nranks}) set_tests_properties(${_testname} PROPERTIES - PASS_REGULAR_EXPRESSION "Volume of material 'black' is 36,?595.") + PASS_REGULAR_EXPRESSION "Volume of material 'black' is 36,?66[0-9].") # Test 2D MFEM mesh (winding number) set(_testname quest_shaping_driver_ex_heroic_roses_mfem_cp) From 06093b67ef4037cf8f3b3f6549c6144da91fc40a Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 8 Jul 2026 18:03:17 -0700 Subject: [PATCH 705/986] quest: Minor cleanup of InOutOctree via `if constexp` instead of SFINAE The 2D and 3D functions were sufficiently different that I kept them separate, so this is mostly cosmetic --- src/axom/quest/InOutOctree.hpp | 66 +++++++++++++++++++++------------- 1 file changed, 42 insertions(+), 24 deletions(-) diff --git a/src/axom/quest/InOutOctree.hpp b/src/axom/quest/InOutOctree.hpp index 62d9fd9c03..4047ed55ef 100644 --- a/src/axom/quest/InOutOctree.hpp +++ b/src/axom/quest/InOutOctree.hpp @@ -13,8 +13,6 @@ */ #include "axom/core.hpp" -#include "axom/core/NumericLimits.hpp" -#include "axom/core/utilities/FileUtilities.hpp" #include "axom/slic.hpp" #include "axom/slam.hpp" #include "axom/primal.hpp" @@ -324,13 +322,16 @@ class InOutOctree : public spin::SpatialOctree * \param queryPt The point we are querying * \param leafBlk The block of the gray leaf * \param data The data associated with the leaf block - * \return True, if the point is inside the local surface associated with this - * block, false otherwise + * \return True, if the point is inside the local surface associated with this block, false otherwise */ - template - typename std::enable_if::type withinGrayBlock(const SpacePt& queryPt, - const BlockIndex& leafBlk, - const InOutBlockData& data) const; + bool withinGrayBlock(const SpacePt& queryPt, + const BlockIndex& leafBlk, + const InOutBlockData& data) const; + + template + bool withinGrayBlock3D(const SpacePt& queryPt, + const BlockIndex& leafBlk, + const InOutBlockData& data) const; /** * \brief Determines whether the specified 2D point is within the gray leaf @@ -338,13 +339,12 @@ class InOutOctree : public spin::SpatialOctree * \param queryPt The point we are querying * \param leafBlk The block of the gray leaf * \param data The data associated with the leaf block - * \return True, if the point is inside the local surface associated with this - * block, false otherwise + * \return True, if the point is inside the local surface associated with this block, false otherwise */ template - typename std::enable_if::type withinGrayBlock(const SpacePt& queryPt, - const BlockIndex& leafBlk, - const InOutBlockData& data) const; + bool withinGrayBlock2D(const SpacePt& queryPt, + const BlockIndex& leafBlk, + const InOutBlockData& data) const; /** * \brief Returns the index of the mesh vertex associated with the given leaf block @@ -1009,7 +1009,7 @@ bool InOutOctree::colorLeafAndNeighbors(const BlockIndex& leafBlk, InOutBlo { SpacePt faceCenter = SpacePt::midpoint(this->blockBoundingBox(leafBlk).getCentroid(), this->blockBoundingBox(neighborBlk).getCentroid()); - if(withinGrayBlock(faceCenter, neighborBlk, neighborData)) + if(withinGrayBlock(faceCenter, neighborBlk, neighborData)) { leafData.setBlack(); } @@ -1076,7 +1076,7 @@ bool InOutOctree::colorLeafAndNeighbors(const BlockIndex& leafBlk, InOutBlo SpacePt::midpoint(this->blockBoundingBox(leafBlk).getCentroid(), this->blockBoundingBox(leafBlk.faceNeighbor(i)).getCentroid()); - if(withinGrayBlock(faceCenter, leafBlk, leafData)) + if(withinGrayBlock(faceCenter, leafBlk, leafData)) { neighborData.setBlack(); } @@ -1127,13 +1127,30 @@ typename InOutOctree::CellIndexSet InOutOctree::leafCells(const BlockI return m_grayLeafToElementRelationLevelMap[leafBlk.level()][leafData.dataIndex()]; } +template +bool InOutOctree::withinGrayBlock(const SpacePt& queryPt, + const BlockIndex& leafBlk, + const InOutBlockData& leafData) const +{ + if constexpr(DIM == 3) + { + return withinGrayBlock3D(queryPt, leafBlk, leafData); + } + else + { + static_assert(DIM == 2, "InOutOctree only supports dimensions 2 and 3"); + return withinGrayBlock2D(queryPt, leafBlk, leafData); + } +} + template template -typename std::enable_if::type InOutOctree::withinGrayBlock( - const SpacePt& queryPt, - const BlockIndex& leafBlk, - const InOutBlockData& leafData) const +bool InOutOctree::withinGrayBlock3D(const SpacePt& queryPt, + const BlockIndex& leafBlk, + const InOutBlockData& leafData) const { + static_assert(DIM == 3 && TDIM == 3, "withinGrayBlock3D is only valid for 3D InOutOctrees"); + /// Finds a ray from queryPt to a point of a triangle within leafBlk. /// Then find the first triangle along this ray. The orientation of the ray /// against this triangle's normal indicates queryPt's containment. @@ -1267,11 +1284,12 @@ typename std::enable_if::type InOutOctree::withinGrayBlock template template -typename std::enable_if::type InOutOctree::withinGrayBlock( - const SpacePt& queryPt, - const BlockIndex& leafBlk, - const InOutBlockData& leafData) const +bool InOutOctree::withinGrayBlock2D(const SpacePt& queryPt, + const BlockIndex& leafBlk, + const InOutBlockData& leafData) const { + static_assert(DIM == 2 && TDIM == 2, "withinGrayBlock2D is only valid for 2D InOutOctrees"); + /// Finds a ray from queryPt to a point of a segment within leafBlk. /// Then finds the first segment along this ray. The orientation of the ray /// against this segment's normal indicates queryPt's containment. @@ -1530,7 +1548,7 @@ bool InOutOctree::within(const SpacePt& pt) const case InOutBlockData::White: return false; case InOutBlockData::Gray: - return withinGrayBlock(pt, block, data); + return withinGrayBlock(pt, block, data); case InOutBlockData::Undetermined: SLIC_ASSERT_MSG(false, axom::fmt::format("Error -- All leaf blocks must have a color. The color of " From d009e4b8dc84b4dbe577549d7b7bbc37e6cea014 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 8 Jul 2026 18:06:25 -0700 Subject: [PATCH 706/986] Updates RELEASE-NOTES --- RELEASE-NOTES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 6001ef47d1..97229006f9 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -83,6 +83,7 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ and marked the templated `axom::sidre::View::getAttributeScalar()` overloads `const` so they can be called on a `const View`. Also added `const` overloads for `axom::sidre::Buffer::getData()` and `axom::sidre::Buffer::getVoidPtr()` so they can be called on a `const Buffer`. +- Quest: Fixes `InOutOctree::within()` for query points that lie on (or very near) the surface, in both 2D (segment meshes) and 3D (triangle meshes). ## [Version 0.14.0] - Release date 2026-03-31 From 0ae79a77db7823b5c5e5a3124a62df154a8c4f82 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 9 Jul 2026 11:57:16 -0700 Subject: [PATCH 707/986] quest: Removes unnecessary TDIM template parameter from dimension-specific InOutOctree::withinGrayBlock*D implementation This was vestigial from the earlier SFINAE formulation. --- src/axom/quest/InOutOctree.hpp | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/axom/quest/InOutOctree.hpp b/src/axom/quest/InOutOctree.hpp index 4047ed55ef..2100696958 100644 --- a/src/axom/quest/InOutOctree.hpp +++ b/src/axom/quest/InOutOctree.hpp @@ -317,7 +317,10 @@ class InOutOctree : public spin::SpatialOctree } /** - * \brief Determines whether the specified 3D point is within the gray leaf + * \brief Determines whether the specified point is within the gray leaf + * + * Dispatches to the dimension-specific implementation (withinGrayBlock2D() + * or withinGrayBlock3D()) based on the octree dimension. * * \param queryPt The point we are querying * \param leafBlk The block of the gray leaf @@ -328,7 +331,15 @@ class InOutOctree : public spin::SpatialOctree const BlockIndex& leafBlk, const InOutBlockData& data) const; - template + /** + * \brief Determines whether the specified 3D point is within the gray leaf + * + * \param queryPt The point we are querying + * \param leafBlk The block of the gray leaf + * \param data The data associated with the leaf block + * \return True, if the point is inside the local surface associated with this block, false otherwise + * \pre This function is only valid for 3D InOutOctrees + */ bool withinGrayBlock3D(const SpacePt& queryPt, const BlockIndex& leafBlk, const InOutBlockData& data) const; @@ -340,8 +351,8 @@ class InOutOctree : public spin::SpatialOctree * \param leafBlk The block of the gray leaf * \param data The data associated with the leaf block * \return True, if the point is inside the local surface associated with this block, false otherwise + * \pre This function is only valid for 2D InOutOctrees */ - template bool withinGrayBlock2D(const SpacePt& queryPt, const BlockIndex& leafBlk, const InOutBlockData& data) const; @@ -1144,12 +1155,11 @@ bool InOutOctree::withinGrayBlock(const SpacePt& queryPt, } template -template bool InOutOctree::withinGrayBlock3D(const SpacePt& queryPt, const BlockIndex& leafBlk, const InOutBlockData& leafData) const { - static_assert(DIM == 3 && TDIM == 3, "withinGrayBlock3D is only valid for 3D InOutOctrees"); + static_assert(DIM == 3, "withinGrayBlock3D is only valid for 3D InOutOctrees"); /// Finds a ray from queryPt to a point of a triangle within leafBlk. /// Then find the first triangle along this ray. The orientation of the ray @@ -1283,12 +1293,11 @@ bool InOutOctree::withinGrayBlock3D(const SpacePt& queryPt, } template -template bool InOutOctree::withinGrayBlock2D(const SpacePt& queryPt, const BlockIndex& leafBlk, const InOutBlockData& leafData) const { - static_assert(DIM == 2 && TDIM == 2, "withinGrayBlock2D is only valid for 2D InOutOctrees"); + static_assert(DIM == 2, "withinGrayBlock2D is only valid for 2D InOutOctrees"); /// Finds a ray from queryPt to a point of a segment within leafBlk. /// Then finds the first segment along this ray. The orientation of the ray From 0e3c5f4e2d5f12f82e0803b2727d7c8089438ac5 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 9 Jul 2026 12:35:50 -0700 Subject: [PATCH 708/986] quest: Adds note about points on surface being inside for InOutOctree Also addresses PR suggestions about comments in new tests. --- src/axom/quest/InOutOctree.hpp | 9 +- src/axom/quest/tests/quest_inout_octree.cpp | 91 +++++++++++-------- src/axom/quest/tests/quest_inout_quadtree.cpp | 32 ++++--- 3 files changed, 81 insertions(+), 51 deletions(-) diff --git a/src/axom/quest/InOutOctree.hpp b/src/axom/quest/InOutOctree.hpp index 2100696958..fd77227aaa 100644 --- a/src/axom/quest/InOutOctree.hpp +++ b/src/axom/quest/InOutOctree.hpp @@ -174,7 +174,12 @@ class InOutOctree : public spin::SpatialOctree * * \param pt The point at which we are checking for containment * \return True if the point is within (or on) the surface, false otherwise + * * \note Points outside the octree bounding box are considered outside + * + * \note By convention, query points lying on the surface -- or within the + * vertex welding threshold (see setVertexWeldThreshold()) of it -- are + * considered to be \a inside the contained volume. */ bool within(const SpacePt& pt) const; @@ -1289,7 +1294,7 @@ bool InOutOctree::withinGrayBlock3D(const SpacePt& queryPt, // SLIC_DEBUG("Could not determine inside/outside for point " // << queryPt << " on block " << leafBlk); - return false; // query points on boundary might get here -- revisit this. + return false; } template @@ -1427,7 +1432,7 @@ bool InOutOctree::withinGrayBlock2D(const SpacePt& queryPt, SLIC_DEBUG("Could not determine inside/outside for point " << queryPt << " on block " << leafBlk); - return false; // query points on boundary might get here -- revisit this. + return false; } template diff --git a/src/axom/quest/tests/quest_inout_octree.cpp b/src/axom/quest/tests/quest_inout_octree.cpp index 533209eedb..6664603ecf 100644 --- a/src/axom/quest/tests/quest_inout_octree.cpp +++ b/src/axom/quest/tests/quest_inout_octree.cpp @@ -251,29 +251,39 @@ TEST(quest_inout_octree, on_surface_points) using Point3D = primal::Point; using Triangle3D = primal::Triangle; + constexpr double x_lo = 0.; + constexpr double y_lo = 0.; + constexpr double z_lo = 0.; + constexpr double x_mid = 0.5; + constexpr double y_mid = 0.5; + constexpr double z_mid = 0.5; + constexpr double x_hi = 1.; + constexpr double y_hi = 1.; + constexpr double z_hi = 1.; + // 8 corners of the unit cube. - axom::Array V {Point3D {0., 0., 0.}, - Point3D {1., 0., 0.}, - Point3D {1., 1., 0.}, - Point3D {0., 1., 0.}, - Point3D {0., 0., 1.}, - Point3D {1., 0., 1.}, - Point3D {1., 1., 1.}, - Point3D {0., 1., 1.}}; - - // 12 triangles (2 per face), wound CCW as seen from outside (outward normals). - axom::Array> TRI {{0, 3, 2}, - {0, 2, 1}, // z=0 bottom - {4, 5, 6}, - {4, 6, 7}, // z=1 top - {0, 1, 5}, - {0, 5, 4}, // y=0 front - {3, 7, 6}, - {3, 6, 2}, // y=1 back - {0, 4, 7}, - {0, 7, 3}, // x=0 left - {1, 2, 6}, - {1, 6, 5}}; // x=1 right + axom::Array V {Point3D {x_lo, y_lo, z_lo}, + Point3D {x_hi, y_lo, z_lo}, + Point3D {x_hi, y_hi, z_lo}, + Point3D {x_lo, y_hi, z_lo}, + Point3D {x_lo, y_lo, z_hi}, + Point3D {x_hi, y_lo, z_hi}, + Point3D {x_hi, y_hi, z_hi}, + Point3D {x_lo, y_hi, z_hi}}; + + // 12 triangles (2 per face), wound counter-clockwise as seen from outside (outward normals). + axom::Array> TRI {{0, 3, 2}, // z=0 bottom + {0, 2, 1}, + {4, 5, 6}, // z=1 top + {4, 6, 7}, + {0, 1, 5}, // y=0 front + {0, 5, 4}, + {3, 7, 6}, // y=1 back + {3, 6, 2}, + {0, 4, 7}, // x=0 left + {0, 7, 3}, + {1, 2, 6}, // x=1 right + {1, 6, 5}}; // Build a mesh over the triangles and an octree over the mesh std::shared_ptr mesh = [&V, &TRI]() { @@ -320,21 +330,25 @@ TEST(quest_inout_octree, on_surface_points) // Adds some hand-picked on-surface points: face centers, edge midpoints, corners, // and points on the shared diagonal edge between the two triangles of a face. axom::Array onSurface { - SpacePt {0.5, 0.5, 0.0}, - SpacePt {0.5, 0.5, 1.0}, // bottom/top face centers - SpacePt {0.5, 0.0, 0.5}, - SpacePt {0.5, 1.0, 0.5}, // front/back face centers - SpacePt {0.0, 0.5, 0.5}, - SpacePt {1.0, 0.5, 0.5}, // left/right face centers - SpacePt {0.5, 0.0, 0.0}, - SpacePt {0.0, 0.5, 0.0}, - SpacePt {1.0, 1.0, 0.5}, // edge midpoints - SpacePt {0.0, 0.0, 0.0}, - SpacePt {1.0, 1.0, 1.0}, - SpacePt {1.0, 0.0, 1.0}, // corners - SpacePt {0.3, 0.7, 1.0}, - SpacePt {1.0, 0.25, 0.6}, // off-center on faces - SpacePt {0.4, 0.4, 0.0} // on a face diagonal edge + // face centers + SpacePt {x_mid, y_mid, z_lo}, + SpacePt {x_mid, y_mid, z_hi}, + SpacePt {x_mid, y_lo, z_mid}, + SpacePt {x_mid, y_hi, z_mid}, + SpacePt {x_lo, y_mid, z_mid}, + SpacePt {x_hi, y_mid, z_mid}, + // edge midpoints + SpacePt {x_mid, y_lo, z_lo}, + SpacePt {x_lo, y_mid, z_lo}, + SpacePt {x_hi, y_hi, z_mid}, + // corners + SpacePt {x_lo, y_lo, z_lo}, + SpacePt {x_hi, y_hi, z_hi}, + SpacePt {x_hi, y_lo, z_hi}, + // off-center on faces + SpacePt {0.3, 0.7, z_hi}, + SpacePt {x_hi, 0.25, 0.6}, + SpacePt {0.4, 0.4, z_lo} // on a face diagonal edge }; // Also add dense set of samples on each triangle @@ -359,13 +373,14 @@ TEST(quest_inout_octree, on_surface_points) EXPECT_TRUE(octree.within(q)) << "On-surface point " << q << " should be within the surface"; } - // Sanity check for several interior and exterior query points + // Sanity check for several interior query points for(const auto& q_interior : {SpacePt {0.5, 0.5, 0.5}, SpacePt {0.25, 0.75, 0.5}}) { EXPECT_TRUE(expectedWithin(q_interior)); EXPECT_TRUE(octree.within(q_interior)); } + // Sanity check for several exterior query points for(const auto& q_exterior : {SpacePt {0.5, 0.5, 1.5}, SpacePt {1.5, 0.5, 0.5}, SpacePt {2.0, 2.0, 2.0}}) { diff --git a/src/axom/quest/tests/quest_inout_quadtree.cpp b/src/axom/quest/tests/quest_inout_quadtree.cpp index 04e0be17ed..dec35c37be 100644 --- a/src/axom/quest/tests/quest_inout_quadtree.cpp +++ b/src/axom/quest/tests/quest_inout_quadtree.cpp @@ -208,12 +208,20 @@ TEST(quest_inout_quadtree, on_surface_points) return SpacePt::lerp(poly[edge], poly[(edge + 1) == poly.numVertices() ? 0 : edge + 1], t); }; - const Polygon2D unitSquare({SpacePt {0., 0.}, SpacePt {1., 0.}, SpacePt {1., 1.}, SpacePt {0., 1.}}); + constexpr double x_lo = 0.; + constexpr double y_lo = 0.; + constexpr double x_mid = 0.5; + constexpr double y_mid = 0.5; + constexpr double x_hi = 1.; + constexpr double y_hi = 1.; + + const Polygon2D unitSquare( + {SpacePt {x_lo, y_lo}, SpacePt {x_hi, y_lo}, SpacePt {x_hi, y_hi}, SpacePt {x_lo, y_hi}}); // Create mesh of unit square, with several edge refinements for(int segsPerSide : {1, 2, 3, 5}) { - // Build the perimeter vertices (CCW; corners not duplicated). + // Build the perimeter vertices (counter-clockwise; corners not duplicated). axom::Array verts; for(int edge = 0; edge < unitSquare.numVertices(); ++edge) { @@ -245,9 +253,11 @@ TEST(quest_inout_quadtree, on_surface_points) Octree2D octree(bbox, mesh); octree.generateIndex(); - // sanity check on some interior and exterior points - EXPECT_TRUE(octree.within(SpacePt {0.5, 0.5})); + // sanity check on some interior points + EXPECT_TRUE(octree.within(SpacePt {x_mid, y_mid})); EXPECT_TRUE(octree.within(SpacePt {0.25, 0.75})); + + // sanity check on some exterior points EXPECT_FALSE(octree.within(SpacePt {0.5, 1.5})); EXPECT_FALSE(octree.within(SpacePt {1.5, 0.5})); EXPECT_FALSE(octree.within(SpacePt {2.0, 2.0})); @@ -260,15 +270,15 @@ TEST(quest_inout_quadtree, on_surface_points) } // Create a set of query points on the boundary, starting from unit square vertices and edge midpoints - axom::Array queryPoints {SpacePt {0.5, 1.0}, - SpacePt {0.5, 0.0}, - SpacePt {0.0, 0.5}, - SpacePt {1.0, 0.5}, - SpacePt {0.0, 0.0}, - SpacePt {1.0, 1.0}}; + axom::Array queryPoints {SpacePt {x_mid, y_hi}, + SpacePt {x_mid, y_lo}, + SpacePt {x_lo, y_mid}, + SpacePt {x_hi, y_mid}, + SpacePt {x_lo, y_lo}, + SpacePt {x_hi, y_hi}}; // Add regression case from the issue - queryPoints.push_back(SpacePt {0.370667, 1.0}); + queryPoints.push_back(SpacePt {0.370667, y_hi}); // Add a dense set of points along the edges const int edgeSamples = 500 / poly.numVertices(); From 608e295e9acfd317d27cbf9aa1c1bda837143deb Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 9 Jul 2026 12:47:54 -0700 Subject: [PATCH 709/986] quest: Adds InOutOctree::getVertexWeldThreshold() and uses it in tests This ensures that we are using the same on-surface check in the octree and its comparison tests. --- src/axom/quest/InOutOctree.hpp | 11 +++++++++++ src/axom/quest/tests/quest_inout_octree.cpp | 15 ++++++++++++++- src/axom/quest/tests/quest_inout_quadtree.cpp | 12 ++++++++++-- 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/axom/quest/InOutOctree.hpp b/src/axom/quest/InOutOctree.hpp index fd77227aaa..c1e7735b9c 100644 --- a/src/axom/quest/InOutOctree.hpp +++ b/src/axom/quest/InOutOctree.hpp @@ -28,6 +28,7 @@ #include #include #include +#include #ifndef DEBUG_OCTREE_ACTIVE // #define DEBUG_OCTREE_ACTIVE @@ -211,6 +212,16 @@ class InOutOctree : public spin::SpatialOctree m_vertexWeldThresholdSquared = thresh * thresh; } + /*! + * \brief Returns the threshold for welding vertices during octree construction + * + * This is also the distance within which a query point is considered to lie + * on the surface (see within()). + * + * \sa setVertexWeldThreshold() + */ + double getVertexWeldThreshold() const { return std::sqrt(m_vertexWeldThresholdSquared); } + /*! * \brief Controls whether VTK visualization dumps are written during octree generation. * diff --git a/src/axom/quest/tests/quest_inout_octree.cpp b/src/axom/quest/tests/quest_inout_octree.cpp index 6664603ecf..fa11701225 100644 --- a/src/axom/quest/tests/quest_inout_octree.cpp +++ b/src/axom/quest/tests/quest_inout_octree.cpp @@ -300,10 +300,18 @@ TEST(quest_inout_octree, on_surface_points) return m; }(); + // Use an explicit vertex-weld threshold so the test's on-surface tolerance + // and the octree's on-surface tolerance are the same quantity. + const double weldThresh = 1e-6; GeometricBoundingBox bbox = computeBoundingBox(*mesh); Octree3D octree(bbox, mesh); + octree.setVertexWeldThreshold(weldThresh); octree.generateIndex(); + // The octree treats points within its weld threshold of the surface as 'inside' + // The oracle below uses the same tolerance for consistency. + const double edgeTol = octree.getVertexWeldThreshold(); + // Matching triangle list for the winding-number oracle. axom::Array tris; for(const auto& [t0, t1, t2] : TRI) @@ -312,7 +320,12 @@ TEST(quest_inout_octree, on_surface_points) } // Oracle: on-surface (by distance) => within; else sign of rounded GWN sum. - auto expectedWithin = [&tris](const Point3D& q, double edge_tol = 1e-8) -> bool { + // The default tolerance matches the octree's vertex-weld threshold. + auto expectedWithin = [&tris, edgeTol](const Point3D& q, double edge_tol = -1.0) -> bool { + if(edge_tol < 0.0) + { + edge_tol = edgeTol; + } const double edge_tol_2 = edge_tol * edge_tol; double wn = 0.0; for(const auto& tri : tris) diff --git a/src/axom/quest/tests/quest_inout_quadtree.cpp b/src/axom/quest/tests/quest_inout_quadtree.cpp index dec35c37be..f7c3edae50 100644 --- a/src/axom/quest/tests/quest_inout_quadtree.cpp +++ b/src/axom/quest/tests/quest_inout_quadtree.cpp @@ -248,11 +248,19 @@ TEST(quest_inout_quadtree, on_surface_points) return m; }(); - // Build quadtree over the linearized unit square + // Build quadtree over the linearized unit square. + // Use an explicit vertex-weld threshold so the test's on-surface tolerance + // and the octree's on-surface tolerance are the same quantity. + const double weldThresh = 1e-6; GeometricBoundingBox bbox = computeBoundingBox(*mesh); Octree2D octree(bbox, mesh); + octree.setVertexWeldThreshold(weldThresh); octree.generateIndex(); + // The octree treats points within its weld threshold of the surface as 'inside' + // The oracle below uses the same tolerance for consistency. + const double edgeTol = octree.getVertexWeldThreshold(); + // sanity check on some interior points EXPECT_TRUE(octree.within(SpacePt {x_mid, y_mid})); EXPECT_TRUE(octree.within(SpacePt {0.25, 0.75})); @@ -294,7 +302,7 @@ TEST(quest_inout_quadtree, on_surface_points) for(const auto& q : queryPoints) { bool isOnEdge {}; - const int wn = primal::winding_number(q, poly, isOnEdge, /*includeBoundary=*/true); + const int wn = primal::winding_number(q, poly, isOnEdge, /*includeBoundary=*/true, edgeTol); // Sanity check on the oracle: these points really are on the boundary. EXPECT_TRUE(isOnEdge) << axom::fmt::format("Oracle: point {} should be on the boundary", q); From daa3cd173caa3c6b7ab755c6781b5d91d22c6027 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 9 Jul 2026 13:03:36 -0700 Subject: [PATCH 710/986] quest: Adds some InOutOctree tests for points just off the surface --- src/axom/quest/tests/quest_inout_octree.cpp | 54 ++++++++++++++----- src/axom/quest/tests/quest_inout_quadtree.cpp | 29 ++++++++++ 2 files changed, 71 insertions(+), 12 deletions(-) diff --git a/src/axom/quest/tests/quest_inout_octree.cpp b/src/axom/quest/tests/quest_inout_octree.cpp index fa11701225..1953f1c99e 100644 --- a/src/axom/quest/tests/quest_inout_octree.cpp +++ b/src/axom/quest/tests/quest_inout_octree.cpp @@ -17,6 +17,19 @@ #include "quest_test_utilities.hpp" +#include +#include +#include + +// Uncomment the line below for true randomized points +#ifndef INOUT_OCTREE_TESTER_SHOULD_SEED +// #define INOUT_OCTREE_TESTER_SHOULD_SEED +#endif + +#ifdef INOUT_OCTREE_TESTER_SHOULD_SEED + #include // for time() used by srand() +#endif + namespace { const int NUM_PT_TESTS = 50000; @@ -34,18 +47,6 @@ using SpaceVector = Octree3D::SpaceVector; using GridPt = Octree3D::GridPt; using BlockIndex = Octree3D::BlockIndex; -#include -#include - -// Uncomment the line below for true randomized points -#ifndef INOUT_OCTREE_TESTER_SHOULD_SEED -// #define INOUT_OCTREE_TESTER_SHOULD_SEED -#endif - -#ifdef INOUT_OCTREE_TESTER_SHOULD_SEED - #include // for time() used by srand() -#endif - /// Returns a SpacePt corresponding to the given vertex id \a vIdx in \a mesh SpacePt getVertex(axom::mint::Mesh& mesh, int vIdx) { @@ -386,6 +387,35 @@ TEST(quest_inout_octree, on_surface_points) EXPECT_TRUE(octree.within(q)) << "On-surface point " << q << " should be within the surface"; } + // Exercise the tolerance band directly: points just inside the surface weld theshold + // must be 'within'; points just beyond it must not. + const axom::Array> faceCenterAndNormal { + {SpacePt {x_mid, y_mid, z_lo}, SpaceVector {0., 0., -1.}}, // bottom + {SpacePt {x_mid, y_mid, z_hi}, SpaceVector {0., 0., 1.}}, // top + {SpacePt {x_mid, y_lo, z_mid}, SpaceVector {0., -1., 0.}}, // front + {SpacePt {x_mid, y_hi, z_mid}, SpaceVector {0., 1., 0.}}, // back + {SpacePt {x_lo, y_mid, z_mid}, SpaceVector {-1., 0., 0.}}, // left + {SpacePt {x_hi, y_mid, z_mid}, SpaceVector {1., 0., 0.}}}; // right + + for(const auto& [center, outwardNormal] : faceCenterAndNormal) + { + // Comfortably within the tolerance band (interior side) -> inside. + const SpacePt nearInside = center - (0.5 * weldThresh) * outwardNormal; + EXPECT_TRUE(expectedWithin(nearInside)) + << "Oracle: near-surface point " << nearInside << " should be within"; + EXPECT_TRUE(octree.within(nearInside)) + << "Point " << nearInside << " within weld threshold of face center " << center + << " should be inside"; + + // Comfortably beyond the tolerance band on the exterior side -> outside. + const SpacePt farOutside = center + (4. * weldThresh) * outwardNormal; + EXPECT_FALSE(expectedWithin(farOutside)) + << "Oracle: point " << farOutside << " beyond tolerance should be outside"; + EXPECT_FALSE(octree.within(farOutside)) + << "Point " << farOutside << " beyond weld threshold outside face center " << center + << " should be outside"; + } + // Sanity check for several interior query points for(const auto& q_interior : {SpacePt {0.5, 0.5, 0.5}, SpacePt {0.25, 0.75, 0.5}}) { diff --git a/src/axom/quest/tests/quest_inout_quadtree.cpp b/src/axom/quest/tests/quest_inout_quadtree.cpp index f7c3edae50..847b0fed5c 100644 --- a/src/axom/quest/tests/quest_inout_quadtree.cpp +++ b/src/axom/quest/tests/quest_inout_quadtree.cpp @@ -17,6 +17,7 @@ #include #include +#include // Uncomment the define below for true randomized points #ifndef INOUT_OCTREE_TESTER_SHOULD_SEED @@ -314,6 +315,34 @@ TEST(quest_inout_quadtree, on_surface_points) q, segsPerSide); } + + // Exercise the tolerance band directly: points just inside the surface weld theshold + // must be 'within'; points just beyond it must not. + const axom::Array> edgeMidAndNormal { + {SpacePt {x_mid, y_lo}, SpaceVector {0., -1.}}, // bottom edge + {SpacePt {x_hi, y_mid}, SpaceVector {1., 0.}}, // right edge + {SpacePt {x_mid, y_hi}, SpaceVector {0., 1.}}, // top edge + {SpacePt {x_lo, y_mid}, SpaceVector {-1., 0.}}}; // left edge + + for(const auto& [mid, outwardNormal] : edgeMidAndNormal) + { + // Comfortably within the tolerance band (interior side) -> inside. + const SpacePt nearInside = mid - (0.5 * weldThresh) * outwardNormal; + EXPECT_TRUE(octree.within(nearInside)) << axom::fmt::format( + "Point {} within weld threshold of edge midpoint {} should be inside (segsPerSide={})", + nearInside, + mid, + segsPerSide); + + // Comfortably beyond the tolerance band on the exterior side -> outside. + const SpacePt farOutside = mid + (4. * weldThresh) * outwardNormal; + EXPECT_FALSE(octree.within(farOutside)) << axom::fmt::format( + "Point {} beyond weld threshold outside edge midpoint {} should be outside " + "(segsPerSide={})", + farOutside, + mid, + segsPerSide); + } } } From 29b162fb64be7b8514309a5a245c165edf95d4be Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 9 Jul 2026 13:32:51 -0700 Subject: [PATCH 711/986] slam: Fix warning in Release config of llvm --- src/axom/slam/tests/slam_make_helpers.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/axom/slam/tests/slam_make_helpers.cpp b/src/axom/slam/tests/slam_make_helpers.cpp index fd45fe5626..fcef536507 100644 --- a/src/axom/slam/tests/slam_make_helpers.cpp +++ b/src/axom/slam/tests/slam_make_helpers.cpp @@ -249,15 +249,13 @@ TEST(slam_make_helpers, make_variable_relation_carray_rejects_short_begins_size) TEST(slam_make_helpers, make_constant_relation_rejects_undersized_indices) { +#ifdef AXOM_DEBUG auto fromSet = slam::make_range_set(3); auto toSet = slam::make_range_set(5); // A stride-2 constant relation over a size-3 from-set needs 6 indices but we supply 4 here. - // make_constant_relation asserts the exact size at construction in debug builds - // the check compiles out in release builds. Pos indices[4] = {0, 1, 2, 3}; -#ifdef AXOM_DEBUG EXPECT_DEATH_IF_SUPPORTED(slam::make_constant_relation(&fromSet, &toSet, Pos {2}, indices, Pos {4}), ""); #else From c81df88fda0000b6bbbe3372d71ddb295a9b4431 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 9 Jul 2026 18:15:20 -0700 Subject: [PATCH 712/986] quest: Modernizes quest_regression example This file was last updated before our move to C++17, so I took this opportunity to update the file a bit. There are no functional changes in this commit. --- src/axom/quest/tests/quest_regression.cpp | 460 ++++++++++------------ 1 file changed, 216 insertions(+), 244 deletions(-) diff --git a/src/axom/quest/tests/quest_regression.cpp b/src/axom/quest/tests/quest_regression.cpp index 1a2cfb046e..263edb208e 100644 --- a/src/axom/quest/tests/quest_regression.cpp +++ b/src/axom/quest/tests/quest_regression.cpp @@ -6,27 +6,26 @@ /** * \file + * * This file generates and runs regression tests for the Quest signed distance - * and point containment queries. It generates the signed distance - * representation and/or the InOutOctree representation and queries it over a - * uniform grid of a given resolution and bounding box using the quest C - * interface. + * and point containment queries. It generates the signed distance representation + * and/or the InOutOctree representation and queries it over a uniform grid + * of a given resolution and bounding box using the simplified quest interface. * - * The baseline files are stored in a Sidre datastore - * with the following structure: - * /mesh_name (string: name of mesh file, without paths) - * /mesh_bounding_box (a doubles: min_x, min_y, min_z, max_x, max_y, max_z) - * /query_resolution (3 ints: i, j, k of query grid) - * /octree_containment (ints: one for each query point, with value 0 or 1) - * /bvh_containment (ints: one for each query point, with value 0 or 1) - * /bvh_distance (doubles: one for each query point, - * value is min signed distance from the associated point - * to the surface). + * The baseline files are stored in a Sidre datastore with the following structure: + * + * + * ├─• mesh_name (string: name of mesh file, without paths) + * ├─• mesh_bounding_box (6 doubles: min_x, min_y, min_z, max_x, max_y, max_z) + * ├─• query_resolution (3 ints: i, j, k of query grid) + * ├─• octree_containment (ints: one for each query point, with value 0 or 1) + * ├─• bvh_containment (ints: one for each query point, with value 0 or 1) + * └─• bvh_distance (doubles: one for each query point, value is min signed distance + * from the associated point to the surface). * * The 'octree_containment' field is generated by default, or when the * '--containment' command line option is present. Similarly for the - * 'bvh_containment' and 'bvh_distance' and the '--distance' command line - * option. + * 'bvh_containment' and 'bvh_distance' and the '--distance' command line option. * * Usage: run with "-h" or "--help" to see command line options */ @@ -53,8 +52,12 @@ #endif // C/C++ includes +#include #include +#include +#include #include +#include constexpr int DIM = 3; // Default resolution of query grid @@ -73,20 +76,40 @@ using SpacePt = primal::Point; using SpaceVec = primal::Vector; using GridPt = primal::Point; -/// Simple structure to hold the command line arguments -struct Input +namespace { - Input() = default; +int checkedNumNodes(const mint::UniformMesh& mesh) +{ + const auto nnodes = mesh.getNumberOfNodes(); + SLIC_ASSERT_MSG(nnodes <= std::numeric_limits::max(), + "Quest regression test C interface expects an int-sized node count"); + return static_cast(nnodes); +} - ~Input() - { - if(queryMesh != nullptr) - { - delete queryMesh; - queryMesh = nullptr; - } - } +int numOpenMPThreads() +{ +#ifdef AXOM_USE_OPENMP + int numThreads = 1; + #pragma omp parallel + #pragma omp master + numThreads = omp_get_num_threads(); + return numThreads; +#else + return 1; +#endif +} +std::string withoutExtension(const std::string& filename) +{ + const auto extPos = filename.find_last_of('.'); + return extPos == std::string::npos ? filename : filename.substr(0, extPos); +} + +} // namespace + +/// Simple structure to hold the command line arguments +struct Input +{ std::string meshName; std::string baselineRoot; @@ -97,7 +120,7 @@ struct Input SpaceBoundingBox meshBoundingBox; GridPt queryResolution {DEFAULT_RESOLUTION}; - mint::UniformMesh* queryMesh {nullptr}; + std::unique_ptr queryMesh; bool testDistance {true}; bool testContainment {true}; @@ -109,52 +132,45 @@ struct Input /// Parses the command line options void parse(int argc, char** argv, axom::CLI::App& app) { - app.add_option("-m,--mesh", meshName, "Surface mesh file (STL files are currently supported)") + app.add_option("-m,--mesh", meshName) + ->description("Surface mesh file (STL files are currently supported)") ->required() ->check(axom::CLI::ExistingFile); - app - .add_flag("--distance,!--no-distance", - testDistance, - "Indicates whether to test the signed distance") + app.add_flag("--distance,!--no-distance", testDistance) + ->description("Indicates whether to test the signed distance") ->capture_default_str(); - app - .add_flag("--containment,!--no-containment", - testContainment, - "Indicates whether to test the point containment") + app.add_flag("--containment,!--no-containment", testContainment) + ->description("Indicates whether to test the point containment") ->capture_default_str(); // Note: Baselines comparisons only supported when Axom is configured with hdf5 // Users must supply either a baseline, or both the query resolution and bounding box #ifdef AXOM_USE_HDF5 - app - .add_option("-b,--baseline", - baselineRoot, - "root file of baseline, a sidre rootfile.\n" - "Note: Only supported when Axom configured with hdf5") + app.add_option("-b,--baseline", baselineRoot) + ->description( + "root file of baseline, a sidre rootfile.\n" + "Note: Only supported when Axom configured with hdf5") ->check(axom::CLI::ExistingFile); #endif // user can supply 1 or 3 values for resolution the following is not quite right - auto* res = app - .add_option("-r,--resolution", - m_resolution, - "Resolution of the sample grid.\n" - "Note: Only used when baseline not supplied.") + auto* res = app.add_option("-r,--resolution", m_resolution) + ->description( + "Resolution of the sample grid.\n" + "Note: Only used when baseline not supplied.") ->expected(-1); // Optional bounding box for query region - auto* minbb = app - .add_option("--min", - m_queryBoxMins, - "Min bounds for query box (x,y,z).\n" - "Note: Only used when baseline not supplied.") + auto* minbb = app.add_option("--min", m_queryBoxMins) + ->description( + "Min bounds for query box (x,y,z).\n" + "Note: Only used when baseline not supplied.") ->expected(3); - auto* maxbb = app - .add_option("--max", - m_queryBoxMaxs, - "Max bounds for query box (x,y,z)\n" - "Note: Only used when baseline not supplied.") + auto* maxbb = app.add_option("--max", m_queryBoxMaxs) + ->description( + "Max bounds for query box (x,y,z)\n" + "Note: Only used when baseline not supplied.") ->expected(3); // Add some requirements @@ -172,14 +188,12 @@ struct Input // Check if resolution or bounding box values were provided in addition // to baseline. If so, inform user that these will be overridden by baseline - bool hasRes = !m_resolution.empty(); - bool hasBBox = !m_queryBoxMins.empty(); + const bool hasRes = !m_resolution.empty(); + const bool hasBBox = !m_queryBoxMins.empty(); if(!baselineRoot.empty()) { - if(hasRes || hasBBox) - { - SLIC_INFO("Baseline mesh will override values for resolution and bounding box"); - } + SLIC_INFO_IF(hasRes || hasBBox, + "Baseline mesh will override values for resolution and bounding box"); } else if(!hasRes || !hasBBox) { @@ -188,11 +202,10 @@ struct Input } else { - const int dim = 3; queryResolution = - (m_resolution.size() == 1) ? GridPt(m_resolution[0]) : GridPt(m_resolution.data(), dim); - meshBoundingBox.addPoint(SpacePt(m_queryBoxMins.data(), dim)); - meshBoundingBox.addPoint(SpacePt(m_queryBoxMaxs.data(), dim)); + (m_resolution.size() == 1) ? GridPt(m_resolution[0]) : GridPt(m_resolution.data(), DIM); + meshBoundingBox.addPoint(SpacePt(m_queryBoxMins.data(), DIM)); + meshBoundingBox.addPoint(SpacePt(m_queryBoxMaxs.data(), DIM)); } if(!testContainment && !testDistance) @@ -210,11 +223,7 @@ void loadBaselineData(sidre::Group* grp, Input& args) reader.read(grp, args.baselineRoot, "sidre_hdf5"); /// Check that the required fields are present - - if(!grp->hasView("mesh_name")) - { - SLIC_ERROR("Baseline must include a 'mesh_name' view"); - } + SLIC_ERROR_IF(!grp->hasView("mesh_name"), "Baseline must include a 'mesh_name' view"); // Check for bounding box, and load into the args instance if(!grp->hasView("mesh_bounding_box")) @@ -223,14 +232,11 @@ void loadBaselineData(sidre::Group* grp, Input& args) } else { - sidre::View* view = grp->getView("mesh_bounding_box"); - if(view->getNumElements() != 6) - { - SLIC_ERROR("Bounding box must contain six doubles"); - } + auto* view = grp->getView("mesh_bounding_box"); + SLIC_ERROR_IF(view->getNumElements() != 6, "Bounding box must contain six doubles"); - double* data = view->getData(); - args.meshBoundingBox = SpaceBoundingBox(SpacePt(data, 3), SpacePt(data + 3, 3)); + const auto* data = static_cast(view->getData()); + args.meshBoundingBox = SpaceBoundingBox(SpacePt(data, DIM), SpacePt(data + DIM, DIM)); } // Check for query grid resolution, and load into the args instance @@ -240,14 +246,11 @@ void loadBaselineData(sidre::Group* grp, Input& args) } else { - sidre::View* view = grp->getView("query_resolution"); - if(view->getNumElements() != 3) - { - SLIC_ERROR("Query resolution must contain three ints"); - } + auto* view = grp->getView("query_resolution"); + SLIC_ERROR_IF(view->getNumElements() != 3, "Query resolution must contain three ints"); - int* data = view->getData(); - args.queryResolution = GridPt(data, 3); + const auto* data = static_cast(view->getData()); + args.queryResolution = GridPt(data, DIM); } // Optionally check for the InOutOctree point containment data @@ -292,20 +295,60 @@ void loadBaselineData(sidre::Group* grp, Input& args) /** * \brief Generates a mint Uniform mesh with the given bounding box and resolution - * \note Allocates a UniformMesh instance, which must be deleted by the user */ -mint::UniformMesh* createQueryMesh(const SpaceBoundingBox& bb, const GridPt& res) +std::unique_ptr createQueryMesh(const SpaceBoundingBox& bb, const GridPt& res) { const double* low = bb.getMin().data(); const double* high = bb.getMax().data(); - return new mint::UniformMesh(low, high, res[0] + 1, res[1] + 1, res[2] + 1); + return std::make_unique(low, high, res[0] + 1, res[1] + 1, res[2] + 1); } -/** - * Runs the InOutOctree point containment queries and adds results as scalar - * field on uniform mesh - */ +mint::UniformMesh& ensureQueryMesh(Input& clargs) +{ + if(!clargs.hasQueryMesh()) + { + clargs.queryMesh = createQueryMesh(clargs.meshBoundingBox, clargs.queryResolution); + } + + SLIC_ASSERT(clargs.queryMesh != nullptr); + return *clargs.queryMesh; +} + +struct QueryCoordinates +{ + explicit QueryCoordinates(int nnodes) : x(nnodes), y(nnodes), z(nnodes) { } + + axom::Array x; + axom::Array y; + axom::Array z; + double fillTime {0.0}; +}; + +QueryCoordinates makeQueryCoordinates(const mint::UniformMesh& umesh) +{ + const int nnodes = checkedNumNodes(umesh); + QueryCoordinates coords(nnodes); + + utilities::Timer fillTimer(true); + +#pragma omp parallel for schedule(static) + for(int inode = 0; inode < nnodes; ++inode) + { + axom::IndexType i, j, k; + umesh.getNodeGridIndex(inode, i, j, k); + + coords.x[inode] = umesh.evaluateCoordinate(i, mint::X_COORDINATE); + coords.y[inode] = umesh.evaluateCoordinate(j, mint::Y_COORDINATE); + coords.z[inode] = umesh.evaluateCoordinate(k, mint::Z_COORDINATE); + } + fillTimer.stop(); + coords.fillTime = fillTimer.elapsed(); + + return coords; +} + +/// Runs the InOutOctree point containment queries and adds results as scalar field on uniform mesh void runContainmentQueries(Input& clargs) { SLIC_INFO(axom::fmt::format("Initializing InOutOctree over mesh '{}'...", clargs.meshName)); @@ -327,67 +370,36 @@ void runContainmentQueries(Input& clargs) clargs.meshBoundingBox.scale(1.5); } - if(!clargs.hasQueryMesh()) - { - clargs.queryMesh = createQueryMesh(clargs.meshBoundingBox, clargs.queryResolution); - } - SLIC_INFO("Mesh bounding box is: " << SpaceBoundingBox(bbMin, bbMax)); SLIC_INFO("Query bounding box is: " << clargs.meshBoundingBox); -#ifdef AXOM_USE_OPENMP - #pragma omp parallel - #pragma omp master SLIC_INFO( - axom::fmt::format("Querying InOutOctree on uniform grid " - "of resolution {} using {} threads", + axom::fmt::format("Querying InOutOctree on uniform grid of resolution {} using {} threads", clargs.queryResolution, - omp_get_num_threads())); -#else - SLIC_INFO(axom::fmt::format("Querying InOutOctree on uniform grid of resolution {}", - clargs.queryResolution)); -#endif + numOpenMPThreads())); // Add a scalar field for the containment queries - SLIC_ASSERT(clargs.queryMesh != nullptr); - mint::UniformMesh* umesh = clargs.queryMesh; - const axom::IndexType nnodes = umesh->getNumberOfNodes(); + mint::UniformMesh& umesh = ensureQueryMesh(clargs); + const int nnodes = checkedNumNodes(umesh); - int* containment = umesh->createField("octree_containment", mint::NODE_CENTERED); + int* containment = umesh.createField("octree_containment", mint::NODE_CENTERED); SLIC_ASSERT(containment != nullptr); - double* xcoords = new double[nnodes]; - double* ycoords = new double[nnodes]; - double* zcoords = new double[nnodes]; - utilities::Timer fillTimer(true); - -#pragma omp parallel for schedule(static) - for(int inode = 0; inode < nnodes; ++inode) - { - axom::IndexType i, j, k; - umesh->getNodeGridIndex(inode, i, j, k); - - xcoords[inode] = umesh->evaluateCoordinate(i, mint::X_COORDINATE); - ycoords[inode] = umesh->evaluateCoordinate(j, mint::Y_COORDINATE); - zcoords[inode] = umesh->evaluateCoordinate(k, mint::Z_COORDINATE); - } - fillTimer.stop(); + auto coords = makeQueryCoordinates(umesh); utilities::Timer queryTimer(true); - quest::inout_evaluate(xcoords, ycoords, zcoords, nnodes, containment); + quest::inout_evaluate(coords.x.data(), coords.y.data(), coords.z.data(), nnodes, containment); queryTimer.stop(); - SLIC_INFO(axom::fmt::format("Filling coordinates array took {} seconds", fillTimer.elapsed())); - SLIC_INFO( - axom::fmt::format("Querying {}^3 containment field (InOutOctree) " - "took {} seconds (@ {} queries per second)", - clargs.queryResolution, - queryTimer.elapsed(), - nnodes / queryTimer.elapsed())); - - delete[] xcoords; - delete[] ycoords; - delete[] zcoords; + SLIC_INFO(axom::fmt::format(axom::utilities::locale(), + "Filling coordinates array took {:.3Lf} seconds", + coords.fillTime)); + SLIC_INFO(axom::fmt::format(axom::utilities::locale(), + "Querying {}^3 containment field (InOutOctree) took {:.3Lf} seconds " + "(@ {:.0Lf} queries per second)", + clargs.queryResolution, + queryTimer.elapsed(), + nnodes / queryTimer.elapsed())); quest::inout_finalize(); } @@ -405,7 +417,9 @@ void runDistanceQueries(Input& clargs) buildTimer.stop(); - SLIC_INFO(axom::fmt::format("Initialization took {} seconds.", buildTimer.elapsed())); + SLIC_INFO(axom::fmt::format(axom::utilities::locale(), + "Initialization took {:.3Lf} seconds.", + buildTimer.elapsed())); SpacePt bbMin, bbMax; quest::signed_distance_get_mesh_bounds(bbMin.data(), bbMax.data()); @@ -416,56 +430,26 @@ void runDistanceQueries(Input& clargs) clargs.meshBoundingBox.scale(1.5); } - if(!clargs.hasQueryMesh()) - { - clargs.queryMesh = createQueryMesh(clargs.meshBoundingBox, clargs.queryResolution); - } - SLIC_INFO("Mesh bounding box is: " << SpaceBoundingBox(bbMin, bbMax)); SLIC_INFO("Query bounding box is: " << clargs.meshBoundingBox); -#ifdef AXOM_USE_OPENMP - #pragma omp parallel - #pragma omp master - SLIC_INFO( - axom::fmt::format("Querying BVH on uniform grid " - "of resolution {} using {} threads", - clargs.queryResolution, - omp_get_num_threads())); -#else - SLIC_INFO( - axom::fmt::format("Querying BVH on uniform grid of resolution {}", clargs.queryResolution)); -#endif + SLIC_INFO(axom::fmt::format("Querying BVH on uniform grid of resolution {} using {} threads", + clargs.queryResolution, + numOpenMPThreads())); // Add a scalar field for the containment queries - SLIC_ASSERT(clargs.queryMesh != nullptr); - axom::mint::UniformMesh* umesh = clargs.queryMesh; - const int nnodes = umesh->getNumberOfNodes(); + mint::UniformMesh& umesh = ensureQueryMesh(clargs); + const int nnodes = checkedNumNodes(umesh); - int* containment = umesh->createField("bvh_containment", axom::mint::NODE_CENTERED); - double* distance = umesh->createField("bvh_distance", axom::mint::NODE_CENTERED); + int* containment = umesh.createField("bvh_containment", mint::NODE_CENTERED); + double* distance = umesh.createField("bvh_distance", mint::NODE_CENTERED); SLIC_ASSERT(containment != nullptr); SLIC_ASSERT(distance != nullptr); - double* xcoords = new double[nnodes]; - double* ycoords = new double[nnodes]; - double* zcoords = new double[nnodes]; - utilities::Timer fillTimer(true); - -#pragma omp parallel for schedule(static) - for(int inode = 0; inode < nnodes; ++inode) - { - axom::IndexType i, j, k; - umesh->getNodeGridIndex(inode, i, j, k); - - xcoords[inode] = umesh->evaluateCoordinate(i, axom::mint::X_COORDINATE); - ycoords[inode] = umesh->evaluateCoordinate(j, axom::mint::Y_COORDINATE); - zcoords[inode] = umesh->evaluateCoordinate(k, axom::mint::Z_COORDINATE); - } - fillTimer.stop(); + auto coords = makeQueryCoordinates(umesh); utilities::Timer distanceTimer(true); - quest::signed_distance_evaluate(xcoords, ycoords, zcoords, nnodes, distance); + quest::signed_distance_evaluate(coords.x.data(), coords.y.data(), coords.z.data(), nnodes, distance); distanceTimer.stop(); for(int inode = 0; inode < nnodes; ++inode) @@ -473,17 +457,15 @@ void runDistanceQueries(Input& clargs) containment[inode] = (std::signbit(distance[inode]) != 0) ? 1 : 0; } - SLIC_INFO(axom::fmt::format("Filling coordinates array took {} seconds", fillTimer.elapsed())); - SLIC_INFO( - axom::fmt::format("Querying {}^3 signed distance field (BVH) " - "took {} seconds (@ {} queries per second)", - clargs.queryResolution, - distanceTimer.elapsed(), - nnodes / distanceTimer.elapsed())); - - delete[] xcoords; - delete[] ycoords; - delete[] zcoords; + SLIC_INFO(axom::fmt::format(axom::utilities::locale(), + "Filling coordinates array took {:.3Lf} seconds", + coords.fillTime)); + SLIC_INFO(axom::fmt::format( + axom::utilities::locale(), + "Querying {}^3 signed distance field (BVH) took {:.3Lf} seconds (@ {:.0Lf} queries per second)", + clargs.queryResolution, + distanceTimer.elapsed(), + nnodes / distanceTimer.elapsed())); quest::signed_distance_finalize(); } @@ -494,26 +476,22 @@ void runDistanceQueries(Input& clargs) * \return True if all results agree, False otherwise. * \note When there are differences, the first few are logged */ -bool compareDistanceAndContainment(Input& clargs) +bool compareDistanceAndContainment(const Input& clargs) { SLIC_ASSERT(clargs.hasQueryMesh()); bool passed = true; - mint::UniformMesh* umesh = clargs.queryMesh; - const int nnodes = umesh->getNumberOfNodes(); + const mint::UniformMesh& umesh = *clargs.queryMesh; + const int nnodes = checkedNumNodes(umesh); - if(!clargs.testContainment) - { - SLIC_INFO("Cannot compare signed distance and InOutOctree " - << "-- InOutOctree was not generated"); - } + SLIC_INFO_IF( + !clargs.testContainment, + "Cannot compare signed distance and InOutOctree " << "-- InOutOctree was not generated"); - if(!clargs.testDistance) - { - SLIC_INFO("Cannot compare signed distance and InOutOctree " - << "-- Signed distance was not generated"); - } + SLIC_INFO_IF( + !clargs.testDistance, + "Cannot compare signed distance and InOutOctree " << "-- Signed distance was not generated"); if(clargs.testContainment && clargs.testDistance) { @@ -521,9 +499,9 @@ bool compareDistanceAndContainment(Input& clargs) int diffCount = 0; axom::fmt::memory_buffer out; - int* oct_containment = umesh->getFieldPtr("octree_containment", mint::NODE_CENTERED); - int* bvh_containment = umesh->getFieldPtr("bvh_containment", mint::NODE_CENTERED); - double* bvh_distance = umesh->getFieldPtr("bvh_distance", mint::NODE_CENTERED); + const int* oct_containment = umesh.getFieldPtr("octree_containment", mint::NODE_CENTERED); + const int* bvh_containment = umesh.getFieldPtr("bvh_containment", mint::NODE_CENTERED); + const double* bvh_distance = umesh.getFieldPtr("bvh_distance", mint::NODE_CENTERED); for(int inode = 0; inode < nnodes; ++inode) { @@ -539,7 +517,7 @@ bool compareDistanceAndContainment(Input& clargs) if(diffCount < MAX_RESULTS) { primal::Point pt; - umesh->getNode(inode, pt.data()); + umesh.getNode(inode, pt.data()); axom::fmt::format_to(std::back_inserter(out), "\n Disagreement on sample {} @ {}. " @@ -575,37 +553,37 @@ bool compareDistanceAndContainment(Input& clargs) * \return True if all results agree, False otherwise. * \note When there are differences, the first few are logged */ -bool compareToBaselineResults(axom::sidre::Group* grp, Input& clargs) +bool compareToBaselineResults(axom::sidre::Group* grp, const Input& clargs) { SLIC_ASSERT(grp != nullptr); SLIC_ASSERT(clargs.hasQueryMesh()); bool passed = true; - mint::UniformMesh* umesh = clargs.queryMesh; - const int nnodes = umesh->getNumberOfNodes(); + const mint::UniformMesh& umesh = *clargs.queryMesh; + const int nnodes = checkedNumNodes(umesh); - int* base_inout_containment = nullptr; - int* exp_inout_containment = nullptr; - int* base_sd_containment = nullptr; - int* exp_sd_containment = nullptr; - double* base_sd_distance = nullptr; - double* exp_sd_distance = nullptr; + const int* base_inout_containment = nullptr; + const int* exp_inout_containment = nullptr; + const int* base_sd_containment = nullptr; + const int* exp_sd_containment = nullptr; + const double* base_sd_distance = nullptr; + const double* exp_sd_distance = nullptr; // grab pointers to data arrays when appropriate if(clargs.testContainment) { base_inout_containment = grp->getView("octree_containment")->getArray(); - exp_inout_containment = umesh->getFieldPtr("octree_containment", mint::NODE_CENTERED); + exp_inout_containment = umesh.getFieldPtr("octree_containment", mint::NODE_CENTERED); } if(clargs.testDistance) { base_sd_containment = grp->getView("bvh_containment")->getArray(); - exp_sd_containment = umesh->getFieldPtr("bvh_containment", mint::NODE_CENTERED); + exp_sd_containment = umesh.getFieldPtr("bvh_containment", mint::NODE_CENTERED); base_sd_distance = grp->getView("bvh_distance")->getArray(); - exp_sd_distance = umesh->getFieldPtr("bvh_distance", mint::NODE_CENTERED); + exp_sd_distance = umesh.getFieldPtr("bvh_distance", mint::NODE_CENTERED); } if(clargs.testContainment) @@ -622,7 +600,7 @@ bool compareToBaselineResults(axom::sidre::Group* grp, Input& clargs) if(diffCount < MAX_RESULTS) { primal::Point pt; - umesh->getNode(inode, pt.data()); + umesh.getNode(inode, pt.data()); axom::fmt::format_to(std::back_inserter(out), "\n Disagreement on sample {} @ {}. Expected {}, got {}", @@ -670,7 +648,7 @@ bool compareToBaselineResults(axom::sidre::Group* grp, Input& clargs) if(diffCount < MAX_RESULTS) { primal::Point pt; - umesh->getNode(inode, pt.data()); + umesh.getNode(inode, pt.data()); axom::fmt::format_to(std::back_inserter(out), "\n Disagreement on sample {} @ {}. Expected {} ({}), got {} ({})", @@ -712,12 +690,8 @@ void saveBaseline(axom::sidre::Group* grp, Input& clargs) SLIC_ASSERT(grp != nullptr); SLIC_ASSERT(clargs.hasQueryMesh()); - std::string fullMeshName = clargs.meshName; - std::size_t found = fullMeshName.find_last_of("/"); - std::string meshName = fullMeshName.substr(found + 1); - - found = meshName.find_last_of("."); - std::string meshNameNoExt = meshName.substr(0, found); + const std::string meshName = axom::Path(clargs.meshName).baseName(); + const std::string meshNameNoExt = withoutExtension(meshName); grp->createViewString("mesh_name", meshName); @@ -731,37 +705,37 @@ void saveBaseline(axom::sidre::Group* grp, Input& clargs) view = grp->createView("query_resolution", sidre::INT_ID, 3)->allocate(); clargs.queryResolution.to_array(view->getArray()); - axom::mint::UniformMesh* umesh = clargs.queryMesh; - const int nnodes = umesh->getNumberOfNodes(); + const mint::UniformMesh& umesh = *clargs.queryMesh; + const int nnodes = checkedNumNodes(umesh); if(clargs.testContainment) { - int* oct_containment = umesh->getFieldPtr("octree_containment", mint::NODE_CENTERED); + const int* oct_containment = umesh.getFieldPtr("octree_containment", mint::NODE_CENTERED); view = grp->createView("octree_containment", sidre::INT_ID, nnodes)->allocate(); int* contData = view->getArray(); - std::copy(oct_containment, oct_containment + nnodes, contData); + std::copy_n(oct_containment, nnodes, contData); } if(clargs.testDistance) { - int* bvh_containment = umesh->getFieldPtr("bvh_containment", mint::NODE_CENTERED); + const int* bvh_containment = umesh.getFieldPtr("bvh_containment", mint::NODE_CENTERED); view = grp->createView("bvh_containment", sidre::INT_ID, nnodes)->allocate(); int* contData = view->getArray(); - std::copy(bvh_containment, bvh_containment + nnodes, contData); + std::copy_n(bvh_containment, nnodes, contData); - double* bvh_distance = umesh->getFieldPtr("bvh_distance", mint::NODE_CENTERED); + const double* bvh_distance = umesh.getFieldPtr("bvh_distance", mint::NODE_CENTERED); view = grp->createView("bvh_distance", sidre::DOUBLE_ID, nnodes)->allocate(); double* distData = view->getArray(); - std::copy(bvh_distance, bvh_distance + nnodes, distData); + std::copy_n(bvh_distance, nnodes, distData); } const GridPt& res = clargs.queryResolution; - bool resAllSame = (res[0] == res[1] && res[1] == res[2]); - std::string resStr = resAllSame ? axom::fmt::format("{}", res[0]) - : axom::fmt::format("{}_{}_{}", res[0], res[1], res[2]); + const bool resAllSame = (res[0] == res[1] && res[1] == res[2]); + const std::string resStr = resAllSame ? axom::fmt::format("{}", res[0]) + : axom::fmt::format("{}_{}_{}", res[0], res[1], res[2]); - std::string outfile = axom::fmt::format("{}_{}_{}", meshNameNoExt, resStr, "baseline"); - std::string protocol = "sidre_hdf5"; + const std::string outfile = axom::fmt::format("{}_{}_{}", meshNameNoExt, resStr, "baseline"); + const std::string protocol = "sidre_hdf5"; sidre::IOManager writer(MPI_COMM_WORLD); writer.write(grp, 1, outfile, protocol); SLIC_INFO(axom::fmt::format("** Saved baseline file '{}' using '{}' protocol.", outfile, protocol)); @@ -773,7 +747,7 @@ int main(int argc, char** argv) bool allTestsPassed = true; // initialize the problem - MPI_Init(&argc, &argv); + axom::utilities::raii::MPIWrapper mpi_raii_wrapper(argc, argv); axom::slic::SimpleLogger logger; sidre::DataStore ds; @@ -851,7 +825,5 @@ int main(int argc, char** argv) SLIC_INFO("--"); #endif - // finalize - MPI_Finalize(); return (allTestsPassed) ? 0 : 1; } From 11e66344127a7a6ac9a8362f8e0841bceb8e3c70 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 9 Jul 2026 18:16:18 -0700 Subject: [PATCH 713/986] Updates submodule to include updates to quest regression baselines --- data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data b/data index 55e6d239e8..75b49d8e65 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 55e6d239e80593acca6b96ce38b2c9b0a6e6ae3e +Subproject commit 75b49d8e65130cd01db765950c7c65ea03fd7767 From 42b62b1d108c16bd02cbf9c71b594e4427d9b990 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 15 Jul 2026 11:36:55 -0700 Subject: [PATCH 714/986] Adds back a debug log --- src/axom/quest/InOutOctree.hpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/axom/quest/InOutOctree.hpp b/src/axom/quest/InOutOctree.hpp index c1e7735b9c..cfd71a5c15 100644 --- a/src/axom/quest/InOutOctree.hpp +++ b/src/axom/quest/InOutOctree.hpp @@ -1302,8 +1302,7 @@ bool InOutOctree::withinGrayBlock3D(const SpacePt& queryPt, return normal.dot(ray.direction()) > 0.; } - // SLIC_DEBUG("Could not determine inside/outside for point " - // << queryPt << " on block " << leafBlk); + SLIC_DEBUG("Could not determine inside/outside for point " << queryPt << " on block " << leafBlk); return false; } From 98938ac1e5ab603cba471a4bbf8f3c26593d5a0a Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 25 Jun 2026 13:18:02 -0700 Subject: [PATCH 715/986] Python: Establish axom namespace and move pysidre to axom.sidre * Renames the extension module from `pysidre` to `_sidre` so can import `axom.sidre` * Adds python package scaffolding to `src/python` * Introduces `AXOM_PYTHON_MODULE_INSTALL_PREFIX` CMake cache variable * Update pysidre usage to `import axom.sidre as pysidre` and adds a shim to warning users that importing `pysidre` is deprecated. --- src/axom/sidre/CMakeLists.txt | 92 ++++++++++++++----- .../examples/sidre_createdatastore_Py.py | 2 +- src/axom/sidre/nanobind_sidre.cpp | 4 +- src/axom/sidre/tests/CMakeLists.txt | 1 + src/axom/sidre/tests/sidre_attribute_Py.py | 2 +- src/axom/sidre/tests/sidre_buffer_Py.py | 2 +- .../sidre/tests/sidre_datastore_unit_Py.py | 2 +- src/axom/sidre/tests/sidre_external_Py.py | 10 +- src/axom/sidre/tests/sidre_group_Py.py | 2 +- src/axom/sidre/tests/sidre_lifetime_Py.py | 14 +-- src/axom/sidre/tests/sidre_pysidre_shim_Py.py | 52 +++++++++++ src/axom/sidre/tests/sidre_smoke_Py.py | 2 +- src/axom/sidre/tests/sidre_spio_Py.py | 2 +- src/axom/sidre/tests/sidre_view_Py.py | 2 +- src/python/README.md | 64 +++++++++++++ src/python/src/axom/__init__.py | 44 +++++++++ src/python/src/axom/py.typed | 0 src/python/src/axom/sidre/__init__.py | 38 ++++++++ src/python/src/pysidre/__init__.py | 31 +++++++ src/tools/CMakeLists.txt | 20 +++- src/tools/convert_sidre_protocol.py | 2 +- 21 files changed, 332 insertions(+), 56 deletions(-) create mode 100644 src/axom/sidre/tests/sidre_pysidre_shim_Py.py create mode 100644 src/python/README.md create mode 100644 src/python/src/axom/__init__.py create mode 100644 src/python/src/axom/py.typed create mode 100644 src/python/src/axom/sidre/__init__.py create mode 100644 src/python/src/pysidre/__init__.py diff --git a/src/axom/sidre/CMakeLists.txt b/src/axom/sidre/CMakeLists.txt index 750473d630..da933dec99 100644 --- a/src/axom/sidre/CMakeLists.txt +++ b/src/axom/sidre/CMakeLists.txt @@ -138,49 +138,91 @@ endif() if(NANOBIND_FOUND) - nanobind_add_module(pysidre nanobind_sidre.cpp) + # Python bindings for Sidre. + # The pure-Python package scaffolding lives once under src/python/src/ + + # site-packages-shaped install root for Axom's Python package(s). + set(AXOM_PYTHON_MODULE_INSTALL_PREFIX + "${CMAKE_INSTALL_PREFIX}/lib/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages" + CACHE PATH + "Install destination for Axom's Python package(s), relative to which 'axom/' is created") + + # Root of the staged package tree in the build directory. + # The build-tree interpreter (and run_python_with_axom.sh, via _PYEXT_DIR) + # puts this single directory on PYTHONPATH and then 'import axom.sidre' works. + set(_axom_py_build_root "${PROJECT_BINARY_DIR}/python") + set(_pysidre_pkg_src "${CMAKE_CURRENT_SOURCE_DIR}/../../python/src") + + nanobind_add_module(_sidre nanobind_sidre.cpp) # conduit::conduit_python provides conduit_python.hpp # and is needed only by the binding translation unit, not by libsidre - target_link_libraries(pysidre PRIVATE sidre conduit::conduit_python) + target_link_libraries(_sidre PRIVATE sidre conduit::conduit_python) + + # Place the built extension directly into the staged package tree so the + # build tree is import-ready without an extra copy step. + set_target_properties(_sidre PROPERTIES + LIBRARY_OUTPUT_DIRECTORY "${_axom_py_build_root}/axom/sidre") # Use HIP executable linker flags for the python module # (CMake treats modules separately from executables, # executable flags not automatically applied) if(AXOM_ENABLE_HIP) - # Make CMake compile the pysidre file with the HIP compiler. + # Make CMake compile the binding file with the HIP compiler. set_source_files_properties(nanobind_sidre.cpp PROPERTIES LANGUAGE HIP) string (REPLACE " " ";" MODULE_LINK_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") - target_link_options(pysidre PRIVATE ${MODULE_LINK_FLAGS}) + target_link_options(_sidre PRIVATE ${MODULE_LINK_FLAGS}) endif() - install(TARGETS pysidre LIBRARY DESTINATION lib) - - # Type stubs (PEP 561). nanobind_add_stub imports the module to introspect it, - # so its runtime dependencies (conduit for Node interop, numpy for ndarray returns) - # must be importable during the build. We seed PYTHON_PATH with the module's - # output directory plus the conduit/numpy install dirs from their cache variables when set; - # on an interpreter that already has conduit and numpy on its path these extra entries are harmless. - set(_pysidre_stub_pythonpath $) + # Stage the pure-Python package scaffolding into the build tree at configure + # time (axom/ namespace root + py.typed, axom/sidre/ re-export, pysidre shim). + axom_configure_file("${_pysidre_pkg_src}/axom/__init__.py" + "${_axom_py_build_root}/axom/__init__.py" COPYONLY) + axom_configure_file("${_pysidre_pkg_src}/axom/py.typed" + "${_axom_py_build_root}/axom/py.typed" COPYONLY) + axom_configure_file("${_pysidre_pkg_src}/axom/sidre/__init__.py" + "${_axom_py_build_root}/axom/sidre/__init__.py" COPYONLY) + axom_configure_file("${_pysidre_pkg_src}/pysidre/__init__.py" + "${_axom_py_build_root}/pysidre/__init__.py" COPYONLY) + + # Type stubs (PEP 561). nanobind_add_stub imports the module by its bare name + # ('import _sidre'), so the directory holding the built extension must be on + # PYTHON_PATH, along with the module's runtime deps (conduit for Node interop, + # numpy for ndarray returns). On an interpreter that already has conduit/numpy + # these extra entries are harmless. + set(_sidre_stub_pythonpath "${_axom_py_build_root}/axom/sidre") if(CONDUIT_PYTHON_MODULE_DIR) - list(APPEND _pysidre_stub_pythonpath ${CONDUIT_PYTHON_MODULE_DIR}) + list(APPEND _sidre_stub_pythonpath ${CONDUIT_PYTHON_MODULE_DIR}) endif() if(PY_NUMPY_DIR) - list(APPEND _pysidre_stub_pythonpath ${PY_NUMPY_DIR}) + list(APPEND _sidre_stub_pythonpath ${PY_NUMPY_DIR}) endif() nanobind_add_stub( - pysidre_stub - MODULE pysidre - OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/pysidre.pyi" - MARKER_FILE "${CMAKE_CURRENT_BINARY_DIR}/py.typed" - PYTHON_PATH ${_pysidre_stub_pythonpath} - DEPENDS pysidre) - - # Install the stub and py.typed marker next to the extension module so type checkers (mypy, pyright) can find them - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/pysidre.pyi" - "${CMAKE_CURRENT_BINARY_DIR}/py.typed" - DESTINATION lib) + _sidre_stub + MODULE _sidre + OUTPUT "${_axom_py_build_root}/axom/sidre/_sidre.pyi" + PYTHON_PATH ${_sidre_stub_pythonpath} + DEPENDS _sidre) + + #-------------------------------------------------------------------------- + # Install the package tree into the site-packages-shaped prefix. + #-------------------------------------------------------------------------- + install(TARGETS _sidre + LIBRARY DESTINATION "${AXOM_PYTHON_MODULE_INSTALL_PREFIX}/axom/sidre") + + install(FILES "${_axom_py_build_root}/axom/sidre/_sidre.pyi" + "${_pysidre_pkg_src}/axom/sidre/__init__.py" + DESTINATION "${AXOM_PYTHON_MODULE_INSTALL_PREFIX}/axom/sidre") + + # Namespace-root package files install once (not per component). + install(FILES "${_pysidre_pkg_src}/axom/__init__.py" + "${_pysidre_pkg_src}/axom/py.typed" + DESTINATION "${AXOM_PYTHON_MODULE_INSTALL_PREFIX}/axom") + + # Deprecation shim for the historical top-level 'pysidre' module. + install(FILES "${_pysidre_pkg_src}/pysidre/__init__.py" + DESTINATION "${AXOM_PYTHON_MODULE_INSTALL_PREFIX}/pysidre") endif() diff --git a/src/axom/sidre/examples/sidre_createdatastore_Py.py b/src/axom/sidre/examples/sidre_createdatastore_Py.py index 0e8961cc43..cd76b66e82 100644 --- a/src/axom/sidre/examples/sidre_createdatastore_Py.py +++ b/src/axom/sidre/examples/sidre_createdatastore_Py.py @@ -4,7 +4,7 @@ # # SPDX-License-Identifier: (BSD-3-Clause) -import pysidre +import axom.sidre as pysidre import numpy as np import numpy.typing as npt diff --git a/src/axom/sidre/nanobind_sidre.cpp b/src/axom/sidre/nanobind_sidre.cpp index 9c04940034..44e3af6ecd 100644 --- a/src/axom/sidre/nanobind_sidre.cpp +++ b/src/axom/sidre/nanobind_sidre.cpp @@ -511,7 +511,9 @@ class PyIOManager }; #endif -NB_MODULE(pysidre, m_sidre) +// The extension installs as ``axom/sidre/_sidre..so`` and is re-exported +// by the ``axom.sidre`` package (see src/python/src/axom/sidre/__init__.py). +NB_MODULE(_sidre, m_sidre) { m_sidre.doc() = R"pbdoc( A python extension for Axom's Sidre component. diff --git a/src/axom/sidre/tests/CMakeLists.txt b/src/axom/sidre/tests/CMakeLists.txt index 8b34e2648b..71302a46a4 100644 --- a/src/axom/sidre/tests/CMakeLists.txt +++ b/src/axom/sidre/tests/CMakeLists.txt @@ -58,6 +58,7 @@ set(python_sidre_tests sidre_external_Py.py sidre_attribute_Py.py sidre_lifetime_Py.py + sidre_pysidre_shim_Py.py ) set(sidre_gtests_depends_on sidre fmt gtest) diff --git a/src/axom/sidre/tests/sidre_attribute_Py.py b/src/axom/sidre/tests/sidre_attribute_Py.py index 0867ef1e01..4efe9290d1 100644 --- a/src/axom/sidre/tests/sidre_attribute_Py.py +++ b/src/axom/sidre/tests/sidre_attribute_Py.py @@ -4,7 +4,7 @@ # # SPDX-License-Identifier: (BSD-3-Clause) -import pysidre +import axom.sidre as pysidre import numpy as np import conduit diff --git a/src/axom/sidre/tests/sidre_buffer_Py.py b/src/axom/sidre/tests/sidre_buffer_Py.py index a42db76480..732cdf3912 100644 --- a/src/axom/sidre/tests/sidre_buffer_Py.py +++ b/src/axom/sidre/tests/sidre_buffer_Py.py @@ -4,7 +4,7 @@ # # SPDX-License-Identifier: (BSD-3-Clause) -import pysidre +import axom.sidre as pysidre import numpy as np NUM_BYTES_INT_32 = 4 diff --git a/src/axom/sidre/tests/sidre_datastore_unit_Py.py b/src/axom/sidre/tests/sidre_datastore_unit_Py.py index d90f0b5b38..5cd290e739 100644 --- a/src/axom/sidre/tests/sidre_datastore_unit_Py.py +++ b/src/axom/sidre/tests/sidre_datastore_unit_Py.py @@ -4,7 +4,7 @@ # # SPDX-License-Identifier: (BSD-3-Clause) -import pysidre +import axom.sidre as pysidre import random diff --git a/src/axom/sidre/tests/sidre_external_Py.py b/src/axom/sidre/tests/sidre_external_Py.py index d08c1145b2..aa82c075ab 100644 --- a/src/axom/sidre/tests/sidre_external_Py.py +++ b/src/axom/sidre/tests/sidre_external_Py.py @@ -4,13 +4,13 @@ # # SPDX-License-Identifier: (BSD-3-Clause) -import pysidre +import axom.sidre as pysidre import numpy as np from conduit import Node -############################################################################### +# ------------------------------------------------------------------------------ # Tests from sidre_external.cpp -############################################################################### +# ------------------------------------------------------------------------------ def test_create_external_view(): @@ -215,9 +215,9 @@ def test_save_load_external_view(): assert ddata_chk[ii] == ddata[ii] -############################################################################### +# ------------------------------------------------------------------------------ # Tests from sidre_external_F.f -############################################################################### +# ------------------------------------------------------------------------------ # External numpy array via python diff --git a/src/axom/sidre/tests/sidre_group_Py.py b/src/axom/sidre/tests/sidre_group_Py.py index 9fbf58335d..104e98af9a 100644 --- a/src/axom/sidre/tests/sidre_group_Py.py +++ b/src/axom/sidre/tests/sidre_group_Py.py @@ -4,7 +4,7 @@ # # SPDX-License-Identifier: (BSD-3-Clause) -import pysidre +import axom.sidre as pysidre import numpy as np from conduit import Node diff --git a/src/axom/sidre/tests/sidre_lifetime_Py.py b/src/axom/sidre/tests/sidre_lifetime_Py.py index b4f535fb43..bd1a89b9a4 100644 --- a/src/axom/sidre/tests/sidre_lifetime_Py.py +++ b/src/axom/sidre/tests/sidre_lifetime_Py.py @@ -5,16 +5,8 @@ # SPDX-License-Identifier: (BSD-3-Clause) """Lifetime-soundness regression tests for the sidre python bindings. -Each test obtains a sidre-owned object (child proxy, ancestor proxy, harvested -iterator element, or zero-copy numpy array), drops every Python owner, forces a -garbage collection, and then *uses* the object. Before the lifetime audit these -patterns dereferenced freed memory and segfaulted; with reference_internal on -owner-chain accessors, keep_alive on iterator elements, and self-as-owner on -returned arrays, the keep_alive graph keeps the backing DataStore alive and the -accesses are safe. - -These tests therefore only "pass" against the audited bindings; against the -prior bindings they crash the interpreter (the failure mode the audit fixes). +Each test obtains a sidre-owned object (child or ancestor proxy, iterator, zero-copy numpy array), +drops every Python owner, forces a garbage collection, and then uses the object. """ import gc @@ -23,7 +15,7 @@ import numpy as np import pytest -import pysidre +import axom.sidre as pysidre def _force_gc(): diff --git a/src/axom/sidre/tests/sidre_pysidre_shim_Py.py b/src/axom/sidre/tests/sidre_pysidre_shim_Py.py new file mode 100644 index 0000000000..50aa05377c --- /dev/null +++ b/src/axom/sidre/tests/sidre_pysidre_shim_Py.py @@ -0,0 +1,52 @@ +# 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) +"""Tests for the deprecated 'pysidre' compatibility shim. + +The Sidre bindings moved from a top-level 'pysidre' module to the 'axom.sidre' package. +'pysidre' survives as a deprecation shim that re-exports 'axom.sidre' and warns on import. +These tests check that the import keeps working, warns once, and exposes the same objects as 'axom.sidre'. +""" + +import importlib +import sys +import warnings + + +def _fresh_import_pysidre(): + """Import 'pysidre' with a clean module cache so its import-time + DeprecationWarning is (re)emitted deterministically.""" + sys.modules.pop("pysidre", None) + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter("always") + module = importlib.import_module("pysidre") + deprecations = [w for w in caught if issubclass(w.category, DeprecationWarning)] + return module, deprecations + + +def test_pysidre_import_warns_once(): + _module, deprecations = _fresh_import_pysidre() + assert len(deprecations) == 1 + assert "axom.sidre" in str(deprecations[0].message) + + +def test_pysidre_reexports_axom_sidre(): + import axom.sidre as sidre + + pysidre, _ = _fresh_import_pysidre() + + # Core symbols resolve, and to the *same* objects as axom.sidre. + assert pysidre.DataStore is sidre.DataStore + assert pysidre.InvalidIndex == sidre.InvalidIndex + assert pysidre.__version__ == sidre.__version__ + + +def test_pysidre_datastore_roundtrip(): + pysidre, _ = _fresh_import_pysidre() + ds = pysidre.DataStore() + root = ds.getRoot() + grp = root.createGroup("via_shim") + assert root.hasGroup("via_shim") + assert grp.getName() == "via_shim" diff --git a/src/axom/sidre/tests/sidre_smoke_Py.py b/src/axom/sidre/tests/sidre_smoke_Py.py index 78bc39a943..59a6385f01 100644 --- a/src/axom/sidre/tests/sidre_smoke_Py.py +++ b/src/axom/sidre/tests/sidre_smoke_Py.py @@ -4,7 +4,7 @@ # # SPDX-License-Identifier: (BSD-3-Clause) -import pysidre +import axom.sidre as pysidre from conduit import Node diff --git a/src/axom/sidre/tests/sidre_spio_Py.py b/src/axom/sidre/tests/sidre_spio_Py.py index ff42e277cb..0a8cef6e01 100644 --- a/src/axom/sidre/tests/sidre_spio_Py.py +++ b/src/axom/sidre/tests/sidre_spio_Py.py @@ -17,7 +17,7 @@ import pytest -import pysidre +import axom.sidre as pysidre if not pysidre.AXOM_ENABLE_MPI: pytest.skip("pysidre built without MPI", allow_module_level=True) diff --git a/src/axom/sidre/tests/sidre_view_Py.py b/src/axom/sidre/tests/sidre_view_Py.py index e8def9d38e..93558b7781 100644 --- a/src/axom/sidre/tests/sidre_view_Py.py +++ b/src/axom/sidre/tests/sidre_view_Py.py @@ -4,7 +4,7 @@ # # SPDX-License-Identifier: (BSD-3-Clause) -import pysidre +import axom.sidre as pysidre import numpy as np NUM_BYTES_INT_32 = 4 diff --git a/src/python/README.md b/src/python/README.md new file mode 100644 index 0000000000..d898a439d0 --- /dev/null +++ b/src/python/README.md @@ -0,0 +1,64 @@ +[comment]: # (#################################################################) +[comment]: # (Copyright Lawrence Livermore National Security, LLC and other) +[comment]: # (Axom Project Contributors. See top-level LICENSE and COPYRIGHT) +[comment]: # (files for dates and other details.) +[comment]: # +[comment]: # (# SPDX-License-Identifier: BSD-3-Clause) +[comment]: # (#################################################################) + +# Axom Python package source + +This directory (`src/python/`) holds the canonical source of Axom's Python package, `axom`. +It is consumed by two independent build paths that must produce the same on-disk layout: + +1. **The CMake build (in tree).** When Axom is configured with a component's Python bindings enabled (currently Sidre), + the build stages this tree into the build directory and installs it into a `site-packages`-shaped prefix. + See `src/axom/sidre/CMakeLists.txt`: it copies the files below into `${PROJECT_BINARY_DIR}/python/` (so the build tree is import-ready) + and installs them under `AXOM_PYTHON_MODULE_INSTALL_PREFIX`. + The compiled extension (`_sidre`) and its type stub are emitted into this layout by the build; they are not checked in. + +2. **[planned] The pip/uv wheel (out of tree).** A thin, binding-only wheel built with scikit-build-core + will treat this directory as its package root (`wheel.packages = ["src/axom", "src/pysidre"]` in a sibling `pyproject.toml`), + compiling the binding translation unit against an already-installed Axom. + + +## Layout + +This is a standard "src layout" Python project root: + +``` +src/python/ + README.md <- this file + src/ + axom/ <- the 'axom' namespace package (regular package) + __init__.py <- package version (sourced from the extension) + py.typed <- PEP 561 marker (typed package) + sidre/ + __init__.py <- re-exports the compiled 'axom.sidre._sidre' + (_sidre..so) <- compiled extension, produced by the build + (_sidre.pyi) <- type stub, produced by the build + pysidre/ + __init__.py <- deprecation shim re-exporting 'axom.sidre' +``` + +Parenthesized entries are build products and are intentionally not in the repository. + +Each bound Axom component installs as a submodule of the `axom` package +(`axom.sidre`, and later `axom.quest`, `axom.primal`, ...). +A submodule is importable only when its component was enabled in the underlying Axom build. + +## What goes here vs. what does not + +- **Here:** importable pure-Python sources that are part of the installed package: + package `__init__.py` files, the `py.typed` marker, and any future pure-Python helpers or shims. +- **Not here:** the C++ binding code (each component's nanobind translation unit lives with that component, + e.g. `src/axom/sidre/nanobind_sidre.cpp`), + generated artifacts (the `.so` and `.pyi` are produced by the build), + and tests/examples (those live under the component, e.g. `src/axom/sidre/tests/*_Py.py`). + +## Notes + +- These files are installed verbatim (no template substitution). They contain no CMake-configured values. +- A `pyproject.toml` for the standalone wheel is not present yet. We will add it in the future when we add the wheel. + Until then this directory is consumed only by the CMake build. +- End-user instructions for installing and importing the bindings currently live in the Sidre user guide's "Python interface" page. diff --git a/src/python/src/axom/__init__.py b/src/python/src/axom/__init__.py new file mode 100644 index 0000000000..c7799bc27a --- /dev/null +++ b/src/python/src/axom/__init__.py @@ -0,0 +1,44 @@ +# 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) + +"""Python bindings for `LLNL Axom `_. + +Axom is a CS infrastructure library for high-performance computing applications. +Each bound Axom component is exposed as a submodule of this ``axom`` package (for example :mod:`axom.sidre`). +A submodule is importable only when the corresponding component was enabled in the underlying Axom build. +Importing a component that was not built raises :class:`ImportError` with a message naming the missing component. + +The set of submodules present in a given installation therefore mirrors +the ``AXOM_ENABLE_`` configuration of the Axom build the bindings were compiled against. +""" + +# ``axom`` is a regular package (it ships this ``__init__.py``), not an +# implicit namespace package. All bound components install into this single +# package directory from one Axom build; mixing components from different +# builds is unsupported (see the build-id discussion in the bindings design +# notes). + +__all__ = ["__version__"] + + +def _discover_version() -> str: + """Return the Axom version string. + + The version is owned by the C++ build (``AXOM_VERSION_FULL`` in ``axom/config.hpp``) + and surfaced on each extension module's ``__version__`` attribute. + We read it from the ``sidre`` extension when present so there is a single source of truth. + If no component extension is importable (an unusual, effectively content-free install) + we fall back to a sentinel rather than failing the package import. + """ + try: + from axom.sidre import _sidre # noqa: WPS433 (local import is intentional) + + return _sidre.__version__ + except Exception: # pragma: no cover - defensive; see docstring + return "0+unknown" + + +__version__ = _discover_version() diff --git a/src/python/src/axom/py.typed b/src/python/src/axom/py.typed new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/python/src/axom/sidre/__init__.py b/src/python/src/axom/sidre/__init__.py new file mode 100644 index 0000000000..8326001f1b --- /dev/null +++ b/src/python/src/axom/sidre/__init__.py @@ -0,0 +1,38 @@ +# 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) + +"""Python bindings for Axom's Sidre component. + +This package re-exports the compiled ``axom.sidre._sidre`` extension module. +The extension is present only when Axom was configured with Sidre and Python bindings enabled. +If it is missing, importing :mod:`axom.sidre` raises a :class:`ImportError` that names the component, +rather than surfacing an opaque loader error. +""" + +try: + from . import _sidre +except ImportError as exc: # pragma: no cover - exercised only in partial installs + raise ImportError( + "The 'axom.sidre' extension module ('_sidre') is not available in this " + "installation. It is built only when Axom is configured with the Sidre " + "component and Python bindings enabled " + "(AXOM_ENABLE_SIDRE=ON together with nanobind). Rebuild Axom with those " + "options, or install a build that includes them, to use axom.sidre." + ) from exc + +# Re-export the extension's public surface so ``axom.sidre.DataStore`` etc. +# resolve directly on this package. ``_sidre.__all__`` is not defined by the +# nanobind module, so fall back to a filtered ``dir()`` that drops dunders and +# the private extension handle itself. +__version__ = _sidre.__version__ + +__all__ = [_name for _name in dir(_sidre) if not _name.startswith("_")] + +globals().update({_name: getattr(_sidre, _name) for _name in __all__}) + +# ``__version__`` is conventionally public but intentionally excluded from the +# wildcard surface above (it starts with an underscore); expose it explicitly. +__all__.append("__version__") diff --git a/src/python/src/pysidre/__init__.py b/src/python/src/pysidre/__init__.py new file mode 100644 index 0000000000..79fd3f8438 --- /dev/null +++ b/src/python/src/pysidre/__init__.py @@ -0,0 +1,31 @@ +# 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) + +"""Deprecated compatibility shim for the former top-level ``pysidre`` module. + +Axom's Sidre Python bindings used to install as a bare top-level extension module named ``pysidre``. +They now live in the :mod:`axom.sidre` package. +This shim re-exports :mod:`axom.sidre` under the old name so that existing ``import pysidre`` code keeps working, +and emits a single :class:`DeprecationWarning` on import. + +The shim will be removed in the future, and code should ``import axom.sidre`` directly. +""" + +import warnings as _warnings + +_warnings.warn( + "'pysidre' is deprecated and will be removed in a future Axom release; " + "import 'axom.sidre' instead.", + DeprecationWarning, + stacklevel=2, +) + +# Re-export everything axom.sidre exposes, under the legacy module name. +from axom.sidre import * # noqa: F401,F403 (intentional re-export) +from axom.sidre import __all__ as _sidre_all +from axom.sidre import __version__ # noqa: F401 + +__all__ = list(_sidre_all) diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index 9f3f442dc6..cdf280dbd3 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -190,14 +190,24 @@ if(NANOBIND_FOUND) # Based on Conduit's run_python_with_conduit.sh.in script. #-------------------------------------------------------------------------- - # gen python helper to build directory - set(_PYEXT_DIR ${PROJECT_BINARY_DIR}/lib) + # gen python helper to build directory. + # _PYEXT_DIR is the site-packages-shaped root that holds the 'axom' package + # (and the 'pysidre' shim); a single PYTHONPATH entry makes 'import axom.sidre' + # and 'import pysidre' resolve. The Sidre bindings stage that tree under + # ${PROJECT_BINARY_DIR}/python (see src/axom/sidre/CMakeLists.txt). + set(_PYEXT_DIR ${PROJECT_BINARY_DIR}/python) axom_configure_file ("${CMAKE_CURRENT_SOURCE_DIR}/run_python_with_axom.sh.in" "${PROJECT_BINARY_DIR}/bin/run_python_with_axom.sh" @ONLY) - # gen python helper to install directory - set(_PYEXT_DIR ${CMAKE_INSTALL_PREFIX}/lib) + # gen python helper to install directory. + # Mirror the installed package root. Fall back to the lib dir if the + # Python package prefix was never set (e.g. Sidre/bindings disabled). + if(AXOM_PYTHON_MODULE_INSTALL_PREFIX) + set(_PYEXT_DIR ${AXOM_PYTHON_MODULE_INSTALL_PREFIX}) + else() + set(_PYEXT_DIR ${CMAKE_INSTALL_PREFIX}/lib) + endif() axom_configure_file ("${CMAKE_CURRENT_SOURCE_DIR}/run_python_with_axom.sh.in" "${CMAKE_INSTALL_PREFIX}/bin/run_python_with_axom.sh" @ONLY) @@ -212,7 +222,7 @@ if(NANOBIND_FOUND) if(AXOM_ENABLE_TESTS AND AXOM_ENABLE_SIDRE) axom_add_test( NAME run_python_with_axom_build - COMMAND ${PROJECT_BINARY_DIR}/bin/run_python_with_axom.sh -c "import pysidre, conduit, numpy") + COMMAND ${PROJECT_BINARY_DIR}/bin/run_python_with_axom.sh -c "import axom.sidre, conduit, numpy") if(AXOM_ENABLE_PYTHON_TESTS) # The pytest harness is provided per-test via the ENVIRONMENT property; diff --git a/src/tools/convert_sidre_protocol.py b/src/tools/convert_sidre_protocol.py index 0a36242039..2c1b82a4e8 100644 --- a/src/tools/convert_sidre_protocol.py +++ b/src/tools/convert_sidre_protocol.py @@ -29,7 +29,7 @@ from pathlib import Path import numpy as np -import pysidre +import axom.sidre as pysidre VALID_PROTOCOLS = ( "json", From bc208f0606c9a3fecc0b64ca240e16e456764c82 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 25 Jun 2026 13:38:12 -0700 Subject: [PATCH 716/986] Spack: Make Axom a Python extension for the bindings This allows spack environment views to place Axom's installed Python packages onto the interpreter path automatically. This commit also emits `AXOM_PYTHON_MODULE_INSTALL_PREFIX` in the generated host-config. --- scripts/spack/packages/axom/package.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/scripts/spack/packages/axom/package.py b/scripts/spack/packages/axom/package.py index a0f54db43c..dc7c1d305f 100644 --- a/scripts/spack/packages/axom/package.py +++ b/scripts/spack/packages/axom/package.py @@ -277,15 +277,18 @@ class Axom(CachedCMakePackage, CudaPackage, ROCmPackage): depends_on("mfem~mpi", when="~mpi") depends_on("mfem@4.5.0:", when="@0.7.0:") - depends_on("python", when="+python") - # Python with when("+python"): + depends_on("python") + + # extending python allows spack environment views to import axom from python + extends("python") + depends_on("py-nanobind@2.7.0:") depends_on("py-pytest") depends_on("py-numpy") depends_on("py-mpi4py", when="+mpi") - depends_on("conduit+python") + depends_on("conduit+python", when="+conduit") # Devtools with when("+devtools"): @@ -757,6 +760,17 @@ def initconfig_package_entries(self): python_bin_dir = get_spec_path(spec, "python", path_replacements, use_bin=True) entries.append(cmake_cache_path("Python_EXECUTABLE", pjoin(python_bin_dir, "python3"))) + if spec.satisfies("+python"): + # Install Axom's Python package(s) so a spack environment view merges them into + # a single site-packages and `import axom.sidre` works without updating PYTHONPATH + axom_prefix = os.path.realpath(spec.prefix) + for key in path_replacements: + axom_prefix = axom_prefix.replace(key, path_replacements[key]) + py_platlib = pjoin(axom_prefix, spec["python"].package.platlib) + entries.append( + cmake_cache_path("AXOM_PYTHON_MODULE_INSTALL_PREFIX", py_platlib) + ) + if spec.satisfies("^py-jsonschema"): jsonschema_dir = get_spec_path(spec, "py-jsonschema", path_replacements, use_bin=True) jsonschema_path = os.path.join(jsonschema_dir, "jsonschema") From 9317cc24345cafaf14028a8a3fb60b1eecb2b6f9 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 25 Jun 2026 14:01:35 -0700 Subject: [PATCH 717/986] Python: Tests now run direction through python (without wrapper) --- src/axom/sidre/examples/CMakeLists.txt | 11 ++++++-- src/cmake/AxomMacros.cmake | 38 ++++++++++++++++++++------ src/tools/CMakeLists.txt | 31 ++++++++------------- src/tools/run_python_with_axom.sh.in | 19 +++++++++---- 4 files changed, 63 insertions(+), 36 deletions(-) diff --git a/src/axom/sidre/examples/CMakeLists.txt b/src/axom/sidre/examples/CMakeLists.txt index 23cde33e30..721243d3c6 100644 --- a/src/axom/sidre/examples/CMakeLists.txt +++ b/src/axom/sidre/examples/CMakeLists.txt @@ -154,12 +154,19 @@ if(NANOBIND_FOUND) axom_configure_file ("${example_source}" "${EXAMPLE_OUTPUT_DIRECTORY}/${example_source}" COPYONLY) - # Use convenience script to run python examples + # Run python examples directly under the interpreter (no wrapper). + # The runtime environment is supplied via the test's ENVIRONMENT property if(AXOM_ENABLE_PYTHON_TESTS) axom_add_test ( NAME ${exe_name} - COMMAND ${PROJECT_BINARY_DIR}/bin/run_python_with_axom.sh ${EXAMPLE_OUTPUT_DIRECTORY}/${example_source} + COMMAND ${Python_EXECUTABLE} ${EXAMPLE_OUTPUT_DIRECTORY}/${example_source} ) + axom_python_test_environment(_py_example_env) + if(_py_example_env) + set_property(TEST ${exe_name} + APPEND PROPERTY ENVIRONMENT "${_py_example_env}") + endif() + unset(_py_example_env) endif() endforeach() endif() diff --git a/src/cmake/AxomMacros.cmake b/src/cmake/AxomMacros.cmake index 02a0cd6d0e..d24b4d3642 100644 --- a/src/cmake/AxomMacros.cmake +++ b/src/cmake/AxomMacros.cmake @@ -606,17 +606,36 @@ endmacro(axom_configure_file) ##------------------------------------------------------------------------------ ## axom_python_test_environment() ## -## Composes the ENVIRONMENT entry ("PYTHONPATH=::...") that provides -## the pytest paths (pytest and its dependencies) from their respective CMake cache variables. +## Composes the single ENVIRONMENT entry ("PYTHONPATH=::...") needed to +## run Axom's Python tests directly under ${Python_EXECUTABLE} without a wrapper script. ## -## Note: runtime dependencies (e.g. axom, conduit, numpy) are expected to be preprended -## via the run_python_with_axom.sh script. +## We assemble one path list here, ordered: +## +## 1. the staged Python package tree (axom/ + pysidre shim) -- runtime +## 2. conduit's python module dir -- runtime +## 3. numpy, then mpi4py (MPI configs) -- runtime +## 4. pytest and its dependencies (pluggy, iniconfig) -- test harness +## +## Axom's own package tree comes first so it is preferred over anything the +## interpreter might also provide. Entries whose cache variable is unset are skipped; +## conduit/numpy/etc. already on the interpreter's path make the corresponding entries harmless no-ops. ##------------------------------------------------------------------------------ function(axom_python_test_environment output_var) set(_paths "") + + # (1) staged package tree -- mirrors run_python_with_axom.sh's _PYEXT_DIR + blt_list_append(TO _paths ELEMENTS "${PROJECT_BINARY_DIR}/python") + + # (2,3) runtime dependencies + foreach(_var CONDUIT_PYTHON_MODULE_DIR PY_NUMPY_DIR PY_MPI4PY_DIR) + blt_list_append(TO _paths ELEMENTS "${${_var}}" IF ${_var}) + endforeach() + + # (4) test-harness dependencies foreach(_var PY_PYTEST_DIR PY_PLUGGY_DIR PY_INICONFIG_DIR) blt_list_append(TO _paths ELEMENTS "${${_var}}" IF ${_var}) endforeach() + if(_paths) list(JOIN _paths ":" _joined) set(${output_var} "PYTHONPATH=${_joined}" PARENT_SCOPE) @@ -648,11 +667,14 @@ macro(axom_add_python_test) axom_configure_file ("${arg_SOURCE}" "${arg_OUTPUT_DIR}/${arg_SOURCE}" COPYONLY) - # Run unit test with pytest ("python3 -m pytest"). - # The run_python_with_axom.sh wrapper provides the runtime environment - # and the testing dependencies are injected via the test's ENVIRONMENT property when provided. + # Run unit test with pytest ("python3 -m pytest"), invoked directly rather + # than through the run_python_with_axom.sh wrapper. The full runtime + test + # environment is supplied via the test's ENVIRONMENT property (a single + # combined PYTHONPATH; see axom_python_test_environment). Running pytest + # natively keeps the tests composable with IDEs/debuggers and removes the + # bash-only wrapper from the test path. # "-p no:cacheprovider" disables caching. - set(_test_command ${PROJECT_BINARY_DIR}/bin/run_python_with_axom.sh + set(_test_command ${Python_EXECUTABLE} -m pytest -s -p no:cacheprovider ${arg_OUTPUT_DIR}/${arg_SOURCE}) blt_add_test(NAME ${arg_NAME} COMMAND ${_test_command} diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index cdf280dbd3..f23fe0e8ab 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -214,29 +214,11 @@ if(NANOBIND_FOUND) unset(_PYEXT_DIR) - # Smoke tests for the script. - # The wrapper provides the runtime environment only: - # Axom extensions, conduit (Node interop), numpy (ndarray returns), plus mpi4py in MPI configurations. - # nanobind is a build-time dependency (statically linked into the extensions). - # pytest/pluggy/iniconfig are test-harness dependencies. + # Smoke test for the script: verify it makes Axom's bindings and runtime dependencies importable if(AXOM_ENABLE_TESTS AND AXOM_ENABLE_SIDRE) axom_add_test( NAME run_python_with_axom_build COMMAND ${PROJECT_BINARY_DIR}/bin/run_python_with_axom.sh -c "import axom.sidre, conduit, numpy") - - if(AXOM_ENABLE_PYTHON_TESTS) - # The pytest harness is provided per-test via the ENVIRONMENT property; - # verify that the wrapper + injected ENVIRONMENT combination resolves. - axom_add_test( - NAME run_python_with_axom_pytest_harness - COMMAND ${PROJECT_BINARY_DIR}/bin/run_python_with_axom.sh -c "import pytest") - axom_python_test_environment(_py_test_env) - if(_py_test_env) - set_tests_properties(run_python_with_axom_pytest_harness - PROPERTIES ENVIRONMENT "${_py_test_env}") - endif() - unset(_py_test_env) - endif() endif() #-------------------------------------------------------------------------- @@ -256,14 +238,23 @@ if(NANOBIND_FOUND) set(_testname "convert_sidre_protocol_py") axom_add_test( NAME ${_testname} - COMMAND ${PROJECT_BINARY_DIR}/bin/run_python_with_axom.sh + COMMAND ${Python_EXECUTABLE} ${PROJECT_BINARY_DIR}/bin/convert_sidre_protocol.py --input ${box_dir} --output csp_output --protocol json --verbose + NUM_MPI_TASKS 3 ) + axom_python_test_environment(_csp_py_env) + if(_csp_py_env) + set_property(TEST ${_testname} + APPEND + PROPERTY ENVIRONMENT "${_csp_py_env}") + endif() + unset(_csp_py_env) + set_tests_properties(${_testname} PROPERTIES PASS_REGULAR_EXPRESSION "Writing out datastore") endif() diff --git a/src/tools/run_python_with_axom.sh.in b/src/tools/run_python_with_axom.sh.in index 31670449f1..200e70f07b 100755 --- a/src/tools/run_python_with_axom.sh.in +++ b/src/tools/run_python_with_axom.sh.in @@ -7,15 +7,22 @@ # SPDX-License-Identifier: (BSD-3-Clause) ##----------------------------------------------------------------------------- -## Convenience script that runs the python interpreter with Axom's extension(s) -## and their runtime dependencies in the PYTHONPATH: -## - Axom's extension modules (e.g. pysidre) +## Convenience script that runs the python interpreter with Axom's Python package(s) +## and their runtime dependencies already on PYTHONPATH: +## - Axom's Python package tree (the 'axom' namespace package; e.g. axom.sidre) ## - conduit's python module (conduit::Node interop) ## - numpy (ndarray returns) ## - mpi4py (only populated in MPI-enabled configurations) ## -## Build-time (nanobind) and testing dependencies (pytest/pluggy/iniconfig) -## are intentionally NOT added here. They are expected to be added to the PYTHONPATH -## via the test's ENVIRONMENT property +## This is the supported way to run an ad hoc, non-test Python script against a +## build tree that has the bindings enabled -- it assembles conduit/numpy/mpi4py +## from their spack prefixes so a one-off script "just works" without a venv. +## +## +## Caveats: this script is bash-only and, since it prepends the PYTHONPATH, +## does not compose with Jupyter kernels, IDE runners, or debuggers. +## +## Build-time (nanobind) and testing dependencies (pytest/pluggy/iniconfig) +## are intentionally NOT added here. ##----------------------------------------------------------------------------- env PYTHONPATH=@_PYEXT_DIR@:@CONDUIT_PYTHON_MODULE_DIR@:@PY_NUMPY_DIR@:@PY_MPI4PY_DIR@:$PYTHONPATH @Python_EXECUTABLE@ "$@" From ba142d67b0f4a33c617e503ddb98e05a56c51d80 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 25 Jun 2026 17:03:57 -0700 Subject: [PATCH 718/986] Sidre: Update Python docs --- src/axom/sidre/docs/sphinx/index.rst | 1 + .../sidre/docs/sphinx/python_interface.rst | 135 ++++++++++++++++++ src/docs/sphinx/dev_guide/component_org.rst | 40 +++--- 3 files changed, 160 insertions(+), 16 deletions(-) create mode 100644 src/axom/sidre/docs/sphinx/python_interface.rst diff --git a/src/axom/sidre/docs/sphinx/index.rst b/src/axom/sidre/docs/sphinx/index.rst index 14c3cfe462..3cf64fd36f 100644 --- a/src/axom/sidre/docs/sphinx/index.rst +++ b/src/axom/sidre/docs/sphinx/index.rst @@ -99,3 +99,4 @@ needs and use cases. parallel_io_concepts sidre_conduit mfem_sidre_datacollection + python_interface diff --git a/src/axom/sidre/docs/sphinx/python_interface.rst b/src/axom/sidre/docs/sphinx/python_interface.rst new file mode 100644 index 0000000000..f015c3f87a --- /dev/null +++ b/src/axom/sidre/docs/sphinx/python_interface.rst @@ -0,0 +1,135 @@ +.. ## 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) + +****************************************************** +Python interface +****************************************************** + +Sidre ships a Python interface, ``axom.sidre``, that mirrors much of the C++ API, +e.g. to create a ``DataStore``, navigate ``Group`` and ``View`` objects, allocate and describe data, +and exchange data with `Conduit `_ ``Node`` objects and NumPy arrays without copying. +The interface is a compiled extension generated with `nanobind `_, +which is built when Axom is configured with the Sidre component and Python bindings enabled. + +.. code-block:: python + + import axom.sidre as sidre + + ds = sidre.DataStore() + root = ds.getRoot() + + grp = root.createGroup("fields") + view = grp.createViewAndAllocate("density", sidre.TypeID.FLOAT64_ID, 10) + + # Zero-copy NumPy view onto the buffer Sidre owns + arr = view.getDataArray() + arr[:] = 1.0 + + print(ds.getRoot().getView("fields/density").getNumElements()) # 10 + +The module carries a ``__version__`` matching the Axom release, and exposes +feature flags (``AXOM_USE_HDF5``, ``AXOM_ENABLE_MPI``) so Python code can +branch on how Axom was built. + +==================================== +Getting a working ``import axom.sidre`` +==================================== + +There are two supported ways to make the interface importable +with a plain ``python`` that can ``import axom.sidre`` without explicitly extending the ``PYTHONPATH``. + +Spack environment +----------------- + +Axom declares itself a Python extension (``extends("python")``), so a spack +environment with a view installs the bindings into the view's +``site-packages`` alongside their dependencies. + +To use this, build Axom with the ``+python`` variant in an environment whose ``spack.yaml`` enables a view: + +.. code-block:: yaml + + spack: + specs: + - axom+python + view: true + +After ``spack install``, the environment's interpreter should have a working Axom Python installation: + +.. code-block:: bash + + $ spack env activate . + $ python -c "import axom.sidre, conduit, numpy; print(axom.sidre.__version__)" + + +pip / uv wheel (thin, external Axom) +------------------------------------ + +.. note:: + + The pip/uv-installable wheel is planned and not yet available. + This section is a placeholder for the workflow it will enable. + Until it lands, use the spack environment above. + +The wheel will compile only the binding code against an already-installed Axom +(located via ``CMAKE_PREFIX_PATH``); it will not build Axom or its third-party +libraries. Because a pip-built Conduit would produce a second, ABI-incompatible +``libconduit`` in the same process, the wheel will rely on the Conduit Python +module from the same Axom/Conduit build, exposed via a ``.pth`` file rather +than a PyPI install. + +==================================== +Working with Conduit and NumPy +==================================== + +Arrays returned by ``View.getDataArray`` and ``Buffer.getDataArray`` are +zero-copy NumPy views onto memory Sidre owns. The array keeps the owning Sidre +object alive for as long as the array is reachable. + +.. warning:: + + One sharp edge remains, and the binding cannot defend against it: + reallocating a buffer (for example growing a view) can move the underlying storage, + leaving any previously obtained NumPy array pointing at freed memory. + Re-acquire arrays after any operation that may reallocate, + exactly as you would re-slice a NumPy array after resizing its base. + +The ``conduit`` Python module is a hard runtime dependency of the bindings and +must wrap the same Conduit build Axom links. It imports alongside ``axom.sidre``: + +.. code-block:: python + + import axom.sidre as sidre + from conduit import Node + + n = Node() + n["field"] = 100 + assert n["field"] == 100 + +For how Sidre's on-disk layout and its in-memory hierarchy relate to the +Conduit Blueprint data model, see :doc:`sidre_conduit`. + +================================================================== +Running standalone scripts: the ``run_python_with_axom.sh`` helper +================================================================== + +The methods above make ``import axom.sidre`` work in a plain interpreter. +If you are not in a spack environment view and just want to run a one-off +Python script that uses Axom's Python modules, the build generates a helper script, +``run_python_with_axom.sh``, that prepends directories for the required runtime dependencies +to ``PYTHONPATH`` and then runs the interpreter: + +.. code-block:: bash + + $ ./bin/run_python_with_axom.sh my_script.py + $ ./bin/run_python_with_axom.sh -c "import axom.sidre, conduit" + +The helper is a ``PYTHONPATH`` prepend and is bash-only, so it does not compose +with Jupyter kernels, IDE runners, or debuggers + +.. note:: The historical top-level module name ``pysidre`` still works as a + deprecation shim that re-exports ``axom.sidre`` and warns on import. + It will be removed in a future release. Prefer ``import axom.sidre``. diff --git a/src/docs/sphinx/dev_guide/component_org.rst b/src/docs/sphinx/dev_guide/component_org.rst index 75375627e7..4e2fcfc9ca 100644 --- a/src/docs/sphinx/dev_guide/component_org.rst +++ b/src/docs/sphinx/dev_guide/component_org.rst @@ -302,22 +302,30 @@ in other languages Axom supports. Python Interfaces ==================================== -We use the nanobind library to generate Python APIs from our C++ -interface code. Nanobind is a python binding library that generates code -from a *cpp* file that describes C++ functions and their interfaces. - -Please refer to the `nanobind documentation `_ for more information. - -The python interpreter can be launched with Axom extension(s) in the PYTHONPATH -by running the convenience script:: - - ./bin/run_python_with_axom.sh - -.. note:: The Python interface requires Axom to be configured with nanobind - to build and use the interface. This requirement is different from shroud, - which generates interface files. Once shroud generates the interface - files, users are not required to configure Axom with shroud to use the - Fortran interface. +We use the `nanobind `_ library +to build Python APIs from our C++ interface code. A component's bindings are +hand-written in a nanobind translation unit (e.g., ``src/axom/sidre/nanobind_sidre.cpp``) +that describes the classes and functions to expose. +nanobind compiles this into an extension module. + +The bindings install as a Python package. Each bound component is an extension +under the ``axom`` namespace package (for example ``axom.sidre``), with type stubs +and a ``py.typed`` marker so editors and type checkers can introspect it. +The pure-Python package scaffolding lives once under ``src/python/src/`` and is +installed by the CMake build (and, in the future, will be reused verbatim by a pip/uv wheel). + +The end-user view of the Python interface, e.g. how to install and import it, +is documented in the Sidre user guide's Python interface page. +This section covers how the bindings are built and how to add more of them. + +To build the bindings, configure Axom with nanobind (Python with the ``Development.Module`` component, +and nanobind discoverable by the interpreter). This requirement differs from Shroud, which generates Fortran interface files that do not require Shroud at build time once generated. + +.. note:: A spack environment with a view, or (in the future) the pip/uv wheel, + makes ``import axom.sidre`` work in a plain interpreter. + For running ad hoc Python scripts against a build tree, we provide a + generated ``run_python_with_axom.sh`` helper to resolve the runtime dependencies + (Conduit, NumPy, mpi4py) on ``PYTHONPATH``. .. warning:: nanobind's numpy interface does not currently support `arbitrary Python objects `_. From aa6f6daf6c5915d7c6f92010e7282600648ab47f Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 25 Jun 2026 17:38:50 -0700 Subject: [PATCH 719/986] Python: Allows default Python install path to be relative --- scripts/spack/packages/axom/package.py | 9 ++++----- src/axom/sidre/CMakeLists.txt | 28 +++++++++++++++++++++++--- src/tools/CMakeLists.txt | 13 ++++++++++-- src/tools/run_python_with_axom.sh.in | 9 +++++++-- 4 files changed, 47 insertions(+), 12 deletions(-) diff --git a/scripts/spack/packages/axom/package.py b/scripts/spack/packages/axom/package.py index dc7c1d305f..3bcb0783e0 100644 --- a/scripts/spack/packages/axom/package.py +++ b/scripts/spack/packages/axom/package.py @@ -763,12 +763,11 @@ def initconfig_package_entries(self): if spec.satisfies("+python"): # Install Axom's Python package(s) so a spack environment view merges them into # a single site-packages and `import axom.sidre` works without updating PYTHONPATH - axom_prefix = os.path.realpath(spec.prefix) - for key in path_replacements: - axom_prefix = axom_prefix.replace(key, path_replacements[key]) - py_platlib = pjoin(axom_prefix, spec["python"].package.platlib) entries.append( - cmake_cache_path("AXOM_PYTHON_MODULE_INSTALL_PREFIX", py_platlib) + cmake_cache_path( + "AXOM_PYTHON_MODULE_INSTALL_PREFIX", + spec["python"].package.platlib, + ) ) if spec.satisfies("^py-jsonschema"): diff --git a/src/axom/sidre/CMakeLists.txt b/src/axom/sidre/CMakeLists.txt index da933dec99..7fdd583db7 100644 --- a/src/axom/sidre/CMakeLists.txt +++ b/src/axom/sidre/CMakeLists.txt @@ -141,11 +141,33 @@ if(NANOBIND_FOUND) # Python bindings for Sidre. # The pure-Python package scaffolding lives once under src/python/src/ - # site-packages-shaped install root for Axom's Python package(s). + # site-packages-shaped install directory for Axom's Python package(s). + # Keep this relative to the install prefix so `cmake --install --prefix` + # relocates the Python package along with Axom's other install artifacts. + set(_axom_python_install_default + "lib/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages") + set(_axom_python_install_description + "Install destination for Axom's Python package(s), relative to the install prefix") set(AXOM_PYTHON_MODULE_INSTALL_PREFIX - "${CMAKE_INSTALL_PREFIX}/lib/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages" + "${_axom_python_install_default}" CACHE PATH - "Install destination for Axom's Python package(s), relative to which 'axom/' is created") + "${_axom_python_install_description}") + + if(IS_ABSOLUTE "${AXOM_PYTHON_MODULE_INSTALL_PREFIX}") + file(RELATIVE_PATH _axom_python_install_relpath + "${CMAKE_INSTALL_PREFIX}" + "${AXOM_PYTHON_MODULE_INSTALL_PREFIX}") + if(NOT _axom_python_install_relpath MATCHES "^\\.\\.") + set(AXOM_PYTHON_MODULE_INSTALL_PREFIX + "${_axom_python_install_relpath}" + CACHE PATH + "${_axom_python_install_description}" + FORCE) + endif() + unset(_axom_python_install_relpath) + endif() + unset(_axom_python_install_default) + unset(_axom_python_install_description) # Root of the staged package tree in the build directory. # The build-tree interpreter (and run_python_with_axom.sh, via _PYEXT_DIR) diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index f23fe0e8ab..a5d46d6903 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -196,23 +196,32 @@ if(NANOBIND_FOUND) # and 'import pysidre' resolve. The Sidre bindings stage that tree under # ${PROJECT_BINARY_DIR}/python (see src/axom/sidre/CMakeLists.txt). set(_PYEXT_DIR ${PROJECT_BINARY_DIR}/python) + set(_PYEXT_DIR_IS_RELATIVE FALSE) axom_configure_file ("${CMAKE_CURRENT_SOURCE_DIR}/run_python_with_axom.sh.in" "${PROJECT_BINARY_DIR}/bin/run_python_with_axom.sh" @ONLY) # gen python helper to install directory. - # Mirror the installed package root. Fall back to the lib dir if the - # Python package prefix was never set (e.g. Sidre/bindings disabled). + # Mirror the installed package root. + # Keep relative package install dirs relative in the generated script too. + # It resolves them from its own bin/ directory at runtime so `cmake --install --prefix` remains usable. if(AXOM_PYTHON_MODULE_INSTALL_PREFIX) set(_PYEXT_DIR ${AXOM_PYTHON_MODULE_INSTALL_PREFIX}) + if(IS_ABSOLUTE "${_PYEXT_DIR}") + set(_PYEXT_DIR_IS_RELATIVE FALSE) + else() + set(_PYEXT_DIR_IS_RELATIVE TRUE) + endif() else() set(_PYEXT_DIR ${CMAKE_INSTALL_PREFIX}/lib) + set(_PYEXT_DIR_IS_RELATIVE FALSE) endif() axom_configure_file ("${CMAKE_CURRENT_SOURCE_DIR}/run_python_with_axom.sh.in" "${CMAKE_INSTALL_PREFIX}/bin/run_python_with_axom.sh" @ONLY) unset(_PYEXT_DIR) + unset(_PYEXT_DIR_IS_RELATIVE) # Smoke test for the script: verify it makes Axom's bindings and runtime dependencies importable if(AXOM_ENABLE_TESTS AND AXOM_ENABLE_SIDRE) diff --git a/src/tools/run_python_with_axom.sh.in b/src/tools/run_python_with_axom.sh.in index 200e70f07b..7a90a40b8d 100755 --- a/src/tools/run_python_with_axom.sh.in +++ b/src/tools/run_python_with_axom.sh.in @@ -18,11 +18,16 @@ ## build tree that has the bindings enabled -- it assembles conduit/numpy/mpi4py ## from their spack prefixes so a one-off script "just works" without a venv. ## -## ## Caveats: this script is bash-only and, since it prepends the PYTHONPATH, ## does not compose with Jupyter kernels, IDE runners, or debuggers. ## ## Build-time (nanobind) and testing dependencies (pytest/pluggy/iniconfig) ## are intentionally NOT added here. ##----------------------------------------------------------------------------- -env PYTHONPATH=@_PYEXT_DIR@:@CONDUIT_PYTHON_MODULE_DIR@:@PY_NUMPY_DIR@:@PY_MPI4PY_DIR@:$PYTHONPATH @Python_EXECUTABLE@ "$@" +_AXOM_PYEXT_DIR="@_PYEXT_DIR@" +if [ "@_PYEXT_DIR_IS_RELATIVE@" = "TRUE" ]; then + _AXOM_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + _AXOM_PYEXT_DIR="${_AXOM_SCRIPT_DIR}/../${_AXOM_PYEXT_DIR}" +fi + +env PYTHONPATH=${_AXOM_PYEXT_DIR}:@CONDUIT_PYTHON_MODULE_DIR@:@PY_NUMPY_DIR@:@PY_MPI4PY_DIR@:$PYTHONPATH @Python_EXECUTABLE@ "$@" From c4196e08b105dfa6ecc5f331de45b9350c4f8472 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 25 Jun 2026 18:10:14 -0700 Subject: [PATCH 720/986] sidre: Improves ImportError checks in axom.sidre --- src/axom/sidre/tests/sidre_pysidre_shim_Py.py | 63 +++++++++++++++++++ src/python/src/axom/sidre/__init__.py | 5 ++ 2 files changed, 68 insertions(+) diff --git a/src/axom/sidre/tests/sidre_pysidre_shim_Py.py b/src/axom/sidre/tests/sidre_pysidre_shim_Py.py index 50aa05377c..ca8d4ea77c 100644 --- a/src/axom/sidre/tests/sidre_pysidre_shim_Py.py +++ b/src/axom/sidre/tests/sidre_pysidre_shim_Py.py @@ -14,6 +14,8 @@ import sys import warnings +import pytest + def _fresh_import_pysidre(): """Import 'pysidre' with a clean module cache so its import-time @@ -26,6 +28,40 @@ def _fresh_import_pysidre(): return module, deprecations +def _clear_axom_imports(): + # These tests swap between the real staged package and synthetic packages + # under tmp_path; cached modules would otherwise bypass sys.path changes. + for name in list(sys.modules): + if name == "axom" or name.startswith("axom.") or name == "pysidre": + sys.modules.pop(name, None) + + +def _sidre_init_source(): + _clear_axom_imports() + import axom.sidre as sidre + + # Exercise the installed package initializer verbatim instead of keeping a + # test-local copy of its import-error handling logic. + with open(sidre.__file__, "r", encoding="utf-8") as sidre_init: + return sidre_init.read() + + +def _write_fake_axom_sidre(tmp_path, monkeypatch, sidre_init_source, sidre_extension_source=None): + # Build a minimal axom.sidre package layout. Leaving _sidre absent models a + # component-disabled install; adding _sidre.py models a discoverable module + # whose loader/import body fails. + package_root = tmp_path / "axom" + sidre_root = package_root / "sidre" + sidre_root.mkdir(parents=True) + (package_root / "__init__.py").write_text("", encoding="utf-8") + (sidre_root / "__init__.py").write_text(sidre_init_source, encoding="utf-8") + if sidre_extension_source is not None: + (sidre_root / "_sidre.py").write_text(sidre_extension_source, encoding="utf-8") + + _clear_axom_imports() + monkeypatch.syspath_prepend(str(tmp_path)) + + def test_pysidre_import_warns_once(): _module, deprecations = _fresh_import_pysidre() assert len(deprecations) == 1 @@ -50,3 +86,30 @@ def test_pysidre_datastore_roundtrip(): grp = root.createGroup("via_shim") assert root.hasGroup("via_shim") assert grp.getName() == "via_shim" + + +def test_axom_sidre_missing_extension_gets_component_message(tmp_path, monkeypatch): + _write_fake_axom_sidre(tmp_path, monkeypatch, _sidre_init_source()) + + with pytest.raises(ImportError) as caught: + importlib.import_module("axom.sidre") + + assert "The 'axom.sidre' extension module ('_sidre') is not available" in str(caught.value) + assert "AXOM_ENABLE_SIDRE=ON" in str(caught.value) + + +def test_axom_sidre_loader_import_error_is_not_masked(tmp_path, monkeypatch): + # A discoverable _sidre that raises ImportError represents loader failures + # such as missing shared libraries; those errors must remain actionable. + _write_fake_axom_sidre( + tmp_path, + monkeypatch, + _sidre_init_source(), + "raise ImportError('libsidre_dependency_missing')\n", + ) + + with pytest.raises(ImportError) as caught: + importlib.import_module("axom.sidre") + + assert "libsidre_dependency_missing" in str(caught.value) + assert "extension module ('_sidre') is not available" not in str(caught.value) diff --git a/src/python/src/axom/sidre/__init__.py b/src/python/src/axom/sidre/__init__.py index 8326001f1b..68b2977058 100644 --- a/src/python/src/axom/sidre/__init__.py +++ b/src/python/src/axom/sidre/__init__.py @@ -12,9 +12,14 @@ rather than surfacing an opaque loader error. """ +import importlib.util as _importlib_util + try: from . import _sidre except ImportError as exc: # pragma: no cover - exercised only in partial installs + if _importlib_util.find_spec(f"{__name__}._sidre") is not None: + raise + raise ImportError( "The 'axom.sidre' extension module ('_sidre') is not available in this " "installation. It is built only when Axom is configured with the Sidre " From 83292d080b471bf17f2743405fb42cd9acfedff8 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 25 Jun 2026 18:46:05 -0700 Subject: [PATCH 721/986] Improves axom_add_python_test CMake macro to take a COMMAND keyword One can call the macro with either a COMMAND or a SOURCE file. --- src/axom/sidre/examples/CMakeLists.txt | 9 +--- src/cmake/AxomMacros.cmake | 58 ++++++++++++++++---------- src/tools/CMakeLists.txt | 48 +++++++++++---------- 3 files changed, 64 insertions(+), 51 deletions(-) diff --git a/src/axom/sidre/examples/CMakeLists.txt b/src/axom/sidre/examples/CMakeLists.txt index 721243d3c6..bafc03236d 100644 --- a/src/axom/sidre/examples/CMakeLists.txt +++ b/src/axom/sidre/examples/CMakeLists.txt @@ -155,18 +155,11 @@ if(NANOBIND_FOUND) "${EXAMPLE_OUTPUT_DIRECTORY}/${example_source}" COPYONLY) # Run python examples directly under the interpreter (no wrapper). - # The runtime environment is supplied via the test's ENVIRONMENT property if(AXOM_ENABLE_PYTHON_TESTS) - axom_add_test ( + axom_add_python_test( NAME ${exe_name} COMMAND ${Python_EXECUTABLE} ${EXAMPLE_OUTPUT_DIRECTORY}/${example_source} ) - axom_python_test_environment(_py_example_env) - if(_py_example_env) - set_property(TEST ${exe_name} - APPEND PROPERTY ENVIRONMENT "${_py_example_env}") - endif() - unset(_py_example_env) endif() endforeach() endif() diff --git a/src/cmake/AxomMacros.cmake b/src/cmake/AxomMacros.cmake index d24b4d3642..9b6833199f 100644 --- a/src/cmake/AxomMacros.cmake +++ b/src/cmake/AxomMacros.cmake @@ -648,41 +648,57 @@ endfunction() ## axom_add_python_test(NAME [name] ## SOURCE [source] ## OUTPUT_DIR [dir] +## COMMAND [command] ## NUM_MPI_TASKS [n]) ## -## Wrapper around add_test() that handles functionality +## Wrapper around axom_add_test() that handles functionality ## that Axom applies to all python tests. +## +## When SOURCE is provided, the test file is copied to OUTPUT_DIR and run under +## pytest. When COMMAND is provided, it is registered directly as the test +## command. SOURCE and COMMAND are mutually exclusive. ##------------------------------------------------------------------------------ macro(axom_add_python_test) set(options) set(singleValueArgs NAME SOURCE OUTPUT_DIR NUM_MPI_TASKS) - set(multiValueArgs) + set(multiValueArgs COMMAND) # Parse the arguments to the macro cmake_parse_arguments(arg "${options}" "${singleValueArgs}" "${multiValueArgs}" ${ARGN}) - # Copy python test file to build - axom_configure_file ("${arg_SOURCE}" - "${arg_OUTPUT_DIR}/${arg_SOURCE}" COPYONLY) - - # Run unit test with pytest ("python3 -m pytest"), invoked directly rather - # than through the run_python_with_axom.sh wrapper. The full runtime + test - # environment is supplied via the test's ENVIRONMENT property (a single - # combined PYTHONPATH; see axom_python_test_environment). Running pytest - # natively keeps the tests composable with IDEs/debuggers and removes the - # bash-only wrapper from the test path. - # "-p no:cacheprovider" disables caching. - set(_test_command ${Python_EXECUTABLE} - -m pytest -s -p no:cacheprovider ${arg_OUTPUT_DIR}/${arg_SOURCE}) - blt_add_test(NAME ${arg_NAME} - COMMAND ${_test_command} - NUM_MPI_TASKS ${arg_NUM_MPI_TASKS}) + if(arg_SOURCE AND arg_COMMAND) + message(FATAL_ERROR + "axom_add_python_test accepts either SOURCE or COMMAND, not both") + endif() - set_property(TEST ${arg_NAME} - APPEND - PROPERTY ENVIRONMENT "OMPI_MCA_rmaps_base_oversubscribe=1") + if(arg_COMMAND) + set(_test_command ${arg_COMMAND}) + else() + if((NOT arg_SOURCE) OR (NOT arg_OUTPUT_DIR)) + message(FATAL_ERROR + "axom_add_python_test requires SOURCE and OUTPUT_DIR, or COMMAND") + endif() + + # Copy python test file to build + axom_configure_file ("${arg_SOURCE}" + "${arg_OUTPUT_DIR}/${arg_SOURCE}" COPYONLY) + + # Run unit test with pytest ("python3 -m pytest"), invoked directly + # rather than through the run_python_with_axom.sh wrapper. The full + # runtime + test environment is supplied via the test's ENVIRONMENT + # property (a single combined PYTHONPATH; see axom_python_test_environment). + # Running pytest natively keeps the tests composable with IDEs/debuggers + # and removes the bash-only wrapper from the test path. + # "-p no:cacheprovider" disables caching. + set(_test_command ${Python_EXECUTABLE} + -m pytest -s -p no:cacheprovider ${arg_OUTPUT_DIR}/${arg_SOURCE}) + endif() + + axom_add_test(NAME ${arg_NAME} + COMMAND ${_test_command} + NUM_MPI_TASKS ${arg_NUM_MPI_TASKS}) axom_python_test_environment(_py_test_env) if(_py_test_env) diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index a5d46d6903..4fef836761 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -240,31 +240,35 @@ if(NANOBIND_FOUND) axom_configure_file ("${CMAKE_CURRENT_SOURCE_DIR}/convert_sidre_protocol.py" "${CMAKE_INSTALL_PREFIX}/bin/convert_sidre_protocol.py" COPYONLY) - if(AXOM_ENABLE_MPI AND AXOM_ENABLE_TESTS AND AXOM_DATA_DIR) + if(AXOM_ENABLE_PYTHON_TESTS AND AXOM_ENABLE_SIDRE) + set(_testname "convert_sidre_protocol_py") - set(box_dir "${AXOM_DATA_DIR}/quest/box_2D_r3.root") + if(AXOM_ENABLE_MPI AND AXOM_DATA_DIR) + set(box_dir "${AXOM_DATA_DIR}/quest/box_2D_r3.root") + + axom_add_python_test( + NAME ${_testname} + COMMAND ${Python_EXECUTABLE} + ${PROJECT_BINARY_DIR}/bin/convert_sidre_protocol.py + --input ${box_dir} + --output csp_output + --protocol json + --verbose + NUM_MPI_TASKS 3 + ) - set(_testname "convert_sidre_protocol_py") - axom_add_test( - NAME ${_testname} - COMMAND ${Python_EXECUTABLE} - ${PROJECT_BINARY_DIR}/bin/convert_sidre_protocol.py - --input ${box_dir} - --output csp_output - --protocol json - --verbose - NUM_MPI_TASKS 3 - ) + set_tests_properties(${_testname} PROPERTIES + PASS_REGULAR_EXPRESSION "Writing out datastore") + else() + axom_add_python_test( + NAME ${_testname} + COMMAND ${Python_EXECUTABLE} + ${PROJECT_BINARY_DIR}/bin/convert_sidre_protocol.py + --help + ) - axom_python_test_environment(_csp_py_env) - if(_csp_py_env) - set_property(TEST ${_testname} - APPEND - PROPERTY ENVIRONMENT "${_csp_py_env}") + set_tests_properties(${_testname} PROPERTIES + PASS_REGULAR_EXPRESSION "Sidre protocol converter") endif() - unset(_csp_py_env) - - set_tests_properties(${_testname} PROPERTIES - PASS_REGULAR_EXPRESSION "Writing out datastore") endif() endif() From 6c1ffa655ae06e4e79da00b7d8fc253ba145fdc4 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 25 Jun 2026 19:09:01 -0700 Subject: [PATCH 722/986] Fixes python shim test --- src/axom/sidre/tests/sidre_pysidre_shim_Py.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/axom/sidre/tests/sidre_pysidre_shim_Py.py b/src/axom/sidre/tests/sidre_pysidre_shim_Py.py index ca8d4ea77c..5d8c2bbf3b 100644 --- a/src/axom/sidre/tests/sidre_pysidre_shim_Py.py +++ b/src/axom/sidre/tests/sidre_pysidre_shim_Py.py @@ -11,6 +11,7 @@ """ import importlib +from pathlib import Path import sys import warnings @@ -37,13 +38,16 @@ def _clear_axom_imports(): def _sidre_init_source(): - _clear_axom_imports() - import axom.sidre as sidre - - # Exercise the installed package initializer verbatim instead of keeping a - # test-local copy of its import-error handling logic. - with open(sidre.__file__, "r", encoding="utf-8") as sidre_init: - return sidre_init.read() + # Exercise the staged package initializer verbatim instead of keeping a + # test-local copy of its import-error handling logic. Locate the file on + # sys.path without importing axom.sidre; re-importing the real nanobind + # extension after removing it from sys.modules can abort in some builds. + for entry in sys.path: + sidre_init = Path(entry) / "axom" / "sidre" / "__init__.py" + if sidre_init.is_file(): + return sidre_init.read_text(encoding="utf-8") + + raise RuntimeError("Could not locate axom.sidre.__init__.py on sys.path") def _write_fake_axom_sidre(tmp_path, monkeypatch, sidre_init_source, sidre_extension_source=None): From 158da3543b27473278ca2d711144939cbbcda879 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 25 Jun 2026 19:36:48 -0700 Subject: [PATCH 723/986] python: Updates docs to note limitations of running python through spack env --- .../sidre/docs/sphinx/python_interface.rst | 42 ++++++++++++++----- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/src/axom/sidre/docs/sphinx/python_interface.rst b/src/axom/sidre/docs/sphinx/python_interface.rst index f015c3f87a..6c98674935 100644 --- a/src/axom/sidre/docs/sphinx/python_interface.rst +++ b/src/axom/sidre/docs/sphinx/python_interface.rst @@ -34,21 +34,41 @@ The module carries a ``__version__`` matching the Axom release, and exposes feature flags (``AXOM_USE_HDF5``, ``AXOM_ENABLE_MPI``) so Python code can branch on how Axom was built. -==================================== +======================================= Getting a working ``import axom.sidre`` -==================================== +======================================= + +How to make the interface importable depends on whether you are using an +installed Axom package or a build tree from an Axom development environment. +The two workflows are intentionally different. + +Development build tree +---------------------- + +Axom's uberenv-generated TPL environments intentionally use ``view: false``. +Those environments are for configuring and building Axom from a worktree. +They do not make the build-tree package importable by a plain interpreter when activated. + +For development builds, use CTest or the generated ``run_python_with_axom.sh`` helper. +These paths set the build-tree ``PYTHONPATH`` entries needed for Axom's staged package +and its Python runtime dependencies. + +.. code-block:: bash -There are two supported ways to make the interface importable -with a plain ``python`` that can ``import axom.sidre`` without explicitly extending the ``PYTHONPATH``. + $ cd build-axom + $ ctest -R sidre_smoke_Py --output-on-failure + $ ./bin/run_python_with_axom.sh -c "import axom.sidre as sidre; print(sidre.__version__)" -Spack environment ------------------ +Spack environment view +---------------------- Axom declares itself a Python extension (``extends("python")``), so a spack -environment with a view installs the bindings into the view's -``site-packages`` alongside their dependencies. +environment view can expose the bindings in the view's ``site-packages`` alongside their dependencies. +This is useful for testing or using an installed Axom package with a plain interpreter, +but it is not the normal Axom development-build workflow. -To use this, build Axom with the ``+python`` variant in an environment whose ``spack.yaml`` enables a view: +To use this, install Axom with the ``+python`` variant in a dedicated +environment whose ``spack.yaml`` enables a view: .. code-block:: yaml @@ -56,6 +76,7 @@ To use this, build Axom with the ``+python`` variant in an environment whose ``s specs: - axom+python view: true + ... After ``spack install``, the environment's interpreter should have a working Axom Python installation: @@ -72,7 +93,8 @@ pip / uv wheel (thin, external Axom) The pip/uv-installable wheel is planned and not yet available. This section is a placeholder for the workflow it will enable. - Until it lands, use the spack environment above. + Until it lands, use the build-tree helper for development builds + or a dedicated Spack environment view for installed-package testing. The wheel will compile only the binding code against an already-installed Axom (located via ``CMAKE_PREFIX_PATH``); it will not build Axom or its third-party From 8c693856eb881a135c90bbb074e9c8a1b01cbc33 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 25 Jun 2026 19:38:31 -0700 Subject: [PATCH 724/986] Updates RELEASE-NOTES --- RELEASE-NOTES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 97229006f9..f48750287d 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -55,6 +55,7 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ ### Deprecated - Core: Deprecates the pointer-based interface to linear-, quadratic- and cubic- polynomial solvers in favor of an ArrayView-based interface +- Python: The top-level `pysidre` module is deprecated in favor of `axom.sidre`. ### Changed - Updates CMake code check targets to only use checked in files (via `git ls-files`, when available) @@ -65,6 +66,7 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ - Core: Optimization for axom::Array indirection -- since the stride is always 1, we can remove the runtime multiplication - Python: Removes build and test dependencies from `run_python_with_axom.sh` wrapper script - Changed to `#pragma once` instead of unique header guard defines +- Python: Sidre's bindings now install as an `axom` namespace package (`import axom.sidre`) ### Fixed - Primal: Fixes signs of `compute_moments` to match orientation convention in `primal::evaluate_area_integral` From 073407d33d74c06610236b8136d8febba409b1f4 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 25 Jun 2026 20:31:03 -0700 Subject: [PATCH 725/986] python: Fixes installation of run_axom_with_python script Misc: Fixes typos and whitespace issues --- RELEASE-NOTES.md | 2 +- src/axom/sidre/CMakeLists.txt | 4 +-- .../sidre/docs/sphinx/python_interface.rst | 18 ++++++------ src/docs/sphinx/dev_guide/component_org.rst | 14 +++++----- src/python/README.md | 12 ++++---- src/python/src/axom/__init__.py | 28 ++++++------------- src/tools/CMakeLists.txt | 12 ++++++-- src/tools/run_python_with_axom.sh.in | 4 +-- 8 files changed, 45 insertions(+), 49 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index f48750287d..d8e4534c70 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -66,7 +66,7 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ - Core: Optimization for axom::Array indirection -- since the stride is always 1, we can remove the runtime multiplication - Python: Removes build and test dependencies from `run_python_with_axom.sh` wrapper script - Changed to `#pragma once` instead of unique header guard defines -- Python: Sidre's bindings now install as an `axom` namespace package (`import axom.sidre`) +- Python: Sidre's bindings now install under the `axom` Python package (`import axom.sidre`) ### Fixed - Primal: Fixes signs of `compute_moments` to match orientation convention in `primal::evaluate_area_integral` diff --git a/src/axom/sidre/CMakeLists.txt b/src/axom/sidre/CMakeLists.txt index 7fdd583db7..c8d96b242f 100644 --- a/src/axom/sidre/CMakeLists.txt +++ b/src/axom/sidre/CMakeLists.txt @@ -169,8 +169,8 @@ if(NANOBIND_FOUND) unset(_axom_python_install_default) unset(_axom_python_install_description) - # Root of the staged package tree in the build directory. - # The build-tree interpreter (and run_python_with_axom.sh, via _PYEXT_DIR) + # Root of the staged package tree in the build directory. + # The build-tree interpreter (and run_python_with_axom.sh, via _PYEXT_DIR) # puts this single directory on PYTHONPATH and then 'import axom.sidre' works. set(_axom_py_build_root "${PROJECT_BINARY_DIR}/python") set(_pysidre_pkg_src "${CMAKE_CURRENT_SOURCE_DIR}/../../python/src") diff --git a/src/axom/sidre/docs/sphinx/python_interface.rst b/src/axom/sidre/docs/sphinx/python_interface.rst index 6c98674935..f2d09c1b7e 100644 --- a/src/axom/sidre/docs/sphinx/python_interface.rst +++ b/src/axom/sidre/docs/sphinx/python_interface.rst @@ -11,7 +11,7 @@ Python interface Sidre ships a Python interface, ``axom.sidre``, that mirrors much of the C++ API, e.g. to create a ``DataStore``, navigate ``Group`` and ``View`` objects, allocate and describe data, and exchange data with `Conduit `_ ``Node`` objects and NumPy arrays without copying. -The interface is a compiled extension generated with `nanobind `_, +The interface is a compiled extension generated with `nanobind `_, which is built when Axom is configured with the Sidre component and Python bindings enabled. .. code-block:: python @@ -49,7 +49,7 @@ Axom's uberenv-generated TPL environments intentionally use ``view: false``. Those environments are for configuring and building Axom from a worktree. They do not make the build-tree package importable by a plain interpreter when activated. -For development builds, use CTest or the generated ``run_python_with_axom.sh`` helper. +For development builds, use CTest or the generated ``run_python_with_axom.sh`` helper. These paths set the build-tree ``PYTHONPATH`` entries needed for Axom's staged package and its Python runtime dependencies. @@ -91,7 +91,7 @@ pip / uv wheel (thin, external Axom) .. note:: - The pip/uv-installable wheel is planned and not yet available. + The pip/uv-installable wheel is planned and not yet available. This section is a placeholder for the workflow it will enable. Until it lands, use the build-tree helper for development builds or a dedicated Spack environment view for installed-package testing. @@ -109,14 +109,14 @@ Working with Conduit and NumPy Arrays returned by ``View.getDataArray`` and ``Buffer.getDataArray`` are zero-copy NumPy views onto memory Sidre owns. The array keeps the owning Sidre -object alive for as long as the array is reachable. +object alive for as long as the array is reachable. .. warning:: - One sharp edge remains, and the binding cannot defend against it: + One sharp edge remains, and the binding cannot defend against it: reallocating a buffer (for example growing a view) can move the underlying storage, leaving any previously obtained NumPy array pointing at freed memory. - Re-acquire arrays after any operation that may reallocate, + Re-acquire arrays after any operation that may reallocate, exactly as you would re-slice a NumPy array after resizing its base. The ``conduit`` Python module is a hard runtime dependency of the bindings and @@ -138,9 +138,9 @@ Conduit Blueprint data model, see :doc:`sidre_conduit`. Running standalone scripts: the ``run_python_with_axom.sh`` helper ================================================================== -The methods above make ``import axom.sidre`` work in a plain interpreter. +The methods above make ``import axom.sidre`` work in a plain interpreter. If you are not in a spack environment view and just want to run a one-off -Python script that uses Axom's Python modules, the build generates a helper script, +Python script that uses Axom's Python modules, the build generates a helper script, ``run_python_with_axom.sh``, that prepends directories for the required runtime dependencies to ``PYTHONPATH`` and then runs the interpreter: @@ -149,7 +149,7 @@ to ``PYTHONPATH`` and then runs the interpreter: $ ./bin/run_python_with_axom.sh my_script.py $ ./bin/run_python_with_axom.sh -c "import axom.sidre, conduit" -The helper is a ``PYTHONPATH`` prepend and is bash-only, so it does not compose +The helper is a ``PYTHONPATH`` prepend and is bash-only, so it does not compose with Jupyter kernels, IDE runners, or debuggers .. note:: The historical top-level module name ``pysidre`` still works as a diff --git a/src/docs/sphinx/dev_guide/component_org.rst b/src/docs/sphinx/dev_guide/component_org.rst index 4e2fcfc9ca..c2c1e56116 100644 --- a/src/docs/sphinx/dev_guide/component_org.rst +++ b/src/docs/sphinx/dev_guide/component_org.rst @@ -304,26 +304,26 @@ Python Interfaces We use the `nanobind `_ library to build Python APIs from our C++ interface code. A component's bindings are -hand-written in a nanobind translation unit (e.g., ``src/axom/sidre/nanobind_sidre.cpp``) -that describes the classes and functions to expose. +hand-written in a nanobind translation unit (e.g., ``src/axom/sidre/nanobind_sidre.cpp``) +that describes the classes and functions to expose. nanobind compiles this into an extension module. The bindings install as a Python package. Each bound component is an extension -under the ``axom`` namespace package (for example ``axom.sidre``), with type stubs +under the ``axom`` package (for example ``axom.sidre``), with type stubs and a ``py.typed`` marker so editors and type checkers can introspect it. The pure-Python package scaffolding lives once under ``src/python/src/`` and is installed by the CMake build (and, in the future, will be reused verbatim by a pip/uv wheel). The end-user view of the Python interface, e.g. how to install and import it, -is documented in the Sidre user guide's Python interface page. +is documented in the Sidre user guide's Python interface page. This section covers how the bindings are built and how to add more of them. -To build the bindings, configure Axom with nanobind (Python with the ``Development.Module`` component, +To build the bindings, configure Axom with nanobind (Python with the ``Development.Module`` component, and nanobind discoverable by the interpreter). This requirement differs from Shroud, which generates Fortran interface files that do not require Shroud at build time once generated. .. note:: A spack environment with a view, or (in the future) the pip/uv wheel, - makes ``import axom.sidre`` work in a plain interpreter. - For running ad hoc Python scripts against a build tree, we provide a + makes ``import axom.sidre`` work in a plain interpreter. + For running ad hoc Python scripts against a build tree, we provide a generated ``run_python_with_axom.sh`` helper to resolve the runtime dependencies (Conduit, NumPy, mpi4py) on ``PYTHONPATH``. diff --git a/src/python/README.md b/src/python/README.md index d898a439d0..4385fb02ed 100644 --- a/src/python/README.md +++ b/src/python/README.md @@ -11,15 +11,15 @@ This directory (`src/python/`) holds the canonical source of Axom's Python package, `axom`. It is consumed by two independent build paths that must produce the same on-disk layout: -1. **The CMake build (in tree).** When Axom is configured with a component's Python bindings enabled (currently Sidre), +1. **The CMake build (in tree).** When Axom is configured with a component's Python bindings enabled (currently Sidre), the build stages this tree into the build directory and installs it into a `site-packages`-shaped prefix. See `src/axom/sidre/CMakeLists.txt`: it copies the files below into `${PROJECT_BINARY_DIR}/python/` (so the build tree is import-ready) - and installs them under `AXOM_PYTHON_MODULE_INSTALL_PREFIX`. + and installs them under `AXOM_PYTHON_MODULE_INSTALL_PREFIX`. The compiled extension (`_sidre`) and its type stub are emitted into this layout by the build; they are not checked in. 2. **[planned] The pip/uv wheel (out of tree).** A thin, binding-only wheel built with scikit-build-core will treat this directory as its package root (`wheel.packages = ["src/axom", "src/pysidre"]` in a sibling `pyproject.toml`), - compiling the binding translation unit against an already-installed Axom. + compiling the binding translation unit against an already-installed Axom. ## Layout @@ -30,8 +30,8 @@ This is a standard "src layout" Python project root: src/python/ README.md <- this file src/ - axom/ <- the 'axom' namespace package (regular package) - __init__.py <- package version (sourced from the extension) + axom/ <- the 'axom' regular package + __init__.py <- top-level package metadata py.typed <- PEP 561 marker (typed package) sidre/ __init__.py <- re-exports the compiled 'axom.sidre._sidre' @@ -59,6 +59,6 @@ A submodule is importable only when its component was enabled in the underlying ## Notes - These files are installed verbatim (no template substitution). They contain no CMake-configured values. -- A `pyproject.toml` for the standalone wheel is not present yet. We will add it in the future when we add the wheel. +- A `pyproject.toml` for the standalone wheel is not present yet. We will add it in the future when we add the wheel. Until then this directory is consumed only by the CMake build. - End-user instructions for installing and importing the bindings currently live in the Sidre user guide's "Python interface" page. diff --git a/src/python/src/axom/__init__.py b/src/python/src/axom/__init__.py index c7799bc27a..d4dd9dfd43 100644 --- a/src/python/src/axom/__init__.py +++ b/src/python/src/axom/__init__.py @@ -11,10 +11,12 @@ A submodule is importable only when the corresponding component was enabled in the underlying Axom build. Importing a component that was not built raises :class:`ImportError` with a message naming the missing component. -The set of submodules present in a given installation therefore mirrors +The set of submodules present in a given installation therefore mirrors the ``AXOM_ENABLE_`` configuration of the Axom build the bindings were compiled against. """ +from importlib import metadata as _metadata + # ``axom`` is a regular package (it ships this ``__init__.py``), not an # implicit namespace package. All bound components install into this single # package directory from one Axom build; mixing components from different @@ -24,21 +26,9 @@ __all__ = ["__version__"] -def _discover_version() -> str: - """Return the Axom version string. - - The version is owned by the C++ build (``AXOM_VERSION_FULL`` in ``axom/config.hpp``) - and surfaced on each extension module's ``__version__`` attribute. - We read it from the ``sidre`` extension when present so there is a single source of truth. - If no component extension is importable (an unusual, effectively content-free install) - we fall back to a sentinel rather than failing the package import. - """ - try: - from axom.sidre import _sidre # noqa: WPS433 (local import is intentional) - - return _sidre.__version__ - except Exception: # pragma: no cover - defensive; see docstring - return "0+unknown" - - -__version__ = _discover_version() +try: + __version__ = _metadata.version("axom") +except _metadata.PackageNotFoundError: + # CMake build-tree staging does not create Python distribution metadata. + # Component modules, e.g. axom.sidre, still expose the C++ Axom version. + __version__ = "0+unknown" diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index 4fef836761..789152d542 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -201,8 +201,8 @@ if(NANOBIND_FOUND) axom_configure_file ("${CMAKE_CURRENT_SOURCE_DIR}/run_python_with_axom.sh.in" "${PROJECT_BINARY_DIR}/bin/run_python_with_axom.sh" @ONLY) - # gen python helper to install directory. - # Mirror the installed package root. + # gen python helper for install. + # Mirror the installed package root. # Keep relative package install dirs relative in the generated script too. # It resolves them from its own bin/ directory at runtime so `cmake --install --prefix` remains usable. if(AXOM_PYTHON_MODULE_INSTALL_PREFIX) @@ -217,11 +217,17 @@ if(NANOBIND_FOUND) set(_PYEXT_DIR_IS_RELATIVE FALSE) endif() + set(_AXOM_INSTALL_PYTHON_HELPER + "${PROJECT_BINARY_DIR}/bin/run_python_with_axom_install.sh") axom_configure_file ("${CMAKE_CURRENT_SOURCE_DIR}/run_python_with_axom.sh.in" - "${CMAKE_INSTALL_PREFIX}/bin/run_python_with_axom.sh" @ONLY) + "${_AXOM_INSTALL_PYTHON_HELPER}" @ONLY) + install(PROGRAMS "${_AXOM_INSTALL_PYTHON_HELPER}" + DESTINATION bin + RENAME run_python_with_axom.sh) unset(_PYEXT_DIR) unset(_PYEXT_DIR_IS_RELATIVE) + unset(_AXOM_INSTALL_PYTHON_HELPER) # Smoke test for the script: verify it makes Axom's bindings and runtime dependencies importable if(AXOM_ENABLE_TESTS AND AXOM_ENABLE_SIDRE) diff --git a/src/tools/run_python_with_axom.sh.in b/src/tools/run_python_with_axom.sh.in index 7a90a40b8d..7819480a31 100755 --- a/src/tools/run_python_with_axom.sh.in +++ b/src/tools/run_python_with_axom.sh.in @@ -9,7 +9,7 @@ ##----------------------------------------------------------------------------- ## Convenience script that runs the python interpreter with Axom's Python package(s) ## and their runtime dependencies already on PYTHONPATH: -## - Axom's Python package tree (the 'axom' namespace package; e.g. axom.sidre) +## - Axom's Python package tree (the 'axom' package; e.g. axom.sidre) ## - conduit's python module (conduit::Node interop) ## - numpy (ndarray returns) ## - mpi4py (only populated in MPI-enabled configurations) @@ -18,7 +18,7 @@ ## build tree that has the bindings enabled -- it assembles conduit/numpy/mpi4py ## from their spack prefixes so a one-off script "just works" without a venv. ## -## Caveats: this script is bash-only and, since it prepends the PYTHONPATH, +## Caveats: this script is bash-only and, since it prepends the PYTHONPATH, ## does not compose with Jupyter kernels, IDE runners, or debuggers. ## ## Build-time (nanobind) and testing dependencies (pytest/pluggy/iniconfig) From 1fecaf341cbf56812ab1470684011b513d006752 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 1 Jul 2026 21:13:21 -0700 Subject: [PATCH 726/986] sidre: In python interface, pinned views need to be associated with the DataStore --- src/axom/sidre/nanobind_sidre.cpp | 173 +++++++++++++++++----- src/axom/sidre/tests/sidre_lifetime_Py.py | 97 ++++++++++++ 2 files changed, 231 insertions(+), 39 deletions(-) diff --git a/src/axom/sidre/nanobind_sidre.cpp b/src/axom/sidre/nanobind_sidre.cpp index 44e3af6ecd..185e5e939a 100644 --- a/src/axom/sidre/nanobind_sidre.cpp +++ b/src/axom/sidre/nanobind_sidre.cpp @@ -275,46 +275,116 @@ conduit::Node& nbObjectToNode(nb::object& o) * the ndarray is garbage collected. * * To keep Sidre's C++ semantics unchanged while making the Python API safe, - * we maintain a binding-only registry that maps a C++ View* to a copied - * nanobind::ndarray wrapper. Copying nb::ndarray increments the underlying - * ndarray owner's refcount via nanobind's internal handle, so the NumPy storage - * remains alive as long as the View exists. + * we maintain a binding-only registry of "pins": copies of the nanobind ndarray + * wrapper. Copying nb::ndarray increments the underlying ndarray owner's + * refcount via nanobind's internal handle, so the NumPy storage stays alive for + * as long as the pin exists. A pin therefore ties the external array's lifetime + * to the *C++ View's* lifetime, not to any transient Python proxy: the array + * survives even if the Python View object that set it is discarded, as long as + * the View still lives in its DataStore (see the lifetime tests). * - * Pins are released when the external pointer is cleared (e.g. View.clear(), - * setExternalData(None)) and when views/groups are destroyed via the bound Group::destroy* APIs. + * **Per-DataStore scoping.** The registry is keyed by owning DataStore* and, + * within each DataStore, by View*. This is what makes raw-pointer keys safe: * - * **Registry Lifetime:** The registry persists for the process lifetime and may accumulate - * entries for destroyed Views if those Views are destroyed by the C++ DataStore destructor - * rather than through the Python-wrapped destroy methods. This is acceptable because: - * (1) Dangling View* keys are never dereferenced (we only erase, never lookup by pointer) - * (2) The memory overhead is small (one map entry per external View ever created) - * (3) In typical Python usage, Views with external data are explicitly destroyed via - * destroyView()/destroyGroup(), which properly releases pins. + * - When a DataStore's Python object is collected, nanobind destroys the C++ + * DataStore (the user always holds a DataStore through Python, so the two + * lifetimes coincide). We install a weak reference on the DataStore at the + * first pin whose callback erases that DataStore's entire sub-map. Pins are + * thus released no later than DataStore destruction -- the registry never + * grows without bound, even for Views torn down by the C++ DataStore + * destructor rather than an explicit destroyView()/destroyGroup(). + * - A View* is only meaningful within its owning DataStore, and that + * DataStore's sub-map is wiped when the DataStore dies. A View* address + * reused by a *different* DataStore therefore cannot collide with a stale + * pin, and a reused DataStore* address starts from a fresh (empty) sub-map. + * - We never look up a pin by a View* that might be stale: copyView/copyGroup + * only search for the source pin while the source View is live, then re-pin + * the destination from that live ndarray value. + * + * Pins are also released eagerly when the external pointer is cleared + * (View.clear(), setExternalData(None)) and when views/groups are destroyed via + * the bound destroy* APIs, so memory is reclaimed promptly in the common case + * rather than waiting for DataStore destruction. + * + * \note Thread safety: all access goes through the GIL (see the module + * docstring). If the bindings ever release the GIL, this registry needs a mutex. */ -std::unordered_map>& externalDataOwnerRegistry() +struct DataStoreExternalPins +{ + std::unordered_map> pins; + // Holds the weak reference whose callback clears this sub-map; keeping it here + // keeps the callback armed for the lifetime of the entries. + nb::object datastore_weakref; +}; + +std::unordered_map& externalDataOwnerRegistry() { - // Intentionally heap-allocated so Python-owned references are not destroyed - // after interpreter finalization during static shutdown. - static auto* registry = new std::unordered_map>(); + // Intentionally heap-allocated so any Python-owned references it still holds + // are not destroyed after interpreter finalization during static shutdown. + static auto* registry = new std::unordered_map(); return *registry; } -template -void pinExternalDataOwner(View* view, const nb::ndarray& owner) +//! Return the owning DataStore of a View, or nullptr if it has none yet. +DataStore* owningDataStore(View* view) +{ + if(view == nullptr) + { + return nullptr; + } + Group* group = view->getOwningGroup(); + return (group != nullptr) ? group->getDataStore() : nullptr; +} + +//! Erase all pins recorded for \a ds (called when the DataStore is collected). +void releaseDataStoreExternalPins(DataStore* ds) { externalDataOwnerRegistry().erase(ds); } + +/*! + * \brief Record \a owner as the pin for \a view, scoped to its DataStore. + * + * On the first pin into a given DataStore, installs a weak reference on the + * DataStore's Python object so the sub-map is cleared when the DataStore is + * destroyed. Re-assigning a View*'s pin releases the previous ndarray wrapper. + */ +void pinExternalDataOwner(View* view, const nb::ndarray<>& owner) { - if(view != nullptr) + DataStore* ds = owningDataStore(view); + if(view == nullptr || ds == nullptr) + { + return; + } + + DataStoreExternalPins& entry = externalDataOwnerRegistry()[ds]; + if(!entry.datastore_weakref.is_valid()) { - // Note: Map assignment automatically releases the previous ndarray wrapper if present. - // When nb::ndarray<> is destroyed, nanobind decrements the underlying Python object's refcount - externalDataOwnerRegistry()[view] = nb::ndarray<>(owner); + // Retrieve the DataStore's existing Python wrapper and attach a weakref + // whose callback clears this DataStore's pins. nb::find returns a null + // object if no wrapper exists; in that (unexpected) case we simply skip the + // weakref -- the eager release paths still apply, and the worst case is the + // pre-existing process-lifetime retention. + nb::object ds_obj = nb::find(*ds); + if(ds_obj.is_valid() && !ds_obj.is_none()) + { + entry.datastore_weakref = + nb::weakref(ds_obj, nb::cpp_function([ds](nb::handle) { releaseDataStoreExternalPins(ds); })); + } } + + // Map assignment releases the previous ndarray wrapper if one was present. + entry.pins[view] = nb::ndarray<>(owner); } void releaseExternalDataOwner(View* view) { - if(view != nullptr) + DataStore* ds = owningDataStore(view); + if(view == nullptr || ds == nullptr) + { + return; + } + auto it = externalDataOwnerRegistry().find(ds); + if(it != externalDataOwnerRegistry().end()) { - externalDataOwnerRegistry().erase(view); + it->second.pins.erase(view); } } @@ -345,14 +415,17 @@ void releaseExternalDataOwnersOfViews(Group& group) } /*! - * \brief Copy external data pin from source View to destination View. + * \brief Copy the external-data pin from a source View to a destination View. * - * When copyView() creates a shallow copy that shares external data, the new View - * needs its own pin to prevent the numpy array from being garbage collected. - * This function looks up the source View's pin and copies it to the destination. + * copyView() makes a shallow copy that shares the external pointer, so the + * destination needs its own pin to keep the NumPy array alive independently of + * the source. The source View is live for the duration of the copy (the caller + * holds it), so looking up its pin within its own DataStore's sub-map is safe; + * we then pin the destination from that live ndarray value. We never search the + * registry by a View* that could be stale. * - * \param src_view Source View (must have an external data pin) - * \param dst_view Destination View (will receive a copy of the pin) + * \param src_view Source View (live; expected to hold an external data pin) + * \param dst_view Destination View (receives a copy of the pin) */ void copyExternalDataOwner(const View* src_view, View* dst_view) { @@ -361,12 +434,25 @@ void copyExternalDataOwner(const View* src_view, View* dst_view) return; } + DataStore* src_ds = owningDataStore(const_cast(src_view)); + if(src_ds == nullptr) + { + return; + } + auto& registry = externalDataOwnerRegistry(); - auto it = registry.find(const_cast(src_view)); - if(it != registry.end()) + auto ds_it = registry.find(src_ds); + if(ds_it == registry.end()) + { + return; + } + + auto pin_it = ds_it->second.pins.find(const_cast(src_view)); + if(pin_it != ds_it->second.pins.end()) { - // Copy the ndarray handle to the new View, incrementing its refcount - registry[dst_view] = it->second; + // Re-pin the destination from the source's live ndarray value (scoped to + // the destination's own DataStore by pinExternalDataOwner). + pinExternalDataOwner(dst_view, pin_it->second); } } @@ -531,10 +617,16 @@ NB_MODULE(_sidre, m_sidre) **External Data Lifetime:** Views can reference external numpy arrays via setExternalData() or createView(). The binding automatically pins these arrays to prevent garbage collection while - the View exists. Pins are released when: - - View.clear() is called - - The View is destroyed via destroyView() or destroyViewAndData() + the View exists, so an array stays valid even if the Python View object that set + it is discarded (as long as the View still lives in its DataStore). Pins are + scoped per-DataStore and are released when: + - View.clear() or setExternalData(None) is called + - The View is destroyed via destroyView() / destroyViewAndData() - The owning Group hierarchy is destroyed via destroyGroup*() methods + - The owning DataStore is destroyed (a weak reference on the DataStore clears + its remaining pins, so the registry never grows without bound -- even for + Views torn down by the C++ DataStore destructor rather than an explicit + destroy* call) **Reallocation Hazards:** Arrays obtained via getDataArray() are zero-copy views into Sidre storage. @@ -606,7 +698,10 @@ NB_MODULE(_sidre, m_sidre) bindIterator(m_sidre, "ViewIterator"); // Bindings for the DataStore class - nb::class_(m_sidre, "DataStore") + // DataStore is weak-referenceable so the external-data registry can attach a + // weakref callback that releases that DataStore's pins when it is destroyed + // (see externalDataOwnerRegistry). + nb::class_(m_sidre, "DataStore", nb::is_weak_referenceable()) .def(nb::init<>()) .def("getRoot", nb::overload_cast<>(&DataStore::getRoot), diff --git a/src/axom/sidre/tests/sidre_lifetime_Py.py b/src/axom/sidre/tests/sidre_lifetime_Py.py index bd1a89b9a4..2f1f534d49 100644 --- a/src/axom/sidre/tests/sidre_lifetime_Py.py +++ b/src/axom/sidre/tests/sidre_lifetime_Py.py @@ -580,6 +580,103 @@ def test_registry_cleanup_on_explicit_destroy(): f"Only {collected_count}/{len(weak_refs)} arrays collected after explicit destroy" +def test_external_pins_released_when_datastore_destroyed(): + """Pins are released when the DataStore is destroyed without explicit destroy*(). + + This is the implicit counterpart to test_registry_cleanup_on_explicit_destroy: + the Views are torn down by the DataStore destructor (the C++ path), not by a + bound destroyView()/destroyGroup(). A weak reference on the DataStore must + still clear its pins, so the external arrays are collected and the registry + does not accumulate dangling entries. + """ + weak_refs = [] + + def build_and_drop(): + ds = pysidre.DataStore() + root = ds.getRoot() + for i in range(5): + external = np.arange(10, dtype=np.int32) + weak_refs.append(weakref.ref(external)) + # Mix createView(external) and setExternalData() entry points. + if i % 2 == 0: + root.createView(f"view_{i}", external).apply(pysidre.TypeID.INT32_ID, 10) + else: + root.createView(f"view_{i}").setExternalData(pysidre.TypeID.INT32_ID, 10, external) + # Pins keep the arrays alive while ds is alive... + gc.collect() + assert all(ref() is not None for ref in weak_refs) + # ...and ds goes out of scope here without any explicit destroy call. + + build_and_drop() + _force_gc() + + collected = sum(1 for ref in weak_refs if ref() is None) + assert collected == len(weak_refs), \ + f"Only {collected}/{len(weak_refs)} external arrays collected after DataStore destruction" + + +def test_external_pins_released_for_nested_groups_on_datastore_destruction(): + """DataStore destruction releases pins for Views nested in child Groups too.""" + weak_refs = [] + + def build_and_drop(): + ds = pysidre.DataStore() + root = ds.getRoot() + grp = root.createGroup("a/b/c") + for i in range(3): + external = np.arange(8, dtype=np.int64) + weak_refs.append(weakref.ref(external)) + grp.createView(f"deep_{i}", external).apply(pysidre.TypeID.INT64_ID, 8) + gc.collect() + assert all(ref() is not None for ref in weak_refs) + + build_and_drop() + _force_gc() + + assert all(ref() is None for ref in weak_refs), \ + "Nested-Group external arrays were not released on DataStore destruction" + + +def test_external_pins_isolated_between_datastores(): + """A View* address reused across DataStores must not cross-associate pins. + + Each DataStore owns a private pin scope. Destroying one DataStore releases + only its own pins; a concurrently live DataStore is unaffected, even though + the allocator may hand out overlapping View* addresses across them. + """ + keep_alive = [] + surviving_refs = [] + + # Build and drop several DataStores in sequence, encouraging View* reuse. + for _ in range(4): + ds = pysidre.DataStore() + a = np.arange(6, dtype=np.int64) + r = weakref.ref(a) + ds.getRoot().createView("v", a).apply(pysidre.TypeID.INT64_ID, 6) + del a, ds + _force_gc() + # Each dropped DataStore must release its own array. + assert r() is None + + # A long-lived DataStore created afterwards (possibly at a reused address) + # must hold its own pin independently. + survivor = pysidre.DataStore() + b = np.arange(6, dtype=np.int64) + surviving_refs.append(weakref.ref(b)) + survivor.getRoot().createView("v", b).apply(pysidre.TypeID.INT64_ID, 6) + keep_alive.append(survivor) + del b + _force_gc() + assert surviving_refs[0]() is not None, \ + "Survivor DataStore's pin was wrongly released (cross-datastore misattribution)" + + # Cleanup releases the survivor's pin. + del survivor + keep_alive.clear() + _force_gc() + assert surviving_refs[0]() is None + + def test_multiple_concurrent_datastores(): """Multiple active DataStores with external data should not interfere with each other.""" # Create multiple DataStores simultaneously From 52ef44f76a05c59695e3806348b2072d070a382a Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 1 Jul 2026 22:20:31 -0700 Subject: [PATCH 727/986] sidre: Removes unreachable Python binding for setExternal --- src/axom/sidre/nanobind_sidre.cpp | 23 +++++----- src/axom/sidre/tests/sidre_lifetime_Py.py | 52 +++++++++++++++++++++++ 2 files changed, 63 insertions(+), 12 deletions(-) diff --git a/src/axom/sidre/nanobind_sidre.cpp b/src/axom/sidre/nanobind_sidre.cpp index 185e5e939a..f3a8195605 100644 --- a/src/axom/sidre/nanobind_sidre.cpp +++ b/src/axom/sidre/nanobind_sidre.cpp @@ -7,10 +7,12 @@ #include #include #include +#include #include #include #include +#include #include #include "axom/config.hpp" @@ -1034,26 +1036,23 @@ NB_MODULE(_sidre, m_sidre) nb::arg("allocID") = INVALID_ALLOCATOR_ID) .def( "setExternalData", - [](View& self, nb::object external_ptr) { - if(external_ptr.is_none()) + [](View& self, std::optional> external_ptr) { + // A single undescribed-data overload covering both None + // (clear the external pointer and release any pin) and a numpy array (set + pin). + // Using std::optional lets nanobind reject a non-array argument + // with a clean "incompatible function arguments" error + // rather than throwing mid-body from an explicit cast. + if(!external_ptr.has_value()) { View* result = self.setExternalDataPtr(nullptr); releaseExternalDataOwner(&self); return result; } - nb::ndarray<> owner = nb::cast>(external_ptr); - return setExternalDataAndPinOwner(self, owner); + return setExternalDataAndPinOwner(self, *external_ptr); }, nb::rv_policy::reference, - "Set the View to hold undescribed external data (numpy array).", + "Set the View to hold undescribed external data, or clear it when passed None.", nb::arg("external_ptr").none()) - .def( - "setExternalData", - [](View& self, const nb::ndarray<>& external_ptr) { - return setExternalDataAndPinOwner(self, external_ptr); - }, - nb::rv_policy::reference, - "Set the View to hold undescribed external data (numpy array).") .def( "setExternalData", [](View& self, TypeID type, IndexType num_elems, const nb::ndarray<>& external_ptr) { diff --git a/src/axom/sidre/tests/sidre_lifetime_Py.py b/src/axom/sidre/tests/sidre_lifetime_Py.py index 2f1f534d49..6ddd097f97 100644 --- a/src/axom/sidre/tests/sidre_lifetime_Py.py +++ b/src/axom/sidre/tests/sidre_lifetime_Py.py @@ -400,6 +400,58 @@ def test_clear_releases_external_array_owner(): assert ref() is None +def test_set_external_data_none_clears_and_releases_pin(): + """setExternalData(None) clears the external pointer and releases the pin.""" + ds = pysidre.DataStore() + view = ds.getRoot().createView("external") + + external = np.arange(6, dtype=np.int64) + ref = weakref.ref(external) + view.setExternalData(external) + + del external + _force_gc() + assert ref() is not None + assert view.isExternal() + + view.setExternalData(None) + _force_gc() + assert not view.isExternal() + assert ref() is None + + +def test_set_external_data_undescribed_array_pins(): + """The single-argument setExternalData(array) overload pins the array.""" + ds = pysidre.DataStore() + view = ds.getRoot().createView("external") + + def assign(): + external = np.arange(6, dtype=np.int64) + ref = weakref.ref(external) + view.setExternalData(external) # undescribed, single-arg overload + return ref + + ref = assign() + _force_gc() + assert ref() is not None + assert view.isExternal() + + +def test_set_external_data_rejects_non_array_argument(): + """A non-array, non-None argument is rejected with a clean TypeError. + + The single-argument overload takes Optional[ndarray]; nanobind reports + 'incompatible function arguments' rather than throwing from an internal + cast, so callers get the standard overload-resolution diagnostic. + """ + ds = pysidre.DataStore() + view = ds.getRoot().createView("external") + with pytest.raises(TypeError): + view.setExternalData("not an array") + with pytest.raises(TypeError): + view.setExternalData(12345) + + def test_copy_view_with_external_data_preserves_pin(): """copyView on an external View should copy the pin to prevent premature collection.""" ds = pysidre.DataStore() From 71e8428850b6b45d11e1b8ca1e9145345060a69d Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 1 Jul 2026 22:28:02 -0700 Subject: [PATCH 728/986] sidre: Improves some python docs --- src/axom/sidre/nanobind_sidre.cpp | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/axom/sidre/nanobind_sidre.cpp b/src/axom/sidre/nanobind_sidre.cpp index f3a8195605..24bcbbdbbb 100644 --- a/src/axom/sidre/nanobind_sidre.cpp +++ b/src/axom/sidre/nanobind_sidre.cpp @@ -17,6 +17,8 @@ #include "axom/config.hpp" #include "axom/core/Types.hpp" +#include "axom/slic/interface/slic.hpp" + #include "core/SidreTypes.hpp" #include "core/Buffer.hpp" #include "core/View.hpp" @@ -551,9 +553,9 @@ MPI_Comm mpiCommFromObject(nb::object comm) * duplicate or free it. That borrowed-communicator contract works for C++ * callers, but it is unsafe for mpi4py objects: py2f() exposes the object's * current communicator handle, and Python code may later drop or explicitly - * Free() that object while pysidre.IOManager is still alive. + * Free() that object while axom.sidre.IOManager is still alive. * - * PyIOManager keeps the public Python class name as pysidre.IOManager while + * PyIOManager keeps the public Python class name as axom.sidre.IOManager while * giving the binding its own lifetime boundary. It duplicates the input * communicator, constructs sidre::IOManager with that duplicate, destroys the * IOManager first, and then frees the duplicate when MPI is still active. @@ -564,7 +566,8 @@ class PyIOManager PyIOManager(MPI_Comm comm, bool use_scr) { int err = MPI_Comm_dup(comm, &m_comm); - SLIC_ERROR_IF(err != MPI_SUCCESS, "Failed to duplicate MPI communicator for pysidre.IOManager"); + SLIC_ERROR_IF(err != MPI_SUCCESS, + "Failed to duplicate MPI communicator for axom.sidre.IOManager"); m_manager = std::make_unique(m_comm, use_scr); } @@ -849,7 +852,13 @@ NB_MODULE(_sidre, m_sidre) .def("getIndex", &Buffer::getIndex, "Return the unique index of this Buffer object.") .def("getNumViews", &Buffer::getNumViews, "Return number of Views this Buffer is attached to.") // .def("getVoidPtr", &Buffer::getVoidPtr, "Return void-pointer to data held by Buffer.") - .def("getDataArray", &bufferToNumpyArray, "Return the data held by the Buffer as a numpy array.") + .def("getDataArray", + &bufferToNumpyArray, + "Return the data held by the Buffer as a numpy array.\n\n" + "The array is a zero-copy view into the Buffer's storage " + "and keeps the Buffer (and its DataStore) alive while referenced. " + "Buffer.reallocate() can move the storage, leaving a previously returned array " + "pointing at freed memory; re-acquire the array after any reallocation.") .def("getTypeID", &Buffer::getTypeID, "Return type of data owned by this Buffer object.") .def("getNumElements", &Buffer::getNumElements, @@ -1076,7 +1085,14 @@ NB_MODULE(_sidre, m_sidre) &View::getString, nb::rv_policy::reference, "Return the string contained in the View.") - .def("getDataArray", &viewToNumpyArray, "Return the data held by the View as a numpy array.") + .def("getDataArray", + &viewToNumpyArray, + "Return the data held by the View as a numpy array.\n\n" + "The array is a zero-copy view into the View's storage " + "and keeps the View (and its DataStore) alive while referenced. " + "View.reallocate() (or reallocating the underlying Buffer) can move the storage, " + "leaving a previously returned array pointing at freed memory; " + "re-acquire the array after any reallocation.") .def( "getDataInt", From 6264f0b4cbeb1ac96b4a52b0c7939f56209352b4 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 1 Jul 2026 22:34:34 -0700 Subject: [PATCH 729/986] sidre: configure and install sidre Python stubs This allows type checkers to see the sidre Python API --- src/axom/sidre/CMakeLists.txt | 5 +++++ src/python/README.md | 1 + src/python/src/axom/sidre/__init__.pyi | 22 ++++++++++++++++++++++ 3 files changed, 28 insertions(+) create mode 100644 src/python/src/axom/sidre/__init__.pyi diff --git a/src/axom/sidre/CMakeLists.txt b/src/axom/sidre/CMakeLists.txt index c8d96b242f..510ac767a8 100644 --- a/src/axom/sidre/CMakeLists.txt +++ b/src/axom/sidre/CMakeLists.txt @@ -204,6 +204,10 @@ if(NANOBIND_FOUND) "${_axom_py_build_root}/axom/py.typed" COPYONLY) axom_configure_file("${_pysidre_pkg_src}/axom/sidre/__init__.py" "${_axom_py_build_root}/axom/sidre/__init__.py" COPYONLY) + # Hand-written package stub: re-exports the generated _sidre.pyi statically + # so type checkers can see axom.sidre's surface + axom_configure_file("${_pysidre_pkg_src}/axom/sidre/__init__.pyi" + "${_axom_py_build_root}/axom/sidre/__init__.pyi" COPYONLY) axom_configure_file("${_pysidre_pkg_src}/pysidre/__init__.py" "${_axom_py_build_root}/pysidre/__init__.py" COPYONLY) @@ -235,6 +239,7 @@ if(NANOBIND_FOUND) install(FILES "${_axom_py_build_root}/axom/sidre/_sidre.pyi" "${_pysidre_pkg_src}/axom/sidre/__init__.py" + "${_pysidre_pkg_src}/axom/sidre/__init__.pyi" DESTINATION "${AXOM_PYTHON_MODULE_INSTALL_PREFIX}/axom/sidre") # Namespace-root package files install once (not per component). diff --git a/src/python/README.md b/src/python/README.md index 4385fb02ed..ce11e0addf 100644 --- a/src/python/README.md +++ b/src/python/README.md @@ -35,6 +35,7 @@ src/python/ py.typed <- PEP 561 marker (typed package) sidre/ __init__.py <- re-exports the compiled 'axom.sidre._sidre' + __init__.pyi <- package stub; re-exports '_sidre.pyi' for type checkers (_sidre..so) <- compiled extension, produced by the build (_sidre.pyi) <- type stub, produced by the build pysidre/ diff --git a/src/python/src/axom/sidre/__init__.pyi b/src/python/src/axom/sidre/__init__.pyi new file mode 100644 index 0000000000..14915c0f56 --- /dev/null +++ b/src/python/src/axom/sidre/__init__.pyi @@ -0,0 +1,22 @@ +# 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) + +# Type stub for the ``axom.sidre`` package. +# +# At runtime ``__init__.py`` re-exports the compiled ``axom.sidre._sidre`` +# extension's public surface dynamically (via ``globals().update(...)``), which +# a static type checker cannot follow. This stub mirrors that re-export +# statically: ``from ._sidre import *`` pulls the typed declarations from the +# generated, adjacent ``_sidre.pyi`` so that ``axom.sidre.DataStore`` etc. +# resolve for mypy/pyright. Keep this in sync with the re-export logic in +# ``__init__.py``; the runtime module is the source of truth. + +from ._sidre import * # noqa: F401,F403 + +# ``__version__`` is conventionally public but excluded from the wildcard +# surface (it starts with an underscore), so re-export it explicitly here, +# matching ``__init__.py``. +__version__: str From 61a0792c0b9ec976ee4c06060385af20e1ad76fd Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 1 Jul 2026 23:33:55 -0700 Subject: [PATCH 730/986] python: Adds installation tests for Axom Python bindings The tests are in our github-ci tests as well as for spack-based installations --- .../github-actions/linux-build_and_test.sh | 15 +++++++++-- scripts/spack/packages/axom/package.py | 13 ++++++++++ src/examples/CMakeLists.txt | 13 ++++++++++ src/examples/using-with-python/README.md | 12 +++++++++ src/examples/using-with-python/example.py | 26 +++++++++++++++++++ 5 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 src/examples/using-with-python/README.md create mode 100644 src/examples/using-with-python/example.py diff --git a/scripts/github-actions/linux-build_and_test.sh b/scripts/github-actions/linux-build_and_test.sh index a7a780ffe6..6b5041ce1d 100755 --- a/scripts/github-actions/linux-build_and_test.sh +++ b/scripts/github-actions/linux-build_and_test.sh @@ -45,7 +45,7 @@ if [[ "$DO_BUILD" == "yes" ]] ; then fi echo "~~~~~~ RUNNING TESTS ~~~~~~~~" - make CTEST_OUTPUT_ON_FAILURE=1 test ARGS='-T Test --output-on-failure -j$NUM_BUILD_PROCS' + or_die make CTEST_OUTPUT_ON_FAILURE=1 test ARGS='-T Test --output-on-failure -j$NUM_BUILD_PROCS' if [[ "${DO_BENCHMARKS}" == "yes" ]] ; then echo "~~~~~~ RUNNING BENCHMARKS ~~~~~~~~" @@ -56,5 +56,16 @@ if [[ "$DO_BUILD" == "yes" ]] ; then echo "~~~~~~ RUNNING MEMCHECK ~~~~~~~~" or_die ctest -T memcheck fi -fi + echo "~~~~~~ INSTALLING ~~~~~~~~" + or_die make install + + # For configs that generated Python bindings, check that we can run a Python script with Axom + INSTALL_PREFIX=$(awk -F= '/^CMAKE_INSTALL_PREFIX:PATH=/{print $2}' CMakeCache.txt) + PYTHON_RUNNER="${INSTALL_PREFIX}/bin/run_python_with_axom.sh" + PYTHON_EXAMPLE="${INSTALL_PREFIX}/examples/axom/using-with-python/example.py" + if [[ -x "${PYTHON_RUNNER}" && -f "${PYTHON_EXAMPLE}" ]] ; then + echo "~~~~~~ RUNNING INSTALLED PYTHON EXAMPLE ~~~~~~~~" + or_die "${PYTHON_RUNNER}" "${PYTHON_EXAMPLE}" + fi +fi diff --git a/scripts/spack/packages/axom/package.py b/scripts/spack/packages/axom/package.py index 3bcb0783e0..e813bd0af4 100644 --- a/scripts/spack/packages/axom/package.py +++ b/scripts/spack/packages/axom/package.py @@ -881,3 +881,16 @@ def test_install_using_make(self): example = Executable("./example") example() make("clean") + + @run_after("install", when="+examples+python+tools components=sidre") + @on_package_attributes(run_tests=True) + def test_install_using_python(self): + """run python example against installed axom""" + example = join_path(self.prefix.examples.axom, "using-with-python", "example.py") + python_runner = join_path(self.prefix.bin, "run_python_with_axom.sh") + if not os.path.isfile(example): + raise RuntimeError("Missing installed python example: {0}".format(example)) + if not os.path.isfile(python_runner): + raise RuntimeError("Missing installed python runner: {0}".format(python_runner)) + run_python = Executable(python_runner) + run_python(example) diff --git a/src/examples/CMakeLists.txt b/src/examples/CMakeLists.txt index 9a1b6f78b4..87ecbb6e41 100644 --- a/src/examples/CMakeLists.txt +++ b/src/examples/CMakeLists.txt @@ -79,6 +79,19 @@ if (AXOM_ENABLE_EXAMPLES) ) endif() +#------------------------------------------------------------------------------ +# install 'using-with-python' example +#------------------------------------------------------------------------------ +if(AXOM_ENABLE_EXAMPLES AND NANOBIND_FOUND AND AXOM_ENABLE_SIDRE) + install( + FILES + using-with-python/README.md + using-with-python/example.py + DESTINATION + examples/axom/using-with-python + ) +endif() + #------------------------------------------------------------------------------ # configure and install 'radiuss_tutorial' example # This example requires Quest (which requires Slic, Mint, Primal and Spin) diff --git a/src/examples/using-with-python/README.md b/src/examples/using-with-python/README.md new file mode 100644 index 0000000000..7f1a5f0670 --- /dev/null +++ b/src/examples/using-with-python/README.md @@ -0,0 +1,12 @@ +# Using Axom With Python + +This example runs against an installed Axom Python package. + +From an Axom install prefix, run: + +```bash +./bin/run_python_with_axom.sh examples/axom/using-with-python/example.py +``` + +The helper script sets `PYTHONPATH` for `axom.sidre` and its Python runtime +dependencies. diff --git a/src/examples/using-with-python/example.py b/src/examples/using-with-python/example.py new file mode 100644 index 0000000000..b0f6b30e57 --- /dev/null +++ b/src/examples/using-with-python/example.py @@ -0,0 +1,26 @@ +# 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) + +import axom.sidre as sidre +import numpy as np + + +def main(): + datastore = sidre.DataStore() + root = datastore.getRoot() + fields = root.createGroup("fields") + + values = np.arange(8, dtype=np.float64) + fields.createView("values", values).apply(sidre.TypeID.DOUBLE_ID, len(values)) + + view_data = fields.getView("values").getDataArray() + assert np.array_equal(view_data, values) + + print(f"Using installed axom.sidre {sidre.__version__}") + + +if __name__ == "__main__": + main() From 46c633dd8d0e1c270796f2cf828d851a57be9fc4 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 2 Jul 2026 00:17:36 -0700 Subject: [PATCH 731/986] python: Registers axom as the NB_DOMAIN for axom.sidre This will allow the types from all of the Axom python modules to better interoperate. --- src/axom/sidre/CMakeLists.txt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/axom/sidre/CMakeLists.txt b/src/axom/sidre/CMakeLists.txt index 510ac767a8..d0c15acf87 100644 --- a/src/axom/sidre/CMakeLists.txt +++ b/src/axom/sidre/CMakeLists.txt @@ -175,7 +175,13 @@ if(NANOBIND_FOUND) set(_axom_py_build_root "${PROJECT_BINARY_DIR}/python") set(_pysidre_pkg_src "${CMAKE_CURRENT_SOURCE_DIR}/../../python/src") - nanobind_add_module(_sidre nanobind_sidre.cpp) + # Build the extension under the shared 'axom' nanobind domain. + # All of Axom's extension modules (currently just axom.sidre) share NB_DOMAIN=axom + # so that C++ types bound in one module, e.g. a sidre::Group*, + # recognized when passed to another module built from the same Axom build. + # nanobind only shares type bindings across modules that agree on + # domain *and* nanobind ABI, compiler, and build mode. + nanobind_add_module(_sidre nanobind_sidre.cpp NB_DOMAIN axom) # conduit::conduit_python provides conduit_python.hpp # and is needed only by the binding translation unit, not by libsidre target_link_libraries(_sidre PRIVATE sidre conduit::conduit_python) From c18ab0baef403bd6c93928078dd39ca82bfdf12b Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 15 Jul 2026 12:54:08 -0700 Subject: [PATCH 732/986] Adds a spack installation test for Python (when enabled) --- scripts/spack/packages/axom/package.py | 56 +++++++++++++++++-- src/cmake/AxomMacros.cmake | 5 +- .../thirdparty/SetupAxomThirdParty.cmake | 36 ++++++++++-- 3 files changed, 84 insertions(+), 13 deletions(-) diff --git a/scripts/spack/packages/axom/package.py b/scripts/spack/packages/axom/package.py index e813bd0af4..cbefed5647 100644 --- a/scripts/spack/packages/axom/package.py +++ b/scripts/spack/packages/axom/package.py @@ -5,6 +5,7 @@ import os import shutil import socket +import tempfile from os.path import join as pjoin from spack_repo.builtin.build_systems.cached_cmake import ( @@ -286,6 +287,8 @@ class Axom(CachedCMakePackage, CudaPackage, ROCmPackage): depends_on("py-nanobind@2.7.0:") depends_on("py-pytest") + depends_on("py-packaging") + depends_on("py-pygments") depends_on("py-numpy") depends_on("py-mpi4py", when="+mpi") depends_on("conduit+python", when="+conduit") @@ -800,20 +803,23 @@ def initconfig_package_entries(self): ) if spec.satisfies("+python"): + python_platlib = spec["python"].package.platlib + # pytest requires pluggy and iniconfig + # newer pytest releases also import packaging/pygments from separate Spack prefixes. for dep in ( "py-nanobind", "py-pytest", "py-numpy", "py-pluggy", "py-iniconfig", + "py-packaging", + "py-pygments", "py-mpi4py", ): if spec.satisfies("^{0}".format(dep)): - dep_dir = get_spec_path(spec, dep, path_replacements, use_lib=True) - py_libdir = join_path( - dep_dir, f"python{spec['python'].version.up_to(2)}", "site-packages" - ) + dep_dir = get_spec_path(spec, dep, path_replacements) + py_libdir = join_path(dep_dir, python_platlib) entries.append( cmake_cache_path("%s_DIR" % dep.upper().replace("-", "_"), py_libdir) ) @@ -858,7 +864,8 @@ def build_test(self): def test_install_using_cmake(self): """build example with cmake and run""" example_src_dir = join_path(self.prefix.examples.axom, "using-with-cmake") - example_stage_dir = "./cmake" + example_test_dir = tempfile.mkdtemp(prefix="axom-cmake-example-") + example_stage_dir = join_path(example_test_dir, "using-with-cmake") shutil.copytree(example_src_dir, example_stage_dir) with working_dir(join_path(example_stage_dir, "build"), create=True): cmake_args = ["-C ../host-config.cmake", example_src_dir] @@ -874,7 +881,8 @@ def test_install_using_cmake(self): def test_install_using_make(self): """build example with make and run""" example_src_dir = join_path(self.prefix.examples.axom, "using-with-make") - example_stage_dir = "./make" + example_test_dir = tempfile.mkdtemp(prefix="axom-make-example-") + example_stage_dir = join_path(example_test_dir, "using-with-make") shutil.copytree(example_src_dir, example_stage_dir) with working_dir(example_stage_dir, create=True): make(f"AXOM_DIR={self.prefix}") @@ -894,3 +902,39 @@ def test_install_using_python(self): raise RuntimeError("Missing installed python runner: {0}".format(python_runner)) run_python = Executable(python_runner) run_python(example) + + @run_after("install", when="+python components=sidre") + @on_package_attributes(run_tests=True) + def test_axom_sidre_installed_into_site_packages(self): + """Check axom.sidre installed into a site-packages-shaped prefix + and imports from view-shaped site-packages paths. + """ + python_pkg = self.spec["python"].package + python_platlib = python_pkg.platlib + site_packages = join_path(self.prefix, python_platlib) + sidre_pkg_dir = join_path(site_packages, "axom", "sidre") + if not os.path.isdir(sidre_pkg_dir): + raise RuntimeError( + "axom.sidre was not installed under the interpreter platlib: " + "{0}".format(sidre_pkg_dir) + ) + + # Assemble the Python package directories a view would merge into site-packages. + import_path = [site_packages] + if self.spec.satisfies("+conduit"): + for conduit_py in ( + join_path(self.spec["conduit"].prefix, python_platlib), + join_path(self.spec["conduit"].prefix, "python-modules"), + ): + if os.path.isdir(conduit_py): + import_path.append(conduit_py) + + for dep in ("py-numpy", "py-mpi4py"): + if self.spec.satisfies("^{0}".format(dep)): + dep_py = join_path(self.spec[dep].prefix, python_platlib) + if os.path.isdir(dep_py): + import_path.append(dep_py) + + imports = "import axom.sidre as s; import numpy; print('axom.sidre', s.__version__)" + python = Executable(join_path(self.spec["python"].prefix.bin, "python3")) + python("-c", imports, extra_env={"PYTHONPATH": ":".join(import_path)}) diff --git a/src/cmake/AxomMacros.cmake b/src/cmake/AxomMacros.cmake index 9b6833199f..e18bff21b6 100644 --- a/src/cmake/AxomMacros.cmake +++ b/src/cmake/AxomMacros.cmake @@ -614,7 +614,7 @@ endmacro(axom_configure_file) ## 1. the staged Python package tree (axom/ + pysidre shim) -- runtime ## 2. conduit's python module dir -- runtime ## 3. numpy, then mpi4py (MPI configs) -- runtime -## 4. pytest and its dependencies (pluggy, iniconfig) -- test harness +## 4. pytest and its dependencies -- test harness ## ## Axom's own package tree comes first so it is preferred over anything the ## interpreter might also provide. Entries whose cache variable is unset are skipped; @@ -632,7 +632,8 @@ function(axom_python_test_environment output_var) endforeach() # (4) test-harness dependencies - foreach(_var PY_PYTEST_DIR PY_PLUGGY_DIR PY_INICONFIG_DIR) + foreach(_var PY_PYTEST_DIR PY_PLUGGY_DIR PY_INICONFIG_DIR + PY_PACKAGING_DIR PY_PYGMENTS_DIR) blt_list_append(TO _paths ELEMENTS "${${_var}}" IF ${_var}) endforeach() diff --git a/src/cmake/thirdparty/SetupAxomThirdParty.cmake b/src/cmake/thirdparty/SetupAxomThirdParty.cmake index c46f1c6966..709ae46beb 100644 --- a/src/cmake/thirdparty/SetupAxomThirdParty.cmake +++ b/src/cmake/thirdparty/SetupAxomThirdParty.cmake @@ -331,8 +331,8 @@ if(EXISTS ${Python_EXECUTABLE}) ) endif() - # Check if the python environment contains the runtime dependencies for Axom's python - # conduit (Node interop) and numpy (ndarray returns). + # Check if the python environment contains the runtime dependencies + # for Axom's python conduit (Node interop) and numpy (ndarray returns). # nanobind is statically linked at build time and is located separately above. execute_process( COMMAND "${CMAKE_COMMAND}" -E env @@ -342,7 +342,7 @@ if(EXISTS ${Python_EXECUTABLE}) ERROR_QUIET ) - # Check if the python environment contains the pytest test harness, + # Check if the python environment contains the pytest test harness execute_process( COMMAND "${CMAKE_COMMAND}" -E env "${Python_EXECUTABLE}" -c "import pytest" @@ -361,6 +361,32 @@ if(EXISTS ${Python_EXECUTABLE}) ) endif() +if(AXOM_ENABLE_PYTHON_TESTS + AND (NOT PY_PYTEST_IMPORT_CODE EQUAL 0) + AND nanobind_ROOT + AND PY_PYTEST_DIR + AND PY_PLUGGY_DIR + AND PY_INICONFIG_DIR) + set(_axom_pytest_pythonpath + "${PY_PYTEST_DIR}" + "${PY_PLUGGY_DIR}" + "${PY_INICONFIG_DIR}") + foreach(_var PY_PACKAGING_DIR PY_PYGMENTS_DIR) + blt_list_append(TO _axom_pytest_pythonpath ELEMENTS "${${_var}}" IF ${_var}) + endforeach() + list(JOIN _axom_pytest_pythonpath ":" _axom_pytest_pythonpath_joined) + execute_process( + COMMAND "${CMAKE_COMMAND}" -E env + "PYTHONPATH=${_axom_pytest_pythonpath_joined}" + "${Python_EXECUTABLE}" -c "import pytest" + RESULT_VARIABLE PY_PYTEST_IMPORT_CODE + OUTPUT_QUIET + ERROR_QUIET + ) + unset(_axom_pytest_pythonpath) + unset(_axom_pytest_pythonpath_joined) +endif() + # If the python environment does not contain the required runtime modules, # check if library installation paths were provided instead. if((NOT PY_RUNTIME_IMPORT_CODE EQUAL 0) @@ -380,9 +406,9 @@ if(AXOM_ENABLE_PYTHON_TESTS AND nanobind_ROOT AND (NOT PY_PYTEST_DIR OR NOT PY_PLUGGY_DIR OR NOT PY_INICONFIG_DIR)) message(FATAL_ERROR - "Running Axom's python tests requires pytest (and its dependencies pluggy and iniconfig)." + "Running Axom's python tests requires pytest and its import-time dependencies." "\nThe library installation paths can be specified with CMake variables: " - "PY_PYTEST_DIR, PY_PLUGGY_DIR, PY_INICONFIG_DIR." + "PY_PYTEST_DIR, PY_PLUGGY_DIR, PY_INICONFIG_DIR, PY_PACKAGING_DIR, PY_PYGMENTS_DIR." "\nAlternatively, configure with AXOM_ENABLE_PYTHON_TESTS=OFF.") endif() From 691471bd36517ef42061e9592f5d8e0239e64670 Mon Sep 17 00:00:00 2001 From: Kenny Weiss Date: Wed, 15 Jul 2026 14:53:40 -0700 Subject: [PATCH 733/986] Apply suggestions from code review Co-authored-by: Chris White --- scripts/github-actions/linux-build_and_test.sh | 2 +- src/axom/sidre/docs/sphinx/python_interface.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/github-actions/linux-build_and_test.sh b/scripts/github-actions/linux-build_and_test.sh index 6b5041ce1d..ff2eabd8ab 100755 --- a/scripts/github-actions/linux-build_and_test.sh +++ b/scripts/github-actions/linux-build_and_test.sh @@ -61,11 +61,11 @@ if [[ "$DO_BUILD" == "yes" ]] ; then or_die make install # For configs that generated Python bindings, check that we can run a Python script with Axom + echo "~~~~~~ RUNNING INSTALLED PYTHON EXAMPLE ~~~~~~~~" INSTALL_PREFIX=$(awk -F= '/^CMAKE_INSTALL_PREFIX:PATH=/{print $2}' CMakeCache.txt) PYTHON_RUNNER="${INSTALL_PREFIX}/bin/run_python_with_axom.sh" PYTHON_EXAMPLE="${INSTALL_PREFIX}/examples/axom/using-with-python/example.py" if [[ -x "${PYTHON_RUNNER}" && -f "${PYTHON_EXAMPLE}" ]] ; then - echo "~~~~~~ RUNNING INSTALLED PYTHON EXAMPLE ~~~~~~~~" or_die "${PYTHON_RUNNER}" "${PYTHON_EXAMPLE}" fi fi diff --git a/src/axom/sidre/docs/sphinx/python_interface.rst b/src/axom/sidre/docs/sphinx/python_interface.rst index f2d09c1b7e..6de4de79d6 100644 --- a/src/axom/sidre/docs/sphinx/python_interface.rst +++ b/src/axom/sidre/docs/sphinx/python_interface.rst @@ -31,7 +31,7 @@ which is built when Axom is configured with the Sidre component and Python bindi print(ds.getRoot().getView("fields/density").getNumElements()) # 10 The module carries a ``__version__`` matching the Axom release, and exposes -feature flags (``AXOM_USE_HDF5``, ``AXOM_ENABLE_MPI``) so Python code can +feature flags (eg., ``AXOM_USE_HDF5``, ``AXOM_ENABLE_MPI``) so Python code can branch on how Axom was built. ======================================= From bcf268bdb371168556e5a741590a492839d021e6 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 15 Jul 2026 16:07:33 -0700 Subject: [PATCH 734/986] 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 735/986] 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()) { From 23c93ebead68b38a25f7ff6fbe5c28cb55844652 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 15 Jul 2026 16:13:06 -0700 Subject: [PATCH 736/986] sidre: Improve docs about pinned Views in python interface --- src/axom/sidre/nanobind_sidre.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/axom/sidre/nanobind_sidre.cpp b/src/axom/sidre/nanobind_sidre.cpp index 24bcbbdbbb..464d30398e 100644 --- a/src/axom/sidre/nanobind_sidre.cpp +++ b/src/axom/sidre/nanobind_sidre.cpp @@ -312,6 +312,14 @@ conduit::Node& nbObjectToNode(nb::object& o) * * \note Thread safety: all access goes through the GIL (see the module * docstring). If the bindings ever release the GIL, this registry needs a mutex. + * + * \note Pin scoping assumes a pinned View stays within the DataStore it belonged + * to when it was pinned. Sidre reparenting (moveView/moveGroup) stays within a + * single DataStore, so a View's owning DataStore is stable for its lifetime and + * the DataStore* key never goes stale under a supported operation. + * If Sidre ever gained cross-DataStore migration of a live View, that View's pin + * would remain under its original DataStore (and be released when that DataStore is collected), + * so this invariant would need revisiting. */ struct DataStoreExternalPins { @@ -353,6 +361,10 @@ void releaseDataStoreExternalPins(DataStore* ds) { externalDataOwnerRegistry().e void pinExternalDataOwner(View* view, const nb::ndarray<>& owner) { DataStore* ds = owningDataStore(view); + // Enforce the precondition in debug builds; + // release builds fall through to the null-safe early return below. + SLIC_ASSERT_MSG(view == nullptr || ds != nullptr, + "pinExternalDataOwner: a non-null View is expected to have an owning DataStore"); if(view == nullptr || ds == nullptr) { return; From 77f6f49a094cb1c541a8ba15b0cebe914368562b Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 15 Jul 2026 16:26:39 -0700 Subject: [PATCH 737/986] sidre: Removes pysidre shim in favor of axom.sidre in Python interface --- src/axom/sidre/CMakeLists.txt | 26 ++++------ .../sidre/docs/sphinx/python_interface.rst | 6 +-- src/axom/sidre/tests/CMakeLists.txt | 2 +- ..._pysidre_shim_Py.py => sidre_import_Py.py} | 49 +++---------------- src/cmake/AxomMacros.cmake | 2 +- src/python/README.md | 10 ++-- src/python/src/pysidre/__init__.py | 31 ------------ src/tools/CMakeLists.txt | 6 +-- 8 files changed, 25 insertions(+), 107 deletions(-) rename src/axom/sidre/tests/{sidre_pysidre_shim_Py.py => sidre_import_Py.py} (63%) delete mode 100644 src/python/src/pysidre/__init__.py diff --git a/src/axom/sidre/CMakeLists.txt b/src/axom/sidre/CMakeLists.txt index d0c15acf87..2091cb2fdd 100644 --- a/src/axom/sidre/CMakeLists.txt +++ b/src/axom/sidre/CMakeLists.txt @@ -173,7 +173,7 @@ if(NANOBIND_FOUND) # The build-tree interpreter (and run_python_with_axom.sh, via _PYEXT_DIR) # puts this single directory on PYTHONPATH and then 'import axom.sidre' works. set(_axom_py_build_root "${PROJECT_BINARY_DIR}/python") - set(_pysidre_pkg_src "${CMAKE_CURRENT_SOURCE_DIR}/../../python/src") + set(_axom_py_pkg_src "${CMAKE_CURRENT_SOURCE_DIR}/../../python/src") # Build the extension under the shared 'axom' nanobind domain. # All of Axom's extension modules (currently just axom.sidre) share NB_DOMAIN=axom @@ -203,19 +203,17 @@ if(NANOBIND_FOUND) endif() # Stage the pure-Python package scaffolding into the build tree at configure - # time (axom/ namespace root + py.typed, axom/sidre/ re-export, pysidre shim). - axom_configure_file("${_pysidre_pkg_src}/axom/__init__.py" + # time (axom/ namespace root + py.typed, axom/sidre/ re-export). + axom_configure_file("${_axom_py_pkg_src}/axom/__init__.py" "${_axom_py_build_root}/axom/__init__.py" COPYONLY) - axom_configure_file("${_pysidre_pkg_src}/axom/py.typed" + axom_configure_file("${_axom_py_pkg_src}/axom/py.typed" "${_axom_py_build_root}/axom/py.typed" COPYONLY) - axom_configure_file("${_pysidre_pkg_src}/axom/sidre/__init__.py" + axom_configure_file("${_axom_py_pkg_src}/axom/sidre/__init__.py" "${_axom_py_build_root}/axom/sidre/__init__.py" COPYONLY) # Hand-written package stub: re-exports the generated _sidre.pyi statically # so type checkers can see axom.sidre's surface - axom_configure_file("${_pysidre_pkg_src}/axom/sidre/__init__.pyi" + axom_configure_file("${_axom_py_pkg_src}/axom/sidre/__init__.pyi" "${_axom_py_build_root}/axom/sidre/__init__.pyi" COPYONLY) - axom_configure_file("${_pysidre_pkg_src}/pysidre/__init__.py" - "${_axom_py_build_root}/pysidre/__init__.py" COPYONLY) # Type stubs (PEP 561). nanobind_add_stub imports the module by its bare name # ('import _sidre'), so the directory holding the built extension must be on @@ -244,18 +242,14 @@ if(NANOBIND_FOUND) LIBRARY DESTINATION "${AXOM_PYTHON_MODULE_INSTALL_PREFIX}/axom/sidre") install(FILES "${_axom_py_build_root}/axom/sidre/_sidre.pyi" - "${_pysidre_pkg_src}/axom/sidre/__init__.py" - "${_pysidre_pkg_src}/axom/sidre/__init__.pyi" + "${_axom_py_pkg_src}/axom/sidre/__init__.py" + "${_axom_py_pkg_src}/axom/sidre/__init__.pyi" DESTINATION "${AXOM_PYTHON_MODULE_INSTALL_PREFIX}/axom/sidre") # Namespace-root package files install once (not per component). - install(FILES "${_pysidre_pkg_src}/axom/__init__.py" - "${_pysidre_pkg_src}/axom/py.typed" + install(FILES "${_axom_py_pkg_src}/axom/__init__.py" + "${_axom_py_pkg_src}/axom/py.typed" DESTINATION "${AXOM_PYTHON_MODULE_INSTALL_PREFIX}/axom") - - # Deprecation shim for the historical top-level 'pysidre' module. - install(FILES "${_pysidre_pkg_src}/pysidre/__init__.py" - DESTINATION "${AXOM_PYTHON_MODULE_INSTALL_PREFIX}/pysidre") endif() diff --git a/src/axom/sidre/docs/sphinx/python_interface.rst b/src/axom/sidre/docs/sphinx/python_interface.rst index 6de4de79d6..f0c91e283a 100644 --- a/src/axom/sidre/docs/sphinx/python_interface.rst +++ b/src/axom/sidre/docs/sphinx/python_interface.rst @@ -31,7 +31,7 @@ which is built when Axom is configured with the Sidre component and Python bindi print(ds.getRoot().getView("fields/density").getNumElements()) # 10 The module carries a ``__version__`` matching the Axom release, and exposes -feature flags (eg., ``AXOM_USE_HDF5``, ``AXOM_ENABLE_MPI``) so Python code can +feature flags (e.g., ``AXOM_USE_HDF5``, ``AXOM_ENABLE_MPI``) so Python code can branch on how Axom was built. ======================================= @@ -151,7 +151,3 @@ to ``PYTHONPATH`` and then runs the interpreter: The helper is a ``PYTHONPATH`` prepend and is bash-only, so it does not compose with Jupyter kernels, IDE runners, or debuggers - -.. note:: The historical top-level module name ``pysidre`` still works as a - deprecation shim that re-exports ``axom.sidre`` and warns on import. - It will be removed in a future release. Prefer ``import axom.sidre``. diff --git a/src/axom/sidre/tests/CMakeLists.txt b/src/axom/sidre/tests/CMakeLists.txt index 71302a46a4..6378c5a795 100644 --- a/src/axom/sidre/tests/CMakeLists.txt +++ b/src/axom/sidre/tests/CMakeLists.txt @@ -58,7 +58,7 @@ set(python_sidre_tests sidre_external_Py.py sidre_attribute_Py.py sidre_lifetime_Py.py - sidre_pysidre_shim_Py.py + sidre_import_Py.py ) set(sidre_gtests_depends_on sidre fmt gtest) diff --git a/src/axom/sidre/tests/sidre_pysidre_shim_Py.py b/src/axom/sidre/tests/sidre_import_Py.py similarity index 63% rename from src/axom/sidre/tests/sidre_pysidre_shim_Py.py rename to src/axom/sidre/tests/sidre_import_Py.py index 5d8c2bbf3b..8442f717ce 100644 --- a/src/axom/sidre/tests/sidre_pysidre_shim_Py.py +++ b/src/axom/sidre/tests/sidre_import_Py.py @@ -3,37 +3,26 @@ # files for dates and other details. # # SPDX-License-Identifier: (BSD-3-Clause) -"""Tests for the deprecated 'pysidre' compatibility shim. +"""Import-behavior tests for the 'axom.sidre' package. -The Sidre bindings moved from a top-level 'pysidre' module to the 'axom.sidre' package. -'pysidre' survives as a deprecation shim that re-exports 'axom.sidre' and warns on import. -These tests check that the import keeps working, warns once, and exposes the same objects as 'axom.sidre'. +These check that 'axom.sidre' produces an actionable ImportError when the +compiled '_sidre' extension is absent (a component-disabled install), and that +a genuine loader failure (a discoverable '_sidre' that itself raises +ImportError) is surfaced rather than masked by the component-missing message. """ import importlib from pathlib import Path import sys -import warnings import pytest -def _fresh_import_pysidre(): - """Import 'pysidre' with a clean module cache so its import-time - DeprecationWarning is (re)emitted deterministically.""" - sys.modules.pop("pysidre", None) - with warnings.catch_warnings(record=True) as caught: - warnings.simplefilter("always") - module = importlib.import_module("pysidre") - deprecations = [w for w in caught if issubclass(w.category, DeprecationWarning)] - return module, deprecations - - def _clear_axom_imports(): # These tests swap between the real staged package and synthetic packages # under tmp_path; cached modules would otherwise bypass sys.path changes. for name in list(sys.modules): - if name == "axom" or name.startswith("axom.") or name == "pysidre": + if name == "axom" or name.startswith("axom."): sys.modules.pop(name, None) @@ -66,32 +55,6 @@ def _write_fake_axom_sidre(tmp_path, monkeypatch, sidre_init_source, sidre_exten monkeypatch.syspath_prepend(str(tmp_path)) -def test_pysidre_import_warns_once(): - _module, deprecations = _fresh_import_pysidre() - assert len(deprecations) == 1 - assert "axom.sidre" in str(deprecations[0].message) - - -def test_pysidre_reexports_axom_sidre(): - import axom.sidre as sidre - - pysidre, _ = _fresh_import_pysidre() - - # Core symbols resolve, and to the *same* objects as axom.sidre. - assert pysidre.DataStore is sidre.DataStore - assert pysidre.InvalidIndex == sidre.InvalidIndex - assert pysidre.__version__ == sidre.__version__ - - -def test_pysidre_datastore_roundtrip(): - pysidre, _ = _fresh_import_pysidre() - ds = pysidre.DataStore() - root = ds.getRoot() - grp = root.createGroup("via_shim") - assert root.hasGroup("via_shim") - assert grp.getName() == "via_shim" - - def test_axom_sidre_missing_extension_gets_component_message(tmp_path, monkeypatch): _write_fake_axom_sidre(tmp_path, monkeypatch, _sidre_init_source()) diff --git a/src/cmake/AxomMacros.cmake b/src/cmake/AxomMacros.cmake index e18bff21b6..be871ebbdd 100644 --- a/src/cmake/AxomMacros.cmake +++ b/src/cmake/AxomMacros.cmake @@ -611,7 +611,7 @@ endmacro(axom_configure_file) ## ## We assemble one path list here, ordered: ## -## 1. the staged Python package tree (axom/ + pysidre shim) -- runtime +## 1. the staged Python package tree (the 'axom' package) -- runtime ## 2. conduit's python module dir -- runtime ## 3. numpy, then mpi4py (MPI configs) -- runtime ## 4. pytest and its dependencies -- test harness diff --git a/src/python/README.md b/src/python/README.md index ce11e0addf..92125f71c9 100644 --- a/src/python/README.md +++ b/src/python/README.md @@ -8,17 +8,17 @@ # Axom Python package source -This directory (`src/python/`) holds the canonical source of Axom's Python package, `axom`. +This directory (`src/python/`) holds the canonical source of Axom's Python package. It is consumed by two independent build paths that must produce the same on-disk layout: 1. **The CMake build (in tree).** When Axom is configured with a component's Python bindings enabled (currently Sidre), the build stages this tree into the build directory and installs it into a `site-packages`-shaped prefix. - See `src/axom/sidre/CMakeLists.txt`: it copies the files below into `${PROJECT_BINARY_DIR}/python/` (so the build tree is import-ready) - and installs them under `AXOM_PYTHON_MODULE_INSTALL_PREFIX`. + See `src/axom/sidre/CMakeLists.txt`: it copies the files below into `${PROJECT_BINARY_DIR}/python/` + (so the build tree is import-ready) and installs them under `AXOM_PYTHON_MODULE_INSTALL_PREFIX`. The compiled extension (`_sidre`) and its type stub are emitted into this layout by the build; they are not checked in. 2. **[planned] The pip/uv wheel (out of tree).** A thin, binding-only wheel built with scikit-build-core - will treat this directory as its package root (`wheel.packages = ["src/axom", "src/pysidre"]` in a sibling `pyproject.toml`), + will treat this directory as its package root (`wheel.packages = ["src/axom"]` in a sibling `pyproject.toml`), compiling the binding translation unit against an already-installed Axom. @@ -38,8 +38,6 @@ src/python/ __init__.pyi <- package stub; re-exports '_sidre.pyi' for type checkers (_sidre..so) <- compiled extension, produced by the build (_sidre.pyi) <- type stub, produced by the build - pysidre/ - __init__.py <- deprecation shim re-exporting 'axom.sidre' ``` Parenthesized entries are build products and are intentionally not in the repository. diff --git a/src/python/src/pysidre/__init__.py b/src/python/src/pysidre/__init__.py deleted file mode 100644 index 79fd3f8438..0000000000 --- a/src/python/src/pysidre/__init__.py +++ /dev/null @@ -1,31 +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) - -"""Deprecated compatibility shim for the former top-level ``pysidre`` module. - -Axom's Sidre Python bindings used to install as a bare top-level extension module named ``pysidre``. -They now live in the :mod:`axom.sidre` package. -This shim re-exports :mod:`axom.sidre` under the old name so that existing ``import pysidre`` code keeps working, -and emits a single :class:`DeprecationWarning` on import. - -The shim will be removed in the future, and code should ``import axom.sidre`` directly. -""" - -import warnings as _warnings - -_warnings.warn( - "'pysidre' is deprecated and will be removed in a future Axom release; " - "import 'axom.sidre' instead.", - DeprecationWarning, - stacklevel=2, -) - -# Re-export everything axom.sidre exposes, under the legacy module name. -from axom.sidre import * # noqa: F401,F403 (intentional re-export) -from axom.sidre import __all__ as _sidre_all -from axom.sidre import __version__ # noqa: F401 - -__all__ = list(_sidre_all) diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index 789152d542..33cba4dff5 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -191,10 +191,8 @@ if(NANOBIND_FOUND) #-------------------------------------------------------------------------- # gen python helper to build directory. - # _PYEXT_DIR is the site-packages-shaped root that holds the 'axom' package - # (and the 'pysidre' shim); a single PYTHONPATH entry makes 'import axom.sidre' - # and 'import pysidre' resolve. The Sidre bindings stage that tree under - # ${PROJECT_BINARY_DIR}/python (see src/axom/sidre/CMakeLists.txt). + # _PYEXT_DIR is the site-packages-shaped root that holds the 'axom' package. + # Adding an entry for the Sidre bindings to PYTHONPATH entry makes 'import axom.sidre' resolve. set(_PYEXT_DIR ${PROJECT_BINARY_DIR}/python) set(_PYEXT_DIR_IS_RELATIVE FALSE) From 71fd96a0b3df68a28e13d502892dce7716361a21 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 15 Jul 2026 16:40:34 -0700 Subject: [PATCH 738/986] sidre: Use sidre instead of pysidre in Python unit tests and example --- .../examples/sidre_createdatastore_Py.py | 24 +- src/axom/sidre/tests/sidre_attribute_Py.py | 83 +++--- src/axom/sidre/tests/sidre_buffer_Py.py | 24 +- .../sidre/tests/sidre_datastore_unit_Py.py | 86 +++--- src/axom/sidre/tests/sidre_external_Py.py | 75 +++--- src/axom/sidre/tests/sidre_group_Py.py | 245 +++++++++--------- src/axom/sidre/tests/sidre_lifetime_Py.py | 164 ++++++------ src/axom/sidre/tests/sidre_smoke_Py.py | 14 +- src/axom/sidre/tests/sidre_spio_Py.py | 36 +-- src/axom/sidre/tests/sidre_view_Py.py | 108 ++++---- src/tools/convert_sidre_protocol.py | 16 +- 11 files changed, 436 insertions(+), 439 deletions(-) diff --git a/src/axom/sidre/examples/sidre_createdatastore_Py.py b/src/axom/sidre/examples/sidre_createdatastore_Py.py index cd76b66e82..35a31b021c 100644 --- a/src/axom/sidre/examples/sidre_createdatastore_Py.py +++ b/src/axom/sidre/examples/sidre_createdatastore_Py.py @@ -4,7 +4,7 @@ # # SPDX-License-Identifier: (BSD-3-Clause) -import axom.sidre as pysidre +import axom.sidre as sidre import numpy as np import numpy.typing as npt @@ -13,8 +13,8 @@ # all the features in the C++ source. -def create_datastore(region: npt.NDArray[np.int_]) -> pysidre.DataStore: - ds = pysidre.DataStore() +def create_datastore(region: npt.NDArray[np.int_]) -> sidre.DataStore: + ds = sidre.DataStore() root = ds.getRoot() # Create two attributes @@ -42,10 +42,10 @@ def create_datastore(region: npt.NDArray[np.int_]) -> pysidre.DataStore: each node in a 16 x 16 x 16 hexahedron mesh. Each view is described by number of elements, offset, and stride into that data. """ - buff = ds.createBuffer(pysidre.TypeID.DOUBLE_ID, 3 * nodecount).allocate() - nodes.createView("x", buff).apply(pysidre.TypeID.DOUBLE_ID, nodecount, 0, 3) - nodes.createView("y", buff).apply(pysidre.TypeID.DOUBLE_ID, nodecount, 1, 3) - nodes.createView("z", buff).apply(pysidre.TypeID.DOUBLE_ID, nodecount, 2, 3) + buff = ds.createBuffer(sidre.TypeID.DOUBLE_ID, 3 * nodecount).allocate() + nodes.createView("x", buff).apply(sidre.TypeID.DOUBLE_ID, nodecount, 0, 3) + nodes.createView("y", buff).apply(sidre.TypeID.DOUBLE_ID, nodecount, 1, 3) + nodes.createView("z", buff).apply(sidre.TypeID.DOUBLE_ID, nodecount, 2, 3) """ Populate "fields" group @@ -55,8 +55,8 @@ def create_datastore(region: npt.NDArray[np.int_]) -> pysidre.DataStore: and stride (1). These Views could point to data associated with each of the 15 x 15 x 15 hexahedron elements defined by the nodes above. """ - temp = fields.createViewAndAllocate("temp", pysidre.TypeID.DOUBLE_ID, eltcount) - rho = fields.createViewAndAllocate("rho", pysidre.TypeID.DOUBLE_ID, eltcount) + temp = fields.createViewAndAllocate("temp", sidre.TypeID.DOUBLE_ID, eltcount) + rho = fields.createViewAndAllocate("rho", sidre.TypeID.DOUBLE_ID, eltcount) # Explicitly set values for the "vis" Attribute on the "temp" and "rho" buffers. temp.setAttributeScalar("vis", 1) @@ -69,12 +69,12 @@ def create_datastore(region: npt.NDArray[np.int_]) -> pysidre.DataStore: # numpy of int region has been passed in as a function argument. As with "temp" # and "rho", view "region" has default offset and stride. - ext.createView("region", region).apply(pysidre.TypeID.INT_ID, eltcount) + ext.createView("region", region).apply(sidre.TypeID.INT_ID, eltcount) return ds -def access_datastore(ds: pysidre.DataStore) -> pysidre.DataStore: +def access_datastore(ds: sidre.DataStore) -> sidre.DataStore: # Retrieve Group pointers root = ds.getRoot() state = root.getGroup("state") @@ -108,7 +108,7 @@ def access_datastore(ds: pysidre.DataStore) -> pysidre.DataStore: return ds -def iterate_datastore(ds: pysidre.DataStore) -> None: +def iterate_datastore(ds: sidre.DataStore) -> None: fill_line = "=" * 80 print(fill_line) diff --git a/src/axom/sidre/tests/sidre_attribute_Py.py b/src/axom/sidre/tests/sidre_attribute_Py.py index 4efe9290d1..ac92fc0fd5 100644 --- a/src/axom/sidre/tests/sidre_attribute_Py.py +++ b/src/axom/sidre/tests/sidre_attribute_Py.py @@ -4,7 +4,7 @@ # # SPDX-License-Identifier: (BSD-3-Clause) -import axom.sidre as pysidre +import axom.sidre as sidre import numpy as np import conduit @@ -34,7 +34,7 @@ # Python equivalent of nullptr g_attr_null = None -if pysidre.AXOM_USE_HDF5: +if sidre.AXOM_USE_HDF5: g_nprotocols = 3 g_protocols = ["sidre_json", "sidre_hdf5", "json"] else: @@ -51,7 +51,7 @@ def test_create_attr(): print("Some warnings are expected in the 'create_attr' test") - ds = pysidre.DataStore() + ds = sidre.DataStore() nattrs = ds.getNumAttributes() assert nattrs == 0 @@ -64,7 +64,7 @@ def test_create_attr(): # Create string attribute color = ds.createAttributeString(g_name_color, g_color_none) assert color is not None - assert color.getTypeID() == pysidre.TypeID.CHAR8_STR_ID + assert color.getTypeID() == sidre.TypeID.CHAR8_STR_ID attr_index = color.getIndex() assert attr_index == 0 @@ -141,7 +141,7 @@ def test_create_attr(): def test_view_attr(): print("Some warnings are expected in the 'view_attr' test") - ds = pysidre.DataStore() + ds = sidre.DataStore() # Create all attributes for DataStore attr_color = ds.createAttributeString(g_name_color, g_color_none) @@ -268,16 +268,16 @@ def test_view_attr(): def test_view_int_and_double(): print("Some warnings are expected in the 'view_int_and_double' test") - ds = pysidre.DataStore() + ds = sidre.DataStore() # Create all attributes for DataStore attr_dump = ds.createAttributeScalar(g_name_dump, g_dump_no) assert attr_dump is not None - assert attr_dump.getTypeID() == pysidre.TypeID.INT32_ID + assert attr_dump.getTypeID() == sidre.TypeID.INT32_ID attr_size = ds.createAttributeScalar(g_name_size, g_size_small) assert attr_size is not None - assert attr_size.getTypeID() == pysidre.TypeID.FLOAT64_ID + assert attr_size.getTypeID() == sidre.TypeID.FLOAT64_ID root = ds.getRoot() @@ -328,16 +328,16 @@ def test_view_int_and_double(): def test_set_default(): print("Some warnings are expected in the 'set_default' test") - ds = pysidre.DataStore() + ds = sidre.DataStore() # Create all attributes for DataStore attr_dump = ds.createAttributeScalar(g_name_dump, g_dump_no) assert attr_dump is not None - assert attr_dump.getTypeID() == pysidre.TypeID.INT32_ID + assert attr_dump.getTypeID() == sidre.TypeID.INT32_ID attr_size = ds.createAttributeScalar(g_name_size, g_size_small) assert attr_size is not None - assert attr_size.getTypeID() == pysidre.TypeID.FLOAT64_ID + assert attr_size.getTypeID() == sidre.TypeID.FLOAT64_ID root = ds.getRoot() @@ -383,7 +383,7 @@ def test_set_default(): def test_as_node(): print("Some warnings are expected in the 'as_node' test") - ds = pysidre.DataStore() + ds = sidre.DataStore() # Create attributes for DataStore attr_color = ds.createAttributeString(g_name_color, g_color_none) @@ -418,7 +418,7 @@ def test_as_node(): def test_overloads(): print("Some warnings are expected in the 'overloads' test") - ds = pysidre.DataStore() + ds = sidre.DataStore() # Create string and scalar attributes attr_color = ds.createAttributeString(g_name_color, g_color_none) @@ -497,12 +497,12 @@ def test_overloads(): # Check some errors assert view.getAttributeScalarInt(g_attr_null) == 0 - assert view.getAttributeScalarInt(pysidre.InvalidIndex) == 0 + assert view.getAttributeScalarInt(sidre.InvalidIndex) == 0 assert view.getAttributeScalarInt("noname") == 0 def test_loop_attributes(): - ds = pysidre.DataStore() + ds = sidre.DataStore() # Create attributes for DataStore color = ds.createAttributeString(g_name_color, g_color_none) @@ -525,9 +525,9 @@ def test_loop_attributes(): idx3 = ds.getNextValidAttributeIndex(idx2) assert idx3 == 2 idx4 = ds.getNextValidAttributeIndex(idx3) - assert idx4 == pysidre.InvalidIndex + assert idx4 == sidre.InvalidIndex idx5 = ds.getNextValidAttributeIndex(idx4) - assert idx5 == pysidre.InvalidIndex + assert idx5 == sidre.InvalidIndex # ---------------------------------------- root = ds.getRoot() @@ -545,7 +545,7 @@ def test_loop_attributes(): idx3 = view1.getNextValidAttrValueIndex(idx2) assert idx3 == 2 idx4 = view1.getNextValidAttrValueIndex(idx3) - assert idx4 == pysidre.InvalidIndex + assert idx4 == sidre.InvalidIndex # set first attribute view2 = root.createView("view2") @@ -554,7 +554,7 @@ def test_loop_attributes(): idx1 = view2.getFirstValidAttrValueIndex() assert idx1 == 0 idx2 = view2.getNextValidAttrValueIndex(idx1) - assert idx2 == pysidre.InvalidIndex + assert idx2 == sidre.InvalidIndex # set last attribute view3 = root.createView("view3") @@ -563,7 +563,7 @@ def test_loop_attributes(): idx1 = view3.getFirstValidAttrValueIndex() assert idx1 == 2 idx2 = view3.getNextValidAttrValueIndex(idx1) - assert idx2 == pysidre.InvalidIndex + assert idx2 == sidre.InvalidIndex # set first and last attributes view4 = root.createView("view4") @@ -575,19 +575,19 @@ def test_loop_attributes(): idx2 = view4.getNextValidAttrValueIndex(idx1) assert idx2 == 2 idx3 = view4.getNextValidAttrValueIndex(idx2) - assert idx3 == pysidre.InvalidIndex + assert idx3 == sidre.InvalidIndex # no attributes view5 = root.createView("view5") idx1 = view5.getFirstValidAttrValueIndex() - assert idx1 == pysidre.InvalidIndex + assert idx1 == sidre.InvalidIndex idx2 = view5.getNextValidAttrValueIndex(idx1) - assert idx2 == pysidre.InvalidIndex + assert idx2 == sidre.InvalidIndex def test_iterate_attributes(): - ds = pysidre.DataStore() + ds = sidre.DataStore() # Create attributes for DataStore color = ds.createAttributeString(g_name_color, g_color_none) @@ -627,7 +627,7 @@ def test_save_attributes(): idata = np.zeros(5, dtype=int) file_path_base = "sidre_attribute_datastore_" - ds1 = pysidre.DataStore() + ds1 = sidre.DataStore() root1 = ds1.getRoot() # Create attributes for DataStore @@ -649,13 +649,13 @@ def test_save_attributes(): view1a.setAttributeScalar(size, g_size_small) # buffer - view1b = root1.createViewAndAllocate("buffer", pysidre.TypeID.INT_ID, 5) + view1b = root1.createViewAndAllocate("buffer", sidre.TypeID.INT_ID, 5) bdata = view1b.getDataArray() view1b.setAttributeString(color, "color-buffer") view1b.setAttributeScalar(size, g_size_medium) # external - view1c = root1.createView("external", pysidre.TypeID.INT_ID, 5, idata) + view1c = root1.createView("external", sidre.TypeID.INT_ID, 5, idata) view1c.setAttributeScalar(size, g_size_large) # scalar @@ -692,7 +692,7 @@ def test_save_attributes(): file_path = file_path_base + g_protocols[i] - ds2 = pysidre.DataStore() + ds2 = sidre.DataStore() root2 = ds2.getRoot() root2.load(file_path, g_protocols[i]) @@ -757,7 +757,7 @@ def test_save_by_attribute(): jdata = np.zeros(5, dtype=int) file_path_base = "sidre_attribute_by_attribute_" - ds1 = pysidre.DataStore() + ds1 = sidre.DataStore() root1 = ds1.getRoot() # Create attributes for DataStore @@ -772,9 +772,8 @@ def test_save_by_attribute(): root1.createViewScalar("grp1a/grp1b/view3", 3) root1.createViewScalar("grp2a/view4", 4) # make sure empty "views" not saved root1.createViewScalar("grp2a/grp2b/view5", 5).setAttributeScalar(dump, g_dump_yes) - root1.createView("view6", pysidre.TypeID.INT32_ID, 5, - idata).setAttributeScalar(dump, g_dump_yes) - root1.createView("grp3a/grp3b/view7", pysidre.TypeID.INT32_ID, 5, jdata) + root1.createView("view6", sidre.TypeID.INT32_ID, 5, idata).setAttributeScalar(dump, g_dump_yes) + root1.createView("grp3a/grp3b/view7", sidre.TypeID.INT32_ID, 5, jdata) for i in range(5): idata[i] = i @@ -798,7 +797,7 @@ def test_save_by_attribute(): continue file_path = file_path_base + g_protocols[i] - ds2 = pysidre.DataStore() + ds2 = sidre.DataStore() root2 = ds2.getRoot() root2.load(file_path, g_protocols[i]) @@ -821,7 +820,7 @@ def test_save_load_group_with_attributes_new_ds(): filename = f"saveFile_{protocol}.{ext}" # Set up first datastore and save to disk - ds1 = pysidre.DataStore() + ds1 = sidre.DataStore() ds1.createAttributeScalar("attr", 10) ds1.createAttributeString(g_name_color, g_color_none) @@ -831,9 +830,9 @@ def test_save_load_group_with_attributes_new_ds(): gr1.createViewScalar("scalar3", 3).setAttributeString(g_name_color, g_color_blue) assert ds1.getNumAttributes() == 2 - assert (pysidre.TypeID.INT32_ID == ds1.getAttribute("attr").getTypeID() - or pysidre.TypeID.INT64_ID == ds1.getAttribute("attr").getTypeID()) - assert pysidre.TypeID.CHAR8_STR_ID == ds1.getAttribute(g_name_color).getTypeID() + assert (sidre.TypeID.INT32_ID == ds1.getAttribute("attr").getTypeID() + or sidre.TypeID.INT64_ID == ds1.getAttribute("attr").getTypeID()) + assert sidre.TypeID.CHAR8_STR_ID == ds1.getAttribute(g_name_color).getTypeID() assert not gr1.getView("scalar1").hasAttributeValue(g_name_color) @@ -852,14 +851,14 @@ def test_save_load_group_with_attributes_new_ds(): continue # Load second datastore from saved data - ds2 = pysidre.DataStore() + ds2 = sidre.DataStore() gr2 = ds2.getRoot().createGroup("gr") gr2.load(filename, protocol) assert ds2.getNumAttributes() == 2 - assert (pysidre.TypeID.INT32_ID == ds2.getAttribute("attr").getTypeID() - or pysidre.TypeID.INT64_ID == ds2.getAttribute("attr").getTypeID()) - assert pysidre.TypeID.CHAR8_STR_ID == ds2.getAttribute(g_name_color).getTypeID() + assert (sidre.TypeID.INT32_ID == ds2.getAttribute("attr").getTypeID() + or sidre.TypeID.INT64_ID == ds2.getAttribute("attr").getTypeID()) + assert sidre.TypeID.CHAR8_STR_ID == ds2.getAttribute(g_name_color).getTypeID() assert gr2.hasView("scalar1") assert not gr2.getView("scalar1").hasAttributeValue(g_name_color) @@ -891,7 +890,7 @@ def test_save_load_group_with_attributes_same_ds(): print(f"Checking attribute save/load w/ protocol '{protocol}' using file '{filename}'") # Create the DataStore and attributes - ds = pysidre.DataStore() + ds = sidre.DataStore() ds.createAttributeScalar("attr", 10) ds.createAttributeString(g_name_color, g_color_none) diff --git a/src/axom/sidre/tests/sidre_buffer_Py.py b/src/axom/sidre/tests/sidre_buffer_Py.py index 732cdf3912..5b3ee76848 100644 --- a/src/axom/sidre/tests/sidre_buffer_Py.py +++ b/src/axom/sidre/tests/sidre_buffer_Py.py @@ -4,14 +4,14 @@ # # SPDX-License-Identifier: (BSD-3-Clause) -import axom.sidre as pysidre +import axom.sidre as sidre import numpy as np NUM_BYTES_INT_32 = 4 def test_create_buffers(): - ds = pysidre.DataStore() + ds = sidre.DataStore() assert ds.getNumBuffers() == 0 dbuff_0 = ds.createBuffer() @@ -34,17 +34,17 @@ def test_create_buffers(): def test_alloc_buffer_for_int_array(): - ds = pysidre.DataStore() + ds = sidre.DataStore() dbuff = ds.createBuffer() elem_count = 10 - dbuff.allocate(pysidre.TypeID.INT32_ID, elem_count) + dbuff.allocate(sidre.TypeID.INT32_ID, elem_count) # Should be a warning and no-op, buffer is already allocated, we don't want # to re-allocate and leak memory. dbuff.allocate() - assert dbuff.getTypeID() == pysidre.TypeID.INT32_ID + assert dbuff.getTypeID() == sidre.TypeID.INT32_ID assert dbuff.getNumElements() == elem_count assert dbuff.getTotalBytes() == NUM_BYTES_INT_32 * elem_count @@ -65,12 +65,12 @@ def test_alloc_buffer_for_int_array(): def test_init_buffer_for_int_array(): elem_count = 10 - ds = pysidre.DataStore() + ds = sidre.DataStore() dbuff = ds.createBuffer() - dbuff.allocate(pysidre.TypeID.INT32_ID, elem_count) + dbuff.allocate(sidre.TypeID.INT32_ID, elem_count) - assert dbuff.getTypeID() == pysidre.TypeID.INT32_ID + assert dbuff.getTypeID() == sidre.TypeID.INT32_ID assert dbuff.getNumElements() == elem_count assert dbuff.getTotalBytes() == NUM_BYTES_INT_32 * elem_count @@ -92,12 +92,12 @@ def test_realloc_buffer(): orig_elem_count = 5 mod_elem_count = 10 - ds = pysidre.DataStore() + ds = sidre.DataStore() dbuff = ds.createBuffer() - dbuff.allocate(pysidre.TypeID.INT32_ID, orig_elem_count) + dbuff.allocate(sidre.TypeID.INT32_ID, orig_elem_count) - assert dbuff.getTypeID() == pysidre.TypeID.INT32_ID + assert dbuff.getTypeID() == sidre.TypeID.INT32_ID assert dbuff.getNumElements() == orig_elem_count assert dbuff.getTotalBytes() == NUM_BYTES_INT_32 * orig_elem_count @@ -111,7 +111,7 @@ def test_realloc_buffer(): dbuff.reallocate(mod_elem_count) - assert dbuff.getTypeID() == pysidre.TypeID.INT32_ID + assert dbuff.getTypeID() == sidre.TypeID.INT32_ID assert dbuff.getNumElements() == mod_elem_count assert dbuff.getTotalBytes() == NUM_BYTES_INT_32 * mod_elem_count diff --git a/src/axom/sidre/tests/sidre_datastore_unit_Py.py b/src/axom/sidre/tests/sidre_datastore_unit_Py.py index 5cd290e739..fed76f9185 100644 --- a/src/axom/sidre/tests/sidre_datastore_unit_Py.py +++ b/src/axom/sidre/tests/sidre_datastore_unit_Py.py @@ -4,7 +4,7 @@ # # SPDX-License-Identifier: (BSD-3-Clause) -import axom.sidre as pysidre +import axom.sidre as sidre import random @@ -16,20 +16,20 @@ def verify_empty_group_named(dg, name): assert not dg.hasGroup(0) assert not dg.hasGroup(1) assert not dg.hasGroup("some_name") - assert dg.getGroupIndex("some_other_name") == pysidre.InvalidIndex - assert dg.getFirstValidGroupIndex() == pysidre.InvalidIndex - assert dg.getNextValidGroupIndex(0) == pysidre.InvalidIndex - assert dg.getNextValidGroupIndex(4) == pysidre.InvalidIndex + assert dg.getGroupIndex("some_other_name") == sidre.InvalidIndex + assert dg.getFirstValidGroupIndex() == sidre.InvalidIndex + assert dg.getNextValidGroupIndex(0) == sidre.InvalidIndex + assert dg.getNextValidGroupIndex(4) == sidre.InvalidIndex assert dg.getNumViews() == 0 assert not dg.hasView(-1) assert not dg.hasView(0) assert not dg.hasView(1) assert not dg.hasView("some_name") - assert dg.getViewIndex("some_other_name") == pysidre.InvalidIndex - assert dg.getFirstValidViewIndex() == pysidre.InvalidIndex - assert dg.getNextValidViewIndex(0) == pysidre.InvalidIndex - assert dg.getNextValidViewIndex(4) == pysidre.InvalidIndex + assert dg.getViewIndex("some_other_name") == sidre.InvalidIndex + assert dg.getFirstValidViewIndex() == sidre.InvalidIndex + assert dg.getNextValidViewIndex(0) == sidre.InvalidIndex + assert dg.getNextValidViewIndex(4) == sidre.InvalidIndex def verify_buffer_identity(ds, bs): @@ -41,7 +41,7 @@ def verify_buffer_identity(ds, bs): # Does ds contain the buffer IDs and pointers we expect? iterated_count = 0 idx = ds.getFirstValidBufferIndex() - while idx != pysidre.InvalidIndex and iterated_count < bufcount: + while idx != sidre.InvalidIndex and iterated_count < bufcount: assert idx in bs if idx in bs: assert bs[idx] == ds.getBuffer(idx) @@ -50,11 +50,11 @@ def verify_buffer_identity(ds, bs): # Have we iterated over exactly the number of buffers we expect, finishing on InvalidIndex? assert iterated_count == bufcount - assert idx == pysidre.InvalidIndex + assert idx == sidre.InvalidIndex def test_default_ctor(): - ds = pysidre.DataStore() + ds = sidre.DataStore() # After construction, the DataStore should contain no buffers. assert ds.getNumBuffers() == 0 @@ -64,9 +64,9 @@ def test_default_ctor(): assert not ds.hasBuffer(1) assert not ds.hasBuffer(8) - assert ds.getFirstValidBufferIndex() == pysidre.InvalidIndex - assert ds.getNextValidBufferIndex(0) == pysidre.InvalidIndex - assert ds.getNextValidBufferIndex(4) == pysidre.InvalidIndex + assert ds.getFirstValidBufferIndex() == sidre.InvalidIndex + assert ds.getNextValidBufferIndex(0) == sidre.InvalidIndex + assert ds.getNextValidBufferIndex(4) == sidre.InvalidIndex # The new DataStore should contain exactly one group, the root group. # The root group should be named "" and should contain no views and no groups. @@ -82,7 +82,7 @@ def test_default_ctor(): # The dtor destroys all buffers and deletes the root group. # An outside tool should be used to check for proper memory cleanup. def test_create_destroy_buffers_basic(): - ds = pysidre.DataStore() + ds = sidre.DataStore() assert ds.getNumBuffers() == 0 # Basic tests @@ -92,7 +92,7 @@ def test_create_destroy_buffers_basic(): buffer_index = ds.getFirstValidBufferIndex() assert dbuff.getIndex() == 0 assert dbuff.getIndex() == buffer_index - assert ds.getNextValidBufferIndex(buffer_index) == pysidre.InvalidIndex + assert ds.getNextValidBufferIndex(buffer_index) == sidre.InvalidIndex # Do we get the buffer we expect? assert dbuff == ds.getBuffer(buffer_index) @@ -102,14 +102,14 @@ def test_create_destroy_buffers_basic(): ds.destroyBuffer(buffer_index) # should be no buffers assert ds.getNumBuffers() == 0 - assert ds.getFirstValidBufferIndex() == pysidre.InvalidIndex + assert ds.getFirstValidBufferIndex() == sidre.InvalidIndex assert not ds.hasBuffer(buffer_index) assert ds.getBuffer(buffer_index) is None assert ds.getBuffer(bad_buffer_index) is None def test_create_destroy_buffers_order(): - ds = pysidre.DataStore() + ds = sidre.DataStore() assert ds.getNumBuffers() == 0 dbuff = ds.createBuffer() @@ -119,7 +119,7 @@ def test_create_destroy_buffers_order(): ds.destroyBuffer(dbuff) # After destroy, test that buffer index should be available again for reuse. - dbuff2 = ds.createBuffer(pysidre.TypeID.FLOAT32_ID, 16) + dbuff2 = ds.createBuffer(sidre.TypeID.FLOAT32_ID, 16) d2_index = dbuff2.getIndex() assert ds.getFirstValidBufferIndex() == buffer_index assert d2_index == buffer_index @@ -164,7 +164,7 @@ def test_create_destroy_buffers_order(): def test_create_destroy_buffers_views(): - ds = pysidre.DataStore() + ds = sidre.DataStore() assert ds.getNumBuffers() == 0 dbuff3 = ds.createBuffer() @@ -253,39 +253,39 @@ def irhall(n): # Test iteration through buffers, as well as proper index and buffer behavior # while buffers are created and deleted def test_iterate_buffers_basic(): - ds = pysidre.DataStore() + ds = sidre.DataStore() assert ds.getNumBuffers() == 0 bad_buffer_index = 9999 # Do we get sidre::InvalidIndex for several queries with no buffers? - assert ds.getFirstValidBufferIndex() == pysidre.InvalidIndex - assert ds.getNextValidBufferIndex(0) == pysidre.InvalidIndex - assert ds.getNextValidBufferIndex(bad_buffer_index) == pysidre.InvalidIndex - assert ds.getNextValidBufferIndex(pysidre.InvalidIndex) == pysidre.InvalidIndex + assert ds.getFirstValidBufferIndex() == sidre.InvalidIndex + assert ds.getNextValidBufferIndex(0) == sidre.InvalidIndex + assert ds.getNextValidBufferIndex(bad_buffer_index) == sidre.InvalidIndex + assert ds.getNextValidBufferIndex(sidre.InvalidIndex) == sidre.InvalidIndex # Create one data buffer, verify its index is zero, and that iterators behave as expected initial = ds.createBuffer() assert initial.getIndex() == 0 assert ds.getNumBuffers() == 1 assert ds.getFirstValidBufferIndex() == 0 - assert ds.getNextValidBufferIndex(0) == pysidre.InvalidIndex + assert ds.getNextValidBufferIndex(0) == sidre.InvalidIndex # Destroy the data buffer, verify that iterators behave as expected ds.destroyBuffer(initial) assert ds.getNumBuffers() == 0 - assert ds.getFirstValidBufferIndex() == pysidre.InvalidIndex - assert ds.getNextValidBufferIndex(0) == pysidre.InvalidIndex + assert ds.getFirstValidBufferIndex() == sidre.InvalidIndex + assert ds.getNextValidBufferIndex(0) == sidre.InvalidIndex def test_iterate_buffers_simple(): - ds = pysidre.DataStore() + ds = sidre.DataStore() assert ds.getNumBuffers() == 0 bs = {} bufcount = 20 for i in range(bufcount): - b = ds.createBuffer(pysidre.TypeID.FLOAT64_ID, 400 * i) + b = ds.createBuffer(sidre.TypeID.FLOAT64_ID, 400 * i) idx = b.getIndex() bs[idx] = b @@ -293,14 +293,14 @@ def test_iterate_buffers_simple(): def test_iterate_buffers_iterators(): - ds = pysidre.DataStore() + ds = sidre.DataStore() assert ds.getNumBuffers() == 0 bs = {} bufcount = 20 for i in range(bufcount): - b = ds.createBuffer(pysidre.TypeID.FLOAT64_ID, 400 * i) + b = ds.createBuffer(sidre.TypeID.FLOAT64_ID, 400 * i) idx = b.getIndex() bs[idx] = b @@ -308,7 +308,7 @@ def test_iterate_buffers_iterators(): for buff in ds.buffers(): idx = buff.getIndex() found_buffers += 1 - assert pysidre.indexIsValid(idx) + assert sidre.indexIsValid(idx) assert ds.getBuffer(idx) == buff assert bs[idx] == buff assert found_buffers == bufcount @@ -316,7 +316,7 @@ def test_iterate_buffers_iterators(): # Test creating and allocating buffers, then destroying several of them def test_create_delete_buffers_iterate(): - ds = pysidre.DataStore() + ds = sidre.DataStore() assert ds.getNumBuffers() == 0 bs = {} @@ -324,7 +324,7 @@ def test_create_delete_buffers_iterate(): # Initially, create some buffers of varying size for i in range(bufcount): - b = ds.createBuffer(pysidre.TypeID.FLOAT64_ID, (400 * i) % 10000) + b = ds.createBuffer(sidre.TypeID.FLOAT64_ID, (400 * i) % 10000) b.allocate() idx = b.getIndex() bs[idx] = b @@ -341,12 +341,12 @@ def test_create_delete_buffers_iterate(): def test_iterate_buffers_with_delete_iterators(): - ds = pysidre.DataStore() + ds = sidre.DataStore() assert ds.getNumBuffers() == 0 init_buff_count = 22 for i in range(init_buff_count): - ds.createBuffer(pysidre.TypeID.FLOAT64_ID, 400 * i) + ds.createBuffer(sidre.TypeID.FLOAT64_ID, 400 * i) assert ds.getNumBuffers() == init_buff_count # Remove a few buffers by index @@ -358,7 +358,7 @@ def test_iterate_buffers_with_delete_iterators(): # Add a buffer, expect it to reuse a lower index assert not ds.hasBuffer(5) - buff = ds.createBuffer(pysidre.TypeID.FLOAT64_ID, 10) + buff = ds.createBuffer(sidre.TypeID.FLOAT64_ID, 10) idx = buff.getIndex() assert idx < init_buff_count assert ds.hasBuffer(idx) @@ -378,14 +378,14 @@ def test_iterate_buffers_with_delete_iterators(): for buff in ds.buffers(): idx = buff.getIndex() found_buffers += 1 - assert pysidre.indexIsValid(idx) + assert sidre.indexIsValid(idx) assert ds.getBuffer(idx) == buff assert found_buffers == exp_buff_count # Test creating+allocating buffers, then destroying several of them, repeatedly def test_loop_create_delete_buffers_iterate(): - ds = pysidre.DataStore() + ds = sidre.DataStore() assert ds.getNumBuffers() == 0 bs = {} @@ -394,7 +394,7 @@ def test_loop_create_delete_buffers_iterate(): # Initially, create some buffers of varying size for i in range(initbufcount): - b = ds.createBuffer(pysidre.TypeID.FLOAT64_ID, (400 * i) % 10000) + b = ds.createBuffer(sidre.TypeID.FLOAT64_ID, (400 * i) % 10000) b.allocate() idx = b.getIndex() bs[idx] = b @@ -422,7 +422,7 @@ def test_loop_create_delete_buffers_iterate(): elif delta > 0: addcount = delta for _ in range(addcount): - buf = ds.createBuffer(pysidre.TypeID.FLOAT64_ID, 400) + buf = ds.createBuffer(sidre.TypeID.FLOAT64_ID, 400) buf.allocate() addid = buf.getIndex() assert ds.hasBuffer(addid) diff --git a/src/axom/sidre/tests/sidre_external_Py.py b/src/axom/sidre/tests/sidre_external_Py.py index aa82c075ab..586b9f93e7 100644 --- a/src/axom/sidre/tests/sidre_external_Py.py +++ b/src/axom/sidre/tests/sidre_external_Py.py @@ -4,7 +4,7 @@ # # SPDX-License-Identifier: (BSD-3-Clause) -import axom.sidre as pysidre +import axom.sidre as sidre import numpy as np from conduit import Node @@ -14,7 +14,7 @@ def test_create_external_view(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() length = 11 @@ -29,26 +29,25 @@ def test_create_external_view(): view = None match i: case 0: - view = root.createView("data0", pysidre.TypeID.INT64_ID, length, idata) + view = root.createView("data0", sidre.TypeID.INT64_ID, length, idata) case 1: - view = root.createView("data1", pysidre.TypeID.INT64_ID, + view = root.createView("data1", sidre.TypeID.INT64_ID, length).setExternalData(idata) case 2: - view = root.createView("data2").setExternalData(pysidre.TypeID.INT64_ID, length, + view = root.createView("data2").setExternalData(sidre.TypeID.INT64_ID, length, idata) case 3: - view = root.createView("data3", idata).apply(pysidre.TypeID.INT64_ID, length) + view = root.createView("data3", idata).apply(sidre.TypeID.INT64_ID, length) case 4: - view = root.createViewWithShape("data4", pysidre.TypeID.INT64_ID, ndims, shape, - idata) + view = root.createViewWithShape("data4", sidre.TypeID.INT64_ID, ndims, shape, idata) case 5: - view = root.createViewWithShape("data5", pysidre.TypeID.INT64_ID, ndims, + view = root.createViewWithShape("data5", sidre.TypeID.INT64_ID, ndims, shape).setExternalData(idata) case 6: - view = root.createView("data6").setExternalData(pysidre.TypeID.INT64_ID, ndims, - shape, idata) + view = root.createView("data6").setExternalData(sidre.TypeID.INT64_ID, ndims, shape, + idata) case 7: - view = root.createView("data7", idata).apply(pysidre.TypeID.INT64_ID, ndims, shape) + view = root.createView("data7", idata).apply(sidre.TypeID.INT64_ID, ndims, shape) assert view is not None assert root.getNumViews() == i + 1 @@ -60,7 +59,7 @@ def test_create_external_view(): assert view.isExternal() assert not view.isOpaque() - assert view.getTypeID() == pysidre.TypeID.INT64_ID + assert view.getTypeID() == sidre.TypeID.INT64_ID assert view.getNumElements() == length view.print() @@ -73,7 +72,7 @@ def test_create_external_view(): def test_verify_external_layout(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() SZ = 11 @@ -84,7 +83,7 @@ def test_verify_external_layout(): associated with external pointers (described or undescribed).""") # Create some internal views - root.createViewAndAllocate("int/desc/bufferview", pysidre.TypeID.INT64_ID, SZ) + root.createViewAndAllocate("int/desc/bufferview", sidre.TypeID.INT64_ID, SZ) root.createViewScalar("int/scalar/scalarview", SZ) root.createViewString("int/string/stringview", "A string") @@ -106,7 +105,7 @@ def test_verify_external_layout(): assert emptyNode.number_of_children() == 0 # Create some external views - root.createView("ext/desc/external_desc", pysidre.TypeID.INT64_ID, SZ, extData) + root.createView("ext/desc/external_desc", sidre.TypeID.INT64_ID, SZ, extData) root.createView("ext/undesc/external_opaque").setExternalData(extData) # Sanity check on the external views @@ -149,11 +148,11 @@ def test_verify_external_layout(): def test_save_load_external_view(): - if not pysidre.AXOM_USE_HDF5: - print("pysidre.Group.loadExternalData() is only implemented for the 'sidre_hdf5' protocol") + if not sidre.AXOM_USE_HDF5: + print("sidre.Group.loadExternalData() is only implemented for the 'sidre_hdf5' protocol") return - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() length = 11 @@ -162,13 +161,13 @@ def test_save_load_external_view(): ddata = np.array([ii * 2.0 for ii in range(length)], dtype=np.float64) # Create views with external data - root.createView("idata", idata).apply(pysidre.TypeID.INT64_ID, length) - root.createView("ddata", ddata).apply(pysidre.TypeID.FLOAT64_ID, length) + root.createView("idata", idata).apply(sidre.TypeID.INT64_ID, length) + root.createView("ddata", ddata).apply(sidre.TypeID.FLOAT64_ID, length) assert root.getNumViews() == 2 root.save("sidre_external_save_load_external_view", "sidre_hdf5") - ds2 = pysidre.DataStore() + ds2 = sidre.DataStore() load_group = ds2.getRoot() # Load from file, the Views with external data will be described but @@ -184,8 +183,8 @@ def test_save_load_external_view(): assert load_ddata.isExternal() assert load_idata.getNumElements() == length assert load_ddata.getNumElements() == length - assert load_idata.getTypeID() == pysidre.TypeID.INT64_ID - assert load_ddata.getTypeID() == pysidre.TypeID.FLOAT64_ID + assert load_idata.getTypeID() == sidre.TypeID.INT64_ID + assert load_ddata.getTypeID() == sidre.TypeID.FLOAT64_ID # Create arrays that will serve as locations for external data new_idata = np.zeros(length, dtype=np.int64) @@ -224,16 +223,16 @@ def test_save_load_external_view(): # Register with datastore then # Query metadata using datastore API. def test_external_int(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() iarray = np.array(range(1, 11)) view = root.createView("iarray", iarray) - view.apply(pysidre.TypeID.INT64_ID, 10) + view.apply(sidre.TypeID.INT64_ID, 10) assert view.isExternal() == True - assert view.getTypeID() == pysidre.TypeID.INT64_ID + assert view.getTypeID() == sidre.TypeID.INT64_ID assert view.getNumElements() == np.size(iarray) assert view.getNumDimensions() == 1 @@ -247,7 +246,7 @@ def test_external_int(): def test_external_int_3d(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() # create 3D numpy array @@ -258,10 +257,10 @@ def test_external_int_3d(): for k in range(4): iarray[i, j, k] = (i + 1) * 100 + (j + 1) * 10 + (k + 1) view = root.createView("iarray", iarray) - view.apply(pysidre.TypeID.INT64_ID, 3, np.array([2, 3, 4])) + view.apply(sidre.TypeID.INT64_ID, 3, np.array([2, 3, 4])) assert view.isExternal() == True - assert view.getTypeID() == pysidre.TypeID.INT64_ID + assert view.getTypeID() == sidre.TypeID.INT64_ID assert view.getNumElements() == np.size(iarray) assert view.getNumDimensions() == 3 @@ -278,14 +277,14 @@ def test_external_int_3d(): # check other types def test_external_float(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() darray = np.array([(i + 0.5) for i in range(1, 11)]) view = root.createView("darray", darray) - view.apply(pysidre.TypeID.FLOAT64_ID, 10) + view.apply(sidre.TypeID.FLOAT64_ID, 10) - assert view.getTypeID() == pysidre.TypeID.FLOAT64_ID + assert view.getTypeID() == sidre.TypeID.FLOAT64_ID assert view.getNumElements() == np.size(darray) dpointer = view.getDataArray() @@ -294,15 +293,15 @@ def test_external_float(): # Datastore owns a multi-dimension array. def test_datastore_int_3d(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() extents_in = [2, 3, 4] - view = root.createViewWithShapeAndAllocate("iarray", pysidre.TypeID.INT32_ID, 3, extents_in) + view = root.createViewWithShapeAndAllocate("iarray", sidre.TypeID.INT32_ID, 3, extents_in) ipointer = view.getDataArray() - assert view.getTypeID() == pysidre.TypeID.INT32_ID + assert view.getTypeID() == sidre.TypeID.INT32_ID assert view.getNumElements() == np.size(ipointer) assert view.getNumDimensions() == 3 assert view.getNumDimensions() == ipointer.ndim @@ -316,9 +315,9 @@ def test_datastore_int_3d(): # Reshape as 1D using shape extents_in[0] = np.size(ipointer) - view.apply(pysidre.TypeID.INT32_ID, 1, np.array([extents_in[0]])) + view.apply(sidre.TypeID.INT32_ID, 1, np.array([extents_in[0]])) assert view.getNumElements() == np.size(ipointer) # Reshape as 1D using length - view.apply(pysidre.TypeID.INT32_ID, extents_in[0]) + view.apply(sidre.TypeID.INT32_ID, extents_in[0]) assert view.getNumElements() == np.size(ipointer) diff --git a/src/axom/sidre/tests/sidre_group_Py.py b/src/axom/sidre/tests/sidre_group_Py.py index 104e98af9a..70a28831df 100644 --- a/src/axom/sidre/tests/sidre_group_Py.py +++ b/src/axom/sidre/tests/sidre_group_Py.py @@ -4,11 +4,11 @@ # # SPDX-License-Identifier: (BSD-3-Clause) -import axom.sidre as pysidre +import axom.sidre as sidre import numpy as np from conduit import Node -if pysidre.AXOM_USE_HDF5: +if sidre.AXOM_USE_HDF5: NPROTOCOLS = 3 PROTOCOLS = ["sidre_json", "sidre_hdf5", "json"] else: @@ -20,7 +20,7 @@ # getName() # ------------------------------------------------------------------------------ def test_get_name(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() grp = root.createGroup("test") @@ -34,7 +34,7 @@ def test_get_name(): # getPath(), getPathName() # ------------------------------------------------------------------------------ def test_get_path_name(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() group = root.createGroup("test/a/b/c") grp2 = root.getGroup("test/a") @@ -61,7 +61,7 @@ def test_get_path_name(): # createGroup(), getGroup(), hasGroup() with path strings #------------------------------------------------------------------------------ def test_group_with_path(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() # Test full path access when building incrementally @@ -111,7 +111,7 @@ def test_group_with_path(): assert root.hasGroup(1) assert root.hasGroup(2) assert not root.hasGroup(3) - assert not root.hasGroup(pysidre.InvalidIndex) + assert not root.hasGroup(sidre.InvalidIndex) testbnumgroups = group_testa.getGroup("testb").getNumGroups() group_cdup = group_testa.createGroup("testb/testc") @@ -124,7 +124,7 @@ def test_group_with_path(): # createGroup(), destroyGroup() with path strings #------------------------------------------------------------------------------ def test_destroy_group_with_path(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() # Test full path access when building incrementally @@ -155,7 +155,7 @@ def test_destroy_group_with_path(): # Verify getParent() # ------------------------------------------------------------------------------ def test_get_parent(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() parent = root.createGroup("parent") child = parent.createGroup("child") @@ -167,7 +167,7 @@ def test_get_parent(): # Verify getDataStore() # ------------------------------------------------------------------------------ def test_get_datastore(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() grp = root.createGroup("parent") @@ -181,7 +181,7 @@ def test_get_datastore(): # Verify getGroup() # ------------------------------------------------------------------------------ def test_get_group(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() parent = root.createGroup("parent") @@ -203,7 +203,7 @@ def test_get_group(): # getView() # ------------------------------------------------------------------------------ def test_get_view(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() parent = root.createGroup("parent") @@ -220,10 +220,10 @@ def test_get_view(): def test_group_and_view_checksum(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() group = root.createGroup("checksum_group") - view = group.createViewAndAllocate("values", pysidre.TypeID.INT32_ID, 4) + view = group.createViewAndAllocate("values", sidre.TypeID.INT32_ID, 4) data = view.getDataArray() data[:] = np.array([1, 2, 3, 4], dtype=np.int32) @@ -270,7 +270,7 @@ def test_group_and_view_checksum(): # createView, hasView(), getView(), destroyView() with path strings #------------------------------------------------------------------------------ def test_view_with_path(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() # Test with full path access when building incrementally @@ -345,7 +345,7 @@ def test_view_with_path(): # Verify getViewName() and getViewIndex() #------------------------------------------------------------------------------ def test_get_view_name_index(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() parent = root.createGroup("parent") @@ -367,18 +367,18 @@ def test_get_view_name_index(): assert view2.getName() == name2 idx3 = parent.getViewIndex("view3") - assert idx3 == pysidre.InvalidIndex + assert idx3 == sidre.InvalidIndex name3 = parent.getViewName(idx3) assert name3 == "" - assert not pysidre.nameIsValid(name3) + assert not sidre.nameIsValid(name3) #------------------------------------------------------------------------------ # Verify getFirstValidGroupIndex() and getNextValidGroupIndex() #------------------------------------------------------------------------------ def test_get_first_and_next_group_index(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() parent = root.createGroup("parent") @@ -392,7 +392,7 @@ def test_get_first_and_next_group_index(): assert idx1 == 0 assert idx2 == 1 - assert idx3 == pysidre.InvalidIndex + assert idx3 == sidre.InvalidIndex group1out = parent.getGroup(idx1) group2out = parent.getGroup(idx2) @@ -405,15 +405,15 @@ def test_get_first_and_next_group_index(): badidx1 = emptygrp.getFirstValidGroupIndex() badidx2 = emptygrp.getNextValidGroupIndex(badidx1) - assert badidx1 == pysidre.InvalidIndex - assert badidx2 == pysidre.InvalidIndex + assert badidx1 == sidre.InvalidIndex + assert badidx2 == sidre.InvalidIndex #------------------------------------------------------------------------------ # Verify Groups holding items in the list format #------------------------------------------------------------------------------ def test_child_lists(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() # parent is a Group in list format. @@ -433,8 +433,8 @@ def test_child_lists(): else: unnamed_view = parent.createViewString("", "foo") if not unnamed_view.isApplied(): - unnamed_view.apply(pysidre.TypeID.INT_ID, i) - unnamed_view.allocate(pysidre.TypeID.INT_ID, i) + unnamed_view.apply(sidre.TypeID.INT_ID, i) + unnamed_view.allocate(sidre.TypeID.INT_ID, i) vdata = unnamed_view.getDataArray() # Returns numpy array for j in range(i): vdata[j] = j + 3 @@ -452,7 +452,7 @@ def test_child_lists(): # Access data from unnamed Groups held by parent. scalars = set() idx = parent.getFirstValidGroupIndex() - while pysidre.indexIsValid(idx): + while sidre.indexIsValid(idx): unnamed_group = parent.getGroup(idx) val_view = unnamed_group.getView("val") val = val_view.getDataInt() @@ -467,7 +467,7 @@ def test_child_lists(): # Destroy five of the unnamed Groups held by parent. idx = parent.getFirstValidGroupIndex() - while pysidre.indexIsValid(idx): + while sidre.indexIsValid(idx): if idx % 2 == 1: parent.destroyGroup(idx) idx = parent.getNextValidGroupIndex(idx) @@ -478,10 +478,10 @@ def test_child_lists(): # Access data from the unnamed Views. idx = parent.getFirstValidViewIndex() - while pysidre.indexIsValid(idx): + while sidre.indexIsValid(idx): unnamed_view = parent.getView(idx) if idx % 3 == 0: - assert unnamed_view.getTypeID() == pysidre.TypeID.INT32_ID + assert unnamed_view.getTypeID() == sidre.TypeID.INT32_ID num_elems = unnamed_view.getNumElements() assert num_elems == idx vdata = unnamed_view.getDataArray() @@ -504,7 +504,7 @@ def test_child_lists(): # Verify results with various path arguments for items in list #------------------------------------------------------------------------------ def test_list_item_names(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() # Create a group that uses the list format. @@ -572,7 +572,7 @@ def test_list_item_names(): #------------------------------------------------------------------------------ def test_string_list(): # Round-trip test from Python list of strings to Group and back. - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() str_vec = [ @@ -596,7 +596,7 @@ def test_string_list(): # Get strings from the Group. idx = my_strings.getFirstValidViewIndex() - while pysidre.indexIsValid(idx): + while sidre.indexIsValid(idx): str_view = my_strings.getView(idx) assert str_view is not None assert str_view.isString() @@ -611,7 +611,7 @@ def test_string_list(): # Iterate Groups with getFirstValidGroupIndex, getNextValidGroupIndex #------------------------------------------------------------------------------ def test_iterate_groups(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() parent = root.createGroup("parent") @@ -631,7 +631,7 @@ def test_iterate_groups(): groupcount = 0 idx = parent.getFirstValidGroupIndex() - while pysidre.indexIsValid(idx): + while sidre.indexIsValid(idx): groupcount += 1 idx = parent.getNextValidGroupIndex(idx) assert groupcount == 4 @@ -642,7 +642,7 @@ def test_iterate_groups(): #------------------------------------------------------------------------------ def test_iterate_groups_with_iterator(): - ds = pysidre.DataStore() + ds = sidre.DataStore() foo_group = ds.getRoot().createGroup("foo") foo_group.createGroup("bar_group") foo_group.createGroup("bar_group/child_1") @@ -684,7 +684,7 @@ def test_iterate_groups_with_iterator(): # Verify getFirstValidViewIndex() and getNextValidIndex() #------------------------------------------------------------------------------ def test_get_first_and_next_view_index(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() parent = root.createGroup("parent") @@ -698,7 +698,7 @@ def test_get_first_and_next_view_index(): idx3 = parent.getNextValidViewIndex(idx2) assert idx1 == 0 assert idx2 == 1 - assert idx3 == pysidre.InvalidIndex + assert idx3 == sidre.InvalidIndex view1out = parent.getView(idx1) view2out = parent.getView(idx2) @@ -710,15 +710,15 @@ def test_get_first_and_next_view_index(): badidx1 = emptygrp.getFirstValidViewIndex() badidx2 = emptygrp.getNextValidViewIndex(badidx1) - assert badidx1 == pysidre.InvalidIndex - assert badidx2 == pysidre.InvalidIndex + assert badidx1 == sidre.InvalidIndex + assert badidx2 == sidre.InvalidIndex #------------------------------------------------------------------------------ # Iterate Views with getFirstValidViewIndex, getNextValidViewIndex #------------------------------------------------------------------------------ def test_iterate_views(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() parent = root.createGroup("parent") @@ -738,7 +738,7 @@ def test_iterate_views(): viewcount = 0 idx = parent.getFirstValidViewIndex() - while pysidre.indexIsValid(idx): + while sidre.indexIsValid(idx): viewcount += 1 idx = parent.getNextValidViewIndex(idx) assert viewcount == 9 @@ -748,7 +748,7 @@ def test_iterate_views(): # Verify getGroupName() and getGroupIndex() #------------------------------------------------------------------------------ def test_get_group_name_index(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() parent = root.createGroup("parent") @@ -769,11 +769,11 @@ def test_get_group_name_index(): assert grp2.getName() == name2 idx3 = parent.getGroupIndex("grp3") - assert idx3 == pysidre.InvalidIndex + assert idx3 == sidre.InvalidIndex name3 = parent.getGroupName(idx3) assert name3 == "" - assert not pysidre.nameIsValid(name3) + assert not sidre.nameIsValid(name3) # ------------------------------------------------------------------------------ @@ -784,7 +784,7 @@ def test_get_group_name_index(): # hasView() # ------------------------------------------------------------------------------ def test_create_destroy_has_view(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() group = root.createGroup("parent") @@ -817,22 +817,21 @@ def test_create_destroy_has_view(): assert not group.hasView("view") # Try API call that specifies specific type and length - group.createViewAndAllocate("viewWithLength1", pysidre.TypeID.INT32_ID, 50) + group.createViewAndAllocate("viewWithLength1", sidre.TypeID.INT32_ID, 50) iview2 = group.getViewIndex("viewWithLength1") assert iview == iview2 # reuse slot # Error condition check - try again with duplicate name, should be a no-op - assert group.createViewAndAllocate("viewWithLength1", pysidre.TypeID.FLOAT64_ID, 50) is None + assert group.createViewAndAllocate("viewWithLength1", sidre.TypeID.FLOAT64_ID, 50) is None group.destroyViewAndData("viewWithLength1") assert not group.hasView("viewWithLength1") # Should not allow negative length - assert group.createViewAndAllocate("viewWithLengthBadLen", pysidre.TypeID.FLOAT64_ID, - -1) is None + assert group.createViewAndAllocate("viewWithLengthBadLen", sidre.TypeID.FLOAT64_ID, -1) is None # Try API call that specifies data type in another way - group.createViewAndAllocate("viewWithLength2", pysidre.TypeID.FLOAT64_ID, 50) - assert group.createViewAndAllocate("viewWithLength2", pysidre.TypeID.FLOAT64_ID, 50) is None + group.createViewAndAllocate("viewWithLength2", sidre.TypeID.FLOAT64_ID, 50) + assert group.createViewAndAllocate("viewWithLength2", sidre.TypeID.FLOAT64_ID, 50) is None # Destroy view and its buffer using index indx = group.getFirstValidViewIndex() @@ -843,7 +842,7 @@ def test_create_destroy_has_view(): assert ds.getBuffer(bindx) is None # Destroy view but not the buffer - view = group.createViewAndAllocate("viewWithLength2", pysidre.TypeID.INT_ID, 50) + view = group.createViewAndAllocate("viewWithLength2", sidre.TypeID.INT_ID, 50) buff = view.getBuffer() group.destroyView("viewWithLength2") assert buff.isAllocated() @@ -853,10 +852,10 @@ def test_create_destroy_has_view(): # createViewAndAllocate() with zero-sized array #------------------------------------------------------------------------------ def test_create_zero_sized_view(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() - zero_sized_view = root.createViewAndAllocate("foo", pysidre.TypeID.INT_ID, 0) + zero_sized_view = root.createViewAndAllocate("foo", sidre.TypeID.INT_ID, 0) assert zero_sized_view.isDescribed() assert zero_sized_view.isAllocated() @@ -865,7 +864,7 @@ def test_create_zero_sized_view(): # Verify createGroup(), destroyGroup(), hasGroup() #------------------------------------------------------------------------------ def test_create_destroy_has_group(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() grp = root.createGroup("grp") @@ -884,7 +883,7 @@ def test_create_destroy_has_group(): # Test various destroy methods #------------------------------------------------------------------------------ def test_destroy_group_and_data(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() group0 = root.createGroup("group0") group1 = root.createGroup("group1") @@ -894,12 +893,12 @@ def test_destroy_group_and_data(): child3 = group1.createGroup("child3") child4 = group1.createGroup("child4") - child0.createViewAndAllocate("intview", pysidre.TypeID.INT_ID, 15) + child0.createViewAndAllocate("intview", sidre.TypeID.INT_ID, 15) foo0 = child0.createGroup("foo") child0.createGroup("empty") child0.createViewScalar("sclview", 3.14159) child0.createViewString("strview", "Hello world.") - foo0.createViewAndAllocate("fooview", pysidre.TypeID.FLOAT64_ID, 12) + foo0.createViewAndAllocate("fooview", sidre.TypeID.FLOAT64_ID, 12) int0_view = child0.getView("intview") int0_vals = int0_view.getDataArray() @@ -919,33 +918,33 @@ def test_destroy_group_and_data(): flt_idx = fltbuf.getIndex() # Attach buffers to views in other children - child1.createView("intview", pysidre.TypeID.INT_ID, 15, intbuf) + child1.createView("intview", sidre.TypeID.INT_ID, 15, intbuf) foo1 = child1.createGroup("foo") child1.createGroup("empty") child1.createViewScalar("sclview", 3.14159) child1.createViewString("strview", "Hello world.") - foo1.createView("fooview", pysidre.TypeID.FLOAT64_ID, 12, fltbuf) + foo1.createView("fooview", sidre.TypeID.FLOAT64_ID, 12, fltbuf) - child2.createView("intview", pysidre.TypeID.INT_ID, 15, intbuf) + child2.createView("intview", sidre.TypeID.INT_ID, 15, intbuf) foo2 = child2.createGroup("foo") child2.createGroup("empty") child2.createViewScalar("sclview", 3.14159) child2.createViewString("strview", "Hello world.") - foo2.createView("fooview", pysidre.TypeID.FLOAT64_ID, 12, fltbuf) + foo2.createView("fooview", sidre.TypeID.FLOAT64_ID, 12, fltbuf) - child3.createView("intview", pysidre.TypeID.INT_ID, 15, intbuf) + child3.createView("intview", sidre.TypeID.INT_ID, 15, intbuf) foo3 = child3.createGroup("foo") child3.createGroup("empty") child3.createViewScalar("sclview", 3.14159) child3.createViewString("strview", "Hello world.") - foo3.createView("fooview", pysidre.TypeID.FLOAT64_ID, 12, fltbuf) + foo3.createView("fooview", sidre.TypeID.FLOAT64_ID, 12, fltbuf) - child4.createView("intview", pysidre.TypeID.INT_ID, 15, intbuf) + child4.createView("intview", sidre.TypeID.INT_ID, 15, intbuf) foo4 = child4.createGroup("foo") child4.createGroup("empty") child4.createViewScalar("sclview", 3.14159) child4.createViewString("strview", "Hello world.") - foo4.createView("fooview", pysidre.TypeID.FLOAT64_ID, 12, fltbuf) + foo4.createView("fooview", sidre.TypeID.FLOAT64_ID, 12, fltbuf) # Beginning state: 2 Buffers, each attached to 5 Views. assert ds.getNumBuffers() == 2 @@ -1029,7 +1028,7 @@ def test_destroy_group_and_data(): def test_group_name_collisions(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() flds = root.createGroup("fields") flds.createView("a") @@ -1055,13 +1054,13 @@ def test_group_name_collisions(): # Print all group names idx = root.getFirstValidGroupIndex() - while pysidre.indexIsValid(idx): + while sidre.indexIsValid(idx): print(root.getGroup(idx).getName()) idx = root.getNextValidGroupIndex(idx) def test_view_copy_move(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() flds = root.createGroup("fields") extdata = np.array([0] * 10) @@ -1071,9 +1070,9 @@ def test_view_copy_move(): # Create views in different states views[0] = flds.createView("empty0") - views[1] = flds.createView("empty1", pysidre.TypeID.INT32_ID, 10) - views[2] = flds.createViewAndAllocate("buffer", pysidre.TypeID.INT32_ID, 10) - views[3] = flds.createView("external", pysidre.TypeID.INT32_ID, 10) + views[1] = flds.createView("empty1", sidre.TypeID.INT32_ID, 10) + views[2] = flds.createViewAndAllocate("buffer", sidre.TypeID.INT32_ID, 10) + views[3] = flds.createView("external", sidre.TypeID.INT32_ID, 10) views[3].setExternalData(extdata) views[4] = flds.createViewScalar("scalar", 25) views[5] = flds.createViewString("string", "I am string") @@ -1163,7 +1162,7 @@ def test_view_copy_move(): def test_groups_move_copy(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() flds = root.createGroup("fields") @@ -1254,7 +1253,7 @@ def test_groups_move_copy(): def test_group_deep_copy(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() flds = root.createGroup("fields") @@ -1274,14 +1273,14 @@ def test_group_deep_copy(): assert flds.hasGroup("b") viewlen = 8 - ownsbuf = ga.createViewAndAllocate("ownsbuf", pysidre.TypeID.INT32_ID, viewlen) + ownsbuf = ga.createViewAndAllocate("ownsbuf", sidre.TypeID.INT32_ID, viewlen) int_vals = ownsbuf.getDataArray() for i in range(viewlen): int_vals[i] = i + 1 buflen = 24 dbuff = ds.createBuffer() - dbuff.allocate(pysidre.TypeID.FLOAT64_ID, buflen) + dbuff.allocate(sidre.TypeID.FLOAT64_ID, buflen) buf_ptr = dbuff.getDataArray() for i in range(buflen): buf_ptr[i] = 2.0 * float(i) @@ -1299,7 +1298,7 @@ def test_group_deep_copy(): ext_array = np.array([-1.0 * float(i) for i in range(extlen)]) for i in range(NUM_VIEWS): - gb.createView(names[i], ext_array).apply(pysidre.TypeID.FLOAT64_ID, size[i], offset[i], + gb.createView(names[i], ext_array).apply(sidre.TypeID.FLOAT64_ID, size[i], offset[i], stride[i]) deep_copy = root.createGroup("deep_copy") @@ -1351,15 +1350,15 @@ def test_group_deep_copy(): def test_create_destroy_view_and_buffer2(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() grp = root.createGroup("grp") viewName1 = "viewBuffer1" viewName2 = "viewBuffer2" - view1 = grp.createViewAndAllocate(viewName1, pysidre.TypeID.INT_ID, 1) - view2 = grp.createViewAndAllocate(viewName2, pysidre.TypeID.INT_ID, 1) + view1 = grp.createViewAndAllocate(viewName1, sidre.TypeID.INT_ID, 1) + view2 = grp.createViewAndAllocate(viewName2, sidre.TypeID.INT_ID, 1) assert grp.hasView(viewName1) assert grp.getView(viewName1) == view1 @@ -1384,7 +1383,7 @@ def test_create_destroy_view_and_buffer2(): def test_create_destroy_alloc_view_and_buffer(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() grp = root.createGroup("grp") @@ -1393,7 +1392,7 @@ def test_create_destroy_alloc_view_and_buffer(): # Use create + alloc convenience methods # This one is the DataType method - view1 = grp.createViewAndAllocate(viewName1, pysidre.TypeID.INT_ID, 10) + view1 = grp.createViewAndAllocate(viewName1, sidre.TypeID.INT_ID, 10) assert grp.hasChildView(viewName1) assert grp.getView(viewName1) == view1 @@ -1409,11 +1408,11 @@ def test_create_destroy_alloc_view_and_buffer(): def test_create_view_of_buffer_with_schema(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() # Use create + alloc convenience methods - base = root.createViewAndAllocate("base", pysidre.TypeID.INT_ID, 10) + base = root.createViewAndAllocate("base", sidre.TypeID.INT_ID, 10) base_vals = base.getDataArray() for i in range(10): if i < 5: @@ -1426,7 +1425,7 @@ def test_create_view_of_buffer_with_schema(): # Create two views into this buffer # View for the first 5 values sub_a = root.createView("sub_a", base_buff) - sub_a.apply(pysidre.TypeID.INT_ID, 5) + sub_a.apply(sidre.TypeID.INT_ID, 5) sub_a_vals = sub_a.getDataArray() for i in range(5): @@ -1434,15 +1433,15 @@ def test_create_view_of_buffer_with_schema(): def test_create_destroy_view_and_data(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() grp = root.createGroup("grp") view_name1 = "viewBuffer1" view_name2 = "viewBuffer2" - view1 = grp.createViewAndAllocate(view_name1, pysidre.TypeID.INT32_ID, 1) - view2 = grp.createViewAndAllocate(view_name2, pysidre.TypeID.INT32_ID, 1) + view1 = grp.createViewAndAllocate(view_name1, sidre.TypeID.INT32_ID, 1) + view2 = grp.createViewAndAllocate(view_name2, sidre.TypeID.INT32_ID, 1) assert grp.hasView(view_name1) assert grp.getView(view_name1) == view1 @@ -1460,7 +1459,7 @@ def test_create_destroy_view_and_data(): def test_create_destroy_alloc_view_and_data(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() grp = root.createGroup("grp") @@ -1469,7 +1468,7 @@ def test_create_destroy_alloc_view_and_data(): # Use create + alloc convenience methods # this one is the DataType & method - view1 = grp.createViewAndAllocate(view_name1, pysidre.TypeID.INT32_ID, 10) + view1 = grp.createViewAndAllocate(view_name1, sidre.TypeID.INT32_ID, 10) assert grp.hasView(view_name1) assert grp.getView(view_name1) == view1 @@ -1484,12 +1483,12 @@ def test_create_destroy_alloc_view_and_data(): def test_create_view_of_buffer_with_datatype(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() # Use create + alloc convenience methods # this one is the DataType & method - base = root.createViewAndAllocate("base", pysidre.TypeID.INT32_ID, 10) + base = root.createViewAndAllocate("base", sidre.TypeID.INT32_ID, 10) base_vals = base.getDataArray() base_vals[0:5] = 10 @@ -1498,7 +1497,7 @@ def test_create_view_of_buffer_with_datatype(): base_buff = base.getBuffer() # Create view into this buffer - sub_a = root.createView("sub_a", pysidre.TypeID.INT32_ID, 10, base_buff) + sub_a = root.createView("sub_a", sidre.TypeID.INT32_ID, 10, base_buff) sub_a_vals = root.getView("sub_a").getDataArray() @@ -1510,7 +1509,7 @@ def test_create_view_of_buffer_with_datatype(): def test_save_restore_empty_datastore(): file_path_base = "py_sidre_empty_datastore_" - ds1 = pysidre.DataStore() + ds1 = sidre.DataStore() root1 = ds1.getRoot() for i in range(NPROTOCOLS): @@ -1522,7 +1521,7 @@ def test_save_restore_empty_datastore(): continue file_path = file_path_base + PROTOCOLS[i] - ds2 = pysidre.DataStore() + ds2 = sidre.DataStore() root2 = ds2.getRoot() root2.load(file_path, PROTOCOLS[i]) @@ -1534,7 +1533,7 @@ def test_save_restore_empty_datastore(): def test_save_restore_scalars_and_strings(): file_path_base = "py_sidre_save_scalars_and_strings_" - ds1 = pysidre.DataStore() + ds1 = sidre.DataStore() root1 = ds1.getRoot() view = root1.createViewScalar("i0", 1) @@ -1551,7 +1550,7 @@ def test_save_restore_scalars_and_strings(): continue file_path = file_path_base + PROTOCOLS[i] - ds2 = pysidre.DataStore() + ds2 = sidre.DataStore() root2 = ds2.getRoot() root2.load(file_path, PROTOCOLS[i]) @@ -1590,13 +1589,13 @@ def test_save_restore_external_data(): int2d1 = np.column_stack((foo1, foo1 + nfoo)) int2d2 = np.zeros((10, 2), dtype=int) - ds1 = pysidre.DataStore() + ds1 = sidre.DataStore() root1 = ds1.getRoot() - root1.createView("external_array", pysidre.TypeID.INT64_ID, nfoo, foo1) - root1.createView("empty_array", pysidre.TypeID.INT64_ID, 0, foo3) + root1.createView("external_array", sidre.TypeID.INT64_ID, nfoo, foo1) + root1.createView("empty_array", sidre.TypeID.INT64_ID, 0, foo3) root1.createView("external_undescribed").setExternalData(foo4) - root1.createViewWithShape("int2d", pysidre.TypeID.INT64_ID, 2, shape, int2d1) + root1.createViewWithShape("int2d", sidre.TypeID.INT64_ID, 2, shape, int2d1) for protocol in PROTOCOLS: file_path = file_path_base + protocol @@ -1609,7 +1608,7 @@ def test_save_restore_external_data(): continue file_path = file_path_base + protocol - ds2 = pysidre.DataStore() + ds2 = sidre.DataStore() root2 = ds2.getRoot() assert root2.load(file_path, protocol) == True @@ -1619,14 +1618,14 @@ def test_save_restore_external_data(): view1 = root2.getView("external_array") assert view1.isExternal() == True, "external_array is external" assert view1.isDescribed() == True, "external_array is described" - assert view1.getTypeID() == pysidre.TypeID.INT64_ID, "external_array get TypeId" + assert view1.getTypeID() == sidre.TypeID.INT64_ID, "external_array get TypeId" assert view1.getNumElements() == nfoo, "external_array get num elements" view1.setExternalData(foo2) view2 = root2.getView("empty_array") assert view2.isExternal() == True, "empty_array is external" assert view2.isDescribed() == True, "empty_array is described" - assert view2.getTypeID() == pysidre.TypeID.INT64_ID, "empty_array get TypeId" + assert view2.getTypeID() == sidre.TypeID.INT64_ID, "empty_array get TypeId" view2.setExternalData(foo3) view3 = root2.getView("external_undescribed") @@ -1637,7 +1636,7 @@ def test_save_restore_external_data(): view4 = root2.getView("int2d") assert view4.isExternal() == True, "int2d is external" assert view4.isDescribed() == True, "int2d is described" - assert view4.getTypeID() == pysidre.TypeID.INT64_ID, "int2d get TypeId" + assert view4.getTypeID() == sidre.TypeID.INT64_ID, "int2d get TypeId" assert view4.getNumElements() == nfoo * 2, "int2d get num elements" assert view4.getNumDimensions() == 2, "int2d get num dimensions" @@ -1664,14 +1663,14 @@ def test_save_restore_other(): file_path_base = "py_sidre_empty_other_" ndata = 10 - ds1 = pysidre.DataStore() + ds1 = sidre.DataStore() root1 = ds1.getRoot() shape1 = np.array([ndata, 2]) view1 = root1.createView("empty_view") - view2 = root1.createView("empty_described", pysidre.TypeID.INT32_ID, ndata) - view3 = root1.createViewWithShape("empty_shape", pysidre.TypeID.INT32_ID, 2, shape1) - view4 = root1.createViewWithShapeAndAllocate("buffer_shape", pysidre.TypeID.INT32_ID, 2, shape1) + view2 = root1.createView("empty_described", sidre.TypeID.INT32_ID, ndata) + view3 = root1.createViewWithShape("empty_shape", sidre.TypeID.INT32_ID, 2, shape1) + view4 = root1.createViewWithShapeAndAllocate("buffer_shape", sidre.TypeID.INT32_ID, 2, shape1) for protocol in PROTOCOLS: file_path = file_path_base + protocol @@ -1685,7 +1684,7 @@ def test_save_restore_other(): file_path = file_path_base + protocol - ds2 = pysidre.DataStore() + ds2 = sidre.DataStore() root2 = ds2.getRoot() root2.load(file_path, protocol) @@ -1697,13 +1696,13 @@ def test_save_restore_other(): view2 = root2.getView("empty_described") assert view2.isEmpty() == True, "empty_described is empty" assert view2.isDescribed() == True, "empty_described is described" - assert view2.getTypeID() == pysidre.TypeID.INT32_ID, "empty_described get TypeID" + assert view2.getTypeID() == sidre.TypeID.INT32_ID, "empty_described get TypeID" assert view2.getNumElements() == ndata, "empty_described get num elements" view3 = root2.getView("empty_shape") assert view3.isEmpty() == True, "empty_shape is empty" assert view3.isDescribed() == True, "empty_shape is described" - assert view3.getTypeID() == pysidre.TypeID.INT32_ID, "empty_shape get TypeID" + assert view3.getTypeID() == sidre.TypeID.INT32_ID, "empty_shape get TypeID" assert view3.getNumElements() == ndata * 2, "empty_shape get num elements" shape2 = np.zeros(7) rank, shape2 = view3.getShape(7, shape2) @@ -1713,7 +1712,7 @@ def test_save_restore_other(): view4 = root2.getView("buffer_shape") assert view4.hasBuffer() == True, "buffer_shape has buffer" assert view4.isDescribed() == True, "buffer_shape is described" - assert view4.getTypeID() == pysidre.TypeID.INT32_ID, "buffer_shape get TypeID" + assert view4.getTypeID() == sidre.TypeID.INT32_ID, "buffer_shape get TypeID" assert view4.getNumElements() == ndata * 2, "buffer_shape get num elements" shape2 = np.zeros(7) rank, shape2 = view4.getShape(7, shape2) @@ -1722,7 +1721,7 @@ def test_save_restore_other(): def test_rename_group(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() child1 = root.createGroup("g_a") child2 = root.createGroup("g_b") @@ -1748,11 +1747,11 @@ def test_rename_group(): assert child3.getName() == "g_c" # Rename root group - assert not pysidre.indexIsValid(root.getIndex()) + assert not sidre.indexIsValid(root.getIndex()) assert root.getParent() == root assert root.getName() == "" root.rename("newroot") - assert not pysidre.indexIsValid(root.getIndex()) + assert not sidre.indexIsValid(root.getIndex()) assert root.getParent() == root assert root.getName() == "newroot" @@ -1760,7 +1759,7 @@ def test_rename_group(): # Fortran comment - redo these, the C++ tests were heavily rewritten def test_save_restore_simple(): file_path = "py_out_sidre_group_save_restore_simple" - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() flds = root.createGroup("fields") @@ -1774,7 +1773,7 @@ def test_save_restore_simple(): root.save(file_path, "sidre_conduit_json") - ds2 = pysidre.DataStore() + ds2 = sidre.DataStore() root2 = ds2.getRoot() root2.load(file_path, "sidre_conduit_json") @@ -1790,7 +1789,7 @@ def test_save_restore_simple(): def test_save_restore_complex(): file_path = "py_out_sidre_group_save_restore_complex" - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() flds = root.createGroup("fields") @@ -1809,7 +1808,7 @@ def test_save_restore_complex(): root.save(file_path, "sidre_conduit_json") - ds2 = pysidre.DataStore() + ds2 = sidre.DataStore() root2 = ds2.getRoot() root2.load(file_path, "sidre_conduit_json") @@ -1839,7 +1838,7 @@ def test_save_load_preserve_contents(): file_path_base0 = "py_sidre_save_preserve_contents_tree0_" file_path_base1 = "py_sidre_save_preserve_contents_tree1_" - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() tree0 = root.createGroup("tree0") @@ -1850,7 +1849,7 @@ def test_save_load_preserve_contents(): i0_view = ga.createViewScalar("i0", 100) f0_view = ga.createViewScalar("f0", 3000.0) s0_view = gb.createViewString("s0", "foo") - i10_view = gc.createViewAndAllocate("int10", pysidre.TypeID.INT32_ID, 10) + i10_view = gc.createViewAndAllocate("int10", sidre.TypeID.INT32_ID, 10) v1_vals = i10_view.getDataArray() for i in range(10): @@ -1870,7 +1869,7 @@ def test_save_load_preserve_contents(): gy = tree1.createGroup("y") gz = tree1.createGroup("z") - i20_view = gx.createViewAndAllocate("int20", pysidre.TypeID.INT32_ID, 20) + i20_view = gx.createViewAndAllocate("int20", sidre.TypeID.INT32_ID, 20) v2_vals = i20_view.getDataArray() for i in range(20): v2_vals[i] = 2 * i @@ -1881,7 +1880,7 @@ def test_save_load_preserve_contents(): file_path1 = file_path_base1 + protocol assert tree1.save(file_path1, protocol) - dsload = pysidre.DataStore() + dsload = sidre.DataStore() ldroot = dsload.getRoot() ldtree0 = ldroot.createGroup("tree0") diff --git a/src/axom/sidre/tests/sidre_lifetime_Py.py b/src/axom/sidre/tests/sidre_lifetime_Py.py index 6ddd097f97..2196179523 100644 --- a/src/axom/sidre/tests/sidre_lifetime_Py.py +++ b/src/axom/sidre/tests/sidre_lifetime_Py.py @@ -15,7 +15,7 @@ import numpy as np import pytest -import axom.sidre as pysidre +import axom.sidre as sidre def _force_gc(): @@ -28,7 +28,7 @@ def _force_gc(): # Child proxies must pin their owner chain (parent -> owned child) # --------------------------------------------------------------------------- def test_root_outlives_datastore(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() del ds _force_gc() @@ -39,7 +39,7 @@ def test_root_outlives_datastore(): def test_child_group_outlives_datastore(): - ds = pysidre.DataStore() + ds = sidre.DataStore() grp = ds.getRoot().createGroup("a/b/c") del ds _force_gc() @@ -49,8 +49,8 @@ def test_child_group_outlives_datastore(): def test_view_outlives_datastore(): - ds = pysidre.DataStore() - view = ds.getRoot().createViewAndAllocate("field", pysidre.TypeID.INT_ID, 4) + ds = sidre.DataStore() + view = ds.getRoot().createViewAndAllocate("field", sidre.TypeID.INT_ID, 4) del ds _force_gc() assert view.getNumElements() == 4 @@ -58,8 +58,8 @@ def test_view_outlives_datastore(): def test_buffer_outlives_datastore(): - ds = pysidre.DataStore() - buff = ds.createBuffer(pysidre.TypeID.INT_ID, 8) + ds = sidre.DataStore() + buff = ds.createBuffer(sidre.TypeID.INT_ID, 8) buff.allocate() del ds _force_gc() @@ -70,7 +70,7 @@ def test_buffer_outlives_datastore(): # Ancestor proxies (child -> ancestor) must pin the object they were minted from # --------------------------------------------------------------------------- def test_owning_group_outlives_datastore(): - ds = pysidre.DataStore() + ds = sidre.DataStore() view = ds.getRoot().createView("v") owner = view.getOwningGroup() del ds @@ -80,7 +80,7 @@ def test_owning_group_outlives_datastore(): def test_get_datastore_back_reference(): - ds = pysidre.DataStore() + ds = sidre.DataStore() grp = ds.getRoot().createGroup("child") back = grp.getDataStore() del ds @@ -91,8 +91,8 @@ def test_get_datastore_back_reference(): def test_view_buffer_back_reference(): - ds = pysidre.DataStore() - view = ds.getRoot().createViewAndAllocate("field", pysidre.TypeID.INT_ID, 4) + ds = sidre.DataStore() + view = ds.getRoot().createViewAndAllocate("field", sidre.TypeID.INT_ID, 4) buff = view.getBuffer() del ds del view @@ -104,7 +104,7 @@ def test_view_buffer_back_reference(): # Iterator elements harvested into a list must outlive the collection + store # --------------------------------------------------------------------------- def test_harvested_views_outlive_datastore(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() for i in range(4): root.createView(f"v{i}") @@ -117,7 +117,7 @@ def test_harvested_views_outlive_datastore(): def test_harvested_groups_outlive_datastore(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() for i in range(3): root.createGroup(f"g{i}") @@ -130,9 +130,9 @@ def test_harvested_groups_outlive_datastore(): def test_harvested_buffers_outlive_datastore(): - ds = pysidre.DataStore() + ds = sidre.DataStore() for _ in range(3): - ds.createBuffer(pysidre.TypeID.INT_ID, 2).allocate() + ds.createBuffer(sidre.TypeID.INT_ID, 2).allocate() harvested = list(ds.buffers()) del ds _force_gc() @@ -140,7 +140,7 @@ def test_harvested_buffers_outlive_datastore(): def test_harvested_attributes_outlive_datastore(): - ds = pysidre.DataStore() + ds = sidre.DataStore() ds.createAttributeString("a0", "x") ds.createAttributeScalar("a1", 42) harvested = list(ds.attributes()) @@ -153,7 +153,7 @@ def test_harvested_attributes_outlive_datastore(): # Iterator adaptors must outlive the owning Group/DataStore # --------------------------------------------------------------------------- def test_views_adaptor_outlives_group_and_datastore(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() for i in range(3): root.createView(f"v{i}") @@ -165,7 +165,7 @@ def test_views_adaptor_outlives_group_and_datastore(): def test_groups_adaptor_outlives_group_and_datastore(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() for i in range(3): root.createGroup(f"g{i}") @@ -177,9 +177,9 @@ def test_groups_adaptor_outlives_group_and_datastore(): def test_buffers_adaptor_outlives_datastore(): - ds = pysidre.DataStore() + ds = sidre.DataStore() for _ in range(3): - ds.createBuffer(pysidre.TypeID.INT_ID, 2).allocate() + ds.createBuffer(sidre.TypeID.INT_ID, 2).allocate() adaptor = ds.buffers() del ds _force_gc() @@ -187,7 +187,7 @@ def test_buffers_adaptor_outlives_datastore(): def test_attributes_adaptor_outlives_datastore(): - ds = pysidre.DataStore() + ds = sidre.DataStore() ds.createAttributeString("a0", "x") ds.createAttributeScalar("a1", 42) adaptor = ds.attributes() @@ -200,7 +200,7 @@ def test_attributes_adaptor_outlives_datastore(): # Lookup accessors should return proxies that pin their owner chain # --------------------------------------------------------------------------- def test_get_view_outlives_group_and_datastore(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() root.createView("v") view = root.getView("v") @@ -211,7 +211,7 @@ def test_get_view_outlives_group_and_datastore(): def test_get_group_outlives_group_and_datastore(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() root.createGroup("g") grp = root.getGroup("g") @@ -222,8 +222,8 @@ def test_get_group_outlives_group_and_datastore(): def test_get_buffer_outlives_datastore(): - ds = pysidre.DataStore() - ds.createBuffer(pysidre.TypeID.INT_ID, 7).allocate() + ds = sidre.DataStore() + ds.createBuffer(sidre.TypeID.INT_ID, 7).allocate() buff = ds.getBuffer(0) del ds _force_gc() @@ -231,7 +231,7 @@ def test_get_buffer_outlives_datastore(): def test_get_attribute_outlives_datastore(): - ds = pysidre.DataStore() + ds = sidre.DataStore() ds.createAttributeString("a0", "x") attr = ds.getAttribute("a0") del ds @@ -240,7 +240,7 @@ def test_get_attribute_outlives_datastore(): def test_parent_group_outlives_datastore(): - ds = pysidre.DataStore() + ds = sidre.DataStore() child = ds.getRoot().createGroup("a/b") parent = child.getParent() del ds @@ -251,7 +251,7 @@ def test_parent_group_outlives_datastore(): def test_moved_group_outlives_owner_chain(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() src = root.createGroup("src") dst = root.createGroup("dst") @@ -275,8 +275,8 @@ def test_moved_group_outlives_owner_chain(): # Zero-copy numpy arrays must pin their backing View / Buffer (and DataStore) # --------------------------------------------------------------------------- def test_view_array_outlives_datastore(): - ds = pysidre.DataStore() - view = ds.getRoot().createViewAndAllocate("field", pysidre.TypeID.INT_ID, 4) + ds = sidre.DataStore() + view = ds.getRoot().createViewAndAllocate("field", sidre.TypeID.INT_ID, 4) arr = view.getDataArray() arr[:] = [10, 20, 30, 40] del ds @@ -289,8 +289,8 @@ def test_view_array_outlives_datastore(): def test_buffer_array_outlives_datastore(): - ds = pysidre.DataStore() - buff = ds.createBuffer(pysidre.TypeID.INT_ID, 4) + ds = sidre.DataStore() + buff = ds.createBuffer(sidre.TypeID.INT_ID, 4) buff.allocate() arr = buff.getDataArray() arr[:] = [1, 2, 3, 4] @@ -303,8 +303,8 @@ def test_buffer_array_outlives_datastore(): def test_view_array_survives_owner_chain_collection(): # Keep only the array; let the entire DataStore/Group/View chain be dropped. def make_array(): - ds = pysidre.DataStore() - view = ds.getRoot().createViewAndAllocate("field", pysidre.TypeID.FLOAT64_ID, 5) + ds = sidre.DataStore() + view = ds.getRoot().createViewAndAllocate("field", sidre.TypeID.FLOAT64_ID, 5) a = view.getDataArray() a[:] = np.arange(5, dtype=np.float64) return a @@ -318,13 +318,13 @@ def make_array(): # External numpy storage borrowed by Sidre must stay alive with the C++ View # --------------------------------------------------------------------------- def test_create_view_external_array_owner_survives_discarded_proxy(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() def create_external_view(): external = np.arange(6, dtype=np.int64) ref = weakref.ref(external) - root.createView("external", external).apply(pysidre.TypeID.INT64_ID, 6) + root.createView("external", external).apply(sidre.TypeID.INT64_ID, 6) return ref ref = create_external_view() @@ -334,14 +334,14 @@ def create_external_view(): def test_create_view_with_shape_external_array_owner_survives_discarded_proxy(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() shape = np.array([2, 3]) def create_external_view(): external = np.arange(6, dtype=np.int64) ref = weakref.ref(external) - root.createViewWithShape("shaped", pysidre.TypeID.INT64_ID, 2, shape, external) + root.createViewWithShape("shaped", sidre.TypeID.INT64_ID, 2, shape, external) return ref ref = create_external_view() @@ -351,14 +351,14 @@ def create_external_view(): def test_set_external_data_array_owner_survives_discarded_proxy(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() def set_external_data(): view = root.createView("external") external = np.arange(6, dtype=np.int64) ref = weakref.ref(external) - view.setExternalData(pysidre.TypeID.INT64_ID, 6, external) + view.setExternalData(sidre.TypeID.INT64_ID, 6, external) return ref ref = set_external_data() @@ -368,7 +368,7 @@ def set_external_data(): def test_set_external_data_with_shape_array_owner_survives_discarded_proxy(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() shape = np.array([2, 3]) @@ -376,7 +376,7 @@ def set_external_data(): view = root.createView("shaped") external = np.arange(6, dtype=np.int64) ref = weakref.ref(external) - view.setExternalData(pysidre.TypeID.INT64_ID, 2, shape, external) + view.setExternalData(sidre.TypeID.INT64_ID, 2, shape, external) return ref ref = set_external_data() @@ -386,11 +386,11 @@ def set_external_data(): def test_clear_releases_external_array_owner(): - ds = pysidre.DataStore() + ds = sidre.DataStore() view = ds.getRoot().createView("external") external = np.arange(6, dtype=np.int64) ref = weakref.ref(external) - view.setExternalData(pysidre.TypeID.INT64_ID, 6, external) + view.setExternalData(sidre.TypeID.INT64_ID, 6, external) del external _force_gc() assert ref() is not None @@ -402,7 +402,7 @@ def test_clear_releases_external_array_owner(): def test_set_external_data_none_clears_and_releases_pin(): """setExternalData(None) clears the external pointer and releases the pin.""" - ds = pysidre.DataStore() + ds = sidre.DataStore() view = ds.getRoot().createView("external") external = np.arange(6, dtype=np.int64) @@ -422,7 +422,7 @@ def test_set_external_data_none_clears_and_releases_pin(): def test_set_external_data_undescribed_array_pins(): """The single-argument setExternalData(array) overload pins the array.""" - ds = pysidre.DataStore() + ds = sidre.DataStore() view = ds.getRoot().createView("external") def assign(): @@ -444,7 +444,7 @@ def test_set_external_data_rejects_non_array_argument(): 'incompatible function arguments' rather than throwing from an internal cast, so callers get the standard overload-resolution diagnostic. """ - ds = pysidre.DataStore() + ds = sidre.DataStore() view = ds.getRoot().createView("external") with pytest.raises(TypeError): view.setExternalData("not an array") @@ -454,7 +454,7 @@ def test_set_external_data_rejects_non_array_argument(): def test_copy_view_with_external_data_preserves_pin(): """copyView on an external View should copy the pin to prevent premature collection.""" - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() src_group = root.createGroup("src") dst_group = root.createGroup("dst") @@ -463,7 +463,7 @@ def test_copy_view_with_external_data_preserves_pin(): external = np.arange(10, dtype=np.int64) ref = weakref.ref(external) src_view = src_group.createView("original", external) - src_view.apply(pysidre.TypeID.INT64_ID, 10) + src_view.apply(sidre.TypeID.INT64_ID, 10) del external _force_gc() assert ref() is not None # Pin keeps it alive @@ -487,7 +487,7 @@ def test_copy_view_with_external_data_preserves_pin(): def test_copy_group_with_external_data_preserves_pins(): """copyGroup should recursively copy pins for all external Views in hierarchy.""" - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() src = root.createGroup("source") @@ -497,9 +497,9 @@ def test_copy_group_with_external_data_preserves_pins(): ref1 = weakref.ref(external1) ref2 = weakref.ref(external2) - src.createView("view1", external1).apply(pysidre.TypeID.INT32_ID, 5) + src.createView("view1", external1).apply(sidre.TypeID.INT32_ID, 5) child = src.createGroup("child") - child.createView("view2", external2).apply(pysidre.TypeID.INT64_ID, 8) + child.createView("view2", external2).apply(sidre.TypeID.INT64_ID, 8) del external1 del external2 @@ -534,7 +534,7 @@ def test_copy_group_with_external_data_preserves_pins(): def test_move_view_with_external_data_preserves_pin(): """moveView should preserve the pin since the View* pointer doesn't change.""" - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() src = root.createGroup("src") dst = root.createGroup("dst") @@ -543,7 +543,7 @@ def test_move_view_with_external_data_preserves_pin(): external = np.arange(12, dtype=np.int64) ref = weakref.ref(external) view = src.createView("moveable", external) - view.apply(pysidre.TypeID.INT64_ID, 12) + view.apply(sidre.TypeID.INT64_ID, 12) del external _force_gc() assert ref() is not None # Pin keeps it alive @@ -570,13 +570,13 @@ def test_move_view_with_external_data_preserves_pin(): def test_destroy_view_by_index_releases_external_pin(): """destroyView(IndexType) should release the external data pin.""" - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() external = np.arange(7, dtype=np.int32) ref = weakref.ref(external) view = root.createView("indexed", external) - view.apply(pysidre.TypeID.INT32_ID, 7) + view.apply(sidre.TypeID.INT32_ID, 7) view_idx = view.getIndex() del external del view @@ -591,16 +591,16 @@ def test_destroy_view_by_index_releases_external_pin(): def test_pin_overwrite_warning(): """Setting external data twice on the same View correctly replaces the pin.""" - ds = pysidre.DataStore() + ds = sidre.DataStore() view = ds.getRoot().createView("test") # First external data external1 = np.arange(5, dtype=np.int32) - view.setExternalData(pysidre.TypeID.INT32_ID, 5, external1) + view.setExternalData(sidre.TypeID.INT32_ID, 5, external1) # Second external data on same view - old pin released, new pin created external2 = np.arange(10, dtype=np.int64) - view.setExternalData(pysidre.TypeID.INT64_ID, 10, external2) + view.setExternalData(sidre.TypeID.INT64_ID, 10, external2) # The second pin should be active; first pin was automatically released np.testing.assert_array_equal(view.getDataArray(), external2) @@ -608,7 +608,7 @@ def test_pin_overwrite_warning(): def test_registry_cleanup_on_explicit_destroy(): """Pins are released when Views are explicitly destroyed, preventing registry bloat.""" - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() weak_refs = [] @@ -616,7 +616,7 @@ def test_registry_cleanup_on_explicit_destroy(): external = np.arange(10, dtype=np.int32) weak_refs.append(weakref.ref(external)) view = root.createView(f"view_{i}") - view.setExternalData(pysidre.TypeID.INT32_ID, 10, external) + view.setExternalData(sidre.TypeID.INT32_ID, 10, external) del external # Pin keeps it alive del view # Don't hold view reference @@ -644,16 +644,16 @@ def test_external_pins_released_when_datastore_destroyed(): weak_refs = [] def build_and_drop(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() for i in range(5): external = np.arange(10, dtype=np.int32) weak_refs.append(weakref.ref(external)) # Mix createView(external) and setExternalData() entry points. if i % 2 == 0: - root.createView(f"view_{i}", external).apply(pysidre.TypeID.INT32_ID, 10) + root.createView(f"view_{i}", external).apply(sidre.TypeID.INT32_ID, 10) else: - root.createView(f"view_{i}").setExternalData(pysidre.TypeID.INT32_ID, 10, external) + root.createView(f"view_{i}").setExternalData(sidre.TypeID.INT32_ID, 10, external) # Pins keep the arrays alive while ds is alive... gc.collect() assert all(ref() is not None for ref in weak_refs) @@ -672,13 +672,13 @@ def test_external_pins_released_for_nested_groups_on_datastore_destruction(): weak_refs = [] def build_and_drop(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() grp = root.createGroup("a/b/c") for i in range(3): external = np.arange(8, dtype=np.int64) weak_refs.append(weakref.ref(external)) - grp.createView(f"deep_{i}", external).apply(pysidre.TypeID.INT64_ID, 8) + grp.createView(f"deep_{i}", external).apply(sidre.TypeID.INT64_ID, 8) gc.collect() assert all(ref() is not None for ref in weak_refs) @@ -701,10 +701,10 @@ def test_external_pins_isolated_between_datastores(): # Build and drop several DataStores in sequence, encouraging View* reuse. for _ in range(4): - ds = pysidre.DataStore() + ds = sidre.DataStore() a = np.arange(6, dtype=np.int64) r = weakref.ref(a) - ds.getRoot().createView("v", a).apply(pysidre.TypeID.INT64_ID, 6) + ds.getRoot().createView("v", a).apply(sidre.TypeID.INT64_ID, 6) del a, ds _force_gc() # Each dropped DataStore must release its own array. @@ -712,10 +712,10 @@ def test_external_pins_isolated_between_datastores(): # A long-lived DataStore created afterwards (possibly at a reused address) # must hold its own pin independently. - survivor = pysidre.DataStore() + survivor = sidre.DataStore() b = np.arange(6, dtype=np.int64) surviving_refs.append(weakref.ref(b)) - survivor.getRoot().createView("v", b).apply(pysidre.TypeID.INT64_ID, 6) + survivor.getRoot().createView("v", b).apply(sidre.TypeID.INT64_ID, 6) keep_alive.append(survivor) del b _force_gc() @@ -737,7 +737,7 @@ def test_multiple_concurrent_datastores(): arrays = [] for ds_idx in range(3): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() datastores.append(ds) @@ -748,7 +748,7 @@ def test_multiple_concurrent_datastores(): external = np.arange(view_idx * 10, (view_idx + 1) * 10, dtype=np.int32) ds_arrays.append(external) view = root.createView(f"view_{view_idx}") - view.setExternalData(pysidre.TypeID.INT32_ID, 10, external) + view.setExternalData(sidre.TypeID.INT32_ID, 10, external) ds_views.append(view) views.append(ds_views) @@ -791,11 +791,11 @@ def test_multiple_concurrent_datastores(): err_msg=f"After DS0 destroy: {ds_label} view{view_idx} data mismatch") # Create a new DataStore and verify it doesn't conflict - ds_new = pysidre.DataStore() + ds_new = sidre.DataStore() root_new = ds_new.getRoot() external_new = np.arange(100, 110, dtype=np.int32) view_new = root_new.createView("new_view") - view_new.setExternalData(pysidre.TypeID.INT32_ID, 10, external_new) + view_new.setExternalData(sidre.TypeID.INT32_ID, 10, external_new) # Verify new DataStore works np.testing.assert_array_equal(view_new.getDataArray(), external_new) @@ -815,8 +815,8 @@ def test_multiple_concurrent_datastores(): def test_concurrent_datastores_with_copy_move(): """Copy/move operations should work correctly with multiple concurrent DataStores.""" - ds1 = pysidre.DataStore() - ds2 = pysidre.DataStore() + ds1 = sidre.DataStore() + ds2 = sidre.DataStore() root1 = ds1.getRoot() root2 = ds2.getRoot() @@ -824,7 +824,7 @@ def test_concurrent_datastores_with_copy_move(): # Create view with external data in DS1 external1 = np.arange(10, dtype=np.int32) view1 = root1.createView("src") - view1.setExternalData(pysidre.TypeID.INT32_ID, 10, external1) + view1.setExternalData(sidre.TypeID.INT32_ID, 10, external1) # Copy view to a group in DS1 grp1 = root1.createGroup("grp1") @@ -834,7 +834,7 @@ def test_concurrent_datastores_with_copy_move(): # Create view with different external data in DS2 external2 = np.arange(20, 30, dtype=np.int64) view2 = root2.createView("other") - view2.setExternalData(pysidre.TypeID.INT64_ID, 10, external2) + view2.setExternalData(sidre.TypeID.INT64_ID, 10, external2) # Verify both DataStores maintain correct data np.testing.assert_array_equal(view1.getDataArray(), external1) @@ -852,8 +852,8 @@ def test_concurrent_datastores_with_copy_move(): def test_concurrent_datastores_registry_isolation(): """Registry should correctly isolate pins between different DataStores.""" - ds1 = pysidre.DataStore() - ds2 = pysidre.DataStore() + ds1 = sidre.DataStore() + ds2 = sidre.DataStore() external1 = np.arange(5, dtype=np.int32) external2 = np.arange(5, dtype=np.int64) @@ -863,10 +863,10 @@ def test_concurrent_datastores_registry_isolation(): # Both DataStores use external data view1 = ds1.getRoot().createView("v1") - view1.setExternalData(pysidre.TypeID.INT32_ID, 5, external1) + view1.setExternalData(sidre.TypeID.INT32_ID, 5, external1) view2 = ds2.getRoot().createView("v2") - view2.setExternalData(pysidre.TypeID.INT64_ID, 5, external2) + view2.setExternalData(sidre.TypeID.INT64_ID, 5, external2) del external1, external2 # Only pins keep them alive _force_gc() diff --git a/src/axom/sidre/tests/sidre_smoke_Py.py b/src/axom/sidre/tests/sidre_smoke_Py.py index 59a6385f01..0bb6cc8c67 100644 --- a/src/axom/sidre/tests/sidre_smoke_Py.py +++ b/src/axom/sidre/tests/sidre_smoke_Py.py @@ -4,28 +4,28 @@ # # SPDX-License-Identifier: (BSD-3-Clause) -import axom.sidre as pysidre +import axom.sidre as sidre from conduit import Node # Python automatically calls destructor during garbage collection def test_create_datastore(): - ds = pysidre.DataStore() + ds = sidre.DataStore() assert True def test_valid_invalid(): - ds = pysidre.DataStore() + ds = sidre.DataStore() idx = 3 - assert idx != pysidre.InvalidIndex + assert idx != sidre.InvalidIndex name = "foo" - assert pysidre.nameIsValid(name) + assert sidre.nameIsValid(name) root = ds.getRoot() - assert root.getGroupName(idx) == pysidre.InvalidName - assert root.getGroupIndex(name) == pysidre.InvalidIndex + assert root.getGroupName(idx) == sidre.InvalidName + assert root.getGroupIndex(name) == sidre.InvalidIndex def test_conduit_in_sidre_smoke(): diff --git a/src/axom/sidre/tests/sidre_spio_Py.py b/src/axom/sidre/tests/sidre_spio_Py.py index 0a8cef6e01..e8649c459d 100644 --- a/src/axom/sidre/tests/sidre_spio_Py.py +++ b/src/axom/sidre/tests/sidre_spio_Py.py @@ -17,10 +17,10 @@ import pytest -import axom.sidre as pysidre +import axom.sidre as sidre -if not pysidre.AXOM_ENABLE_MPI: - pytest.skip("pysidre built without MPI", allow_module_level=True) +if not sidre.AXOM_ENABLE_MPI: + pytest.skip("sidre built without MPI", allow_module_level=True) mpi4py = pytest.importorskip("mpi4py") from mpi4py import MPI # noqa: E402 @@ -33,9 +33,9 @@ def _shared_base(tmp_path, name): def _fill_datastore(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() - view = root.createViewAndAllocate("field", pysidre.TypeID.INT_ID, 4) + view = root.createViewAndAllocate("field", sidre.TypeID.INT_ID, 4) rank = MPI.COMM_WORLD.Get_rank() view.getDataArray()[:] = [rank, rank + 1, rank + 2, rank + 3] return ds @@ -43,21 +43,21 @@ def _fill_datastore(): def test_iomanager_legacy_use_scr_constructor(): # Preserve the previous IOManager(use_scr=False) positional API. - pysidre.IOManager(False) + sidre.IOManager(False) def test_iomanager_rejects_non_communicator(): with pytest.raises(AttributeError): - pysidre.IOManager(object()) + sidre.IOManager(object()) def test_iomanager_default_communicator(tmp_path): # No communicator argument -> MPI_COMM_WORLD (preserves the prior behavior). world = MPI.COMM_WORLD ds = _fill_datastore() - iom = pysidre.IOManager() + iom = sidre.IOManager() base = _shared_base(tmp_path, "default_comm") - iom.write(ds.getRoot(), 1, base, pysidre.Group.getDefaultIOProtocol()) + iom.write(ds.getRoot(), 1, base, sidre.Group.getDefaultIOProtocol()) world.Barrier() assert iom.getNumFilesFromRoot(base + ".root") == 1 assert iom.getNumGroupsFromRoot(base + ".root") == world.Get_size() @@ -67,12 +67,12 @@ def test_iomanager_explicit_world_communicator(tmp_path): # Passing COMM_WORLD explicitly must match the default path. world = MPI.COMM_WORLD ds = _fill_datastore() - iom = pysidre.IOManager(MPI.COMM_WORLD) + iom = sidre.IOManager(MPI.COMM_WORLD) base = _shared_base(tmp_path, "explicit_world") - iom.write(ds.getRoot(), 1, base, pysidre.Group.getDefaultIOProtocol()) + iom.write(ds.getRoot(), 1, base, sidre.Group.getDefaultIOProtocol()) world.Barrier() - ds_in = pysidre.DataStore() + ds_in = sidre.DataStore() iom.read(ds_in.getRoot(), base + ".root") arr = ds_in.getRoot().getView("field").getDataArray() rank = world.Get_rank() @@ -83,12 +83,12 @@ def test_iomanager_owned_duplicate_survives_comm_free(tmp_path): # IOManager duplicates the input communicator, so callers may free their # mpi4py communicator after construction. comm = MPI.COMM_SELF.Dup() - iom = pysidre.IOManager(comm) + iom = sidre.IOManager(comm) comm.Free() ds = _fill_datastore() base = _shared_base(tmp_path, f"freed_comm_rank{MPI.COMM_WORLD.Get_rank()}") - iom.write(ds.getRoot(), 1, base, pysidre.Group.getDefaultIOProtocol()) + iom.write(ds.getRoot(), 1, base, sidre.Group.getDefaultIOProtocol()) assert iom.getNumFilesFromRoot(base + ".root") == 1 assert iom.getNumGroupsFromRoot(base + ".root") == 1 MPI.COMM_WORLD.Barrier() @@ -106,12 +106,12 @@ def test_iomanager_split_communicator(tmp_path): try: sub_size = sub.Get_size() ds = _fill_datastore() - iom = pysidre.IOManager(sub) + iom = sidre.IOManager(sub) sub.Free() sub_freed = True # tmp_path differs per rank; rendezvous on a shared, rank-0-broadcast dir base = _shared_base(tmp_path, f"split_color{color}") - iom.write(ds.getRoot(), 1, base, pysidre.Group.getDefaultIOProtocol()) + iom.write(ds.getRoot(), 1, base, sidre.Group.getDefaultIOProtocol()) assert iom.getNumFilesFromRoot(base + ".root") == 1 assert iom.getNumGroupsFromRoot(base + ".root") == sub_size finally: @@ -122,10 +122,10 @@ def test_iomanager_split_communicator(tmp_path): def test_distributed_generate_blueprint_index(tmp_path): # The distributed generateBlueprintIndex overload is built only under # nanobind >= 2.10; skip cleanly if this build omitted it. - if not pysidre.AXOM_HAS_DISTRIBUTED_BLUEPRINT_INDEX_BINDING: + if not sidre.AXOM_HAS_DISTRIBUTED_BLUEPRINT_INDEX_BINDING: pytest.skip("generateBlueprintIndex not bound") - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() mesh = root.createGroup("mesh") coords = mesh.createGroup("coordsets/coords") diff --git a/src/axom/sidre/tests/sidre_view_Py.py b/src/axom/sidre/tests/sidre_view_Py.py index 93558b7781..bae6074582 100644 --- a/src/axom/sidre/tests/sidre_view_Py.py +++ b/src/axom/sidre/tests/sidre_view_Py.py @@ -4,7 +4,7 @@ # # SPDX-License-Identifier: (BSD-3-Clause) -import axom.sidre as pysidre +import axom.sidre as sidre import numpy as np NUM_BYTES_INT_32 = 4 @@ -54,11 +54,11 @@ def check_view_values(view, state, is_described, is_allocated, is_applied, lengt def test_create_views(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() - dv_0 = root.createViewAndAllocate("field0", pysidre.TypeID.INT_ID, 1) - dv_1 = root.createViewAndAllocate("field1", pysidre.TypeID.INT_ID, 1) + dv_0 = root.createViewAndAllocate("field0", sidre.TypeID.INT_ID, 1) + dv_1 = root.createViewAndAllocate("field1", sidre.TypeID.INT_ID, 1) db_0 = dv_0.getBuffer() db_1 = dv_1.getBuffer() @@ -68,7 +68,7 @@ def test_create_views(): def test_get_path_name(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() v1 = root.createView("test/a/b/v1") @@ -89,7 +89,7 @@ def test_get_path_name(): def test_create_view_from_path(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() baz = root.createView("foo/bar/baz") @@ -120,33 +120,33 @@ def check_scalar_values(view, state, is_described, is_allocated, is_applied, typ assert ndims == 1, f"{name} getShape" assert dims[0] == length, f"{name} dims[0]" - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() i1 = 1 i0view = root.createView("i0") i0view.setScalar(i1) - check_scalar_values(i0view, SCALARVIEW, True, True, True, pysidre.TypeID.INT32_ID, 1) + check_scalar_values(i0view, SCALARVIEW, True, True, True, sidre.TypeID.INT32_ID, 1) i2 = i0view.getDataInt() assert i1 == i2 i1 = 2 i1view = root.createViewScalar("i1", i1) - check_scalar_values(i1view, SCALARVIEW, True, True, True, pysidre.TypeID.INT32_ID, 1) + check_scalar_values(i1view, SCALARVIEW, True, True, True, sidre.TypeID.INT32_ID, 1) i2 = i1view.getDataInt() assert i1 == i2 s1 = "i am a string" s0view = root.createView("s0") s0view.setString(s1) - check_scalar_values(s0view, STRINGVIEW, True, True, True, pysidre.TypeID.CHAR8_STR_ID, + check_scalar_values(s0view, STRINGVIEW, True, True, True, sidre.TypeID.CHAR8_STR_ID, len(s1) + 1) s2 = s0view.getString() assert s1 == s2 s1 = "i too am a string" s1view = root.createViewString("s1", s1) - check_scalar_values(s1view, STRINGVIEW, True, True, True, pysidre.TypeID.CHAR8_STR_ID, + check_scalar_values(s1view, STRINGVIEW, True, True, True, sidre.TypeID.CHAR8_STR_ID, len(s1) + 1) s2 = s1view.getString() assert s1 == s2 @@ -162,10 +162,10 @@ def check_scalar_values(view, state, is_described, is_allocated, is_applied, typ def test_int_buffer_from_view(): elem_count = 10 - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() - dv = root.createViewAndAllocate("u0", pysidre.TypeID.INT32_ID, elem_count) + dv = root.createViewAndAllocate("u0", sidre.TypeID.INT32_ID, elem_count) data = dv.getDataArray() for i in range(elem_count): @@ -178,13 +178,13 @@ def test_int_buffer_from_view(): def test_view_dtype_support(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() dtype_pairs = [ - (pysidre.TypeID.INT8_ID, np.int8), - (pysidre.TypeID.UINT16_ID, np.uint16), - (pysidre.TypeID.FLOAT32_ID, np.float32), + (sidre.TypeID.INT8_ID, np.int8), + (sidre.TypeID.UINT16_ID, np.uint16), + (sidre.TypeID.FLOAT32_ID, np.float32), ] for idx, (type_id, expected_dtype) in enumerate(dtype_pairs): @@ -193,23 +193,23 @@ def test_view_dtype_support(): def test_detach_external_and_attach_buffer(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() external = np.array([1, 2], dtype=np.int32) - view = root.createView("external", pysidre.TypeID.INT32_ID, 2, external) + view = root.createView("external", sidre.TypeID.INT32_ID, 2, external) assert view.isExternal() assert not view.hasBuffer() view.setExternalData(None) assert view.isEmpty() - replacement = ds.createBuffer(pysidre.TypeID.INT32_ID, 4) + replacement = ds.createBuffer(sidre.TypeID.INT32_ID, 4) replacement.allocate() replacement_data = replacement.getDataArray() replacement_data[:] = [3, 4, 5, 6] - view.attachBuffer(pysidre.TypeID.INT32_ID, 4, replacement) + view.attachBuffer(sidre.TypeID.INT32_ID, 4, replacement) assert view.hasBuffer() assert not view.isExternal() @@ -217,34 +217,34 @@ def test_detach_external_and_attach_buffer(): def test_detach_buffer_and_attach_buffer(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() - original = ds.createBuffer(pysidre.TypeID.INT32_ID, 2) + original = ds.createBuffer(sidre.TypeID.INT32_ID, 2) original.allocate() original.getDataArray()[:] = [1, 2] - view = root.createView("buffered", pysidre.TypeID.INT32_ID, 2, original) + view = root.createView("buffered", sidre.TypeID.INT32_ID, 2, original) assert view.hasBuffer() view.attachBuffer(None) assert view.isEmpty() assert not view.hasBuffer() - replacement = ds.createBuffer(pysidre.TypeID.INT32_ID, 4) + replacement = ds.createBuffer(sidre.TypeID.INT32_ID, 4) replacement.allocate() replacement.getDataArray()[:] = [3, 4, 5, 6] - view.attachBuffer(pysidre.TypeID.INT32_ID, 4, replacement) + view.attachBuffer(sidre.TypeID.INT32_ID, 4, replacement) assert view.hasBuffer() assert list(view.getDataArray()) == [3, 4, 5, 6] def test_int_array_multi_view(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() - dbuff = ds.createBuffer(pysidre.TypeID.INT32_ID, 10) + dbuff = ds.createBuffer(sidre.TypeID.INT32_ID, 10) dbuff.allocate() data = dbuff.getDataArray() @@ -281,11 +281,11 @@ def test_int_array_multi_view(): def test_init_int_array_multi_view(): - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() dbuff = ds.createBuffer() - dbuff.allocate(pysidre.TypeID.INT32_ID, 10) + dbuff.allocate(sidre.TypeID.INT32_ID, 10) data = dbuff.getDataArray() for i in range(10): @@ -319,11 +319,11 @@ def test_init_int_array_multi_view(): def test_int_array_depth_view(): - ds = pysidre.DataStore() + ds = sidre.DataStore() depth_nelems = 10 total_nelems = 4 * depth_nelems - dbuff = ds.createBuffer(pysidre.TypeID.INT32_ID, total_nelems) + dbuff = ds.createBuffer(sidre.TypeID.INT32_ID, total_nelems) # Get access to our root data Group root = ds.getRoot() @@ -375,7 +375,7 @@ def test_int_array_depth_view(): def test_int_array_view_attach_buffer(): # Create our main data store - ds = pysidre.DataStore() + ds = sidre.DataStore() # Get access to our root data Group root = ds.getRoot() @@ -384,17 +384,17 @@ def test_int_array_view_attach_buffer(): # Create 2 "field" views with type and # elems elem_count = 0 - field0 = root.createView("field0", pysidre.TypeID.INT32_ID, field_nelems) + field0 = root.createView("field0", sidre.TypeID.INT32_ID, field_nelems) elem_count = elem_count + field0.getNumElements() print(f"elem_count field0 {elem_count}") - field1 = root.createView("field1", pysidre.TypeID.INT32_ID, field_nelems) + field1 = root.createView("field1", sidre.TypeID.INT32_ID, field_nelems) elem_count = elem_count + field1.getNumElements() print(f"elem_count field1 {elem_count}") assert elem_count == 2 * field_nelems # Create buffer to hold data for all fields and allocate - dbuff = ds.createBuffer(pysidre.TypeID.INT32_ID, elem_count) + dbuff = ds.createBuffer(sidre.TypeID.INT32_ID, elem_count) dbuff.allocate() assert dbuff.getNumElements() == elem_count @@ -435,13 +435,13 @@ def test_int_array_view_attach_buffer(): def test_int_array_offset_stride(): # create our main data store - ds = pysidre.DataStore() + ds = sidre.DataStore() # get access to our root data Group root = ds.getRoot() field_nelems = 20 - field0 = root.createViewAndAllocate("field0", pysidre.TypeID.DOUBLE_ID, field_nelems) + field0 = root.createViewAndAllocate("field0", sidre.TypeID.DOUBLE_ID, field_nelems) assert field0.getNumElements() == field_nelems assert field0.getBytesPerElement() == NUM_BYTES_DOUBLE assert field0.getTotalBytes() == NUM_BYTES_DOUBLE * field_nelems @@ -543,7 +543,7 @@ def test_int_array_multi_view_resize(): # into the new views # Create our main data store - ds = pysidre.DataStore() + ds = sidre.DataStore() # Get access to our root data Group root = ds.getRoot() @@ -553,7 +553,7 @@ def test_int_array_multi_view_resize(): # Create a view to hold the base buffer and allocate # we will create 4 sub views of this array - base_old = r_old.createViewAndAllocate("base_data", pysidre.TypeID.INT32_ID, 40) + base_old = r_old.createViewAndAllocate("base_data", sidre.TypeID.INT32_ID, 40) # Init the buff with values that align with the 4 subsections data = base_old.getDataArray() @@ -596,7 +596,7 @@ def test_int_array_multi_view_resize(): # Create a view to hold the base buffer base_new = r_new.createView("base_data") - base_new.allocate(pysidre.TypeID.INT32_ID, 48) + base_new.allocate(sidre.TypeID.INT32_ID, 48) base_new_data = base_new.getDataArray() for i in range(48): base_new_data[i] = 0 @@ -657,13 +657,13 @@ def test_int_array_multi_view_resize(): def test_int_array_realloc(): # Create our main data store - ds = pysidre.DataStore() + ds = sidre.DataStore() # Get access to our root data Group root = ds.getRoot() - a1 = root.createViewAndAllocate("a1", pysidre.TypeID.DOUBLE_ID, 5) - a2 = root.createViewAndAllocate("a2", pysidre.TypeID.DOUBLE_ID, 5) + a1 = root.createViewAndAllocate("a1", sidre.TypeID.DOUBLE_ID, 5) + a2 = root.createViewAndAllocate("a2", sidre.TypeID.DOUBLE_ID, 5) a1_data = a1.getDataArray() a2_data = a2.getDataArray() @@ -699,7 +699,7 @@ def test_int_array_realloc(): def test_simple_opaque(): # Create our main data store - ds = pysidre.DataStore() + ds = sidre.DataStore() # Get access to our root data Group root = ds.getRoot() @@ -713,10 +713,10 @@ def test_simple_opaque(): assert opq_view.isExternal() == True assert opq_view.isApplied() == False assert opq_view.isOpaque() == True - assert opq_view.getTypeID() == pysidre.TypeID.NO_TYPE_ID + assert opq_view.getTypeID() == sidre.TypeID.NO_TYPE_ID # Apply type to get data - opq_view.apply(pysidre.TypeID.INT32_ID, 1) + opq_view.apply(sidre.TypeID.INT32_ID, 1) opq_data = opq_view.getDataArray() assert opq_data[0] == 42 @@ -725,7 +725,7 @@ def test_simple_opaque(): def test_clear_view(): BLEN = 10 - ds = pysidre.DataStore() + ds = sidre.DataStore() root = ds.getRoot() # Create an empty view @@ -735,7 +735,7 @@ def test_clear_view(): check_view_values(view, EMPTYVIEW, False, False, False, 0) # Describe an empty view - view = root.createView("v_described", pysidre.TypeID.INT32_ID, BLEN) + view = root.createView("v_described", sidre.TypeID.INT32_ID, BLEN) check_view_values(view, EMPTYVIEW, True, False, False, BLEN) view.clear() check_view_values(view, EMPTYVIEW, False, False, False, 0) @@ -753,7 +753,7 @@ def test_clear_view(): # Allocated view, Buffer will be released nbuf = ds.getNumBuffers() - view = root.createViewAndAllocate("v_allocated", pysidre.TypeID.INT32_ID, BLEN) + view = root.createViewAndAllocate("v_allocated", sidre.TypeID.INT32_ID, BLEN) check_view_values(view, BUFFERVIEW, True, True, True, BLEN) view.clear() check_view_values(view, EMPTYVIEW, False, False, False, 0) @@ -770,12 +770,12 @@ def test_clear_view(): # Explicit buffer attached to two views dbuff = ds.createBuffer() - dbuff.allocate(pysidre.TypeID.INT32_ID, BLEN) + dbuff.allocate(sidre.TypeID.INT32_ID, BLEN) nbuf = ds.getNumBuffers() assert dbuff.getNumViews() == 0 - vother = root.createView("v_other", pysidre.TypeID.INT32_ID, BLEN) - view = root.createView("v_buffer", pysidre.TypeID.INT32_ID, BLEN) + vother = root.createView("v_other", sidre.TypeID.INT32_ID, BLEN) + view = root.createView("v_buffer", sidre.TypeID.INT32_ID, BLEN) vother.attachBuffer(dbuff) assert dbuff.getNumViews() == 1 view.attachBuffer(dbuff) @@ -790,7 +790,7 @@ def test_clear_view(): # External View ext_data = np.array(BLEN) - view = root.createView("v_external", pysidre.TypeID.INT32_ID, BLEN, ext_data) + view = root.createView("v_external", sidre.TypeID.INT32_ID, BLEN, ext_data) check_view_values(view, EXTERNALVIEW, True, True, True, BLEN) view.clear() check_view_values(view, EMPTYVIEW, False, False, False, 0) diff --git a/src/tools/convert_sidre_protocol.py b/src/tools/convert_sidre_protocol.py index 2c1b82a4e8..4ec3c82dfa 100644 --- a/src/tools/convert_sidre_protocol.py +++ b/src/tools/convert_sidre_protocol.py @@ -29,7 +29,7 @@ from pathlib import Path import numpy as np -import axom.sidre as pysidre +import axom.sidre as sidre VALID_PROTOCOLS = ( "json", @@ -88,7 +88,7 @@ def parse_args() -> argparse.Namespace: # # Also initializes the data in each allocated array to zeros. # -def allocate_external_data(group: pysidre.Group, holders: list[np.ndarray], verbose: bool) -> None: +def allocate_external_data(group: sidre.Group, holders: list[np.ndarray], verbose: bool) -> None: # for each view for view in group.views(): if view.isExternal(): @@ -117,7 +117,7 @@ def allocate_external_data(group: pysidre.Group, holders: list[np.ndarray], verb # several views in the original dataset pointing to the same memory. # def modify_final_values( - view: pysidre.View, + view: sidre.View, original_size: int, retained_size: int | None = None, ) -> None: @@ -165,7 +165,7 @@ def modify_final_values( # This will be followed by at most the first max_size elements of the # original array. # -def truncate_bulk_data(group: pysidre.Group, max_size: int, verbose: bool) -> None: +def truncate_bulk_data(group: sidre.Group, max_size: int, verbose: bool) -> None: # for each view for view in group.views(): is_array = view.hasBuffer() or view.isExternal() @@ -194,8 +194,8 @@ def truncate_bulk_data(group: pysidre.Group, max_size: int, verbose: bool) -> No def main() -> int: args = parse_args() - if not pysidre.AXOM_ENABLE_MPI: - raise RuntimeError("pysidre.IOManager bindings require an MPI-enabled Axom build") + if not sidre.AXOM_ENABLE_MPI: + raise RuntimeError("sidre.IOManager bindings require an MPI-enabled Axom build") try: from mpi4py import MPI @@ -212,8 +212,8 @@ def main() -> int: comm_size = MPI.COMM_WORLD.Get_size() input_path = Path(args.input) - manager = pysidre.IOManager() - datastore = pysidre.DataStore() + manager = sidre.IOManager() + datastore = sidre.DataStore() root = datastore.getRoot() num_files = manager.getNumFilesFromRoot(str(input_path)) From 279e34b62a2152a9a3b482176473111f943c90d6 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 15 Jul 2026 16:44:37 -0700 Subject: [PATCH 739/986] Updates RELEASE-NOTES --- RELEASE-NOTES.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index d8e4534c70..86d22a0314 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -49,13 +49,14 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ - Slam: Adds `make_*_set`, `make_*_relation` and `make_map` helper functions for building sets, relations and maps - Primal: Adds `primal::Sphere::contains(const Point&, bool includeBoundary = true)` to efficiently test whether a point lies within a sphere. Use `getOrientation()` when a tolerance-aware boundary classification is needed. +- Python: Adds the `AXOM_PYTHON_MODULE_INSTALL_PREFIX` CMake variable to control where Axom installs its Python + package(s), relative to the install prefix. ### Removed - Bump: Removed `axom::bump::views::MultiBufferMaterialView`, which was a view type for an obsolete flavor of Blueprint matset. ### Deprecated - Core: Deprecates the pointer-based interface to linear-, quadratic- and cubic- polynomial solvers in favor of an ArrayView-based interface -- Python: The top-level `pysidre` module is deprecated in favor of `axom.sidre`. ### Changed - Updates CMake code check targets to only use checked in files (via `git ls-files`, when available) @@ -67,6 +68,7 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ - Python: Removes build and test dependencies from `run_python_with_axom.sh` wrapper script - Changed to `#pragma once` instead of unique header guard defines - Python: Sidre's bindings now install under the `axom` Python package (`import axom.sidre`) + Code that previously imported `pysidre` needs to be updated to `axom.sidre`. ### Fixed - Primal: Fixes signs of `compute_moments` to match orientation convention in `primal::evaluate_area_integral` From 16c6a1ac252d66c5760a684c0fece56ae9ad47cd Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 15 Jul 2026 19:01:54 -0700 Subject: [PATCH 740/986] Bugfix: We weren't passing in the right parallel flag for `make test` in CI --- scripts/github-actions/linux-build_and_test.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/github-actions/linux-build_and_test.sh b/scripts/github-actions/linux-build_and_test.sh index ff2eabd8ab..9037f47912 100755 --- a/scripts/github-actions/linux-build_and_test.sh +++ b/scripts/github-actions/linux-build_and_test.sh @@ -30,8 +30,8 @@ export BUILD_TYPE=${BUILD_TYPE:-Debug} if [[ "$DO_BUILD" == "yes" ]] ; then echo "~~~~~~ FIND NUMPROCS ~~~~~~~~" - NUMPROCS=`python3 -c "import os; print(f'{os.cpu_count()}')"` - NUM_BUILD_PROCS=`python3 -c "import os; print(f'{max(2, os.cpu_count() * 8 // 10)}')"` + NUMPROCS=$(python3 -c 'import os; print(os.cpu_count())') + NUM_BUILD_PROCS=$(python3 -c 'import os; print(max(2, os.cpu_count() * 8 // 10))') echo "~~~~~~ RUNNING CMAKE ~~~~~~~~" or_die python3 ./config-build.py -bp builddir -hc ./host-configs/docker/${HOST_CONFIG} -bt ${BUILD_TYPE} -DENABLE_GTEST_DEATH_TESTS=ON ${CMAKE_EXTRA_FLAGS} @@ -39,13 +39,13 @@ if [[ "$DO_BUILD" == "yes" ]] ; then echo "~~~~~~ BUILDING ~~~~~~~~" if [[ ${CMAKE_EXTRA_FLAGS} == *COVERAGE* ]] ; then - or_die make -j $NUM_BUILD_PROCS + or_die make -j ${NUM_BUILD_PROCS} else - or_die make -j $NUM_BUILD_PROCS VERBOSE=1 + or_die make -j ${NUM_BUILD_PROCS} VERBOSE=1 fi echo "~~~~~~ RUNNING TESTS ~~~~~~~~" - or_die make CTEST_OUTPUT_ON_FAILURE=1 test ARGS='-T Test --output-on-failure -j$NUM_BUILD_PROCS' + or_die make CTEST_OUTPUT_ON_FAILURE=1 test ARGS="-T Test --output-on-failure -j${NUM_BUILD_PROCS}" if [[ "${DO_BENCHMARKS}" == "yes" ]] ; then echo "~~~~~~ RUNNING BENCHMARKS ~~~~~~~~" From 9d4b13839a3e62a114a822e534dee3d82c01f871 Mon Sep 17 00:00:00 2001 From: Kenny Weiss Date: Thu, 16 Jul 2026 00:01:00 -0700 Subject: [PATCH 741/986] Removes `or_die` bugfix from this branch to handle in a dedicated branch --- scripts/github-actions/linux-build_and_test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/github-actions/linux-build_and_test.sh b/scripts/github-actions/linux-build_and_test.sh index 9037f47912..fc7ad631f5 100755 --- a/scripts/github-actions/linux-build_and_test.sh +++ b/scripts/github-actions/linux-build_and_test.sh @@ -45,7 +45,7 @@ if [[ "$DO_BUILD" == "yes" ]] ; then fi echo "~~~~~~ RUNNING TESTS ~~~~~~~~" - or_die make CTEST_OUTPUT_ON_FAILURE=1 test ARGS="-T Test --output-on-failure -j${NUM_BUILD_PROCS}" + make CTEST_OUTPUT_ON_FAILURE=1 test ARGS="-T Test --output-on-failure -j${NUM_BUILD_PROCS}" if [[ "${DO_BENCHMARKS}" == "yes" ]] ; then echo "~~~~~~ RUNNING BENCHMARKS ~~~~~~~~" From 503395d08161b86b0758342b49bb41708f8845c1 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Fri, 17 Jul 2026 13:26:51 -0700 Subject: [PATCH 742/986] Add verbose to MIR options and use it to decide whether to call SLIC_INFO. The default setting is off. --- src/axom/bump/Options.hpp | 25 +++++++++++++++++++++++++ src/axom/mir/ElviraAlgorithm.hpp | 18 +++++++++++++----- src/axom/mir/EquiZAlgorithm.hpp | 8 ++++++-- src/axom/mir/detail/elvira_detail.hpp | 22 ++-------------------- src/axom/mir/tests/mir_elvira2d.cpp | 1 + src/axom/mir/tests/mir_elvira3d.cpp | 1 + src/axom/mir/tests/mir_equiz2d.cpp | 2 ++ src/axom/mir/tests/mir_equiz3d.cpp | 1 + 8 files changed, 51 insertions(+), 27 deletions(-) diff --git a/src/axom/bump/Options.hpp b/src/axom/bump/Options.hpp index 1230f3000f..062eeb9c41 100644 --- a/src/axom/bump/Options.hpp +++ b/src/axom/bump/Options.hpp @@ -130,6 +130,31 @@ class Options return name; } + /** + * \brief Get whether the algorithm should issue verbose output. + * \return True if the output should be verbose, false otherwise. + */ + bool verbose() const { return flagValue("verbose", false); } + +protected: + /** + * \brief Get whether the flag is set in the options. + * + * \param key The name of the key that contains the flag. + * \param defaultValue The default value for the flag. + * + * \return True if key is present and set to non-zero, false otherwise. + */ + bool flagValue(const std::string &key, bool defaultValue) const + { + bool retval = defaultValue; + if(options().has_path(key)) + { + retval = options().fetch_existing(key).to_int() != 0; + } + return retval; + } + protected: const conduit::Node &m_options; // A reference to the options node. }; diff --git a/src/axom/mir/ElviraAlgorithm.hpp b/src/axom/mir/ElviraAlgorithm.hpp index 579e82aeb4..19f1270654 100644 --- a/src/axom/mir/ElviraAlgorithm.hpp +++ b/src/axom/mir/ElviraAlgorithm.hpp @@ -185,6 +185,7 @@ class ElviraAlgorithm : public axom::mir::MIRAlgorithm "The mesh and the material do not have the same number of zones."); // Copy the options to make sure they are in the right memory space. + const ELVIRAOptions opts(n_options); conduit::Node n_options_copy; utils::copy(n_options_copy, n_options, getAllocatorID()); n_options_copy["topology"] = n_topo.name(); @@ -204,8 +205,11 @@ class ElviraAlgorithm : public axom::mir::MIRAlgorithm zlb.setAllocatorID(getAllocatorID()); zlb.execute(selectedZonesView, cleanZones, mixedZones); SLIC_ASSERT((cleanZones.size() + mixedZones.size()) == selectedZonesView.size()); - SLIC_INFO( - axom::fmt::format("cleanZones: {}, mixedZones: {}", cleanZones.size(), mixedZones.size())); + if(opts.verbose()) + { + SLIC_INFO( + axom::fmt::format("cleanZones: {}, mixedZones: {}", cleanZones.size(), mixedZones.size())); + } if(cleanZones.size() > 0 && mixedZones.size() > 0) { @@ -301,7 +305,7 @@ class ElviraAlgorithm : public axom::mir::MIRAlgorithm utils::copy(n_newMatset, n_matset); // Add an originalElements array. - const std::string originalElementsField(ELVIRAOptions(n_options).originalElementsField()); + const std::string originalElementsField(opts.originalElementsField()); addOriginal(n_newFields[originalElementsField], n_newTopo.name(), "element", cleanZones); } else @@ -533,6 +537,7 @@ class ElviraAlgorithm : public axom::mir::MIRAlgorithm // Handle options. // When coordinates have float value, we can't necessarily get beyond a // certain tolerance so set the tolerance accordingly. + const ELVIRAOptions opts(n_options); constexpr double DEFAULT_TOLERANCE = std::is_same::value ? (axom::numeric_limits::epsilon() * 4.f) : 1.e-10; @@ -607,8 +612,11 @@ class ElviraAlgorithm : public axom::mir::MIRAlgorithm const auto maxCuts = reduce_maxcuts.get(); SLIC_ASSERT(numFragments > 0); SLIC_ASSERT(maxCuts > 0); - SLIC_INFO( - axom::fmt::format("ElviraAlgorithm: numFragments: {}, maxCuts: {}", numFragments, maxCuts)); + if(opts.verbose()) + { + SLIC_INFO( + axom::fmt::format("ElviraAlgorithm: numFragments: {}, maxCuts: {}", numFragments, maxCuts)); + } #if defined(AXOM_ELVIRA_GATHER_INFO) if(!axom::execution_space::onDevice()) diff --git a/src/axom/mir/EquiZAlgorithm.hpp b/src/axom/mir/EquiZAlgorithm.hpp index c32f12a8c5..89e3e6b266 100644 --- a/src/axom/mir/EquiZAlgorithm.hpp +++ b/src/axom/mir/EquiZAlgorithm.hpp @@ -141,6 +141,7 @@ class EquiZAlgorithm : public axom::mir::MIRAlgorithm "The mesh and the material do not have the same number of zones."); // Copy the options. + const axom::bump::Options opts(n_options); conduit::Node n_options_copy; utils::copy(n_options_copy, n_options, getAllocatorID()); n_options_copy["topology"] = n_topo.name(); @@ -159,8 +160,11 @@ class EquiZAlgorithm : public axom::mir::MIRAlgorithm // Come up with lists of clean/mixed zones. axom::Array cleanZones, mixedZones; makeZoneLists(n_options_copy, cleanZones, mixedZones); - SLIC_INFO( - axom::fmt::format("cleanZones: {}, mixedZones: {}", cleanZones.size(), mixedZones.size())); + if(opts.verbose()) + { + SLIC_INFO( + axom::fmt::format("cleanZones: {}, mixedZones: {}", cleanZones.size(), mixedZones.size())); + } if(cleanZones.size() > 0 && mixedZones.size() > 0) { diff --git a/src/axom/mir/detail/elvira_detail.hpp b/src/axom/mir/detail/elvira_detail.hpp index 141864a3de..a04019ea2d 100644 --- a/src/axom/mir/detail/elvira_detail.hpp +++ b/src/axom/mir/detail/elvira_detail.hpp @@ -43,31 +43,13 @@ class ELVIRAOptions : public axom::bump::Options * \brief Get whether the plane equation fields should appear in the output. * \return True if plane equation fields should appear, false otherwise. */ - bool plane() const { return flagValue("plane"); } + bool plane() const { return flagValue("plane", false); } /** * \brief Get whether the output should be a point mesh. * \return True if the output should be a point mesh, false otherwise. */ - bool pointmesh() const { return flagValue("pointmesh"); } - -protected: - /** - * \brief Get whether the flag is set in the options. - * - * \param key The name of the key that contains the flag. - * - * \return True if key is present and set to non-zero, false otherwise. - */ - bool flagValue(const std::string &key) const - { - bool retval = false; - if(options().has_path(key)) - { - retval = options().fetch_existing(key).to_int() != 0; - } - return retval; - } + bool pointmesh() const { return flagValue("pointmesh", false); } }; namespace detail diff --git a/src/axom/mir/tests/mir_elvira2d.cpp b/src/axom/mir/tests/mir_elvira2d.cpp index dc020d4779..d34e34cc33 100644 --- a/src/axom/mir/tests/mir_elvira2d.cpp +++ b/src/axom/mir/tests/mir_elvira2d.cpp @@ -120,6 +120,7 @@ struct braid2d_mat_test options["matset"] = "mat"; options["plane"] = 1; options["pointmesh"] = pointMesh ? 1 : 0; + options["verbose"] = 1; if(cleanMats) { // Set the output names diff --git a/src/axom/mir/tests/mir_elvira3d.cpp b/src/axom/mir/tests/mir_elvira3d.cpp index 41eec8e8c0..65933e933e 100644 --- a/src/axom/mir/tests/mir_elvira3d.cpp +++ b/src/axom/mir/tests/mir_elvira3d.cpp @@ -101,6 +101,7 @@ struct test_Elvira3D MIR m(topologyView, coordsetView, matsetView); conduit::Node deviceMIRMesh; conduit::Node options; + options["verbose"] = 1; options["matset"] = "mat"; options["plane"] = pointMesh ? 1 : 0; options["pointmesh"] = pointMesh ? 1 : 0; diff --git a/src/axom/mir/tests/mir_equiz2d.cpp b/src/axom/mir/tests/mir_equiz2d.cpp index 83fffa2c45..42e610626d 100644 --- a/src/axom/mir/tests/mir_equiz2d.cpp +++ b/src/axom/mir/tests/mir_equiz2d.cpp @@ -109,6 +109,7 @@ void braid2d_mat_test(const std::string &type, using MIR = axom::mir::EquiZAlgorithm; MIR m(topologyView, coordsetView, matsetView); conduit::Node options; + options["verbose"] = 1; options["matset"] = "mat"; if(cleanMats) { @@ -318,6 +319,7 @@ class test_Polygonal_MIR using MIR = axom::mir::EquiZAlgorithm; MIR m(topologyView, coordsetView, matsetView); conduit::Node options; + options["verbose"] = 1; options["matset"] = "target2_matset"; options["matsetName"] = "mir_matset"; m.execute(n_dev, options, n_mir); diff --git a/src/axom/mir/tests/mir_equiz3d.cpp b/src/axom/mir/tests/mir_equiz3d.cpp index 1dff31f326..a2691784ef 100644 --- a/src/axom/mir/tests/mir_equiz3d.cpp +++ b/src/axom/mir/tests/mir_equiz3d.cpp @@ -69,6 +69,7 @@ void braid3d_mat_test(const std::string &type, const std::string &mattype, const using MIR = axom::mir::EquiZAlgorithm; MIR m(topologyView, coordsetView, matsetView); conduit::Node options; + options["verbose"] = 1; options["matset"] = "mat"; m.execute(deviceMesh, options, deviceMIRMesh); } From af45ceaf1c2fd3ea7a1f81e81a5e1716c6af42c7 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Fri, 17 Jul 2026 17:24:48 -0700 Subject: [PATCH 743/986] Added curvature methods and tests for BezierCurve --- RELEASE-NOTES.md | 2 + src/axom/primal/docs/sphinx/primitive.rst | 6 +- src/axom/primal/geometry/BezierCurve.hpp | 157 ++++++++++++++++++ src/axom/primal/tests/primal_bezier_curve.cpp | 22 +++ .../primal/tests/primal_rational_bezier.cpp | 29 ++++ 5 files changed, 215 insertions(+), 1 deletion(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 86d22a0314..6cf4002d2b 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -42,6 +42,8 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ - Primal: Adds KnotVector constructors that skip validity assertion checks, allowing the user to call `isValid()` and handle the error appropriately. - Primal: Adds a `primal::BezierTriangle` class +- Primal: Adds `BezierCurve::curvature()` and `BezierCurve::curvatureDerivatives()` helpers, complementing the + existing curvature support on `NURBSCurve`. - Inlet: Added the ability to have collections (array and dictionary) with variant values. - Inlet: Added the ability to have collections (array and dictionary) with variant user defined structures. - Sidre: Added `axom::sidre::View::checksum()` and `axom::sidre::Group::checksum()` methods that return checksum values. A `Group::checksum(conduit::Node&)` overload emits diffable checksum metadata for group/view subtrees. diff --git a/src/axom/primal/docs/sphinx/primitive.rst b/src/axom/primal/docs/sphinx/primitive.rst index ea402f9b9e..81a8f26ca5 100644 --- a/src/axom/primal/docs/sphinx/primitive.rst +++ b/src/axom/primal/docs/sphinx/primitive.rst @@ -7,6 +7,7 @@ Primal includes the following primitives: - Segment, Ray, Vector - Plane, Triangle, Polygon - Quadrilateral +- BezierCurve, NURBSCurve - Sphere - Tetrahedron - Hexahedron @@ -20,6 +21,10 @@ dimension. The primitives do not inherit from a common base class. This was a design choice in favor of simplicity and performance. Geometric primitives can be tested for equality and can be printed to strings. +Curve primitives such as ``BezierCurve`` and ``NURBSCurve`` also provide +evaluation, derivative, and curvature-related helpers; see the generated +Primal API documentation for the full interface. + Primal also includes functions to merge a pair of BoundingBox or a pair of OrientedBoundingBox objects and to create new OrientedBoundingBox objects from a list of points. @@ -39,4 +44,3 @@ less error-prone to write ``#include axom/primal.hpp``. :start-after: _using_start :end-before: _using_end :language: C++ - diff --git a/src/axom/primal/geometry/BezierCurve.hpp b/src/axom/primal/geometry/BezierCurve.hpp index 26616a1513..34735bb948 100644 --- a/src/axom/primal/geometry/BezierCurve.hpp +++ b/src/axom/primal/geometry/BezierCurve.hpp @@ -21,6 +21,7 @@ #include "axom/primal/geometry/BoundingBox.hpp" #include "axom/primal/geometry/OrientedBoundingBox.hpp" +#include "axom/primal/operators/curvature.hpp" #include "axom/primal/operators/squared_distance.hpp" #include @@ -818,6 +819,162 @@ class BezierCurve } } + /*! + * \brief Computes the 0th, 1st, 2nd, and 3rd derivatives of a Bezier curve + * + * \param [in] t Parameter value at which to compute derivatives + * \param [out] eval The value of the curve at \a t + * \param [out] Dt The first derivative at \a t + * \param [out] DtDt The second derivative at \a t + * \param [out] DtDtDt The third derivative at \a t + */ + void evaluateThirdDerivative(T t, + PointType& eval, + VectorType& Dt, + VectorType& DtDt, + VectorType& DtDtDt) const + { + using axom::utilities::lerp; + + const int ord = getOrder(); + std::vector dCarray(ord + 1); + + if(!isRational()) + { + for(int i = 0; i < NDIMS; ++i) + { + for(int p = 0; p <= ord; ++p) + { + dCarray[p] = m_controlPoints[p][i]; + } + + for(int p = 1; p <= ord - 3; ++p) + { + const int end = ord - p; + for(int k = 0; k <= end; ++k) + { + dCarray[k] = lerp(dCarray[k], dCarray[k + 1], t); + } + } + + if(ord == 0) + { + eval[i] = dCarray[0]; + Dt[i] = 0.0; + DtDt[i] = 0.0; + DtDtDt[i] = 0.0; + } + else if(ord == 1) + { + eval[i] = (1 - t) * dCarray[0] + t * dCarray[1]; + Dt[i] = ord * (dCarray[1] - dCarray[0]); + DtDt[i] = 0.0; + DtDtDt[i] = 0.0; + } + else if(ord == 2) + { + eval[i] = + (1 - t) * (1 - t) * dCarray[0] + 2 * (1 - t) * t * dCarray[1] + t * t * dCarray[2]; + Dt[i] = + ord * ((1 - t) * (dCarray[1] - dCarray[0]) + t * (dCarray[2] - dCarray[1])); + DtDt[i] = ord * (ord - 1) * (dCarray[2] - 2 * dCarray[1] + dCarray[0]); + DtDtDt[i] = 0.0; + } + else + { + const T omt = 1 - t; + eval[i] = omt * omt * omt * dCarray[0] + 3 * omt * omt * t * dCarray[1] + + 3 * omt * t * t * dCarray[2] + t * t * t * dCarray[3]; + Dt[i] = ord * (omt * omt * (dCarray[1] - dCarray[0]) + + 2 * omt * t * (dCarray[2] - dCarray[1]) + + t * t * (dCarray[3] - dCarray[2])); + DtDt[i] = ord * (ord - 1) * + (omt * (dCarray[2] - 2 * dCarray[1] + dCarray[0]) + + t * (dCarray[3] - 2 * dCarray[2] + dCarray[1])); + DtDtDt[i] = ord * (ord - 1) * (ord - 2) * + (dCarray[3] - 3 * dCarray[2] + 3 * dCarray[1] - dCarray[0]); + } + } + } + else + { + BezierCurve projective(ord); + BezierCurve weights(ord); + + for(int p = 0; p <= ord; ++p) + { + weights[p][0] = m_weights[p]; + + for(int i = 0; i < NDIMS; ++i) + { + projective[p][i] = m_controlPoints[p][i] * m_weights[p]; + } + } + + Point P; + Vector P_t, P_tt, P_ttt; + + Point W; + Vector W_t, W_tt, W_ttt; + + projective.evaluateThirdDerivative(t, P, P_t, P_tt, P_ttt); + weights.evaluateThirdDerivative(t, W, W_t, W_tt, W_ttt); + + for(int i = 0; i < NDIMS; ++i) + { + eval[i] = P[i] / W[0]; + Dt[i] = (P_t[i] - eval[i] * W_t[0]) / W[0]; + DtDt[i] = (P_tt[i] - 2 * Dt[i] * W_t[0] - eval[i] * W_tt[0]) / W[0]; + DtDtDt[i] = + (P_ttt[i] - 3 * DtDt[i] * W_t[0] - 3 * Dt[i] * W_tt[0] - eval[i] * W_ttt[0]) / W[0]; + } + } + } + + /*! + * \brief Computes the third derivative of a Bezier curve at parameter value \a t + * + * \param [in] t Parameter value at which to compute the third derivative + * \return The third derivative vector of the Bezier curve at \a t + */ + VectorType d3td3(T t) const + { + PointType eval; + VectorType Dt, DtDt, DtDtDt; + evaluateThirdDerivative(t, eval, Dt, DtDt, DtDtDt); + return DtDtDt; + } + + /*! + * \brief Evaluates the curvature at parameter \a t + * + * \param [in] t The parameter value + * + * \return The curvature value at \a t + */ + double curvature(T t) const + { + return axom::primal::curvature(dt(t), dtdt(t)); + } + + /*! + * \brief Evaluates the curvature derivatives at \a t + * + * \param [in] t The parameter value + * \param [in] d The number of derivatives to compute (must be 1 or 2) + * \param [out] ders An array that will contain the curvature derivatives at \a t + */ + void curvatureDerivatives(T t, int d, axom::Array& ders) const + { + SLIC_ASSERT(d == 1 || d == 2); + + axom::Array curveDers(3); + curveDers[0] = dt(t); + curveDers[1] = dtdt(t); + curveDers[2] = d3td3(t); + axom::primal::curvatureDerivatives(d, curveDers, ders); + } + ///@} ///@{ diff --git a/src/axom/primal/tests/primal_bezier_curve.cpp b/src/axom/primal/tests/primal_bezier_curve.cpp index 20be6e8c46..7711045259 100644 --- a/src/axom/primal/tests/primal_bezier_curve.cpp +++ b/src/axom/primal/tests/primal_bezier_curve.cpp @@ -355,6 +355,28 @@ TEST(primal_beziercurve_, batch_derivatives) } } +//------------------------------------------------------------------------------ +TEST(primal_beziercurve, curvature) +{ + constexpr int DIM = 2; + using CoordType = double; + using PointType = primal::Point; + using BezierCurveType = primal::BezierCurve; + + PointType controlPoints[3] = {PointType {0.0, 0.0}, + PointType {0.5, 0.5}, + PointType {1.0, 0.0}}; + BezierCurveType curve(controlPoints, 2); + + EXPECT_NEAR(curve.curvature(0.5), -2.0, 1e-14); + + axom::Array curvatureDers; + curve.curvatureDerivatives(0.5, 2, curvatureDers); + ASSERT_EQ(curvatureDers.size(), 2); + EXPECT_NEAR(curvatureDers[0], 0.0, 1e-14); + EXPECT_NEAR(curvatureDers[1], 24.0, 1e-14); +} + //------------------------------------------------------------------------------ TEST(primal_beziercurve, split_cubic) { diff --git a/src/axom/primal/tests/primal_rational_bezier.cpp b/src/axom/primal/tests/primal_rational_bezier.cpp index 5679e2566a..d8de494f4b 100644 --- a/src/axom/primal/tests/primal_rational_bezier.cpp +++ b/src/axom/primal/tests/primal_rational_bezier.cpp @@ -18,6 +18,8 @@ #include "axom/slic.hpp" #include "axom/fmt.hpp" +#include + namespace primal = axom::primal; //------------------------------------------------------------------------------ @@ -200,6 +202,33 @@ TEST(primal_rationalbezier, second_derivative) } } +//------------------------------------------------------------------------------ +TEST(primal_rationalbezier, curvature) +{ + constexpr int DIM = 2; + using CoordType = double; + using PointType = primal::Point; + using BezierCurveType = primal::BezierCurve; + + const CoordType weight = 1.0 / std::sqrt(2.0); + PointType controlPoints[3] = {PointType {1.0, 0.0}, + PointType {1.0, 1.0}, + PointType {0.0, 1.0}}; + CoordType weights[3] = {1.0, weight, 1.0}; + BezierCurveType curve(controlPoints, weights, 2); + + for(const CoordType t : {0.0, 0.25, 0.5, 0.75, 1.0}) + { + EXPECT_NEAR(curve.curvature(t), 1.0, 1e-12); + + axom::Array curvatureDers; + curve.curvatureDerivatives(t, 2, curvatureDers); + ASSERT_EQ(curvatureDers.size(), 2); + EXPECT_NEAR(curvatureDers[0], 0.0, 1e-10); + EXPECT_NEAR(curvatureDers[1], 0.0, 1e-10); + } +} + //------------------------------------------------------------------------------ TEST(primal_rationalbezier, split_cubic) { From c5e9684bd074ddc98b49e587087eade52c192088 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 20 Jul 2026 13:53:42 -0700 Subject: [PATCH 744/986] Change curvatureDerivative interface --- src/axom/primal/geometry/BezierCurve.hpp | 19 ++--- src/axom/primal/geometry/NURBSCurve.hpp | 21 ++--- src/axom/primal/operators/curvature.hpp | 102 +++++++---------------- 3 files changed, 45 insertions(+), 97 deletions(-) diff --git a/src/axom/primal/geometry/BezierCurve.hpp b/src/axom/primal/geometry/BezierCurve.hpp index 34735bb948..65d259e84a 100644 --- a/src/axom/primal/geometry/BezierCurve.hpp +++ b/src/axom/primal/geometry/BezierCurve.hpp @@ -958,21 +958,18 @@ class BezierCurve } /*! - * \brief Evaluates the curvature derivatives at \a t + * \brief Evaluates the first curvature derivative at parameter \a t * * \param [in] t The parameter value - * \param [in] d The number of derivatives to compute (must be 1 or 2) - * \param [out] ders An array that will contain the curvature derivatives at \a t + * + * \return The first curvature derivative with respect to the curve parameter */ - void curvatureDerivatives(T t, int d, axom::Array& ders) const + double curvatureDerivative(T t) const { - SLIC_ASSERT(d == 1 || d == 2); - - axom::Array curveDers(3); - curveDers[0] = dt(t); - curveDers[1] = dtdt(t); - curveDers[2] = d3td3(t); - axom::primal::curvatureDerivatives(d, curveDers, ders); + PointType eval; + VectorType Dt, DtDt, DtDtDt; + evaluateThirdDerivative(t, eval, Dt, DtDt, DtDtDt); + return axom::primal::curvatureDerivative(Dt, DtDt, DtDtDt); } ///@} diff --git a/src/axom/primal/geometry/NURBSCurve.hpp b/src/axom/primal/geometry/NURBSCurve.hpp index 0c59326d12..f6f2ad702c 100644 --- a/src/axom/primal/geometry/NURBSCurve.hpp +++ b/src/axom/primal/geometry/NURBSCurve.hpp @@ -1468,23 +1468,20 @@ class NURBSCurve return axom::primal::curvature(Dt, DtDt); } - /*! - * \brief Evaluates the curvature derivatives evaluated at \a t. - * - * \param t The parameter value. - * \param d The number of derivatives to compute (must be 1 or 2). - * \param[out] ders An array that will contain the curvature derivatives evaluated at \a t. + /*! + * \brief Evaluates the first curvature derivative at parameter \a t * - * \return The curvature derivative value(s) evaluated at \a t. - */ - void curvatureDerivatives(T t, int d, axom::Array &ders) const + * \param t The parameter value. + * + * \return The first curvature derivative with respect to the curve parameter. + */ + double curvatureDerivative(T t) const { - SLIC_ASSERT(d == 1 || d == 2); PointType eval; axom::Array curveDers; - // Evaluate d+1 curve derivatives at t. + // Evaluate 1st, 2nd, and 3rd curve derivatives at t. evaluateDerivatives(t, 3, eval, curveDers); - axom::primal::curvatureDerivatives(d, curveDers, ders); + return axom::primal::curvatureDerivative(curveDers[0], curveDers[1], curveDers[2]); } ///@} diff --git a/src/axom/primal/operators/curvature.hpp b/src/axom/primal/operators/curvature.hpp index 05f594627f..c5141931b1 100644 --- a/src/axom/primal/operators/curvature.hpp +++ b/src/axom/primal/operators/curvature.hpp @@ -57,88 +57,42 @@ T curvature(const VectorType &Dt, const VectorType &DtDt) } } -/*! - * \brief Evaluates the curvature derivatives using supplied curve derivatives. - * - * \param[in] d The number of derivatives to compute (1=1st deriv, 2=1st & 2nd derivs) - * \param[in] curveDerivs The derivatives, up to order \a d, of the curve. - * \param[out] ders An array that will contain the curvature derivatives. - */ +/*! + * \brief Evaluates the first parameter derivative of curvature using supplied + * curve derivatives. + * + * \param[in] D1 The 1st derivative of the curve. + * \param[in] D2 The 2nd derivative of the curve. + * \param[in] D3 The 3rd derivative of the curve. + * + * \return The curvature derivative evaluated with respect to the curve + * parameter. + */ template -void curvatureDerivatives(int d, - const axom::Array &curveDerivs, - axom::Array &ders) +T curvatureDerivative(const VectorType& D1, + const VectorType& D2, + const VectorType& D3) { - SLIC_ASSERT(d == 1 || d == 2); - SLIC_ASSERT(curveDerivs.size() == 3); - - ders.resize(d); - - const VectorType& D1 = curveDerivs[0]; - const VectorType& D2 = curveDerivs[1]; - const VectorType& D3 = curveDerivs[2]; - -#if 0 - // Original 2D only - const T xp = D1[0]; // x' - const T xpp = D2[0]; // x'' - const T xppp = D3[0]; // x''' - - const T yp = D1[1]; // y' - const T ypp = D2[1]; // y'' - const T yppp = D3[1]; // y''' - - // 1st derivative of curvature. - const T xp2_plus_yp2 = xp * xp + yp * yp; - const T A = -3. * (xp * ypp - yp * xpp) * 2. * (xp * xpp + yp * ypp); - const T B = 2. * pow(xp2_plus_yp2, 5. / 2.); - const T C = xp * yppp - yp * xppp; - const T D = pow(xp2_plus_yp2, 3. / 2.); - ders[0] = A / B + C / D; - - if(d >= 2) - { - // 2nd derivative of curvature. - const T E = 15. * (-yp * xpp + xp * ypp) * - pow(2. * xp * xpp + 2. * yp * ypp, 2.) / - (4. * pow(xp2_plus_yp2, 7. / 2.)); - const T F = 3. * (2. * xp * xpp + 2. * yp * ypp) * - (-yp * xppp + xp * yppp) / pow(xp2_plus_yp2, 5. / 2.); - const T G = 3. * (-yp * xpp + xp * ypp) * - (2. * (xpp * xpp) + 2. * (ypp * ypp) + 2. * xp * xppp + 2. * yp * yppp) / - (2. * pow(xp2_plus_yp2, 5. / 2.)); - const T H = (-ypp * xppp + xpp * yppp) / pow(xp2_plus_yp2, 3. / 2.); - - ders[1] = E - F - G + H; - } -#else const T D1Norm = D1.norm(); const T D1Norm3 = pow(D1Norm, 3.); const T D1Norm5 = pow(D1Norm, 5.); - const T D1D2Norm = VectorType::cross_product(D1, D2).norm(); - const T D1D3Norm = VectorType::cross_product(D1, D3).norm(); - - // 1st derivative of curvature. - const T A = -3. * D1D2Norm * 2. * D1.dot(D2); - const T B = 2. * D1Norm5; - const T C = D1D3Norm; - const T D = D1Norm3; - ders[0] = A / B + C / D; - - if(d >= 2) - { - // 2nd derivative of curvature. - const T E = 15. * D1D2Norm * pow(2. * D1.dot(D2), 2.) / (4. * pow(D1Norm, 7.)); - - const T F = 3. * (2. * D1.dot(D2)) * D1D3Norm / D1Norm5; - const T G = 3. * D1D2Norm * (D2.squared_norm() * D1.dot(D3)) / D1Norm5; - - const T H = VectorType::cross_product(D2, D3).norm() / D1Norm3; + if constexpr(VectorType::dimension() == 2) + { + const T det12 = D1[0] * D2[1] - D1[1] * D2[0]; + const T det13 = D1[0] * D3[1] - D1[1] * D3[0]; + return det13 / D1Norm3 - 3. * det12 * D1.dot(D2) / D1Norm5; + } + else + { + const auto cross12 = VectorType::cross_product(D1, D2); + const auto cross13 = VectorType::cross_product(D1, D3); + const T cross12Norm = cross12.norm(); + const T crossTerm = cross12.dot(cross13); - ders[1] = E - F - G + H; + return crossTerm / (cross12Norm * D1Norm3) - + 3. * cross12Norm * D1.dot(D2) / D1Norm5; } -#endif } } // namespace primal From 632d2207d14bd8f989d2413ab1a7fc0a7fcc08b7 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 20 Jul 2026 13:54:53 -0700 Subject: [PATCH 745/986] Enhance curvature tests --- RELEASE-NOTES.md | 2 +- src/axom/primal/tests/primal_bezier_curve.cpp | 119 +++++++- src/axom/primal/tests/primal_nurbs_curve.cpp | 258 ++++++++++++++++++ .../primal/tests/primal_rational_bezier.cpp | 7 +- 4 files changed, 373 insertions(+), 13 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 6cf4002d2b..cf2dce9788 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -42,7 +42,7 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ - Primal: Adds KnotVector constructors that skip validity assertion checks, allowing the user to call `isValid()` and handle the error appropriately. - Primal: Adds a `primal::BezierTriangle` class -- Primal: Adds `BezierCurve::curvature()` and `BezierCurve::curvatureDerivatives()` helpers, complementing the +- Primal: Adds `BezierCurve::curvature()` and `BezierCurve::curvatureDerivative()` helpers, complementing the existing curvature support on `NURBSCurve`. - Inlet: Added the ability to have collections (array and dictionary) with variant values. - Inlet: Added the ability to have collections (array and dictionary) with variant user defined structures. diff --git a/src/axom/primal/tests/primal_bezier_curve.cpp b/src/axom/primal/tests/primal_bezier_curve.cpp index 7711045259..1ce2dfac5c 100644 --- a/src/axom/primal/tests/primal_bezier_curve.cpp +++ b/src/axom/primal/tests/primal_bezier_curve.cpp @@ -14,8 +14,11 @@ #include "axom/slic.hpp" #include "axom/primal/geometry/BezierCurve.hpp" +#include "axom/primal/operators/curvature.hpp" #include "axom/primal/operators/squared_distance.hpp" +#include + namespace primal = axom::primal; //------------------------------------------------------------------------------ @@ -368,13 +371,117 @@ TEST(primal_beziercurve, curvature) PointType {1.0, 0.0}}; BezierCurveType curve(controlPoints, 2); - EXPECT_NEAR(curve.curvature(0.5), -2.0, 1e-14); + for(const CoordType t : {0.25, 0.5, 0.75}) + { + const CoordType denom = 1. + (2. * t - 1.) * (2. * t - 1.); + const CoordType expectedCurvature = -2. / std::pow(denom, 1.5); + const CoordType expectedCurvatureDerivative = + 12. * (2. * t - 1.) / std::pow(denom, 2.5); + + EXPECT_NEAR(curve.curvature(t), expectedCurvature, 1e-14); + EXPECT_NEAR(curve.curvatureDerivative(t), expectedCurvatureDerivative, 1e-14); + } +} + +//------------------------------------------------------------------------------ +TEST(primal_curvature_operator, curvature_derivative_2d) +{ + using VectorType = primal::Vector; + + const VectorType D1 {1.0, 1.0}; + const VectorType D2 {0.0, 2.0}; + const VectorType D3 {0.0, 0.0}; + + EXPECT_NEAR(primal::curvatureDerivative(D1, D2, D3), -3.0 / std::sqrt(2.0), 1e-14); +} + +//------------------------------------------------------------------------------ +TEST(primal_curvature_operator, curvature_derivative_3d) +{ + using VectorType = primal::Vector; + + const VectorType D1 {1.0, 1.0, 0.0}; + const VectorType D2 {0.0, -2.0, 0.0}; + const VectorType D3 {0.0, 0.0, 0.0}; + + EXPECT_NEAR(primal::curvatureDerivative(D1, D2, D3), -3.0 / std::sqrt(2.0), 1e-14); +} + +//------------------------------------------------------------------------------ +TEST(primal_curvature_operator, curvature_derivative_3d_wavy_curve) +{ + using CoordType = double; + using VectorType = primal::Vector; + + constexpr CoordType radius = 1.0; + constexpr CoordType amplitude = 0.5; + constexpr CoordType frequency = 20.0; + + auto D1 = [](CoordType theta) { + return VectorType {-radius * std::sin(theta), + radius * std::cos(theta), + -amplitude * frequency * std::sin(frequency * theta)}; + }; + + auto D2 = [](CoordType theta) { + return VectorType {-radius * std::cos(theta), + -radius * std::sin(theta), + -amplitude * frequency * frequency * std::cos(frequency * theta)}; + }; + + auto D3 = [](CoordType theta) { + return VectorType {radius * std::sin(theta), + -radius * std::cos(theta), + amplitude * frequency * frequency * frequency * + std::sin(frequency * theta)}; + }; + + auto curvatureFromTheta = [&](CoordType theta) { + const auto d1 = D1(theta); + const auto d2 = D2(theta); + return VectorType::cross_product(d1, d2).norm() / std::pow(d1.norm(), 3.0); + }; + + constexpr CoordType h = 1.e-7; + for(const CoordType theta : {0.13, 0.41, 0.77, 1.19}) + { + const auto d1 = D1(theta); + const auto d2 = D2(theta); + const auto d3 = D3(theta); + + const CoordType curvatureExpected = curvatureFromTheta(theta); + const CoordType curvatureDerivativeExpected = + (curvatureFromTheta(theta + h) - curvatureFromTheta(theta - h)) / (2.0 * h); + + EXPECT_NEAR(primal::curvature(d1, d2), curvatureExpected, 1e-12); + EXPECT_NEAR(primal::curvatureDerivative(d1, d2, d3), + curvatureDerivativeExpected, + 1e-5); + } +} + +//------------------------------------------------------------------------------ +TEST(primal_beziercurve, curvature_reverse_orientation) +{ + constexpr int DIM = 2; + using CoordType = double; + using PointType = primal::Point; + using BezierCurveType = primal::BezierCurve; + + PointType controlPoints[3] = {PointType {0.0, 0.0}, + PointType {0.5, 0.5}, + PointType {1.0, 0.0}}; + BezierCurveType curve(controlPoints, 2); + BezierCurveType reversed(curve); + reversed.reverseOrientation(); - axom::Array curvatureDers; - curve.curvatureDerivatives(0.5, 2, curvatureDers); - ASSERT_EQ(curvatureDers.size(), 2); - EXPECT_NEAR(curvatureDers[0], 0.0, 1e-14); - EXPECT_NEAR(curvatureDers[1], 24.0, 1e-14); + for(const CoordType t : {0.0, 0.25, 0.5, 0.75, 1.0}) + { + EXPECT_NEAR(curve.curvature(t), -reversed.curvature(1.0 - t), 1e-14); + EXPECT_NEAR(curve.curvatureDerivative(t), + reversed.curvatureDerivative(1.0 - t), + 1e-14); + } } //------------------------------------------------------------------------------ diff --git a/src/axom/primal/tests/primal_nurbs_curve.cpp b/src/axom/primal/tests/primal_nurbs_curve.cpp index 417352d4ca..68d0c88342 100644 --- a/src/axom/primal/tests/primal_nurbs_curve.cpp +++ b/src/axom/primal/tests/primal_nurbs_curve.cpp @@ -1280,6 +1280,54 @@ void checkCircularArcCurvature3D(T tol) } } +template +void checkCircularArcCurvatureDerivative2D(T tol) +{ + using NURBSCurveType = primal::NURBSCurve; + + const T radius = 4.; + const auto curve = NURBSCurveType::make_circular_arc_nurbs(T(0.15) * T(M_PI), + T(1.85) * T(M_PI), + T(0.), + T(0.), + radius); + + constexpr int numSamples = 17; + for(int i = 0; i < numSamples; ++i) + { + const T t = static_cast(i) / static_cast(numSamples - 1); + EXPECT_NEAR(curve.curvatureDerivative(t), T(0.), tol); + } +} + +template +void checkCircularArcCurvatureDerivative3D(T tol) +{ + using NURBSCurve2D = primal::NURBSCurve; + using NURBSCurve3D = primal::NURBSCurve; + using Vector3D = primal::Vector; + + const T radius = 4.; + const auto curve2d = NURBSCurve2D::make_circular_arc_nurbs(T(0.1) * T(M_PI), + T(1.6) * T(M_PI), + T(0.), + T(0.), + radius); + + const T a0 = T(M_PI) / T(4.); + const T a1 = a0 + T(M_PI) / T(2.); + const Vector3D uvec {std::cos(a0), T(0.), -std::sin(a0)}; + const Vector3D vvec {std::cos(a1), T(0.), -std::sin(a1)}; + const auto curve3d = promoteTo3D(curve2d, uvec, vvec); + + constexpr int numSamples = 17; + for(int i = 0; i < numSamples; ++i) + { + const T t = static_cast(i) / static_cast(numSamples - 1); + EXPECT_NEAR(curve3d.curvatureDerivative(t), T(0.), tol); + } +} + TEST(primal_nurbscurve, curvature2d) { checkCircularArcCurvature2D(1.e-5F); @@ -1292,6 +1340,216 @@ TEST(primal_nurbscurve, curvature3d) checkCircularArcCurvature3D(1.e-10); } +TEST(primal_nurbscurve, curvature_derivative2d) +{ + checkCircularArcCurvatureDerivative2D(1.e-5F); + checkCircularArcCurvatureDerivative2D(1.e-10); +} + +TEST(primal_nurbscurve, curvature_derivative3d) +{ + checkCircularArcCurvatureDerivative3D(1.e-5F); + checkCircularArcCurvatureDerivative3D(1.e-10); +} + +//------------------------------------------------------------------------------ +TEST(primal_nurbscurve, curvature_reverse_orientation) +{ + constexpr int DIM = 2; + using CoordType = double; + using PointType = primal::Point; + using NURBSCurveType = primal::NURBSCurve; + + PointType controlPoints[3] = {PointType {0.0, 0.0}, + PointType {0.5, 0.5}, + PointType {1.0, 0.0}}; + NURBSCurveType curve(controlPoints, 3, 2); + NURBSCurveType reversed(curve); + reversed.reverseOrientation(); + + for(const CoordType t : {0.0, 0.25, 0.5, 0.75, 1.0}) + { + EXPECT_NEAR(curve.curvature(t), -reversed.curvature(1.0 - t), 1e-12); + EXPECT_NEAR(curve.curvatureDerivative(t), + reversed.curvatureDerivative(1.0 - t), + 1e-12); + } +} + +//------------------------------------------------------------------------------ +TEST(primal_nurbscurve, quartic_graph_derivatives_and_curvature) +{ + constexpr int DIM = 2; + using CoordType = double; + using PointType = primal::Point; + using VectorType = primal::Vector; + using NURBSCurveType = primal::NURBSCurve; + + // This degree-4 nonrational NURBS span exactly represents + // f(x) = 0.25 * (x + 1) * (x - 2) * (x^2 - 0.25) + // over x in [-1, 2], parameterized by x(t) = -1 + 3 t. + auto analyticY = [](CoordType t) { + return (81.0 / 4.0) * t * t * t * t - (135.0 / 4.0) * t * t * t + + (243.0 / 16.0) * t * t - (27.0 / 16.0) * t; + }; + + auto analyticYp = [](CoordType t) { + return 81.0 * t * t * t - (405.0 / 4.0) * t * t + (243.0 / 8.0) * t - (27.0 / 16.0); + }; + + auto analyticYpp = [](CoordType t) { + return 243.0 * t * t - (405.0 / 2.0) * t + (243.0 / 8.0); + }; + + auto analyticYppp = [](CoordType t) { return 486.0 * t - (405.0 / 2.0); }; + + auto analyticCurvature = [&](CoordType t) { + const CoordType yp = analyticYp(t); + const CoordType ypp = analyticYpp(t); + const CoordType denom = 9.0 + yp * yp; + return 3.0 * ypp / std::pow(denom, 1.5); + }; + + auto analyticCurvatureDerivative = [&](CoordType t) { + const CoordType yp = analyticYp(t); + const CoordType ypp = analyticYpp(t); + const CoordType yppp = analyticYppp(t); + const CoordType denom = 9.0 + yp * yp; + return 3.0 * yppp / std::pow(denom, 1.5) - + 9.0 * yp * ypp * ypp / std::pow(denom, 2.5); + }; + + PointType controlPoints[5] = { + PointType {-1.0, 0.0}, + PointType {-0.25, -27.0 / 64.0}, + PointType {0.5, 27.0 / 16.0}, + PointType {1.25, -135.0 / 64.0}, + PointType {2.0, 0.0}}; + + NURBSCurveType curve(controlPoints, 5, 4); + + for(const CoordType t : {0.0, 1.0 / 6.0, 1.0 / 3.0, 0.5, 2.0 / 3.0, 5.0 / 6.0, 1.0}) + { + const PointType eval = curve.evaluate(t); + const VectorType Dt = curve.dt(t); + const VectorType DtDt = curve.dtdt(t); + + PointType evalBatch; + axom::Array ders; + curve.evaluateDerivatives(t, 3, evalBatch, ders); + + EXPECT_NEAR(eval[0], -1.0 + 3.0 * t, 1e-13); + EXPECT_NEAR(eval[1], analyticY(t), 1e-13); + EXPECT_NEAR(evalBatch[0], eval[0], 1e-13); + EXPECT_NEAR(evalBatch[1], eval[1], 1e-13); + + EXPECT_NEAR(Dt[0], 3.0, 1e-13); + EXPECT_NEAR(Dt[1], analyticYp(t), 1e-12); + EXPECT_NEAR(DtDt[0], 0.0, 1e-13); + EXPECT_NEAR(DtDt[1], analyticYpp(t), 1e-11); + + ASSERT_EQ(ders.size(), 3); + EXPECT_NEAR(ders[0][0], 3.0, 1e-13); + EXPECT_NEAR(ders[0][1], analyticYp(t), 1e-12); + EXPECT_NEAR(ders[1][0], 0.0, 1e-13); + EXPECT_NEAR(ders[1][1], analyticYpp(t), 1e-11); + EXPECT_NEAR(ders[2][0], 0.0, 1e-13); + EXPECT_NEAR(ders[2][1], analyticYppp(t), 1e-10); + + EXPECT_NEAR(curve.curvature(t), analyticCurvature(t), 1e-12); + EXPECT_NEAR(curve.curvatureDerivative(t), analyticCurvatureDerivative(t), 1e-11); + } +} + +//------------------------------------------------------------------------------ +TEST(primal_nurbscurve, repeated_knot_kink_behavior) +{ + constexpr int DIM = 2; + using CoordType = double; + using PointType = primal::Point; + using VectorType = primal::Vector; + using NURBSCurveType = primal::NURBSCurve; + + // This quadratic nonrational NURBS curve has an internal knot with + // multiplicity equal to the degree, so it is only C^0 at t = 0.5. + PointType controlPoints[5] = { + PointType {0.0, 0.0}, PointType {1.0, 0.0}, PointType {1.0, 1.0}, + PointType {2.0, 1.0}, PointType {3.0, 1.0}}; + CoordType knots[8] = {0.0, 0.0, 0.0, 0.5, 0.5, 1.0, 1.0, 1.0}; + NURBSCurveType curve(controlPoints, 5, knots, 8); + + ASSERT_TRUE(curve.isValidNURBS()); + + const PointType pStart = curve.evaluate(0.0); + const PointType pEnd = curve.evaluate(1.0); + const VectorType dtStart = curve.dt(0.0); + const VectorType dtEnd = curve.dt(1.0); + const VectorType dtdtStart = curve.dtdt(0.0); + const VectorType dtdtEnd = curve.dtdt(1.0); + + EXPECT_NEAR(pStart[0], 0.0, 1e-14); + EXPECT_NEAR(pStart[1], 0.0, 1e-14); + EXPECT_NEAR(dtStart[0], 4.0, 1e-12); + EXPECT_NEAR(dtStart[1], 0.0, 1e-12); + EXPECT_NEAR(dtdtStart[0], -8.0, 1e-12); + EXPECT_NEAR(dtdtStart[1], 8.0, 1e-12); + EXPECT_NEAR(curve.curvature(0.0), 0.5, 1e-12); + + EXPECT_NEAR(pEnd[0], 3.0, 1e-14); + EXPECT_NEAR(pEnd[1], 1.0, 1e-14); + EXPECT_NEAR(dtEnd[0], 4.0, 1e-12); + EXPECT_NEAR(dtEnd[1], 0.0, 1e-12); + EXPECT_NEAR(dtdtEnd[0], 0.0, 1e-12); + EXPECT_NEAR(dtdtEnd[1], 0.0, 1e-12); + EXPECT_NEAR(curve.curvature(1.0), 0.0, 1e-12); + EXPECT_NEAR(curve.curvatureDerivative(1.0), 0.0, 1e-12); + + const CoordType tLine = 0.75; + EXPECT_NEAR(curve.curvature(tLine), 0.0, 1e-12); + EXPECT_NEAR(curve.curvatureDerivative(tLine), 0.0, 1e-12); + + const CoordType eps = 1.e-4; + const CoordType tLeft = 0.5 - eps; + const CoordType tRight = 0.5 + eps; + + const PointType pMid = curve.evaluate(0.5); + const PointType pLeft = curve.evaluate(tLeft); + const PointType pRight = curve.evaluate(tRight); + + EXPECT_NEAR(pMid[0], 1.0, 1e-14); + EXPECT_NEAR(pMid[1], 1.0, 1e-14); + EXPECT_NEAR(pLeft[0], 1.0 - 4.0 * eps * eps, 1e-12); + EXPECT_NEAR(pLeft[1], 1.0 - 4.0 * eps + 4.0 * eps * eps, 1e-12); + EXPECT_NEAR(pRight[0], 1.0 + 4.0 * eps, 1e-12); + EXPECT_NEAR(pRight[1], 1.0, 1e-12); + + const VectorType dtLeft = curve.dt(tLeft); + const VectorType dtRight = curve.dt(tRight); + const VectorType dtdtLeft = curve.dtdt(tLeft); + const VectorType dtdtRight = curve.dtdt(tRight); + + EXPECT_NEAR(dtLeft[0], 8.0 * eps, 1e-10); + EXPECT_NEAR(dtLeft[1], 4.0 - 8.0 * eps, 1e-10); + EXPECT_NEAR(dtRight[0], 4.0, 1e-12); + EXPECT_NEAR(dtRight[1], 0.0, 1e-12); + + EXPECT_NEAR(dtdtLeft[0], -8.0, 1e-10); + EXPECT_NEAR(dtdtLeft[1], 8.0, 1e-10); + EXPECT_NEAR(dtdtRight[0], 0.0, 1e-12); + EXPECT_NEAR(dtdtRight[1], 0.0, 1e-12); + + // Position is continuous at the knot, but the sided derivatives are not. + EXPECT_GT((dtLeft - dtRight).norm(), 1.0); + EXPECT_GT((dtdtLeft - dtdtRight).norm(), 1.0); + + const CoordType kLeft = curve.curvature(tLeft); + const CoordType kRight = curve.curvature(tRight); + const CoordType expectedKLeft = + 1.0 / (2.0 * std::pow(1.0 - 4.0 * eps + 8.0 * eps * eps, 1.5)); + EXPECT_NEAR(kLeft, expectedKLeft, 1e-9); + EXPECT_NEAR(kRight, 0.0, 1e-12); +} + //------------------------------------------------------------------------------ TEST(primal_nurbscurve, nurbscurve_intersections) { diff --git a/src/axom/primal/tests/primal_rational_bezier.cpp b/src/axom/primal/tests/primal_rational_bezier.cpp index d8de494f4b..dc8172ef8a 100644 --- a/src/axom/primal/tests/primal_rational_bezier.cpp +++ b/src/axom/primal/tests/primal_rational_bezier.cpp @@ -220,12 +220,7 @@ TEST(primal_rationalbezier, curvature) for(const CoordType t : {0.0, 0.25, 0.5, 0.75, 1.0}) { EXPECT_NEAR(curve.curvature(t), 1.0, 1e-12); - - axom::Array curvatureDers; - curve.curvatureDerivatives(t, 2, curvatureDers); - ASSERT_EQ(curvatureDers.size(), 2); - EXPECT_NEAR(curvatureDers[0], 0.0, 1e-10); - EXPECT_NEAR(curvatureDers[1], 0.0, 1e-10); + EXPECT_NEAR(curve.curvatureDerivative(t), 0.0, 1e-10); } } From 706055c40b5d602e701f11feea93c3ff9bd106da Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 20 Jul 2026 14:18:06 -0700 Subject: [PATCH 746/986] Fixed a test --- src/axom/primal/tests/primal_bezier_curve.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/axom/primal/tests/primal_bezier_curve.cpp b/src/axom/primal/tests/primal_bezier_curve.cpp index 1ce2dfac5c..2704c54c56 100644 --- a/src/axom/primal/tests/primal_bezier_curve.cpp +++ b/src/axom/primal/tests/primal_bezier_curve.cpp @@ -404,7 +404,7 @@ TEST(primal_curvature_operator, curvature_derivative_3d) const VectorType D2 {0.0, -2.0, 0.0}; const VectorType D3 {0.0, 0.0, 0.0}; - EXPECT_NEAR(primal::curvatureDerivative(D1, D2, D3), -3.0 / std::sqrt(2.0), 1e-14); + EXPECT_NEAR(primal::curvatureDerivative(D1, D2, D3), 3.0 / std::sqrt(2.0), 1e-14); } //------------------------------------------------------------------------------ From 5a40cf8d54f0d00500ae6d9d0d9e5cb984f0604a Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 20 Jul 2026 14:21:38 -0700 Subject: [PATCH 747/986] Changed BezierCurve::curvature --- src/axom/primal/geometry/BezierCurve.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/axom/primal/geometry/BezierCurve.hpp b/src/axom/primal/geometry/BezierCurve.hpp index 65d259e84a..85ffda44c3 100644 --- a/src/axom/primal/geometry/BezierCurve.hpp +++ b/src/axom/primal/geometry/BezierCurve.hpp @@ -954,7 +954,10 @@ class BezierCurve */ double curvature(T t) const { - return axom::primal::curvature(dt(t), dtdt(t)); + PointType eval; + VectorType Dt, DtDt; + evaluateSecondDerivative(t, eval, Dt, DtDt); + return axom::primal::curvature(Dt, DtDt); } /*! From 09c952c9b496a4ee6dbc1e8909f5fc661be1ff4c Mon Sep 17 00:00:00 2001 From: Chris White Date: Mon, 20 Jul 2026 14:37:36 -0700 Subject: [PATCH 748/986] add basic sandbox config and skills files --- .agents/skills | 1 + .gitignore | 1 + sandbox-config.yaml | 4 + skills/building/SKILL.md | 66 ++++++ skills/building/scripts/determine_host_config | 207 ++++++++++++++++++ skills/building/scripts/is_compute_node | 26 +++ skills/cpp-style/SKILL.md | 58 +++++ skills/cpp-unit-testing/SKILL.md | 140 ++++++++++++ skills/execution_policy.yaml | 104 +++++++++ skills/git_policy.yaml | 92 ++++++++ 10 files changed, 699 insertions(+) create mode 120000 .agents/skills create mode 100644 sandbox-config.yaml create mode 100644 skills/building/SKILL.md create mode 100755 skills/building/scripts/determine_host_config create mode 100755 skills/building/scripts/is_compute_node create mode 100644 skills/cpp-style/SKILL.md create mode 100644 skills/cpp-unit-testing/SKILL.md create mode 100644 skills/execution_policy.yaml create mode 100644 skills/git_policy.yaml diff --git a/.agents/skills b/.agents/skills new file mode 120000 index 0000000000..42c5394a18 --- /dev/null +++ b/.agents/skills @@ -0,0 +1 @@ +../skills \ No newline at end of file diff --git a/.gitignore b/.gitignore index e59d1e724e..e918142159 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ uberenv_libs .idea *.opendb scripts/make_local_branch_from_fork_pr.sh +slirp.out diff --git a/sandbox-config.yaml b/sandbox-config.yaml new file mode 100644 index 0000000000..18cc05dc2d --- /dev/null +++ b/sandbox-config.yaml @@ -0,0 +1,4 @@ +mounts: + paths: + - "ro:/collab/usr/gapps/axom/devtools" + - "ro:/usr/WS1/axom/libs" diff --git a/skills/building/SKILL.md b/skills/building/SKILL.md new file mode 100644 index 0000000000..dc2f7bd575 --- /dev/null +++ b/skills/building/SKILL.md @@ -0,0 +1,66 @@ +--- +name: building +description: Instructions for building Axom +--- + +## Compute-node requirement (Codex) + +Only run compilation and test commands on a compute node. Determine this by running: + +```bash +./skills/building/scripts/is_compute_node +``` + +If it prints `login`, do **not** run `./config-build.py`, `cmake --build ...`, or `ctest ...`. Stop and ask the user to switch to/allocate a compute node, then continue once `is_compute_node` prints `compute`. + +## Axom build + +This repository builds Axom against externally provided TPLs (e.g., via Spack/uberenv + a host-config). + +1) Configure (recommended wrapper around CMake): + +```bash +./config-build.py -bp build -ip install -hc "$(./skills/building/scripts/determine_host_config)" --exportcompilercommands +``` + +2) Build and test: + +```bash +cmake --build build -j +ctest --test-dir build +``` + +## Common build options + +Common CMake options (and their defaults) live in `src/cmake/AxomOptions.cmake` and `src/cmake/CMakeBasics.cmake`. Pass them at configure time as `-D