// 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 "../mesh/mesh_headers.hpp" #include "eltrans/eltrans_basis.hpp" #include "fem.hpp" #include "eltrans.hpp" #include "../general/forall.hpp" #include namespace mfem { BatchInverseElementTransformation::BatchInverseElementTransformation() { static Kernels kernels; } BatchInverseElementTransformation::BatchInverseElementTransformation( const GridFunction &gf, MemoryType d_mt) : BatchInverseElementTransformation() { UpdateNodes(gf, d_mt); } BatchInverseElementTransformation::BatchInverseElementTransformation( const Mesh &mesh, MemoryType d_mt) : BatchInverseElementTransformation(*mesh.GetNodes(), d_mt) {} BatchInverseElementTransformation::~BatchInverseElementTransformation() {} void BatchInverseElementTransformation::UpdateNodes(const Mesh &mesh, MemoryType d_mt) { UpdateNodes(*mesh.GetNodes(), d_mt); } void BatchInverseElementTransformation::UpdateNodes(const GridFunction &gf, MemoryType d_mt) { MemoryType my_d_mt = (d_mt != MemoryType::DEFAULT) ? d_mt : Device::GetDeviceMemoryType(); gf_ = &gf; const FiniteElementSpace *fespace = gf.FESpace(); const int max_order = fespace->GetMaxElementOrder(); const int ndof1d = max_order + 1; int ND = ndof1d; const int dim = fespace->GetMesh()->Dimension(); MFEM_VERIFY(fespace->GetMesh()->GetNumGeometries(dim) <= 1, "Mixed meshes are not supported."); for (int d = 1; d < dim; ++d) { ND *= ndof1d; } const int vdim = fespace->GetVDim(); const int NE = fespace->GetNE(); node_pos.SetSize(vdim * ND * NE, my_d_mt); const FiniteElement *fe = fespace->GetTypicalFE(); const TensorBasisElement *tfe = dynamic_cast(fe); if (fespace->IsVariableOrder() || tfe == nullptr) { MFEM_VERIFY(fe->GetGeomType() == Geometry::SEGMENT || fe->GetGeomType() == Geometry::SQUARE || fe->GetGeomType() == Geometry::CUBE, "unsupported geometry type"); // project onto GLL nodes basis_type = BasisType::GaussLobatto; points1d = poly1d.GetPointsArray(max_order, BasisType::GaussLobatto); points1d->HostRead(); // either mixed order, or not a tensor basis node_pos.HostWrite(); real_t tmp[3]; int sdim = vdim; Vector pos(tmp, sdim); IntegrationPoint ip; int idcs[3]; for (int e = 0; e < NE; ++e) { fe = fespace->GetFE(e); for (int i = 0; i < ND; ++i) { idcs[0] = i % ndof1d; idcs[1] = i / ndof1d; idcs[2] = idcs[1] / ndof1d; idcs[1] = idcs[1] % ndof1d; ip.x = (*points1d)[idcs[0]]; ip.y = (*points1d)[idcs[1]]; ip.z = (*points1d)[idcs[2]]; gf.GetVectorValue(e, ip, pos); for (int d = 0; d < sdim; ++d) { node_pos[i + (d + e * sdim) * ND] = pos[d]; } } } } else { const Operator *elem_restr = fespace->GetElementRestriction(ElementDofOrdering::LEXICOGRAPHIC); elem_restr->Mult(gf, node_pos); basis_type = tfe->GetBasisType(); points1d = poly1d.GetPointsArray(max_order, basis_type); } } void BatchInverseElementTransformation::Transform(const Vector &pts, const Array &elems, Array &types, Vector &refs, bool use_device, Array *iters) const { if (!Device::Allows(Backend::DEVICE_MASK)) { // no devices available use_device = false; } const FiniteElementSpace *fespace = gf_->FESpace(); const FiniteElement *fe = fespace->GetTypicalFE(); const int dim = fe->GetDim(); const int vdim = fespace->GetVDim(); const int NE = fespace->GetNE(); const int order = fe->GetOrder(); int npts = elems.Size(); auto geom = fe->GetGeomType(); types.SetSize(npts); refs.SetSize(npts * dim); auto pptr = pts.Read(use_device); auto eptr = elems.Read(use_device); auto mptr = node_pos.Read(use_device); auto tptr = types.Write(use_device); auto xptr = refs.ReadWrite(use_device); int* iter_ptr = nullptr; if (iters != nullptr) { iters->SetSize(npts); iter_ptr = iters->Write(use_device); } auto nptr = points1d->Read(use_device); int ndof1d = points1d->Size(); switch (init_guess_type) { case InverseElementTransformation::Center: { real_t cx, cy, cz; auto ip0 = Geometries.GetCenter(geom); cx = ip0.x; cy = ip0.y; cz = ip0.z; switch (dim) { case 1: forall_switch(use_device, npts, [=] MFEM_HOST_DEVICE(int i) { xptr[i] = cx; }); break; case 2: forall_switch(use_device, npts, [=] MFEM_HOST_DEVICE(int i) { xptr[i] = cx; xptr[i + npts] = cy; }); break; case 3: forall_switch(use_device, npts, [=] MFEM_HOST_DEVICE(int i) { xptr[i] = cx; xptr[i + npts] = cy; xptr[i + 2 * npts] = cz; }); break; } } break; case InverseElementTransformation::ClosestRefNode: case InverseElementTransformation::ClosestPhysNode: { int nq1d = (qpts_order >= 0 ? qpts_order : std::max(order + rel_qpts_order, 0)) + 1; int btype = BasisType::GetNodalBasis(guess_points_type); if ((btype == BasisType::Invalid || btype == basis_type) && nq1d == ndof1d) { // special case: test points are basis nodal points if (init_guess_type == InverseElementTransformation::ClosestPhysNode) { FindClosestPhysDof::Run(geom, vdim, use_device, npts, NE, ndof1d, mptr, pptr, eptr, nptr, xptr); } else { FindClosestRefDof::Run(geom, vdim, use_device, npts, NE, ndof1d, mptr, pptr, eptr, nptr, xptr); } } else { BasisType::Check(btype); auto qpoints = poly1d.GetPointsArray(nq1d - 1, btype); auto qptr = qpoints->Read(use_device); if (init_guess_type == InverseElementTransformation::ClosestPhysNode) { FindClosestPhysPoint::Run(geom, vdim, use_device, npts, NE, ndof1d, nq1d, mptr, pptr, eptr, nptr, qptr, xptr); } else { FindClosestRefPoint::Run(geom, vdim, use_device, npts, NE, ndof1d, nq1d, mptr, pptr, eptr, nptr, qptr, xptr); } } } break; case InverseElementTransformation::GivenPoint: // nothing to do here break; case InverseElementTransformation::EdgeScan: { int nq1d = (qpts_order >= 0 ? qpts_order : std::max(order + rel_qpts_order, 0)) + 1; int btype = BasisType::GetNodalBasis(guess_points_type); if (btype == BasisType::Invalid) { // default to closed uniform points btype = BasisType::ClosedUniform; } auto qpoints = poly1d.GetPointsArray(nq1d - 1, btype); auto qptr = qpoints->Read(use_device); NewtonEdgeScan::Run(geom, vdim, solver_type, use_device, ref_tol, phys_rtol, max_iter, npts, NE, ndof1d, mptr, pptr, eptr, nptr, qptr, nq1d, tptr, iter_ptr, xptr); } return; } // general case: for each point, use guess inside refs NewtonSolve::Run(geom, vdim, solver_type, use_device, ref_tol, phys_rtol, max_iter, npts, NE, ndof1d, mptr, pptr, eptr, nptr, tptr, iter_ptr, xptr); } /// \cond DO_NOT_DOCUMENT namespace internal { // data for batch inverse transform newton solvers struct InvTNewtonSolverBase { real_t ref_tol; real_t phys_rtol; // physical space coordinates of mesh element nodes const real_t *mptr; // physical space point coordinates to find const real_t *pptr; // element indices const int *eptr; // newton solve result code int *tptr; // number of iterations taken int *iter_ptr; // result ref coords real_t *xptr; eltrans::Lagrange basis1d; int max_iter; // number of points in pptr int npts; }; // helper for computing dx = (pseudo)-inverse jac * [pt - F(x)] template struct InvTLinSolve; template <> struct InvTLinSolve<1, 1> { static void MFEM_HOST_DEVICE solve(const real_t *jac, const real_t *rhs, real_t *dx) { dx[0] = rhs[0] / jac[0]; } }; template <> struct InvTLinSolve<1, 2> { static void MFEM_HOST_DEVICE solve(const real_t *jac, const real_t *rhs, real_t *dx) { auto a00 = jac[0]; auto a10 = jac[1 * MFEM_THREAD_SIZE(x)]; real_t den = a00 * a00 + a10 * a10; dx[0] = (a00 * rhs[0] + a10 * rhs[1 * MFEM_THREAD_SIZE(x)]) / den; } }; template <> struct InvTLinSolve<1, 3> { static void MFEM_HOST_DEVICE solve(const real_t *jac, const real_t *rhs, real_t *dx) { auto a00 = jac[0]; auto a10 = jac[1 * MFEM_THREAD_SIZE(x)]; auto a20 = jac[2 * MFEM_THREAD_SIZE(x)]; real_t den = a00 * a00 + a10 * a10 + a20 * a20; dx[0] = (a00 * rhs[0] + a10 * rhs[1 * MFEM_THREAD_SIZE(x)] + a20 * rhs[2 * MFEM_THREAD_SIZE(x)]) / den; } }; template <> struct InvTLinSolve<2, 2> { static void MFEM_HOST_DEVICE solve(const real_t *jac, const real_t *rhs, real_t *dx) { auto a00 = jac[(0 + 0 * 2) * MFEM_THREAD_SIZE(x)]; auto a10 = jac[(1 + 0 * 2) * MFEM_THREAD_SIZE(x)]; auto a01 = jac[(0 + 1 * 2) * MFEM_THREAD_SIZE(x)]; auto a11 = jac[(1 + 1 * 2) * MFEM_THREAD_SIZE(x)]; real_t den = 1 / (a00 * a11 - a01 * a10); dx[0] = (a11 * rhs[0 * MFEM_THREAD_SIZE(x)] - a01 * rhs[1 * MFEM_THREAD_SIZE(x)]) * den; dx[1] = (a00 * rhs[1 * MFEM_THREAD_SIZE(x)] - a10 * rhs[0 * MFEM_THREAD_SIZE(x)]) * den; } }; template <> struct InvTLinSolve<2, 3> { static void MFEM_HOST_DEVICE solve(const real_t *jac, const real_t *rhs, real_t *dx) { auto a00 = jac[(0 + 0 * 3) * MFEM_THREAD_SIZE(x)]; auto a01 = jac[(0 + 1 * 3) * MFEM_THREAD_SIZE(x)]; auto a10 = jac[(1 + 0 * 3) * MFEM_THREAD_SIZE(x)]; auto a11 = jac[(1 + 1 * 3) * MFEM_THREAD_SIZE(x)]; auto a20 = jac[(2 + 0 * 3) * MFEM_THREAD_SIZE(x)]; auto a21 = jac[(2 + 1 * 3) * MFEM_THREAD_SIZE(x)]; // a00**2*a11**2 + a00**2*a21**2 - 2*a00*a01*a10*a11 - 2*a00*a01*a20*a21 + // a01**2*a10**2 + a01**2*a20**2 + a10**2*a21**2 - 2*a10*a11*a20*a21 + // a11**2*a20**2 real_t den = 1 / (a00 * a00 * a11 * a11 + a00 * a00 * a21 * a21 - 2 * a00 * a01 * a10 * a11 - 2 * a00 * a01 * a20 * a21 + a01 * a01 * a10 * a10 + a01 * a01 * a20 * a20 + a10 * a10 * a21 * a21 - 2 * a10 * a11 * a20 * a21 + a11 * a11 * a20 * a20); // x0*(a00*(a01**2 + a11**2 + a21**2) - a01*(a00*a01 + a10*a11 + a20*a21)) // + x1*(a10*(a01**2 + a11**2 + a21**2) - a11*(a00*a01 + a10*a11 + a20*a21)) // + x2*(a20*(a01**2 + a11**2 + a21**2) - a21*(a00*a01 + a10*a11 + a20*a21)) dx[0] = (rhs[0 * MFEM_THREAD_SIZE(x)] * (a00 * (a01 * a01 + a11 * a11 + a21 * a21) - a01 * (a00 * a01 + a10 * a11 + a20 * a21)) + rhs[1 * MFEM_THREAD_SIZE(x)] * (a10 * (a01 * a01 + a11 * a11 + a21 * a21) - a11 * (a00 * a01 + a10 * a11 + a20 * a21)) + rhs[2 * MFEM_THREAD_SIZE(x)] * (a20 * (a01 * a01 + a11 * a11 + a21 * a21) - a21 * (a00 * a01 + a10 * a11 + a20 * a21))) * den; // x0*(a01*(a00**2 + a10**2 + a20**2)-a00*(a00*a01 + a10*a11 + a20*a21)) // +x1*(a11*(a00**2 + a10**2 + a20**2)-a10*(a00*a01 + a10*a11 + a20*a21)) // +x2*(a21*(a00**2 + a10**2 + a20**2)-a20*(a00*a01 + a10*a11 + a20*a21)) dx[1] = (rhs[0 * MFEM_THREAD_SIZE(x)] * (a01 * (a00 * a00 + a10 * a10 + a20 * a20) - a00 * (a00 * a01 + a10 * a11 + a20 * a21)) + rhs[1 * MFEM_THREAD_SIZE(x)] * (a11 * (a00 * a00 + a10 * a10 + a20 * a20) - a10 * (a00 * a01 + a10 * a11 + a20 * a21)) + rhs[2 * MFEM_THREAD_SIZE(x)] * (a21 * (a00 * a00 + a10 * a10 + a20 * a20) - a20 * (a00 * a01 + a10 * a11 + a20 * a21))) * den; } }; template <> struct InvTLinSolve<3, 3> { static void MFEM_HOST_DEVICE solve(const real_t *jac, const real_t *rhs, real_t *dx) { auto a00 = jac[(0 + 0 * 3) * MFEM_THREAD_SIZE(x)]; auto a01 = jac[(0 + 1 * 3) * MFEM_THREAD_SIZE(x)]; auto a02 = jac[(0 + 2 * 3) * MFEM_THREAD_SIZE(x)]; auto a10 = jac[(1 + 0 * 3) * MFEM_THREAD_SIZE(x)]; auto a11 = jac[(1 + 1 * 3) * MFEM_THREAD_SIZE(x)]; auto a12 = jac[(1 + 2 * 3) * MFEM_THREAD_SIZE(x)]; auto a20 = jac[(2 + 0 * 3) * MFEM_THREAD_SIZE(x)]; auto a21 = jac[(2 + 1 * 3) * MFEM_THREAD_SIZE(x)]; auto a22 = jac[(2 + 2 * 3) * MFEM_THREAD_SIZE(x)]; real_t den = 1 / (a00 * a11 * a22 - a00 * a12 * a22 - a01 * a10 * a22 + a01 * a12 * a20 + a02 * a10 * a21 - a02 * a11 * a20); dx[0] = (rhs[0 * MFEM_THREAD_SIZE(x)] * (a11 * a22 - a12 * a21) - rhs[1 * MFEM_THREAD_SIZE(x)] * (a01 * a22 - a02 * a21) + rhs[2 * MFEM_THREAD_SIZE(x)] * (a01 * a12 - a02 * a11)) * den; dx[1] = (rhs[0 * MFEM_THREAD_SIZE(x)] * (a12 * a20 - a10 * a22) + rhs[1 * MFEM_THREAD_SIZE(x)] * (a00 * a22 - a02 * a20) - rhs[2 * MFEM_THREAD_SIZE(x)] * (a00 * a12 - a02 * a10)) * den; dx[2] = (rhs[0 * MFEM_THREAD_SIZE(x)] * (a10 * a21 - a11 * a20) - rhs[1 * MFEM_THREAD_SIZE(x)] * (a00 * a21 - a01 * a20) + rhs[2 * MFEM_THREAD_SIZE(x)] * (a00 * a11 - a01 * a10)) * den; } }; template struct ProjectType; template <> struct ProjectType { static MFEM_HOST_DEVICE bool project(real_t &x, real_t &dx) { x += dx; return false; } }; template <> struct ProjectType { static MFEM_HOST_DEVICE bool project(real_t &x, real_t &y, real_t &dx, real_t &dy) { x += dx; y += dy; return false; } }; template <> struct ProjectType { static MFEM_HOST_DEVICE bool project(real_t &x, real_t &y, real_t &z, real_t &dx, real_t &dy, real_t &dz) { x += dx; y += dy; z += dz; return false; } }; template struct ProjectType { template static MFEM_HOST_DEVICE bool project(Ts &&...args) { return eltrans::GeometryUtils::project(args...); } }; template struct InvTNewtonSolver; template struct InvTNewtonSolver : public InvTNewtonSolverBase { static int ndofs(int ndof1d) { return ndof1d; } // theoretically unbounded static constexpr MFEM_HOST_DEVICE int max_dof1d() { return 0x1000; } int MFEM_HOST_DEVICE operator()(int idx) const { // parallelize one team per pt constexpr int Dim = 1; int iter = 0; MFEM_SHARED real_t ref_coord[Dim]; // contiguous in team_x, then SDim MFEM_SHARED real_t phys_coord[SDim * max_team_x]; // contiguous in team_x, SDim, then Dim MFEM_SHARED real_t jac[SDim * Dim * max_team_x]; MFEM_SHARED bool term_flag[1]; MFEM_SHARED int res[1]; MFEM_SHARED real_t dx[Dim]; MFEM_SHARED real_t prev_dx[Dim]; MFEM_SHARED bool hit_bdr[1]; MFEM_SHARED bool prev_hit_bdr[1]; real_t phys_tol = 0; if (MFEM_THREAD_ID(x) == 0) { term_flag[0] = false; res[0] = InverseElementTransformation::Unknown; for (int d = 0; d < Dim; ++d) { ref_coord[d] = xptr[idx + d * npts]; dx[d] = 0; prev_dx[d] = 0; } for (int d = 0; d < SDim; ++d) { phys_tol += pptr[idx + d * npts] * pptr[idx + d * npts]; } phys_tol = fmax(phys_rtol * phys_rtol, phys_tol * phys_rtol * phys_rtol); hit_bdr[0] = prev_hit_bdr[0] = false; } // for each iteration while (true) { MFEM_SYNC_THREAD; // compute phys_coord and jacobian at the same time for (int d = 0; d < SDim; ++d) { phys_coord[MFEM_THREAD_ID(x) + d * MFEM_THREAD_SIZE(x)] = 0; } for (int i = 0; i < SDim * Dim; ++i) { jac[MFEM_THREAD_ID(x) + i * MFEM_THREAD_SIZE(x)] = 0; } MFEM_SYNC_THREAD; MFEM_FOREACH_THREAD(j0, x, basis1d.pN) { real_t b0, db0; basis1d.eval_d1(b0, db0, ref_coord[0], j0); for (int d = 0; d < SDim; ++d) { phys_coord[MFEM_THREAD_ID(x) + d * MFEM_THREAD_SIZE(x)] += mptr[j0 + (eptr[idx] * SDim + d) * basis1d.pN] * b0; jac[MFEM_THREAD_ID(x) + (d + 0 * SDim) * MFEM_THREAD_SIZE(x)] += mptr[j0 + (eptr[idx] * SDim + d) * basis1d.pN] * db0; } } for (int i = (MFEM_THREAD_SIZE(x) >> 1); i > 0; i >>= 1) { MFEM_SYNC_THREAD; int a = MFEM_THREAD_ID(x); int b = a + i; if (a < i && b < basis1d.pN) { for (int d = 0; d < SDim; ++d) { phys_coord[a + d * MFEM_THREAD_SIZE(x)] += phys_coord[b + d * MFEM_THREAD_SIZE(x)]; } for (int j = 0; j < SDim * Dim; ++j) { jac[a + j * MFEM_THREAD_SIZE(x)] += jac[b + j * MFEM_THREAD_SIZE(x)]; } } } MFEM_SYNC_THREAD; // rest of newton solve logic is serial, have thread 0 solve for it if (MFEM_THREAD_ID(x) == 0) { // compute objective function // f(x) = 1/2 |pt - F(x)|^2 real_t dist = 0; for (int d = 0; d < SDim; ++d) { real_t tmp = pptr[idx + d * npts] - phys_coord[d * MFEM_THREAD_SIZE(x)]; phys_coord[d * MFEM_THREAD_SIZE(x)] = tmp; dist += tmp * tmp; } // phys_coord now contains pt - F(x) // check for phys_tol convergence if (dist <= phys_tol) { // found solution res[0] = eltrans::GeometryUtils::inside( ref_coord[0]) ? InverseElementTransformation::Inside : InverseElementTransformation::Outside; tptr[idx] = res[0]; for (int d = 0; d < Dim; ++d) { xptr[idx + d * npts] = ref_coord[d]; } if (iter_ptr) { iter_ptr[idx] = iter + 1; } term_flag[0] = true; } else if (iter >= max_iter) { // terminate on max iterations tptr[idx] = InverseElementTransformation::Unknown; res[0] = InverseElementTransformation::Unknown; // might as well save where we failed at for (int d = 0; d < Dim; ++d) { xptr[idx + d * npts] = ref_coord[d]; } if (iter_ptr) { iter_ptr[idx] = iter + 1; } term_flag[0] = true; } else { // compute dx = (pseudo)-inverse jac * [pt - F(x)] InvTLinSolve::solve(jac, phys_coord, dx); hit_bdr[0] = ProjectType::project( ref_coord[0], dx[0]); // check for ref coord convergence or stagnation on boundary if (hit_bdr[0]) { if (prev_hit_bdr[0]) { real_t dx_change = 0; for (int d = 0; d < Dim; ++d) { real_t tmp = dx[d] - prev_dx[d]; dx_change += tmp * tmp; } if (dx_change <= ref_tol * ref_tol) { // stuck on the boundary tptr[idx] = InverseElementTransformation::Outside; res[0] = InverseElementTransformation::Outside; for (int d = 0; d < Dim; ++d) { xptr[idx + d * npts] = ref_coord[d]; } if (iter_ptr) { iter_ptr[idx] = iter + 1; } term_flag[0] = true; } } } prev_hit_bdr[0] = hit_bdr[0]; } } MFEM_SYNC_THREAD; if (term_flag[0]) { return res[0]; } MFEM_FOREACH_THREAD(d, x, Dim) { prev_dx[d] = dx[d]; } ++iter; } } }; template struct InvTNewtonSolver : public InvTNewtonSolverBase { static int ndofs(int ndof1d) { return ndof1d * ndof1d; } static constexpr MFEM_HOST_DEVICE int max_dof1d() { return 32; } int MFEM_HOST_DEVICE operator()(int idx) const { // parallelize one team per pt constexpr int Dim = 2; int iter = 0; MFEM_SHARED real_t ref_coord[Dim]; // contiguous in team_x, then SDim MFEM_SHARED real_t phys_coord[SDim * max_team_x]; MFEM_SHARED real_t basis0[max_dof1d()]; MFEM_SHARED real_t dbasis0[max_dof1d()]; MFEM_SHARED real_t basis1[max_dof1d()]; MFEM_SHARED real_t dbasis1[max_dof1d()]; // contiguous in team_x, SDim, then Dim MFEM_SHARED real_t jac[SDim * Dim * max_team_x]; MFEM_SHARED bool term_flag[1]; MFEM_SHARED int res[1]; MFEM_SHARED real_t dx[Dim]; MFEM_SHARED real_t prev_dx[Dim]; MFEM_SHARED bool hit_bdr[1]; MFEM_SHARED bool prev_hit_bdr[1]; real_t phys_tol = 0; if (MFEM_THREAD_ID(x) == 0) { term_flag[0] = false; res[0] = InverseElementTransformation::Unknown; hit_bdr[0] = false; prev_hit_bdr[0] = false; for (int d = 0; d < Dim; ++d) { ref_coord[d] = xptr[idx + d * npts]; dx[d] = 0; prev_dx[d] = 0; } for (int d = 0; d < SDim; ++d) { phys_tol += pptr[idx + d * npts] * pptr[idx + d * npts]; } phys_tol = fmax(phys_rtol * phys_rtol, phys_tol * phys_rtol * phys_rtol); } // for each iteration while (true) { MFEM_SYNC_THREAD; // compute phys_coord and jacobian at the same time for (int d = 0; d < SDim; ++d) { phys_coord[MFEM_THREAD_ID(x) + d * MFEM_THREAD_SIZE(x)] = 0; } for (int i = 0; i < SDim * Dim; ++i) { jac[MFEM_THREAD_ID(x) + i * MFEM_THREAD_SIZE(x)] = 0; } MFEM_FOREACH_THREAD(j0, x, basis1d.pN) { basis1d.eval_d1(basis0[j0], dbasis0[j0], ref_coord[0], j0); basis1d.eval_d1(basis1[j0], dbasis1[j0], ref_coord[1], j0); } MFEM_SYNC_THREAD; MFEM_FOREACH_THREAD(jidx, x, basis1d.pN * basis1d.pN) { int idcs[Dim]; idcs[0] = jidx % basis1d.pN; idcs[1] = jidx / basis1d.pN; for (int d = 0; d < SDim; ++d) { phys_coord[MFEM_THREAD_ID(x) + d * MFEM_THREAD_SIZE(x)] += mptr[idcs[0] + (idcs[1] + (eptr[idx] * SDim + d) * basis1d.pN) * basis1d.pN] * basis0[idcs[0]] * basis1[idcs[1]]; jac[MFEM_THREAD_ID(x) + (d + 0 * SDim) * MFEM_THREAD_SIZE(x)] += mptr[idcs[0] + (idcs[1] + (eptr[idx] * SDim + d) * basis1d.pN) * basis1d.pN] * dbasis0[idcs[0]] * basis1[idcs[1]]; jac[MFEM_THREAD_ID(x) + (d + 1 * SDim) * MFEM_THREAD_SIZE(x)] += mptr[idcs[0] + (idcs[1] + (eptr[idx] * SDim + d) * basis1d.pN) * basis1d.pN] * basis0[idcs[0]] * dbasis1[idcs[1]]; } } for (int i = (MFEM_THREAD_SIZE(x) >> 1); i > 0; i >>= 1) { MFEM_SYNC_THREAD; int a = MFEM_THREAD_ID(x); int b = a + i; if (a < i && b < basis1d.pN * basis1d.pN) { for (int d = 0; d < SDim; ++d) { phys_coord[a + d * MFEM_THREAD_SIZE(x)] += phys_coord[b + d * MFEM_THREAD_SIZE(x)]; } for (int j = 0; j < SDim * Dim; ++j) { jac[a + j * MFEM_THREAD_SIZE(x)] += jac[b + j * MFEM_THREAD_SIZE(x)]; } } } MFEM_SYNC_THREAD; // rest of newton solve logic is serial, have thread 0 solve for it if (MFEM_THREAD_ID(x) == 0) { // compute objective function // f(x) = 1/2 |pt - F(x)|^2 real_t dist = 0; for (int d = 0; d < SDim; ++d) { real_t tmp = pptr[idx + d * npts] - phys_coord[d * MFEM_THREAD_SIZE(x)]; phys_coord[d * MFEM_THREAD_SIZE(x)] = tmp; dist += tmp * tmp; } // phys_coord now contains pt - F(x) // check for phys_tol convergence if (dist <= phys_tol) { // found solution res[0] = eltrans::GeometryUtils::inside( ref_coord[0], ref_coord[1]) ? InverseElementTransformation::Inside : InverseElementTransformation::Outside; tptr[idx] = res[0]; for (int d = 0; d < Dim; ++d) { xptr[idx + d * npts] = ref_coord[d]; } if (iter_ptr) { iter_ptr[idx] = iter + 1; } term_flag[0] = true; } else if (iter >= max_iter) { // terminate on max iterations tptr[idx] = InverseElementTransformation::Unknown; res[0] = InverseElementTransformation::Unknown; // might as well save where we failed at for (int d = 0; d < Dim; ++d) { xptr[idx + d * npts] = ref_coord[d]; } if (iter_ptr) { iter_ptr[idx] = iter + 1; } term_flag[0] = true; } else { // compute dx = (pseudo)-inverse jac * [pt - F(x)] InvTLinSolve::solve(jac, phys_coord, dx); hit_bdr[0] = ProjectType::project( ref_coord[0], ref_coord[1], dx[0], dx[1]); // check for ref coord convergence or stagnation on boundary if (hit_bdr[0]) { if (prev_hit_bdr[0]) { real_t dx_change = 0; for (int d = 0; d < Dim; ++d) { real_t tmp = dx[d] - prev_dx[d]; dx_change += tmp * tmp; } if (dx_change <= ref_tol * ref_tol) { // stuck on the boundary tptr[idx] = InverseElementTransformation::Outside; res[0] = InverseElementTransformation::Outside; for (int d = 0; d < Dim; ++d) { xptr[idx + d * npts] = ref_coord[d]; } if (iter_ptr) { iter_ptr[idx] = iter + 1; } term_flag[0] = true; } } } prev_hit_bdr[0] = hit_bdr[0]; } } MFEM_SYNC_THREAD; if (term_flag[0]) { return res[0]; } MFEM_FOREACH_THREAD(d, x, Dim) { prev_dx[d] = dx[d]; } MFEM_SYNC_THREAD; ++iter; } } }; template struct InvTNewtonSolver : public InvTNewtonSolverBase { static int ndofs(int ndof1d) { return ndof1d * ndof1d * ndof1d; } static constexpr MFEM_HOST_DEVICE int max_dof1d() { return 32; } int MFEM_HOST_DEVICE operator()(int idx) const { // parallelize one team per pt constexpr int Dim = 3; int iter = 0; MFEM_SHARED real_t ref_coord[Dim]; // contiguous in team_x, then SDim MFEM_SHARED real_t phys_coord[SDim * max_team_x]; MFEM_SHARED real_t basis0[max_dof1d()]; MFEM_SHARED real_t dbasis0[max_dof1d()]; MFEM_SHARED real_t basis1[max_dof1d()]; MFEM_SHARED real_t dbasis1[max_dof1d()]; MFEM_SHARED real_t basis2[max_dof1d()]; MFEM_SHARED real_t dbasis2[max_dof1d()]; // contiguous in team_x, SDim, then Dim MFEM_SHARED real_t jac[SDim * Dim * max_team_x]; MFEM_SHARED bool term_flag[1]; MFEM_SHARED int res[1]; MFEM_SHARED real_t dx[Dim]; MFEM_SHARED real_t prev_dx[Dim]; MFEM_SHARED bool hit_bdr[1]; MFEM_SHARED bool prev_hit_bdr[1]; real_t phys_tol = 0; if (MFEM_THREAD_ID(x) == 0) { term_flag[0] = false; res[0] = InverseElementTransformation::Unknown; hit_bdr[0] = false; prev_hit_bdr[0] = false; for (int d = 0; d < Dim; ++d) { ref_coord[d] = xptr[idx + d * npts]; dx[d] = 0; prev_dx[d] = 0; } for (int d = 0; d < SDim; ++d) { phys_tol += pptr[idx + d * npts] * pptr[idx + d * npts]; } phys_tol = fmax(phys_rtol * phys_rtol, phys_tol * phys_rtol * phys_rtol); } // for each iteration while (true) { MFEM_SYNC_THREAD; // compute phys_coord and jacobian at the same time for (int d = 0; d < SDim; ++d) { phys_coord[MFEM_THREAD_ID(x) + d * MFEM_THREAD_SIZE(x)] = 0; } for (int i = 0; i < SDim * Dim; ++i) { jac[MFEM_THREAD_ID(x) + i * MFEM_THREAD_SIZE(x)] = 0; } MFEM_FOREACH_THREAD(j0, x, basis1d.pN) { basis1d.eval_d1(basis0[j0], dbasis0[j0], ref_coord[0], j0); basis1d.eval_d1(basis1[j0], dbasis1[j0], ref_coord[1], j0); basis1d.eval_d1(basis2[j0], dbasis2[j0], ref_coord[2], j0); } MFEM_SYNC_THREAD; MFEM_FOREACH_THREAD(jidx, x, basis1d.pN * basis1d.pN * basis1d.pN) { int idcs[Dim]; idcs[0] = jidx % basis1d.pN; idcs[1] = jidx / basis1d.pN; idcs[2] = idcs[1] / basis1d.pN; idcs[1] %= basis1d.pN; for (int d = 0; d < SDim; ++d) { phys_coord[MFEM_THREAD_ID(x) + d * MFEM_THREAD_SIZE(x)] += mptr[idcs[0] + (idcs[1] + (idcs[2] + (eptr[idx] * SDim + d) * basis1d.pN) * basis1d.pN) * basis1d.pN] * basis0[idcs[0]] * basis1[idcs[1]] * basis2[idcs[2]]; jac[MFEM_THREAD_ID(x) + (d + 0 * SDim) * MFEM_THREAD_SIZE(x)] += mptr[idcs[0] + (idcs[1] + (idcs[2] + (eptr[idx] * SDim + d) * basis1d.pN) * basis1d.pN) * basis1d.pN] * dbasis0[idcs[0]] * basis1[idcs[1]] * basis2[idcs[2]]; jac[MFEM_THREAD_ID(x) + (d + 1 * SDim) * MFEM_THREAD_SIZE(x)] += mptr[idcs[0] + (idcs[1] + (idcs[2] + (eptr[idx] * SDim + d) * basis1d.pN) * basis1d.pN) * basis1d.pN] * basis0[idcs[0]] * dbasis1[idcs[1]] * basis2[idcs[2]]; jac[MFEM_THREAD_ID(x) + (d + 2 * SDim) * MFEM_THREAD_SIZE(x)] += mptr[idcs[0] + (idcs[1] + (idcs[2] + (eptr[idx] * SDim + d) * basis1d.pN) * basis1d.pN) * basis1d.pN] * basis0[idcs[0]] * basis1[idcs[1]] * dbasis2[idcs[2]]; } } for (int i = (MFEM_THREAD_SIZE(x) >> 1); i > 0; i >>= 1) { MFEM_SYNC_THREAD; int a = MFEM_THREAD_ID(x); int b = a + i; if (a < i && b < basis1d.pN * basis1d.pN * basis1d.pN) { for (int d = 0; d < SDim; ++d) { phys_coord[a + d * MFEM_THREAD_SIZE(x)] += phys_coord[b + d * MFEM_THREAD_SIZE(x)]; } for (int j = 0; j < SDim * Dim; ++j) { jac[a + j * MFEM_THREAD_SIZE(x)] += jac[b + j * MFEM_THREAD_SIZE(x)]; } } } MFEM_SYNC_THREAD; // rest of newton solve logic is serial, have thread 0 solve for it if (MFEM_THREAD_ID(x) == 0) { // compute objective function // f(x) = 1/2 |pt - F(x)|^2 real_t dist = 0; for (int d = 0; d < SDim; ++d) { real_t tmp = pptr[idx + d * npts] - phys_coord[d * MFEM_THREAD_SIZE(x)]; phys_coord[d * MFEM_THREAD_SIZE(x)] = tmp; dist += tmp * tmp; } // phys_coord now contains pt - F(x) // check for phys_tol convergence if (dist <= phys_tol) { // found solution res[0] = eltrans::GeometryUtils::inside( ref_coord[0], ref_coord[1], ref_coord[2]) ? InverseElementTransformation::Inside : InverseElementTransformation::Outside; tptr[idx] = res[0]; for (int d = 0; d < Dim; ++d) { xptr[idx + d * npts] = ref_coord[d]; } if (iter_ptr) { iter_ptr[idx] = iter + 1; } term_flag[0] = true; } else if (iter >= max_iter) { // terminate on max iterations tptr[idx] = InverseElementTransformation::Unknown; res[0] = InverseElementTransformation::Unknown; // might as well save where we failed at for (int d = 0; d < Dim; ++d) { xptr[idx + d * npts] = ref_coord[d]; } if (iter_ptr) { iter_ptr[idx] = iter + 1; } term_flag[0] = true; } else { // compute dx = (pseudo)-inverse jac * [pt - F(x)] InvTLinSolve::solve(jac, phys_coord, dx); hit_bdr[0] = ProjectType::project( ref_coord[0], ref_coord[1], ref_coord[2], dx[0], dx[1], dx[2]); // check for ref coord convergence or stagnation on boundary if (hit_bdr[0]) { if (prev_hit_bdr[0]) { real_t dx_change = 0; for (int d = 0; d < Dim; ++d) { real_t tmp = dx[d] - prev_dx[d]; dx_change += tmp * tmp; } if (dx_change <= ref_tol * ref_tol) { // stuck on the boundary tptr[idx] = InverseElementTransformation::Outside; res[0] = InverseElementTransformation::Outside; for (int d = 0; d < Dim; ++d) { xptr[idx + d * npts] = ref_coord[d]; } if (iter_ptr) { iter_ptr[idx] = iter + 1; } term_flag[0] = true; } } } } } MFEM_SYNC_THREAD; if (term_flag[0]) { return res[0]; } MFEM_FOREACH_THREAD(d, x, Dim) { prev_dx[d] = dx[d]; } ++iter; } } }; // data for finding the batch transform initial guess co-located at dofs struct DofFinderBase { // physical space coordinates of mesh element nodes const real_t *mptr; // physical space point coordinates to find const real_t *pptr; // element indices const int *eptr; // reference space nodes to test const real_t *qptr; // initial guess results real_t *xptr; eltrans::Lagrange basis1d; // number of points in pptr int npts; }; template struct PhysDofFinder; template struct PhysDofFinder : public DofFinderBase { static int ndofs(int ndofs1d) { return ndofs1d; } void MFEM_HOST_DEVICE operator()(int idx) const { constexpr int Dim = 1; // L-2 norm squared MFEM_SHARED real_t dists[max_team_x]; MFEM_SHARED real_t ref_buf[Dim * max_team_x]; MFEM_FOREACH_THREAD(i, x, max_team_x) { #ifdef MFEM_USE_DOUBLE dists[i] = HUGE_VAL; #else dists[i] = HUGE_VALF; #endif } MFEM_SYNC_THREAD; // team serial portion MFEM_FOREACH_THREAD(i, x, basis1d.pN) { real_t phys_coord[SDim]; for (int d = 0; d < SDim; ++d) { phys_coord[d] = mptr[i + (d + eptr[idx] * SDim) * basis1d.pN]; } real_t dist = 0; // L-2 norm squared for (int d = 0; d < SDim; ++d) { real_t tmp = phys_coord[d] - pptr[idx + d * npts]; dist += tmp * tmp; } if (dist < dists[MFEM_THREAD_ID(x)]) { // closer guess in physical space dists[MFEM_THREAD_ID(x)] = dist; ref_buf[MFEM_THREAD_ID(x)] = basis1d.z[i]; } } // now do tree reduce for (int i = (MFEM_THREAD_SIZE(x) >> 1); i > 0; i >>= 1) { MFEM_SYNC_THREAD; if (MFEM_THREAD_ID(x) < i) { if (dists[MFEM_THREAD_ID(x) + i] < dists[MFEM_THREAD_ID(x)]) { dists[MFEM_THREAD_ID(x)] = dists[MFEM_THREAD_ID(x) + i]; ref_buf[MFEM_THREAD_ID(x)] = ref_buf[MFEM_THREAD_ID(x) + i]; } } } // write results out // not needed in 1D // MFEM_SYNC_THREAD; if (MFEM_THREAD_ID(x) == 0) { xptr[idx] = ref_buf[0]; } } }; template struct PhysDofFinder : public DofFinderBase { static int ndofs(int ndofs1d) { return ndofs1d * ndofs1d; } void MFEM_HOST_DEVICE operator()(int idx) const { constexpr int Dim = 2; int n = basis1d.pN * basis1d.pN; if (n > max_team_x) { n = max_team_x; } // L-2 norm squared MFEM_SHARED real_t dists[max_team_x]; MFEM_SHARED real_t ref_buf[Dim * max_team_x]; MFEM_FOREACH_THREAD(i, x, max_team_x) { #ifdef MFEM_USE_DOUBLE dists[i] = HUGE_VAL; #else dists[i] = HUGE_VALF; #endif } // team serial portion MFEM_FOREACH_THREAD(j, x, basis1d.pN * basis1d.pN) { real_t phys_coord[SDim] = {0}; int idcs[Dim]; idcs[0] = j % basis1d.pN; idcs[1] = j / basis1d.pN; for (int d = 0; d < SDim; ++d) { phys_coord[d] = mptr[idcs[0] + (idcs[1] + (d + eptr[idx] * SDim) * basis1d.pN) * basis1d.pN]; } real_t dist = 0; // L-2 norm squared for (int d = 0; d < SDim; ++d) { real_t tmp = pptr[idx + d * npts] - phys_coord[d]; dist += tmp * tmp; } if (dist < dists[MFEM_THREAD_ID(x)]) { // closer guess in physical space dists[MFEM_THREAD_ID(x)] = dist; for (int d = 0; d < Dim; ++d) { ref_buf[MFEM_THREAD_ID(x) + d * n] = basis1d.z[idcs[d]]; } } } // now do tree reduce for (int i = (MFEM_THREAD_SIZE(x) >> 1); i > 0; i >>= 1) { MFEM_SYNC_THREAD; if (MFEM_THREAD_ID(x) < i) { if (dists[MFEM_THREAD_ID(x) + i] < dists[MFEM_THREAD_ID(x)]) { dists[MFEM_THREAD_ID(x)] = dists[MFEM_THREAD_ID(x) + i]; for (int d = 0; d < Dim; ++d) { ref_buf[MFEM_THREAD_ID(x) + d * n] = ref_buf[MFEM_THREAD_ID(x) + i + d * n]; } } } } // write results out MFEM_SYNC_THREAD; MFEM_FOREACH_THREAD(d, x, Dim) { xptr[idx + d * npts] = ref_buf[d * n]; } } }; template struct PhysDofFinder : public DofFinderBase { static int ndofs(int ndofs1d) { return ndofs1d * ndofs1d * ndofs1d; } void MFEM_HOST_DEVICE operator()(int idx) const { constexpr int Dim = 3; int n = basis1d.pN * basis1d.pN * basis1d.pN; if (n > max_team_x) { n = max_team_x; } // L-2 norm squared MFEM_SHARED real_t dists[max_team_x]; MFEM_SHARED real_t ref_buf[Dim * max_team_x]; MFEM_FOREACH_THREAD(i, x, max_team_x) { #ifdef MFEM_USE_DOUBLE dists[i] = HUGE_VAL; #else dists[i] = HUGE_VALF; #endif } // team serial portion MFEM_FOREACH_THREAD(j, x, basis1d.pN * basis1d.pN * basis1d.pN) { real_t phys_coord[SDim] = {0}; int idcs[Dim]; idcs[0] = j % basis1d.pN; idcs[1] = j / basis1d.pN; idcs[2] = idcs[1] / basis1d.pN; idcs[1] = idcs[1] % basis1d.pN; for (int d = 0; d < SDim; ++d) { phys_coord[d] = mptr[idcs[0] + (idcs[1] + (idcs[2] + (d + eptr[idx] * SDim) * basis1d.pN) * basis1d.pN) * basis1d.pN]; } real_t dist = 0; // L-2 norm squared for (int d = 0; d < SDim; ++d) { real_t tmp = pptr[idx + d * npts] - phys_coord[d]; dist += tmp * tmp; } if (dist < dists[MFEM_THREAD_ID(x)]) { // closer guess in physical space dists[MFEM_THREAD_ID(x)] = dist; for (int d = 0; d < Dim; ++d) { ref_buf[MFEM_THREAD_ID(x) + d * n] = basis1d.z[idcs[d]]; } } } // now do tree reduce for (int i = (MFEM_THREAD_SIZE(x) >> 1); i > 0; i >>= 1) { MFEM_SYNC_THREAD; if (MFEM_THREAD_ID(x) < i) { if (dists[MFEM_THREAD_ID(x) + i] < dists[MFEM_THREAD_ID(x)]) { dists[MFEM_THREAD_ID(x)] = dists[MFEM_THREAD_ID(x) + i]; for (int d = 0; d < Dim; ++d) { ref_buf[MFEM_THREAD_ID(x) + d * n] = ref_buf[MFEM_THREAD_ID(x) + i + d * n]; } } } } // write results out MFEM_SYNC_THREAD; MFEM_FOREACH_THREAD(d, x, Dim) { xptr[idx + d * npts] = ref_buf[d * n]; } } }; // data for finding the batch transform initial guess struct NodeFinderBase { // physical space coordinates of mesh element nodes const real_t *mptr; // physical space point coordinates to find const real_t *pptr; // element indices const int *eptr; // reference space nodes to test const real_t *qptr; // initial guess results real_t *xptr; eltrans::Lagrange basis1d; // number of points in pptr int npts; // number of points per element along each dimension to test int nq1d; // total number of points to test int nq; }; template struct PhysNodeFinder; template struct PhysNodeFinder : public NodeFinderBase { static int compute_nq(int nq1d) { return nq1d; } static int ndofs(int ndof1d) { return ndof1d; } void MFEM_HOST_DEVICE operator()(int idx) const { constexpr int Dim = 1; // L-2 norm squared MFEM_SHARED real_t dists[max_team_x]; MFEM_SHARED real_t ref_buf[Dim * max_team_x]; MFEM_FOREACH_THREAD(i, x, max_team_x) { #ifdef MFEM_USE_DOUBLE dists[i] = HUGE_VAL; #else dists[i] = HUGE_VALF; #endif } MFEM_SYNC_THREAD; // team serial portion MFEM_FOREACH_THREAD(i, x, nq) { real_t phys_coord[SDim] = {0}; for (int j0 = 0; j0 < basis1d.pN; ++j0) { real_t b = basis1d.eval(qptr[i], j0); for (int d = 0; d < SDim; ++d) { phys_coord[d] += mptr[j0 + (d + eptr[idx] * SDim) * basis1d.pN] * b; } } real_t dist = 0; // L-2 norm squared for (int d = 0; d < SDim; ++d) { real_t tmp = phys_coord[d] - pptr[idx + d * npts]; dist += tmp * tmp; } if (dist < dists[MFEM_THREAD_ID(x)]) { // closer guess in physical space dists[MFEM_THREAD_ID(x)] = dist; ref_buf[MFEM_THREAD_ID(x)] = qptr[i]; } } // now do tree reduce for (int i = (MFEM_THREAD_SIZE(x) >> 1); i > 0; i >>= 1) { MFEM_SYNC_THREAD; if (MFEM_THREAD_ID(x) < i) { if (dists[MFEM_THREAD_ID(x) + i] < dists[MFEM_THREAD_ID(x)]) { dists[MFEM_THREAD_ID(x)] = dists[MFEM_THREAD_ID(x) + i]; ref_buf[MFEM_THREAD_ID(x)] = ref_buf[MFEM_THREAD_ID(x) + i]; } } } // write results out // not needed in 1D // MFEM_SYNC_THREAD; if (MFEM_THREAD_ID(x) == 0) { xptr[idx] = ref_buf[0]; } } }; template struct PhysNodeFinder : public NodeFinderBase { static int compute_nq(int nq1d) { return nq1d * nq1d; } static int ndofs(int ndof1d) { return ndof1d * ndof1d; } void MFEM_HOST_DEVICE operator()(int idx) const { constexpr int Dim = 2; constexpr int max_dof1d = 32; int n = (nq < max_team_x) ? nq : max_team_x; // L-2 norm squared MFEM_SHARED real_t dists[max_team_x]; MFEM_SHARED real_t ref_buf[Dim * max_team_x]; MFEM_SHARED real_t basis[max_dof1d * max_q1d]; MFEM_FOREACH_THREAD(i, x, max_team_x) { #ifdef MFEM_USE_DOUBLE dists[i] = HUGE_VAL; #else dists[i] = HUGE_VALF; #endif } MFEM_FOREACH_THREAD(j0, x, nq1d) { for (int i0 = 0; i0 < basis1d.pN; ++i0) { basis[j0 + i0 * nq1d] = basis1d.eval(qptr[j0], i0); } } MFEM_SYNC_THREAD; // team serial portion MFEM_FOREACH_THREAD(j, x, nq) { real_t phys_coord[SDim] = {0}; int idcs[Dim]; idcs[0] = j % nq1d; idcs[1] = j / nq1d; for (int i1 = 0; i1 < basis1d.pN; ++i1) { for (int i0 = 0; i0 < basis1d.pN; ++i0) { real_t b = basis[idcs[0] + i0 * nq1d] * basis[idcs[1] + i1 * nq1d]; for (int d = 0; d < SDim; ++d) { phys_coord[d] += mptr[i0 + (i1 + (d + eptr[idx] * SDim) * basis1d.pN) * basis1d.pN] * b; } } } real_t dist = 0; // L-2 norm squared for (int d = 0; d < SDim; ++d) { real_t tmp = pptr[idx + d * npts] - phys_coord[d]; dist += tmp * tmp; } if (dist < dists[MFEM_THREAD_ID(x)]) { // closer guess in physical space dists[MFEM_THREAD_ID(x)] = dist; for (int d = 0; d < Dim; ++d) { ref_buf[MFEM_THREAD_ID(x) + d * n] = qptr[idcs[d]]; } } } // now do tree reduce for (int i = (MFEM_THREAD_SIZE(x) >> 1); i > 0; i >>= 1) { MFEM_SYNC_THREAD; if (MFEM_THREAD_ID(x) < i) { if (dists[MFEM_THREAD_ID(x) + i] < dists[MFEM_THREAD_ID(x)]) { dists[MFEM_THREAD_ID(x)] = dists[MFEM_THREAD_ID(x) + i]; for (int d = 0; d < Dim; ++d) { ref_buf[MFEM_THREAD_ID(x) + d * n] = ref_buf[MFEM_THREAD_ID(x) + i + d * n]; } } } } // write results out MFEM_SYNC_THREAD; MFEM_FOREACH_THREAD(d, x, Dim) { xptr[idx + d * npts] = ref_buf[d * n]; } } }; template struct PhysNodeFinder : public NodeFinderBase { static int compute_nq(int nq1d) { return nq1d * nq1d * nq1d; } static int ndofs(int ndof1d) { return ndof1d * ndof1d * ndof1d; } void MFEM_HOST_DEVICE operator()(int idx) const { constexpr int Dim = 3; constexpr int max_dof1d = 32; int n = (nq < max_team_x) ? nq : max_team_x; // L-2 norm squared MFEM_SHARED real_t dists[max_team_x]; MFEM_SHARED real_t ref_buf[Dim * max_team_x]; // contiguous in quad MFEM_SHARED real_t basis[max_dof1d * max_q1d]; MFEM_FOREACH_THREAD(i, x, max_team_x) { #ifdef MFEM_USE_DOUBLE dists[i] = HUGE_VAL; #else dists[i] = HUGE_VALF; #endif } MFEM_FOREACH_THREAD(j0, x, nq1d) { for (int i0 = 0; i0 < basis1d.pN; ++i0) { basis[j0 + i0 * nq1d] = basis1d.eval(qptr[j0], i0); } } MFEM_SYNC_THREAD; // team serial portion MFEM_FOREACH_THREAD(j, x, nq) { real_t phys_coord[SDim] = {0}; int idcs[Dim]; idcs[0] = j % nq1d; idcs[1] = j / nq1d; idcs[2] = idcs[1] / nq1d; idcs[1] = idcs[1] % nq1d; for (int i2 = 0; i2 < basis1d.pN; ++i2) { for (int i1 = 0; i1 < basis1d.pN; ++i1) { for (int i0 = 0; i0 < basis1d.pN; ++i0) { real_t b = basis[idcs[0] + i0 * nq1d] * basis[idcs[1] + i1 * nq1d] * basis[idcs[2] + i2 * nq1d]; for (int d = 0; d < SDim; ++d) { phys_coord[d] += mptr[i0 + (i1 + (i2 + (d + eptr[idx] * SDim) * basis1d.pN) * basis1d.pN) * basis1d.pN] * b; } } } } real_t dist = 0; // L-2 norm squared for (int d = 0; d < SDim; ++d) { real_t tmp = pptr[idx + d * npts] - phys_coord[d]; dist += tmp * tmp; } if (dist < dists[MFEM_THREAD_ID(x)]) { // closer guess in physical space dists[MFEM_THREAD_ID(x)] = dist; for (int d = 0; d < Dim; ++d) { ref_buf[MFEM_THREAD_ID(x) + d * n] = qptr[idcs[d]]; } } } // now do tree reduce for (int i = (MFEM_THREAD_SIZE(x) >> 1); i > 0; i >>= 1) { MFEM_SYNC_THREAD; if (MFEM_THREAD_ID(x) < i) { if (dists[MFEM_THREAD_ID(x) + i] < dists[MFEM_THREAD_ID(x)]) { dists[MFEM_THREAD_ID(x)] = dists[MFEM_THREAD_ID(x) + i]; for (int d = 0; d < Dim; ++d) { ref_buf[MFEM_THREAD_ID(x) + d * n] = ref_buf[MFEM_THREAD_ID(x) + i + d * n]; } } } } // write results out MFEM_SYNC_THREAD; MFEM_FOREACH_THREAD(d, x, Dim) { xptr[idx + d * npts] = ref_buf[d * n]; } } }; template static void ClosestPhysNodeImpl(int npts, int nelems, int ndof1d, int nq1d, const real_t *mptr, const real_t *pptr, const int *eptr, const real_t *nptr, const real_t *qptr, real_t *xptr) { constexpr int max_team_x = 64; constexpr int max_q1d = 128; PhysNodeFinder func; MFEM_VERIFY(ndof1d <= 32, "maximum of 32 dofs per dim is allowed"); MFEM_VERIFY(nq1d <= max_q1d, "maximum of 128 test points per dim is allowed"); func.basis1d.z = nptr; func.basis1d.pN = ndof1d; func.mptr = mptr; func.pptr = pptr; func.eptr = eptr; func.qptr = qptr; func.xptr = xptr; func.npts = npts; func.nq1d = nq1d; func.nq = func.compute_nq(nq1d); if (use_device) { // team_x must be a power of 2 int team_x = max_team_x; while (true) { if (team_x <= func.nq) { break; } team_x >>= 1; } team_x = std::min(max_team_x, 2 * team_x); forall_2D(npts, team_x, 1, func); } else { forall_switch(false, npts, func); } } template static void ClosestPhysDofImpl(int npts, int nelems, int ndof1d, const real_t *mptr, const real_t *pptr, const int *eptr, const real_t *nptr, real_t *xptr) { constexpr int max_team_x = 64; PhysDofFinder func; func.basis1d.z = nptr; func.basis1d.pN = ndof1d; func.mptr = mptr; func.pptr = pptr; func.eptr = eptr; func.xptr = xptr; func.npts = npts; if (use_device) { int team_x = max_team_x; int ndof = func.ndofs(ndof1d); while (true) { if (team_x <= ndof) { break; } team_x >>= 1; } team_x = std::min(max_team_x, 2 * team_x); forall_2D(npts, team_x, 1, func); } else { forall_switch(false, npts, func); } } template static void ClosestRefDofImpl(int npts, int nelems, int ndof1d, const real_t *mptr, const real_t *pptr, const int *eptr, const real_t *nptr, real_t *xptr) { // TODO MFEM_ABORT("ClostestRefDofImpl not implemented yet"); } template static void ClosestRefNodeImpl(int npts, int nelems, int ndof1d, int nq1d, const real_t *mptr, const real_t *pptr, const int *eptr, const real_t *nptr, const real_t *qptr, real_t *xptr) { // TODO MFEM_ABORT("ClostestRefNodeImpl not implemented yet"); } template static void NewtonSolveImpl(real_t ref_tol, real_t phys_rtol, int max_iter, int npts, int nelems, int ndof1d, const real_t *mptr, const real_t *pptr, const int *eptr, const real_t *nptr, int *tptr, int *iter_ptr, real_t *xptr) { constexpr int max_team_x = use_device ? 64 : 1; InvTNewtonSolver func; MFEM_VERIFY(ndof1d <= func.max_dof1d(), "exceeded max_dof1d limit (32 for 2D/3D)"); func.ref_tol = ref_tol; func.phys_rtol = phys_rtol; func.max_iter = max_iter; func.basis1d.z = nptr; func.basis1d.pN = ndof1d; func.mptr = mptr; func.pptr = pptr; func.eptr = eptr; func.xptr = xptr; func.iter_ptr = iter_ptr; func.tptr = tptr; func.npts = npts; if (use_device) { int team_x = max_team_x; int ndof = func.ndofs(ndof1d); while (true) { if (team_x <= ndof) { break; } team_x >>= 1; } team_x = std::min(max_team_x, 2 * team_x); forall_2D(npts, team_x, 1, func); } else { forall_switch(false, npts, func); } } template struct InvTNewtonEdgeScanner { InvTNewtonSolver solver; // 1D ref space initial guesses const real_t *qptr; // num 1D points in qptr int nq1d; void MFEM_HOST_DEVICE operator()(int idx) const { // can only be outside if all test points report outside int res = InverseElementTransformation::Outside; for (int i = 0; i < nq1d; ++i) { for (int d = 0; d < eltrans::GeometryUtils::Dimension(); ++d) { if (MFEM_THREAD_ID(x) == 0) { for (int d2 = 0; d2 < eltrans::GeometryUtils::Dimension(); ++d2) { solver.xptr[idx + d2 * solver.npts] = 0; } solver.xptr[idx + d * solver.npts] = qptr[i]; } MFEM_SYNC_THREAD; int res_tmp = solver(idx); switch (res_tmp) { case InverseElementTransformation::Inside: return; case InverseElementTransformation::Outside: break; case InverseElementTransformation::Unknown: res = InverseElementTransformation::Unknown; break; } if (qptr[i] == 0) { // don't repeat test the origin break; } } } if (MFEM_THREAD_ID(x) == 0) { solver.tptr[idx] = res; } } }; template static void NewtonEdgeScanImpl(real_t ref_tol, real_t phys_rtol, int max_iter, int npts, int nelems, int ndof1d, const real_t *mptr, const real_t *pptr, const int *eptr, const real_t *nptr, const real_t *qptr, int nq1d, int *tptr, int *iter_ptr, real_t *xptr) { constexpr int max_team_x = use_device ? 64 : 1; InvTNewtonEdgeScanner func; MFEM_VERIFY(ndof1d <= func.solver.max_dof1d(), "exceeded max_dof1d limit (32 for 2D/3D)"); func.solver.ref_tol = ref_tol; func.solver.phys_rtol = phys_rtol; func.solver.max_iter = max_iter; func.solver.basis1d.z = nptr; func.solver.basis1d.pN = ndof1d; func.solver.mptr = mptr; func.solver.pptr = pptr; func.solver.eptr = eptr; func.solver.xptr = xptr; func.solver.iter_ptr = iter_ptr; func.solver.tptr = tptr; func.solver.npts = npts; func.nq1d = nq1d; func.qptr = qptr; if (use_device) { int team_x = max_team_x; int ndof = func.solver.ndofs(ndof1d); while (true) { if (team_x <= ndof) { break; } team_x >>= 1; } team_x = std::min(max_team_x, 2 * team_x); forall_2D(npts, team_x, 1, func); } else { forall_switch(false, npts, func); } } } // namespace internal template BatchInverseElementTransformation::ClosestPhysPointKernelType BatchInverseElementTransformation::FindClosestPhysPoint::Kernel() { return internal::ClosestPhysNodeImpl; } template BatchInverseElementTransformation::ClosestPhysDofKernelType BatchInverseElementTransformation::FindClosestPhysDof::Kernel() { return internal::ClosestPhysDofImpl; } template BatchInverseElementTransformation::ClosestRefPointKernelType BatchInverseElementTransformation::FindClosestRefPoint::Kernel() { return internal::ClosestRefNodeImpl; } template BatchInverseElementTransformation::ClosestRefDofKernelType BatchInverseElementTransformation::FindClosestRefDof::Kernel() { return internal::ClosestRefDofImpl; } template BatchInverseElementTransformation::NewtonKernelType BatchInverseElementTransformation::NewtonSolve::Kernel() { return internal::NewtonSolveImpl; } template BatchInverseElementTransformation::NewtonEdgeScanKernelType BatchInverseElementTransformation::NewtonEdgeScan::Kernel() { return internal::NewtonEdgeScanImpl; } BatchInverseElementTransformation::ClosestPhysPointKernelType BatchInverseElementTransformation::FindClosestPhysPoint::Fallback(int, int, bool) { MFEM_ABORT("Invalid Geom/SDim combination"); } BatchInverseElementTransformation::ClosestRefPointKernelType BatchInverseElementTransformation::FindClosestRefPoint::Fallback(int, int, bool) { MFEM_ABORT("Invalid Geom/SDim combination"); } BatchInverseElementTransformation::NewtonKernelType BatchInverseElementTransformation::NewtonSolve::Fallback( int, int, InverseElementTransformation::SolverType, bool) { MFEM_ABORT("Invalid Geom/SDim/SolverType combination"); } BatchInverseElementTransformation::NewtonEdgeScanKernelType BatchInverseElementTransformation::NewtonEdgeScan::Fallback( int, int, InverseElementTransformation::SolverType, bool) { MFEM_ABORT("Invalid Geom/SDim/SolverType combination"); } BatchInverseElementTransformation::ClosestPhysDofKernelType BatchInverseElementTransformation::FindClosestPhysDof::Fallback(int, int, bool) { MFEM_ABORT("Invalid Geom/SDim combination"); } BatchInverseElementTransformation::ClosestRefDofKernelType BatchInverseElementTransformation::FindClosestRefDof::Fallback(int, int, bool) { MFEM_ABORT("Invalid Geom/SDim combination"); } BatchInverseElementTransformation::Kernels::Kernels() { using BatchInvTr = BatchInverseElementTransformation; constexpr auto SEGMENT = Geometry::SEGMENT; constexpr auto SQUARE = Geometry::SQUARE; constexpr auto CUBE = Geometry::CUBE; constexpr auto Newton = InverseElementTransformation::Newton; constexpr auto NewtonElementProject = InverseElementTransformation::NewtonElementProject; BatchInvTr::AddFindClosestSpecialization(); BatchInvTr::AddFindClosestSpecialization(); BatchInvTr::AddFindClosestSpecialization(); BatchInvTr::AddFindClosestSpecialization(); BatchInvTr::AddFindClosestSpecialization(); BatchInvTr::AddFindClosestSpecialization(); // NewtonSolve BatchInvTr::AddNewtonSolveSpecialization(); BatchInvTr::AddNewtonSolveSpecialization(); BatchInvTr::AddNewtonSolveSpecialization(); BatchInvTr::AddNewtonSolveSpecialization(); BatchInvTr::AddNewtonSolveSpecialization(); BatchInvTr::AddNewtonSolveSpecialization(); BatchInvTr::AddNewtonSolveSpecialization(); BatchInvTr::AddNewtonSolveSpecialization(); BatchInvTr::AddNewtonSolveSpecialization(); BatchInvTr::AddNewtonSolveSpecialization(); BatchInvTr::AddNewtonSolveSpecialization(); BatchInvTr::AddNewtonSolveSpecialization(); } /// \endcond DO_NOT_DOCUMENT } // namespace mfem