Compare commits

...
Author SHA1 Message Date
Will Pazner 35cea24e21 Move face map logic from restriction.cpp into FiniteElement derived classes
Use inheritance instead of switch statement to dispatch
2023-01-09 20:46:51 -08:00
Will Pazner 9732e4dc6f Make SupportsDevice const
In both LinearFormIntegrator and LinearForm classes
2023-01-09 19:40:21 -08:00
Will Pazner 035301c212 Comment 2023-01-09 19:35:51 -08:00
Will Pazner f6efd6e56b Fix comment 2023-01-09 19:17:26 -08:00
Will Pazner 896922fbe4 Update CHANGELOG 2023-01-08 17:14:57 -08:00
Will Pazner f726606b96 Add GetFaceNormal3D to help manage 3D hexahedron face cases 2023-01-05 08:55:45 -08:00
Will Pazner 02fa406807 Refactor FillFaceMap and add explanatory comment 2023-01-05 08:55:20 -08:00
Will Pazner 1e34a98337 ND face restriction 2023-01-05 08:55:19 -08:00
Will Pazner bbd28f5f45 VectorFEBoundaryFluxLFIntegrator kernel in 3D 2023-01-04 18:49:04 -08:00
Will Pazner f413481bff RT face restriction in 3D 2023-01-04 18:49:04 -08:00
Will Pazner 61a922a812 Fix Mesh::FaceInformation output with operator<<
Newline was missing after face topology.

Also use '\n' instead of std::endl for all but last newline since there is no
reason to flush the buffer before then.
2023-01-04 18:48:46 -08:00
Will Pazner 25ba954475 Test device kernel for VectorFEBoundaryFluxLFIntegrator in 2D 2023-01-04 18:48:46 -08:00
Will Pazner a9829da2cb Add device kernel for VectorFEBoundaryFluxLFIntegrator 2023-01-04 18:48:46 -08:00
Will Pazner 1b2ab9253b Add test case for 2D RT face restriction 2023-01-04 18:48:44 -08:00
Will Pazner 067f571dc4 Support 2D RT elements in H1_ND_RT_FaceRestriction 2023-01-04 14:18:27 -08:00
Will Pazner 452cf127c6 Rename H1FaceRestriction to H1_ND_RT_FaceRestriction
The same class should be able to be used for H1, ND, and RT spaces
2023-01-04 14:18:27 -08:00
Will Pazner 328df07f5d Refactor GetFaceDofs to use offset and strides 2023-01-04 14:18:27 -08:00
Veselin Dobrev 576401f779 Merge pull request #3389 from mfem/fix-gnutls-shadow
Fix some shadow warnings
2023-01-03 19:43:45 -08:00
Veselin Dobrev 12dab69fe7 Merge branch 'master' into fix-gnutls-shadow 2023-01-03 12:42:21 -08:00
Will Pazner b210fb5398 Merge pull request #3362 from mfem/bugfix/kweiss/quad-face-ctor
Adds a QuadratureFunction constructor from external data
2023-01-03 12:33:22 -08:00
Veselin Dobrev f327a1f2fe Merge branch 'master' into fix-gnutls-shadow 2023-01-03 12:25:21 -08:00
Will Pazner b35eb5acd2 Merge remote-tracking branch 'origin/master' into bugfix/kweiss/quad-face-ctor 2023-01-03 11:17:25 -08:00
Tzanio Kolev eeece17619 Merge pull request #3293 from mfem/artv3/raja-launch-updates
RAJA launch API updates
2023-01-03 11:16:50 -08:00
Will Pazner 2905874bc0 Fix shadow warnings in ND and RT with MFEM_THREAD_SAFE 2022-12-29 14:53:35 -08:00
Will Pazner a8730f78fd Fix shadow warning when GNUTLS is enabled 2022-12-29 14:47:36 -08:00
Tzanio Kolev 70bcb774f2 Merge branch 'master' into bugfix/kweiss/quad-face-ctor 2022-12-28 11:06:34 -08:00
Will Pazner b9efa256b4 Add comment that QuadratureFunction constructor takes a host pointer 2022-12-27 15:08:03 -08:00
Veselin Dobrev 4952b82c48 Restore CHANGELOG entries (deleted by mistake?)
Use C++14 when UMPIRE is enabled.

Fix and enhance FindUMPIRE.cmake.
2022-12-13 18:43:59 -08:00
Arturo Vargas e3b05bea8e update raja notes 2022-12-13 15:16:36 -08:00
Kenneth Weiss d8a223bd56 Adds a QuadratureFunction constructor over an external data pointer
This was present in mfem@4.4, but missing in mfem@4.5.
The following call: `QuadratureFunction(qspace, qf_data, vdim);`
is equivalent to: `QuadratureFunction qf; qf.SetSpace(qspace, qf_data, vdim);`
2022-12-12 19:17:32 -08:00
Tzanio Kolev 486c43a197 Merge branch 'master' into artv3/raja-launch-updates 2022-12-12 10:50:36 -08:00
Tzanio Kolev 51a5982eb6 Merge branch 'master' into artv3/raja-launch-updates 2022-12-11 16:07:13 -08:00
Veselin Dobrev 7799d447a3 Merge branch 'master' into artv3/raja-launch-updates 2022-12-08 06:17:36 -08:00
Veselin Dobrev 892352e9cd In FindRAJA.cmake, remove a workaround that is no longer needed
with RAJA v2022.10.3.

