// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced // at the Lawrence Livermore National Laboratory. All Rights reserved. See files // LICENSE and NOTICE for details. LLNL-CODE-806117. // // This file is part of the MFEM library. For more information and source code // availability visit https://mfem.org. // // MFEM is free software; you can redistribute it and/or modify it under the // terms of the BSD-3 license. We welcome feedback and contributions, see file // CONTRIBUTING.md for details. #include "mfem.hpp" #include "unit_tests.hpp" #include #include #include #include #include using namespace mfem; // Prefix string for a single element 2D mfem quad mesh std::string meshPrefixStr = "MFEM mesh v1.0" "\n\n" "dimension" "\n" "2" "\n\n" "elements" "\n" "1" "\n" "1 3 0 1 2 3" "\n\n" "boundary" "\n" "0" "\n\n"; // Nodal grid function for a C-shaped quadratic quadrilateral std::string CShapedNodesStr = "vertices" "\n" "4" "\n\n" "nodes" "\n" "FiniteElementSpace" "\n" "FiniteElementCollection: Quadratic" "\n" "VDim: 2" "\n" "Ordering: 1" "\n" "0 0" "\n" "0 2" "\n" "0 6" "\n" "0 8" "\n" "0 1" "\n" "-6 4" "\n" "0 7" "\n" "-8 4" "\n" "-7 4" "\n"; // Nodal grid function for a C-shaped quadratic quadrilateral embedded in 3D std::string EmbCShapedNodesStr = "vertices" "\n" "4" "\n\n" "nodes" "\n" "FiniteElementSpace" "\n" "FiniteElementCollection: Quadratic" "\n" "VDim: 3" "\n" "Ordering: 1" "\n" "0 0 0" "\n" "0 2 2" "\n" "0 6 6" "\n" "0 8 8" "\n" "0 1 1" "\n" "-6 4 4" "\n" "0 7 7" "\n" "-8 4 4" "\n" "-7 4 4" "\n"; TEST_CASE("InverseElementTransformation", "[InverseElementTransformation]") { typedef InverseElementTransformation InvTransform; const real_t tol = 2e-14; SECTION("{ C-shaped Q2 Quad }") { // Create quadratic with single C-shaped quadrilateral std::stringstream meshStr; meshStr << meshPrefixStr << CShapedNodesStr; Mesh mesh( meshStr ); REQUIRE( mesh.GetNE() == 1 ); REQUIRE( mesh.GetNodes() != nullptr ); // Optionally, dump mesh to disk bool dumpMesh = false; if (dumpMesh) { std::string filename = "c_shaped_quadratic_mesh"; VisItDataCollection dataCol(filename, &mesh); dataCol.Save(); } const int times = 100; const int dim = 2; // Create a uniform grid of integration points over the element const int geom = mesh.GetElementBaseGeometry(0); RefinedGeometry* ref = GlobGeometryRefiner.Refine(Geometry::Type(geom), times); const IntegrationRule& intRule = ref->RefPts; // Create a transformation IsoparametricTransformation tr; mesh.GetElementTransformation(0, &tr); Vector v(dim); const int npts = intRule.GetNPoints(); int pts_found = 0; real_t max_err = 0.0; for (int i=0; iRefPts; // Create a transformation IsoparametricTransformation tr; mesh.GetElementTransformation(0, &tr); Vector v(dim); const int npts = intRule.GetNPoints(); Vector orig_ref_space; Vector phys_space; Array elems; Array res_type; Vector res_ref_space; BatchInverseElementTransformation itransform(mesh); itransform.SetInitialGuessType(InverseElementTransformation::Center); orig_ref_space.SetSize(npts * dim); phys_space.SetSize(npts * sdim); elems.SetSize(npts); res_type.SetSize(npts); res_ref_space.SetSize(npts * dim); orig_ref_space.HostWrite(); phys_space.HostWrite(); elems.HostWrite(); for (int i=0; i( [](const Vector &xi, Vector &c) { c[0] = xi[0]; c[1] = xi[0] * xi[0]; })); mesh.Transform(coeff); } REQUIRE( mesh.GetNE() == 1 ); REQUIRE( mesh.GetNodes() != nullptr ); const int times = 100; const int dim = mesh.Dimension(); const int sdim = mesh.SpaceDimension(); REQUIRE( dim == 1 ); REQUIRE( sdim == 2 ); // Create a uniform grid of integration points over the element const int geom = mesh.GetElementBaseGeometry(0); RefinedGeometry* ref = GlobGeometryRefiner.Refine(Geometry::Type(geom), times); const IntegrationRule& intRule = ref->RefPts; // Create a transformation IsoparametricTransformation tr; mesh.GetElementTransformation(0, &tr); Vector v(dim); const int npts = intRule.GetNPoints(); Vector orig_ref_space; Vector phys_space; Array elems; Array res_type; Vector res_ref_space; BatchInverseElementTransformation itransform(mesh); itransform.SetInitialGuessType(InverseElementTransformation::Center); orig_ref_space.SetSize(npts * dim); phys_space.SetSize(npts * sdim); elems.SetSize(npts); res_type.SetSize(npts); res_ref_space.SetSize(npts * dim); orig_ref_space.HostWrite(); phys_space.HostWrite(); elems.HostWrite(); for (int i=0; i( [](const Vector &xi, Vector &c) { c[0] = xi[0]; c[1] = xi[0] * xi[0]; c[2] = 1 - xi[0] * xi[0]; })); mesh.Transform(coeff); } REQUIRE( mesh.GetNE() == 1 ); REQUIRE( mesh.GetNodes() != nullptr ); const int times = 100; const int dim = mesh.Dimension(); const int sdim = mesh.SpaceDimension(); REQUIRE( dim == 1 ); REQUIRE( sdim == 3 ); // Create a uniform grid of integration points over the element const int geom = mesh.GetElementBaseGeometry(0); RefinedGeometry* ref = GlobGeometryRefiner.Refine(Geometry::Type(geom), times); const IntegrationRule& intRule = ref->RefPts; // Create a transformation IsoparametricTransformation tr; mesh.GetElementTransformation(0, &tr); Vector v(dim); const int npts = intRule.GetNPoints(); Vector orig_ref_space; Vector phys_space; Array elems; Array res_type; Vector res_ref_space; BatchInverseElementTransformation itransform(mesh); itransform.SetInitialGuessType(InverseElementTransformation::Center); orig_ref_space.SetSize(npts * dim); phys_space.SetSize(npts * sdim); elems.SetSize(npts); res_type.SetSize(npts); res_ref_space.SetSize(npts * dim); orig_ref_space.HostWrite(); phys_space.HostWrite(); elems.HostWrite(); for (int i=0; iRefPts; // Create a transformation IsoparametricTransformation tr; mesh.GetElementTransformation(0, &tr); Vector v(dim); const int npts = intRule.GetNPoints(); Vector orig_ref_space; Vector phys_space; Array elems; Array res_type; Vector res_ref_space; BatchInverseElementTransformation itransform(mesh); // itransform.SetInitialGuessType(InverseElementTransformation::EdgeScan); // itransform.SetInitGuessRelOrder(3); // itransform.SetInitGuessPointsType(Quadrature1D::ClosedUniform); orig_ref_space.SetSize(npts * dim); phys_space.SetSize(npts * sdim); elems.SetSize(npts); res_type.SetSize(npts); res_ref_space.SetSize(npts * dim); orig_ref_space.HostWrite(); phys_space.HostWrite(); elems.HostWrite(); for (int i=0; iRefPts; // Create a transformation IsoparametricTransformation tr; mesh.GetElementTransformation(0, &tr); Vector v(dim); const int npts = intRule.GetNPoints(); Vector orig_ref_space; Vector phys_space; Array elems; Array res_type; Vector res_ref_space; BatchInverseElementTransformation itransform(mesh); itransform.SetInitialGuessType(InverseElementTransformation::Center); orig_ref_space.SetSize(npts * dim); phys_space.SetSize(npts * sdim); elems.SetSize(npts); res_type.SetSize(npts); res_ref_space.SetSize(npts * dim); orig_ref_space.HostWrite(); phys_space.HostWrite(); elems.HostWrite(); for (int i=0; i elems; Array res_type; Vector res_ref_space; BatchInverseElementTransformation itransform(mesh); itransform.SetInitialGuessType(InverseElementTransformation::EdgeScan); itransform.SetInitGuessOrder(3); itransform.SetInitGuessPointsType(Quadrature1D::ClosedUniform); orig_ref_space.SetSize(npts * dim); phys_space.SetSize(npts * dim); elems.SetSize(npts); res_type.SetSize(npts); res_ref_space.SetSize(npts * dim); orig_ref_space.HostWrite(); phys_space.HostWrite(); elems.HostWrite(); ElementTransformation &T = *mesh.GetElementTransformation(0); IntegrationPoint ip; Vector pt; pt.SetSize(dim); for (int i = 0; i < npts; i++) { elems[i] = 0; Geometry::GetRandomPoint(T.GetGeometryType(), ip); T.Transform(ip, pt); real_t tmp[3]; ip.Get(tmp, dim); for (int d = 0; d < dim; ++d) { orig_ref_space(i + d * npts) = tmp[d]; phys_space(i + d * npts) = pt(d); } } // now batch reverse transform itransform.Transform(phys_space, elems, res_type, res_ref_space); res_type.HostRead(); res_ref_space.HostReadWrite(); res_ref_space -= orig_ref_space; res_ref_space.HostRead(); int pts_found = 0; real_t max_err = 0; for (int i = 0; i < npts; ++i) { if (AsConst(res_type)[i] == InverseElementTransformation::Inside) { ++pts_found; for (int d = 0; d < dim; ++d) { max_err = fmax(max_err, fabs(AsConst(res_ref_space)[i + d * npts])); } } } CAPTURE(pts_found, npts, max_err); REQUIRE( pts_found == npts ); REQUIRE( max_err <= tol ); } SECTION("{ Multi Spiral Q20 Quad }") { // Load the spiral mesh from file: std::ifstream mesh_file("./data/quad-spiral-q20.mesh"); REQUIRE( mesh_file.good() ); const int npts = 100; // number of random points to test const int rand_seed = 189548; srand(rand_seed); Mesh mesh(mesh_file); mesh.UniformRefinement(); mesh.UniformRefinement(); mesh.UniformRefinement(); REQUIRE( mesh.Dimension() == 2 ); REQUIRE( mesh.SpaceDimension() == 2 ); REQUIRE(mesh.GetNE() == 4 * 4 * 4); std::mt19937 gen(rand_seed); std::uniform_int_distribution distr(0, mesh.GetNE() - 1); int dim = mesh.Dimension(); Vector orig_ref_space; Vector phys_space; Array elems; Array res_type; Vector res_ref_space; BatchInverseElementTransformation itransform(mesh); itransform.SetInitialGuessType(InverseElementTransformation::EdgeScan); itransform.SetInitGuessRelOrder(3 - 20); itransform.SetInitGuessPointsType(Quadrature1D::ClosedUniform); orig_ref_space.SetSize(npts * dim); phys_space.SetSize(npts * dim); elems.SetSize(npts); res_type.SetSize(npts); res_ref_space.SetSize(npts * dim); orig_ref_space.HostWrite(); phys_space.HostWrite(); elems.HostWrite(); IntegrationPoint ip; Vector pt; pt.SetSize(dim); for (int i = 0; i < npts; i++) { elems[i] = distr(gen); ElementTransformation &T = *mesh.GetElementTransformation(elems[i]); Geometry::GetRandomPoint(T.GetGeometryType(), ip); T.Transform(ip, pt); real_t tmp[3]; ip.Get(tmp, dim); for (int d = 0; d < dim; ++d) { orig_ref_space(i + d * npts) = tmp[d]; phys_space(i + d * npts) = pt(d); } } // now batch reverse transform itransform.Transform(phys_space, elems, res_type, res_ref_space); res_type.HostRead(); res_ref_space.HostReadWrite(); res_ref_space -= orig_ref_space; res_ref_space.HostRead(); int pts_found = 0; real_t max_err = 0; for (int i = 0; i < npts; ++i) { if (AsConst(res_type)[i] == InverseElementTransformation::Inside) { ++pts_found; for (int d = 0; d < dim; ++d) { max_err = fmax(max_err, fabs(AsConst(res_ref_space)[i + d * npts])); } } } CAPTURE(pts_found, npts, max_err); REQUIRE( pts_found == npts ); REQUIRE( max_err <= tol ); } SECTION("{ 3D Spiral }") { // Load the spiral mesh from file: std::ifstream mesh_file("./data/spiral_3D_p9.mesh"); REQUIRE( mesh_file.good() ); const int npts = 100; // number of random points to test const int rand_seed = 189548; srand(rand_seed); Mesh mesh(mesh_file); REQUIRE( mesh.Dimension() == 3 ); REQUIRE( mesh.SpaceDimension() == 3 ); REQUIRE( mesh.GetNE() == 1 ); int dim = mesh.Dimension(); Vector orig_ref_space; Vector phys_space; Array elems; Array res_type; Vector res_ref_space; BatchInverseElementTransformation itransform(mesh); orig_ref_space.SetSize(npts * dim); phys_space.SetSize(npts * dim); elems.SetSize(npts); res_type.SetSize(npts); res_ref_space.SetSize(npts * dim); orig_ref_space.HostWrite(); phys_space.HostWrite(); elems.HostWrite(); ElementTransformation &T = *mesh.GetElementTransformation(0); IntegrationPoint ip; Vector pt; pt.SetSize(dim); for (int i = 0; i < npts; i++) { elems[i] = 0; Geometry::GetRandomPoint(T.GetGeometryType(), ip); T.Transform(ip, pt); real_t tmp[3]; ip.Get(tmp, dim); for (int d = 0; d < dim; ++d) { orig_ref_space(i + d * npts) = tmp[d]; phys_space(i + d * npts) = pt(d); } } // now batch reverse transform itransform.Transform(phys_space, elems, res_type, res_ref_space); res_type.HostRead(); res_ref_space.HostReadWrite(); res_ref_space -= orig_ref_space; res_ref_space.HostRead(); int pts_found = 0; real_t max_err = 0; for (int i = 0; i < npts; ++i) { if (AsConst(res_type)[i] == InverseElementTransformation::Inside) { ++pts_found; for (int d = 0; d < dim; ++d) { max_err = fmax(max_err, fabs(AsConst(res_ref_space)[i + d * npts])); } } } CAPTURE(pts_found, npts, max_err); REQUIRE( pts_found == npts ); REQUIRE( max_err <= tol ); } SECTION("{ Multi 3D Spiral }") { // Load the spiral mesh from file: std::ifstream mesh_file("./data/spiral_3D_p9.mesh"); REQUIRE( mesh_file.good() ); const int npts = 100; // number of random points to test const int rand_seed = 189548; srand(rand_seed); Mesh mesh(mesh_file); mesh.UniformRefinement(); mesh.UniformRefinement(); mesh.UniformRefinement(); REQUIRE( mesh.Dimension() == 3 ); REQUIRE( mesh.SpaceDimension() == 3 ); REQUIRE( mesh.GetNE() == 8*8*8 ); std::mt19937 gen(rand_seed); std::uniform_int_distribution distr(0, mesh.GetNE() - 1); int dim = mesh.Dimension(); Vector orig_ref_space; Vector phys_space; Array elems; Array res_type; Vector res_ref_space; BatchInverseElementTransformation itransform(mesh); orig_ref_space.SetSize(npts * dim); phys_space.SetSize(npts * dim); elems.SetSize(npts); res_type.SetSize(npts); res_ref_space.SetSize(npts * dim); orig_ref_space.HostWrite(); phys_space.HostWrite(); elems.HostWrite(); IntegrationPoint ip; Vector pt; pt.SetSize(dim); for (int i = 0; i < npts; i++) { elems[i] = distr(gen); ElementTransformation &T = *mesh.GetElementTransformation(elems[i]); Geometry::GetRandomPoint(T.GetGeometryType(), ip); T.Transform(ip, pt); real_t tmp[3]; ip.Get(tmp, dim); for (int d = 0; d < dim; ++d) { orig_ref_space(i + d * npts) = tmp[d]; phys_space(i + d * npts) = pt(d); } } // now batch reverse transform itransform.Transform(phys_space, elems, res_type, res_ref_space); res_type.HostRead(); res_ref_space.HostReadWrite(); res_ref_space -= orig_ref_space; res_ref_space.HostRead(); int pts_found = 0; real_t max_err = 0; for (int i = 0; i < npts; ++i) { if (AsConst(res_type)[i] == InverseElementTransformation::Inside) { ++pts_found; for (int d = 0; d < dim; ++d) { max_err = fmax(max_err, fabs(AsConst(res_ref_space)[i + d * npts])); } } } CAPTURE(pts_found, npts, max_err); REQUIRE( pts_found == npts ); REQUIRE( max_err <= tol ); } }