In the CMake build system, disable the 'hooke' miniapp when
HIP is enabled to avoid AMD HIP compiler crash (already done
in the GNU make build system).
2022-12-08 06:07:32 -08:00
Arturo Vargas 941f930bae Merge branch 'master' into artv3/raja-launch-updates 2022-12-07 13:21:40 -08:00
Arturo Vargas 41faa5428f add CAMP_LIB flag 2022-11-22 09:30:52 -08:00
Veselin Dobrev d7b1fb4880 CMake CUDA tweak 2022-11-20 22:14:28 -08:00
Veselin Dobrev 5578279386 In defaults.cmake, remove lines that are no longer needed 2022-11-20 20:20:27 -08:00
Veselin Dobrev 0a133cc3fa Update FindRAJA.cmake to use the exported RAJA CMake configs 2022-11-20 19:29:39 -08:00
Arturo Vargas 44f1c544fb Will need RAJA >= 2022.10.3 2022-11-17 10:20:53 -08:00
Arturo Vargas 67d95f4032 add -lcamp 2022-11-17 10:19:43 -08:00
Arturo Vargas eec6f95561 make note of raja change in change log 2022-11-01 15:22:36 -07:00
Arturo Vargas 23fcd110f5 updates for raja launch 2022-11-01 15:21:30 -07:00
34 changed files with 850 additions and 263 deletions
+7 -3
View File
@@ -8,8 +8,6 @@
https://mfem.org
Version 4.5.1 (development)
===========================
@@ -20,7 +18,10 @@ Meshing improvements
Discretization improvements
---------------------------
- TBD
- Face restriction operators for Nedelec and Raviart-Thomas finite element
spaces are now supported through the H1_ND_RT_FaceRestriction class.
- VectorFEBoundaryFluxLFIntegrator is now supported on device/GPU.
Linear and nonlinear solvers
----------------------------
@@ -36,6 +37,9 @@ Integrations, testing and documentation
- Removed the support for the Mesquite toolkit. We recommend using MFEM's TMOP
functionality instead for mesh optimization. See the mesh-optimizer miniapp.
- The following integrations have updated minimum version requirements:
* RAJA >= 2022.10.3
Miscellaneous
-------------
- VisItDataCollection now correctly handles collection names containing
+7 -6
View File
@@ -82,8 +82,12 @@ if (MFEM_USE_STRUMPACK)
# Just needed to find the MPI_Fortran libraries to link with
set(XSDK_ENABLE_Fortran ON)
endif()
# SUNDIALS, STRUMPACK, and Ginkgo require C++14:
if ((MFEM_USE_SUNDIALS OR MFEM_USE_STRUMPACK OR MFEM_USE_GINKGO) AND
# SUNDIALS, STRUMPACK, Ginkgo, RAJA and Umpire require C++14:
if ((MFEM_USE_SUNDIALS OR
MFEM_USE_STRUMPACK OR
MFEM_USE_GINKGO OR
MFEM_USE_RAJA OR
MFEM_USE_UMPIRE) AND
("${CMAKE_CXX_STANDARD}" LESS "14"))
set(CMAKE_CXX_STANDARD 14)
endif()
@@ -107,10 +111,7 @@ if (MFEM_USE_CUDA)
set(CMAKE_CUDA_HOST_COMPILER ${CMAKE_CXX_COMPILER})
endif()
enable_language(CUDA)
set(CMAKE_CUDA_STANDARD 11)
if (MFEM_USE_GINKGO)
set(CMAKE_CUDA_STANDARD 14)
endif()
set(CMAKE_CUDA_STANDARD ${CMAKE_CXX_STANDARD})
set(CMAKE_CUDA_STANDARD_REQUIRED ON)
set(CMAKE_CUDA_EXTENSIONS OFF)
set(CUDA_FLAGS "--expt-extended-lambda")
+2 -2
View File
@@ -793,10 +793,10 @@ The specific libraries and their options are:
Versions: libCEED >= 0.10.
- RAJA (optional), used when MFEM_USE_RAJA = YES.
Beginning with MFEM v4.3, only RAJA v0.14.0+ is supported.
Beginning with MFEM v4.5.1, only RAJA v2022.10.3+ is supported.
URL: https://github.com/LLNL/RAJA
Options: RAJA_DIR, RAJA_OPT, RAJA_LIB.
Versions: RAJA >= 0.14.0.
Versions: RAJA >= 2022.10.3.
- Moonolith (optional), use when MFEM_USE_MOONOLITH = YES.
URL: https://bitbucket.org/zulianp/par_moonolith
+21 -11
View File
@@ -14,17 +14,27 @@
# - RAJA_LIBRARIES
# - RAJA_INCLUDE_DIRS
include(MfemCmakeUtilities)
mfem_find_package(RAJA RAJA RAJA_DIR "include" "RAJA/RAJA.hpp" "lib" "RAJA"
"Paths to headers required by RAJA." "Libraries required by RAJA.")
if (NOT RAJA_CONFIG_CMAKE)
set(RAJA_CONFIG_CMAKE "${RAJA_DIR}/share/raja/cmake/raja-config.cmake")
if (RAJA_FOUND)
return()
endif()
if (EXISTS "${RAJA_CONFIG_CMAKE}")
include("${RAJA_CONFIG_CMAKE}")
if (ENABLE_CUDA AND NOT MFEM_USE_CUDA)
message(FATAL_ERROR
"RAJA is built with CUDA: MFEM_USE_CUDA=YES is required")
message(STATUS "Looking for RAJA ...")
if (RAJA_DIR)
message(STATUS " in RAJA_DIR = ${RAJA_DIR}")
find_package(RAJA CONFIG NO_DEFAULT_PATH PATHS "${RAJA_DIR}")
endif()
if (NOT RAJA_FOUND)
message(STATUS " in standard CMake locations")
find_package(RAJA CONFIG)
endif()
if (RAJA_FOUND)
set(RAJA_LIBRARIES "RAJA" CACHE STRING "RAJA imported target." FORCE)
set(RAJA_INCLUDE_DIRS "" CACHE STRING "RAJA include dirs (not used)" FORCE)
message(STATUS
"Found RAJA target: ${RAJA_LIBRARIES} (version: ${RAJA_VERSION})")
else()
set(msg STATUS)
if (RAJA_FIND_REQUIRED)
set(msg FATAL_ERROR)
endif()
message(${msg} "RAJA not found. Please set RAJA_DIR to the RAJA prefix.")
endif()
+18 -1
View File
@@ -14,6 +14,23 @@
# - UMPIRE_LIBRARIES
# - UMPIRE_INCLUDE_DIRS
find_package(umpire REQUIRED CONFIG)
if (NOT umpire_DIR AND UMPIRE_DIR)
set(umpire_DIR ${UMPIRE_DIR}/lib/cmake/umpire)
endif()
message(STATUS "Looking for UMPIRE ...")
message(STATUS " in UMPIRE_DIR = ${UMPIRE_DIR}")
message(STATUS " umpire_DIR = ${umpire_DIR}")
find_package(umpire CONFIG)
set(UMPIRE_FOUND ${umpire_FOUND})
set(UMPIRE_LIBRARIES "umpire")
if (UMPIRE_FOUND)
message(STATUS
"Found UMPIRE target: ${UMPIRE_LIBRARIES} (version: ${umpire_VERSION})")
else()
set(msg STATUS)
if (UMPIRE_FIND_REQUIRED)
set(msg FATAL_ERROR)
endif()
message(${msg}
"UMPIRE not found. Please set UMPIRE_DIR to the install prefix.")
endif()
-5
View File
@@ -227,11 +227,6 @@ set(MKL_LIBRARY_DIR "" CACHE STRING "Custom library subdirectory")
set(OCCA_DIR "${MFEM_DIR}/../occa" CACHE PATH "Path to OCCA")
set(RAJA_DIR "${MFEM_DIR}/../raja" CACHE PATH "Path to RAJA")
# If RAJA is built with external CAMP:
# set(RAJA_REQUIRED_PACKAGES "camp"
# CACHE STRING "Packages that RAJA depends on.")
# set(camp_DIR "${MFEM_DIR}/../camp/lib/cmake/camp"
# CACHE PATH "Path to CAMP CMake files.")
set(CEED_DIR "${MFEM_DIR}/../libCEED" CACHE PATH "Path to libCEED")
set(UMPIRE_DIR "${MFEM_DIR}/../umpire" CACHE PATH "Path to Umpire")
set(CALIPER_DIR "${MFEM_DIR}/../caliper" CACHE PATH "Path to Caliper")
+11 -2
View File
@@ -506,20 +506,29 @@ CEED_OPT = -I$(CEED_DIR)/include
CEED_LIB = $(XLINKER)-rpath,$(CEED_DIR)/lib -L$(CEED_DIR)/lib -lceed
# RAJA library configuration
ifeq ($(MFEM_USE_RAJA),YES)
BASE_FLAGS = -std=c++14
endif
RAJA_DIR = @MFEM_DIR@/../raja
RAJA_OPT = -I$(RAJA_DIR)/include
ifdef CUB_DIR
RAJA_OPT += -I$(CUB_DIR)
endif
CAMP_LIB = -lcamp
ifdef CAMP_DIR
RAJA_OPT += -I$(CAMP_DIR)/include
CAMP_LIB = $(XLINKER)-rpath,$(CAMP_DIR)/lib -L$(CAMP_DIR)/lib -lcamp
endif
RAJA_LIB = $(XLINKER)-rpath,$(RAJA_DIR)/lib -L$(RAJA_DIR)/lib -lRAJA
RAJA_LIB = $(XLINKER)-rpath,$(RAJA_DIR)/lib -L$(RAJA_DIR)/lib -lRAJA $(CAMP_LIB)
# UMPIRE library configuration
ifeq ($(MFEM_USE_UMPIRE),YES)
BASE_FLAGS = -std=c++14
endif
UMPIRE_DIR = @MFEM_DIR@/../umpire
UMPIRE_OPT = -I$(UMPIRE_DIR)/include $(if $(CAMP_DIR), -I$(CAMP_DIR)/include)
UMPIRE_LIB = -L$(UMPIRE_DIR)/lib -lumpire
UMPIRE_LIB = -L$(UMPIRE_DIR)/lib -lumpire $(CAMP_LIB)
# MKL CPardiso library configuration
MKL_CPARDISO_DIR ?=
+3
View File
@@ -44,6 +44,7 @@ set(SRCS
eltrans.cpp
estimators.cpp
fe.cpp
fe/face_map_utils.cpp
fe/fe_base.cpp
fe/fe_fixed_order.cpp
fe/fe_h1.cpp
@@ -74,6 +75,7 @@ set(SRCS
linearform_ext.cpp
lininteg.cpp
lininteg_boundary.cpp
lininteg_boundary_flux.cpp
lininteg_domain.cpp
lininteg_domain_grad.cpp
lor/lor.cpp
@@ -151,6 +153,7 @@ set(HDRS
eltrans.hpp
estimators.hpp
fe.hpp
fe/face_map_utils.hpp
fe/fe_base.hpp
fe/fe_fixed_order.hpp
fe/fe_h1.hpp
+60
View File
@@ -0,0 +1,60 @@
// Copyright (c) 2010-2022, 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.
// Finite Element Base classes
#include "face_map_utils.hpp"
namespace mfem
{
std::pair<int,int> GetFaceNormal3D(const int face_id)
{
switch (face_id)
{
case 0: return std::make_pair(2, 0); // z = 0
case 1: return std::make_pair(1, 0); // y = 0
case 2: return std::make_pair(0, 1); // x = 1
case 3: return std::make_pair(1, 1); // y = 1
case 4: return std::make_pair(0, 0); // x = 0
case 5: return std::make_pair(2, 1); // z = 1
default: MFEM_ABORT("Invalid face ID.")
}
return std::make_pair(-1, -1); // invalid
}
void FillFaceMap(const int n_face_dofs_per_component,
const std::vector<int> offsets,
const std::vector<int> &strides,
const std::vector<int> &n_dofs_per_dim,
Array<int> &face_map)
{
const int n_components = offsets.size();
const int face_dim = strides.size() / n_components;
for (int comp = 0; comp < n_components; ++comp)
{
const int offset = offsets[comp];
for (int i = 0; i < n_face_dofs_per_component; ++i)
{
int idx = offset;
int j = i;
for (int d = 0; d < face_dim; ++d)
{
const int dof1d = n_dofs_per_dim[comp*(face_dim) + d];
idx += strides[comp*(face_dim) + d]*(j % dof1d);
j /= dof1d;
}
face_map[comp*n_face_dofs_per_component + i] = idx;
}
}
}
}
+49
View File
@@ -0,0 +1,49 @@
// Copyright (c) 2010-2022, 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.
#ifndef MFEM_FACE_MAP_UTILS_HPP
#define MFEM_FACE_MAP_UTILS_HPP
#include "../../general/array.hpp"
#include <utility> // std::pair
#include <vector>
namespace mfem
{
/// Each face of a hexahedron is given by a level set x_i = l, where x_i is one
/// of x, y, or z (corresponding to i = 0, i=1, i = 3), and l is either 0 or 1.
/// Returns i and level.
std::pair<int,int> GetFaceNormal3D(const int face_id);
/// @brief Fills in the entries of the lexicographic face_map.
///
/// For use in FiniteElement::GetFaceMap.
///
/// n_face_dofs_per_component is the number of DOFs for each vector component
/// on the face (there is only one vector component in all cases except for 3D
/// Nedelec elements, where the face DOFs have two components to span the
/// tangent space).
///
/// The DOFs for the i-th vector component begin at offsets[i] (i.e. the number
/// of vector components is given by offsets.size()).
///
/// The DOFs for each vector component are arranged in a Cartesian grid defined
/// by strides and n_dofs_per_dim.
void FillFaceMap(const int n_face_dofs_per_component,
const std::vector<int> offsets,
const std::vector<int> &strides,
const std::vector<int> &n_dofs_per_dim,
Array<int> &face_map);
} // namespace mfem
#endif
+56
View File
@@ -12,6 +12,7 @@
// Finite Element Base classes
#include "fe_base.hpp"
#include "face_map_utils.hpp"
#include "../coefficient.hpp"
namespace mfem
@@ -370,6 +371,12 @@ const DofToQuad &FiniteElement::GetDofToQuad(const IntegrationRule &,
return *dof2quad_array[0]; // suppress a warning
}
void FiniteElement::GetFaceMap(const int face_id,
Array<int> &face_map) const
{
MFEM_ABORT("method is not implemented for this element");
}
FiniteElement::~FiniteElement()
{
for (int i = 0; i < dof2quad_array.Size(); i++)
@@ -2427,6 +2434,55 @@ void NodalTensorFiniteElement::SetMapType(const int map_type)
}
}
void NodalTensorFiniteElement::GetFaceMap(const int face_id,
Array<int> &face_map) const
{
const int dof1d = order + 1;
int n_face_dofs = pow(dof1d, dim - 1);
std::vector<int> offsets, strides;
switch (dim)
{
case 1:
offsets = {(face_id == 0) ? 0 : dof1d - 1};
break;
case 2:
strides = {(face_id == 0 || face_id == 2) ? 1 : dof1d};
switch (face_id)
{
case 0: offsets = {0}; break; // y = 0
case 1: offsets = {dof1d - 1}; break; // x = 1
case 2: offsets = {(dof1d-1)*dof1d}; break; // y = 1
case 3: offsets = {0}; break; // x = 0
}
break;
case 3:
{
const auto f = GetFaceNormal3D(face_id);
const int face_normal = f.first, level = f.second;
if (face_normal == 0) // x-normal
{
offsets = {level ? dof1d-1 : 0};
strides = {dof1d, dof1d*dof1d};
}
else if (face_normal == 1) // y-normal
{
offsets = {level ? (dof1d-1)*dof1d : 0};
strides = {1, dof1d*dof1d};
}
else if (face_normal == 2) // z-normal
{
offsets = {level ? (dof1d-1)*dof1d*dof1d : 0};
strides = {1, dof1d};
}
break;
}
}
// same number of DOFs in each dimension, repeat dof1d (dim - 1) times
std::vector<int> n_dofs(dim - 1, dof1d);
FillFaceMap(n_face_dofs, offsets, strides, n_dofs, face_map);
}
VectorTensorFiniteElement::VectorTensorFiniteElement(const int dims,
const int d,
const int p,
+6
View File
@@ -579,6 +579,10 @@ public:
/** See the documentation for DofToQuad for more details. */
virtual const DofToQuad &GetDofToQuad(const IntegrationRule &ir,
DofToQuad::Mode mode) const;
virtual void GetFaceMap(const int face_id,
Array<int> &face_map) const;
/// Deconstruct the FiniteElement
virtual ~FiniteElement();
@@ -1267,6 +1271,8 @@ public:
NodalFiniteElement::GetTransferMatrix(fe, Trans, I);
}
}
virtual void GetFaceMap(const int face_id, Array<int> &face_map) const;
};
class VectorTensorFiniteElement : public VectorFiniteElement,
+64 -2
View File
@@ -12,6 +12,7 @@
// Nedelec Finite Element classes
#include "fe_nd.hpp"
#include "face_map_utils.hpp"
#include "../coefficient.hpp"
namespace mfem
@@ -314,7 +315,6 @@ void ND_HexahedronElement::CalcVShape(const IntegrationPoint &ip,
#ifdef MFEM_THREAD_SAFE
Vector shape_cx(p + 1), shape_ox(p), shape_cy(p + 1), shape_oy(p);
Vector shape_cz(p + 1), shape_oz(p);
Vector dshape_cx(p + 1), dshape_cy(p + 1), dshape_cz(p + 1);
#endif
if (obasis1d.IsIntegratedType())
@@ -482,6 +482,50 @@ void ND_HexahedronElement::CalcCurlShape(const IntegrationPoint &ip,
}
}
void ND_HexahedronElement::GetFaceMap(const int face_id,
Array<int> &face_map) const
{
const int p = order;
const int pp1 = p + 1;
const int n_face_dofs_per_component = p*pp1;
const int n_dof_per_dim = p*pp1*pp1;
std::vector<int> n_dofs = {p, pp1, pp1, p};
std::vector<int> offsets, strides;
const auto f = GetFaceNormal3D(face_id);
const int face_normal = f.first, level = f.second;
if (face_normal == 0) // x-normal
{
offsets =
{
n_dof_per_dim + (level ? pp1 - 1 : 0),
2*n_dof_per_dim + (level ? pp1 - 1 : 0)
};
strides = {pp1, p*pp1, pp1, pp1*pp1};
}
else if (face_normal == 1) // y-normal
{
offsets =
{
level ? p*(pp1 - 1) : 0,
2*n_dof_per_dim + (level ? pp1*(pp1 - 1) : 0)
};
strides = {1, p*pp1, 1, pp1*pp1};
}
else if (face_normal == 2) // z-normal
{
offsets =
{
level ? p*pp1*(pp1 - 1) : 0,
n_dof_per_dim + (level ? p*pp1*(pp1 - 1) : 0)
};
strides = {1, p, 1, pp1};
}
FillFaceMap(n_face_dofs_per_component, offsets, strides, n_dofs, face_map);
}
const double ND_QuadrilateralElement::tk[8] =
{ 1.,0., 0.,1., -1.,0., 0.,-1. };
@@ -659,7 +703,6 @@ void ND_QuadrilateralElement::CalcVShape(const IntegrationPoint &ip,
#ifdef MFEM_THREAD_SAFE
Vector shape_cx(p + 1), shape_ox(p), shape_cy(p + 1), shape_oy(p);
Vector dshape_cx(p + 1), dshape_cy(p + 1);
#endif
if (obasis1d.IsIntegratedType())
@@ -773,6 +816,25 @@ void ND_QuadrilateralElement::CalcCurlShape(const IntegrationPoint &ip,
}
}
void ND_QuadrilateralElement::GetFaceMap(const int face_id,
Array<int> &face_map) const
{
const int p = order;
const int pp1 = order + 1;
const int n_face_dofs_per_component = p;
std::vector<int> strides = {(face_id == 0 || face_id == 2) ? 1 : pp1};
std::vector<int> n_dofs = {p};
std::vector<int> offsets;
switch (face_id)
{
case 0: offsets = {0}; break; // y = 0
case 1: offsets = {p*pp1 + pp1 - 1}; break; // x = 1
case 2: offsets = {p*(pp1 - 1)}; break; // y = 1
case 3: offsets = {p*pp1}; break; // x = 0
}
FillFaceMap(n_face_dofs_per_component, offsets, strides, n_dofs, face_map);
}
const double ND_TetrahedronElement::tk[18] =
{ 1.,0.,0., 0.,1.,0., 0.,0.,1., -1.,1.,0., -1.,0.,1., 0.,-1.,1. };
+4
View File
@@ -91,6 +91,8 @@ public:
DenseMatrix &curl) const
{ ProjectCurl_ND(tk, dof2tk, fe, Trans, curl); }
virtual void GetFaceMap(const int face_id, Array<int> &face_map) const;
protected:
void ProjectIntegrated(VectorCoefficient &vc,
ElementTransformation &Trans,
@@ -155,6 +157,8 @@ public:
DenseMatrix &grad) const
{ ProjectGrad_ND(tk, dof2tk, fe, Trans, grad); }
virtual void GetFaceMap(const int face_id, Array<int> &face_map) const;
protected:
void ProjectIntegrated(VectorCoefficient &vc,
ElementTransformation &Trans,
+51 -2
View File
@@ -12,6 +12,7 @@
// Raviart-Thomas Finite Element classes
#include "fe_rt.hpp"
#include "face_map_utils.hpp"
#include "../coefficient.hpp"
namespace mfem
@@ -145,7 +146,6 @@ void RT_QuadrilateralElement::CalcVShape(const IntegrationPoint &ip,
#ifdef MFEM_THREAD_SAFE
Vector shape_cx(pp1 + 1), shape_ox(pp1), shape_cy(pp1 + 1), shape_oy(pp1);
Vector dshape_cx(pp1 + 1), dshape_cy(pp1 + 1);
#endif
if (obasis1d.IsIntegratedType())
@@ -298,6 +298,27 @@ void RT_QuadrilateralElement::ProjectIntegrated(VectorCoefficient &vc,
}
}
void RT_QuadrilateralElement::GetFaceMap(const int face_id,
Array<int> &face_map) const
{
const int p = order;
const int pp1 = p + 1;
const int n_face_dofs = p;
std::vector<int> offsets;
std::vector<int> strides = {(face_id == 0 || face_id == 2) ? 1 : pp1};
switch (face_id)
{
case 0: offsets = {p*pp1}; break; // y = 0
case 1: offsets = {pp1 - 1}; break; // x = 1
case 2: offsets = {p*pp1 + p*(pp1 - 1)}; break; // y = 1
case 3: offsets = {0}; break; // x = 0
}
std::vector<int> n_dofs(dim - 1, p);
FillFaceMap(n_face_dofs, offsets, strides, n_dofs, face_map);
}
const double RT_HexahedronElement::nk[18] =
{ 0.,0.,-1., 0.,-1.,0., 1.,0.,0., 0.,1.,0., -1.,0.,0., 0.,0.,1. };
@@ -476,7 +497,6 @@ void RT_HexahedronElement::CalcVShape(const IntegrationPoint &ip,
#ifdef MFEM_THREAD_SAFE
Vector shape_cx(pp1 + 1), shape_ox(pp1), shape_cy(pp1 + 1), shape_oy(pp1);
Vector shape_cz(pp1 + 1), shape_oz(pp1);
Vector dshape_cx(pp1 + 1), dshape_cy(pp1 + 1), dshape_cz(pp1 + 1);
#endif
if (obasis1d.IsIntegratedType())
@@ -688,6 +708,35 @@ void RT_HexahedronElement::ProjectIntegrated(VectorCoefficient &vc,
}
}
void RT_HexahedronElement::GetFaceMap(const int face_id,
Array<int> &face_map) const
{
const int p = order;
const int pp1 = p + 1;
int n_face_dofs = p*p;
std::vector<int> strides, offsets;
const int n_dof_per_dim = p*p*pp1;
const auto f = GetFaceNormal3D(face_id);
const int face_normal = f.first, level = f.second;
if (face_normal == 0) // x-normal
{
offsets = {level ? pp1 - 1 : 0};
strides = {pp1, p*pp1};
}
else if (face_normal == 1) // y-normal
{
offsets = {n_dof_per_dim + (level ? p*(pp1 - 1) : 0)};
strides = {1, p*pp1};
}
else if (face_normal == 2) // z-normal
{
offsets = {2*n_dof_per_dim + (level ? p*p*(pp1 - 1) : 0)};
strides = {1, p};
}
std::vector<int> n_dofs = {p, p};
FillFaceMap(n_face_dofs, offsets, strides, n_dofs, face_map);
}
const double RT_TriangleElement::nk[6] =
{ 0., -1., 1., 1., -1., 0. };
+6
View File
@@ -82,6 +82,8 @@ public:
DenseMatrix &curl) const
{ ProjectGrad_RT(nk, dof2nk, fe, Trans, curl); }
virtual void GetFaceMap(const int face_id, Array<int> &face_map) const;
protected:
void ProjectIntegrated(VectorCoefficient &vc, ElementTransformation &Trans,
Vector &dofs) const;
@@ -145,6 +147,10 @@ public:
DenseMatrix &curl) const
{ ProjectCurl_RT(nk, dof2nk, fe, Trans, curl); }
/// @brief Return the mapping from lexicographically ordered DOFs to face
/// DOFs corresponding to local face @a face_id.
virtual void GetFaceMap(const int face_id, Array<int> &face_map) const;
protected:
void ProjectIntegrated(VectorCoefficient &vc,
ElementTransformation &Trans,
+1 -1
View File
@@ -1317,7 +1317,7 @@ const FaceRestriction *FiniteElementSpace::GetFaceRestriction(
}
else
{
res = new H1FaceRestriction(*this, e_ordering, type);
res = new H1_ND_RT_FaceRestriction(*this, e_ordering, type);
}
L2F[key] = res;
return res;
+1 -1
View File
@@ -101,7 +101,7 @@ void LinearForm::AddInteriorFaceIntegrator(LinearFormIntegrator *lfi)
interior_face_integs.Append(lfi);
}
bool LinearForm::SupportsDevice()
bool LinearForm::SupportsDevice() const
{
// return false for NURBS meshes, so we dont convert it to non-NURBS
// through Assemble, AssembleDevice, GetGeometricFactors and EnsureNodes
+1 -1
View File
@@ -203,7 +203,7 @@ public:
void Assemble();
/// Return true if assembly on device is supported, false otherwise.
virtual bool SupportsDevice();
virtual bool SupportsDevice() const;
/// Assembles delta functions of the linear form
void AssembleDelta();
+13 -7
View File
@@ -31,7 +31,7 @@ protected:
public:
/// Method probing for assembly on device
virtual bool SupportsDevice() { return false; }
virtual bool SupportsDevice() const { return false; }
/// Method defining assembly on device
virtual void AssembleDevice(const FiniteElementSpace &fes,
@@ -121,7 +121,7 @@ public:
DomainLFIntegrator(Coefficient &QF, const IntegrationRule *ir)
: DeltaLFIntegrator(QF, ir), Q(QF), oa(1), ob(1) { }
virtual bool SupportsDevice() { return true; }
virtual bool SupportsDevice() const { return true; }
/// Method defining assembly on device
virtual void AssembleDevice(const FiniteElementSpace &fes,
@@ -154,7 +154,7 @@ public:
DomainLFGradIntegrator(VectorCoefficient &QF)
: DeltaLFIntegrator(QF), Q(QF) { }
virtual bool SupportsDevice() { return true; }
virtual bool SupportsDevice() const { return true; }
/// Method defining assembly on device
virtual void AssembleDevice(const FiniteElementSpace &fes,
@@ -187,7 +187,7 @@ public:
BoundaryLFIntegrator(Coefficient &QG, int a = 1, int b = 1)
: Q(QG), oa(a), ob(b) { }
virtual bool SupportsDevice() { return true; }
virtual bool SupportsDevice() const { return true; }
/// Method defining assembly on device
virtual void AssembleDevice(const FiniteElementSpace &fes,
@@ -217,7 +217,7 @@ public:
BoundaryNormalLFIntegrator(VectorCoefficient &QG, int a = 1, int b = 1)
: Q(QG), oa(a), ob(b) { }
virtual bool SupportsDevice() { return true; }
virtual bool SupportsDevice() const { return true; }
/// Method defining assembly on device
virtual void AssembleDevice(const FiniteElementSpace &fes,
@@ -262,7 +262,7 @@ public:
VectorDomainLFIntegrator(VectorCoefficient &QF)
: DeltaLFIntegrator(QF), Q(QF) { }
virtual bool SupportsDevice() { return true; }
virtual bool SupportsDevice() const { return true; }
/// Method defining assembly on device
virtual void AssembleDevice(const FiniteElementSpace &fes,
@@ -296,7 +296,7 @@ public:
VectorDomainLFGradIntegrator(VectorCoefficient &QF)
: DeltaLFIntegrator(QF), Q(QF) { }
virtual bool SupportsDevice() override { return true; }
virtual bool SupportsDevice() const override { return true; }
/// Method defining assembly on device
virtual void AssembleDevice(const FiniteElementSpace &fes,
@@ -456,6 +456,12 @@ public:
Vector &elvect);
using LinearFormIntegrator::AssembleRHSElementVect;
virtual bool SupportsDevice() const { return true; }
virtual void AssembleDevice(const FiniteElementSpace &fes,
const Array<int> &markers,
Vector &b);
};
/// Class for boundary integration \f$ L(v) = (n \times f, v) \f$
+180
View File
@@ -0,0 +1,180 @@
// Copyright (c) 2010-2022, 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 "fem.hpp"
#include "../fem/kernels.hpp"
#include "../general/forall.hpp"
namespace mfem
{
template<int T_D1D = 0, int T_Q1D = 0> static
void BFLFEvalAssemble2D(const int nbe, const int d, const int q,
const int *markers, const double *b,
const double *weights, const Vector &coeff, double *y)
{
const auto F = coeff.Read();
const auto M = Reshape(markers, nbe);
const auto B = Reshape(b, q, d);
const auto W = Reshape(weights, q);
const bool const_coeff = coeff.Size() == 1;
const auto C = const_coeff ? Reshape(F,1,1) : Reshape(F,q,nbe);
auto Y = Reshape(y, d, nbe);
MFEM_FORALL(e, nbe,
{
if (M(e) == 0) { return; } // ignore
constexpr int Q = T_Q1D ? T_Q1D : MAX_Q1D;
double QQ[Q];
for (int qx = 0; qx < q; ++qx)
{
const double coeff_val = const_coeff ? C(0,0) : C(qx,e);
QQ[qx] = W(qx) * coeff_val;
}
for (int dx = 0; dx < d; ++dx)
{
double u = 0;
for (int qx = 0; qx < q; ++qx) { u += QQ[qx] * B(qx,dx); }
Y(dx,e) += u;
}
});
}
template<int T_D1D = 0, int T_Q1D = 0> static
void BFLFEvalAssemble3D(const int nbe, const int d, const int q,
const int *markers, const double *b,
const double *weights, const Vector &coeff, double *y)
{
const auto F = coeff.Read();
const auto M = Reshape(markers, nbe);
const auto B = Reshape(b, q, d);
const auto W = Reshape(weights, q, q);
const bool const_coeff = coeff.Size() == 1;
const auto C = const_coeff ? Reshape(F,1,1,1) : Reshape(F,q,q,nbe);
auto Y = Reshape(y, d, d, nbe);
MFEM_FORALL_2D(e, nbe, q, q, 1,
{
if (M(e) == 0) { return; } // ignore
constexpr int Q = T_Q1D ? T_Q1D : MAX_Q1D;
constexpr int D = T_D1D ? T_D1D : MAX_D1D;
MFEM_SHARED double sBt[Q*D];
MFEM_SHARED double sQQ[Q*Q];
MFEM_SHARED double sQD[Q*D];
const DeviceMatrix Bt(sBt, d, q);
kernels::internal::LoadB<D,Q>(d, q, B, sBt);
const DeviceMatrix QQ(sQQ, q, q);
const DeviceMatrix QD(sQD, q, d);
MFEM_FOREACH_THREAD(x,x,q)
{
MFEM_FOREACH_THREAD(y,y,q)
{
const double coeff_val = const_coeff ? C(0,0,0) : C(x,y,e);
QQ(y,x) = W(x,y) * coeff_val;
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(qy,y,q)
{
MFEM_FOREACH_THREAD(dx,x,d)
{
double u = 0.0;
for (int qx = 0; qx < q; ++qx) { u += QQ(qy,qx) * Bt(dx,qx); }
QD(qy,dx) = u;
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(dy,y,d)
{
MFEM_FOREACH_THREAD(dx,x,d)
{
double u = 0.0;
for (int qy = 0; qy < q; ++qy) { u += QD(qy,dx) * Bt(dy,qy); }
Y(dx,dy,e) += u;
}
}
MFEM_SYNC_THREAD;
});
}
static void BFLFEvalAssemble(const FiniteElementSpace &fes,
const IntegrationRule &ir,
const Array<int> &markers,
const Vector &coeff,
Vector &y)
{
Mesh &mesh = *fes.GetMesh();
const int dim = mesh.Dimension();
const FiniteElement &el = *fes.GetBE(0);
const DofToQuad &maps = el.GetDofToQuad(ir, DofToQuad::TENSOR);
const int d = maps.ndof, q = maps.nqpt;
auto ker = (dim == 2) ? BFLFEvalAssemble2D<> : BFLFEvalAssemble3D<>;
if (dim==2)
{
if (d==1 && q==1) { ker=BFLFEvalAssemble2D<1,1>; }
if (d==2 && q==2) { ker=BFLFEvalAssemble2D<2,2>; }
if (d==3 && q==3) { ker=BFLFEvalAssemble2D<3,3>; }
if (d==4 && q==4) { ker=BFLFEvalAssemble2D<4,4>; }
if (d==5 && q==5) { ker=BFLFEvalAssemble2D<5,5>; }
if (d==2 && q==3) { ker=BFLFEvalAssemble2D<2,3>; }
if (d==3 && q==4) { ker=BFLFEvalAssemble2D<3,4>; }
if (d==4 && q==5) { ker=BFLFEvalAssemble2D<4,5>; }
if (d==5 && q==6) { ker=BFLFEvalAssemble2D<5,6>; }
}
if (dim==3)
{
if (d==1 && q==1) { ker=BFLFEvalAssemble3D<1,1>; }
if (d==2 && q==2) { ker=BFLFEvalAssemble3D<2,2>; }
if (d==3 && q==3) { ker=BFLFEvalAssemble3D<3,3>; }
if (d==4 && q==4) { ker=BFLFEvalAssemble3D<4,4>; }
if (d==5 && q==5) { ker=BFLFEvalAssemble3D<5,5>; }
if (d==2 && q==3) { ker=BFLFEvalAssemble3D<2,3>; }
if (d==3 && q==4) { ker=BFLFEvalAssemble3D<3,4>; }
if (d==4 && q==5) { ker=BFLFEvalAssemble3D<4,5>; }
if (d==5 && q==6) { ker=BFLFEvalAssemble3D<5,6>; }
}
MFEM_VERIFY(ker, "No kernel ndof " << d << " nqpt " << q);
const int nbe = fes.GetMesh()->GetNFbyType(FaceType::Boundary);
const int *M = markers.Read();
const double *B = maps.B.Read();
const double *W = ir.GetWeights().Read();
double *Y = y.ReadWrite();
ker(nbe, d, q, M, B, W, coeff, Y);
}
void VectorFEBoundaryFluxLFIntegrator::AssembleDevice(
const FiniteElementSpace &fes,
const Array<int> &markers,
Vector &b)
{
const FiniteElement &fe = *fes.GetBE(0);
const int qorder = oa * fe.GetOrder() + ob;
const Geometry::Type gtype = fe.GetGeomType();
const IntegrationRule &ir = IntRule ? *IntRule : IntRules.Get(gtype, qorder);
Mesh &mesh = *fes.GetMesh();
FaceQuadratureSpace qs(mesh, ir, FaceType::Boundary);
CoefficientVector coeff(F, qs, CoefficientStorage::COMPRESSED);
BFLFEvalAssemble(fes, ir, markers, coeff, b);
}
} // namespace mfem
+1 -1
View File
@@ -569,7 +569,7 @@ const FaceRestriction *ParFiniteElementSpace::GetFaceRestriction(
{
if (Conforming())
{
res = new H1FaceRestriction(*this, e_ordering, type);
res = new H1_ND_RT_FaceRestriction(*this, e_ordering, type);
}
else
{
+1 -1
View File
@@ -54,7 +54,7 @@ void ParLinearForm::Assemble()
}
}
bool ParLinearForm::SupportsDevice()
bool ParLinearForm::SupportsDevice() const
{
bool parallel;
bool local = LinearForm::SupportsDevice();
+1 -1
View File
@@ -120,7 +120,7 @@ public:
void Assemble();
/// Return true if assembly on device is supported, false otherwise.
virtual bool SupportsDevice();
virtual bool SupportsDevice() const;
void AssembleSharedFaces();
+11
View File
@@ -47,6 +47,17 @@ public:
QuadratureFunction(QuadratureSpaceBase *qspace_, int vdim_ = 1)
: QuadratureFunction(*qspace_, vdim_) { }
/** @brief Create a QuadratureFunction based on the given QuadratureSpaceBase,
using the external (host) data, @a qf_data. */
/** The QuadratureFunction does not assume ownership of the
QuadratureSpaceBase or the external data.
@warning @a qspace_ may not be NULL.
@note @a qf_data must be a valid **host** pointer (see the constructor
Vector::Vector(double *, int)). */
QuadratureFunction(QuadratureSpaceBase *qspace_, double *qf_data, int vdim_ = 1)
: Vector(qf_data, vdim_*qspace_->GetSize()),
qspace(qspace_), own_qspace(false), vdim(vdim_) { }
/** @brief Copy constructor. The QuadratureSpace ownership flag, #own_qspace,
in the new object is set to false. */
QuadratureFunction(const QuadratureFunction &orig)
+71 -166
View File
@@ -594,119 +594,11 @@ void L2ElementRestriction::FillJAndData(const Vector &ea_data,
});
}
/** Return the face degrees of freedom returned in Lexicographic order.
Note: Only for quad and hex */
void GetFaceDofs(const int dim, const int face_id,
const int dof1d, Array<int> &face_map)
{
switch (dim)
{
case 1:
switch (face_id)
{
case 0: // WEST
face_map[0] = 0;
break;
case 1: // EAST
face_map[0] = dof1d-1;
break;
}
break;
case 2:
switch (face_id)
{
case 0: // SOUTH
for (int i = 0; i < dof1d; ++i)
{
face_map[i] = i;
}
break;
case 1: // EAST
for (int i = 0; i < dof1d; ++i)
{
face_map[i] = dof1d-1 + i*dof1d;
}
break;
case 2: // NORTH
for (int i = 0; i < dof1d; ++i)
{
face_map[i] = (dof1d-1)*dof1d + i;
}
break;
case 3: // WEST
for (int i = 0; i < dof1d; ++i)
{
face_map[i] = i*dof1d;
}
break;
}
break;
case 3:
switch (face_id)
{
case 0: // BOTTOM
for (int i = 0; i < dof1d; ++i)
{
for (int j = 0; j < dof1d; ++j)
{
face_map[i+j*dof1d] = i + j*dof1d;
}
}
break;
case 1: // SOUTH
for (int i = 0; i < dof1d; ++i)
{
for (int j = 0; j < dof1d; ++j)
{
face_map[i+j*dof1d] = i + j*dof1d*dof1d;
}
}
break;
case 2: // EAST
for (int i = 0; i < dof1d; ++i)
{
for (int j = 0; j < dof1d; ++j)
{
face_map[i+j*dof1d] = dof1d-1 + i*dof1d + j*dof1d*dof1d;
}
}
break;
case 3: // NORTH
for (int i = 0; i < dof1d; ++i)
{
for (int j = 0; j < dof1d; ++j)
{
face_map[i+j*dof1d] = (dof1d-1)*dof1d + i + j*dof1d*dof1d;
}
}
break;
case 4: // WEST
for (int i = 0; i < dof1d; ++i)
{
for (int j = 0; j < dof1d; ++j)
{
face_map[i+j*dof1d] = i*dof1d + j*dof1d*dof1d;
}
}
break;
case 5: // TOP
for (int i = 0; i < dof1d; ++i)
{
for (int j = 0; j < dof1d; ++j)
{
face_map[i+j*dof1d] = (dof1d-1)*dof1d*dof1d + i + j*dof1d;
}
}
break;
}
break;
}
}
H1FaceRestriction::H1FaceRestriction(const FiniteElementSpace &fes,
const ElementDofOrdering e_ordering,
const FaceType type,
bool build)
H1_ND_RT_FaceRestriction::H1_ND_RT_FaceRestriction(
const FiniteElementSpace &fes,
const ElementDofOrdering e_ordering,
const FaceType type,
bool build)
: fes(fes),
nf(fes.GetNFbyType(type)),
vdim(fes.GetVDim()),
@@ -727,18 +619,36 @@ H1FaceRestriction::H1FaceRestriction(const FiniteElementSpace &fes,
CheckFESpace(e_ordering);
ComputeScatterIndicesAndOffsets(e_ordering, type);
// Get the mapping from native DOF ordering to lexicographic ordering.
const FiniteElement *fe = fes.GetFE(0);
const TensorBasisElement* el =
dynamic_cast<const TensorBasisElement*>(fe);
const Array<int> &dof_map_ = el->GetDofMap();
if (dof_map_.Size() > 0)
{
dof_map.MakeRef(dof_map_);
}
else
{
// For certain types of elements dof_map_ is empty, in this case that
// means the element is already ordered lexicographically, so the
// permutation is the identity.
dof_map.SetSize(elem_dofs);
for (int i = 0; i < elem_dofs; ++i) { dof_map[i] = i; }
}
ComputeScatterIndicesAndOffsets(e_ordering, type);
ComputeGatherIndices(e_ordering,type);
}
H1FaceRestriction::H1FaceRestriction(const FiniteElementSpace &fes,
const ElementDofOrdering e_ordering,
const FaceType type)
: H1FaceRestriction(fes, e_ordering, type, true)
H1_ND_RT_FaceRestriction::H1_ND_RT_FaceRestriction(
const FiniteElementSpace &fes,
const ElementDofOrdering e_ordering,
const FaceType type)
: H1_ND_RT_FaceRestriction(fes, e_ordering, type, true)
{ }
void H1FaceRestriction::Mult(const Vector& x, Vector& y) const
void H1_ND_RT_FaceRestriction::Mult(const Vector& x, Vector& y) const
{
if (nf==0) { return; }
// Assumes all elements have the same number of dofs
@@ -750,17 +660,20 @@ void H1FaceRestriction::Mult(const Vector& x, Vector& y) const
auto d_y = Reshape(y.Write(), nface_dofs, vd, nf);
MFEM_FORALL(i, nfdofs,
{
const int idx = d_indices[i];
const int s_idx = d_indices[i];
const int sgn = (s_idx >= 0) ? 1 : -1;
const int idx = (s_idx >= 0) ? s_idx : -1 - s_idx;
const int dof = i % nface_dofs;
const int face = i / nface_dofs;
for (int c = 0; c < vd; ++c)
{
d_y(dof, c, face) = d_x(t?c:idx, t?idx:c);
d_y(dof, c, face) = sgn*d_x(t?c:idx, t?idx:c);
}
});
}
void H1FaceRestriction::AddMultTranspose(const Vector& x, Vector& y) const
void H1_ND_RT_FaceRestriction::AddMultTranspose(
const Vector& x, Vector& y) const
{
if (nf==0) { return; }
// Assumes all elements have the same number of dofs
@@ -780,15 +693,17 @@ void H1FaceRestriction::AddMultTranspose(const Vector& x, Vector& y) const
double dof_value = 0;
for (int j = offset; j < next_offset; ++j)
{
const int idx_j = d_indices[j];
dof_value += d_x(idx_j % nface_dofs, c, idx_j / nface_dofs);
const int s_idx_j = d_indices[j];
const int sgn = (s_idx_j >= 0) ? 1 : -1;
const int idx_j = (s_idx_j >= 0) ? s_idx_j : -1 - s_idx_j;
dof_value += sgn*d_x(idx_j % nface_dofs, c, idx_j / nface_dofs);
}
d_y(t?c:i,t?i:c) += dof_value;
}
});
}
void H1FaceRestriction::CheckFESpace(const ElementDofOrdering e_ordering)
void H1_ND_RT_FaceRestriction::CheckFESpace(const ElementDofOrdering e_ordering)
{
#ifdef MFEM_USE_MPI
@@ -810,7 +725,7 @@ void H1FaceRestriction::CheckFESpace(const ElementDofOrdering e_ordering)
(tfe->GetBasisType()==BasisType::GaussLobatto ||
tfe->GetBasisType()==BasisType::Positive),
"Only Gauss-Lobatto and Bernstein basis are supported in "
"H1FaceRestriction.");
"H1_ND_RT_FaceRestriction.");
// Assuming all finite elements are using Gauss-Lobatto.
const bool dof_reorder = (e_ordering == ElementDofOrdering::LEXICOGRAPHIC);
@@ -824,16 +739,11 @@ void H1FaceRestriction::CheckFESpace(const ElementDofOrdering e_ordering)
if (el) { continue; }
MFEM_ABORT("Finite element not suitable for lexicographic ordering");
}
const FiniteElement *fe = fes.GetFaceElement(0);
const TensorBasisElement* el =
dynamic_cast<const TensorBasisElement*>(fe);
const Array<int> &fe_dof_map = el->GetDofMap();
MFEM_VERIFY(fe_dof_map.Size() > 0, "invalid dof map");
}
#endif
}
void H1FaceRestriction::ComputeScatterIndicesAndOffsets(
void H1_ND_RT_FaceRestriction::ComputeScatterIndicesAndOffsets(
const ElementDofOrdering ordering,
const FaceType type)
{
@@ -871,7 +781,7 @@ void H1FaceRestriction::ComputeScatterIndicesAndOffsets(
}
}
void H1FaceRestriction::ComputeGatherIndices(
void H1_ND_RT_FaceRestriction::ComputeGatherIndices(
const ElementDofOrdering ordering,
const FaceType type)
{
@@ -904,7 +814,9 @@ void H1FaceRestriction::ComputeGatherIndices(
gather_offsets[0] = 0;
}
void H1FaceRestriction::SetFaceDofsScatterIndices(
static inline int absdof(int i) { return i < 0 ? -1-i : i; }
void H1_ND_RT_FaceRestriction::SetFaceDofsScatterIndices(
const Mesh::FaceInformation &face,
const int face_index,
const ElementDofOrdering ordering)
@@ -914,32 +826,29 @@ void H1FaceRestriction::SetFaceDofsScatterIndices(
MFEM_ASSERT(face.element[0].orientation==0,
"FaceRestriction used on degenerated mesh.");
const TensorBasisElement* el =
dynamic_cast<const TensorBasisElement*>(fes.GetFE(0));
const int *dof_map = el->GetDofMap().GetData();
fes.GetFE(0)->GetFaceMap(face.element[0].local_face_id, face_map);
const Table& e2dTable = fes.GetElementToDofTable();
const int* elem_map = e2dTable.GetJ();
const int face_id = face.element[0].local_face_id;
const int dim = fes.GetMesh()->Dimension();
const int dof1d = fes.GetFE(0)->GetOrder()+1;
const int elem_index = face.element[0].index;
const bool dof_reorder = (ordering == ElementDofOrdering::LEXICOGRAPHIC);
GetFaceDofs(dim, face_id, dof1d, face_map); // Only for quad and hex
for (int face_dof = 0; face_dof < face_dofs; ++face_dof)
{
const int nat_volume_dof = face_map[face_dof];
const int volume_dof = (!dof_reorder)?
nat_volume_dof:
dof_map[nat_volume_dof];
const int global_dof = elem_map[elem_index*elem_dofs + volume_dof];
const int s_volume_dof = (!dof_reorder) ?
nat_volume_dof :
dof_map[nat_volume_dof]; // signed
const int volume_dof = absdof(s_volume_dof);
const int s_global_dof = elem_map[elem_index*elem_dofs + volume_dof];
const int global_dof = absdof(s_global_dof);
const int restriction_dof = face_dofs*face_index + face_dof;
scatter_indices[restriction_dof] = global_dof;
scatter_indices[restriction_dof] = s_global_dof;
++gather_offsets[global_dof + 1];
}
}
void H1FaceRestriction::SetFaceDofsGatherIndices(
void H1_ND_RT_FaceRestriction::SetFaceDofsGatherIndices(
const Mesh::FaceInformation &face,
const int face_index,
const ElementDofOrdering ordering)
@@ -947,25 +856,25 @@ void H1FaceRestriction::SetFaceDofsGatherIndices(
MFEM_ASSERT(!(face.IsNonconformingCoarse()),
"This method should not be used on nonconforming coarse faces.");
const TensorBasisElement* el =
dynamic_cast<const TensorBasisElement*>(fes.GetFE(0));
const int *dof_map = el->GetDofMap().GetData();
fes.GetFE(0)->GetFaceMap(face.element[0].local_face_id, face_map);
const Table& e2dTable = fes.GetElementToDofTable();
const int* elem_map = e2dTable.GetJ();
const int face_id = face.element[0].local_face_id;
const int dim = fes.GetMesh()->Dimension();
const int dof1d = fes.GetFE(0)->GetOrder()+1;
const int elem_index = face.element[0].index;
const bool dof_reorder = (ordering == ElementDofOrdering::LEXICOGRAPHIC);
GetFaceDofs(dim, face_id, dof1d, face_map); // Only for quad and hex
for (int face_dof = 0; face_dof < face_dofs; ++face_dof)
{
const int nat_volume_dof = face_map[face_dof];
const int volume_dof = (!dof_reorder)?nat_volume_dof:dof_map[nat_volume_dof];
const int global_dof = elem_map[elem_index*elem_dofs + volume_dof];
const int s_volume_dof = (!dof_reorder)?nat_volume_dof:dof_map[nat_volume_dof];
const int volume_dof = absdof(s_volume_dof);
const int s_global_dof = elem_map[elem_index*elem_dofs + volume_dof];
const int sgn = (s_global_dof >= 0) ? 1 : -1;
const int global_dof = absdof(s_global_dof);
const int restriction_dof = face_dofs*face_index + face_dof;
gather_indices[gather_offsets[global_dof]++] = restriction_dof;
const int s_restriction_dof = (sgn >= 0) ? restriction_dof : -1 -
restriction_dof;
gather_indices[gather_offsets[global_dof]++] = s_restriction_dof;
}
}
@@ -1517,10 +1426,8 @@ void L2FaceRestriction::SetFaceDofsScatterIndices1(
const Table& e2dTable = fes.GetElementToDofTable();
const int* elem_map = e2dTable.GetJ();
const int face_id1 = face.element[0].local_face_id;
const int dim = fes.GetMesh()->Dimension();
const int dof1d = fes.GetFE(0)->GetOrder()+1;
const int elem_index = face.element[0].index;
GetFaceDofs(dim, face_id1, dof1d, face_map); // Only for quad and hex
fes.GetFE(0)->GetFaceMap(face_id1, face_map);
for (int face_dof_elem1 = 0; face_dof_elem1 < face_dofs; ++face_dof_elem1)
{
@@ -1546,7 +1453,7 @@ void L2FaceRestriction::PermuteAndSetFaceDofsScatterIndices2(
const int orientation = face.element[1].orientation;
const int dim = fes.GetMesh()->Dimension();
const int dof1d = fes.GetFE(0)->GetOrder()+1;
GetFaceDofs(dim, face_id2, dof1d, face_map); // Only for quad and hex
fes.GetFE(0)->GetFaceMap(face_id2, face_map);
for (int face_dof_elem1 = 0; face_dof_elem1 < face_dofs; ++face_dof_elem1)
{
@@ -1574,7 +1481,7 @@ void L2FaceRestriction::PermuteAndSetSharedFaceDofsScatterIndices2(
const int orientation = face.element[1].orientation;
const int dim = fes.GetMesh()->Dimension();
const int dof1d = fes.GetFE(0)->GetOrder()+1;
GetFaceDofs(dim, face_id2, dof1d, face_map); // Only for quad and hex
fes.GetFE(0)->GetFaceMap(face_id2, face_map);
Array<int> face_nbr_dofs;
const ParFiniteElementSpace &pfes =
static_cast<const ParFiniteElementSpace&>(this->fes);
@@ -1616,10 +1523,8 @@ void L2FaceRestriction::SetFaceDofsGatherIndices1(
const Table& e2dTable = fes.GetElementToDofTable();
const int* elem_map = e2dTable.GetJ();
const int face_id1 = face.element[0].local_face_id;
const int dim = fes.GetMesh()->Dimension();
const int dof1d = fes.GetFE(0)->GetOrder()+1;
const int elem_index = face.element[0].index;
GetFaceDofs(dim, face_id1, dof1d, face_map); // Only for quad and hex
fes.GetFE(0)->GetFaceMap(face_id1, face_map);
for (int face_dof_elem1 = 0; face_dof_elem1 < face_dofs; ++face_dof_elem1)
{
@@ -1645,7 +1550,7 @@ void L2FaceRestriction::PermuteAndSetFaceDofsGatherIndices2(
const int orientation = face.element[1].orientation;
const int dim = fes.GetMesh()->Dimension();
const int dof1d = fes.GetFE(0)->GetOrder()+1;
GetFaceDofs(dim, face_id2, dof1d, face_map); // Only for quad and hex
fes.GetFE(0)->GetFaceMap(face_id2, face_map);
for (int face_dof_elem1 = 0; face_dof_elem1 < face_dofs; ++face_dof_elem1)
{
+17 -12
View File
@@ -216,7 +216,7 @@ public:
/// Operator that extracts Face degrees of freedom for H1 FiniteElementSpaces.
/** Objects of this type are typically created and owned by FiniteElementSpace
objects, see FiniteElementSpace::GetFaceRestriction(). */
class H1FaceRestriction : public FaceRestriction
class H1_ND_RT_FaceRestriction : public FaceRestriction
{
protected:
const FiniteElementSpace &fes;
@@ -230,29 +230,30 @@ protected:
Array<int> scatter_indices; // Scattering indices for element 1 on each face
Array<int> gather_offsets; // offsets for the gathering indices of each dof
Array<int> gather_indices; // gathering indices for each dof
Array<int> dof_map; // mapping to lexicographic ordering
/** @brief Construct an H1FaceRestriction.
/** @brief Construct an H1_ND_RT_FaceRestriction.
@param[in] fes The FiniteElementSpace on which this operates
@param[in] ordering Request a specific element ordering
@param[in] type Request internal or boundary faces dofs
@param[in] build Request the NCL2FaceRestriction to compute the
scatter/gather indices. False should only be used
when inheriting from H1FaceRestriction.
when inheriting from H1_ND_RT_FaceRestriction.
*/
H1FaceRestriction(const FiniteElementSpace& fes,
const ElementDofOrdering ordering,
const FaceType type,
bool build);
H1_ND_RT_FaceRestriction(const FiniteElementSpace& fes,
const ElementDofOrdering ordering,
const FaceType type,
bool build);
public:
/** @brief Construct an H1FaceRestriction.
/** @brief Construct an H1_ND_RT_FaceRestriction.
@param[in] fes The FiniteElementSpace on which this operates
@param[in] ordering Request a specific element ordering
@param[in] type Request internal or boundary faces dofs */
H1FaceRestriction(const FiniteElementSpace& fes,
const ElementDofOrdering ordering,
const FaceType type);
H1_ND_RT_FaceRestriction(const FiniteElementSpace& fes,
const ElementDofOrdering ordering,
const FaceType type);
/** @brief Scatter the degrees of freedom, i.e. goes from L-Vector to
face E-Vector.
@@ -303,7 +304,7 @@ private:
protected:
mutable Array<int> face_map; // Used in the computation of GetFaceDofs
/** @brief Verify that H1FaceRestriction is build from an H1 FESpace.
/** @brief Verify that H1_ND_RT_FaceRestriction is build from an H1 FESpace.
@param[in] ordering The FESpace element ordering.
*/
@@ -332,6 +333,10 @@ protected:
const ElementDofOrdering ordering);
};
/// @brief Alias for H1_ND_RT_FaceRestriction, for backwards compatibility and
/// as base class for ParNCH1FaceRestriction.
using H1FaceRestriction = H1_ND_RT_FaceRestriction;
/// Operator that extracts Face degrees of freedom for L2 spaces.
/** Objects of this type are typically created and owned by FiniteElementSpace
objects, see FiniteElementSpace::GetFaceRestriction(). */
+14 -14
View File
@@ -116,20 +116,20 @@ void OmpWrap(const int N, HBODY &&h_body)
/// RAJA Cuda and Hip backends
#if defined(MFEM_USE_RAJA) && defined(RAJA_ENABLE_CUDA)
using cuda_launch_policy =
RAJA::expt::LaunchPolicy<RAJA::expt::null_launch_t, RAJA::expt::cuda_launch_t<true>>;
RAJA::LaunchPolicy<RAJA::cuda_launch_t<true>>;
using cuda_teams_x =
RAJA::expt::LoopPolicy<RAJA::loop_exec,RAJA::cuda_block_x_direct>;
RAJA::LoopPolicy<RAJA::cuda_block_x_direct>;
using cuda_threads_z =
RAJA::expt::LoopPolicy<RAJA::loop_exec,RAJA::cuda_thread_z_direct>;
RAJA::LoopPolicy<RAJA::cuda_thread_z_direct>;
#endif
#if defined(MFEM_USE_RAJA) && defined(RAJA_ENABLE_HIP)
using hip_launch_policy =
RAJA::expt::LaunchPolicy<RAJA::expt::null_launch_t, RAJA::expt::hip_launch_t<true>>;
RAJA::LaunchPolicy<RAJA::hip_launch_t<true>>;
using hip_teams_x =
RAJA::expt::LoopPolicy<RAJA::loop_exec,RAJA::hip_block_x_direct>;
RAJA::LoopPolicy<RAJA::hip_block_x_direct>;
using hip_threads_z =
RAJA::expt::LoopPolicy<RAJA::loop_exec,RAJA::hip_thread_z_direct>;
RAJA::LoopPolicy<RAJA::hip_thread_z_direct>;
#endif
#if defined(MFEM_USE_RAJA) && defined(RAJA_ENABLE_CUDA)
@@ -148,11 +148,11 @@ void RajaCuWrap2D(const int N, DBODY &&d_body,
MFEM_VERIFY(BZ>0, "");
const int G = (N+BZ-1)/BZ;
using namespace RAJA::expt;
using namespace RAJA;
using RAJA::RangeSegment;
launch<cuda_launch_policy>
(DEVICE, Grid(Teams(G), Threads(X, Y, BZ)),
(LaunchParams(Teams(G), Threads(X, Y, BZ)),
[=] RAJA_DEVICE (LaunchContext ctx)
{
@@ -181,11 +181,11 @@ void RajaCuWrap3D(const int N, DBODY &&d_body,
{
MFEM_VERIFY(N>0, "");
const int GRID = G == 0 ? N : G;
using namespace RAJA::expt;
using namespace RAJA;
using RAJA::RangeSegment;
launch<cuda_launch_policy>
(DEVICE, Grid(Teams(GRID), Threads(X, Y, Z)),
(LaunchParams(Teams(GRID), Threads(X, Y, Z)),
[=] RAJA_DEVICE (LaunchContext ctx)
{
@@ -250,11 +250,11 @@ void RajaHipWrap2D(const int N, DBODY &&d_body,
MFEM_VERIFY(BZ>0, "");
const int G = (N+BZ-1)/BZ;
using namespace RAJA::expt;
using namespace RAJA;
using RAJA::RangeSegment;
launch<hip_launch_policy>
(DEVICE, Grid(Teams(G), Threads(X, Y, BZ)),
(LaunchParams(Teams(G), Threads(X, Y, BZ)),
[=] RAJA_DEVICE (LaunchContext ctx)
{
@@ -283,11 +283,11 @@ void RajaHipWrap3D(const int N, DBODY &&d_body,
{
MFEM_VERIFY(N>0, "");
const int GRID = G == 0 ? N : G;
using namespace RAJA::expt;
using namespace RAJA;
using RAJA::RangeSegment;
launch<hip_launch_policy>
(DEVICE, Grid(Teams(GRID), Threads(X, Y, Z)),
(LaunchParams(Teams(GRID), Threads(X, Y, Z)),
[=] RAJA_DEVICE (LaunchContext ctx)
{
+5 -4
View File
@@ -432,17 +432,18 @@ static int mfem_gnutls_verify_callback(gnutls_session_t session)
}
#ifdef MFEM_DEBUG
gnutls_datum_t out;
gnutls_datum_t status_str;
gnutls_certificate_type_t type = gnutls_certificate_type_get(session);
ret = gnutls_certificate_verification_status_print(status, type, &out, 0);
ret = gnutls_certificate_verification_status_print(
status, type, &status_str, 0);
if (ret < 0)
{
mfem::out << "Error in gnutls_certificate_verification_status_print:"
<< gnutls_strerror(ret) << std::endl;
return GNUTLS_E_CERTIFICATE_ERROR;
}
mfem::out << out.data << std::endl;
gnutls_free(out.data);
mfem::out << status_str.data << std::endl;
gnutls_free(status_str.data);
#endif
#else // --> GNUTLS_VERSION_NUMBER < 0x030104
int ret = gnutls_certificate_verify_peers2(session, &status);
+11 -10
View File
@@ -1319,6 +1319,7 @@ std::ostream& operator<<(std::ostream& os, const Mesh::FaceInformation& info)
os << "NA";
break;
}
os << '\n';
os << "element[0].location=";
switch (info.element[0].location)
{
@@ -1332,7 +1333,7 @@ std::ostream& operator<<(std::ostream& os, const Mesh::FaceInformation& info)
os << "NA";
break;
}
os << std::endl;
os << '\n';
os << "element[1].location=";
switch (info.element[1].location)
{
@@ -1346,7 +1347,7 @@ std::ostream& operator<<(std::ostream& os, const Mesh::FaceInformation& info)
os << "NA";
break;
}
os << std::endl;
os << '\n';
os << "element[0].conformity=";
switch (info.element[0].conformity)
{
@@ -1363,7 +1364,7 @@ std::ostream& operator<<(std::ostream& os, const Mesh::FaceInformation& info)
os << "NA";
break;
}
os << std::endl;
os << '\n';
os << "element[1].conformity=";
switch (info.element[1].conformity)
{
@@ -1380,13 +1381,13 @@ std::ostream& operator<<(std::ostream& os, const Mesh::FaceInformation& info)
os << "NA";
break;
}
os << std::endl;
os << "element[0].index=" << info.element[0].index << std::endl
<< "element[1].index=" << info.element[1].index << std::endl
<< "element[0].local_face_id=" << info.element[0].local_face_id << std::endl
<< "element[1].local_face_id=" << info.element[1].local_face_id << std::endl
<< "element[0].orientation=" << info.element[0].orientation << std::endl
<< "element[1].orientation=" << info.element[1].orientation << std::endl
os << '\n';
os << "element[0].index=" << info.element[0].index << '\n'
<< "element[1].index=" << info.element[1].index << '\n'
<< "element[0].local_face_id=" << info.element[0].local_face_id << '\n'
<< "element[1].local_face_id=" << info.element[1].local_face_id << '\n'
<< "element[0].orientation=" << info.element[0].orientation << '\n'
<< "element[1].orientation=" << info.element[1].orientation << '\n'
<< "ncface=" << info.ncface << std::endl;
return os;
}
+14 -10
View File
@@ -31,16 +31,20 @@ if (MFEM_USE_MPI)
EXTRA_SOURCES ${HOOKE_SOURCES}
EXTRA_HEADERS ${HOOKE_HEADERS})
add_mfem_miniapp(hooke
MAIN hooke.cpp
${HOOKE_COMMON_FILES}
LIBRARIES mfem)
# The hooke miniapp crashes the AMD HIP compiler, so for now we disable it
# when HIP is enabled:
if (NOT MFEM_USE_HIP)
add_mfem_miniapp(hooke
MAIN hooke.cpp
${HOOKE_COMMON_FILES}
LIBRARIES mfem)
# Add parallel tests.
if (MFEM_ENABLE_TESTING)
add_test(NAME hooke_np=${MFEM_MPI_NP}
COMMAND ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} ${MFEM_MPI_NP}
${MPIEXEC_PREFLAGS} $<TARGET_FILE:hooke> -rs 0 -no-vis
${MPIEXEC_POSTFLAGS})
# Add parallel tests.
if (MFEM_ENABLE_TESTING)
add_test(NAME hooke_np=${MFEM_MPI_NP}
COMMAND ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} ${MFEM_MPI_NP}
${MPIEXEC_PREFLAGS} $<TARGET_FILE:hooke> -rs 0 -no-vis
${MPIEXEC_POSTFLAGS})
endif()
endif()
endif ()
+1
View File
@@ -68,6 +68,7 @@ set(UNIT_TESTS_SRCS
fem/test_estimator.cpp
fem/test_face_elem_trans.cpp
fem/test_face_permutation.cpp
fem/test_face_restriction.cpp
fem/test_fe.cpp
fem/test_get_value.cpp
fem/test_getderivative.cpp
+113
View File
@@ -0,0 +1,113 @@
// Copyright (c) 2010-2022, 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"
using namespace mfem;
Mesh MakeCartesianMesh(int nx, int dim)
{
if (dim == 2)
{
return Mesh::MakeCartesian2D(nx, nx, Element::QUADRILATERAL, true);
}
else
{
return Mesh::MakeCartesian3D(nx, nx, nx, Element::HEXAHEDRON);
}
}
TEST_CASE("Vector FE Face Restriction", "[FaceRestriction]")
{
enum class SpaceType {RT, ND};
const auto space_type = GENERATE(SpaceType::RT, SpaceType::ND);
const int dim = GENERATE(2, 3);
const int nx = 3;
const int order = 4;
CAPTURE(dim);
Mesh mesh = MakeCartesianMesh(nx, dim);
int ndof_per_face;
std::unique_ptr<FiniteElementCollection> fec;
if (space_type == SpaceType::RT)
{
fec.reset(new RT_FECollection(order-1, dim));
ndof_per_face = int(pow(order, dim-1));
}
else
{
fec.reset(new ND_FECollection(order, dim));
ndof_per_face = (dim - 1)*order*int(pow(order + 1, dim - 2));
}
FiniteElementSpace fes(&mesh, fec.get());
auto ordering = ElementDofOrdering::LEXICOGRAPHIC;
auto ftype = FaceType::Boundary;
const int nfaces = fes.GetNFbyType(FaceType::Boundary);
const FaceRestriction *face_restr =
fes.GetFaceRestriction(ordering, ftype);
REQUIRE(face_restr != nullptr);
Array<int> bdr_dofs;
fes.GetBoundaryTrueDofs(bdr_dofs);
// Set gf to have random values on the boundary, zero on the interior
GridFunction gf(&fes);
gf.Randomize(0);
gf.SetSubVectorComplement(bdr_dofs, 0.0);
// Mapping to face E-vector and back to L-vector should give back the
// original grid function.
Vector face_vec(face_restr->Height());
REQUIRE(face_vec.Size() == nfaces*ndof_per_face);
face_restr->Mult(gf, face_vec);
if (space_type == SpaceType::ND && dim == 3)
{
// Adjust for multiplicity. In all other cases, each boundary DOF is
// unique (not shared between faces). In the case of 3D ND elements, some
// boundary DOFs are shared between two faces (i.e. those that lie on
// element edges).
//
// This adjustment will ensure that the original vector is recovered after
// multiplying by the transpose of the face restriction operator.
const int n = order*(order+1);
for (int f = 0; f < fes.GetNFbyType(ftype); ++f)
{
for (int d = 0; d < 2; ++d)
{
const int nx = (d == 0) ? order : order + 1;
const int ny = (d == 0) ? order + 1 : order;
for (int i = 0; i < n; ++i)
{
const int ix = i % nx;
const int iy = i / nx;
if ((d == 0 && (iy == 0 || iy == ny - 1)) ||
(d == 1 && (ix == 0 || ix == nx - 1)))
{
face_vec[f*ndof_per_face + d*n + i] *= 0.5;
}
}
}
}
}
GridFunction gf2(&fes);
face_restr->MultTranspose(face_vec, gf2);
gf2 -= gf;
REQUIRE(gf2.Normlinf() == MFEM_Approx(0.0));
}
+29
View File
@@ -282,4 +282,33 @@ TEST_CASE("Linear Form Extension", "[LinearFormExtension], [CUDA]")
REQUIRE(d1.Norml2() == MFEM_Approx(0.0));
}
SECTION("VectorFE")
{
Mesh mesh(mesh_file);
const int dim = mesh.Dimension();
CAPTURE(mesh_file, dim, p);
RT_FECollection fec(p-1, dim);
FiniteElementSpace fes(&mesh, &fec);
FunctionCoefficient coeff(f);
LinearForm d1(&fes);
d1.AddBoundaryIntegrator(new VectorFEBoundaryFluxLFIntegrator(coeff));
d1.UseFastAssembly(true);
d1.Assemble();
LinearForm d2(&fes);
d2.AddBoundaryIntegrator(new VectorFEBoundaryFluxLFIntegrator(coeff));
d2.UseFastAssembly(false);
d2.Assemble();
CAPTURE(d1.Norml2(), d2.Norml2());
d1 -= d2;
REQUIRE(d1.Norml2() == MFEM_Approx(0.0));
}
}