Compare commits
265
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
552d6857cb | ||
|
|
a37d46e917 | ||
|
|
4acdb072b6 | ||
|
|
9dbb184537 | ||
|
|
d67762a1c9 | ||
|
|
cd4e583f9f | ||
|
|
ee94776558 | ||
|
|
f9be12c539 | ||
|
|
af478afd00 | ||
|
|
519e86f134 | ||
|
|
f2578fd479 | ||
|
|
563cd25971 | ||
|
|
875c62ca1f | ||
|
|
731b0b7049 | ||
|
|
3c77ad25e9 | ||
|
|
e13d1a1d53 | ||
|
|
09e73b5cee | ||
|
|
464b441409 | ||
|
|
8ae9b217f9 | ||
|
|
3f0a5ad501 | ||
|
|
dfb697b9ae | ||
|
|
cf01c6cc8b | ||
|
|
e4dc9a3941 | ||
|
|
6a104e94e1 | ||
|
|
bf1b25d82e | ||
|
|
b7a0b2cf9a | ||
|
|
9a8f0ad0ef | ||
|
|
ee2c069531 | ||
|
|
27a60a4ca3 | ||
|
|
b1d5104fed | ||
|
|
26eecf7575 | ||
|
|
9c26ef5401 | ||
|
|
bce6e2ca76 | ||
|
|
26f8f0e955 | ||
|
|
dfc2dfeb52 | ||
|
|
7173477670 | ||
|
|
30249a4857 | ||
|
|
8a9bdf863a | ||
|
|
b72368c698 | ||
|
|
e6224e00aa | ||
|
|
7b82f7b8e0 | ||
|
|
582ffe8b66 | ||
|
|
aa5b2d4b95 | ||
|
|
23616a21f0 | ||
|
|
df9cc72e58 | ||
|
|
15740fcbd3 | ||
|
|
862c539276 | ||
|
|
7ff759a7ee | ||
|
|
d220a2975c | ||
|
|
cdde0046ef | ||
|
|
9f36ae566c | ||
|
|
4465fcbd46 | ||
|
|
807b13b236 | ||
|
|
70f4468f0b | ||
|
|
5db2e7b347 | ||
|
|
c1df332094 | ||
|
|
75b115cf93 | ||
|
|
59579f2cdb | ||
|
|
c0d2821d3e | ||
|
|
082e25ffe8 | ||
|
|
7d343e56a5 | ||
|
|
1eb3d185d5 | ||
|
|
a2fd029daa | ||
|
|
b48b990ac8 | ||
|
|
80c69e9856 | ||
|
|
dbdee73113 | ||
|
|
7a8a1976a2 | ||
|
|
6911ed0a8a | ||
|
|
986e2c04d2 | ||
|
|
9db5388362 | ||
|
|
dbf987fb73 | ||
|
|
c4a6d855eb | ||
|
|
80c2ee6fed | ||
|
|
19fa34eabe | ||
|
|
0e2d49799c | ||
|
|
562b980e7d | ||
|
|
2adb4576d3 | ||
|
|
9f2490cd95 | ||
|
|
4126d32772 | ||
|
|
080780f047 | ||
|
|
ca2ee9ab05 | ||
|
|
0d87692863 | ||
|
|
7bc231eb85 | ||
|
|
9794243c8b | ||
|
|
ea3bed0a91 | ||
|
|
877937b8ae | ||
|
|
b50d546dac | ||
|
|
fd01ef188f | ||
|
|
f2a29326c0 | ||
|
|
1f4a40d9a0 | ||
|
|
e671fa5737 | ||
|
|
40d1550fd6 | ||
|
|
ae33f831eb | ||
|
|
193f8a6801 | ||
|
|
110720dd04 | ||
|
|
3fd335c77b | ||
|
|
90e1f08bc7 | ||
|
|
37d004206a | ||
|
|
e1d9b7cfff | ||
|
|
d1c44a7369 | ||
|
|
2c39547bdd | ||
|
|
db66a4423f | ||
|
|
8425377bd1 | ||
|
|
ea76f8d477 | ||
|
|
992c5dfc4c | ||
|
|
ec94332c2a | ||
|
|
5e75bd0837 | ||
|
|
5f889293c2 | ||
|
|
afc5aa96dc | ||
|
|
9125bf8291 | ||
|
|
1f89bfa040 | ||
|
|
e0c1018c9c | ||
|
|
7f263221dd | ||
|
|
f82e8c04f9 | ||
|
|
c731dee195 | ||
|
|
64c78ff17e | ||
|
|
01444aa93c | ||
|
|
07c0f31e37 | ||
|
|
b70589bcac | ||
|
|
17bbaf500c | ||
|
|
5cf82dc903 | ||
|
|
f19ec00b0a | ||
|
|
9480e5c5bb | ||
|
|
2cfb86aade | ||
|
|
058fdaae3f | ||
|
|
9a92e4875b | ||
|
|
cc21011998 | ||
|
|
09aa9374a9 | ||
|
|
78e8cdd7e8 | ||
|
|
86e75206b1 | ||
|
|
04a3fd9bb2 | ||
|
|
6e111c3ada | ||
|
|
ea449e1c41 | ||
|
|
c9115e74fb | ||
|
|
35040b0336 | ||
|
|
4ce11b4a12 | ||
|
|
f17b1c4e4f | ||
|
|
7cfd3f5c7e | ||
|
|
63ea83c455 | ||
|
|
38ee46c40f | ||
|
|
c271283490 | ||
|
|
808f5c94db | ||
|
|
287631bee4 | ||
|
|
4da6e52698 | ||
|
|
cc12d37693 | ||
|
|
35fe98417c | ||
|
|
936f1fc848 | ||
|
|
2cadeaad4c | ||
|
|
f7fa092013 | ||
|
|
9103db88b6 | ||
|
|
0303f12887 | ||
|
|
6650a1dffe | ||
|
|
9a22d4533f | ||
|
|
412715c2e4 | ||
|
|
9803cbb671 | ||
|
|
6805b8c7f6 | ||
|
|
7aeba0ff83 | ||
|
|
dbd55a8fb4 | ||
|
|
277199c3a5 | ||
|
|
e7e00e6e39 | ||
|
|
e78c1b8b4c | ||
|
|
dcec51b98a | ||
|
|
2e9f545a4e | ||
|
|
e49854f3ba | ||
|
|
0bb71aa1fa | ||
|
|
07dc0f6cfa | ||
|
|
847183e668 | ||
|
|
8a1a264eaa | ||
|
|
606a597303 | ||
|
|
e194835abd | ||
|
|
208f9b81b3 | ||
|
|
e50ebb573e | ||
|
|
c315298a86 | ||
|
|
12bafa69e8 | ||
|
|
1729961a89 | ||
|
|
6dcc19abab | ||
|
|
a5fccc7514 | ||
|
|
cfe25c432c | ||
|
|
b8fe4cbf97 | ||
|
|
ea0abf46fe | ||
|
|
556b43f900 | ||
|
|
aa567465ac | ||
|
|
986cee600f | ||
|
|
7634a4b663 | ||
|
|
1368cfb8cb | ||
|
|
53c561cbf0 | ||
|
|
3c2e847e0a | ||
|
|
f65f60dbb5 | ||
|
|
9dab032bd0 | ||
|
|
e49a62ab1b | ||
|
|
18a256d17b | ||
|
|
47b73713a6 | ||
|
|
43f4d9d6e3 | ||
|
|
081bbdd1c2 | ||
|
|
7c80866b49 | ||
|
|
7ec6704a51 | ||
|
|
3c1606903c | ||
|
|
cdfe8102ae | ||
|
|
754235e932 | ||
|
|
0cc04c8bd9 | ||
|
|
5f82901f73 | ||
|
|
41a262563b | ||
|
|
b081aa7eea | ||
|
|
3c7a63f742 | ||
|
|
ffe14214ae | ||
|
|
5b82bf0328 | ||
|
|
900085cb9d | ||
|
|
0376760aa1 | ||
|
|
2ab9ae818e | ||
|
|
1035382fad | ||
|
|
90f33b1a86 | ||
|
|
136ae2d98f | ||
|
|
449a57d9ad | ||
|
|
41b65d6333 | ||
|
|
17e48c4d46 | ||
|
|
7ac5b61955 | ||
|
|
c98f117689 | ||
|
|
337a0298bf | ||
|
|
7930b9b3ca | ||
|
|
2534d2207d | ||
|
|
260b817b3c | ||
|
|
613d5dd826 | ||
|
|
323ee572b6 | ||
|
|
56ff5ac5bb | ||
|
|
e1a06bd6c8 | ||
|
|
9acae54669 | ||
|
|
7d92e22a45 | ||
|
|
7c0bc9c338 | ||
|
|
c0d8ba243d | ||
|
|
c946067b9b | ||
|
|
db803eb74a | ||
|
|
c35e5ad7fe | ||
|
|
059f0acee6 | ||
|
|
e18d795334 | ||
|
|
377c5d16f5 | ||
|
|
674379e6c9 | ||
|
|
243b234033 | ||
|
|
56c5f17e01 | ||
|
|
5afe2a09a3 | ||
|
|
64f8ab42c1 | ||
|
|
5fb9fc8ec5 | ||
|
|
5a5dcd44df | ||
|
|
d4ff68d2bd | ||
|
|
ac3417555c | ||
|
|
e8bd89a672 | ||
|
|
fc3c897fa6 | ||
|
|
58bc2b070e | ||
|
|
991284d3b6 | ||
|
|
587d437f32 | ||
|
|
c0ae0f0a4b | ||
|
|
615448bbc3 | ||
|
|
68cfee09e0 | ||
|
|
a0656da6ef | ||
|
|
1395d44724 | ||
|
|
a42a394111 | ||
|
|
0fa8b85391 | ||
|
|
d4db7ef8cd | ||
|
|
3d9af90191 | ||
|
|
1ab9f62208 | ||
|
|
179e6ec141 | ||
|
|
2434d4ac71 | ||
|
|
a9e5c58897 | ||
|
|
35d9fa1f6c | ||
|
|
fb9117e9fb | ||
|
|
d4d149a5ff |
@@ -29,3 +29,47 @@ jobs:
|
||||
operations-per-run: 500
|
||||
exempt-issue-labels: "bug,WIP,ready-for-review,in-review,in-next"
|
||||
exempt-pr-labels: "bug,WIP,ready-for-review,in-review,in-next"
|
||||
|
||||
# Stale action for PRs with "in-review" label.
|
||||
stale-in-review-pr:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
actions: write
|
||||
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
stale-pr-message: ':warning: This PR has been automatically marked as stale because it has not had any activity in the last 150 days. *If no activity occurs in the next 30 days, it will be automatically closed.* Thank you for your contributions.'
|
||||
only-pr-labels: "in-review"
|
||||
days-before-pr-stale: 150
|
||||
days-before-pr-close: 30
|
||||
days-before-issue-stale: -1
|
||||
days-before-issue-close: -1
|
||||
stale-pr-label: 'stale'
|
||||
operations-per-run: 500
|
||||
|
||||
# Stale action for PRs with "WIP" label.
|
||||
stale-wip-pr:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
actions: write
|
||||
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
stale-pr-message: ':warning: This PR has been automatically marked as stale because it has not had any activity in the last 300 days. *If no activity occurs in the next 30 days, it will be automatically closed.* Thank you for your contributions.'
|
||||
only-pr-labels: "WIP"
|
||||
days-before-pr-stale: 300
|
||||
days-before-pr-close: 30
|
||||
days-before-issue-stale: -1
|
||||
days-before-issue-close: -1
|
||||
stale-pr-label: 'stale'
|
||||
operations-per-run: 500
|
||||
|
||||
@@ -208,10 +208,13 @@ miniapps/electromagnetics/volta
|
||||
miniapps/electromagnetics/tesla
|
||||
miniapps/electromagnetics/maxwell
|
||||
miniapps/electromagnetics/joule
|
||||
miniapps/electromagnetics/lorentz
|
||||
miniapps/electromagnetics/Volta-AMR*
|
||||
miniapps/electromagnetics/Tesla-AMR*
|
||||
miniapps/electromagnetics/Maxwell-Parallel*
|
||||
miniapps/electromagnetics/Joule_[0-9]*
|
||||
miniapps/electromagnetics/Lorentz_[0-9]*
|
||||
miniapps/electromagnetics/Lorentz.dat
|
||||
|
||||
miniapps/gslib/field-diff
|
||||
miniapps/gslib/field-interp
|
||||
|
||||
@@ -91,6 +91,10 @@ New and updated examples and miniapps
|
||||
- Added a new miniapp (tools/gridfunction-bounds) to compute piecewise linear
|
||||
bounds on a given high-order grid function.
|
||||
|
||||
- Added a new miniapp (electromagnetics/lorentz) which computes the trajectory
|
||||
of a charged particle, subject to Lorentz forces, in electrostatic and/or
|
||||
magnetostatic fields as computed by the volta or tesla miniapps.
|
||||
|
||||
API changes:
|
||||
-----------
|
||||
- mfem::internal::tensor and mfem::internal::dual have been moved to
|
||||
|
||||
+11
-6
@@ -278,6 +278,11 @@ if (MFEM_USE_OPENMP OR MFEM_USE_LEGACY_OPENMP)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Umpire (must be included before hypre, so hypre can use it if needed)
|
||||
if (MFEM_USE_UMPIRE)
|
||||
find_package(UMPIRE REQUIRED)
|
||||
endif()
|
||||
|
||||
# MPI -> hypre; PETSc (optional)
|
||||
if (MFEM_USE_MPI)
|
||||
find_package(MPI REQUIRED)
|
||||
@@ -495,14 +500,13 @@ endif()
|
||||
|
||||
# RAJA
|
||||
if (MFEM_USE_RAJA)
|
||||
# RAJA uses FindCUDA, which needs CMP0146=OLD in CMake >= 3.27
|
||||
if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.27.0)
|
||||
cmake_policy(SET CMP0146 OLD)
|
||||
endif()
|
||||
find_package(RAJA REQUIRED)
|
||||
endif()
|
||||
|
||||
# UMPIRE
|
||||
if (MFEM_USE_UMPIRE)
|
||||
find_package(UMPIRE REQUIRED)
|
||||
endif()
|
||||
|
||||
# GOOGLE-BENCHMARK
|
||||
if (MFEM_USE_BENCHMARK)
|
||||
find_package(Benchmark REQUIRED)
|
||||
@@ -596,7 +600,7 @@ set(MFEM_TPLS OPENMP HYPRE LAPACK BLAS SuperLUDist STRUMPACK METIS SuiteSparse
|
||||
NETCDF MPFR PUMI HIOP POSIXCLOCKS MFEMBacktrace ZLIB OCCA CEED RAJA UMPIRE
|
||||
ADIOS2 MKL_CPARDISO MKL_PARDISO AMGX MAGMA CUSPARSE CUBLAS CALIPER CODIPACK
|
||||
BENCHMARK PARELAG TRIBOL MPI_CXX HIP HIPBLAS HIPSPARSE MOONOLITH BLITZ
|
||||
ALGOIM ENZYME)
|
||||
ALGOIM ENZYME CUDA::cudart)
|
||||
|
||||
# Add all created targets and *_FOUND libraries in the variables TPL_TARGETS and
|
||||
# TPL_LIBRARIES, respectively.
|
||||
@@ -614,6 +618,7 @@ foreach(TPL IN LISTS MFEM_TPLS)
|
||||
endif()
|
||||
endif()
|
||||
endforeach(TPL)
|
||||
|
||||
list(REVERSE TPL_LIBRARIES)
|
||||
list(REMOVE_DUPLICATES TPL_LIBRARIES)
|
||||
list(REVERSE TPL_LIBRARIES)
|
||||
|
||||
@@ -84,6 +84,31 @@ set_and_check(MFEM_LIBRARY_DIR "@PACKAGE_LIB_INSTALL_DIR@")
|
||||
|
||||
check_required_components(MFEM)
|
||||
|
||||
include(CMakeFindDependencyMacro)
|
||||
|
||||
if (MFEM_USE_CUDA)
|
||||
# required for projects linking to MFEM+CUDA, even if they don't use CUDA directly
|
||||
find_dependency(CUDAToolkit)
|
||||
endif (MFEM_USE_CUDA)
|
||||
|
||||
if (MFEM_USE_HIP)
|
||||
# hip/rocm uses the modern MFEM way of linking to targets, need to find dependencies
|
||||
find_dependency(HIP)
|
||||
find_dependency(HIPBLAS)
|
||||
find_dependency(HIPSPARSE)
|
||||
if (MFEM_USE_MPI)
|
||||
# assume HYPRE uses HIP
|
||||
# alternatively could check HYPRE_USING_HIP
|
||||
find_dependency(rocsparse)
|
||||
find_dependency(rocrand)
|
||||
find_dependency(rocsolver)
|
||||
endif (MFEM_USE_MPI)
|
||||
endif (MFEM_USE_HIP)
|
||||
|
||||
if (MFEM_USE_RAJA)
|
||||
find_dependency(RAJA)
|
||||
endif()
|
||||
|
||||
if (NOT TARGET mfem)
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/MFEMTargets.cmake)
|
||||
endif (NOT TARGET mfem)
|
||||
|
||||
@@ -27,6 +27,7 @@ if (HYPRE_FOUND OR TARGET HYPRE)
|
||||
if (HYPRE_USING_HIP)
|
||||
find_package(rocsparse REQUIRED)
|
||||
find_package(rocrand REQUIRED)
|
||||
find_package(rocsolver REQUIRED)
|
||||
endif()
|
||||
if (HYPRE_LIBRARIES AND HYPRE_INCLUDE_DIRS AND HYPRE_VERSION)
|
||||
find_package_handle_standard_args(HYPRE
|
||||
@@ -37,51 +38,91 @@ if (HYPRE_FOUND OR TARGET HYPRE)
|
||||
endif()
|
||||
|
||||
if (HYPRE_FETCH OR FETCH_TPLS)
|
||||
# Collect all HYPRE_ENABLE variables and pass them to hypre, assuming they are BOOL.
|
||||
set(HYPRE_CMAKE_OPTIONS "")
|
||||
get_cmake_property(all_vars VARIABLES)
|
||||
foreach(var ${all_vars})
|
||||
if(var MATCHES "^HYPRE_ENABLE")
|
||||
list(APPEND HYPRE_CMAKE_OPTIONS "-D${var}:BOOL=${${var}}")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
set(HYPRE_FETCH_VERSION 2.33.0)
|
||||
set(HYPRE_FETCH_TAG "v${HYPRE_FETCH_VERSION}" CACHE STRING "Tag, branch, or commit for HYPRE")
|
||||
add_library(HYPRE STATIC IMPORTED)
|
||||
# set options and associated dependencies
|
||||
set(CMAKE_OPTIONS)
|
||||
list(APPEND CMAKE_OPTIONS -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE})
|
||||
list(APPEND HYPRE_CMAKE_OPTIONS -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE})
|
||||
if (MFEM_USE_CUDA)
|
||||
list(APPEND CMAKE_OPTIONS -DHYPRE_WITH_CUDA:BOOL=ON)
|
||||
list(APPEND HYPRE_CMAKE_OPTIONS -DHYPRE_ENABLE_CUDA:BOOL=ON -DCMAKE_CUDA_ARCHITECTURES:STRING=${CMAKE_CUDA_ARCHITECTURES})
|
||||
find_package(CUDAToolkit REQUIRED)
|
||||
target_link_libraries(HYPRE INTERFACE CUDA::cusparse CUDA::curand CUDA::cublas)
|
||||
elseif (MFEM_USE_HIP)
|
||||
list(APPEND CMAKE_OPTIONS -DHYPRE_WITH_HIP:BOOL=ON)
|
||||
list(APPEND HYPRE_CMAKE_OPTIONS -DHYPRE_ENABLE_HIP:BOOL=ON)
|
||||
find_package(rocsparse REQUIRED)
|
||||
find_package(rocrand REQUIRED)
|
||||
target_link_libraries(HYPRE INTERFACE rocsparse rocrand)
|
||||
endif()
|
||||
if (MFEM_USE_CUDA OR MFEM_USE_HIP)
|
||||
if (MFEM_USE_UMPIRE)
|
||||
if (EXISTS ${umpire_DIR})
|
||||
list(APPEND HYPRE_CMAKE_OPTIONS -DHYPRE_ENABLE_UMPIRE:BOOL=ON -Dumpire_DIR:PATH=${umpire_DIR})
|
||||
else()
|
||||
message(FATAL_ERROR "MFEM_USE_UMPIRE=ON, however umpire_DIR isn't visible to HYPRE")
|
||||
endif()
|
||||
else()
|
||||
list(APPEND HYPRE_CMAKE_OPTIONS -DHYPRE_ENABLE_UMPIRE:BOOL=OFF)
|
||||
message(WARNING
|
||||
"================================================================================
|
||||
Umpire is disabled while building HYPRE with GPU support.
|
||||
This is not recommended for performance reasons!
|
||||
Consider enabling Umpire with -DMFEM_USE_UMPIRE=ON and providing -DUMPIRE_DIR.
|
||||
================================================================================")
|
||||
endif()
|
||||
endif()
|
||||
if (MFEM_USE_SINGLE)
|
||||
list(APPEND CMAKE_OPTIONS -DHYPRE_ENABLE_SINGLE:BOOL=ON)
|
||||
list(APPEND HYPRE_CMAKE_OPTIONS -DHYPRE_ENABLE_SINGLE:BOOL=ON)
|
||||
endif()
|
||||
# define external project and create future include directory so it is present
|
||||
# to pass CMake checks at end of MFEM configuration step
|
||||
message(STATUS "Will fetch HYPRE ${HYPRE_FETCH_VERSION} to be built with ${CMAKE_OPTIONS}")
|
||||
set(PREFIX ${CMAKE_BINARY_DIR}/fetch/hypre)
|
||||
message(STATUS "Will fetch HYPRE ${HYPRE_FETCH_TAG} to be built with ${HYPRE_CMAKE_OPTIONS}")
|
||||
set(HYPRE_INSTALL ${CMAKE_BINARY_DIR}/fetch/hypre)
|
||||
include(ExternalProject)
|
||||
ExternalProject_Add(hypre
|
||||
GIT_REPOSITORY https://github.com/hypre-space/hypre.git
|
||||
GIT_TAG v${HYPRE_FETCH_VERSION}
|
||||
GIT_TAG ${HYPRE_FETCH_TAG}
|
||||
GIT_SHALLOW TRUE
|
||||
GIT_PROGRESS TRUE
|
||||
UPDATE_DISCONNECTED TRUE
|
||||
SOURCE_SUBDIR src
|
||||
PREFIX ${PREFIX}
|
||||
CMAKE_CACHE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${PREFIX} -DCMAKE_INSTALL_LIBDIR:PATH=lib ${CMAKE_OPTIONS})
|
||||
file(MAKE_DIRECTORY ${PREFIX}/include)
|
||||
PREFIX ${HYPRE_INSTALL}
|
||||
BUILD_COMMAND ${CMAKE_COMMAND} --build . -- -j${CMAKE_BUILD_PARALLEL_LEVEL}
|
||||
CMAKE_CACHE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${HYPRE_INSTALL} -DCMAKE_INSTALL_LIBDIR:PATH=lib ${HYPRE_CMAKE_OPTIONS})
|
||||
file(MAKE_DIRECTORY ${HYPRE_INSTALL}/include)
|
||||
# set imported library target properties
|
||||
add_dependencies(HYPRE hypre)
|
||||
set_target_properties(HYPRE PROPERTIES
|
||||
IMPORTED_LOCATION ${PREFIX}/lib/libHYPRE.a
|
||||
INTERFACE_INCLUDE_DIRECTORIES ${PREFIX}/include)
|
||||
IMPORTED_LOCATION ${HYPRE_INSTALL}/lib/libHYPRE.a
|
||||
INTERFACE_INCLUDE_DIRECTORIES ${HYPRE_INSTALL}/include)
|
||||
# convert HYPRE version to integer
|
||||
string(REGEX MATCHALL "[0-9]+" HYPRE_SPLIT_VERSION ${HYPRE_FETCH_VERSION})
|
||||
list(GET HYPRE_SPLIT_VERSION 0 HYPRE_MAJOR_VERSION)
|
||||
list(GET HYPRE_SPLIT_VERSION 1 HYPRE_MINOR_VERSION)
|
||||
list(GET HYPRE_SPLIT_VERSION 2 HYPRE_PATCH_VERSION)
|
||||
math(EXPR HYPRE_VERSION "10000*${HYPRE_MAJOR_VERSION} + 100*${HYPRE_MINOR_VERSION} + ${HYPRE_PATCH_VERSION}")
|
||||
# set cache variables that would otherwise be set after mfem_find_package call
|
||||
set(HYPRE_VERSION ${HYPRE_VERSION} CACHE STRING "HYPRE version." FORCE)
|
||||
if (HYPRE_FETCH_TAG MATCHES "^v?([0-9]+)\\.([0-9]+)\\.([0-9]+)$")
|
||||
# Exact release tag X.Y.Z
|
||||
string(REGEX MATCHALL "[0-9]+" HYPRE_SPLIT_VERSION "${HYPRE_FETCH_TAG}")
|
||||
elseif (HYPRE_FETCH_VERSION MATCHES "([0-9]+)\\.([0-9]+)(\\.([0-9]+))?")
|
||||
string(REGEX MATCHALL "[0-9]+" HYPRE_SPLIT_VERSION "${HYPRE_FETCH_VERSION}")
|
||||
else (NOT DEFINED HYPRE_VERSION)
|
||||
message(FATAL_ERROR "Unable to find HYPRE release version. Please provide it via -DHYPRE_VERSION")
|
||||
endif()
|
||||
if (HYPRE_SPLIT_VERSION AND NOT DEFINED HYPRE_VERSION)
|
||||
list(GET HYPRE_SPLIT_VERSION 0 HYPRE_MAJOR_VERSION)
|
||||
list(GET HYPRE_SPLIT_VERSION 1 HYPRE_MINOR_VERSION)
|
||||
if (HYPRE_SPLIT_VERSION GREATER 2)
|
||||
list(GET HYPRE_SPLIT_VERSION 2 HYPRE_PATCH_VERSION)
|
||||
else()
|
||||
set(HYPRE_PATCH_VERSION 0)
|
||||
endif()
|
||||
math(EXPR HYPRE_VERSION "10000*${HYPRE_MAJOR_VERSION} + 100*${HYPRE_MINOR_VERSION} + ${HYPRE_PATCH_VERSION}")
|
||||
set(HYPRE_VERSION ${HYPRE_VERSION} CACHE STRING "HYPRE version." FORCE)
|
||||
endif()
|
||||
return()
|
||||
endif()
|
||||
|
||||
@@ -149,7 +190,8 @@ endif()
|
||||
if (HYPRE_FOUND AND HYPRE_USING_HIP)
|
||||
find_package(rocsparse REQUIRED)
|
||||
find_package(rocrand REQUIRED)
|
||||
list(APPEND HYPRE_LIBRARIES ${rocsparse_LIBRARIES} ${rocrand_LIBRARIES})
|
||||
find_package(rocsolver REQUIRED)
|
||||
list(APPEND HYPRE_LIBRARIES ${rocsparse_LIBRARIES} ${rocrand_LIBRARIES} roc::rocsolver roc::rocblas)
|
||||
set(HYPRE_LIBRARIES ${HYPRE_LIBRARIES} CACHE STRING
|
||||
"HYPRE libraries + dependencies." FORCE)
|
||||
message(STATUS "Updated HYPRE_LIBRARIES: ${HYPRE_LIBRARIES}")
|
||||
|
||||
@@ -718,7 +718,7 @@ function(mfem_get_target_options Target CompileOptsVar LinkOptsVar)
|
||||
get_target_property(IsImported ${tgt} IMPORTED)
|
||||
# message(STATUS "${tgt}[IMPORTED]: ${IsImported}")
|
||||
# Generally, the possible target types are: STATIC_LIBRARY, MODULE_LIBRARY,
|
||||
# SHARED_LIBRARY, INTERFACE_LIBRARY, EXECUTABLE.
|
||||
# SHARED_LIBRARY, INTERFACE_LIBRARY, UNKNOWN_LIBRARY, EXECUTABLE.
|
||||
get_target_property(type ${tgt} TYPE)
|
||||
# message(STATUS "${tgt}[TYPE]: ${type}")
|
||||
unset(ImportConfig)
|
||||
@@ -766,7 +766,7 @@ function(mfem_get_target_options Target CompileOptsVar LinkOptsVar)
|
||||
else()
|
||||
message(STATUS " *** Warning: [${tgt}] LOCATION not defined!")
|
||||
endif()
|
||||
elseif ("${type}" STREQUAL "SHARED_LIBRARY")
|
||||
elseif ("${type}" STREQUAL "SHARED_LIBRARY" OR "${type}" STREQUAL "UNKNOWN_LIBRARY")
|
||||
get_target_property(Location ${tgt} LOCATION)
|
||||
if (Location)
|
||||
get_filename_component(Dir ${Location} DIRECTORY)
|
||||
@@ -932,12 +932,14 @@ function(mfem_export_mk_files)
|
||||
endif()
|
||||
set(MFEM_BUILD_TAG "${CMAKE_SYSTEM}")
|
||||
set(MFEM_PREFIX "${CMAKE_INSTALL_PREFIX}")
|
||||
# For the next 4 variable, these are the values for the build-tree version of
|
||||
# For the next 4 variables, these are the values for the build-tree version of
|
||||
# 'config.mk'
|
||||
set(MFEM_INC_DIR "${PROJECT_BINARY_DIR}")
|
||||
set(MFEM_LIB_DIR "${PROJECT_BINARY_DIR}")
|
||||
set(MFEM_TEST_MK "${PROJECT_SOURCE_DIR}/config/test.mk")
|
||||
set(MFEM_CONFIG_EXTRA "MFEM_BUILD_DIR ?= ${PROJECT_BINARY_DIR}")
|
||||
# TODO: CUDA/HIP support:
|
||||
set(MFEM_XLINKER "${CMAKE_CXX_LINKER_WRAPPER_FLAG}")
|
||||
set(MFEM_MPIEXEC ${MPIEXEC})
|
||||
if (NOT MFEM_MPIEXEC)
|
||||
set(MFEM_MPIEXEC "mpirun")
|
||||
|
||||
@@ -88,6 +88,7 @@ MFEM_BUILD_TAG = @MFEM_BUILD_TAG@
|
||||
MFEM_PREFIX = @MFEM_PREFIX@
|
||||
MFEM_INC_DIR = @MFEM_INC_DIR@
|
||||
MFEM_LIB_DIR = @MFEM_LIB_DIR@
|
||||
MFEM_XLINKER = @MFEM_XLINKER@
|
||||
|
||||
# Location of test.mk
|
||||
MFEM_TEST_MK = @MFEM_TEST_MK@
|
||||
|
||||
+1
-1
@@ -57,7 +57,7 @@ CUDA_DIR = $(or $(CUDA_HOME),$(patsubst %/,%,$(dir \
|
||||
CLANG_CUDA_FLAGS = -xcuda --cuda-path=$(CUDA_DIR) --cuda-gpu-arch=$(CUDA_ARCH)
|
||||
# flags for nvcc
|
||||
NVCC_FLAGS = -x=cu --expt-extended-lambda --expt-relaxed-constexpr \
|
||||
-arch=$(CUDA_ARCH)
|
||||
-arch=$(CUDA_ARCH) -isystem "$(CUDA_DIR)/include"
|
||||
# Prefixes for passing flags to the host compiler and linker when using
|
||||
# CUDA_CXX=nvcc
|
||||
CUDA_XCOMPILER = -Xcompiler=
|
||||
|
||||
+593
@@ -0,0 +1,593 @@
|
||||
MFEM mesh v1.0
|
||||
|
||||
# Created by: Pointwise
|
||||
|
||||
# MFEM Geometry Types:
|
||||
#
|
||||
# POINT = 0
|
||||
# SEGMENT = 1
|
||||
# TRIANGLE = 2
|
||||
# SQUARE = 3
|
||||
# TETRAHEDRON = 4
|
||||
# CUBE = 5
|
||||
# PRISM = 6
|
||||
|
||||
dimension
|
||||
2
|
||||
|
||||
elements
|
||||
160
|
||||
1 3 1 164 163 0
|
||||
1 3 164 165 162 163
|
||||
1 3 2 166 164 1
|
||||
1 3 166 132 165 164
|
||||
1 3 3 167 166 2
|
||||
1 3 167 131 132 166
|
||||
1 3 4 168 167 3
|
||||
1 3 168 130 131 167
|
||||
1 3 5 169 168 4
|
||||
1 3 169 129 130 168
|
||||
1 3 6 170 169 5
|
||||
1 3 170 128 129 169
|
||||
1 3 171 172 170 6
|
||||
1 3 172 127 128 170
|
||||
1 3 124 125 172 171
|
||||
1 3 125 126 127 172
|
||||
1 3 162 165 173 161
|
||||
1 3 165 132 133 173
|
||||
1 3 161 173 174 160
|
||||
1 3 173 133 134 174
|
||||
1 3 160 174 175 159
|
||||
1 3 174 134 135 175
|
||||
1 3 6 7 176 171
|
||||
1 3 7 8 177 176
|
||||
1 3 171 176 123 124
|
||||
1 3 176 177 122 123
|
||||
1 3 159 175 178 158
|
||||
1 3 175 135 136 178
|
||||
1 3 158 178 179 157
|
||||
1 3 178 136 137 179
|
||||
1 3 157 179 180 156
|
||||
1 3 179 137 138 180
|
||||
1 3 122 177 181 121
|
||||
1 3 177 8 182 181
|
||||
1 3 8 9 183 182
|
||||
1 3 9 10 184 183
|
||||
1 3 10 11 185 184
|
||||
1 3 11 12 186 185
|
||||
1 3 12 13 187 186
|
||||
1 3 13 14 15 187
|
||||
1 3 121 181 119 120
|
||||
1 3 181 182 118 119
|
||||
1 3 182 183 117 118
|
||||
1 3 183 184 188 117
|
||||
1 3 184 185 109 188
|
||||
1 3 185 186 108 109
|
||||
1 3 186 187 189 108
|
||||
1 3 187 15 16 189
|
||||
1 3 109 110 190 188
|
||||
1 3 110 111 191 190
|
||||
1 3 111 112 113 191
|
||||
1 3 188 190 116 117
|
||||
1 3 190 191 115 116
|
||||
1 3 191 113 114 115
|
||||
1 3 189 192 107 108
|
||||
1 3 192 193 106 107
|
||||
1 3 193 194 105 106
|
||||
1 3 194 195 104 105
|
||||
1 3 195 196 103 104
|
||||
1 3 16 17 192 189
|
||||
1 3 17 18 193 192
|
||||
1 3 18 19 194 193
|
||||
1 3 19 20 195 194
|
||||
1 3 20 21 196 195
|
||||
1 3 97 98 197 96
|
||||
1 3 98 99 198 197
|
||||
1 3 99 100 199 198
|
||||
1 3 100 101 200 199
|
||||
1 3 101 102 201 200
|
||||
1 3 102 103 202 201
|
||||
1 3 103 196 203 202
|
||||
1 3 196 21 22 203
|
||||
1 3 96 197 204 95
|
||||
1 3 197 198 39 204
|
||||
1 3 198 199 38 39
|
||||
1 3 199 200 205 38
|
||||
1 3 200 201 32 205
|
||||
1 3 201 202 31 32
|
||||
1 3 202 203 206 31
|
||||
1 3 203 22 23 206
|
||||
1 3 32 33 207 205
|
||||
1 3 33 34 35 207
|
||||
1 3 205 207 37 38
|
||||
1 3 207 35 36 37
|
||||
1 3 39 40 208 204
|
||||
1 3 40 41 209 208
|
||||
1 3 41 42 210 209
|
||||
1 3 42 43 211 210
|
||||
1 3 43 44 212 211
|
||||
1 3 204 208 94 95
|
||||
1 3 208 209 93 94
|
||||
1 3 209 210 92 93
|
||||
1 3 210 211 91 92
|
||||
1 3 211 212 90 91
|
||||
1 3 90 212 213 89
|
||||
1 3 212 44 214 213
|
||||
1 3 44 45 215 214
|
||||
1 3 45 46 216 215
|
||||
1 3 46 47 217 216
|
||||
1 3 47 48 218 217
|
||||
1 3 48 49 219 218
|
||||
1 3 49 50 51 219
|
||||
1 3 89 213 87 88
|
||||
1 3 213 214 86 87
|
||||
1 3 214 215 85 86
|
||||
1 3 215 216 84 85
|
||||
1 3 216 217 83 84
|
||||
1 3 217 218 82 83
|
||||
1 3 218 219 220 82
|
||||
1 3 219 51 52 220
|
||||
1 3 53 221 220 52
|
||||
1 3 221 81 82 220
|
||||
1 3 54 222 221 53
|
||||
1 3 222 80 81 221
|
||||
1 3 55 223 222 54
|
||||
1 3 223 79 80 222
|
||||
1 3 26 27 224 25
|
||||
1 3 27 28 29 224
|
||||
1 3 25 224 225 24
|
||||
1 3 224 29 30 225
|
||||
1 3 24 225 206 23
|
||||
1 3 225 30 31 206
|
||||
1 3 154 155 226 153
|
||||
1 3 155 156 180 226
|
||||
1 3 153 226 227 152
|
||||
1 3 226 180 138 227
|
||||
1 3 152 227 228 151
|
||||
1 3 227 138 139 228
|
||||
1 3 151 228 229 150
|
||||
1 3 228 139 140 229
|
||||
1 3 150 229 230 149
|
||||
1 3 229 140 141 230
|
||||
1 3 149 230 231 148
|
||||
1 3 230 141 142 231
|
||||
1 3 148 231 232 147
|
||||
1 3 231 142 143 232
|
||||
1 3 147 232 145 146
|
||||
1 3 232 143 144 145
|
||||
1 3 56 233 223 55
|
||||
1 3 233 78 79 223
|
||||
1 3 57 234 233 56
|
||||
1 3 234 77 78 233
|
||||
1 3 58 235 234 57
|
||||
1 3 235 76 77 234
|
||||
1 3 61 236 59 60
|
||||
1 3 236 235 58 59
|
||||
1 3 62 237 236 61
|
||||
1 3 237 76 235 236
|
||||
1 3 63 238 237 62
|
||||
1 3 238 75 76 237
|
||||
1 3 64 239 238 63
|
||||
1 3 239 74 75 238
|
||||
1 3 65 240 239 64
|
||||
1 3 240 73 74 239
|
||||
1 3 66 241 240 65
|
||||
1 3 241 72 73 240
|
||||
1 3 67 242 241 66
|
||||
1 3 242 71 72 241
|
||||
1 3 68 69 242 67
|
||||
1 3 69 70 71 242
|
||||
|
||||
boundary
|
||||
164
|
||||
3 1 0 1
|
||||
3 1 1 2
|
||||
3 1 2 3
|
||||
3 1 3 4
|
||||
3 1 4 5
|
||||
3 1 5 6
|
||||
3 1 6 7
|
||||
3 1 7 8
|
||||
3 1 8 9
|
||||
3 1 9 10
|
||||
3 1 10 11
|
||||
3 1 11 12
|
||||
3 1 12 13
|
||||
3 1 13 14
|
||||
3 1 16 17
|
||||
3 1 17 18
|
||||
3 1 18 19
|
||||
3 1 19 20
|
||||
3 1 20 21
|
||||
3 1 21 22
|
||||
3 1 22 23
|
||||
3 1 23 24
|
||||
3 1 24 25
|
||||
3 1 25 26
|
||||
3 1 26 27
|
||||
3 1 27 28
|
||||
3 1 28 29
|
||||
3 1 29 30
|
||||
3 1 30 31
|
||||
3 1 31 32
|
||||
3 1 32 33
|
||||
3 1 33 34
|
||||
3 1 34 35
|
||||
3 1 35 36
|
||||
3 1 36 37
|
||||
3 1 37 38
|
||||
3 1 38 39
|
||||
3 1 39 40
|
||||
3 1 40 41
|
||||
3 1 41 42
|
||||
3 1 42 43
|
||||
3 1 43 44
|
||||
3 1 49 50
|
||||
3 1 48 49
|
||||
3 1 47 48
|
||||
3 1 46 47
|
||||
3 1 45 46
|
||||
3 1 44 45
|
||||
3 1 52 53
|
||||
3 1 53 54
|
||||
3 1 54 55
|
||||
3 1 57 58
|
||||
3 1 56 57
|
||||
3 1 55 56
|
||||
3 1 60 61
|
||||
3 1 61 62
|
||||
3 1 62 63
|
||||
3 1 63 64
|
||||
3 1 64 65
|
||||
3 1 65 66
|
||||
3 1 66 67
|
||||
3 1 67 68
|
||||
3 1 75 76
|
||||
3 1 74 75
|
||||
3 1 73 74
|
||||
3 1 72 73
|
||||
3 1 71 72
|
||||
3 1 70 71
|
||||
3 1 76 77
|
||||
3 1 77 78
|
||||
3 1 78 79
|
||||
3 1 81 82
|
||||
3 1 80 81
|
||||
3 1 79 80
|
||||
3 1 82 83
|
||||
3 1 83 84
|
||||
3 1 84 85
|
||||
3 1 85 86
|
||||
3 1 86 87
|
||||
3 1 87 88
|
||||
3 1 94 95
|
||||
3 1 93 94
|
||||
3 1 92 93
|
||||
3 1 91 92
|
||||
3 1 90 91
|
||||
3 1 96 97
|
||||
3 1 95 96
|
||||
3 1 97 98
|
||||
3 1 98 99
|
||||
3 1 99 100
|
||||
3 1 100 101
|
||||
3 1 101 102
|
||||
3 1 102 103
|
||||
3 1 107 108
|
||||
3 1 106 107
|
||||
3 1 105 106
|
||||
3 1 104 105
|
||||
3 1 103 104
|
||||
3 1 108 109
|
||||
3 1 109 110
|
||||
3 1 110 111
|
||||
3 1 111 112
|
||||
3 1 112 113
|
||||
3 1 113 114
|
||||
3 1 114 115
|
||||
3 1 115 116
|
||||
3 1 116 117
|
||||
3 1 119 120
|
||||
3 1 118 119
|
||||
3 1 117 118
|
||||
3 1 131 132
|
||||
3 1 130 131
|
||||
3 1 129 130
|
||||
3 1 128 129
|
||||
3 1 127 128
|
||||
3 1 126 127
|
||||
3 1 132 133
|
||||
3 1 133 134
|
||||
3 1 134 135
|
||||
3 1 137 138
|
||||
3 1 136 137
|
||||
3 1 135 136
|
||||
3 1 138 139
|
||||
3 1 139 140
|
||||
3 1 140 141
|
||||
3 1 141 142
|
||||
3 1 142 143
|
||||
3 1 143 144
|
||||
3 1 147 148
|
||||
3 1 146 147
|
||||
3 1 153 154
|
||||
3 1 152 153
|
||||
3 1 151 152
|
||||
3 1 150 151
|
||||
3 1 149 150
|
||||
3 1 148 149
|
||||
3 1 156 157
|
||||
3 1 157 158
|
||||
3 1 158 159
|
||||
3 1 161 162
|
||||
3 1 160 161
|
||||
3 1 159 160
|
||||
2 1 69 70
|
||||
2 1 68 69
|
||||
3 1 88 89
|
||||
3 1 89 90
|
||||
3 1 121 122
|
||||
3 1 120 121
|
||||
3 1 123 124
|
||||
3 1 122 123
|
||||
3 1 125 126
|
||||
3 1 124 125
|
||||
1 1 144 145
|
||||
1 1 145 146
|
||||
3 1 15 16
|
||||
3 1 14 15
|
||||
3 1 50 51
|
||||
3 1 51 52
|
||||
3 1 59 60
|
||||
3 1 58 59
|
||||
3 1 154 155
|
||||
3 1 155 156
|
||||
3 1 163 0
|
||||
3 1 162 163
|
||||
|
||||
vertices
|
||||
243
|
||||
2
|
||||
4 4
|
||||
4 3.5
|
||||
4 3
|
||||
4 2.5
|
||||
4 2
|
||||
4 1.5
|
||||
4 1
|
||||
4.5 1
|
||||
5 1
|
||||
5 1.5
|
||||
5 2
|
||||
5 2.5
|
||||
5 3
|
||||
5 3.5
|
||||
5 4
|
||||
5.500 4
|
||||
6 4
|
||||
6.500 4
|
||||
7 4
|
||||
7.5 4
|
||||
8 4
|
||||
8.5 4
|
||||
9 4
|
||||
9.5 4
|
||||
10 4
|
||||
10.5 4
|
||||
11 4
|
||||
11 3.5
|
||||
11 3
|
||||
10.5 3
|
||||
10 3
|
||||
9.5 3
|
||||
9.5 2.5
|
||||
10 2.5
|
||||
10.5 2.5
|
||||
10.5 2
|
||||
10.5 1.5
|
||||
10 1.5
|
||||
9.5 1.5
|
||||
9.5 1
|
||||
10 1
|
||||
10.5 1
|
||||
11 1
|
||||
11.5 1
|
||||
12 1
|
||||
12 1.5
|
||||
12 2
|
||||
12 2.5
|
||||
12 3
|
||||
12 3.5
|
||||
12 4
|
||||
12.5 4
|
||||
13 4
|
||||
13.333 3.75
|
||||
13.666 3.5
|
||||
14.000 3.25
|
||||
14.333 3.5
|
||||
14.666 3.75
|
||||
15.000 4
|
||||
15.500 4
|
||||
16.000 4
|
||||
16.000 3.5
|
||||
16.000 3
|
||||
16.000 2.5
|
||||
16.000 2
|
||||
16.000 1.5
|
||||
16.000 1
|
||||
16.000 0.5
|
||||
16.000 0
|
||||
15.500 0
|
||||
15.000 0
|
||||
15.000 0.5000000000000002
|
||||
15.000 1
|
||||
15.000 1.5
|
||||
15.000 2
|
||||
15.000 2.5
|
||||
15.000 3
|
||||
14.666 2.75
|
||||
14.333 2.5
|
||||
14.000 2.25
|
||||
13.666 2.5
|
||||
13.333 2.75
|
||||
13 3
|
||||
13 2.5
|
||||
13 2
|
||||
13 1.5
|
||||
13 1
|
||||
13 0.500
|
||||
13 0
|
||||
12.5 0
|
||||
12 0
|
||||
11.5 0
|
||||
11 0
|
||||
10.5 0
|
||||
10 0
|
||||
9.5 0
|
||||
9 0
|
||||
8.5 0
|
||||
8.5 0.5
|
||||
8.5 1
|
||||
8.5 1.5
|
||||
8.5 2
|
||||
8.5 2.5
|
||||
8.5 3
|
||||
8 3
|
||||
7.5 3
|
||||
7 3
|
||||
6.500 3
|
||||
6 3
|
||||
6 2.5
|
||||
6.5 2.5
|
||||
7 2.5
|
||||
7.5 2.5
|
||||
7.5 2
|
||||
7.5 1.5
|
||||
7.000 1.5
|
||||
6.5 1.5
|
||||
6 1.5
|
||||
6 1
|
||||
6 0.5
|
||||
6 0
|
||||
5.5 0
|
||||
5 0
|
||||
4.5 0
|
||||
4 0
|
||||
3.5 0
|
||||
3 0
|
||||
3 0.500
|
||||
3 1
|
||||
3 1.5
|
||||
3 2
|
||||
3 2.5
|
||||
3 3
|
||||
2.666 2.75
|
||||
2.333 2.5
|
||||
2.000 2.25
|
||||
1.666 2.5
|
||||
1.333 2.75
|
||||
1.000 3
|
||||
1.000 2.5
|
||||
1.000 2
|
||||
1.000 1.5
|
||||
1.000 1
|
||||
1.000 0.5000
|
||||
1.000 0
|
||||
0.5000 0
|
||||
0.0000 0
|
||||
0.0000 0.5
|
||||
0.0000 1
|
||||
0.0000 1.5
|
||||
0.0000 2
|
||||
0.0000 2.5
|
||||
0.0000 3
|
||||
0.0000 3.5
|
||||
0.0000 4
|
||||
0.5000 4
|
||||
1.000 4
|
||||
1.333 3.75
|
||||
1.666 3.5
|
||||
2.000 3.25
|
||||
2.333 3.5
|
||||
2.666 3.75
|
||||
3 4
|
||||
3.5 4
|
||||
3.5 3.5
|
||||
3 3.5
|
||||
3.5 3
|
||||
3.5 2.5
|
||||
3.5 2
|
||||
3.5 1.5
|
||||
3.5 1
|
||||
4 0.5
|
||||
3.5 0.5
|
||||
2.666 3.25
|
||||
2.333 3
|
||||
2.000 2.75
|
||||
4.5 0.5
|
||||
5 0.5
|
||||
1.666 3
|
||||
1.333 3.25
|
||||
1.000 3.5
|
||||
5.5 0.5
|
||||
5.500 1
|
||||
5.500 1.5
|
||||
5.500 2
|
||||
5.500 2.5
|
||||
5.500 3
|
||||
5.500 3.5
|
||||
6 2
|
||||
6 3.5
|
||||
6.5 2
|
||||
7 2
|
||||
6.5 3.5
|
||||
7 3.5
|
||||
7.5 3.5
|
||||
8 3.5
|
||||
8.5 3.5
|
||||
9 0.5
|
||||
9 1
|
||||
9 1.5
|
||||
9 2
|
||||
9 2.5
|
||||
9 3
|
||||
9 3.5
|
||||
9.5 0.5
|
||||
9.5 2
|
||||
9.5 3.5
|
||||
10 2
|
||||
10 0.5
|
||||
10.5 0.5
|
||||
11 0.5
|
||||
11.5 0.5
|
||||
12 0.5
|
||||
12.5 0.500
|
||||
12.5 1
|
||||
12.5 1.5
|
||||
12.5 2
|
||||
12.5 2.5
|
||||
12.5 3
|
||||
12.5 3.5
|
||||
13 3.5
|
||||
13.333 3.250
|
||||
13.666 3
|
||||
14.000 2.75
|
||||
10.5 3.5
|
||||
10 3.5
|
||||
0.500 3.5
|
||||
0.500 3
|
||||
0.500 2.5
|
||||
0.500 2
|
||||
0.500 1.5
|
||||
0.500 1
|
||||
0.500 0.5
|
||||
14.333 3
|
||||
14.666 3.25
|
||||
15.000 3.5
|
||||
15.500 3.5
|
||||
15.500 3
|
||||
15.500 2.5
|
||||
15.500 2
|
||||
15.500 1.5
|
||||
15.500 1
|
||||
15.500 0.5
|
||||
@@ -202,6 +202,7 @@ namespace mfem {
|
||||
* - <a class="el" href="tesla_8cpp_source.html">Tesla</a>: simple magnetostatics simulation code
|
||||
* - <a class="el" href="maxwell_8cpp_source.html">Maxwell</a>: simple transient full-wave electromagnetics simulation code
|
||||
* - <a class="el" href="joule_8cpp_source.html">Joule</a>: transient magnetics and Joule heating miniapp
|
||||
* - <a class="el" href="lorentz_8cpp_source.html">Lorentz</a>: simple particle tracking code based on the Lorentz force
|
||||
* - <a class="el" href="classmfem_1_1navier_1_1NavierSolver.html">Navier</a>: solve the transient incompressible Navier-Stokes equations
|
||||
* - <a class="el" href="mobius-strip_8cpp_source.html">Mobius Strip</a>: generate various Mobius strip-like meshes
|
||||
* - <a class="el" href="klein-bottle_8cpp_source.html">Klein Bottle</a>: generate three types of Klein bottle surfaces
|
||||
|
||||
@@ -246,8 +246,13 @@ set(HDRS
|
||||
lor/lor_ams.hpp
|
||||
lor/lor_batched.hpp
|
||||
lor/lor_h1.hpp
|
||||
lor/lor_dg.hpp
|
||||
lor/lor_nd.hpp
|
||||
lor/lor_rt.hpp
|
||||
lor/lor_h1_impl.hpp
|
||||
lor/lor_dg_impl.hpp
|
||||
lor/lor_nd_impl.hpp
|
||||
lor/lor_rt_impl.hpp
|
||||
lor/lor_util.hpp
|
||||
multigrid.hpp
|
||||
nonlinearform.hpp
|
||||
|
||||
@@ -3539,6 +3539,8 @@ public:
|
||||
|
||||
const IntegrationRule &GetRule(int order, Geometry::Type geom);
|
||||
|
||||
real_t GetPenaltyParameter() const { return kappa; }
|
||||
|
||||
/// arguments: nf, B, Bt, G, Gt, sigma, pa_data, x, dxdn, y, dydn, dofs1D,
|
||||
/// quad1D
|
||||
using ApplyKernelType = void (*)(const int, const Array<real_t> &,
|
||||
|
||||
@@ -259,6 +259,30 @@ inline void FaceIdxToVolIdx3D(const int index, const int size1d,
|
||||
i = yz_plane ? level : _i;
|
||||
}
|
||||
|
||||
MFEM_HOST_DEVICE
|
||||
inline int FaceIdxToVolIdx(int dim, int i, int size1d, int face0, int face1,
|
||||
int side, int orientation)
|
||||
{
|
||||
if (dim == 2)
|
||||
{
|
||||
int ix, iy;
|
||||
internal::FaceIdxToVolIdx2D(i, size1d, face0, face1, side, ix, iy);
|
||||
return ix + iy*size1d;
|
||||
}
|
||||
else if (dim == 3)
|
||||
{
|
||||
int ix, iy, iz;
|
||||
internal::FaceIdxToVolIdx3D(i, size1d, face0, face1, side, orientation,
|
||||
ix, iy, iz);
|
||||
return ix + size1d*iy + size1d*size1d*iz;
|
||||
}
|
||||
else
|
||||
{
|
||||
MFEM_ABORT_KERNEL("Invalid dimension");
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
||||
} // namespace mfem
|
||||
|
||||
+2
-4
@@ -2456,8 +2456,7 @@ RT_FECollection::RT_FECollection(const int order, const int dim,
|
||||
const char *cb_name = BasisType::Name(cb_type); // this may abort
|
||||
MFEM_ABORT("unknown closed BasisType: " << cb_name);
|
||||
}
|
||||
if (Quadrature1D::CheckOpen(op_type) == Quadrature1D::Invalid &&
|
||||
ob_type != BasisType::IntegratedGLL)
|
||||
if (Quadrature1D::CheckOpen(op_type) == Quadrature1D::Invalid)
|
||||
{
|
||||
const char *ob_name = BasisType::Name(ob_type); // this may abort
|
||||
MFEM_ABORT("unknown open BasisType: " << ob_name);
|
||||
@@ -2784,8 +2783,7 @@ ND_FECollection::ND_FECollection(const int p, const int dim,
|
||||
int cp_type = BasisType::GetQuadrature1D(cb_type);
|
||||
|
||||
// Error checking
|
||||
if (Quadrature1D::CheckOpen(op_type) == Quadrature1D::Invalid &&
|
||||
ob_type != BasisType::IntegratedGLL)
|
||||
if (Quadrature1D::CheckOpen(op_type) == Quadrature1D::Invalid)
|
||||
{
|
||||
const char *ob_name = BasisType::Name(ob_type);
|
||||
MFEM_ABORT("Invalid open basis point type: " << ob_name);
|
||||
|
||||
@@ -947,6 +947,7 @@ int Quadrature1D::CheckOpen(int type)
|
||||
case OpenUniform:
|
||||
case ClosedUniform:
|
||||
case OpenHalfUniform:
|
||||
case ClosedGL:
|
||||
return type; // all types can work as open
|
||||
default:
|
||||
return Invalid;
|
||||
|
||||
+1
-1
@@ -565,7 +565,7 @@ public:
|
||||
Specifically, given the Dirichlet data $u_D$, the linear form assembles the
|
||||
following integrals on the boundary:
|
||||
$$
|
||||
\sigma \langle u_D, (Q \nabla v)) \cdot n \rangle + \kappa \langle {h^{-1} Q} u_D, v \rangle,
|
||||
\sigma \langle u_D, (Q \nabla v) \cdot n \rangle + \kappa \langle {h^{-1} Q} u_D, v \rangle,
|
||||
$$
|
||||
where Q is a scalar or matrix diffusion coefficient and v is the test
|
||||
function. The parameters $\sigma$ and $\kappa$ should be the same as the ones
|
||||
|
||||
+264
-22
@@ -14,9 +14,11 @@
|
||||
#include "../../general/forall.hpp"
|
||||
#include <climits>
|
||||
#include "../pbilinearform.hpp"
|
||||
#include "../../fem/fe/face_map_utils.hpp"
|
||||
|
||||
// Specializations
|
||||
#include "lor_h1.hpp"
|
||||
#include "lor_dg.hpp"
|
||||
#include "lor_nd.hpp"
|
||||
#include "lor_rt.hpp"
|
||||
|
||||
@@ -54,17 +56,18 @@ bool BatchedLORAssembly::FormIsSupported(BilinearForm &a)
|
||||
// Batched LOR requires all tensor elements
|
||||
if (!UsesTensorBasis(*a.FESpace())) { return false; }
|
||||
|
||||
if (dynamic_cast<const H1_FECollection*>(fec))
|
||||
if (dynamic_cast<const H1_FECollection*>(fec) ||
|
||||
dynamic_cast<const DG_FECollection*>(fec))
|
||||
{
|
||||
if (HasIntegrators<DiffusionIntegrator, MassIntegrator>(a)) { return true; }
|
||||
return HasIntegrators<DiffusionIntegrator, MassIntegrator>(a);
|
||||
}
|
||||
else if (dynamic_cast<const ND_FECollection*>(fec))
|
||||
{
|
||||
if (HasIntegrators<CurlCurlIntegrator, VectorFEMassIntegrator>(a)) { return true; }
|
||||
return HasIntegrators<CurlCurlIntegrator, VectorFEMassIntegrator>(a);
|
||||
}
|
||||
else if (dynamic_cast<const RT_FECollection*>(fec))
|
||||
{
|
||||
if (HasIntegrators<DivDivIntegrator, VectorFEMassIntegrator>(a)) { return true; }
|
||||
return HasIntegrators<DivDivIntegrator, VectorFEMassIntegrator>(a);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -75,12 +78,14 @@ void BatchedLORAssembly::FormLORVertexCoordinates(FiniteElementSpace &fes_ho,
|
||||
Mesh &mesh_ho = *fes_ho.GetMesh();
|
||||
mesh_ho.EnsureNodes();
|
||||
|
||||
const bool dg = fes_ho.IsDGSpace();
|
||||
|
||||
// Get nodal points at the LOR vertices
|
||||
const int dim = mesh_ho.Dimension();
|
||||
const int sdim = mesh_ho.SpaceDimension();
|
||||
const int nel_ho = mesh_ho.GetNE();
|
||||
const int order = fes_ho.GetMaxElementOrder();
|
||||
const int nd1d = order + 1;
|
||||
const int nd1d = dg ? order + 2 : order + 1;
|
||||
const int ndof_per_el = static_cast<int>(pow(nd1d, dim));
|
||||
|
||||
const GridFunction *nodal_gf = mesh_ho.GetNodes();
|
||||
@@ -92,7 +97,8 @@ void BatchedLORAssembly::FormLORVertexCoordinates(FiniteElementSpace &fes_ho,
|
||||
Vector nodal_evec(nodal_restriction->Height());
|
||||
nodal_restriction->Mult(*nodal_gf, nodal_evec);
|
||||
|
||||
IntegrationRule ir = GetCollocatedIntRule(fes_ho);
|
||||
const IntegrationRule ir = GetLobattoIntRule(
|
||||
mesh_ho.GetTypicalElementGeometry(), nd1d);
|
||||
|
||||
// Map from nodal E-vector to Q-vector at the LOR vertex points
|
||||
X_vert.SetSize(sdim*ndof_per_el*nel_ho);
|
||||
@@ -159,6 +165,7 @@ int BatchedLORAssembly::FillI(SparseMatrix &A) const
|
||||
const auto K = dof_glob2loc_offsets_.Read();
|
||||
const auto map = Reshape(sparse_mapping.Read(), nnz_per_row, ndof_per_el);
|
||||
|
||||
|
||||
auto I = A.WriteI();
|
||||
|
||||
mfem::forall(nvdof + 1, [=] MFEM_HOST_DEVICE (int ii) { I[ii] = 0; });
|
||||
@@ -358,6 +365,177 @@ void BatchedLORAssembly::FillJAndData(SparseMatrix &A) const
|
||||
});
|
||||
}
|
||||
|
||||
void BatchedLORAssembly::SparseIJToCSR_DG(OperatorHandle &A) const
|
||||
{
|
||||
const int ndof_per_el = fes_ho.GetFE(0)->GetDof();
|
||||
const int nel_ho = fes_ho.GetNE();
|
||||
const int nnz_per_row = sparse_ij.Size()/ndof_per_el/nel_ho;
|
||||
const int dim = fes_ho.GetMesh()->Dimension();
|
||||
const int nrows = nel_ho*ndof_per_el;
|
||||
const int p = fes_ho.GetMaxElementOrder();
|
||||
const int pp1 = p + 1;
|
||||
const int nnz = nrows*nnz_per_row;
|
||||
|
||||
const int face_nbr_vsize = [&]()
|
||||
{
|
||||
#ifdef MFEM_USE_MPI
|
||||
if (auto *par_fes = dynamic_cast<ParFiniteElementSpace*>(&fes_ho))
|
||||
{
|
||||
return par_fes->GetFaceNbrVSize();
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}();
|
||||
|
||||
// If A contains an existing SparseMatrix, reuse it (and try to reuse its
|
||||
// I, J, A arrays if they are big enough)
|
||||
SparseMatrix *A_mat = A.Is<SparseMatrix>();
|
||||
if (!A_mat)
|
||||
{
|
||||
A_mat = new SparseMatrix;
|
||||
A.Reset(A_mat);
|
||||
}
|
||||
|
||||
// The second argument (nrows + face_nbr_vsize) accounts for additional
|
||||
// columns contributed by DG face neighbors in parallel finite element
|
||||
// spaces. In serial, face_nbr_vsize is set to 0.
|
||||
A_mat->OverrideSize(nrows, nrows + face_nbr_vsize);
|
||||
|
||||
EnsureCapacity(A_mat->GetMemoryI(), nrows + 1);
|
||||
EnsureCapacity(A_mat->GetMemoryJ(), nnz);
|
||||
EnsureCapacity(A_mat->GetMemoryData(), nnz);
|
||||
|
||||
Array<int> nbr_info(nel_ho*3*2*dim);
|
||||
auto h_nbr_info = Reshape(nbr_info.HostWrite(), nel_ho, 2*dim, 3);
|
||||
const int num_faces = fes_ho.GetMesh()->GetNumFaces();
|
||||
for (int f = 0; f < num_faces; f++)
|
||||
{
|
||||
Mesh::FaceInformation finfo = fes_ho.GetMesh()->GetFaceInformation(f);
|
||||
int e0 = finfo.element[0].index;
|
||||
int f0 = finfo.element[0].local_face_id;
|
||||
if (finfo.IsBoundary())
|
||||
{
|
||||
h_nbr_info(e0,f0,0) = -1;
|
||||
h_nbr_info(e0,f0,1)= -1;
|
||||
h_nbr_info(e0,f0,2)= -1;
|
||||
}
|
||||
else if (finfo.IsShared())
|
||||
{
|
||||
// Face neighbors elements are indexed after the last local element
|
||||
h_nbr_info(e0,f0,0) = nel_ho + finfo.element[1].index;
|
||||
h_nbr_info(e0,f0,1)= finfo.element[1].orientation;
|
||||
h_nbr_info(e0,f0,2)= finfo.element[1].local_face_id;
|
||||
}
|
||||
else if (finfo.IsInterior())
|
||||
{
|
||||
int e1 = finfo.element[1].index;
|
||||
int f1 = finfo.element[1].local_face_id;
|
||||
h_nbr_info(e0,f0,0) = e1;
|
||||
h_nbr_info(e0,f0,1)= finfo.element[1].orientation;
|
||||
h_nbr_info(e0,f0,2)= f1;
|
||||
h_nbr_info(e1,f1,0) = e0;
|
||||
h_nbr_info(e1,f1,1) = finfo.element[1].orientation;
|
||||
h_nbr_info(e1,f1,2) = f0;
|
||||
}
|
||||
};
|
||||
|
||||
auto h_I = A_mat->HostWriteI();
|
||||
h_I[0] = 0;
|
||||
for (int i = 0; i < nrows; ++i)
|
||||
{
|
||||
const int iel_ho = i / ndof_per_el;
|
||||
const int iloc = i % ndof_per_el;
|
||||
static const int lex_map_2[4] = {3, 1, 0, 2};
|
||||
static const int lex_map_3[6] = {4, 2, 1, 3, 0, 5};
|
||||
const int local_i[3] = {iloc % pp1, (iloc/pp1)%pp1, iloc/pp1/pp1};
|
||||
int bdr_count = 0;
|
||||
for (int n_idx = 0; n_idx < dim; ++n_idx)
|
||||
{
|
||||
for (int e_i = 0; e_i < 2; ++e_i)
|
||||
{
|
||||
const int j_lex = e_i + n_idx*2;
|
||||
const int f = (dim == 3) ? lex_map_3[j_lex]:lex_map_2[j_lex];
|
||||
const bool boundary = (local_i[n_idx] == e_i * p);
|
||||
if (boundary)
|
||||
{
|
||||
int neighbor_idx = h_nbr_info(iel_ho, f, 0);
|
||||
if (neighbor_idx == -1)
|
||||
{
|
||||
++bdr_count;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
h_I[i+1] = h_I[i] + (nnz_per_row - bdr_count);
|
||||
}
|
||||
|
||||
const auto V = Reshape(sparse_ij.Read(), nnz_per_row, ndof_per_el, nel_ho);
|
||||
auto J = A_mat->WriteJ();
|
||||
auto AV = A_mat->WriteData();
|
||||
auto I = A_mat->ReadI();
|
||||
|
||||
auto d_nbr_info = Reshape(nbr_info.Read(), nel_ho, 2*dim, 3);
|
||||
mfem::forall(nrows, [=] MFEM_HOST_DEVICE (int i)
|
||||
{
|
||||
const int e = i / ndof_per_el;
|
||||
const int iloc = i % ndof_per_el;
|
||||
const int local_x = iloc % pp1;
|
||||
const int local_y = (iloc/pp1)%pp1;
|
||||
const int local_z = iloc/pp1/pp1;
|
||||
const int local_i[3] = {local_x, local_y, local_z};
|
||||
int offset = I[i];
|
||||
static const int lex_map_2[4] = {3, 1, 0, 2};
|
||||
static const int lex_map_3[6] = {4,2,1,3,0,5};
|
||||
const int *lex_map = (dim == 2) ? lex_map_2 : lex_map_3;
|
||||
AV[offset] = V(0, iloc, e);
|
||||
J[offset] = i;
|
||||
++offset;
|
||||
for (int n_idx = 0; n_idx < dim; ++n_idx)
|
||||
{
|
||||
// qi is the face lexicographic index, obtained by taking the
|
||||
// lexicographic index of the coordinates ommiting n_idx.
|
||||
int qi = 0;
|
||||
int stride = 1;
|
||||
for (int d = 0; d < dim; ++d)
|
||||
{
|
||||
if (d != n_idx)
|
||||
{
|
||||
qi += local_i[d]*stride;
|
||||
stride *= pp1;
|
||||
}
|
||||
}
|
||||
for (int e_i = 0; e_i < 2; ++e_i)
|
||||
{
|
||||
const int j_lex = e_i + n_idx*2;
|
||||
const int f = lex_map[j_lex];
|
||||
const bool bdr = (local_i[n_idx] == e_i * p);
|
||||
if (bdr)
|
||||
{
|
||||
const int nbr_e = d_nbr_info(e, f, 0);
|
||||
const int nbr_ori = d_nbr_info(e, f, 1);
|
||||
const int nbr_f = d_nbr_info(e, f, 2);
|
||||
if (nbr_e != -1)
|
||||
{
|
||||
const int nbr_loc_idx = internal::FaceIdxToVolIdx(
|
||||
dim, qi, pp1, f, nbr_f, 1, nbr_ori);
|
||||
J[offset] = nbr_e*ndof_per_el + nbr_loc_idx;
|
||||
AV[offset] = V(f+1, iloc, e);
|
||||
++offset;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int shift = (e_i == 0) ? -1 : 1;
|
||||
for (int n = 0; n < n_idx; ++n) { shift *= pp1; }
|
||||
J[offset] = i + shift;
|
||||
AV[offset] = V(f+1, iloc, e);
|
||||
++offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void BatchedLORAssembly::SparseIJToCSR(OperatorHandle &A) const
|
||||
{
|
||||
const int nvdof = fes_ho.GetVSize();
|
||||
@@ -372,12 +550,11 @@ void BatchedLORAssembly::SparseIJToCSR(OperatorHandle &A) const
|
||||
}
|
||||
|
||||
A_mat->OverrideSize(nvdof, nvdof);
|
||||
EnsureCapacity(A_mat->GetMemoryI(), nvdof + 1);
|
||||
|
||||
A_mat->GetMemoryI().New(nvdof+1, Device::GetDeviceMemoryType());
|
||||
int nnz = FillI(*A_mat);
|
||||
|
||||
A_mat->GetMemoryJ().New(nnz, Device::GetDeviceMemoryType());
|
||||
A_mat->GetMemoryData().New(nnz, Device::GetDeviceMemoryType());
|
||||
const int nnz = FillI(*A_mat);
|
||||
EnsureCapacity(A_mat->GetMemoryJ(), nnz);
|
||||
EnsureCapacity(A_mat->GetMemoryData(), nnz);
|
||||
FillJAndData(*A_mat);
|
||||
}
|
||||
|
||||
@@ -431,6 +608,19 @@ void BatchedLORAssembly::AssembleWithoutBC(BilinearForm &a, OperatorHandle &A)
|
||||
// Assemble the matrix, depending on what the form is.
|
||||
// This fills in the arrays sparse_ij and sparse_mapping.
|
||||
const FiniteElementCollection *fec = fes_ho.FEColl();
|
||||
|
||||
// Handle DG case separately, because assembly of CSR matrix requires
|
||||
// handling face terms.
|
||||
if (dynamic_cast<const DG_FECollection*>(fec))
|
||||
{
|
||||
if (HasIntegrators<DiffusionIntegrator, MassIntegrator>(a))
|
||||
{
|
||||
AssemblyKernel<BatchedLOR_DG>(a);
|
||||
}
|
||||
SparseIJToCSR_DG(A);
|
||||
return;
|
||||
}
|
||||
|
||||
if (dynamic_cast<const H1_FECollection*>(fec))
|
||||
{
|
||||
if (HasIntegrators<DiffusionIntegrator, MassIntegrator>(a))
|
||||
@@ -453,10 +643,47 @@ void BatchedLORAssembly::AssembleWithoutBC(BilinearForm &a, OperatorHandle &A)
|
||||
}
|
||||
}
|
||||
|
||||
return SparseIJToCSR(A);
|
||||
SparseIJToCSR(A);
|
||||
}
|
||||
|
||||
#ifdef MFEM_USE_MPI
|
||||
void BatchedLORAssembly::ParAssemble_DG(SparseMatrix &A_local,
|
||||
OperatorHandle &A)
|
||||
{
|
||||
auto &par_fes = static_cast<ParFiniteElementSpace&>(fes_ho);
|
||||
|
||||
// handle the case when 'a' contains off-diagonal
|
||||
const int lvsize = par_fes.GetVSize();
|
||||
const Array<HYPRE_BigInt> &face_nbr_glob_ldof =
|
||||
par_fes.GetFaceNbrGlobalDofMapArray();
|
||||
const HYPRE_BigInt ldof_offset = par_fes.GetMyDofOffset();
|
||||
|
||||
const int nnz_local = A_local.NumNonZeroElems();
|
||||
Array<HYPRE_BigInt> glob_J(nnz_local);
|
||||
|
||||
const HYPRE_BigInt *d_face_nbr_glob_ldof = face_nbr_glob_ldof.Read();
|
||||
const int *d_J = A_local.ReadJ();
|
||||
HYPRE_BigInt *d_glob_J = glob_J.Write();
|
||||
|
||||
mfem::forall(nnz_local, [=] MFEM_HOST_DEVICE (int i)
|
||||
{
|
||||
if (d_J[i] < lvsize)
|
||||
{
|
||||
d_glob_J[i] = d_J[i] + ldof_offset;
|
||||
}
|
||||
else
|
||||
{
|
||||
d_glob_J[i] = d_face_nbr_glob_ldof[d_J[i] - lvsize];
|
||||
}
|
||||
});
|
||||
|
||||
A.Reset(new HypreParMatrix(
|
||||
par_fes.GetComm(), lvsize, par_fes.GlobalVSize(),
|
||||
par_fes.GlobalVSize(), A_local.HostReadWriteI(),
|
||||
glob_J.HostReadWrite(), A_local.HostReadWriteData(),
|
||||
par_fes.GetDofOffsets(), par_fes.GetDofOffsets()));
|
||||
}
|
||||
|
||||
void BatchedLORAssembly::ParAssemble(
|
||||
BilinearForm &a, const Array<int> &ess_dofs, OperatorHandle &A)
|
||||
{
|
||||
@@ -464,13 +691,18 @@ void BatchedLORAssembly::ParAssemble(
|
||||
OperatorHandle A_local;
|
||||
AssembleWithoutBC(a, A_local);
|
||||
|
||||
ParBilinearForm *pa =
|
||||
dynamic_cast<ParBilinearForm*>(&a);
|
||||
|
||||
pa->ParallelRAP(*A_local.As<SparseMatrix>(), A, true);
|
||||
|
||||
A.As<HypreParMatrix>()->EliminateBC(ess_dofs,
|
||||
Operator::DiagonalPolicy::DIAG_ONE);
|
||||
if (dynamic_cast<const DG_FECollection*>(fes_ho.FEColl()))
|
||||
{
|
||||
ParAssemble_DG(*A_local.As<SparseMatrix>(), A);
|
||||
}
|
||||
else
|
||||
{
|
||||
ParBilinearForm *pa =
|
||||
dynamic_cast<ParBilinearForm*>(&a);
|
||||
pa->ParallelRAP(*A_local.As<SparseMatrix>(), A, true);
|
||||
A.As<HypreParMatrix>()->EliminateBC(ess_dofs,
|
||||
Operator::DiagonalPolicy::DIAG_ONE);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -504,12 +736,22 @@ BatchedLORAssembly::BatchedLORAssembly(FiniteElementSpace &fes_ho_)
|
||||
FormLORVertexCoordinates(fes_ho, X_vert);
|
||||
}
|
||||
|
||||
IntegrationRule GetCollocatedIntRule(FiniteElementSpace &fes)
|
||||
IntegrationRule GetLobattoIntRule(Geometry::Type geom, int nd1d)
|
||||
{
|
||||
IntegrationRules irs(0, Quadrature1D::GaussLobatto);
|
||||
const Geometry::Type geom = fes.GetMesh()->GetTypicalElementGeometry();
|
||||
const int nd1d = fes.GetMaxElementOrder() + 1;
|
||||
return irs.Get(geom, 2*nd1d - 3);
|
||||
}
|
||||
|
||||
IntegrationRule GetCollocatedIntRule(FiniteElementSpace &fes)
|
||||
{
|
||||
const Geometry::Type geom = fes.GetMesh()->GetTypicalElementGeometry();
|
||||
return GetLobattoIntRule(geom, fes.GetMaxElementOrder() + 1);
|
||||
}
|
||||
|
||||
IntegrationRule GetCollocatedFaceIntRule(FiniteElementSpace &fes)
|
||||
{
|
||||
const Geometry::Type geom = fes.GetMesh()->GetTypicalFaceGeometry();
|
||||
return GetLobattoIntRule(geom, fes.GetMaxElementOrder() + 1);
|
||||
}
|
||||
|
||||
} // namespace mfem
|
||||
|
||||
+32
-2
@@ -25,6 +25,7 @@ namespace mfem
|
||||
/// supported, currently:
|
||||
///
|
||||
/// - H1 diffusion + mass
|
||||
/// - DG diffusion + mass (in progress)
|
||||
/// - ND curl-curl + mass
|
||||
/// - RT div-div + mass
|
||||
///
|
||||
@@ -73,6 +74,9 @@ public:
|
||||
/// Return the vertices of the LOR mesh in E-vector format
|
||||
const Vector &GetLORVertexCoordinates() { return X_vert; }
|
||||
|
||||
/// Specialized implementation of SparseIJToCSR for DG spaces.
|
||||
void SparseIJToCSR_DG(OperatorHandle &A) const;
|
||||
|
||||
protected:
|
||||
/// After assembling the "sparse IJ" format, convert it to CSR.
|
||||
void SparseIJToCSR(OperatorHandle &A) const;
|
||||
@@ -105,6 +109,9 @@ public:
|
||||
void FillJAndData(SparseMatrix &A) const;
|
||||
|
||||
#ifdef MFEM_USE_MPI
|
||||
/// Assemble the parallel DG matrix (with shared faces).
|
||||
void ParAssemble_DG(SparseMatrix &A_local, OperatorHandle &A);
|
||||
|
||||
/// Assemble the system in parallel and place the result in @a A.
|
||||
void ParAssemble(BilinearForm &a, const Array<int> &ess_dofs,
|
||||
OperatorHandle &A);
|
||||
@@ -128,9 +135,8 @@ void EnsureCapacity(Memory<T> &mem, int capacity)
|
||||
|
||||
/// Return the first domain integrator in the form @a i of type @a T.
|
||||
template <typename T>
|
||||
static T *GetIntegrator(BilinearForm &a)
|
||||
static T *GetIntegrator(Array<BilinearFormIntegrator*> *integs)
|
||||
{
|
||||
Array<BilinearFormIntegrator*> *integs = a.GetDBFI();
|
||||
if (integs != NULL)
|
||||
{
|
||||
for (auto *i : *integs)
|
||||
@@ -144,8 +150,32 @@ static T *GetIntegrator(BilinearForm &a)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static T *GetIntegrator(BilinearForm &a)
|
||||
{
|
||||
return GetIntegrator<T>(a.GetDBFI());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static T *GetInteriorFaceIntegrator(BilinearForm &a)
|
||||
{
|
||||
return GetIntegrator<T>(a.GetFBFI());
|
||||
}
|
||||
|
||||
/// @brief Return the Gauss-Lobatto rule for geometry @a geom with @a nd1d
|
||||
/// points per dimension.
|
||||
IntegrationRule GetLobattoIntRule(Geometry::Type geom, int nd1d);
|
||||
|
||||
/// @brief Return the Gauss-Lobatto rule collocated with the element nodes.
|
||||
///
|
||||
/// Assumes @a fes uses Gauss-Lobatto basis.
|
||||
IntegrationRule GetCollocatedIntRule(FiniteElementSpace &fes);
|
||||
|
||||
/// @brief Return the Gauss-Lobatto rule collocated with face nodes.
|
||||
///
|
||||
/// Assumes @a fes uses Gauss-Lobatto basis.
|
||||
IntegrationRule GetCollocatedFaceIntRule(FiniteElementSpace &fes);
|
||||
|
||||
template <typename INTEGRATOR>
|
||||
void ProjectLORCoefficient(BilinearForm &a, CoefficientVector &coeff_vector)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
// 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.
|
||||
|
||||
#ifndef MFEM_LOR_DG
|
||||
#define MFEM_LOR_DG
|
||||
|
||||
#include "lor_batched.hpp"
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
|
||||
// BatchedLORKernel specialization for DG spaces. Not user facing. See the
|
||||
// classes BatchedLORAssembly and BatchedLORKernel .
|
||||
class BatchedLOR_DG : BatchedLORKernel
|
||||
{
|
||||
IntegrationRule ir_face; ///< Collocated Gauss-Lobatto face quadrature rule.
|
||||
real_t kappa; ///< DG penalty parameter.
|
||||
public:
|
||||
template <int ORDER, int SDIM> void Assemble2D();
|
||||
template <int ORDER> void Assemble3D();
|
||||
BatchedLOR_DG(BilinearForm &a,
|
||||
FiniteElementSpace &fes_ho_,
|
||||
Vector &X_vert_,
|
||||
Vector &sparse_ij_,
|
||||
Array<int> &sparse_mapping_)
|
||||
: BatchedLORKernel(fes_ho_, X_vert_, sparse_ij_, sparse_mapping_),
|
||||
ir_face(GetLobattoIntRule(fes_ho_.GetMesh()->GetTypicalFaceGeometry(),
|
||||
fes_ho_.GetMaxElementOrder() + 1))
|
||||
{
|
||||
ProjectLORCoefficient<MassIntegrator>(a, c1);
|
||||
ProjectLORCoefficient<DiffusionIntegrator>(a, c2);
|
||||
|
||||
auto *integ = GetInteriorFaceIntegrator<DGDiffusionIntegrator>(a);
|
||||
if (integ)
|
||||
{
|
||||
kappa = integ->GetPenaltyParameter();
|
||||
}
|
||||
else
|
||||
{
|
||||
kappa = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Compute and return the face info array.
|
||||
///
|
||||
/// The face info array has shape (6, nf), where @a nf is the number of
|
||||
/// faces. For each face @a i, the column (:,i) has entries (e0, f0, o0, e1,
|
||||
/// f1, o1), where @a e is adjacent element, @a f is the local face index,
|
||||
/// and @a o is the orientation. For boundary and shared faces, (e1, f1, o1)
|
||||
/// are all set to -1.
|
||||
Array<int> GetFaceInfo() const;
|
||||
|
||||
/// @brief Compute and return the boundary penalty factor.
|
||||
///
|
||||
/// The returned vector has shape (nq, nf), where @a nq is the number of
|
||||
/// nodes per face, and @a nf is the number of faces.
|
||||
///
|
||||
/// The boundary penalty factor is $J_f / h = J_f^2 / J_e$ (since $h = J_e /
|
||||
/// J_f$), where $J_f$ is the face Jacobian determinant, and $J_e$ is the
|
||||
/// element Jacobian determinant.
|
||||
Vector GetBdrPenaltyFactor() const;
|
||||
|
||||
/// Assemble the face penalty terms in the matrix @a sparse_ij.
|
||||
void AssembleFaceTerms();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#include "lor_dg_impl.hpp"
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,392 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "lor_dg.hpp"
|
||||
#include "../fe/face_map_utils.hpp"
|
||||
#include "../../linalg/dtensor.hpp"
|
||||
#include "../../general/forall.hpp"
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
|
||||
Array<int> BatchedLOR_DG::GetFaceInfo() const
|
||||
{
|
||||
Mesh &mesh = *fes_ho.GetMesh();
|
||||
const int nf = mesh.GetNumFaces();
|
||||
Array<int> face_info(nf * 6); // (e0, f0, o0, e1, f1, o1)
|
||||
auto h_face_info = Reshape(face_info.HostWrite(), 6, nf);
|
||||
for (int f = 0; f < nf; ++f)
|
||||
{
|
||||
auto finfo = mesh.GetFaceInformation(f);
|
||||
h_face_info(0, f) = finfo.element[0].index;
|
||||
h_face_info(1, f) = finfo.element[0].local_face_id;
|
||||
h_face_info(2, f) = finfo.element[0].orientation;
|
||||
if (finfo.IsLocal()) // Interior, non-shared face
|
||||
{
|
||||
h_face_info(3, f) = finfo.element[1].index;
|
||||
h_face_info(4, f) = finfo.element[1].local_face_id;
|
||||
h_face_info(5, f) = finfo.element[1].orientation;
|
||||
}
|
||||
else
|
||||
{
|
||||
h_face_info(3, f) = -1;
|
||||
h_face_info(4, f) = -1;
|
||||
h_face_info(5, f) = -1;
|
||||
}
|
||||
}
|
||||
return face_info;
|
||||
}
|
||||
|
||||
Vector BatchedLOR_DG::GetBdrPenaltyFactor() const
|
||||
{
|
||||
Mesh &mesh = *fes_ho.GetMesh();
|
||||
|
||||
const int nf = mesh.GetNumFaces();
|
||||
Array<int> f_int(mesh.GetNFbyType(FaceType::Interior));
|
||||
Array<int> f_bdr(mesh.GetNFbyType(FaceType::Boundary));
|
||||
{
|
||||
int i_int = 0;
|
||||
int i_bdr = 0;
|
||||
for (int i = 0; i < nf; ++i)
|
||||
{
|
||||
const auto f = mesh.GetFaceInformation(i);
|
||||
if (f.IsBoundary())
|
||||
{
|
||||
f_bdr[i_bdr] = i;
|
||||
++i_bdr;
|
||||
}
|
||||
else if (f.IsInterior())
|
||||
{
|
||||
f_int[i_int] = i;
|
||||
++i_int;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto geom = fes_ho.GetMesh()->GetGeometricFactors(
|
||||
ir, GeometricFactors::DETERMINANTS);
|
||||
|
||||
const int nq = ir_face.Size();
|
||||
Vector face_Jh(nq * nf);
|
||||
for (const FaceType ft : {FaceType::Interior, FaceType::Boundary})
|
||||
{
|
||||
const int nft = mesh.GetNFbyType(ft);
|
||||
auto *geom_face = mesh.GetFaceGeometricFactors(
|
||||
ir_face, FaceGeometricFactors::DETERMINANTS, ft);
|
||||
|
||||
const L2FaceValues fv = (ft == FaceType::Interior)
|
||||
? L2FaceValues::DoubleValued
|
||||
: L2FaceValues::SingleValued;
|
||||
const int m = (fv == L2FaceValues::DoubleValued) ? 2 : 1;
|
||||
|
||||
auto *r = fes_ho.GetFaceRestriction(ElementDofOrdering::LEXICOGRAPHIC, ft, fv);
|
||||
Vector detJ_r(nq * m * nft);
|
||||
r->Mult(geom->detJ, detJ_r);
|
||||
|
||||
const auto *d_i = (ft == FaceType::Interior) ? f_int.Read() : f_bdr.Read();
|
||||
const auto d_detJ_face = Reshape(geom_face->detJ.Read(), nq, nft);
|
||||
const auto d_detJ_r = Reshape(detJ_r.Read(), nq, m, nft);
|
||||
auto d_face_Jh = Reshape(face_Jh.Write(), nq, nf);
|
||||
|
||||
mfem::forall(nft * nq, [=] MFEM_HOST_DEVICE (int ii)
|
||||
{
|
||||
const int i = ii % nq;
|
||||
const int f = ii / nq;
|
||||
const real_t J_el = 0.5*(d_detJ_r(i, 0, f) + d_detJ_r(i, m==2?1:0, f));
|
||||
const real_t J_f = d_detJ_face(i, f);
|
||||
d_face_Jh(i, d_i[f]) = J_f * J_f / J_el;
|
||||
});
|
||||
}
|
||||
return face_Jh;
|
||||
}
|
||||
|
||||
void BatchedLOR_DG::AssembleFaceTerms()
|
||||
{
|
||||
Mesh &mesh = *fes_ho.GetMesh();
|
||||
|
||||
const int nnz_per_row = 1 + mesh.Dimension()*2;
|
||||
const int pp1 = fes_ho.GetMaxElementOrder() + 1;
|
||||
const int nel_ho = mesh.GetNE();
|
||||
const int nf = mesh.GetNumFaces();
|
||||
const int nd_face = ir_face.Size();
|
||||
const int nd = ir.Size();
|
||||
const int dim = mesh.Dimension();
|
||||
|
||||
Array<int> face_info = GetFaceInfo();
|
||||
const auto d_face_info = Reshape(face_info.Read(), 6, nf);
|
||||
|
||||
Vector face_Jh = GetBdrPenaltyFactor();
|
||||
const auto d_face_Jh = Reshape(face_Jh.Read(), nd_face, nf);
|
||||
|
||||
const auto *w_face = ir_face.GetWeights().Read();
|
||||
|
||||
// Penalty parameter (avoid capturing *this in lambda)
|
||||
const real_t d_kappa = kappa;
|
||||
|
||||
// Get diffusion coefficient
|
||||
const bool const_dq = c2.Size() == 1;
|
||||
const auto DQ = const_dq?Reshape(c2.Read(),1,1):Reshape(c2.Read(),nd,nel_ho);
|
||||
|
||||
// Sparse matrix entries
|
||||
auto V = Reshape(sparse_ij.ReadWrite(), nnz_per_row, nd, nel_ho);
|
||||
|
||||
mfem::forall(nf, [=] MFEM_HOST_DEVICE (int f)
|
||||
{
|
||||
const int f_0 = d_face_info(1, f);
|
||||
const int f_1 = d_face_info(4, f);
|
||||
const int nsides = (f_1 >= 0) ? 2 : 1;
|
||||
for (int el_i = 0; el_i < nsides; ++el_i)
|
||||
{
|
||||
const int e = d_face_info(3*el_i, f);
|
||||
const int o = d_face_info(3*el_i + 2, f);
|
||||
const int v_idx = 1 + ((el_i == 0) ? f_0 : f_1);
|
||||
for (int i = 0; i < nd_face; ++i)
|
||||
{
|
||||
const int ii = internal::FaceIdxToVolIdx(dim, i, pp1, f_0, f_1, el_i, o);
|
||||
const real_t Jh = d_face_Jh(i, f);
|
||||
const real_t dq = const_dq ? DQ(0,0) : DQ(ii, e);
|
||||
V(v_idx, ii, e) = -dq*d_kappa*Jh*w_face[i];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <int ORDER, int SDIM>
|
||||
void BatchedLOR_DG::Assemble2D()
|
||||
{
|
||||
MFEM_VERIFY(SDIM == 2, "Surface meshes not currently supported for LOR-DG.")
|
||||
|
||||
static constexpr int pp1 = ORDER + 1;
|
||||
static constexpr int ndof_per_el = pp1*pp1;
|
||||
static constexpr int nnz_per_row = 5;
|
||||
const int nel_ho = fes_ho.GetNE();
|
||||
|
||||
// Get element geometric factors; calling before AssembleFaceTerms, since
|
||||
// in AssembleFaceTerms, element Jacobian determinants are used, potentially
|
||||
// saving recomputation.
|
||||
const auto factors = GeometricFactors::DETERMINANTS |
|
||||
GeometricFactors::JACOBIANS;
|
||||
const auto *geom = fes_ho.GetMesh()->GetGeometricFactors(ir, factors);
|
||||
|
||||
// Sparse matrix entries
|
||||
sparse_ij.SetSize(nnz_per_row*ndof_per_el*nel_ho);
|
||||
sparse_ij.UseDevice(true);
|
||||
sparse_ij = 0.0;
|
||||
auto V = Reshape(sparse_ij.ReadWrite(), nnz_per_row, pp1, pp1, nel_ho);
|
||||
|
||||
AssembleFaceTerms();
|
||||
|
||||
// Populate Gauss-Lobatto quadrature rule of size (p+1)
|
||||
IntegrationRule ir_pp1;
|
||||
QuadratureFunctions1D::GaussLobatto(pp1, &ir_pp1);
|
||||
Vector glx_pp1(pp1), glw_pp1(pp1);
|
||||
for (int i = 0; i < pp1; ++i)
|
||||
{
|
||||
glx_pp1[i] = ir_pp1[i].x;
|
||||
glw_pp1[i] = ir_pp1[i].weight;
|
||||
}
|
||||
const auto *x_pp1 = glx_pp1.Read();
|
||||
const auto *w_1d = glw_pp1.Read();
|
||||
|
||||
// Get coefficients for mass and diffusion
|
||||
const bool const_mq = c1.Size() == 1;
|
||||
const auto MQ = const_mq
|
||||
? Reshape(c1.Read(), 1, 1, 1)
|
||||
: Reshape(c1.Read(), pp1, pp1, nel_ho);
|
||||
const bool const_dq = c2.Size() == 1;
|
||||
const auto DQ = const_dq
|
||||
? Reshape(c2.Read(), 1, 1, 1)
|
||||
: Reshape(c2.Read(), pp1, pp1, nel_ho);
|
||||
|
||||
const auto detJ = Reshape(geom->detJ.Read(), pp1, pp1, nel_ho);
|
||||
const auto J = Reshape(geom->J.Read(), pp1, pp1, 2, 2, nel_ho);
|
||||
const auto W = Reshape(ir.GetWeights().Read(), pp1, pp1);
|
||||
|
||||
mfem::forall(nel_ho, [=] MFEM_HOST_DEVICE (int iel_ho)
|
||||
{
|
||||
for (int iy = 0; iy < pp1; ++iy)
|
||||
{
|
||||
for (int ix = 0; ix < pp1; ++ix)
|
||||
{
|
||||
const real_t mq = const_mq ? MQ(0,0,0) : MQ(ix, iy, iel_ho);
|
||||
const real_t dq = const_dq ? DQ(0,0,0) : DQ(ix, iy, iel_ho);
|
||||
|
||||
for (int n_idx = 0; n_idx < 2; ++n_idx)
|
||||
{
|
||||
for (int e_i = 0; e_i < 2; ++e_i)
|
||||
{
|
||||
const int i_0 = (n_idx == 0) ? ix + e_i : ix;
|
||||
const int j_0 = (n_idx == 1) ? iy + e_i : iy;
|
||||
|
||||
const bool bdr = (n_idx == 0 && (i_0 == 0 || i_0 == pp1)) ||
|
||||
(n_idx == 1 && (j_0 == 0 || j_0 == pp1));
|
||||
|
||||
if (bdr) { continue; }
|
||||
|
||||
static constexpr int lex_map[] = {4, 2, 1, 3};
|
||||
const int v_idx_lex = e_i + n_idx*2;
|
||||
const int v_idx = lex_map[v_idx_lex];
|
||||
|
||||
const int w_idx = (n_idx == 0) ? iy : ix;
|
||||
const int x_idx = (n_idx == 0) ? i_0 : j_0;
|
||||
|
||||
const real_t J1 = J(ix, iy, n_idx, !n_idx, iel_ho);
|
||||
const real_t J2 = J(ix, iy, !n_idx, !n_idx, iel_ho);
|
||||
const real_t Jh = (J1*J1 + J2*J2) / detJ(ix, iy, iel_ho);
|
||||
|
||||
V(v_idx, ix, iy, iel_ho) =
|
||||
-dq * Jh * w_1d[w_idx] / (x_pp1[x_idx] - x_pp1[x_idx -1]);
|
||||
}
|
||||
}
|
||||
V(0, ix, iy, iel_ho) = mq * detJ(ix, iy, iel_ho) * W(ix, iy);
|
||||
for (int i = 1; i < nnz_per_row; ++i)
|
||||
{
|
||||
V(0, ix, iy, iel_ho) -= V(i, ix, iy, iel_ho);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <int ORDER>
|
||||
void BatchedLOR_DG::Assemble3D()
|
||||
{
|
||||
static constexpr int pp1 = ORDER + 1;
|
||||
static constexpr int ndof_per_el = pp1*pp1*pp1;
|
||||
static constexpr int nnz_per_row = 7;
|
||||
const int nel_ho = fes_ho.GetNE();
|
||||
|
||||
// Get element geometric factors; calling before AssembleFaceTerms, since
|
||||
// in AssembleFaceTerms, element Jacobian determinants are used, potentially
|
||||
// saving recomputation.
|
||||
const auto factors = GeometricFactors::DETERMINANTS |
|
||||
GeometricFactors::JACOBIANS;
|
||||
const auto geom = fes_ho.GetMesh()->GetGeometricFactors(ir, factors);
|
||||
|
||||
sparse_ij.SetSize(nnz_per_row*ndof_per_el*nel_ho);
|
||||
sparse_ij.UseDevice(true);
|
||||
sparse_ij = 0.0;
|
||||
auto V = Reshape(sparse_ij.Write(), nnz_per_row, pp1, pp1, pp1, nel_ho);
|
||||
|
||||
AssembleFaceTerms();
|
||||
|
||||
// Populate Gauss-Lobatto quadrature rule of size (p+1)
|
||||
IntegrationRule ir_pp1;
|
||||
QuadratureFunctions1D::GaussLobatto(pp1, &ir_pp1);
|
||||
Vector glx_pp1(pp1), glw_pp1(pp1);
|
||||
for (int i = 0; i < pp1; ++i)
|
||||
{
|
||||
glx_pp1[i] = ir_pp1[i].x;
|
||||
glw_pp1[i] = ir_pp1[i].weight;
|
||||
}
|
||||
const auto *x_pp1 = glx_pp1.Read();
|
||||
const auto *w_1d = glw_pp1.Read();
|
||||
|
||||
const bool const_mq = c1.Size() == 1;
|
||||
const auto MQ = const_mq
|
||||
? Reshape(c1.Read(), 1, 1, 1, 1)
|
||||
: Reshape(c1.Read(), pp1, pp1, pp1, nel_ho);
|
||||
const bool const_dq = c2.Size() == 1;
|
||||
const auto DQ = const_dq
|
||||
? Reshape(c2.Read(), 1, 1, 1, 1)
|
||||
: Reshape(c2.Read(), pp1, pp1, pp1, nel_ho);
|
||||
const auto W = Reshape(ir.GetWeights().Read(), pp1, pp1, pp1);
|
||||
|
||||
const auto detJ = Reshape(geom->detJ.Read(), pp1, pp1, pp1, nel_ho);
|
||||
const auto J = Reshape(geom->J.Read(), pp1, pp1, pp1, 3, 3, nel_ho);
|
||||
|
||||
mfem::forall(nel_ho, [=] MFEM_HOST_DEVICE (int iel_ho)
|
||||
{
|
||||
for (int iz = 0; iz < pp1; ++iz)
|
||||
{
|
||||
for (int iy = 0; iy < pp1; ++iy)
|
||||
{
|
||||
for (int ix = 0; ix < pp1; ++ix)
|
||||
{
|
||||
const real_t mq = const_mq ? MQ(0,0,0,0) : MQ(ix, iy, iz, iel_ho);
|
||||
const real_t dq = const_dq ? DQ(0,0,0,0) : DQ(ix, iy, iz, iel_ho);
|
||||
|
||||
const real_t DETJ = detJ(ix, iy, iz, iel_ho);
|
||||
|
||||
for (int n_idx = 0; n_idx < 3; ++n_idx)
|
||||
{
|
||||
for (int e_i = 0; e_i < 2; ++e_i)
|
||||
{
|
||||
static constexpr int lex_map[] = {5,3,2,4,1,6};
|
||||
const int v_idx_lex = e_i + n_idx*2;
|
||||
const int v_idx = lex_map[v_idx_lex];
|
||||
|
||||
const int i_0 = (n_idx == 0) ? ix + e_i : ix;
|
||||
const int j_0 = (n_idx == 1) ? iy + e_i : iy;
|
||||
const int k_0 = (n_idx == 2) ? iz + e_i : iz;
|
||||
|
||||
const bool bdr =
|
||||
(n_idx == 0 && (i_0 == 0 || i_0 == pp1)) ||
|
||||
(n_idx == 1 && (j_0 == 0 || j_0 == pp1)) ||
|
||||
(n_idx == 2 && (k_0 == 0 || k_0 == pp1));
|
||||
|
||||
if (bdr) { continue; }
|
||||
|
||||
int x_idx = (n_idx == 0) ? i_0 : (n_idx == 1) ? j_0 : k_0;
|
||||
int w_idx_1 = (n_idx == 0) ? iy : (n_idx == 1) ? iz : ix;
|
||||
int w_idx_2 = (n_idx == 0) ? iz : (n_idx == 1) ? ix : iy;
|
||||
|
||||
const real_t J00 = J(ix, iy, iz, 0, 0, iel_ho);
|
||||
const real_t J01 = J(ix, iy, iz, 0, 1, iel_ho);
|
||||
const real_t J02 = J(ix, iy, iz, 0, 2, iel_ho);
|
||||
const real_t J10 = J(ix, iy, iz, 1, 0, iel_ho);
|
||||
const real_t J11 = J(ix, iy, iz, 1, 1, iel_ho);
|
||||
const real_t J12 = J(ix, iy, iz, 1, 2, iel_ho);
|
||||
const real_t J20 = J(ix, iy, iz, 2, 0, iel_ho);
|
||||
const real_t J21 = J(ix, iy, iz, 2, 1, iel_ho);
|
||||
const real_t J22 = J(ix, iy, iz, 2, 2, iel_ho);
|
||||
|
||||
real_t JinvJinvT_diag = 0.0;
|
||||
if (n_idx == 0)
|
||||
{
|
||||
JinvJinvT_diag = J02*J02*(J11*J11 + J21*J21) + (J12*J21 - J11*J22)*
|
||||
(J12*J21 - J11*J22) - 2*J01*J02*(J11*J12 + J21*J22) + J01*J01*
|
||||
(J12*J12 + J22*J22);
|
||||
}
|
||||
else if (n_idx == 1)
|
||||
{
|
||||
JinvJinvT_diag = J02*J02*(J10*J10 + J20*J20) + (J12*J20 - J10*J22)*
|
||||
(J12*J20 - J10*J22) - 2*J00*J02*(J10*J12 + J20*J22) + J00*J00*
|
||||
(J12*J12 + J22*J22);
|
||||
}
|
||||
else if (n_idx == 2)
|
||||
{
|
||||
JinvJinvT_diag = J01*J01*(J10*J10 + J20*J20) + (J11*J20 - J10*J21)*
|
||||
(J11*J20 - J10*J21) - 2*J00*J01*(J10*J11 + J20*J21) + J00*J00*
|
||||
(J11*J11 + J21*J21);
|
||||
}
|
||||
|
||||
const real_t Jh = JinvJinvT_diag / DETJ;
|
||||
|
||||
V(v_idx, ix, iy, iz, iel_ho) = -dq * Jh * w_1d[w_idx_1] * w_1d[w_idx_2] /
|
||||
(x_pp1[x_idx] - x_pp1[x_idx -1]);
|
||||
}
|
||||
}
|
||||
V(0, ix, iy, iz, iel_ho) = mq * DETJ * W(ix, iy, iz);
|
||||
for (int i = 1; i < 7; ++i)
|
||||
{
|
||||
V(0, ix, iy, iz, iel_ho) -= V(i, ix, iy, iz, iel_ho);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace mfem
|
||||
+49
-8
@@ -436,7 +436,7 @@ void NonlinearForm::Mult(const Vector &x, Vector &y) const
|
||||
// In parallel, the result is in 'py' which is an alias for 'aux2'.
|
||||
}
|
||||
|
||||
Operator &NonlinearForm::GetGradient(const Vector &x) const
|
||||
Operator &NonlinearForm::GetGradient(const Vector &x, bool finalize) const
|
||||
{
|
||||
if (ext)
|
||||
{
|
||||
@@ -644,6 +644,8 @@ Operator &NonlinearForm::GetGradient(const Vector &x) const
|
||||
}
|
||||
}
|
||||
|
||||
if (!finalize) { return *Grad; }
|
||||
|
||||
if (!Grad->Finalized())
|
||||
{
|
||||
Grad->Finalize(skip_zeros);
|
||||
@@ -788,12 +790,10 @@ BlockNonlinearForm::BlockNonlinearForm(Array<FiniteElementSpace *> &f) :
|
||||
}
|
||||
|
||||
void BlockNonlinearForm::SetEssentialBC(
|
||||
const Array<Array<int> *> &bdr_attr_is_ess, Array<Vector *> &rhs)
|
||||
const Array<Array<int>*> &bdr_attr_is_ess, Array<Vector*> &rhs)
|
||||
{
|
||||
for (int s = 0; s < fes.Size(); ++s)
|
||||
{
|
||||
ess_tdofs[s]->SetSize(ess_tdofs.Size());
|
||||
|
||||
fes[s]->GetEssentialTrueDofs(*bdr_attr_is_ess[s], *ess_tdofs[s]);
|
||||
|
||||
if (rhs[s])
|
||||
@@ -803,6 +803,19 @@ void BlockNonlinearForm::SetEssentialBC(
|
||||
}
|
||||
}
|
||||
|
||||
void BlockNonlinearForm::SetEssentialTrueDofs(
|
||||
const Array<Array<int>*> &ess_tdof_list, Array<Vector*> &rhs)
|
||||
{
|
||||
for (int s = 0; s < fes.Size(); ++s)
|
||||
{
|
||||
*ess_tdofs[s] = *ess_tdof_list[s];
|
||||
if (rhs[s])
|
||||
{
|
||||
rhs[s]->SetSubVector(*ess_tdofs[s], 0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
real_t BlockNonlinearForm::GetEnergyBlocked(const BlockVector &bx) const
|
||||
{
|
||||
Array<Array<int> *> vdofs(fes.Size());
|
||||
@@ -1192,7 +1205,14 @@ const BlockVector &BlockNonlinearForm::Prolongate(const BlockVector &bx) const
|
||||
aux1.Update(block_offsets);
|
||||
for (int s = 0; s < fes.Size(); s++)
|
||||
{
|
||||
P[s]->Mult(bx.GetBlock(s), aux1.GetBlock(s));
|
||||
if (P[s])
|
||||
{
|
||||
P[s]->Mult(bx.GetBlock(s), aux1.GetBlock(s));
|
||||
}
|
||||
else
|
||||
{
|
||||
aux1.GetBlock(s) = bx.GetBlock(s);
|
||||
}
|
||||
}
|
||||
return aux1;
|
||||
}
|
||||
@@ -1221,11 +1241,16 @@ void BlockNonlinearForm::Mult(const Vector &x, Vector &y) const
|
||||
{
|
||||
cP[s]->MultTranspose(pby.GetBlock(s), by.GetBlock(s));
|
||||
}
|
||||
else if (needs_prolongation)
|
||||
{
|
||||
by.GetBlock(s) = pby.GetBlock(s);
|
||||
}
|
||||
by.GetBlock(s).SetSubVector(*ess_tdofs[s], 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
void BlockNonlinearForm::ComputeGradientBlocked(const BlockVector &bx) const
|
||||
void BlockNonlinearForm::ComputeGradientBlocked(const BlockVector &bx,
|
||||
bool finalize) const
|
||||
{
|
||||
const int skip_zeros = 0;
|
||||
Array<Array<int> *> vdofs(fes.Size());
|
||||
@@ -1479,7 +1504,7 @@ void BlockNonlinearForm::ComputeGradientBlocked(const BlockVector &bx) const
|
||||
}
|
||||
}
|
||||
|
||||
if (!Grads(0,0)->Finalized())
|
||||
if (finalize && !Grads(0,0)->Finalized())
|
||||
{
|
||||
for (int i=0; i<fes.Size(); ++i)
|
||||
{
|
||||
@@ -1518,7 +1543,23 @@ Operator &BlockNonlinearForm::GetGradient(const Vector &x) const
|
||||
for (int s2 = 0; s2 < fes.Size(); ++s2)
|
||||
{
|
||||
delete cGrads(s1, s2);
|
||||
cGrads(s1, s2) = RAP(*cP[s1], *Grads(s1, s2), *cP[s2]);
|
||||
if (cP[s1] && cP[s2])
|
||||
{
|
||||
cGrads(s1, s2) = RAP(*cP[s1], *Grads(s1, s2), *cP[s2]);
|
||||
}
|
||||
else if (cP[s1])
|
||||
{
|
||||
cGrads(s1, s2) = TransposeMult(*cP[s1], *Grads(s1, s2));
|
||||
}
|
||||
else if (cP[s2])
|
||||
{
|
||||
cGrads(s1, s2) = mfem::Mult(*Grads(s1, s2), *cP[s2]);
|
||||
}
|
||||
else
|
||||
{
|
||||
cGrads(s1, s2) = NULL;
|
||||
continue;
|
||||
}
|
||||
mGrads(s1, s2) = cGrads(s1, s2);
|
||||
}
|
||||
}
|
||||
|
||||
+40
-4
@@ -217,7 +217,12 @@ public:
|
||||
In general, @a x may have non-homogeneous essential boundary values.
|
||||
|
||||
The state @a x must be a true-dof vector. */
|
||||
Operator &GetGradient(const Vector &x) const override;
|
||||
Operator &GetGradient(const Vector &x) const override { return GetGradient(x, true); }
|
||||
|
||||
/** @brief Compute the gradient Operator of the NonlinearForm corresponding
|
||||
to the state @a x with optional finalization and elimintaion. */
|
||||
/** @see GetGradient(const Vector &) */
|
||||
Operator &GetGradient(const Vector &x, bool finalize) const;
|
||||
|
||||
/// Update the NonlinearForm to propagate updates of the associated FE space.
|
||||
/** After calling this method, the essential boundary conditions need to be
|
||||
@@ -308,7 +313,7 @@ protected:
|
||||
void MultBlocked(const BlockVector &bx, BlockVector &by) const;
|
||||
|
||||
/// Specialized version of GetGradient() for BlockVector
|
||||
void ComputeGradientBlocked(const BlockVector &bx) const;
|
||||
void ComputeGradientBlocked(const BlockVector &bx, bool finalize = true) const;
|
||||
|
||||
public:
|
||||
/// Construct an empty BlockNonlinearForm. Initialize with SetSpaces().
|
||||
@@ -363,8 +368,39 @@ public:
|
||||
Array<int> &bdr_marker)
|
||||
{ bfnfi.Append(nlfi); bfnfi_marker.Append(&bdr_marker); }
|
||||
|
||||
virtual void SetEssentialBC(const Array<Array<int> *>&bdr_attr_is_ess,
|
||||
Array<Vector *> &rhs);
|
||||
/** @brief Set essential boundary conditions to each finite element space
|
||||
using boundary attribute markers.
|
||||
|
||||
This method calls `FiniteElementSpace::GetEssentialTrueDofs()` for each
|
||||
space and stores ess_tdof_lists internally.
|
||||
|
||||
If `rhs` vectors are non-null, the entries corresponding to these
|
||||
essential DoFs are set to zero. This ensures compatibility with the
|
||||
output of the `Mult()` method, which also zeroes out these entries.
|
||||
|
||||
@param[in] bdr_attr_is_ess A list of boundary attribute markers for each
|
||||
space.
|
||||
@param[in,out] rhs An array of optional right-hand side vectors.
|
||||
If a vector at `rhs[i]` is non-null, its essential DoFs will be set
|
||||
to zero. */
|
||||
virtual void SetEssentialBC(const Array<Array<int>*> &bdr_attr_is_ess,
|
||||
Array<Vector*> &rhs);
|
||||
|
||||
/** @brief Set essential boundary conditions to each finite element space
|
||||
using essential true dof lists.
|
||||
|
||||
This method stores a copy of the provided essential true dof lists.
|
||||
|
||||
If `rhs` vectors are non-null, the entries corresponding to these
|
||||
essential DoFs are set to zero. This ensures compatibility with the
|
||||
output of the `Mult()` method, which also zeroes out these entries.
|
||||
|
||||
@param[in] ess_tdof_list A list of essential true dofs for each space.
|
||||
@param[in,out] rhs An array of optional right-hand side vectors.
|
||||
If a vector at `rhs[i]` is non-null, its essential DoFs will be set
|
||||
to zero. */
|
||||
virtual void SetEssentialTrueDofs(const Array<Array<int>*> &ess_tdof_list,
|
||||
Array<Vector*> &rhs);
|
||||
|
||||
virtual real_t GetEnergy(const Vector &x) const;
|
||||
|
||||
|
||||
+251
-39
@@ -151,6 +151,15 @@ void ParBilinearForm::ParallelRAP(SparseMatrix &loc_A, OperatorHandle &A,
|
||||
}
|
||||
}
|
||||
|
||||
HypreParMatrix *ParBilinearForm::ParallelAssembleInternalMatrix()
|
||||
{
|
||||
if (p_mat.Ptr() == NULL)
|
||||
{
|
||||
ParallelAssemble(p_mat, mat);
|
||||
}
|
||||
return p_mat.As<HypreParMatrix>();
|
||||
}
|
||||
|
||||
void ParBilinearForm::ParallelAssemble(OperatorHandle &A, SparseMatrix *A_local)
|
||||
{
|
||||
A.Clear();
|
||||
@@ -333,6 +342,15 @@ void ParBilinearForm
|
||||
A.EliminateRowsCols(dof_list, X, B);
|
||||
}
|
||||
|
||||
void ParBilinearForm::ParallelEliminateEssentialBC(
|
||||
const Array<int> &bdr_attr_is_ess, const HypreParVector &X, HypreParVector &B)
|
||||
{
|
||||
Array<int> dof_list;
|
||||
pfes->GetEssentialTrueDofs(bdr_attr_is_ess, dof_list);
|
||||
|
||||
p_mat.As<HypreParMatrix>()->EliminateRowsCols(dof_list, X, B);
|
||||
}
|
||||
|
||||
HypreParMatrix *ParBilinearForm::
|
||||
ParallelEliminateEssentialBC(const Array<int> &bdr_attr_is_ess,
|
||||
HypreParMatrix &A) const
|
||||
@@ -344,6 +362,26 @@ ParallelEliminateEssentialBC(const Array<int> &bdr_attr_is_ess,
|
||||
return A.EliminateRowsCols(dof_list);
|
||||
}
|
||||
|
||||
void ParBilinearForm::ParallelEliminateEssentialBC(const Array<int>
|
||||
&bdr_attr_is_ess)
|
||||
{
|
||||
Array<int> tdofs_list;
|
||||
pfes->GetEssentialTrueDofs(bdr_attr_is_ess, tdofs_list);
|
||||
|
||||
ParallelEliminateTDofs(tdofs_list);
|
||||
}
|
||||
|
||||
void ParBilinearForm::ParallelEliminateTDofs(const Array<int> &tdofs_list)
|
||||
{
|
||||
p_mat_e.EliminateRowsCols(p_mat, tdofs_list);
|
||||
}
|
||||
|
||||
void ParBilinearForm::ParallelEliminateTDofsInRHS(
|
||||
const Array<int> &tdofs_list, const Vector &x, Vector &b)
|
||||
{
|
||||
p_mat.EliminateBC(p_mat_e, tdofs_list, x, b);
|
||||
}
|
||||
|
||||
void ParBilinearForm::TrueAddMult(const Vector &x, Vector &y, const real_t a)
|
||||
const
|
||||
{
|
||||
@@ -485,7 +523,7 @@ void ParBilinearForm::FormLinearSystem(
|
||||
HypreParVector true_X(pfes), true_B(pfes);
|
||||
P.MultTranspose(b, true_B);
|
||||
R.Mult(x, true_X);
|
||||
p_mat.EliminateBC(p_mat_e, ess_tdof_list, true_X, true_B);
|
||||
ParallelEliminateTDofsInRHS(ess_tdof_list, true_X, true_B);
|
||||
R.MultTranspose(true_B, b);
|
||||
hybridization->ReduceRHS(true_B, B);
|
||||
X.SetSize(B.Size());
|
||||
@@ -498,17 +536,11 @@ void ParBilinearForm::FormLinearSystem(
|
||||
B.SetSize(X.Size());
|
||||
P.MultTranspose(b, B);
|
||||
R.Mult(x, X);
|
||||
p_mat.EliminateBC(p_mat_e, ess_tdof_list, X, B);
|
||||
ParallelEliminateTDofsInRHS(ess_tdof_list, X, B);
|
||||
if (!copy_interior) { X.SetSubVectorComplement(ess_tdof_list, 0.0); }
|
||||
}
|
||||
}
|
||||
|
||||
void ParBilinearForm::EliminateVDofsInRHS(
|
||||
const Array<int> &vdofs, const Vector &x, Vector &b)
|
||||
{
|
||||
p_mat.EliminateBC(p_mat_e, vdofs, x, b);
|
||||
}
|
||||
|
||||
void ParBilinearForm::FormSystemMatrix(const Array<int> &ess_tdof_list,
|
||||
OperatorHandle &A)
|
||||
{
|
||||
@@ -553,7 +585,7 @@ void ParBilinearForm::FormSystemMatrix(const Array<int> &ess_tdof_list,
|
||||
mat = NULL;
|
||||
delete mat_e;
|
||||
mat_e = NULL;
|
||||
p_mat_e.EliminateRowsCols(p_mat, ess_tdof_list);
|
||||
ParallelEliminateTDofs(ess_tdof_list);
|
||||
}
|
||||
if (hybridization)
|
||||
{
|
||||
@@ -615,36 +647,180 @@ void ParBilinearForm::Update(FiniteElementSpace *nfes)
|
||||
p_mat_e.Clear();
|
||||
}
|
||||
|
||||
|
||||
HypreParMatrix *ParMixedBilinearForm::ParallelAssemble()
|
||||
void ParMixedBilinearForm::pAllocMat()
|
||||
{
|
||||
// construct the block-diagonal matrix A
|
||||
HypreParMatrix *A =
|
||||
new HypreParMatrix(trial_pfes->GetComm(),
|
||||
test_pfes->GlobalVSize(),
|
||||
trial_pfes->GlobalVSize(),
|
||||
test_pfes->GetDofOffsets(),
|
||||
trial_pfes->GetDofOffsets(),
|
||||
mat);
|
||||
const int trial_nbr_size = trial_pfes->GetFaceNbrVSize();
|
||||
const int test_nbr_size = test_pfes->GetFaceNbrVSize();
|
||||
|
||||
HypreParMatrix *rap = RAP(test_pfes->Dof_TrueDof_Matrix(), A,
|
||||
trial_pfes->Dof_TrueDof_Matrix());
|
||||
|
||||
delete A;
|
||||
|
||||
return rap;
|
||||
if (keep_nbr_block)
|
||||
{
|
||||
mat = new SparseMatrix(height + test_nbr_size, width + trial_nbr_size);
|
||||
}
|
||||
else
|
||||
{
|
||||
mat = new SparseMatrix(height, width + trial_nbr_size);
|
||||
}
|
||||
}
|
||||
|
||||
void ParMixedBilinearForm::ParallelAssemble(OperatorHandle &A)
|
||||
void ParMixedBilinearForm::AssembleSharedFaces(int skip_zeros)
|
||||
{
|
||||
// construct the rectangular block-diagonal matrix dA
|
||||
OperatorHandle dA(A.Type());
|
||||
dA.MakeRectangularBlockDiag(trial_pfes->GetComm(),
|
||||
test_pfes->GlobalVSize(),
|
||||
trial_pfes->GlobalVSize(),
|
||||
test_pfes->GetDofOffsets(),
|
||||
trial_pfes->GetDofOffsets(),
|
||||
mat);
|
||||
ParMesh *pmesh = trial_pfes->GetParMesh();
|
||||
FaceElementTransformations *T;
|
||||
Array<int> tr_vdofs1, tr_vdofs2, tr_vdofs_all;
|
||||
Array<int> te_vdofs1, te_vdofs2, te_vdofs_all;
|
||||
DenseMatrix elemmat;
|
||||
|
||||
int nfaces = pmesh->GetNSharedFaces();
|
||||
for (int i = 0; i < nfaces; i++)
|
||||
{
|
||||
T = pmesh->GetSharedFaceTransformations(i);
|
||||
int Elem2NbrNo = T->Elem2No - pmesh->GetNE();
|
||||
trial_pfes->GetElementVDofs(T->Elem1No, tr_vdofs1);
|
||||
test_pfes->GetElementVDofs(T->Elem1No, te_vdofs1);
|
||||
trial_pfes->GetFaceNbrElementVDofs(Elem2NbrNo, tr_vdofs2);
|
||||
test_pfes->GetFaceNbrElementVDofs(Elem2NbrNo, te_vdofs2);
|
||||
|
||||
tr_vdofs1.Copy(tr_vdofs_all);
|
||||
for (int j = 0; j < tr_vdofs2.Size(); j++)
|
||||
{
|
||||
if (tr_vdofs2[j] >= 0)
|
||||
{
|
||||
tr_vdofs2[j] += width;
|
||||
}
|
||||
else
|
||||
{
|
||||
tr_vdofs2[j] -= width;
|
||||
}
|
||||
}
|
||||
tr_vdofs_all.Append(tr_vdofs2);
|
||||
|
||||
if (keep_nbr_block)
|
||||
{
|
||||
te_vdofs1.Copy(te_vdofs_all);
|
||||
for (int j = 0; j < te_vdofs2.Size(); j++)
|
||||
{
|
||||
if (te_vdofs2[j] >= 0)
|
||||
{
|
||||
te_vdofs2[j] += height;
|
||||
}
|
||||
else
|
||||
{
|
||||
te_vdofs2[j] -= height;
|
||||
}
|
||||
}
|
||||
te_vdofs_all.Append(te_vdofs2);
|
||||
}
|
||||
|
||||
for (int k = 0; k < interior_face_integs.Size(); k++)
|
||||
{
|
||||
interior_face_integs[k]->
|
||||
AssembleFaceMatrix(*trial_pfes->GetFE(T->Elem1No),
|
||||
*test_pfes->GetFE(T->Elem1No),
|
||||
*trial_pfes->GetFaceNbrFE(Elem2NbrNo),
|
||||
*test_pfes->GetFaceNbrFE(Elem2NbrNo),
|
||||
*T, elemmat);
|
||||
if (keep_nbr_block)
|
||||
{
|
||||
mat->AddSubMatrix(te_vdofs_all, tr_vdofs_all, elemmat, skip_zeros);
|
||||
}
|
||||
else
|
||||
{
|
||||
mat->AddSubMatrix(te_vdofs1, tr_vdofs_all, elemmat, skip_zeros);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ParMixedBilinearForm::Assemble(int skip_zeros)
|
||||
{
|
||||
if (interior_face_integs.Size())
|
||||
{
|
||||
trial_pfes->ExchangeFaceNbrData();
|
||||
test_pfes->ExchangeFaceNbrData();
|
||||
if (!ext && mat == NULL)
|
||||
{
|
||||
pAllocMat();
|
||||
}
|
||||
}
|
||||
|
||||
MixedBilinearForm::Assemble(skip_zeros);
|
||||
|
||||
if (!ext && interior_face_integs.Size() > 0)
|
||||
{
|
||||
AssembleSharedFaces(skip_zeros);
|
||||
}
|
||||
}
|
||||
|
||||
HypreParMatrix *ParMixedBilinearForm::ParallelAssembleInternalMatrix()
|
||||
{
|
||||
if (p_mat.Ptr() == NULL)
|
||||
{
|
||||
ParallelAssemble(p_mat, mat);
|
||||
}
|
||||
return p_mat.As<HypreParMatrix>();
|
||||
}
|
||||
|
||||
HypreParMatrix *ParMixedBilinearForm::ParallelAssemble(SparseMatrix *m)
|
||||
{
|
||||
OperatorHandle Mh(Operator::Hypre_ParCSR);
|
||||
ParallelAssemble(Mh, m);
|
||||
Mh.SetOperatorOwner(false);
|
||||
return Mh.As<HypreParMatrix>();
|
||||
}
|
||||
|
||||
void ParMixedBilinearForm::ParallelAssemble(OperatorHandle &A,
|
||||
SparseMatrix *A_local)
|
||||
{
|
||||
A.Clear();
|
||||
|
||||
if (A_local == NULL) { return; }
|
||||
MFEM_VERIFY(A_local->Finalized(), "the local matrix must be finalized");
|
||||
|
||||
OperatorHandle dA(A.Type()), hdA;
|
||||
|
||||
if (interior_face_integs.Size() == 0)
|
||||
{
|
||||
// construct the rectangular block-diagonal matrix dA
|
||||
dA.MakeRectangularBlockDiag(trial_pfes->GetComm(),
|
||||
test_pfes->GlobalVSize(),
|
||||
trial_pfes->GlobalVSize(),
|
||||
test_pfes->GetDofOffsets(),
|
||||
trial_pfes->GetDofOffsets(),
|
||||
A_local);
|
||||
}
|
||||
else
|
||||
{
|
||||
// handle the case when 'a' contains off-diagonal
|
||||
const int lvrows = test_pfes->GetVSize();
|
||||
const int lvcols = trial_pfes->GetVSize();
|
||||
const HYPRE_BigInt *face_nbr_glob_lcol = trial_pfes->GetFaceNbrGlobalDofMap();
|
||||
const HYPRE_BigInt lcol_offset = trial_pfes->GetMyDofOffset();
|
||||
|
||||
Array<HYPRE_BigInt> glob_J(A_local->NumNonZeroElems());
|
||||
const int *J = A_local->GetJ();
|
||||
for (int i = 0; i < glob_J.Size(); i++)
|
||||
{
|
||||
if (J[i] < lvcols)
|
||||
{
|
||||
glob_J[i] = J[i] + lcol_offset;
|
||||
}
|
||||
else
|
||||
{
|
||||
glob_J[i] = face_nbr_glob_lcol[J[i] - lvcols];
|
||||
}
|
||||
}
|
||||
|
||||
// TODO - construct dA directly in the A format
|
||||
hdA.Reset(
|
||||
new HypreParMatrix(trial_pfes->GetComm(), lvrows, test_pfes->GlobalVSize(),
|
||||
trial_pfes->GlobalVSize(), A_local->GetI(), glob_J,
|
||||
A_local->GetData(), test_pfes->GetDofOffsets(),
|
||||
trial_pfes->GetDofOffsets()));
|
||||
// - hdA owns the new HypreParMatrix
|
||||
// - the above constructor copies all input arrays
|
||||
glob_J.DeleteAll();
|
||||
dA.ConvertFrom(hdA);
|
||||
}
|
||||
|
||||
OperatorHandle P_test(A.Type()), P_trial(A.Type());
|
||||
|
||||
@@ -670,6 +846,44 @@ void ParMixedBilinearForm::TrueAddMult(const Vector &x, Vector &y,
|
||||
test_pfes->Dof_TrueDof_Matrix()->MultTranspose(a, Yaux, 1.0, y);
|
||||
}
|
||||
|
||||
void ParMixedBilinearForm::ParallelEliminateTrialEssentialBC(
|
||||
const Array<int> &bdr_attr_is_ess)
|
||||
{
|
||||
Array<int> trial_tdof_list;
|
||||
trial_pfes->GetEssentialTrueDofs(bdr_attr_is_ess, trial_tdof_list);
|
||||
|
||||
ParallelEliminateTrialTDofs(trial_tdof_list);
|
||||
}
|
||||
|
||||
void ParMixedBilinearForm::ParallelEliminateTrialTDofs(
|
||||
const Array<int> &trial_tdof_list)
|
||||
{
|
||||
HypreParMatrix *temp = p_mat.As<HypreParMatrix>()->EliminateCols(
|
||||
trial_tdof_list);
|
||||
p_mat_e.Reset(temp, true);
|
||||
}
|
||||
|
||||
void ParMixedBilinearForm::ParallelEliminateTrialTDofsInRHS(
|
||||
const Array<int> &trial_tdof_list, const Vector &x, Vector &b)
|
||||
{
|
||||
p_mat_e.As<HypreParMatrix>()->Mult(-1.0, x, 1.0, b);
|
||||
}
|
||||
|
||||
void ParMixedBilinearForm::ParallelEliminateTestEssentialBC(
|
||||
const Array<int> &bdr_attr_is_ess)
|
||||
{
|
||||
Array<int> test_tdof_list;
|
||||
test_pfes->GetEssentialTrueDofs(bdr_attr_is_ess, test_tdof_list);
|
||||
|
||||
ParallelEliminateTestTDofs(test_tdof_list);
|
||||
}
|
||||
|
||||
void ParMixedBilinearForm::ParallelEliminateTestTDofs(
|
||||
const Array<int> &test_tdof_list)
|
||||
{
|
||||
p_mat.As<HypreParMatrix>()->EliminateRows(test_tdof_list);
|
||||
}
|
||||
|
||||
void ParMixedBilinearForm::FormRectangularSystemMatrix(
|
||||
const Array<int>
|
||||
&trial_tdof_list,
|
||||
@@ -690,10 +904,8 @@ void ParMixedBilinearForm::FormRectangularSystemMatrix(
|
||||
mat = NULL;
|
||||
delete mat_e;
|
||||
mat_e = NULL;
|
||||
HypreParMatrix *temp =
|
||||
p_mat.As<HypreParMatrix>()->EliminateCols(trial_tdof_list);
|
||||
p_mat.As<HypreParMatrix>()->EliminateRows(test_tdof_list);
|
||||
p_mat_e.Reset(temp, true);
|
||||
ParallelEliminateTrialTDofs(trial_tdof_list);
|
||||
ParallelEliminateTestTDofs(test_tdof_list);
|
||||
}
|
||||
|
||||
A = p_mat;
|
||||
@@ -723,7 +935,7 @@ void ParMixedBilinearForm::FormRectangularLinearSystem(
|
||||
test_P->MultTranspose(b, B);
|
||||
trial_R->Mult(x, X);
|
||||
|
||||
p_mat_e.As<HypreParMatrix>()->Mult(-1.0, X, 1.0, B);
|
||||
ParallelEliminateTrialTDofsInRHS(trial_tdof_list, X, B);
|
||||
B.SetSubVector(test_tdof_list, 0.0);
|
||||
}
|
||||
|
||||
|
||||
+128
-5
@@ -73,7 +73,7 @@ public:
|
||||
/** When set to true and the ParBilinearForm has interior face integrators,
|
||||
the local SparseMatrix will include the rows (in addition to the columns)
|
||||
corresponding to face-neighbor dofs. The default behavior is to disregard
|
||||
those rows. Must be called before the first Assemble call. */
|
||||
those rows. Must be called before the first Assemble() call. */
|
||||
void KeepNbrBlock(bool knb = true) { keep_nbr_block = knb; }
|
||||
|
||||
/** @brief Set the operator type id for the parallel matrix/operator when
|
||||
@@ -101,6 +101,14 @@ public:
|
||||
diagonal for this case. */
|
||||
void AssembleDiagonal(Vector &diag) const override;
|
||||
|
||||
/// Returns the matrix assembled on the true dofs, i.e. P^t A P.
|
||||
/** The returned matrix is the internal one, owned by the form. It is not
|
||||
reassembled if it has been already constructed. If FormSystemMatrix()
|
||||
has been called before, it is the system matrix with eliminated
|
||||
essential DOFs, otherwise the parallel matrix is assembled here without
|
||||
the elimination process. */
|
||||
HypreParMatrix *ParallelAssembleInternalMatrix();
|
||||
|
||||
/// Returns the matrix assembled on the true dofs, i.e. P^t A P.
|
||||
/** The returned matrix has to be deleted by the caller. */
|
||||
HypreParMatrix *ParallelAssemble() { return ParallelAssemble(mat); }
|
||||
@@ -146,6 +154,13 @@ public:
|
||||
const HypreParVector &X,
|
||||
HypreParVector &B) const;
|
||||
|
||||
/// Eliminate essential boundary DOFs from the parallel system matrix.
|
||||
/** The array @a bdr_attr_is_ess marks boundary attributes that constitute
|
||||
the essential part of the boundary. */
|
||||
void ParallelEliminateEssentialBC(const Array<int> &bdr_attr_is_ess,
|
||||
const HypreParVector &X,
|
||||
HypreParVector &B);
|
||||
|
||||
/// Eliminate essential boundary DOFs from a parallel assembled matrix @a A.
|
||||
/** The array @a bdr_attr_is_ess marks boundary attributes that constitute
|
||||
the essential part of the boundary. The eliminated part is stored in a
|
||||
@@ -157,6 +172,12 @@ public:
|
||||
HypreParMatrix *ParallelEliminateEssentialBC(const Array<int> &bdr_attr_is_ess,
|
||||
HypreParMatrix &A) const;
|
||||
|
||||
/// Eliminate essential boundary DOFs from the parallel system matrix.
|
||||
/** The array @a bdr_attr_is_ess marks boundary attributes that constitute
|
||||
the essential part of the boundary. This method relies on
|
||||
ParallelEliminateTDofs(const Array<int> &), see it for details. */
|
||||
void ParallelEliminateEssentialBC(const Array<int> &bdr_attr_is_ess);
|
||||
|
||||
/// Eliminate essential true DOFs from a parallel assembled matrix @a A.
|
||||
/** Given a list of essential true dofs and the parallel assembled matrix
|
||||
@a A, eliminate the true dofs from the matrix, storing the eliminated
|
||||
@@ -169,6 +190,28 @@ public:
|
||||
HypreParMatrix &A) const
|
||||
{ return A.EliminateRowsCols(tdofs_list); }
|
||||
|
||||
/// Eliminate essential true DOFs from the parallel system matrix.
|
||||
/** Given a list of essential true dofs, eliminate the true dofs from
|
||||
the parallel assembled system matrix, storing the eliminated part
|
||||
internally. This method works in conjunction with
|
||||
ParallelEliminateTDofsInRHS() and allows elimination of boundary
|
||||
conditions in multiple right-hand sides. */
|
||||
void ParallelEliminateTDofs(const Array<int> &tdofs_list);
|
||||
|
||||
/** @brief Use the stored eliminated part of the parallel system matrix for
|
||||
elimination of boundary conditions in the r.h.s. */
|
||||
/** Given a list of essential true dofs, eliminate the true dofs from the
|
||||
right-hand side @a b using the solution vector @a x and the previously
|
||||
stored eliminated part of the parallel assembled system matrix produced
|
||||
by ParallelEliminateTDofs(const Array<int> &). */
|
||||
void ParallelEliminateTDofsInRHS(const Array<int> &tdofs, const Vector &x,
|
||||
Vector &b);
|
||||
|
||||
/// @deprecated Use ParallelEliminateTDofsInRHS() instead.
|
||||
MFEM_DEPRECATED void EliminateVDofsInRHS(const Array<int> &vdofs,
|
||||
const Vector &x, Vector &b)
|
||||
{ ParallelEliminateTDofsInRHS(vdofs, x, b); }
|
||||
|
||||
/** @brief Compute @a y += @a a (P^t A P) @a x, where @a x and @a y are
|
||||
vectors on the true dofs. */
|
||||
void TrueAddMult(const Vector &x, Vector &y, const real_t a = 1.0) const;
|
||||
@@ -238,8 +281,6 @@ public:
|
||||
|
||||
void Update(FiniteElementSpace *nfes = NULL) override;
|
||||
|
||||
void EliminateVDofsInRHS(const Array<int> &vdofs, const Vector &x, Vector &b);
|
||||
|
||||
virtual ~ParBilinearForm() { }
|
||||
};
|
||||
|
||||
@@ -257,6 +298,13 @@ protected:
|
||||
/// Matrix and eliminated matrix
|
||||
OperatorHandle p_mat, p_mat_e;
|
||||
|
||||
bool keep_nbr_block;
|
||||
|
||||
// Allocate mat - called when (mat == NULL && fbfi.Size() > 0)
|
||||
void pAllocMat();
|
||||
|
||||
void AssembleSharedFaces(int skip_zeros = 1);
|
||||
|
||||
private:
|
||||
/// Copy construction is not supported; body is undefined.
|
||||
ParMixedBilinearForm(const ParMixedBilinearForm &);
|
||||
@@ -276,6 +324,7 @@ public:
|
||||
{
|
||||
trial_pfes = trial_fes;
|
||||
test_pfes = test_fes;
|
||||
keep_nbr_block = false;
|
||||
}
|
||||
|
||||
/** @brief Create a ParMixedBilinearForm on the given FiniteElementSpace%s
|
||||
@@ -295,15 +344,89 @@ public:
|
||||
{
|
||||
trial_pfes = trial_fes;
|
||||
test_pfes = test_fes;
|
||||
keep_nbr_block = false;
|
||||
}
|
||||
|
||||
/** When set to true and the ParMixedBilinearForm has interior face
|
||||
integrators, the local SparseMatrix will include the rows (in addition
|
||||
to the columns) corresponding to face-neighbor dofs. The default
|
||||
behavior is to disregard those rows. Must be called before the first
|
||||
Assemble() call. */
|
||||
void KeepNbrBlock(bool knb = true) { keep_nbr_block = knb; }
|
||||
|
||||
/// Assemble the local matrix
|
||||
void Assemble(int skip_zeros = 1);
|
||||
|
||||
/// Returns the matrix assembled on the true dofs, i.e. P_test^t A P_trial.
|
||||
HypreParMatrix *ParallelAssemble();
|
||||
/** The returned matrix is the internal one, owned by the form. It is not
|
||||
reassembled if it has been already constructed. If
|
||||
FormRectangularSystemMatrix() has been called before, it is the system
|
||||
matrix with eliminated essential DOFs, otherwise the parallel matrix is
|
||||
assembled here without the elimination process. */
|
||||
HypreParMatrix *ParallelAssembleInternalMatrix();
|
||||
|
||||
/// Returns the matrix assembled on the true dofs, i.e. P_test^t A P_trial.
|
||||
/** The returned matrix has to be deleted by the caller. */
|
||||
HypreParMatrix *ParallelAssemble() { return ParallelAssemble(mat); }
|
||||
|
||||
/** @brief Returns the eliminated matrix assembled on the true dofs, i.e.
|
||||
P_test^t A_local P_trial. */
|
||||
/** The returned matrix has to be deleted by the caller. */
|
||||
HypreParMatrix *ParallelAssembleElim() { return ParallelAssemble(mat_e); }
|
||||
|
||||
/** @brief Return the matrix @a m assembled on the true dofs, i.e. P_test^t
|
||||
A_local P_trial. */
|
||||
/** The returned matrix has to be deleted by the caller. */
|
||||
HypreParMatrix *ParallelAssemble(SparseMatrix *m);
|
||||
|
||||
/** @brief Returns the matrix assembled on the true dofs, i.e.
|
||||
@a A = P_test^t A_local P_trial, in the format (type id) specified by
|
||||
@a A. */
|
||||
void ParallelAssemble(OperatorHandle &A);
|
||||
void ParallelAssemble(OperatorHandle &A) { ParallelAssemble(A, mat); }
|
||||
|
||||
/** Returns the eliminated matrix assembled on the true dofs, i.e.
|
||||
@a A_elim = P^t A_elim_local P in the format (type id) specified by @a A.
|
||||
*/
|
||||
void ParallelAssembleElim(OperatorHandle &A_elim)
|
||||
{ ParallelAssemble(A_elim, mat_e); }
|
||||
|
||||
/** Returns the matrix @a A_local assembled on the true dofs, i.e.
|
||||
@a A = P_test^t A_local P_trial in the format (type id) specified by
|
||||
@a A. */
|
||||
void ParallelAssemble(OperatorHandle &A, SparseMatrix *A_local);
|
||||
|
||||
/// Eliminate essential boundary trial DOFs from the parallel system matrix.
|
||||
/** The array @a bdr_attr_is_ess marks boundary attributes that constitute
|
||||
the essential part of the boundary. This method relies on
|
||||
ParallelEliminateTrialTDofs(const Array<int> &), see it for details. */
|
||||
void ParallelEliminateTrialEssentialBC(const Array<int> &bdr_attr_is_ess);
|
||||
|
||||
/// Eliminate essential trial true DOFs from the parallel system matrix.
|
||||
/** Given a list of essential trial true dofs, eliminate the trial true dofs
|
||||
from the parallel assembled system matrix, storing the eliminated part
|
||||
internally. This method works in conjunction with
|
||||
ParallelEliminateTrialTDofsInRHS() and allows elimination of boundary
|
||||
conditions in multiple right-hand sides. */
|
||||
void ParallelEliminateTrialTDofs(const Array<int> &trial_tdof_list);
|
||||
|
||||
/** @brief Use the stored eliminated part of the parallel system matrix for
|
||||
elimination of boundary conditions in the r.h.s. */
|
||||
/** Given a list of essential trial true dofs, eliminate the trial true dofs
|
||||
from the right-hand side @a B using the solution vector @a X and the
|
||||
previously stored eliminated part of the parallel assembled system
|
||||
matrix produced by ParallelEliminateTrialTDofs(const Array<int> &). */
|
||||
void ParallelEliminateTrialTDofsInRHS(const Array<int> &trial_tdof_list,
|
||||
const Vector &X, Vector &B);
|
||||
|
||||
/// Eliminate essential boundary test DOFs from the parallel system matrix.
|
||||
/** The array @a bdr_attr_is_ess marks boundary attributes that constitute
|
||||
the essential part of the boundary. */
|
||||
void ParallelEliminateTestEssentialBC(const Array<int> &bdr_attr_is_ess);
|
||||
|
||||
/// Eliminate essential test true DOFs from the parallel system matrix.
|
||||
/** Given a list of essential test true dofs, eliminate the test true dofs
|
||||
from the parallel assembled system matrix. */
|
||||
void ParallelEliminateTestTDofs(const Array<int> &test_tdof_list);
|
||||
|
||||
using MixedBilinearForm::FormRectangularSystemMatrix;
|
||||
using MixedBilinearForm::FormRectangularLinearSystem;
|
||||
|
||||
@@ -481,6 +481,7 @@ public:
|
||||
that the number of DOFs is @a ndofs. */
|
||||
const FiniteElement *GetFaceNbrFE(int i, int ndofs = 0) const;
|
||||
const FiniteElement *GetFaceNbrFaceFE(int i) const;
|
||||
const Array<HYPRE_BigInt> &GetFaceNbrGlobalDofMapArray() { return face_nbr_glob_dof_map; }
|
||||
const HYPRE_BigInt *GetFaceNbrGlobalDofMap() { return face_nbr_glob_dof_map; }
|
||||
ElementTransformation *GetFaceNbrElementTransformation(int i) const
|
||||
{ return pmesh->GetFaceNbrElementTransformation(i); }
|
||||
|
||||
+424
-44
@@ -105,6 +105,59 @@ const SparseMatrix &ParNonlinearForm::GetLocalGradient(const Vector &x) const
|
||||
return *Grad;
|
||||
}
|
||||
|
||||
void ParNonlinearForm::GradientSharedFaces(const Vector &x,
|
||||
int skip_zeros) const
|
||||
{
|
||||
ParFiniteElementSpace *pfes = ParFESpace();
|
||||
ParMesh *pmesh = pfes->GetParMesh();
|
||||
FaceElementTransformations *T;
|
||||
Array<int> vdofs1, vdofs2, vdofs_all;
|
||||
DenseMatrix elemmat;
|
||||
Vector el_x, nbr_x, face_x;
|
||||
const Vector &px = Prolongate(x);
|
||||
|
||||
ParGridFunction pgf(pfes, const_cast<Vector&>(px), 0);
|
||||
pgf.ExchangeFaceNbrData();
|
||||
|
||||
int nfaces = pmesh->GetNSharedFaces();
|
||||
for (int i = 0; i < nfaces; i++)
|
||||
{
|
||||
T = pmesh->GetSharedFaceTransformations(i);
|
||||
int Elem2NbrNo = T->Elem2No - pmesh->GetNE();
|
||||
|
||||
pfes->GetElementVDofs(T->Elem1No, vdofs1);
|
||||
pfes->GetFaceNbrElementVDofs(Elem2NbrNo, vdofs2);
|
||||
face_x.SetSize(vdofs1.Size() + vdofs2.Size());
|
||||
|
||||
el_x.MakeRef(face_x, 0, vdofs1.Size());
|
||||
pgf.GetSubVector(vdofs1, el_x);
|
||||
|
||||
nbr_x.MakeRef(face_x, vdofs1.Size(), vdofs2.Size());
|
||||
pgf.FaceNbrData().GetSubVector(vdofs2, nbr_x);
|
||||
|
||||
vdofs1.Copy(vdofs_all);
|
||||
for (int j = 0; j < vdofs2.Size(); j++)
|
||||
{
|
||||
if (vdofs2[j] >= 0)
|
||||
{
|
||||
vdofs2[j] += height;
|
||||
}
|
||||
else
|
||||
{
|
||||
vdofs2[j] -= height;
|
||||
}
|
||||
}
|
||||
vdofs_all.Append(vdofs2);
|
||||
for (int k = 0; k < fnfi.Size(); k++)
|
||||
{
|
||||
fnfi[k]->AssembleFaceGrad(*pfes->GetFE(T->Elem1No),
|
||||
*pfes->GetFaceNbrFE(Elem2NbrNo),
|
||||
*T, face_x, elemmat);
|
||||
Grad->AddSubMatrix(vdofs1, vdofs_all, elemmat, skip_zeros);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Operator &ParNonlinearForm::GetGradient(const Vector &x) const
|
||||
{
|
||||
if (NonlinearForm::ext) { return NonlinearForm::GetGradient(x); }
|
||||
@@ -112,19 +165,61 @@ Operator &ParNonlinearForm::GetGradient(const Vector &x) const
|
||||
ParFiniteElementSpace *pfes = ParFESpace();
|
||||
|
||||
pGrad.Clear();
|
||||
OperatorHandle dA(pGrad.Type()), Ph(pGrad.Type()), hdA;
|
||||
|
||||
NonlinearForm::GetGradient(x); // (re)assemble Grad, no b.c.
|
||||
|
||||
OperatorHandle dA(pGrad.Type()), Ph(pGrad.Type());
|
||||
|
||||
if (fnfi.Size() == 0)
|
||||
if (fnfi.Size())
|
||||
{
|
||||
dA.MakeSquareBlockDiag(pfes->GetComm(), pfes->GlobalVSize(),
|
||||
pfes->GetDofOffsets(), Grad);
|
||||
const int skip_zeros = 0;
|
||||
|
||||
pfes->ExchangeFaceNbrData();
|
||||
if (Grad == NULL)
|
||||
{
|
||||
int nbr_size = pfes->GetFaceNbrVSize();
|
||||
Grad = new SparseMatrix(pfes->GetVSize(), pfes->GetVSize() + nbr_size);
|
||||
}
|
||||
|
||||
NonlinearForm::GetGradient(x, false); // (re)assemble Grad, no b.c.
|
||||
|
||||
GradientSharedFaces(x, skip_zeros);
|
||||
|
||||
Grad->Finalize(skip_zeros);
|
||||
|
||||
// handle the case when 'a' contains off-diagonal
|
||||
int lvsize = pfes->GetVSize();
|
||||
const HYPRE_BigInt *face_nbr_glob_ldof = pfes->GetFaceNbrGlobalDofMap();
|
||||
HYPRE_BigInt ldof_offset = pfes->GetMyDofOffset();
|
||||
|
||||
Array<HYPRE_BigInt> glob_J(Grad->NumNonZeroElems());
|
||||
int *J = Grad->GetJ();
|
||||
for (int i = 0; i < glob_J.Size(); i++)
|
||||
{
|
||||
if (J[i] < lvsize)
|
||||
{
|
||||
glob_J[i] = J[i] + ldof_offset;
|
||||
}
|
||||
else
|
||||
{
|
||||
glob_J[i] = face_nbr_glob_ldof[J[i] - lvsize];
|
||||
}
|
||||
}
|
||||
|
||||
// TODO - construct dA directly in the A format
|
||||
hdA.Reset(
|
||||
new HypreParMatrix(pfes->GetComm(), lvsize, pfes->GlobalVSize(),
|
||||
pfes->GlobalVSize(), Grad->GetI(), glob_J,
|
||||
Grad->GetData(), pfes->GetDofOffsets(),
|
||||
pfes->GetDofOffsets()));
|
||||
// - hdA owns the new HypreParMatrix
|
||||
// - the above constructor copies all input arrays
|
||||
glob_J.DeleteAll();
|
||||
dA.ConvertFrom(hdA);
|
||||
}
|
||||
else
|
||||
{
|
||||
MFEM_ABORT("TODO: assemble contributions from shared face terms");
|
||||
NonlinearForm::GetGradient(x); // (re)assemble Grad, no b.c.
|
||||
|
||||
dA.MakeSquareBlockDiag(pfes->GetComm(), pfes->GlobalVSize(),
|
||||
pfes->GetDofOffsets(), Grad);
|
||||
}
|
||||
|
||||
// RAP the local gradient dA.
|
||||
@@ -199,9 +294,8 @@ const ParFiniteElementSpace *ParBlockNonlinearForm::ParFESpace(int k) const
|
||||
}
|
||||
|
||||
// Here, rhs is a true dof vector
|
||||
void ParBlockNonlinearForm::SetEssentialBC(const
|
||||
Array<Array<int> *>&bdr_attr_is_ess,
|
||||
Array<Vector *> &rhs)
|
||||
void ParBlockNonlinearForm::SetEssentialBC(
|
||||
const Array<Array<int>*> &bdr_attr_is_ess, Array<Vector*> &rhs)
|
||||
{
|
||||
Array<Vector *> nullarray(fes.Size());
|
||||
nullarray = NULL;
|
||||
@@ -217,6 +311,23 @@ void ParBlockNonlinearForm::SetEssentialBC(const
|
||||
}
|
||||
}
|
||||
|
||||
void ParBlockNonlinearForm::SetEssentialTrueDofs(
|
||||
const Array<Array<int>*> &ess_tdof_list, Array<Vector*> &rhs)
|
||||
{
|
||||
Array<Vector *> nullarray(fes.Size());
|
||||
nullarray = nullptr;
|
||||
|
||||
BlockNonlinearForm::SetEssentialTrueDofs(ess_tdof_list, nullarray);
|
||||
|
||||
for (int s = 0; s < fes.Size(); ++s)
|
||||
{
|
||||
if (rhs[s])
|
||||
{
|
||||
rhs[s]->SetSubVector(*ess_tdofs[s], 0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
real_t ParBlockNonlinearForm::GetEnergy(const Vector &x) const
|
||||
{
|
||||
// xs_true is not modified, so const_cast is okay
|
||||
@@ -255,7 +366,70 @@ void ParBlockNonlinearForm::Mult(const Vector &x, Vector &y) const
|
||||
|
||||
if (fnfi.Size() > 0)
|
||||
{
|
||||
MFEM_ABORT("TODO: assemble contributions from shared face terms");
|
||||
// Terms over shared interior faces in parallel.
|
||||
ParMesh *pmesh = ParFESpace(0)->GetParMesh();
|
||||
FaceElementTransformations *tr;
|
||||
|
||||
Array<Array<int> *>vdofs(fes.Size());
|
||||
Array<Array<int> *>vdofs2(fes.Size());
|
||||
Array<Vector *> el_x(fes.Size());
|
||||
Array<const Vector *> el_x_const(fes.Size());
|
||||
Array<Vector *> el_y(fes.Size());
|
||||
Array<const FiniteElement *> fe(fes.Size());
|
||||
Array<const FiniteElement *> fe2(fes.Size());
|
||||
Array<ParGridFunction *> pgfs(fes.Size());
|
||||
for (int s=0; s<fes.Size(); ++s)
|
||||
{
|
||||
el_x_const[s] = el_x[s] = new Vector();
|
||||
el_y[s] = new Vector();
|
||||
vdofs[s] = new Array<int>;
|
||||
vdofs2[s] = new Array<int>;
|
||||
pgfs[s] = new ParGridFunction(const_cast<ParFiniteElementSpace*>(ParFESpace(s)),
|
||||
xs.GetBlock(s));
|
||||
pgfs[s]->ExchangeFaceNbrData();
|
||||
}
|
||||
|
||||
const int n_shared_faces = pmesh->GetNSharedFaces();
|
||||
for (int i = 0; i < n_shared_faces; i++)
|
||||
{
|
||||
tr = pmesh->GetSharedFaceTransformations(i, true);
|
||||
int Elem2NbrNo = tr->Elem2No - pmesh->GetNE();
|
||||
|
||||
for (int s=0; s<fes.Size(); ++s)
|
||||
{
|
||||
const ParFiniteElementSpace *pfes = ParFESpace(s);
|
||||
fe[s] = pfes->GetFE(tr->Elem1No);
|
||||
fe2[s] = pfes->GetFaceNbrFE(Elem2NbrNo);
|
||||
|
||||
pfes->GetElementVDofs(tr->Elem1No, *(vdofs[s]));
|
||||
pfes->GetFaceNbrElementVDofs(Elem2NbrNo, *(vdofs2[s]));
|
||||
|
||||
el_x[s]->SetSize(vdofs[s]->Size() + vdofs2[s]->Size());
|
||||
xs.GetBlock(s).GetSubVector(*(vdofs[s]), el_x[s]->GetData());
|
||||
pgfs[s]->FaceNbrData().GetSubVector(*(vdofs2[s]),
|
||||
el_x[s]->GetData() + vdofs[s]->Size());
|
||||
}
|
||||
|
||||
for (int k = 0; k < fnfi.Size(); ++k)
|
||||
{
|
||||
fnfi[k]->AssembleFaceVector(fe, fe2, *tr, el_x_const, el_y);
|
||||
|
||||
for (int s=0; s<fes.Size(); ++s)
|
||||
{
|
||||
if (el_y[s]->Size() == 0) { continue; }
|
||||
ys.GetBlock(s).AddElementVector(*(vdofs[s]), *el_y[s]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int s=0; s<fes.Size(); ++s)
|
||||
{
|
||||
delete pgfs[s];
|
||||
delete vdofs2[s];
|
||||
delete vdofs[s];
|
||||
delete el_y[s];
|
||||
delete el_x[s];
|
||||
}
|
||||
}
|
||||
|
||||
for (int s=0; s<fes.Size(); ++s)
|
||||
@@ -312,6 +486,106 @@ void ParBlockNonlinearForm::SetGradientType(Operator::Type tid)
|
||||
}
|
||||
}
|
||||
|
||||
void ParBlockNonlinearForm::GradientSharedFaces(const BlockVector &xs,
|
||||
int skip_zeros) const
|
||||
{
|
||||
// Terms over shared interior faces in parallel.
|
||||
ParMesh *pmesh = ParFESpace(0)->GetParMesh();
|
||||
FaceElementTransformations *tr;
|
||||
|
||||
Array<Array<int> *>vdofs(fes.Size());
|
||||
Array<Array<int> *>vdofs2(fes.Size());
|
||||
Array<Array<int> *>vdofs_all(fes.Size());
|
||||
Array<Vector *> el_x(fes.Size());
|
||||
Array<const Vector *> el_x_const(fes.Size());
|
||||
Array2D<DenseMatrix *> elmats(fes.Size(), fes.Size());
|
||||
Array<const FiniteElement *> fe(fes.Size());
|
||||
Array<const FiniteElement *> fe2(fes.Size());
|
||||
Array<ParGridFunction *> pgfs(fes.Size());
|
||||
|
||||
for (int s1=0; s1<fes.Size(); ++s1)
|
||||
{
|
||||
el_x_const[s1] = el_x[s1] = new Vector();
|
||||
vdofs[s1] = new Array<int>;
|
||||
vdofs2[s1] = new Array<int>;
|
||||
vdofs_all[s1] = new Array<int>;
|
||||
pgfs[s1] = new ParGridFunction(
|
||||
const_cast<ParFiniteElementSpace*>(ParFESpace(s1)),
|
||||
const_cast<Vector&>(xs.GetBlock(s1)));
|
||||
pgfs[s1]->ExchangeFaceNbrData();
|
||||
for (int s2=0; s2<fes.Size(); ++s2)
|
||||
{
|
||||
elmats(s1,s2) = new DenseMatrix();
|
||||
}
|
||||
}
|
||||
|
||||
const int n_shared_faces = pmesh->GetNSharedFaces();
|
||||
for (int i = 0; i < n_shared_faces; i++)
|
||||
{
|
||||
tr = pmesh->GetSharedFaceTransformations(i, true);
|
||||
int Elem2NbrNo = tr->Elem2No - pmesh->GetNE();
|
||||
|
||||
for (int s=0; s<fes.Size(); ++s)
|
||||
{
|
||||
const ParFiniteElementSpace *pfes = ParFESpace(s);
|
||||
fe[s] = pfes->GetFE(tr->Elem1No);
|
||||
fe2[s] = pfes->GetFaceNbrFE(Elem2NbrNo);
|
||||
|
||||
pfes->GetElementVDofs(tr->Elem1No, *(vdofs[s]));
|
||||
pfes->GetFaceNbrElementVDofs(Elem2NbrNo, *(vdofs2[s]));
|
||||
|
||||
el_x[s]->SetSize(vdofs[s]->Size() + vdofs2[s]->Size());
|
||||
xs.GetBlock(s).GetSubVector(*(vdofs[s]), el_x[s]->GetData());
|
||||
pgfs[s]->FaceNbrData().GetSubVector(*(vdofs2[s]),
|
||||
el_x[s]->GetData() + vdofs[s]->Size());
|
||||
|
||||
vdofs[s]->Copy(*vdofs_all[s]);
|
||||
|
||||
const int lvsize = pfes->GetVSize();
|
||||
for (int j = 0; j < vdofs2[s]->Size(); j++)
|
||||
{
|
||||
if ((*vdofs2[s])[j] >= 0)
|
||||
{
|
||||
(*vdofs2[s])[j] += lvsize;
|
||||
}
|
||||
else
|
||||
{
|
||||
(*vdofs2[s])[j] -= lvsize;
|
||||
}
|
||||
}
|
||||
vdofs_all[s]->Append(*(vdofs2[s]));
|
||||
}
|
||||
|
||||
for (int k = 0; k < fnfi.Size(); ++k)
|
||||
{
|
||||
fnfi[k]->AssembleFaceGrad(fe, fe2, *tr, el_x_const, elmats);
|
||||
|
||||
for (int s1=0; s1<fes.Size(); ++s1)
|
||||
{
|
||||
for (int s2=0; s2<fes.Size(); ++s2)
|
||||
{
|
||||
if (elmats(s1,s2)->Height() == 0) { continue; }
|
||||
Grads(s1,s2)->AddSubMatrix(*vdofs[s1], *vdofs_all[s2],
|
||||
*elmats(s1,s2), skip_zeros);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int s1=0; s1<fes.Size(); ++s1)
|
||||
{
|
||||
delete pgfs[s1];
|
||||
delete vdofs_all[s1];
|
||||
delete vdofs2[s1];
|
||||
delete vdofs[s1];
|
||||
delete el_x[s1];
|
||||
for (int s2=0; s2<fes.Size(); ++s2)
|
||||
{
|
||||
delete elmats(s1,s2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BlockOperator & ParBlockNonlinearForm::GetGradient(const Vector &x) const
|
||||
{
|
||||
if (pBlockGrad == NULL)
|
||||
@@ -331,49 +605,155 @@ BlockOperator & ParBlockNonlinearForm::GetGradient(const Vector &x) const
|
||||
}
|
||||
}
|
||||
|
||||
GetLocalGradient(x); // gradients are stored in 'Grads'
|
||||
// xs_true is not modified, so const_cast is okay
|
||||
xs_true.Update(const_cast<Vector &>(x), block_trueOffsets);
|
||||
xs.Update(block_offsets);
|
||||
|
||||
for (int s=0; s<fes.Size(); ++s)
|
||||
{
|
||||
fes[s]->GetProlongationMatrix()->Mult(
|
||||
xs_true.GetBlock(s), xs.GetBlock(s));
|
||||
}
|
||||
|
||||
if (fnfi.Size() > 0)
|
||||
{
|
||||
MFEM_ABORT("TODO: assemble contributions from shared face terms");
|
||||
}
|
||||
const int skip_zeros = 0;
|
||||
|
||||
for (int s1=0; s1<fes.Size(); ++s1)
|
||||
{
|
||||
for (int s2=0; s2<fes.Size(); ++s2)
|
||||
for (int s=0; s<fes.Size(); ++s)
|
||||
{
|
||||
OperatorHandle dA(phBlockGrad(s1,s2)->Type()),
|
||||
Ph(phBlockGrad(s1,s2)->Type()),
|
||||
Rh(phBlockGrad(s1,s2)->Type());
|
||||
const_cast<ParFiniteElementSpace*>(pfes[s])->ExchangeFaceNbrData();
|
||||
}
|
||||
|
||||
if (s1 == s2)
|
||||
for (int s1=0; s1<fes.Size(); ++s1)
|
||||
{
|
||||
for (int s2=0; s2<fes.Size(); ++s2)
|
||||
{
|
||||
dA.MakeSquareBlockDiag(pfes[s1]->GetComm(), pfes[s1]->GlobalVSize(),
|
||||
pfes[s1]->GetDofOffsets(), Grads(s1,s1));
|
||||
Ph.ConvertFrom(pfes[s1]->Dof_TrueDof_Matrix());
|
||||
phBlockGrad(s1,s1)->MakePtAP(dA, Ph);
|
||||
|
||||
OperatorHandle Ae;
|
||||
Ae.EliminateRowsCols(*phBlockGrad(s1,s1), *ess_tdofs[s1]);
|
||||
if (Grads(s1,s2) == NULL)
|
||||
{
|
||||
int nbr_size = pfes[s2]->GetFaceNbrVSize();
|
||||
Grads(s1,s2) = new SparseMatrix(pfes[s1]->GetVSize(),
|
||||
pfes[s2]->GetVSize() + nbr_size);
|
||||
}
|
||||
}
|
||||
else
|
||||
}
|
||||
|
||||
// (re)assemble Grad without b.c. into 'Grads'
|
||||
BlockNonlinearForm::ComputeGradientBlocked(xs, false);
|
||||
|
||||
GradientSharedFaces(xs, skip_zeros);
|
||||
|
||||
// finalize the gradients
|
||||
for (int s1=0; s1<fes.Size(); ++s1)
|
||||
for (int s2=0; s2<fes.Size(); ++s2)
|
||||
{
|
||||
dA.MakeRectangularBlockDiag(pfes[s1]->GetComm(),
|
||||
pfes[s1]->GlobalVSize(),
|
||||
pfes[s2]->GlobalVSize(),
|
||||
pfes[s1]->GetDofOffsets(),
|
||||
pfes[s2]->GetDofOffsets(),
|
||||
Grads(s1,s2));
|
||||
Rh.ConvertFrom(pfes[s1]->Dof_TrueDof_Matrix());
|
||||
Ph.ConvertFrom(pfes[s2]->Dof_TrueDof_Matrix());
|
||||
|
||||
phBlockGrad(s1,s2)->MakeRAP(Rh, dA, Ph);
|
||||
|
||||
phBlockGrad(s1,s2)->EliminateRows(*ess_tdofs[s1]);
|
||||
phBlockGrad(s1,s2)->EliminateCols(*ess_tdofs[s2]);
|
||||
Grads(s1,s2)->Finalize(skip_zeros);
|
||||
}
|
||||
|
||||
pBlockGrad->SetBlock(s1, s2, phBlockGrad(s1,s2)->Ptr());
|
||||
for (int s1=0; s1<fes.Size(); ++s1)
|
||||
{
|
||||
for (int s2=0; s2<fes.Size(); ++s2)
|
||||
{
|
||||
OperatorHandle hdA;
|
||||
OperatorHandle dA(phBlockGrad(s1,s2)->Type()),
|
||||
Ph(phBlockGrad(s1,s2)->Type()),
|
||||
Rh(phBlockGrad(s1,s2)->Type());
|
||||
|
||||
// handle the case when 'a' contains off-diagonal
|
||||
int lvsize = pfes[s2]->GetVSize();
|
||||
const HYPRE_BigInt *face_nbr_glob_ldof =
|
||||
const_cast<ParFiniteElementSpace*>(pfes[s2])->GetFaceNbrGlobalDofMap();
|
||||
HYPRE_BigInt ldof_offset = pfes[s2]->GetMyDofOffset();
|
||||
|
||||
Array<HYPRE_BigInt> glob_J(Grads(s1,s2)->NumNonZeroElems());
|
||||
int *J = Grads(s1,s2)->GetJ();
|
||||
for (int i = 0; i < glob_J.Size(); i++)
|
||||
{
|
||||
if (J[i] < lvsize)
|
||||
{
|
||||
glob_J[i] = J[i] + ldof_offset;
|
||||
}
|
||||
else
|
||||
{
|
||||
glob_J[i] = face_nbr_glob_ldof[J[i] - lvsize];
|
||||
}
|
||||
}
|
||||
|
||||
// TODO - construct dA directly in the A format
|
||||
hdA.Reset(
|
||||
new HypreParMatrix(pfes[s2]->GetComm(), pfes[s1]->GetVSize(),
|
||||
pfes[s1]->GlobalVSize(), pfes[s2]->GlobalVSize(),
|
||||
Grads(s1,s2)->GetI(), glob_J, Grads(s1,s2)->GetData(),
|
||||
pfes[s1]->GetDofOffsets(), pfes[s2]->GetDofOffsets()));
|
||||
// - hdA owns the new HypreParMatrix
|
||||
// - the above constructor copies all input arrays
|
||||
glob_J.DeleteAll();
|
||||
dA.ConvertFrom(hdA);
|
||||
|
||||
if (s1 == s2)
|
||||
{
|
||||
Ph.ConvertFrom(pfes[s1]->Dof_TrueDof_Matrix());
|
||||
phBlockGrad(s1,s1)->MakePtAP(dA, Ph);
|
||||
|
||||
OperatorHandle Ae;
|
||||
Ae.EliminateRowsCols(*phBlockGrad(s1,s1), *ess_tdofs[s1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
Rh.ConvertFrom(pfes[s1]->Dof_TrueDof_Matrix());
|
||||
Ph.ConvertFrom(pfes[s2]->Dof_TrueDof_Matrix());
|
||||
|
||||
phBlockGrad(s1,s2)->MakeRAP(Rh, dA, Ph);
|
||||
|
||||
phBlockGrad(s1,s2)->EliminateRows(*ess_tdofs[s1]);
|
||||
phBlockGrad(s1,s2)->EliminateCols(*ess_tdofs[s2]);
|
||||
}
|
||||
|
||||
pBlockGrad->SetBlock(s1, s2, phBlockGrad(s1,s2)->Ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// (re)assemble Grad without b.c. into 'Grads'
|
||||
BlockNonlinearForm::ComputeGradientBlocked(xs);
|
||||
|
||||
for (int s1=0; s1<fes.Size(); ++s1)
|
||||
{
|
||||
for (int s2=0; s2<fes.Size(); ++s2)
|
||||
{
|
||||
OperatorHandle dA(phBlockGrad(s1,s2)->Type()),
|
||||
Ph(phBlockGrad(s1,s2)->Type()),
|
||||
Rh(phBlockGrad(s1,s2)->Type());
|
||||
|
||||
if (s1 == s2)
|
||||
{
|
||||
dA.MakeSquareBlockDiag(pfes[s1]->GetComm(), pfes[s1]->GlobalVSize(),
|
||||
pfes[s1]->GetDofOffsets(), Grads(s1,s1));
|
||||
Ph.ConvertFrom(pfes[s1]->Dof_TrueDof_Matrix());
|
||||
phBlockGrad(s1,s1)->MakePtAP(dA, Ph);
|
||||
|
||||
OperatorHandle Ae;
|
||||
Ae.EliminateRowsCols(*phBlockGrad(s1,s1), *ess_tdofs[s1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
dA.MakeRectangularBlockDiag(pfes[s1]->GetComm(),
|
||||
pfes[s1]->GlobalVSize(),
|
||||
pfes[s2]->GlobalVSize(),
|
||||
pfes[s1]->GetDofOffsets(),
|
||||
pfes[s2]->GetDofOffsets(),
|
||||
Grads(s1,s2));
|
||||
Rh.ConvertFrom(pfes[s1]->Dof_TrueDof_Matrix());
|
||||
Ph.ConvertFrom(pfes[s2]->Dof_TrueDof_Matrix());
|
||||
|
||||
phBlockGrad(s1,s2)->MakeRAP(Rh, dA, Ph);
|
||||
|
||||
phBlockGrad(s1,s2)->EliminateRows(*ess_tdofs[s1]);
|
||||
phBlockGrad(s1,s2)->EliminateCols(*ess_tdofs[s2]);
|
||||
}
|
||||
|
||||
pBlockGrad->SetBlock(s1, s2, phBlockGrad(s1,s2)->Ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+37
-3
@@ -29,6 +29,8 @@ protected:
|
||||
mutable ParGridFunction X, Y;
|
||||
mutable OperatorHandle pGrad;
|
||||
|
||||
void GradientSharedFaces(const Vector &x, int skip_zeros = 1) const;
|
||||
|
||||
public:
|
||||
ParNonlinearForm(ParFiniteElementSpace *pf);
|
||||
|
||||
@@ -81,6 +83,8 @@ protected:
|
||||
mutable Array2D<OperatorHandle *> phBlockGrad;
|
||||
mutable BlockOperator *pBlockGrad;
|
||||
|
||||
void GradientSharedFaces(const BlockVector &xs, int skip_zeros) const;
|
||||
|
||||
public:
|
||||
/// Computes the energy of the system
|
||||
real_t GetEnergy(const Vector &x) const override;
|
||||
@@ -102,9 +106,39 @@ public:
|
||||
gradient-type (if different from the default) must be set again. */
|
||||
void SetParSpaces(Array<ParFiniteElementSpace *> &pf);
|
||||
|
||||
// Here, rhs is a true dof vector
|
||||
void SetEssentialBC(const Array<Array<int> *>&bdr_attr_is_ess,
|
||||
Array<Vector *> &rhs) override;
|
||||
/** @brief Set essential boundary conditions to each finite element space
|
||||
using boundary attribute markers.
|
||||
|
||||
This method calls `FiniteElementSpace::GetEssentialTrueDofs()` for each
|
||||
space and stores ess_tdof_lists internally.
|
||||
|
||||
If `rhs` vectors are non-null, the entries corresponding to these
|
||||
essential DoFs are set to zero. This ensures compatibility with the
|
||||
output of the `Mult()` method, which also zeroes out these entries.
|
||||
|
||||
@param[in] bdr_attr_is_ess A list of boundary attribute markers for each
|
||||
space.
|
||||
@param[in,out] rhs An array of optional right-hand side vectors.
|
||||
If a vector at `rhs[i]` is non-null, its essential DoFs will be set
|
||||
to zero. */
|
||||
virtual void SetEssentialBC(const Array<Array<int>*> &bdr_attr_is_ess,
|
||||
Array<Vector*> &rhs) override;
|
||||
|
||||
/** @brief Set essential boundary conditions to each finite element space
|
||||
using essential true dof lists.
|
||||
|
||||
This method stores a copy of the provided essential true dof lists.
|
||||
|
||||
If `rhs` vectors are non-null, the entries corresponding to these
|
||||
essential DoFs are set to zero. This ensures compatibility with the
|
||||
output of the `Mult()` method, which also zeroes out these entries.
|
||||
|
||||
@param[in] ess_tdof_list A list of essential true dofs for each space.
|
||||
@param[in,out] rhs An array of optional right-hand side vectors.
|
||||
If a vector at `rhs[i]` is non-null, its essential DoFs will be set
|
||||
to zero. */
|
||||
virtual void SetEssentialTrueDofs(const Array<Array<int>*> &ess_tdof_list,
|
||||
Array<Vector*> &rhs) override;
|
||||
|
||||
/// Block T-Vector to Block T-Vector
|
||||
void Mult(const Vector &x, Vector &y) const override;
|
||||
|
||||
@@ -1,214 +0,0 @@
|
||||
// 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.
|
||||
|
||||
|
||||
// Abstract array data type
|
||||
|
||||
#include "array.hpp"
|
||||
#include "../general/forall.hpp"
|
||||
#include <fstream>
|
||||
#include <type_traits>
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
|
||||
template <class T>
|
||||
void Array<T>::Print(std::ostream &os, int width) const
|
||||
{
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
os << data[i];
|
||||
if ( !((i+1) % width) || i+1 == size )
|
||||
{
|
||||
os << '\n';
|
||||
}
|
||||
else
|
||||
{
|
||||
os << " ";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void Array<T>::Save(std::ostream &os, int fmt) const
|
||||
{
|
||||
if (fmt == 0)
|
||||
{
|
||||
os << size << '\n';
|
||||
}
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
os << operator[](i) << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void Array<T>::Load(std::istream &in, int fmt)
|
||||
{
|
||||
if (fmt == 0)
|
||||
{
|
||||
int new_size;
|
||||
in >> new_size;
|
||||
SetSize(new_size);
|
||||
}
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
in >> operator[](i);
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
T Array<T>::Max() const
|
||||
{
|
||||
MFEM_ASSERT(size > 0, "Array is empty with size " << size);
|
||||
|
||||
T max = operator[](0);
|
||||
for (int i = 1; i < size; i++)
|
||||
{
|
||||
if (max < operator[](i))
|
||||
{
|
||||
max = operator[](i);
|
||||
}
|
||||
}
|
||||
|
||||
return max;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
T Array<T>::Min() const
|
||||
{
|
||||
MFEM_ASSERT(size > 0, "Array is empty with size " << size);
|
||||
|
||||
T min = operator[](0);
|
||||
for (int i = 1; i < size; i++)
|
||||
{
|
||||
if (operator[](i) < min)
|
||||
{
|
||||
min = operator[](i);
|
||||
}
|
||||
}
|
||||
|
||||
return min;
|
||||
}
|
||||
|
||||
// Partial Sum
|
||||
template <class T>
|
||||
void Array<T>::PartialSum()
|
||||
{
|
||||
T sum = static_cast<T>(0);
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
sum+=operator[](i);
|
||||
operator[](i) = sum;
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void Array<T>::Abs()
|
||||
{
|
||||
static_assert(std::is_arithmetic<T>::value, "Use with arithmetic types!");
|
||||
const bool useDevice = UseDevice();
|
||||
const int N = size;
|
||||
auto y = ReadWrite(useDevice);
|
||||
mfem::forall_switch(useDevice, N, [=] MFEM_HOST_DEVICE (int i)
|
||||
{
|
||||
y[i] = std::abs(y[i]);
|
||||
});
|
||||
}
|
||||
|
||||
// Sum
|
||||
template <class T>
|
||||
T Array<T>::Sum() const
|
||||
{
|
||||
T sum = static_cast<T>(0);
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
sum+=operator[](i);
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
int Array<T>::IsSorted() const
|
||||
{
|
||||
T val_prev = operator[](0), val;
|
||||
for (int i = 1; i < size; i++)
|
||||
{
|
||||
val=operator[](i);
|
||||
if (val < val_prev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
val_prev = val;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
bool Array<T>::IsConstant() const
|
||||
{
|
||||
if (size < 2) { return true; }
|
||||
const T v0 = data[0];
|
||||
for (int i = 1; i < size; i++)
|
||||
{
|
||||
if (data[i] != v0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void Array2D<T>::Load(const char *filename, int fmt)
|
||||
{
|
||||
std::ifstream in;
|
||||
in.open(filename, std::ifstream::in);
|
||||
MFEM_VERIFY(in.is_open(), "File " << filename << " does not exist.");
|
||||
Load(in, fmt);
|
||||
in.close();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void Array2D<T>::Print(std::ostream &os, int width_)
|
||||
{
|
||||
int height = this->NumRows();
|
||||
int width = this->NumCols();
|
||||
|
||||
for (int i = 0; i < height; i++)
|
||||
{
|
||||
os << "[row " << i << "]\n";
|
||||
for (int j = 0; j < width; j++)
|
||||
{
|
||||
os << (*this)(i,j);
|
||||
if ( (j+1) == width_ || (j+1) % width_ == 0 )
|
||||
{
|
||||
os << '\n';
|
||||
}
|
||||
else
|
||||
{
|
||||
os << ' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template class Array<char>;
|
||||
template class Array<int>;
|
||||
template class Array<long long>;
|
||||
template class Array<real_t>;
|
||||
template class Array2D<int>;
|
||||
template class Array2D<real_t>;
|
||||
|
||||
} // namespace mfem
|
||||
+213
-15
@@ -16,9 +16,13 @@
|
||||
#include "mem_manager.hpp"
|
||||
#include "device.hpp"
|
||||
#include "error.hpp"
|
||||
#include "forall.hpp"
|
||||
#include "globals.hpp"
|
||||
#include "reducers.hpp"
|
||||
#include "scan.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
@@ -135,6 +139,8 @@ public:
|
||||
/// Return the device flag of the Memory object used by the Array
|
||||
bool UseDevice() const { return data.UseDevice(); }
|
||||
|
||||
void UseDevice(bool use_dev) { data.UseDevice(use_dev); }
|
||||
|
||||
/// Return true if the data will be deleted by the Array
|
||||
inline bool OwnsData() const { return data.OwnsHostPtr(); }
|
||||
|
||||
@@ -275,11 +281,11 @@ public:
|
||||
|
||||
/** @brief Find the maximal element in the array, using the comparison
|
||||
operator `<` for class T. */
|
||||
T Max() const;
|
||||
inline T Max() const;
|
||||
|
||||
/** @brief Find the minimal element in the array, using the comparison
|
||||
operator `<` for class T. */
|
||||
T Min() const;
|
||||
inline T Min() const;
|
||||
|
||||
/// Sorts the array in ascending order. This requires operator< to be defined for T.
|
||||
void Sort() { std::sort((T*)data, data + size); }
|
||||
@@ -297,22 +303,22 @@ public:
|
||||
}
|
||||
|
||||
/// Return 1 if the array is sorted from lowest to highest. Otherwise return 0.
|
||||
int IsSorted() const;
|
||||
inline int IsSorted() const;
|
||||
|
||||
/// Does the Array have Size zero.
|
||||
bool IsEmpty() const { return Size() == 0; }
|
||||
|
||||
/// Return true if all entries of the array are the same.
|
||||
bool IsConstant() const;
|
||||
inline bool IsConstant() const;
|
||||
|
||||
/// Fill the entries of the array with the cumulative sum of the entries.
|
||||
void PartialSum();
|
||||
inline void PartialSum();
|
||||
|
||||
/// Replace each entry of the array with its absolute value.
|
||||
void Abs();
|
||||
inline void Abs();
|
||||
|
||||
/// Return the sum of all the array entries using the '+'' operator for class 'T'.
|
||||
T Sum() const;
|
||||
inline T Sum() const;
|
||||
|
||||
/// Set all entries of the array to the provided constant.
|
||||
inline void operator=(const T &a);
|
||||
@@ -797,8 +803,14 @@ template <typename T> template <typename CT>
|
||||
inline Array<T> &Array<T>::operator=(const Array<CT> &src)
|
||||
{
|
||||
SetSize(src.Size());
|
||||
for (int i = 0; i < size; i++) { (*this)[i] = T(src[i]); }
|
||||
return *this;
|
||||
|
||||
const bool use_dev = UseDevice() || src.UseDevice();
|
||||
const auto x = src.Read(use_dev);
|
||||
auto y = Write(use_dev);
|
||||
mfem::forall_switch(use_dev, size, [=] MFEM_HOST_DEVICE (int i)
|
||||
{
|
||||
y[i] = x[i];
|
||||
});
|
||||
}
|
||||
|
||||
template <class T>
|
||||
@@ -1014,19 +1026,24 @@ template <class T>
|
||||
inline void Array<T>::GetSubArray(int offset, int sa_size, Array<T> &sa) const
|
||||
{
|
||||
sa.SetSize(sa_size);
|
||||
for (int i = 0; i < sa_size; i++)
|
||||
const bool use_dev = UseDevice() || sa.UseDevice();
|
||||
const auto x = Read(use_dev);
|
||||
auto y = sa.Write(use_dev);
|
||||
mfem::forall_switch(use_dev, sa_size, [=] MFEM_HOST_DEVICE (int i)
|
||||
{
|
||||
sa[i] = (*this)[offset+i];
|
||||
}
|
||||
y[i] = x[offset + i];
|
||||
});
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline void Array<T>::operator=(const T &a)
|
||||
{
|
||||
for (int i = 0; i < size; i++)
|
||||
const bool use_dev = UseDevice();
|
||||
auto x = Write(use_dev);
|
||||
mfem::forall_switch(use_dev, size, [=] MFEM_HOST_DEVICE (int i)
|
||||
{
|
||||
data[i] = a;
|
||||
}
|
||||
x[i] = a;
|
||||
});
|
||||
}
|
||||
|
||||
template <class T>
|
||||
@@ -1035,6 +1052,153 @@ inline void Array<T>::Assign(const T *p)
|
||||
data.CopyFromHost(p, Size());
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline void Array<T>::Print(std::ostream &os, int width) const
|
||||
{
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
os << data[i];
|
||||
if ( !((i+1) % width) || i+1 == size )
|
||||
{
|
||||
os << '\n';
|
||||
}
|
||||
else
|
||||
{
|
||||
os << " ";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline void Array<T>::Save(std::ostream &os, int fmt) const
|
||||
{
|
||||
if (fmt == 0)
|
||||
{
|
||||
os << size << '\n';
|
||||
}
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
os << operator[](i) << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void Array<T>::Load(std::istream &in, int fmt)
|
||||
{
|
||||
if (fmt == 0)
|
||||
{
|
||||
int new_size;
|
||||
in >> new_size;
|
||||
SetSize(new_size);
|
||||
}
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
in >> operator[](i);
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline T Array<T>::Max() const
|
||||
{
|
||||
MFEM_ASSERT(size > 0, "Array is empty with size " << size);
|
||||
|
||||
T max = operator[](0);
|
||||
for (int i = 1; i < size; i++)
|
||||
{
|
||||
if (max < operator[](i))
|
||||
{
|
||||
max = operator[](i);
|
||||
}
|
||||
}
|
||||
|
||||
return max;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline T Array<T>::Min() const
|
||||
{
|
||||
MFEM_ASSERT(size > 0, "Array is empty with size " << size);
|
||||
|
||||
T min = operator[](0);
|
||||
for (int i = 1; i < size; i++)
|
||||
{
|
||||
if (operator[](i) < min)
|
||||
{
|
||||
min = operator[](i);
|
||||
}
|
||||
}
|
||||
|
||||
return min;
|
||||
}
|
||||
|
||||
// Partial Sum
|
||||
template <class T>
|
||||
inline void Array<T>::PartialSum()
|
||||
{
|
||||
auto data_ptr = ReadWrite(UseDevice());
|
||||
InclusiveScan(UseDevice(), data_ptr, data_ptr, size);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline void Array<T>::Abs()
|
||||
{
|
||||
static_assert(std::is_arithmetic<T>::value, "Use with arithmetic types!");
|
||||
const bool useDevice = UseDevice();
|
||||
const int N = size;
|
||||
auto y = ReadWrite(useDevice);
|
||||
mfem::forall_switch(useDevice, N, [=] MFEM_HOST_DEVICE (int i)
|
||||
{
|
||||
y[i] = std::abs(y[i]);
|
||||
});
|
||||
}
|
||||
|
||||
// Sum
|
||||
template <class T>
|
||||
inline T Array<T>::Sum() const
|
||||
{
|
||||
T sum = static_cast<T>(0);
|
||||
if (size > 0)
|
||||
{
|
||||
const auto m_data = Read(UseDevice());
|
||||
reduce(size, sum, [=] MFEM_HOST_DEVICE(int i, T &r) { r += m_data[i]; },
|
||||
/* */ SumReducer<T> {}, UseDevice());
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline int Array<T>::IsSorted() const
|
||||
{
|
||||
T val_prev = operator[](0), val;
|
||||
for (int i = 1; i < size; i++)
|
||||
{
|
||||
val=operator[](i);
|
||||
if (val < val_prev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
val_prev = val;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline bool Array<T>::IsConstant() const
|
||||
{
|
||||
if (size < 2) { return true; }
|
||||
const T v0 = data[0];
|
||||
for (int i = 1; i < size; i++)
|
||||
{
|
||||
if (data[i] != v0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
template <class T>
|
||||
inline const T &Array2D<T>::operator()(int i, int j) const
|
||||
@@ -1074,6 +1238,40 @@ inline T *Array2D<T>::operator[](int i)
|
||||
return &array1d[i*N];
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void Array2D<T>::Load(const char *filename, int fmt)
|
||||
{
|
||||
std::ifstream in;
|
||||
in.open(filename, std::ifstream::in);
|
||||
MFEM_VERIFY(in.is_open(), "File " << filename << " does not exist.");
|
||||
Load(in, fmt);
|
||||
in.close();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void Array2D<T>::Print(std::ostream &os, int width_)
|
||||
{
|
||||
int height = this->NumRows();
|
||||
int width = this->NumCols();
|
||||
|
||||
for (int i = 0; i < height; i++)
|
||||
{
|
||||
os << "[row " << i << "]\n";
|
||||
for (int j = 0; j < width; j++)
|
||||
{
|
||||
os << (*this)(i,j);
|
||||
if ( (j+1) == width_ || (j+1) % width_ == 0 )
|
||||
{
|
||||
os << '\n';
|
||||
}
|
||||
else
|
||||
{
|
||||
os << ' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template <class T>
|
||||
inline void Swap(Array2D<T> &a, Array2D<T> &b)
|
||||
|
||||
+29
-10
@@ -12,7 +12,6 @@
|
||||
#ifndef MFEM_REDUCERS_HPP
|
||||
#define MFEM_REDUCERS_HPP
|
||||
|
||||
#include "array.hpp"
|
||||
#include "forall.hpp"
|
||||
|
||||
#include <cmath>
|
||||
@@ -514,6 +513,33 @@ template<class B, class R> struct reduction_kernel
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class ReductionWorkspace
|
||||
{
|
||||
Memory<T> workspace;
|
||||
|
||||
static ReductionWorkspace &Instance()
|
||||
{
|
||||
static ReductionWorkspace instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
~ReductionWorkspace() { workspace.Delete(); }
|
||||
|
||||
public:
|
||||
static T *Get(int num_blocks)
|
||||
{
|
||||
ReductionWorkspace &instance = Instance();
|
||||
if (instance.workspace.Capacity() < num_blocks)
|
||||
{
|
||||
instance.workspace.Delete();
|
||||
instance.workspace.New(num_blocks, MemoryType::HOST_PINNED);
|
||||
}
|
||||
return instance.workspace;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -529,8 +555,7 @@ template<class B, class R> struct reduction_kernel
|
||||
@tparam T value_type to operate on
|
||||
*/
|
||||
template <class T, class B, class R>
|
||||
void reduce(int N, T &res, B &&body, const R &reducer, bool use_dev,
|
||||
Array<T> &workspace)
|
||||
void reduce(int N, T &res, B &&body, const R &reducer, bool use_dev)
|
||||
{
|
||||
if (N == 0)
|
||||
{
|
||||
@@ -567,13 +592,7 @@ void reduce(int N, T &res, B &&body, const R &reducer, bool use_dev,
|
||||
|
||||
red_type red{nullptr, std::forward<B>(body), reducer, N, items_per_thread};
|
||||
// allocate res to fit block_size entries
|
||||
auto mt = workspace.GetMemory().GetMemoryType();
|
||||
if (mt != MemoryType::HOST_PINNED && mt != MemoryType::MANAGED)
|
||||
{
|
||||
mt = MemoryType::HOST_PINNED;
|
||||
}
|
||||
workspace.SetSize(nblocks, mt);
|
||||
auto work = workspace.HostWrite();
|
||||
auto work = internal::ReductionWorkspace<T>::Get(nblocks);
|
||||
red.work = work;
|
||||
forall_2D(nblocks, block_size, 1, std::move(red));
|
||||
// wait for results
|
||||
|
||||
+52
-22
@@ -28,8 +28,37 @@
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
/// Equivalent to InclusiveScan(use_dev, d_in, d_out, num_items, workspace,
|
||||
/// std::plus<>{})
|
||||
|
||||
namespace internal
|
||||
{
|
||||
class ScanWorkspace
|
||||
{
|
||||
Memory<std::byte> workspace;
|
||||
static ScanWorkspace &Instance()
|
||||
{
|
||||
static ScanWorkspace instance;
|
||||
return instance;
|
||||
}
|
||||
~ScanWorkspace() { workspace.Delete(); }
|
||||
public:
|
||||
static std::byte *Get(int num_bytes)
|
||||
{
|
||||
ScanWorkspace &instance = Instance();
|
||||
if (Size() < num_bytes)
|
||||
{
|
||||
instance.workspace.Delete();
|
||||
instance.workspace.New(num_bytes);
|
||||
}
|
||||
return instance.workspace.Write(MemoryClass::DEVICE, Size());
|
||||
}
|
||||
static int Size()
|
||||
{
|
||||
return Instance().workspace.Capacity();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Equivalent to InclusiveScan(use_dev, d_in, d_out, num_items, std::plus<>{})
|
||||
template <class InputIt, class OutputIt>
|
||||
void InclusiveScan(bool use_dev, InputIt d_in, OutputIt d_out, size_t num_items)
|
||||
{
|
||||
@@ -37,12 +66,12 @@ void InclusiveScan(bool use_dev, InputIt d_in, OutputIt d_out, size_t num_items)
|
||||
#if defined(MFEM_USE_CUDA) || defined(MFEM_USE_HIP)
|
||||
if (use_dev && mfem::Device::Allows(Backend::CUDA_MASK | Backend::HIP_MASK))
|
||||
{
|
||||
static Array<std::byte> workspace;
|
||||
size_t bytes = workspace.Size();
|
||||
if (bytes)
|
||||
using internal::ScanWorkspace;
|
||||
size_t bytes = ScanWorkspace::Size();
|
||||
if (bytes > 0)
|
||||
{
|
||||
auto err = MFEM_CUB_NAMESPACE::DeviceScan::InclusiveSum(
|
||||
workspace.Write(), bytes, d_in, d_out, num_items);
|
||||
ScanWorkspace::Get(bytes), bytes, d_in, d_out, num_items);
|
||||
#if defined(MFEM_USE_CUDA)
|
||||
if (err == cudaSuccess)
|
||||
{
|
||||
@@ -57,11 +86,12 @@ void InclusiveScan(bool use_dev, InputIt d_in, OutputIt d_out, size_t num_items)
|
||||
}
|
||||
// try allocating a larger buffer
|
||||
bytes = 0;
|
||||
// get size of buffer
|
||||
MFEM_GPU_CHECK(MFEM_CUB_NAMESPACE::DeviceScan::InclusiveSum(
|
||||
nullptr, bytes, d_in, d_out, num_items));
|
||||
workspace.SetSize(bytes);
|
||||
// resize buffer (in ScanWorkspace::Get) and try again
|
||||
MFEM_GPU_CHECK(MFEM_CUB_NAMESPACE::DeviceScan::InclusiveSum(
|
||||
workspace.Write(), bytes, d_in, d_out, num_items));
|
||||
ScanWorkspace::Get(bytes), bytes, d_in, d_out, num_items));
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
@@ -101,12 +131,13 @@ void InclusiveScan(bool use_dev, InputIt d_in, OutputIt d_out, size_t num_items,
|
||||
#if defined(MFEM_USE_CUDA) || defined(MFEM_USE_HIP)
|
||||
if (use_dev && mfem::Device::Allows(Backend::CUDA_MASK | Backend::HIP_MASK))
|
||||
{
|
||||
static Array<std::byte> workspace;
|
||||
size_t bytes = workspace.Size();
|
||||
if (bytes)
|
||||
using internal::ScanWorkspace;
|
||||
size_t bytes = ScanWorkspace::Size();
|
||||
if (bytes > 0)
|
||||
{
|
||||
auto err = MFEM_CUB_NAMESPACE::DeviceScan::InclusiveScan(
|
||||
workspace.Write(), bytes, d_in, d_out, scan_op, num_items);
|
||||
ScanWorkspace::Get(bytes), bytes, d_in, d_out, scan_op,
|
||||
num_items);
|
||||
#if defined(MFEM_USE_CUDA)
|
||||
if (err == cudaSuccess)
|
||||
{
|
||||
@@ -123,9 +154,9 @@ void InclusiveScan(bool use_dev, InputIt d_in, OutputIt d_out, size_t num_items,
|
||||
bytes = 0;
|
||||
MFEM_GPU_CHECK(MFEM_CUB_NAMESPACE::DeviceScan::InclusiveScan(
|
||||
nullptr, bytes, d_in, d_out, scan_op, num_items));
|
||||
workspace.SetSize(bytes);
|
||||
MFEM_GPU_CHECK(MFEM_CUB_NAMESPACE::DeviceScan::InclusiveScan(
|
||||
workspace.Write(), bytes, d_in, d_out, scan_op, num_items));
|
||||
ScanWorkspace::Get(bytes), bytes, d_in, d_out, scan_op,
|
||||
num_items));
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
@@ -164,13 +195,13 @@ void ExclusiveScan(bool use_dev, InputIt d_in, OutputIt d_out, size_t num_items,
|
||||
#if defined(MFEM_USE_CUDA) || defined(MFEM_USE_HIP)
|
||||
if (use_dev && mfem::Device::Allows(Backend::CUDA_MASK | Backend::HIP_MASK))
|
||||
{
|
||||
static Array<std::byte> workspace;
|
||||
size_t bytes = workspace.Size();
|
||||
using internal::ScanWorkspace;
|
||||
size_t bytes = ScanWorkspace::Size();
|
||||
if (bytes)
|
||||
{
|
||||
auto err = MFEM_CUB_NAMESPACE::DeviceScan::ExclusiveScan(
|
||||
workspace.Write(), bytes, d_in, d_out, scan_op, init_value,
|
||||
num_items);
|
||||
ScanWorkspace::Get(bytes), bytes, d_in, d_out, scan_op,
|
||||
init_value, num_items);
|
||||
#if defined(MFEM_USE_CUDA)
|
||||
if (err == cudaSuccess)
|
||||
{
|
||||
@@ -187,10 +218,9 @@ void ExclusiveScan(bool use_dev, InputIt d_in, OutputIt d_out, size_t num_items,
|
||||
bytes = 0;
|
||||
MFEM_GPU_CHECK(MFEM_CUB_NAMESPACE::DeviceScan::ExclusiveScan(
|
||||
nullptr, bytes, d_in, d_out, scan_op, init_value, num_items));
|
||||
workspace.SetSize(bytes);
|
||||
MFEM_GPU_CHECK(MFEM_CUB_NAMESPACE::DeviceScan::ExclusiveScan(
|
||||
workspace.Write(), bytes, d_in, d_out, scan_op, init_value,
|
||||
num_items));
|
||||
ScanWorkspace::Get(bytes), bytes, d_in, d_out, scan_op,
|
||||
init_value, num_items));
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
@@ -213,7 +243,7 @@ void ExclusiveScan(bool use_dev, InputIt d_in, OutputIt d_out, size_t num_items,
|
||||
}
|
||||
|
||||
/// Equivalent to ExclusiveScan(use_dev, d_in, d_out, num_items, init_value,
|
||||
/// workspace, std::plus<>{})
|
||||
/// std::plus<>{})
|
||||
template <class InputIt, class OutputIt, class T>
|
||||
void ExclusiveScan(bool use_dev, InputIt d_in, OutputIt d_out, size_t num_items,
|
||||
T init_value)
|
||||
|
||||
@@ -167,7 +167,7 @@ void MagmaBatchedLinAlg::Invert(DenseTensor &A) const
|
||||
magma_int_t status;
|
||||
|
||||
status = MFEM_MAGMA_PREFIX(getrf_batched)(
|
||||
n, n, d_A_ptrs, n, d_P_ptrs, info_array.Write(), n_mat,
|
||||
n, n, d_LU_ptrs, n, d_P_ptrs, info_array.Write(), n_mat,
|
||||
Magma::Queue());
|
||||
MFEM_VERIFY(status == MAGMA_SUCCESS, "");
|
||||
|
||||
|
||||
+13
-11
@@ -561,7 +561,8 @@ void CopyMemory(Memory<T> &src, Memory<T> &dst, MemoryClass dst_mc,
|
||||
this function. In particular, @a dst should be empty or deleted before
|
||||
calling this function. */
|
||||
template <typename SrcT, typename DstT>
|
||||
void CopyConvertMemory(Memory<SrcT> &src, MemoryClass dst_mc, Memory<DstT> &dst)
|
||||
void CopyConvertMemory(const Memory<SrcT> &src, MemoryClass dst_mc,
|
||||
Memory<DstT> &dst)
|
||||
{
|
||||
auto capacity = src.Capacity();
|
||||
dst.New(capacity, GetMemoryType(dst_mc));
|
||||
@@ -842,8 +843,8 @@ static int GetPartitioningArraySize(MPI_Comm comm)
|
||||
///
|
||||
/// Both @a row and @a col are partitioning arrays, whose length is returned by
|
||||
/// GetPartitioningArraySize(), see @ref hypre_partitioning_descr.
|
||||
static bool RowAndColStartsAreEqual(MPI_Comm comm, HYPRE_BigInt *rows,
|
||||
HYPRE_BigInt *cols)
|
||||
static bool RowAndColStartsAreEqual(MPI_Comm comm, const HYPRE_BigInt *rows,
|
||||
const HYPRE_BigInt *cols)
|
||||
{
|
||||
const int part_size = GetPartitioningArraySize(comm);
|
||||
bool are_equal = true;
|
||||
@@ -1131,7 +1132,7 @@ HypreParMatrix::HypreParMatrix(
|
||||
HypreParMatrix::HypreParMatrix(MPI_Comm comm,
|
||||
HYPRE_BigInt *row_starts,
|
||||
HYPRE_BigInt *col_starts,
|
||||
SparseMatrix *sm_a)
|
||||
const SparseMatrix *sm_a)
|
||||
{
|
||||
MFEM_ASSERT(sm_a != NULL, "invalid input");
|
||||
MFEM_VERIFY(!HYPRE_AssumedPartitionCheck(),
|
||||
@@ -1145,7 +1146,7 @@ HypreParMatrix::HypreParMatrix(MPI_Comm comm,
|
||||
|
||||
hypre_CSRMatrixSetDataOwner(csr_a,0);
|
||||
MemoryIJData mem_a;
|
||||
CopyCSR(sm_a, mem_a, csr_a, false);
|
||||
CopyCSR(const_cast<SparseMatrix*>(sm_a), mem_a, csr_a, false);
|
||||
hypre_CSRMatrixSetRownnz(csr_a);
|
||||
|
||||
// NOTE: this call creates a matrix on host even when device support is
|
||||
@@ -1307,10 +1308,11 @@ HypreParMatrix::HypreParMatrix(MPI_Comm comm, int id, int np,
|
||||
HypreParMatrix::HypreParMatrix(MPI_Comm comm, int nrows,
|
||||
HYPRE_BigInt glob_nrows,
|
||||
HYPRE_BigInt glob_ncols,
|
||||
int *I, HYPRE_BigInt *J,
|
||||
real_t *data,
|
||||
HYPRE_BigInt *rows,
|
||||
HYPRE_BigInt *cols)
|
||||
const int *I,
|
||||
const HYPRE_BigInt *J,
|
||||
const real_t *data,
|
||||
const HYPRE_BigInt *rows,
|
||||
const HYPRE_BigInt *cols)
|
||||
{
|
||||
Init();
|
||||
|
||||
@@ -2327,8 +2329,8 @@ void HypreParMatrix::Threshold(real_t threshold)
|
||||
/* TODO: GenerateDiagAndOffd() uses an int array of size equal to the number
|
||||
of columns in csr_A_wo_z which is the global number of columns in A. This
|
||||
does not scale well. */
|
||||
ierr += GenerateDiagAndOffd(csr_A_wo_z,parcsr_A_ptr,
|
||||
col_start,col_end);
|
||||
ierr += hypre_GenerateDiagAndOffd(csr_A_wo_z,parcsr_A_ptr,
|
||||
col_start,col_end);
|
||||
|
||||
ierr += hypre_CSRMatrixDestroy(csr_A_wo_z);
|
||||
|
||||
|
||||
+15
-4
@@ -25,11 +25,18 @@
|
||||
#define HYPRE_TIMING
|
||||
|
||||
// hypre header files
|
||||
#if MFEM_HYPRE_VERSION < 30000
|
||||
#include <seq_mv.h>
|
||||
#include <temp_multivector.h>
|
||||
#else
|
||||
#include <_hypre_seq_mv.h>
|
||||
#include <_hypre_lobpcg_temp_multivector.h>
|
||||
#endif
|
||||
#include <_hypre_parcsr_mv.h>
|
||||
#include <_hypre_parcsr_ls.h>
|
||||
|
||||
#include <HYPRE_parcsr_ls.h>
|
||||
|
||||
#ifdef HYPRE_COMPLEX
|
||||
#error "MFEM does not work with HYPRE's complex numbers support"
|
||||
#endif
|
||||
@@ -53,6 +60,10 @@
|
||||
#error "MFEM_USE_HIP=YES is required when HYPRE is built with HIP!"
|
||||
#endif
|
||||
|
||||
#if MFEM_HYPRE_VERSION > 21500
|
||||
#define HYPRE_AssumedPartitionCheck() 1
|
||||
#endif
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
|
||||
@@ -554,7 +565,7 @@ public:
|
||||
partitioning arrays @a row_starts and @a col_starts. */
|
||||
HypreParMatrix(MPI_Comm comm, HYPRE_BigInt *row_starts,
|
||||
HYPRE_BigInt *col_starts,
|
||||
SparseMatrix *a); // constructor with 4 arguments, v2
|
||||
const SparseMatrix *a); // constructor with 4 arguments, v2
|
||||
|
||||
/// Creates boolean block-diagonal rectangular parallel matrix.
|
||||
/** The new HypreParMatrix does not take ownership of any of the input
|
||||
@@ -583,9 +594,9 @@ public:
|
||||
arrays (so they can be deleted). See @ref hypre_partitioning_descr "here"
|
||||
for a description of the partitioning arrays @a rows and @a cols. */
|
||||
HypreParMatrix(MPI_Comm comm, int nrows, HYPRE_BigInt glob_nrows,
|
||||
HYPRE_BigInt glob_ncols, int *I, HYPRE_BigInt *J,
|
||||
real_t *data, HYPRE_BigInt *rows,
|
||||
HYPRE_BigInt *cols); // constructor with 9 arguments
|
||||
HYPRE_BigInt glob_ncols, const int *I, const HYPRE_BigInt *J,
|
||||
const real_t *data, const HYPRE_BigInt *rows,
|
||||
const HYPRE_BigInt *cols); // constructor with 9 arguments
|
||||
|
||||
/** @brief Copy constructor for a ParCSR matrix which creates a deep copy of
|
||||
structure and data from @a P. */
|
||||
|
||||
@@ -1916,9 +1916,9 @@ hypre_ParCSRMatrixAdd(hypre_ParCSRMatrix *A,
|
||||
/* FIXME: GenerateDiagAndOffd() uses an int array of size equal to the
|
||||
number of columns in csr_C_temp which is the global number of columns
|
||||
in A and B. This does not scale well. */
|
||||
ierr += GenerateDiagAndOffd(csr_C_temp, C,
|
||||
hypre_ParCSRMatrixFirstColDiag(A),
|
||||
hypre_ParCSRMatrixLastColDiag(A));
|
||||
ierr += hypre_GenerateDiagAndOffd(csr_C_temp, C,
|
||||
hypre_ParCSRMatrixFirstColDiag(A),
|
||||
hypre_ParCSRMatrixLastColDiag(A));
|
||||
|
||||
/* delete CSR version of C */
|
||||
ierr += hypre_CSRMatrixDestroy(csr_C_temp);
|
||||
|
||||
@@ -21,6 +21,10 @@
|
||||
// hypre header files
|
||||
#include <_hypre_parcsr_mv.h>
|
||||
|
||||
#if MFEM_HYPRE_VERSION < 30000
|
||||
#define hypre_GenerateDiagAndOffd GenerateDiagAndOffd
|
||||
#endif
|
||||
|
||||
// Older hypre versions do not define HYPRE_BigInt and HYPRE_MPI_BIG_INT, so we
|
||||
// define them here for backward compatibility.
|
||||
#if MFEM_HYPRE_VERSION < 21600
|
||||
|
||||
@@ -1356,6 +1356,7 @@ void PetscParMatrix::MakeWrapper(MPI_Comm comm, const Operator* op, Mat *A)
|
||||
PETSC_DECIDE,PETSC_DECIDE); PCHKERRQ(A,ierr);
|
||||
ierr = MatSetType(*A,MATSHELL); PCHKERRQ(A,ierr);
|
||||
ierr = MatShellSetContext(*A,(void *)op); PCHKERRQ(A,ierr);
|
||||
#if PETSC_VERSION_LT(3,24,0)
|
||||
ierr = MatShellSetOperation(*A,MATOP_MULT,
|
||||
(void (*)())__mfem_mat_shell_apply);
|
||||
PCHKERRQ(A,ierr);
|
||||
@@ -1367,6 +1368,19 @@ void PetscParMatrix::MakeWrapper(MPI_Comm comm, const Operator* op, Mat *A)
|
||||
PCHKERRQ(A,ierr);
|
||||
ierr = MatShellSetOperation(*A,MATOP_DESTROY,
|
||||
(void (*)())__mfem_mat_shell_destroy);
|
||||
#else
|
||||
ierr = MatShellSetOperation(*A,MATOP_MULT,
|
||||
(PetscErrorCodeFn*)__mfem_mat_shell_apply);
|
||||
PCHKERRQ(A,ierr);
|
||||
ierr = MatShellSetOperation(*A,MATOP_MULT_TRANSPOSE,
|
||||
(PetscErrorCodeFn*)__mfem_mat_shell_apply_transpose);
|
||||
PCHKERRQ(A,ierr);
|
||||
ierr = MatShellSetOperation(*A,MATOP_COPY,
|
||||
(PetscErrorCodeFn*)__mfem_mat_shell_copy);
|
||||
PCHKERRQ(A,ierr);
|
||||
ierr = MatShellSetOperation(*A,MATOP_DESTROY,
|
||||
(PetscErrorCodeFn*)__mfem_mat_shell_destroy);
|
||||
#endif
|
||||
#if defined(_USE_DEVICE)
|
||||
MemoryType mt = GetMemoryType(op->GetMemoryClass());
|
||||
if (mt == MemoryType::DEVICE || mt == MemoryType::MANAGED)
|
||||
|
||||
+73
-117
@@ -46,6 +46,12 @@
|
||||
#define MFEM_GPUSPARSE_ALG HIPSPARSE_CSRMV_ALG1
|
||||
#endif // defined(MFEM_USE_CUDA)
|
||||
|
||||
#if defined(MFEM_USE_SINGLE)
|
||||
#define MFEM_CUDA_or_HIP_REAL_T MFEM_CUDA_or_HIP(_R_32F)
|
||||
#elif defined(MFEM_USE_DOUBLE)
|
||||
#define MFEM_CUDA_or_HIP_REAL_T MFEM_CUDA_or_HIP(_R_64F)
|
||||
#endif
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
|
||||
@@ -57,8 +63,10 @@ int SparseMatrix::SparseMatrixCount = 0;
|
||||
/// @cond Suppress_Doxygen_warnings
|
||||
MFEM_cu_or_hip(sparseHandle_t) SparseMatrix::handle = nullptr;
|
||||
/// @endcond
|
||||
#ifndef MFEM_CUDA_1897_WORKAROUND
|
||||
size_t SparseMatrix::bufferSize = 0;
|
||||
void * SparseMatrix::dBuffer = nullptr;
|
||||
#endif
|
||||
#endif // MFEM_USE_CUDA_OR_HIP
|
||||
|
||||
void SparseMatrix::InitGPUSparse()
|
||||
@@ -464,109 +472,67 @@ void SparseMatrix::SortColumnIndices()
|
||||
}
|
||||
|
||||
#ifdef MFEM_USE_CUDA_OR_HIP
|
||||
if ( Device::Allows( Backend::CUDA_MASK ))
|
||||
if (Device::Allows(Backend::CUDA_MASK) || Device::Allows(Backend::HIP_MASK))
|
||||
{
|
||||
#if defined(MFEM_USE_CUDA)
|
||||
size_t pBufferSizeInBytes = 0;
|
||||
void *pBuffer = NULL;
|
||||
|
||||
const int n = Height();
|
||||
const int m = Width();
|
||||
const int m = Height();
|
||||
const int n = Width();
|
||||
const int nnzA = J.Capacity();
|
||||
real_t * d_a_sorted = ReadWriteData();
|
||||
const int * d_ia = ReadI();
|
||||
int * d_ja_sorted = ReadWriteJ();
|
||||
csru2csrInfo_t sortInfoA;
|
||||
const int *d_ia = ReadI();
|
||||
int *d_ja = ReadWriteJ();
|
||||
|
||||
cusparseMatDescr_t matA_descr;
|
||||
cusparseCreateMatDescr( &matA_descr );
|
||||
cusparseSetMatIndexBase( matA_descr, CUSPARSE_INDEX_BASE_ZERO );
|
||||
cusparseSetMatType( matA_descr, CUSPARSE_MATRIX_TYPE_GENERAL );
|
||||
// Get size of temporary buffer needed to sort the column indices,
|
||||
// allocate the temporary buffer.
|
||||
size_t pBufferSizeInBytes;
|
||||
MFEM_cu_or_hip(sparseXcsrsort_bufferSizeExt)(handle, m, n, nnzA, d_ia,
|
||||
d_ja, &pBufferSizeInBytes);
|
||||
void *pBuffer = MFEM_Cu_or_Hip(MemAlloc)(&pBuffer, pBufferSizeInBytes);
|
||||
|
||||
cusparseCreateCsru2csrInfo( &sortInfoA );
|
||||
// Create matrix descriptor, will have default values
|
||||
// CUSPARSE_INDEX_BASE_ZERO and CUSPARSE_MATRIX_TYPE_GENERAL.
|
||||
MFEM_cu_or_hip(sparseMatDescr_t) matA_descr;
|
||||
MFEM_cu_or_hip(sparseCreateMatDescr)(&matA_descr);
|
||||
|
||||
#ifdef MFEM_USE_SINGLE
|
||||
cusparseScsru2csr_bufferSizeExt( handle, n, m, nnzA, d_a_sorted, d_ia,
|
||||
d_ja_sorted, sortInfoA,
|
||||
&pBufferSizeInBytes);
|
||||
#elif defined MFEM_USE_DOUBLE
|
||||
cusparseDcsru2csr_bufferSizeExt( handle, n, m, nnzA, d_a_sorted, d_ia,
|
||||
d_ja_sorted, sortInfoA,
|
||||
&pBufferSizeInBytes);
|
||||
#else
|
||||
MFEM_ABORT("Floating point type undefined");
|
||||
#endif
|
||||
// Initialize permutation to identity
|
||||
Array<int> P(nnzA);
|
||||
int *d_P = P.Write();
|
||||
mfem::forall(nnzA, [=] MFEM_HOST_DEVICE (int i) { d_P[i] = i; });
|
||||
|
||||
CuMemAlloc( &pBuffer, pBufferSizeInBytes );
|
||||
// Sort the column indices. The array d_ja will now be sorted. The
|
||||
// permutation required to sort the values will be returned in d_P.
|
||||
MFEM_cu_or_hip(sparseXcsrsort)(handle, m, n, nnzA, matA_descr, d_ia, d_ja,
|
||||
d_P, pBuffer);
|
||||
|
||||
#ifdef MFEM_USE_SINGLE
|
||||
cusparseScsru2csr( handle, n, m, nnzA, matA_descr, d_a_sorted, d_ia,
|
||||
d_ja_sorted, sortInfoA, pBuffer);
|
||||
#elif defined MFEM_USE_DOUBLE
|
||||
cusparseDcsru2csr( handle, n, m, nnzA, matA_descr, d_a_sorted, d_ia,
|
||||
d_ja_sorted, sortInfoA, pBuffer);
|
||||
#else
|
||||
MFEM_ABORT("Floating point type undefined");
|
||||
#endif
|
||||
// Create a copy of the unsorted matrix values.
|
||||
real_t *d_a = ReadWriteData();
|
||||
void *d_a_unsorted = MFEM_Cu_or_Hip(MemAlloc)(&d_a_unsorted,
|
||||
nnzA * sizeof(real_t));
|
||||
MFEM_Cu_or_Hip(MemcpyDtoD)(d_a_unsorted, d_a, nnzA * sizeof(real_t));
|
||||
|
||||
// The above call is (at least in some cases) asynchronous, so we need to
|
||||
// wait for it to finish before we can free device temporaries.
|
||||
// Create the (input) dense vector with the unsorted values.
|
||||
MFEM_cu_or_hip(sparseDnVecDescr_t) d_a_dense;
|
||||
MFEM_cu_or_hip(sparseCreateDnVec)(&d_a_dense, nnzA, d_a_unsorted,
|
||||
MFEM_CUDA_or_HIP_REAL_T);
|
||||
|
||||
// Create the (output) sparse vector that will have the sorted values.
|
||||
MFEM_cu_or_hip(sparseSpVecDescr_t) d_a_sparse;
|
||||
MFEM_cu_or_hip(sparseCreateSpVec)(&d_a_sparse, nnzA, nnzA, d_P, d_a,
|
||||
MFEM_CU_or_HIP(SPARSE_INDEX_32I),
|
||||
MFEM_CU_or_HIP(SPARSE_INDEX_BASE_ZERO),
|
||||
MFEM_CUDA_or_HIP_REAL_T);
|
||||
|
||||
// Sort the matrix values using the permutation vector.
|
||||
MFEM_cu_or_hip(sparseGather)(handle, d_a_dense, d_a_sparse);
|
||||
|
||||
// The above calls may be asynchronous, so we need to wait for them to
|
||||
// finish before we can free memory.
|
||||
MFEM_STREAM_SYNC;
|
||||
|
||||
cusparseDestroyCsru2csrInfo( sortInfoA );
|
||||
cusparseDestroyMatDescr( matA_descr );
|
||||
MFEM_cu_or_hip(sparseDestroyDnVec)(d_a_dense);
|
||||
MFEM_cu_or_hip(sparseDestroySpVec)(d_a_sparse);
|
||||
MFEM_cu_or_hip(sparseDestroyMatDescr)(matA_descr);
|
||||
|
||||
CuMemFree( pBuffer );
|
||||
#endif
|
||||
}
|
||||
else if ( Device::Allows( Backend::HIP_MASK ))
|
||||
{
|
||||
#if defined(MFEM_USE_HIP)
|
||||
size_t pBufferSizeInBytes = 0;
|
||||
void *pBuffer = NULL;
|
||||
int *P = NULL;
|
||||
|
||||
const int n = Height();
|
||||
const int m = Width();
|
||||
const int nnzA = J.Capacity();
|
||||
real_t * d_a_sorted = ReadWriteData();
|
||||
const int * d_ia = ReadI();
|
||||
int * d_ja_sorted = ReadWriteJ();
|
||||
|
||||
hipsparseMatDescr_t descrA;
|
||||
hipsparseCreateMatDescr( &descrA );
|
||||
// FIXME: There is not in-place version of csr sort in hipSPARSE currently, so we make
|
||||
// a temporary copy of the data for gthr, sort that, and then copy the sorted values
|
||||
// back to the array being returned. Where there is an in-place version available,
|
||||
// we should use it.
|
||||
Array< real_t > a_tmp( nnzA );
|
||||
real_t *d_a_tmp = a_tmp.Write();
|
||||
|
||||
hipsparseXcsrsort_bufferSizeExt(handle, n, m, nnzA, d_ia, d_ja_sorted,
|
||||
&pBufferSizeInBytes);
|
||||
|
||||
HipMemAlloc( &pBuffer, pBufferSizeInBytes );
|
||||
HipMemAlloc( (void**)&P, nnzA * sizeof(int) );
|
||||
|
||||
hipsparseCreateIdentityPermutation(handle, nnzA, P);
|
||||
hipsparseXcsrsort(handle, n, m, nnzA, descrA, d_ia, d_ja_sorted, P, pBuffer);
|
||||
|
||||
#if defined(MFEM_USE_SINGLE)
|
||||
hipsparseSgthr(handle, nnzA, d_a_sorted, d_a_tmp, P,
|
||||
HIPSPARSE_INDEX_BASE_ZERO);
|
||||
#elif defined(MFEM_USE_DOUBLE)
|
||||
hipsparseDgthr(handle, nnzA, d_a_sorted, d_a_tmp, P,
|
||||
HIPSPARSE_INDEX_BASE_ZERO);
|
||||
#else
|
||||
MFEM_ABORT("Unsupported floating point type!");
|
||||
#endif
|
||||
|
||||
A.CopyFrom( a_tmp.GetMemory(), nnzA );
|
||||
hipsparseDestroyMatDescr( descrA );
|
||||
|
||||
HipMemFree( pBuffer );
|
||||
HipMemFree( P );
|
||||
#endif
|
||||
MFEM_Cu_or_Hip(MemFree)(d_a_unsorted);
|
||||
MFEM_Cu_or_Hip(MemFree)(pBuffer);
|
||||
}
|
||||
else
|
||||
#endif // MFEM_USE_CUDA_OR_HIP
|
||||
@@ -821,27 +787,15 @@ void SparseMatrix::AddMult(const Vector &x, Vector &y, const real_t a) const
|
||||
MFEM_CU_or_HIP(SPARSE_INDEX_32I),
|
||||
MFEM_CU_or_HIP(SPARSE_INDEX_32I),
|
||||
MFEM_CU_or_HIP(SPARSE_INDEX_BASE_ZERO),
|
||||
#ifdef MFEM_USE_SINGLE
|
||||
MFEM_CUDA_or_HIP(_R_32F));
|
||||
#else
|
||||
MFEM_CUDA_or_HIP(_R_64F));
|
||||
#endif
|
||||
MFEM_CUDA_or_HIP_REAL_T);
|
||||
|
||||
// Create handles for input/output vectors
|
||||
MFEM_cu_or_hip(sparseCreateDnVec)(&vecX_descr,
|
||||
x.Size(),
|
||||
const_cast<real_t *>(d_x),
|
||||
#ifdef MFEM_USE_SINGLE
|
||||
MFEM_CUDA_or_HIP(_R_32F));
|
||||
#else
|
||||
MFEM_CUDA_or_HIP(_R_64F));
|
||||
#endif
|
||||
MFEM_CUDA_or_HIP_REAL_T);
|
||||
MFEM_cu_or_hip(sparseCreateDnVec)(&vecY_descr, y.Size(), d_y,
|
||||
#ifdef MFEM_USE_SINGLE
|
||||
MFEM_CUDA_or_HIP(_R_32F));
|
||||
#else
|
||||
MFEM_CUDA_or_HIP(_R_64F));
|
||||
#endif
|
||||
MFEM_CUDA_or_HIP_REAL_T);
|
||||
#else
|
||||
cusparseCreateMatDescr(&matA_descr);
|
||||
cusparseSetMatIndexBase(matA_descr, CUSPARSE_INDEX_BASE_ZERO);
|
||||
@@ -860,11 +814,7 @@ void SparseMatrix::AddMult(const Vector &x, Vector &y, const real_t a) const
|
||||
vecX_descr,
|
||||
&beta,
|
||||
vecY_descr,
|
||||
#ifdef MFEM_USE_SINGLE
|
||||
MFEM_CUDA_or_HIP(_R_32F),
|
||||
#else
|
||||
MFEM_CUDA_or_HIP(_R_64F),
|
||||
#endif
|
||||
MFEM_CUDA_or_HIP_REAL_T,
|
||||
MFEM_GPUSPARSE_ALG,
|
||||
&newBufferSize);
|
||||
|
||||
@@ -891,11 +841,7 @@ void SparseMatrix::AddMult(const Vector &x, Vector &y, const real_t a) const
|
||||
vecX_descr,
|
||||
&beta,
|
||||
vecY_descr,
|
||||
#ifdef MFEM_USE_SINGLE
|
||||
MFEM_CUDA_or_HIP(_R_32F),
|
||||
#else
|
||||
MFEM_CUDA_or_HIP(_R_64F),
|
||||
#endif
|
||||
MFEM_CUDA_or_HIP_REAL_T,
|
||||
MFEM_GPUSPARSE_ALG,
|
||||
dBuffer);
|
||||
#else
|
||||
@@ -4372,6 +4318,14 @@ SparseMatrix::~SparseMatrix()
|
||||
#ifdef MFEM_USE_CUDA_OR_HIP
|
||||
if (Device::Allows(Backend::CUDA_MASK | Backend::HIP_MASK))
|
||||
{
|
||||
#ifdef MFEM_CUDA_1897_WORKAROUND
|
||||
if (dBuffer)
|
||||
{
|
||||
MFEM_Cu_or_Hip(MemFree)(dBuffer);
|
||||
dBuffer = nullptr;
|
||||
bufferSize = 0;
|
||||
}
|
||||
#endif
|
||||
if (SparseMatrixCount==1)
|
||||
{
|
||||
if (handle)
|
||||
@@ -4379,12 +4333,14 @@ SparseMatrix::~SparseMatrix()
|
||||
MFEM_cu_or_hip(sparseDestroy)(handle);
|
||||
handle = nullptr;
|
||||
}
|
||||
#ifndef MFEM_CUDA_1897_WORKAROUND
|
||||
if (dBuffer)
|
||||
{
|
||||
MFEM_Cu_or_Hip(MemFree)(dBuffer);
|
||||
dBuffer = nullptr;
|
||||
bufferSize = 0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
SparseMatrixCount--;
|
||||
}
|
||||
|
||||
@@ -98,9 +98,17 @@ protected:
|
||||
#ifdef MFEM_USE_CUDA_OR_HIP
|
||||
// common for hipSPARSE and cuSPARSE
|
||||
static int SparseMatrixCount;
|
||||
mutable bool initBuffers = false;
|
||||
|
||||
#if defined(MFEM_USE_CUDA) && CUDA_VERSION >= 12300 && CUDA_VERSION < 12602
|
||||
// Workaround for bug CUSPARSE-1897
|
||||
#define MFEM_CUDA_1897_WORKAROUND
|
||||
mutable size_t bufferSize = 0;
|
||||
mutable void *dBuffer = nullptr;
|
||||
#else
|
||||
static size_t bufferSize;
|
||||
static void *dBuffer;
|
||||
mutable bool initBuffers = false;
|
||||
#endif
|
||||
|
||||
#if defined(MFEM_USE_CUDA)
|
||||
cusparseStatus_t status;
|
||||
|
||||
+8
-20
@@ -92,18 +92,6 @@ struct LpReducer
|
||||
}
|
||||
};
|
||||
|
||||
static Array<real_t>& vector_workspace()
|
||||
{
|
||||
static Array<real_t> instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
static Array<DevicePair<real_t, real_t>> &Lpvector_workspace()
|
||||
{
|
||||
static Array<DevicePair<real_t, real_t>> instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
Vector::Vector(const Vector &v)
|
||||
{
|
||||
const int s = v.Size();
|
||||
@@ -991,7 +979,7 @@ real_t Vector::Norml2() const
|
||||
}
|
||||
}
|
||||
},
|
||||
L2Reducer{}, UseDevice(), Lpvector_workspace());
|
||||
L2Reducer{}, UseDevice());
|
||||
// final answer
|
||||
return res.second * sqrt(res.first);
|
||||
}
|
||||
@@ -1006,7 +994,7 @@ real_t Vector::Normlinf() const
|
||||
{
|
||||
r = fmax(r, fabs(m_data[i]));
|
||||
},
|
||||
MaxReducer<real_t> {}, UseDevice(), vector_workspace());
|
||||
MaxReducer<real_t> {}, UseDevice());
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -1020,7 +1008,7 @@ real_t Vector::Norml1() const
|
||||
{
|
||||
r += fabs(m_data[i]);
|
||||
},
|
||||
SumReducer<real_t> {}, UseDevice(), vector_workspace());
|
||||
SumReducer<real_t> {}, UseDevice());
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -1063,7 +1051,7 @@ real_t Vector::Normlp(real_t p) const
|
||||
}
|
||||
}
|
||||
},
|
||||
LpReducer{p}, UseDevice(), Lpvector_workspace());
|
||||
LpReducer{p}, UseDevice());
|
||||
// final answer
|
||||
return res.second * pow(res.first, 1.0 / p);
|
||||
} // end if p < infinity()
|
||||
@@ -1096,7 +1084,7 @@ real_t Vector::operator*(const Vector &v) const
|
||||
{
|
||||
r += m_data[i] * v_data[i];
|
||||
},
|
||||
SumReducer<real_t> {}, use_dev, vector_workspace());
|
||||
SumReducer<real_t> {}, use_dev);
|
||||
return res;
|
||||
};
|
||||
|
||||
@@ -1167,7 +1155,7 @@ real_t Vector::Min() const
|
||||
{
|
||||
r = fmin(r, m_data[i]);
|
||||
},
|
||||
MinReducer<real_t> {}, use_dev, vector_workspace());
|
||||
MinReducer<real_t> {}, use_dev);
|
||||
return res;
|
||||
};
|
||||
|
||||
@@ -1213,7 +1201,7 @@ real_t Vector::Max() const
|
||||
{
|
||||
r = fmax(r, m_data[i]);
|
||||
},
|
||||
MaxReducer<real_t> {}, use_dev, vector_workspace());
|
||||
MaxReducer<real_t> {}, use_dev);
|
||||
return res;
|
||||
};
|
||||
|
||||
@@ -1248,7 +1236,7 @@ real_t Vector::Sum() const
|
||||
{
|
||||
r += m_data[i];
|
||||
},
|
||||
SumReducer<real_t> {}, UseDevice(), vector_workspace());
|
||||
SumReducer<real_t> {}, UseDevice());
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
@@ -377,7 +377,7 @@ MFEM_CONFIG_VARS = MFEM_CXX MFEM_HOST_CXX MFEM_CPPFLAGS MFEM_CXXFLAGS\
|
||||
MFEM_INC_DIR MFEM_TPLFLAGS MFEM_INCFLAGS MFEM_PICFLAG MFEM_FLAGS MFEM_LIB_DIR\
|
||||
MFEM_EXT_LIBS MFEM_LIBS MFEM_LIB_FILE MFEM_STATIC MFEM_SHARED MFEM_BUILD_TAG\
|
||||
MFEM_PREFIX MFEM_CONFIG_EXTRA MFEM_MPIEXEC MFEM_MPIEXEC_NP MFEM_MPI_NP\
|
||||
MFEM_TEST_MK
|
||||
MFEM_TEST_MK MFEM_XLINKER
|
||||
|
||||
# Config vars: values of the form @VAL@ are replaced by $(VAL) in config.mk
|
||||
MFEM_CPPFLAGS ?= $(CPPFLAGS)
|
||||
@@ -394,6 +394,7 @@ MFEM_BUILD_TAG ?= $(shell uname -snm)
|
||||
MFEM_PREFIX ?= $(PREFIX)
|
||||
MFEM_INC_DIR ?= $(if $(CONFIG_FILE_DEF),@MFEM_BUILD_DIR@,@MFEM_DIR@)
|
||||
MFEM_LIB_DIR ?= $(if $(CONFIG_FILE_DEF),@MFEM_BUILD_DIR@,@MFEM_DIR@)
|
||||
MFEM_XLINKER ?= $(XLINKER)
|
||||
MFEM_TEST_MK ?= @MFEM_DIR@/config/test.mk
|
||||
# Use "\n" (interpreted by sed) to add a newline.
|
||||
MFEM_CONFIG_EXTRA ?= $(if $(CONFIG_FILE_DEF),MFEM_BUILD_DIR ?= @MFEM_DIR@,)
|
||||
|
||||
@@ -773,7 +773,7 @@ struct BufferReader : BufferReaderBase
|
||||
int header_entry_size = HeaderEntrySize();
|
||||
int nblocks = ReadHeaderEntry(header_buf);
|
||||
header_buf += header_entry_size;
|
||||
std::vector<int> header(nblocks + 2);
|
||||
std::vector<size_t> header(nblocks + 2);
|
||||
for (int i=0; i<nblocks+2; ++i)
|
||||
{
|
||||
header[i] = ReadHeaderEntry(header_buf);
|
||||
@@ -792,7 +792,7 @@ struct BufferReader : BufferReaderBase
|
||||
dest_ptr += dest_len;
|
||||
source_ptr += source_len;
|
||||
}
|
||||
MFEM_VERIFY(int(sizeof(F)*n) == (dest_ptr - dest_start),
|
||||
MFEM_VERIFY(size_t(sizeof(F)*n) == (dest_ptr - dest_start),
|
||||
"AppendedData: wrong data size");
|
||||
buf = uncompressed_data.data();
|
||||
#else
|
||||
|
||||
@@ -17,10 +17,6 @@ SRC = $(if $(MFEM_DIR:../..=),$(MFEM_DIR)/miniapps/autodiff/,)
|
||||
CONFIG_MK = $(or $(wildcard $(MFEM_BUILD_DIR)/config/config.mk),\
|
||||
$(wildcard $(MFEM_INSTALL_DIR)/share/mfem/config.mk))
|
||||
|
||||
# Include defaults.mk to get XLINKER
|
||||
DEFAULTS_MK = $(MFEM_DIR)/config/defaults.mk
|
||||
include $(DEFAULTS_MK)
|
||||
|
||||
MFEM_LIB_FILE = mfem_is_not_built
|
||||
-include $(CONFIG_MK)
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ CONFIG_MK = $(or $(wildcard $(MFEM_BUILD_DIR)/config/config.mk),\
|
||||
# Default target
|
||||
all: lib-common
|
||||
|
||||
# Include defaults.mk to get the definition of BUILD_SOFLAGS
|
||||
DEFAULTS_MK = $(MFEM_DIR)/config/defaults.mk
|
||||
include $(DEFAULTS_MK)
|
||||
|
||||
@@ -30,13 +31,7 @@ MFEM_LIB_FILE = mfem_is_not_built
|
||||
ifneq (clean,$(MAKECMDGOALS))
|
||||
-include $(CONFIG_MK)
|
||||
|
||||
ifeq ($(MFEM_USE_CUDA),YES)
|
||||
XLINKER = $(CUDA_XLINKER)
|
||||
else ifeq ($(MFEM_USE_HIP),YES)
|
||||
XLINKER = $(HIP_XLINKER)
|
||||
else
|
||||
XLINKER = $(CXX_XLINKER)
|
||||
endif
|
||||
XLINKER = $(MFEM_XLINKER)
|
||||
|
||||
BUILD_REAL_DIR = $(realpath .)
|
||||
BUILD_SOFLAGS := $(subst libmfem.,libmfem-common.,$(BUILD_SOFLAGS))
|
||||
|
||||
@@ -15,11 +15,9 @@ MFEM_BUILD_DIR ?= ../..
|
||||
|
||||
SRC = $(if $(MFEM_DIR:../..=),$(MFEM_DIR)/miniapps/diag-smoothers/,)
|
||||
CONFIG_MK = $(MFEM_BUILD_DIR)/config/config.mk
|
||||
DEFAULTS_MK = $(MFEM_DIR)/config/defaults.mk
|
||||
|
||||
MFEM_LIB_FILE = mfem_is_not_built
|
||||
|
||||
-include $(DEFAULTS_MK)
|
||||
-include $(CONFIG_MK)
|
||||
|
||||
DS_COMMON_SRC = ds-common.cpp
|
||||
@@ -31,8 +29,7 @@ MINIAPPS = $(if $(MFEM_USE_MPI:NO=),$(PAR_MINIAPPS),)
|
||||
|
||||
COMMON_LIB = -L$(MFEM_BUILD_DIR)/miniapps/common -lmfem-common
|
||||
COMMON_LIB += $(if $(MFEM_SHARED:YES=),,\
|
||||
$(if $(MFEM_USE_CUDA:YES=),$(CXX_XLINKER),$(CUDA_XLINKER))-rpath,$(abspath\
|
||||
$(MFEM_BUILD_DIR)/miniapps/common))
|
||||
$(MFEM_XLINKER)-rpath,$(abspath $(MFEM_BUILD_DIR)/miniapps/common))
|
||||
|
||||
APP_DEPS = $(DS_COMMON_OBJ) $(MFEM_LIB_FILE) $(CONFIG_MK)
|
||||
APP_LIBS = $(COMMON_LIB) $(MFEM_LIBS)
|
||||
|
||||
@@ -17,10 +17,6 @@ SRC = $(if $(MFEM_DIR:../..=),$(MFEM_DIR)/miniapps/dpg/,)
|
||||
CONFIG_MK = $(or $(wildcard $(MFEM_BUILD_DIR)/config/config.mk),\
|
||||
$(wildcard $(MFEM_INSTALL_DIR)/share/mfem/config.mk))
|
||||
|
||||
# Include defaults.mk to get XLINKER
|
||||
DEFAULTS_MK = $(MFEM_DIR)/config/defaults.mk
|
||||
include $(DEFAULTS_MK)
|
||||
|
||||
MFEM_LIB_FILE = mfem_is_not_built
|
||||
-include $(CONFIG_MK)
|
||||
|
||||
@@ -69,8 +65,7 @@ COMMON_LIB = -L$(MFEM_BUILD_DIR)/miniapps/common -lmfem-common
|
||||
|
||||
# If MFEM_SHARED is set, add the ../common rpath
|
||||
COMMON_LIB += $(if $(MFEM_SHARED:YES=),,\
|
||||
$(if $(MFEM_USE_CUDA:YES=),$(CXX_XLINKER),$(CUDA_XLINKER))-rpath,$(abspath\
|
||||
$(MFEM_BUILD_DIR)/miniapps/common))
|
||||
$(MFEM_XLINKER)-rpath,$(abspath $(MFEM_BUILD_DIR)/miniapps/common))
|
||||
|
||||
.SUFFIXES:
|
||||
.SUFFIXES: .o .cpp .mk
|
||||
|
||||
@@ -34,6 +34,11 @@ if (MFEM_USE_MPI)
|
||||
EXTRA_HEADERS maxwell_solver.hpp ${MFEM_MINIAPPS_COMMON_HEADERS}
|
||||
LIBRARIES mfem-common)
|
||||
|
||||
add_mfem_miniapp(lorentz
|
||||
MAIN lorentz.cpp
|
||||
EXTRA_HEADERS ${MFEM_MINIAPPS_COMMON_HEADERS}
|
||||
LIBRARIES mfem-common)
|
||||
|
||||
# Add the corresponding tests to the "test" target
|
||||
if (MFEM_ENABLE_TESTING)
|
||||
add_test(NAME tesla_np=4
|
||||
|
||||
@@ -0,0 +1,571 @@
|
||||
// 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.
|
||||
//
|
||||
// -----------------------------------------------------
|
||||
// Lorentz Miniapp: Simple Lorentz Force Particle Mover
|
||||
// -----------------------------------------------------
|
||||
//
|
||||
// This miniapp computes the trajectory of a single charged particle subject to
|
||||
// Lorentz forces.
|
||||
//
|
||||
// dp/dt = q (E + v x B)
|
||||
//
|
||||
// The method used is the explicit Boris algortihm which conserves phase space
|
||||
// volume for long term accuracy.
|
||||
//
|
||||
// The electric and magnetic fields are read from VisItDataCollection objects
|
||||
// such as those produced by the Volta and Tesla miniapps. It is notable that
|
||||
// these two fields do not need to be defined on the same mesh. Of course, the
|
||||
// particle trajectory can only be computed on the intersection of the two
|
||||
// domains. The starting point of the path must be chosen within in this
|
||||
// intersection and the trajectory will terminate when it leaves the
|
||||
// intersection or reaches a specified time duration.
|
||||
//
|
||||
// Note that the VisItDataCollection objects must have been stored using the
|
||||
// parallel format e.g. visit_dc.SetFormat(DataCollection::PARALLEL_FORMAT);.
|
||||
// Without this optional format specifier the vector field lookups will fail.
|
||||
//
|
||||
// Compile with: make lorentz
|
||||
//
|
||||
// Sample runs:
|
||||
//
|
||||
// Free particle moving with constant velocity
|
||||
// mpirun -np 4 lorentz -p0 '1 1 1'
|
||||
//
|
||||
// Particle accelerating in a constant electric field
|
||||
// mpirun -np 4 volta -m ../../data/inline-hex.mesh -dbcs '1 6' -dbcv '0 1'
|
||||
// mpirun -np 4 lorentz -er Volta-AMR-Parallel -x0 '0.5 0.5 0.9' -p0 '1 0 0'
|
||||
//
|
||||
// Particle accelerating in a constant magnetic field
|
||||
// mpirun -np 4 tesla -m ../../data/inline-hex.mesh -ubbc '0 0 1'
|
||||
// mpirun -np 4 lorentz -br Tesla-AMR-Parallel -x0 '0.1 0.5 0.1' -p0 '0 0.4 0.1' -tf 9
|
||||
//
|
||||
// Magnetic mirror effect near a charged sphere and a bar magnet
|
||||
// mpirun -np 4 volta -m ../../data/ball-nurbs.mesh -dbcs 1 -cs '0 0 0 0.1 2e-11' -rs 2 -maxit 4
|
||||
// mpirun -np 4 tesla -m ../../data/fichera.mesh -maxit 4 -rs 3 -bm '-0.1 -0.1 -0.1 0.1 0.1 0.1 0.1 -1e10'
|
||||
// mpirun -np 4 lorentz -er Volta-AMR-Parallel -ec 4 -br Tesla-AMR-Parallel -bc 4 -x0 '0.8 0 0' -p0 '-8 -4 4' -q -10 -tf 0.2 -dt 1e-3 -rf 1e-6
|
||||
//
|
||||
// This miniapp demonstrates the use of the ParMesh::FindPoints functionality
|
||||
// to evaluate field data from stored DataCollection objects. While this
|
||||
// miniapp is far from a full particle-in-cell (PIC) code it does demonstrate
|
||||
// some of the building blocks that might be used to construct the particle
|
||||
// mover portion of a PIC code.
|
||||
|
||||
#include "mfem.hpp"
|
||||
#include "../common/fem_extras.hpp"
|
||||
#include "../common/pfem_extras.hpp"
|
||||
#include "electromagnetics.hpp"
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
using namespace mfem;
|
||||
using namespace mfem::common;
|
||||
using namespace mfem::electromagnetics;
|
||||
|
||||
typedef DataCollection::FieldMapType fields_t;
|
||||
|
||||
/// This class implements the Boris algorithm as described in the
|
||||
/// article `Why is Boris algorithm so good?` by H. Qin et al in
|
||||
/// Physics of Plasmas, Volume 20 Issue 8, August 2013,
|
||||
/// https://doi.org/10.1063/1.4818428.
|
||||
class BorisAlgorithm
|
||||
{
|
||||
private:
|
||||
real_t charge_;
|
||||
real_t mass_;
|
||||
|
||||
ParMesh *E_pmesh_;
|
||||
ParGridFunction *E_field_;
|
||||
|
||||
ParMesh *B_pmesh_;
|
||||
ParGridFunction *B_field_;
|
||||
|
||||
mutable Array<int> elem_id_;
|
||||
mutable Array<IntegrationPoint> ip_;
|
||||
|
||||
mutable Vector E_;
|
||||
mutable Vector B_;
|
||||
mutable Vector pxB_;
|
||||
mutable Vector pm_;
|
||||
mutable Vector pp_;
|
||||
|
||||
// Returns true if a usable V has been found. If @a pgf is NULL, V = 0 is
|
||||
// returned as a default value.
|
||||
bool GetValue(ParMesh *pmesh, ParGridFunction *pgf, Vector q, Vector &V)
|
||||
{
|
||||
DenseMatrix point(q.GetData(), 3, 1);
|
||||
|
||||
int pt_found =
|
||||
(pmesh != NULL) ? pmesh->FindPoints(point, elem_id_, ip_, false) : -1;
|
||||
|
||||
// We have a mesh but the point was not found. The path must be outside
|
||||
// the domain of interest.
|
||||
if (pmesh != NULL && pt_found <= 0) { return false; }
|
||||
|
||||
int pt_root = -1;
|
||||
|
||||
if (pt_found > 0 && elem_id_[0] >= 0 && pgf != NULL)
|
||||
{
|
||||
pt_root = pmesh->GetMyRank();
|
||||
|
||||
pgf->GetVectorValue(elem_id_[0], ip_[0], V);
|
||||
}
|
||||
else
|
||||
{
|
||||
pt_root = 0;
|
||||
V = 0.0;
|
||||
}
|
||||
|
||||
// Determine processor which found the field point
|
||||
int glb_pt_root = -1;
|
||||
MPI_Allreduce(&pt_root, &glb_pt_root, 1,
|
||||
MPI_INT, MPI_MAX, MPI_COMM_WORLD);
|
||||
|
||||
// Send the field value to the root processor
|
||||
if (pmesh != NULL && elem_id_[0] >= 0 && glb_pt_root != 0)
|
||||
{
|
||||
MPI_Send(V.GetData(), 3, MPITypeMap<real_t>::mpi_type,
|
||||
0, 1030, MPI_COMM_WORLD);
|
||||
}
|
||||
|
||||
// Receive the field value on the root processor
|
||||
if (Mpi::Root() && pmesh != NULL && glb_pt_root != 0)
|
||||
{
|
||||
MPI_Status status;
|
||||
MPI_Recv(V.GetData(), 3, MPITypeMap<real_t>::mpi_type,
|
||||
glb_pt_root, 1030, MPI_COMM_WORLD, &status);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public:
|
||||
BorisAlgorithm(ParGridFunction *E_gf,
|
||||
ParGridFunction *B_gf,
|
||||
real_t charge, real_t mass)
|
||||
: charge_(charge), mass_(mass),
|
||||
E_field_(E_gf),
|
||||
B_field_(B_gf),
|
||||
E_(3), B_(3), pxB_(3), pm_(3), pp_(3)
|
||||
{
|
||||
E_pmesh_ = (E_field_) ? E_field_->ParFESpace()->GetParMesh() : NULL;
|
||||
B_pmesh_ = (B_field_) ? B_field_->ParFESpace()->GetParMesh() : NULL;
|
||||
}
|
||||
|
||||
bool Step(Vector &q, Vector &p, real_t &t, real_t &dt)
|
||||
{
|
||||
// Locate current point in each mesh, evaluate the fields, and collect
|
||||
// field values on the root processor.
|
||||
if (!GetValue(E_pmesh_, E_field_, q, E_)) { return false; }
|
||||
if (!GetValue(B_pmesh_, B_field_, q, B_)) { return false; }
|
||||
|
||||
// Compute updated position and momentum using the Boris algorithm
|
||||
if (Mpi::Root())
|
||||
{
|
||||
// Compute half of the contribution from q E
|
||||
add(p, 0.5 * dt * charge_, E_, pm_);
|
||||
|
||||
// Compute the contributiobn from q p x B
|
||||
const real_t B2 = B_ * B_;
|
||||
|
||||
// ... along pm x B
|
||||
const real_t a1 = 4.0 * dt * charge_ * mass_;
|
||||
pm_.cross3D(B_, pxB_);
|
||||
pp_.Set(a1, pxB_);
|
||||
|
||||
// ... along pm
|
||||
const real_t a2 = 4.0 * mass_ * mass_ -
|
||||
dt * dt * charge_ * charge_ * B2;
|
||||
pp_.Add(a2, pm_);
|
||||
|
||||
// ... along B
|
||||
const real_t a3 = 2.0 * dt * dt * charge_ * charge_ * (B_ * p);
|
||||
pp_.Add(a3, B_);
|
||||
|
||||
// scale by common denominator
|
||||
const real_t a4 = 4.0 * mass_ * mass_ +
|
||||
dt * dt * charge_ * charge_ * B2;
|
||||
pp_ /= a4;
|
||||
|
||||
// Update the momentum
|
||||
add(pp_, 0.5 * dt * charge_, E_, p);
|
||||
|
||||
// Update the position
|
||||
q.Add(dt / mass_, p);
|
||||
}
|
||||
|
||||
// Update the time
|
||||
t += dt;
|
||||
|
||||
// Broadcast the updated position
|
||||
MPI_Bcast(q.GetData(), 3, MPITypeMap<real_t>::mpi_type,
|
||||
0, MPI_COMM_WORLD);
|
||||
|
||||
// Broadcast the updated momentum
|
||||
MPI_Bcast(p.GetData(), 3, MPITypeMap<real_t>::mpi_type,
|
||||
0, MPI_COMM_WORLD);
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// Open the named VisItDataCollection and read the named field.
|
||||
// Returns pointers to the two new objects.
|
||||
int ReadGridFunction(const char * coll_name, const char * field_name,
|
||||
int pad_digits_cycle, int pad_digits_rank, int cycle,
|
||||
VisItDataCollection *&dc, ParGridFunction *& gf);
|
||||
|
||||
// By default the initial position will be the center of the intersection
|
||||
// of the bounding boxes of the meshes containing the E and B fields.
|
||||
void SetInitialPosition(VisItDataCollection *E_dc,
|
||||
VisItDataCollection *B_dc,
|
||||
Vector &x_init);
|
||||
|
||||
// Build a quadrilateral mesh approximating the trajectory as a
|
||||
// ribbon. One edge of the ribbon follows the trajectory of the
|
||||
// particle. The opposite edge is offset by the acceleration vector
|
||||
// (scaled by a constant called the r_factor).
|
||||
Mesh MakeTrajectoryMesh(int step, real_t m, real_t dt, real_t r_factor,
|
||||
const DenseMatrix &pos_data,
|
||||
const DenseMatrix &mom_data);
|
||||
|
||||
// Prints the program's logo to the given output stream
|
||||
void display_banner(ostream & os);
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
Mpi::Init(argc, argv);
|
||||
Hypre::Init();
|
||||
|
||||
if ( Mpi::Root() ) { display_banner(cout); }
|
||||
|
||||
const char *E_coll_name = "";
|
||||
const char *E_field_name = "E";
|
||||
int E_cycle = 10;
|
||||
int E_pad_digits_cycle = 6;
|
||||
int E_pad_digits_rank = 6;
|
||||
|
||||
const char *B_coll_name = "";
|
||||
const char *B_field_name = "B";
|
||||
int B_cycle = 10;
|
||||
int B_pad_digits_cycle = 6;
|
||||
int B_pad_digits_rank = 6;
|
||||
|
||||
real_t q = 1.0;
|
||||
real_t m = 1.0;
|
||||
real_t dt = 1e-2;
|
||||
real_t t_init = 0.0;
|
||||
real_t t_final = 1.0;
|
||||
real_t r_factor = -1.0;
|
||||
Vector x_init;
|
||||
Vector p_init;
|
||||
int visport = 19916;
|
||||
bool visualization = true;
|
||||
bool visit = true;
|
||||
|
||||
OptionsParser args(argc, argv);
|
||||
args.AddOption(&E_coll_name, "-er", "--e-root-file",
|
||||
"Set the VisIt data collection E field root file prefix.");
|
||||
args.AddOption(&E_field_name, "-ef", "--e-field-name",
|
||||
"Set the VisIt data collection E field name");
|
||||
args.AddOption(&E_cycle, "-ec", "--e-cycle",
|
||||
"Set the E field cycle index to read.");
|
||||
args.AddOption(&E_pad_digits_cycle, "-epdc", "--e-pad-digits-cycle",
|
||||
"Number of digits in E field cycle.");
|
||||
args.AddOption(&E_pad_digits_rank, "-epdr", "--e-pad-digits-rank",
|
||||
"Number of digits in E field MPI rank.");
|
||||
args.AddOption(&B_coll_name, "-br", "--b-root-file",
|
||||
"Set the VisIt data collection B field root file prefix.");
|
||||
args.AddOption(&B_field_name, "-bf", "--b-field-name",
|
||||
"Set the VisIt data collection B field name");
|
||||
args.AddOption(&B_cycle, "-bc", "--b-cycle",
|
||||
"Set the B field cycle index to read.");
|
||||
args.AddOption(&B_pad_digits_cycle, "-bpdc", "--b-pad-digits-cycle",
|
||||
"Number of digits in B field cycle.");
|
||||
args.AddOption(&B_pad_digits_rank, "-bpdr", "--b-pad-digits-rank",
|
||||
"Number of digits in B field MPI rank.");
|
||||
args.AddOption(&q, "-q", "--charge",
|
||||
"Particle charge.");
|
||||
args.AddOption(&m, "-m", "--mass",
|
||||
"Particle mass.");
|
||||
args.AddOption(&dt, "-dt", "--time-step",
|
||||
"Time Step.");
|
||||
args.AddOption(&t_init, "-ti", "--initial-time",
|
||||
"Initial Time.");
|
||||
args.AddOption(&t_final, "-tf", "--final-time",
|
||||
"Final Time.");
|
||||
args.AddOption(&x_init, "-x0", "--initial-position",
|
||||
"Initial position.");
|
||||
args.AddOption(&p_init, "-p0", "--initial-momentum",
|
||||
"Initial momentum.");
|
||||
args.AddOption(&r_factor, "-rf", "--ribbon-factor",
|
||||
"Scale factor for ribbon width (rf * (p1-p0) / (m * dt) "
|
||||
"where p0 and p1 are computed momenta).");
|
||||
args.AddOption(&visualization, "-vis", "--visualization", "-no-vis",
|
||||
"--no-visualization",
|
||||
"Enable or disable GLVis visualization.");
|
||||
args.AddOption(&visit, "-visit", "--visit", "-no-visit", "--no-visit",
|
||||
"Enable or disable VisIt visualization.");
|
||||
args.AddOption(&visport, "-p", "--send-port", "Socket for GLVis.");
|
||||
args.Parse();
|
||||
if (!args.Good())
|
||||
{
|
||||
if (Mpi::Root())
|
||||
{
|
||||
args.PrintUsage(cout);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
if (r_factor <= 0.0)
|
||||
{
|
||||
r_factor = dt;
|
||||
}
|
||||
if (Mpi::Root())
|
||||
{
|
||||
args.PrintOptions(cout);
|
||||
}
|
||||
|
||||
VisItDataCollection *E_dc = NULL;
|
||||
ParGridFunction *E_gf = NULL;
|
||||
|
||||
if (strcmp(E_coll_name, ""))
|
||||
{
|
||||
if (ReadGridFunction(E_coll_name, E_field_name, E_pad_digits_cycle,
|
||||
E_pad_digits_rank, E_cycle, E_dc, E_gf))
|
||||
{
|
||||
mfem::out << "Error loading E field" << endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
VisItDataCollection *B_dc = NULL;
|
||||
ParGridFunction *B_gf = NULL;
|
||||
|
||||
if (strcmp(B_coll_name, ""))
|
||||
{
|
||||
if (ReadGridFunction(B_coll_name, B_field_name, B_pad_digits_cycle,
|
||||
B_pad_digits_rank, B_cycle, B_dc, B_gf))
|
||||
{
|
||||
mfem::out << "Error loading B field" << endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (x_init.Size() < 3)
|
||||
{
|
||||
SetInitialPosition(E_dc, B_dc, x_init);
|
||||
}
|
||||
if (p_init.Size() < 3)
|
||||
{
|
||||
p_init.SetSize(3); p_init = 0.0;
|
||||
}
|
||||
if (Mpi::Root())
|
||||
{
|
||||
mfem::out << "Initial position: "; x_init.Print(mfem::out);
|
||||
mfem::out << "Initial momentum: "; p_init.Print(mfem::out);
|
||||
}
|
||||
|
||||
BorisAlgorithm boris(E_gf, B_gf, q, m);
|
||||
Vector pos(x_init);
|
||||
Vector mom(p_init);
|
||||
|
||||
ofstream ofs("Lorentz.dat");
|
||||
ofs.precision(14);
|
||||
|
||||
int nsteps = 1 + (int)ceil((t_final - t_init) / dt);
|
||||
DenseMatrix pos_data(3, nsteps);
|
||||
DenseMatrix mom_data(3, nsteps + 1);
|
||||
mom_data.SetCol(0, p_init);
|
||||
|
||||
if (Mpi::Root())
|
||||
{
|
||||
mfem::out << "Maximum number of steps: " << nsteps << endl;
|
||||
}
|
||||
|
||||
int step = -1;
|
||||
real_t t = t_init;
|
||||
do
|
||||
{
|
||||
if (Mpi::Root())
|
||||
{
|
||||
ofs << t
|
||||
<< '\t' << pos[0] << '\t' << pos[1] << '\t' << pos[2]
|
||||
<< '\t' << mom[0] << '\t' << mom[1] << '\t' << mom[2]
|
||||
<< '\n';
|
||||
}
|
||||
step++;
|
||||
|
||||
pos_data.SetCol(step, pos);
|
||||
mom_data.SetCol(step + 1, mom);
|
||||
}
|
||||
while (boris.Step(pos, mom, t, dt) && step < nsteps - 1);
|
||||
|
||||
if (Mpi::Root() && (visit || visualization))
|
||||
{
|
||||
Mesh trajectory = MakeTrajectoryMesh(step, m, dt, r_factor,
|
||||
pos_data, mom_data);
|
||||
|
||||
L2_FECollection fec_l2(0, 2);
|
||||
FiniteElementSpace fes_l2(&trajectory, &fec_l2);
|
||||
GridFunction traj_time(&fes_l2);
|
||||
for (int i=0; i<step; i++)
|
||||
{
|
||||
traj_time[i] = dt * i;
|
||||
}
|
||||
|
||||
if (visit)
|
||||
{
|
||||
VisItDataCollection visit_dc("Lorentz", &trajectory);
|
||||
visit_dc.RegisterField("Time", &traj_time);
|
||||
visit_dc.SetCycle(step);
|
||||
visit_dc.SetTime(step * dt);
|
||||
visit_dc.Save();
|
||||
}
|
||||
|
||||
if (visualization)
|
||||
{
|
||||
socketstream traj_sock;
|
||||
traj_sock.precision(8);
|
||||
|
||||
char vishost[] = "localhost";
|
||||
|
||||
int Wx = 0, Wy = 0; // window position
|
||||
int Ww = 350, Wh = 350; // window size
|
||||
|
||||
VisualizeField(traj_sock, vishost, visport,
|
||||
traj_time, "Trajectory", Wx, Wy, Ww, Wh);
|
||||
}
|
||||
}
|
||||
if (Mpi::Root())
|
||||
{
|
||||
mfem::out << "Number of steps taken: " << step << endl;
|
||||
}
|
||||
|
||||
// Clean up
|
||||
delete E_dc;
|
||||
delete B_dc;
|
||||
}
|
||||
|
||||
// Print the Lorentz ascii logo to the given ostream
|
||||
void display_banner(ostream & os)
|
||||
{
|
||||
os << " ____ __ "
|
||||
<< endl
|
||||
<< " | | ___________ ____ _____/ |_________"
|
||||
<< endl
|
||||
<< " | | / _ \\_ __ \\_/ __ \\ / \\ __\\___ /"
|
||||
<< endl
|
||||
<< " | |__( <_> ) | \\/\\ ___/| | \\ | / / "
|
||||
<< endl
|
||||
<< " |_______ \\____/|__| \\___ >___| /__| /_____ \\"
|
||||
<< endl
|
||||
<< " \\/ \\/ \\/ \\/"
|
||||
<< endl << flush;
|
||||
}
|
||||
|
||||
int ReadGridFunction(const char * coll_name, const char * field_name,
|
||||
int pad_digits_cycle, int pad_digits_rank, int cycle,
|
||||
VisItDataCollection *&dc, ParGridFunction *& gf)
|
||||
{
|
||||
dc = new VisItDataCollection(MPI_COMM_WORLD, coll_name);
|
||||
dc->SetPadDigitsCycle(pad_digits_cycle);
|
||||
dc->SetPadDigitsRank(pad_digits_rank);
|
||||
dc->Load(cycle);
|
||||
|
||||
if (dc->Error() != DataCollection::No_Error)
|
||||
{
|
||||
mfem::out << "Error loading VisIt data collection: "
|
||||
<< coll_name << endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (dc->GetMesh()->Dimension() < 3)
|
||||
{
|
||||
mfem::out << "Field must be defined on a three dimensional mesh"
|
||||
<< endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (dc->HasField(field_name))
|
||||
{
|
||||
gf = dc->GetParField(field_name);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void SetInitialPosition(VisItDataCollection *E_dc,
|
||||
VisItDataCollection *B_dc,
|
||||
Vector &x_init)
|
||||
{
|
||||
x_init.SetSize(3); x_init = 0.0;
|
||||
|
||||
if (E_dc != NULL || B_dc != NULL)
|
||||
{
|
||||
Vector E_p_min(3); E_p_min = -infinity();
|
||||
Vector E_p_max(3); E_p_max = infinity();
|
||||
if (E_dc != NULL)
|
||||
{
|
||||
ParMesh * E_pmesh = dynamic_cast<ParMesh*>(E_dc->GetMesh());
|
||||
E_pmesh->GetBoundingBox(E_p_min, E_p_max);
|
||||
}
|
||||
|
||||
Vector B_p_min(3); B_p_min = -infinity();
|
||||
Vector B_p_max(3); B_p_max = infinity();
|
||||
if (B_dc != NULL)
|
||||
{
|
||||
ParMesh *B_pmesh = dynamic_cast<ParMesh*>(B_dc->GetMesh());
|
||||
B_pmesh->GetBoundingBox(B_p_min, B_p_max);
|
||||
}
|
||||
|
||||
for (int d = 0; d<3; d++)
|
||||
{
|
||||
const real_t p_min = std::max(E_p_min[d], B_p_min[d]);
|
||||
const real_t p_max = std::min(E_p_max[d], B_p_max[d]);
|
||||
x_init[d] = 0.5 * (p_min + p_max);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Mesh MakeTrajectoryMesh(int step, real_t m, real_t dt, real_t r_factor,
|
||||
const DenseMatrix &pos_data,
|
||||
const DenseMatrix &mom_data)
|
||||
{
|
||||
Mesh trajectory(2, 2 * (step + 1), step, 0, 3);
|
||||
|
||||
for (int i=0; i<=step; i++)
|
||||
{
|
||||
trajectory.AddVertex(pos_data(0,i), pos_data(1,i), pos_data(2,i));
|
||||
|
||||
real_t dpx = (mom_data(0, i + 1) - mom_data(0, i)) / (m * dt);
|
||||
real_t dpy = (mom_data(1, i + 1) - mom_data(1, i)) / (m * dt);
|
||||
real_t dpz = (mom_data(2, i + 1) - mom_data(2, i)) / (m * dt);
|
||||
|
||||
trajectory.AddVertex(pos_data(0,i) + r_factor * dpx,
|
||||
pos_data(1,i) + r_factor * dpy,
|
||||
pos_data(2,i) + r_factor * dpz);
|
||||
}
|
||||
|
||||
int v[4];
|
||||
for (int i=0; i<step; i++)
|
||||
{
|
||||
v[0] = 2 * i;
|
||||
v[1] = 2 * (i + 1);
|
||||
v[2] = 2 * (i + 1) + 1;
|
||||
v[3] = 2 * i + 1;
|
||||
|
||||
trajectory.AddQuad(v);
|
||||
}
|
||||
|
||||
trajectory.FinalizeQuadMesh(1);
|
||||
|
||||
return trajectory;
|
||||
}
|
||||
@@ -17,15 +17,11 @@ SRC = $(if $(MFEM_DIR:../..=),$(MFEM_DIR)/miniapps/electromagnetics/,)
|
||||
CONFIG_MK = $(or $(wildcard $(MFEM_BUILD_DIR)/config/config.mk),\
|
||||
$(wildcard $(MFEM_INSTALL_DIR)/share/mfem/config.mk))
|
||||
|
||||
# Include defaults.mk to get XLINKER
|
||||
DEFAULTS_MK = $(MFEM_DIR)/config/defaults.mk
|
||||
include $(DEFAULTS_MK)
|
||||
|
||||
MFEM_LIB_FILE = mfem_is_not_built
|
||||
-include $(CONFIG_MK)
|
||||
|
||||
SEQ_MINIAPPS =
|
||||
PAR_MINIAPPS = volta tesla maxwell joule
|
||||
PAR_MINIAPPS = volta tesla maxwell joule lorentz
|
||||
ifeq ($(MFEM_USE_MPI),NO)
|
||||
MINIAPPS = $(SEQ_MINIAPPS)
|
||||
else
|
||||
@@ -41,8 +37,7 @@ COMMON_LIB = -L$(MFEM_BUILD_DIR)/miniapps/common -lmfem-common
|
||||
|
||||
# If MFEM_SHARED is set, add the ../common rpath
|
||||
COMMON_LIB += $(if $(MFEM_SHARED:YES=),,\
|
||||
$(if $(MFEM_USE_CUDA:YES=),$(CXX_XLINKER),$(CUDA_XLINKER))-rpath,$(abspath\
|
||||
$(MFEM_BUILD_DIR)/miniapps/common))
|
||||
$(MFEM_XLINKER)-rpath,$(abspath $(MFEM_BUILD_DIR)/miniapps/common))
|
||||
|
||||
# Remove built-in rules
|
||||
%: %.cpp
|
||||
@@ -56,6 +51,10 @@ all: $(MINIAPPS)
|
||||
$(MFEM_CXX) $(MFEM_LINK_FLAGS) -o $@ $@.o $@_solver.o $(COMMON_LIB) \
|
||||
$(MFEM_LIBS)
|
||||
|
||||
lorentz: %: $(SRC)%.cpp $(MFEM_LIB_FILE) $(CONFIG_MK) | lib-common
|
||||
$(MFEM_CXX) $(MFEM_FLAGS) -c $(<)
|
||||
$(MFEM_CXX) $(MFEM_LINK_FLAGS) -o $@ $@.o $(COMMON_LIB) $(MFEM_LIBS)
|
||||
|
||||
# Rules for compiling miniapp dependencies
|
||||
$(addsuffix _solver.o,$(MINIAPPS)): \
|
||||
%.o: $(SRC)%.cpp $(SRC)%.hpp $(CONFIG_MK)
|
||||
@@ -86,7 +85,7 @@ include $(MFEM_TEST_MK)
|
||||
|
||||
# Testing: Specific execution options
|
||||
RUN_MPI = $(MFEM_MPIEXEC) $(MFEM_MPIEXEC_NP) $(MFEM_MPI_NP)
|
||||
volta-test-par: volta-test-1 volta-test-2
|
||||
volta-test-par: volta-test-1 volta-test-2 volta-test-3
|
||||
volta-test-1: volta
|
||||
@$(call mfem-test,$<, $(RUN_MPI), Electromagnetic miniapp,\
|
||||
-maxit 2 -dbcs 1 -dbcg -ds '0.0 0.0 0.0 0.2 8.0')
|
||||
@@ -94,15 +93,29 @@ volta-test-2: volta
|
||||
@$(call mfem-test,$<, $(RUN_MPI), Electromagnetic miniapp,\
|
||||
-maxit 2 -m ../../data/square-disc.mesh \
|
||||
-dbcs '1 2 3 4 5 6 7 8' -dbcv '0 0 0 0 1 1 1 1')
|
||||
tesla-test-par: tesla
|
||||
volta-test-3: volta
|
||||
@$(call mfem-test,$<, $(RUN_MPI), Electromagnetic miniapp,\
|
||||
-maxit 2 -m ../../data/inline-hex.mesh -dbcs '1 6' -dbcv '0 1')
|
||||
tesla-test-par: tesla-test-1 tesla-test-2
|
||||
tesla-test-1: tesla
|
||||
@$(call mfem-test,$<, $(RUN_MPI), Electromagnetic miniapp,\
|
||||
-maxit 2 -cr '0 0 -0.2 0 0 0.2 0.2 0.4 1')
|
||||
tesla-test-2: tesla
|
||||
@$(call mfem-test,$<, $(RUN_MPI), Electromagnetic miniapp,\
|
||||
-maxit 2 -m ../../data/inline-hex.mesh -ubbc '0 0 1')
|
||||
maxwell-test-par: maxwell
|
||||
@$(call mfem-test,$<, $(RUN_MPI), Electromagnetic miniapp,\
|
||||
-abcs '-1' -dp '-0.3 0.0 0.0 0.3 0.0 0.0 0.1 1 .5 .5')
|
||||
joule-test-par: joule
|
||||
@$(call mfem-test,$<, $(RUN_MPI), Electromagnetic miniapp,\
|
||||
-m cylinder-hex.mesh -p rod -tf 3)
|
||||
lorentz-test-par: lorentz-test-1 lorentz-test-2
|
||||
lorentz-test-1: lorentz volta-test-3
|
||||
@$(call mfem-test,$<, $(RUN_MPI), Electromagnetic miniapp,\
|
||||
-er Volta-AMR-Parallel -ec 2 -x0 '0.5 0.5 0.9' -p0 '1 0 0')
|
||||
lorentz-test-2: lorentz tesla-test-2
|
||||
@$(call mfem-test,$<, $(RUN_MPI), Electromagnetic miniapp,\
|
||||
-br Tesla-AMR-Parallel -bc 2 -x0 '0.1 0.5 0.1' -p0 '0 0.4 0.1' -tf 9)
|
||||
|
||||
# Testing: "test" target and mfem-test* variables are defined in config/test.mk
|
||||
|
||||
@@ -117,4 +130,4 @@ clean-build:
|
||||
rm -rf *.dSYM *.TVD.*breakpoints
|
||||
|
||||
clean-exec:
|
||||
@rm -rf Volta-AMR* Tesla-AMR* Maxwell-Parallel* Joule_*
|
||||
@rm -rf Volta-AMR* Tesla-AMR* Maxwell-Parallel* Joule_* Lorentz*
|
||||
|
||||
@@ -253,6 +253,7 @@ int main(int argc, char *argv[])
|
||||
|
||||
// Initialize VisIt visualization
|
||||
VisItDataCollection visit_dc("Tesla-AMR-Parallel", &pmesh);
|
||||
visit_dc.SetFormat(DataCollection::PARALLEL_FORMAT);
|
||||
|
||||
if ( visit )
|
||||
{
|
||||
|
||||
@@ -266,6 +266,7 @@ int main(int argc, char *argv[])
|
||||
|
||||
// Initialize VisIt visualization
|
||||
VisItDataCollection visit_dc("Volta-AMR-Parallel", &pmesh);
|
||||
visit_dc.SetFormat(DataCollection::PARALLEL_FORMAT);
|
||||
|
||||
if ( visit )
|
||||
{
|
||||
|
||||
@@ -17,10 +17,6 @@ SRC = $(if $(MFEM_DIR:../..=),$(MFEM_DIR)/miniapps/gslib/,)
|
||||
CONFIG_MK = $(or $(wildcard $(MFEM_BUILD_DIR)/config/config.mk),\
|
||||
$(wildcard $(MFEM_INSTALL_DIR)/share/mfem/config.mk))
|
||||
|
||||
# Include defaults.mk to get XLINKER
|
||||
DEFAULTS_MK = $(MFEM_DIR)/config/defaults.mk
|
||||
include $(DEFAULTS_MK)
|
||||
|
||||
MFEM_LIB_FILE = mfem_is_not_built
|
||||
-include $(CONFIG_MK)
|
||||
|
||||
@@ -42,8 +38,7 @@ COMMON_LIB = -L$(MFEM_BUILD_DIR)/miniapps/common -lmfem-common
|
||||
|
||||
# If MFEM_SHARED is set, add the ../common rpath
|
||||
COMMON_LIB += $(if $(MFEM_SHARED:YES=),,\
|
||||
$(if $(MFEM_USE_CUDA:YES=),$(CXX_XLINKER),$(CUDA_XLINKER))-rpath,$(abspath\
|
||||
$(MFEM_BUILD_DIR)/miniapps/common))
|
||||
$(MFEM_XLINKER)-rpath,$(abspath $(MFEM_BUILD_DIR)/miniapps/common))
|
||||
|
||||
.SUFFIXES:
|
||||
.SUFFIXES: .o .cpp .mk
|
||||
|
||||
@@ -17,10 +17,6 @@ SRC = $(if $(MFEM_DIR:../..=),$(MFEM_DIR)/miniapps/meshing/,)
|
||||
CONFIG_MK = $(or $(wildcard $(MFEM_BUILD_DIR)/config/config.mk),\
|
||||
$(wildcard $(MFEM_INSTALL_DIR)/share/mfem/config.mk))
|
||||
|
||||
# Include defaults.mk to get XLINKER
|
||||
DEFAULTS_MK = $(MFEM_DIR)/config/defaults.mk
|
||||
include $(DEFAULTS_MK)
|
||||
|
||||
MFEM_LIB_FILE = mfem_is_not_built
|
||||
-include $(CONFIG_MK)
|
||||
|
||||
@@ -39,8 +35,7 @@ COMMON_LIB = -L$(MFEM_BUILD_DIR)/miniapps/common -lmfem-common
|
||||
|
||||
# If MFEM_SHARED is set, add the ../common rpath
|
||||
COMMON_LIB += $(if $(MFEM_SHARED:YES=),,\
|
||||
$(if $(MFEM_USE_CUDA:YES=),$(CXX_XLINKER),$(CUDA_XLINKER))-rpath,$(abspath\
|
||||
$(MFEM_BUILD_DIR)/miniapps/common))
|
||||
$(MFEM_XLINKER)-rpath,$(abspath $(MFEM_BUILD_DIR)/miniapps/common))
|
||||
|
||||
.SUFFIXES:
|
||||
.SUFFIXES: .o .cpp .mk
|
||||
|
||||
@@ -9,31 +9,46 @@
|
||||
# terms of the BSD-3 license. We welcome feedback and contributions, see file
|
||||
# CONTRIBUTING.md for details.
|
||||
|
||||
list(APPEND SEQMTOP_COMMON_SOURCES
|
||||
paramnonlinearform.cpp
|
||||
mtop_integrators.cpp)
|
||||
|
||||
list(APPEND SEQMTOP_COMMON_HEADERS
|
||||
paramnonlinearform.hpp
|
||||
mtop_integrators.hpp)
|
||||
|
||||
convert_filenames_to_full_paths(SEQMTOP_COMMON_SOURCES)
|
||||
convert_filenames_to_full_paths(SEQMTOP_COMMON_HEADERS)
|
||||
|
||||
set(SEQMTOP_COMMON_FILES
|
||||
EXTRA_SOURCES ${SEQMTOP_COMMON_SOURCES}
|
||||
EXTRA_HEADERS ${SEQMTOP_COMMON_HEADERS})
|
||||
|
||||
add_mfem_miniapp(seqheat
|
||||
MAIN seqheat.cpp
|
||||
${SEQMTOP_COMMON_FILES}
|
||||
LIBRARIES mfem)
|
||||
|
||||
if(MFEM_USE_MPI)
|
||||
|
||||
list(APPEND PARMTOP_COMMON_SOURCES
|
||||
mtop_solvers.cpp
|
||||
grain_reader.cpp)
|
||||
pparamnonlinearform.cpp)
|
||||
list(APPEND PARMTOP_COMMON_HEADERS
|
||||
mtop_solvers.hpp
|
||||
grain_reader.hpp)
|
||||
pparamnonlinearform.hpp)
|
||||
|
||||
convert_filenames_to_full_paths(PARMTOP_COMMON_SOURCES)
|
||||
convert_filenames_to_full_paths(PARMTOP_COMMON_HEADERS)
|
||||
|
||||
set(PARMTOP_COMMON_FILES
|
||||
EXTRA_SOURCES ${PARMTOP_COMMON_SOURCES} ${SEQMTOP_COMMON_SOURCES}
|
||||
EXTRA_HEADERS ${PARMTOP_COMMON_HEADERS} ${SEQMTOP_COMMON_HEADERS})
|
||||
EXTRA_SOURCES ${PARMTOP_COMMON_SOURCES} ${SEQMTOP_COMMON_SOURCES}
|
||||
EXTRA_HEADERS ${PARMTOP_COMMON_HEADERS} ${SEQMTOP_COMMON_HEADERS})
|
||||
|
||||
# message(STATUS "PARMTOP_COMMON_FILES: ${PARMTOP_COMMON_FILES}")
|
||||
# message(STATUS "SEQMTOP_COMMON_FILES: ${SEQMTOP_COMMON_FILES}")
|
||||
|
||||
add_mfem_miniapp(test_stokes
|
||||
MAIN test_stokes.cpp
|
||||
${PARMTOP_COMMON_FILES}
|
||||
LIBRARIES mfem)
|
||||
|
||||
add_mfem_miniapp(test_grain
|
||||
MAIN test_grain.cpp
|
||||
${PARMTOP_COMMON_FILES}
|
||||
LIBRARIES mfem)
|
||||
add_mfem_miniapp(parheat
|
||||
MAIN parheat.cpp
|
||||
${PARMTOP_COMMON_FILES}
|
||||
LIBRARIES mfem)
|
||||
|
||||
endif ()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,419 +0,0 @@
|
||||
// Copyright (c) 2010-2021, 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 <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#include "grain_reader.hpp"
|
||||
#include "mfem.hpp"
|
||||
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
bool
|
||||
file_exists( const std::string & aPath )
|
||||
{
|
||||
// test if file exists
|
||||
std::ifstream tFile( aPath );
|
||||
|
||||
// save result into output variable
|
||||
bool aFileExists;
|
||||
|
||||
if( tFile )
|
||||
{
|
||||
// close file
|
||||
tFile.close();
|
||||
aFileExists = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
aFileExists = false;
|
||||
}
|
||||
|
||||
return aFileExists;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
std::vector<std::string> split_string(
|
||||
const std::string & aString,
|
||||
const std::string & aDelim)
|
||||
{
|
||||
// create empty cell of strings
|
||||
std::vector<std::string> VectorOfStrings;
|
||||
|
||||
size_t start;
|
||||
size_t end = 0;
|
||||
|
||||
while ((start = aString.find_first_not_of(aDelim, end)) != std::string::npos)
|
||||
{
|
||||
end = aString.find(aDelim, start);
|
||||
VectorOfStrings.push_back(aString.substr(start, end - start));
|
||||
}
|
||||
|
||||
return VectorOfStrings;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
Ascii::Ascii( const std::string & aPath, const FileMode & aMode ) :
|
||||
mMode( aMode )
|
||||
{
|
||||
// test if path is absolute
|
||||
if( aPath.substr( 0,1 ) == "/" )
|
||||
{
|
||||
mPath = aPath;
|
||||
}
|
||||
// test if path is relative
|
||||
else if( aPath.substr( 0,2 ) == "./" )
|
||||
{
|
||||
mPath = aPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
MFEM_ASSERT( false, "");
|
||||
//mPath = std::sprint( "%s/%s", std::getenv( "PWD" ), aPath.c_str() );
|
||||
}
|
||||
|
||||
switch ( aMode )
|
||||
{
|
||||
case( FileMode::OPEN_RDONLY ) :
|
||||
{
|
||||
this->load_buffer();
|
||||
break;
|
||||
}
|
||||
case( FileMode::NEW ) :
|
||||
{
|
||||
mBuffer.clear();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
MFEM_ASSERT( false, "Unknown file mode for ASCII file" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
Ascii::~Ascii()
|
||||
{
|
||||
MFEM_ASSERT( ! mChangedSinceLastSave, "File was changed but never saved." );
|
||||
|
||||
mBuffer.clear();
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
bool Ascii::save()
|
||||
{
|
||||
MFEM_ASSERT( mMode != FileMode::OPEN_RDONLY,
|
||||
"File can't be saved since it is opened in write protected mode." );
|
||||
|
||||
// open file
|
||||
std::ofstream tFile( mPath.c_str(), std::ofstream::trunc );
|
||||
|
||||
if( tFile )
|
||||
{
|
||||
// save buffer to file
|
||||
for( std::string & tLine : mBuffer )
|
||||
{
|
||||
tFile << tLine << std::endl;
|
||||
}
|
||||
|
||||
tFile.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
MFEM_ASSERT( false,
|
||||
"Something went wrong while trying to save." );
|
||||
}
|
||||
|
||||
mChangedSinceLastSave = false;
|
||||
|
||||
return mChangedSinceLastSave;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
int Ascii::length() const
|
||||
{
|
||||
return mBuffer.size();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
std::string & Ascii::line( const int aLineNumber )
|
||||
{
|
||||
return mBuffer[aLineNumber];
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const std::string & Ascii::line( const int aLineNumber ) const
|
||||
{
|
||||
return mBuffer[aLineNumber];
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void Ascii::print( const std::string & aLine )
|
||||
{
|
||||
mBuffer.push_back( aLine );
|
||||
|
||||
mChangedSinceLastSave = true;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void Ascii::load_buffer()
|
||||
{
|
||||
// tidy up buffer
|
||||
mBuffer.clear();
|
||||
|
||||
// make sure that file exists
|
||||
MFEM_ASSERT( file_exists( mPath ),
|
||||
"File does not exist." );
|
||||
|
||||
// open file
|
||||
std::ifstream tFile( mPath );
|
||||
|
||||
// test if file can be opened
|
||||
if( tFile )
|
||||
{
|
||||
// temporary container for string
|
||||
std::string tLine;
|
||||
|
||||
while ( std::getline( tFile, tLine ) )
|
||||
{
|
||||
mBuffer.push_back( tLine );
|
||||
}
|
||||
|
||||
// close file
|
||||
tFile.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
MFEM_ASSERT( false, "Someting went wrong while opening file\n " );
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
GrainReader::GrainReader( mfem::ParMesh * mesh, std::string & name )
|
||||
: mesh_(mesh), name_(name)
|
||||
{
|
||||
dim = mesh->Dimension();
|
||||
|
||||
int tNumVertices = mesh->GetNV();
|
||||
for (int i = 0; i < tNumVertices; ++i)
|
||||
{
|
||||
double * Coords = mesh->GetVertex(i);
|
||||
|
||||
xMax = std::max(xMax, Coords[ 0 ]);
|
||||
yMax = std::max(yMax, Coords[ 1 ]);
|
||||
zMax = std::max(zMax, Coords[ 2 ]);
|
||||
|
||||
xMin = std::min(xMin, Coords[ 0 ]);
|
||||
yMin = std::min(yMin, Coords[ 1 ]);
|
||||
zMin = std::min(zMin, Coords[ 2 ]);
|
||||
}
|
||||
|
||||
MPI_Allreduce(MPI_IN_PLACE, &xMax, 1, MPI_DOUBLE, MPI_MAX, mesh_->GetComm());
|
||||
MPI_Allreduce(MPI_IN_PLACE, &yMax, 1, MPI_DOUBLE, MPI_MAX, mesh_->GetComm());
|
||||
MPI_Allreduce(MPI_IN_PLACE, &zMax, 1, MPI_DOUBLE, MPI_MAX, mesh_->GetComm());
|
||||
|
||||
MPI_Allreduce(MPI_IN_PLACE, &xMin, 1, MPI_DOUBLE, MPI_MIN, mesh_->GetComm());
|
||||
MPI_Allreduce(MPI_IN_PLACE, &yMin, 1, MPI_DOUBLE, MPI_MIN, mesh_->GetComm());
|
||||
MPI_Allreduce(MPI_IN_PLACE, &zMin, 1, MPI_DOUBLE, MPI_MIN, mesh_->GetComm());
|
||||
|
||||
Lx = xMax - xMin;
|
||||
Ly = yMax - yMin;
|
||||
Lz = zMax - zMin;
|
||||
|
||||
this->readGrainFile();
|
||||
}
|
||||
|
||||
void GrainReader::readGrainFile()
|
||||
{
|
||||
mfem::Ascii tAsciiReader( name_, FileMode::OPEN_RDONLY );
|
||||
|
||||
int tNumLines = tAsciiReader.length();
|
||||
|
||||
particalType.reserve(9*tNumLines);
|
||||
xPos.reserve(9*tNumLines);
|
||||
yPos.reserve(9*tNumLines);
|
||||
zPos.reserve(9*tNumLines);
|
||||
rad.reserve(9*tNumLines);
|
||||
|
||||
real_t maxRad = 0.0;
|
||||
|
||||
for( int Ik = 0; Ik < tNumLines; Ik++ )
|
||||
{
|
||||
const std::string & tFileLine = tAsciiReader.line( Ik );
|
||||
|
||||
std::vector<std::string> ListOfStrings = split_string( tFileLine, " " );
|
||||
|
||||
particalType.push_back(std::stod( ListOfStrings[1] ));
|
||||
xPos .push_back(std::stod( ListOfStrings[2] ));
|
||||
yPos .push_back(std::stod( ListOfStrings[3] ));
|
||||
zPos .push_back(std::stod( ListOfStrings[4] ));
|
||||
rad .push_back(std::stod( ListOfStrings[5] ) / 2.0);
|
||||
|
||||
maxRad = std::max( rad[Ik], maxRad );
|
||||
}
|
||||
|
||||
maxRad += 1e-6;
|
||||
|
||||
for( int Ik = 0; Ik < tNumLines; Ik++ )
|
||||
{
|
||||
int pType = particalType[Ik];
|
||||
real_t xCopy = xPos[Ik];
|
||||
real_t yCopy = yPos[Ik];
|
||||
real_t zCopy = zPos[Ik];
|
||||
real_t radCopy = rad[Ik];
|
||||
|
||||
real_t xC;
|
||||
real_t yC;
|
||||
|
||||
bool isCopyX = false;
|
||||
bool isCopyY = false;
|
||||
bool isCopyCorner = false;
|
||||
|
||||
if(xPos[Ik] < (xMin + maxRad)){
|
||||
xC = xCopy + Lx;
|
||||
isCopyX = true; }
|
||||
else if(xPos[Ik] > (xMin + Lx - maxRad)) {
|
||||
xC = xCopy - Lx;
|
||||
isCopyX = true; }
|
||||
|
||||
if(isCopyX)
|
||||
{
|
||||
particalType.push_back(pType);
|
||||
xPos .push_back(xC);
|
||||
yPos .push_back(yCopy);
|
||||
zPos .push_back(zCopy);
|
||||
rad .push_back(radCopy);
|
||||
}
|
||||
|
||||
if(yPos[Ik] < (yMin + maxRad)) {
|
||||
yC = yCopy + Ly;
|
||||
isCopyY = true; }
|
||||
else if(yPos[Ik] > (yMin + Ly - maxRad)) {
|
||||
yC = yCopy - Ly;
|
||||
isCopyY = true; }
|
||||
|
||||
if(isCopyY)
|
||||
{
|
||||
particalType.push_back(pType);
|
||||
xPos .push_back(xCopy);
|
||||
yPos .push_back(yC);
|
||||
zPos .push_back(zCopy);
|
||||
rad .push_back(radCopy);
|
||||
}
|
||||
|
||||
if(xPos[Ik] < (xMin + maxRad) && yPos[Ik] < (yMin + maxRad)) {
|
||||
xC = xCopy + Lx;
|
||||
yC = yCopy + Ly;
|
||||
isCopyCorner = true; }
|
||||
else if(xPos[Ik] < (xMin + maxRad) && yPos[Ik] > (yMin + Ly - maxRad)) {
|
||||
xC = xCopy + Lx;
|
||||
yC = yCopy - Ly;
|
||||
isCopyCorner = true; }
|
||||
|
||||
else if(xPos[Ik] > (xMin + Lx - maxRad) && yPos[Ik] < (yMin + maxRad)) {
|
||||
xC = xCopy - Lx;
|
||||
yC = yCopy + Ly;
|
||||
isCopyCorner = true; }
|
||||
else if(xPos[Ik] > (xMin + Lx - maxRad) && yPos[Ik] > (yMin + Ly - maxRad)) {
|
||||
xC = xCopy - Lx;
|
||||
yC = yCopy - Ly;
|
||||
isCopyCorner = true; }
|
||||
|
||||
if(isCopyCorner)
|
||||
{
|
||||
particalType.push_back(pType);
|
||||
xPos .push_back(xC);
|
||||
yPos .push_back(yC);
|
||||
zPos .push_back(zCopy);
|
||||
rad .push_back(radCopy);
|
||||
}
|
||||
}
|
||||
|
||||
particalType.shrink_to_fit();
|
||||
xPos .shrink_to_fit();
|
||||
yPos .shrink_to_fit();
|
||||
zPos .shrink_to_fit();
|
||||
rad .shrink_to_fit();
|
||||
|
||||
numParticles = particalType.size();
|
||||
}
|
||||
|
||||
void GrainReader::computeGridFunction( ::mfem::ParFiniteElementSpace& feSpace)
|
||||
{
|
||||
grainLSField.SetSpace(&feSpace);
|
||||
|
||||
int numNodes = grainLSField.Size();
|
||||
mfem::Vector locationVector(dim);
|
||||
|
||||
int numEle = mesh_->GetNE();
|
||||
|
||||
for ( int e = 0; e<numEle; e++)
|
||||
{
|
||||
const IntegrationRule &ir = feSpace.GetFE(e)->GetNodes();
|
||||
|
||||
// Transformation of the element with the pos_mesh coordinates.
|
||||
mfem::IsoparametricTransformation Tr;
|
||||
feSpace.GetElementTransformation(e, &Tr);
|
||||
|
||||
mfem::DenseMatrix pos_nodes;
|
||||
Tr.Transform(ir, pos_nodes);
|
||||
mfem::Vector valVec(pos_nodes.NumCols());
|
||||
|
||||
for ( int Ik = 0; Ik< pos_nodes.NumCols(); Ik++)
|
||||
{
|
||||
double LSVal = -1000.0;
|
||||
for (int ii = 0; ii < numParticles; ii++)
|
||||
{
|
||||
double val = rad[ii] - pow(pow(std::abs(pos_nodes(0,Ik) - xPos[ii]), 2)
|
||||
+ pow(std::abs(pos_nodes(1,Ik) - yPos[ii]), 2) + pow(std::abs(pos_nodes(2,Ik) - zPos[ii]), 2), 0.5);
|
||||
|
||||
LSVal = std::max(val, LSVal);
|
||||
}
|
||||
valVec[Ik]= LSVal;
|
||||
}
|
||||
|
||||
mfem::Array< int > dofs;
|
||||
feSpace.GetElementDofs( e, dofs );
|
||||
|
||||
grainLSField.SetSubVector(dofs, valVec);
|
||||
}
|
||||
|
||||
// for ( int Ik = 0; Ik<numNodes; Ik++)
|
||||
// {
|
||||
// mesh_->GetNode(Ik, &locationVector[0]);
|
||||
// const double * pCoords(locationVector.GetData());
|
||||
|
||||
// double LSVal = -1000.0;
|
||||
// for (int ii = 0; ii < numParticles; ii++)
|
||||
// {
|
||||
// double val = rad[ii] - pow(pow(std::abs(pCoords[0] - xPos[ii]), 2)
|
||||
// + pow(std::abs(pCoords[1] - yPos[ii]), 2) + pow(std::abs(pCoords[2] - zPos[ii]), 2), 0.5);
|
||||
|
||||
// LSVal = std::max(val, LSVal);
|
||||
// }
|
||||
// grainLSField[Ik]= LSVal;
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,199 +0,0 @@
|
||||
// Copyright (c) 2010-2021, 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 PROJECTS_ASCII_HPP_
|
||||
#define PROJECTS_ASCII_HPP_
|
||||
|
||||
#include <vector>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#include <fstream>
|
||||
#include "mfem.hpp"
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
enum class FileMode
|
||||
{
|
||||
NEW,
|
||||
OPEN_RDONLY,
|
||||
OPEN_RDWR
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* this function tests if a file exists
|
||||
* @param aPath
|
||||
* @return
|
||||
*/
|
||||
bool file_exists( const std::string & aPath );
|
||||
|
||||
|
||||
std::vector<std::string> split_string(
|
||||
const std::string & aString,
|
||||
const std::string & aDelim);
|
||||
|
||||
class Ascii
|
||||
{
|
||||
//------------------------------------------------------------------------------
|
||||
protected:
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
std::string mPath;
|
||||
const FileMode mMode;
|
||||
std::vector< std::string > mBuffer;
|
||||
|
||||
bool mChangedSinceLastSave = false;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
public:
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
Ascii(
|
||||
const std::string & aPath,
|
||||
const enum FileMode & aMode );
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
~Ascii();
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// save the buffer to the file
|
||||
bool save();
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void print( const std::string & aLine );
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* return the number of lines
|
||||
*/
|
||||
int length() const;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
std::string & line( const int aLineNumber );
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const std::string & line( const int aLineNumber ) const;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
private:
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void load_buffer();
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template<typename T>
|
||||
inline std::string stringify(T aValue)
|
||||
{
|
||||
std::ostringstream out;
|
||||
out << aValue;
|
||||
return out.str();
|
||||
}
|
||||
|
||||
template<>
|
||||
inline std::string stringify<bool>(bool aValue)
|
||||
{
|
||||
std::ostringstream out;
|
||||
out << std::boolalpha << aValue;
|
||||
return out.str();
|
||||
}
|
||||
|
||||
template<>
|
||||
inline std::string stringify<double>(double aValue)
|
||||
{
|
||||
std::ostringstream out;
|
||||
out << std::setprecision(14) << std::scientific << aValue;
|
||||
return out.str();
|
||||
}
|
||||
|
||||
template<>
|
||||
inline std::string stringify<long double>(long double aValue)
|
||||
{
|
||||
std::ostringstream out;
|
||||
out << std::setprecision(14) << std::scientific << aValue;
|
||||
return out.str();
|
||||
}
|
||||
|
||||
template<>
|
||||
inline std::string stringify<float>(float aValue)
|
||||
{
|
||||
std::ostringstream out;
|
||||
out << std::setprecision(14) << std::scientific << aValue;
|
||||
return out.str();
|
||||
}
|
||||
|
||||
|
||||
class GrainReader
|
||||
{
|
||||
private:
|
||||
mfem::ParMesh * mesh_;
|
||||
std::string name_;
|
||||
int dim;
|
||||
|
||||
double xMax = DBL_MIN;
|
||||
double yMax = DBL_MIN;
|
||||
double zMax = DBL_MIN;
|
||||
double xMin = DBL_MAX;
|
||||
double yMin = DBL_MAX;
|
||||
double zMin = DBL_MAX;
|
||||
|
||||
double Lx, Ly, Lz;
|
||||
|
||||
std::vector<int> particalType;
|
||||
std::vector<real_t> xPos;
|
||||
std::vector<real_t> yPos;
|
||||
std::vector<real_t> zPos;
|
||||
std::vector<real_t> rad;
|
||||
|
||||
::mfem::ParGridFunction grainLSField;
|
||||
::mfem::QuadratureFunction grainQFField;
|
||||
|
||||
int numParticles;
|
||||
|
||||
public:
|
||||
GrainReader( mfem::ParMesh * mesh, std::string & name );
|
||||
|
||||
~GrainReader(){}
|
||||
|
||||
void readGrainFile();
|
||||
|
||||
void computeGridFunction( ::mfem::ParFiniteElementSpace& feSpace);
|
||||
|
||||
void computeQuadratureFunction( QuadratureSpaceBase &qspace)
|
||||
{
|
||||
::mfem::mfem_error("not implemented yet");
|
||||
grainQFField.SetSpace(&qspace);
|
||||
}
|
||||
|
||||
const ::mfem::ParGridFunction & getGrainGridFunction() const
|
||||
{ return grainLSField; }
|
||||
|
||||
const ::mfem::QuadratureFunction & getGrainQuadratureFunction() const
|
||||
{ return grainQFField; }
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* PROJECTS_ASCII_HPP_ */
|
||||
@@ -17,19 +17,15 @@ SRC = $(if $(MFEM_DIR:../..=),$(MFEM_DIR)/miniapps/mtop/,)
|
||||
CONFIG_MK = $(or $(wildcard $(MFEM_BUILD_DIR)/config/config.mk),\
|
||||
$(wildcard $(MFEM_INSTALL_DIR)/share/mfem/config.mk))
|
||||
|
||||
# Include defaults.mk to get XLINKER
|
||||
DEFAULTS_MK = $(MFEM_DIR)/config/defaults.mk
|
||||
include $(DEFAULTS_MK)
|
||||
|
||||
MFEM_LIB_FILE = mfem_is_not_built
|
||||
-include $(CONFIG_MK)
|
||||
|
||||
MTOP_COMMON_SRC = mtop_solvers.cpp
|
||||
MTOP_COMMON_SRC = mtop_integrators.cpp paramnonlinearform.cpp pparamnonlinearform.cpp
|
||||
|
||||
MTOP_COMMON_OBJ = $(MTOP_COMMON_SRC:.cpp=.o)
|
||||
|
||||
SEQ_MINIAPPS =
|
||||
PAR_MINIAPPS = test_stokes
|
||||
SEQ_MINIAPPS = seqheat
|
||||
PAR_MINIAPPS = parheat
|
||||
ifeq ($(MFEM_USE_MPI),NO)
|
||||
MINIAPPS = $(SEQ_MINIAPPS)
|
||||
else
|
||||
|
||||
@@ -0,0 +1,390 @@
|
||||
// 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 "mtop_integrators.hpp"
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
|
||||
real_t ParametricLinearDiffusion::GetElementEnergy(const
|
||||
Array<const FiniteElement *> &el,
|
||||
const Array<const FiniteElement *> &pel,
|
||||
ElementTransformation &Tr,
|
||||
const Array<const Vector *> &elfun,
|
||||
const Array<const Vector *> &pelfun)
|
||||
{
|
||||
int dof_u0 = el[0]->GetDof();
|
||||
int dof_r0 = pel[0]->GetDof();
|
||||
|
||||
int dim = el[0]->GetDim();
|
||||
int spaceDim = Tr.GetSpaceDim();
|
||||
if (dim != spaceDim)
|
||||
{
|
||||
mfem::mfem_error("ParametricLinearDiffusion::GetElementEnergy"
|
||||
" is not defined on manifold meshes");
|
||||
}
|
||||
|
||||
// shape functions
|
||||
Vector shu0(dof_u0);
|
||||
Vector shr0(dof_r0);
|
||||
DenseMatrix dsu0(dof_u0,dim);
|
||||
DenseMatrix B(dof_u0, 4);
|
||||
B=0.0;
|
||||
|
||||
real_t w;
|
||||
|
||||
Vector param(1); param=0.0;
|
||||
Vector uu(4); uu=0.0;
|
||||
|
||||
real_t energy =0.0;
|
||||
|
||||
const IntegrationRule *ir;
|
||||
{
|
||||
int order= 2 * el[0]->GetOrder() + Tr.OrderGrad(el[0])
|
||||
+pel[0]->GetOrder();
|
||||
ir=&IntRules.Get(Tr.GetGeometryType(),order);
|
||||
}
|
||||
|
||||
for (int i = 0; i < ir->GetNPoints(); i++)
|
||||
{
|
||||
const IntegrationPoint &ip = ir->IntPoint(i);
|
||||
Tr.SetIntPoint(&ip);
|
||||
w=Tr.Weight();
|
||||
w = ip.weight * w;
|
||||
|
||||
el[0]->CalcPhysDShape(Tr,dsu0);
|
||||
el[0]->CalcPhysShape(Tr,shu0);
|
||||
pel[0]->CalcPhysShape(Tr,shr0);
|
||||
|
||||
param[0]=shr0*(*pelfun[0]);
|
||||
|
||||
// set the matrix B
|
||||
for (int jj=0; jj<dim; jj++)
|
||||
{
|
||||
B.SetCol(jj,dsu0.GetColumn(jj));
|
||||
}
|
||||
B.SetCol(3,shu0);
|
||||
B.MultTranspose(*elfun[0],uu);
|
||||
energy=energy+w * qfun.QEnergy(Tr,ip,param,uu);
|
||||
}
|
||||
return energy;
|
||||
}
|
||||
|
||||
|
||||
void ParametricLinearDiffusion::AssembleElementVector(const
|
||||
Array<const FiniteElement *> &el,
|
||||
const Array<const FiniteElement *> &pel,
|
||||
ElementTransformation &Tr,
|
||||
const Array<const Vector *> &elfun,
|
||||
const Array<const Vector *> &pelfun,
|
||||
const Array<Vector *> &elvec)
|
||||
{
|
||||
int dof_u0 = el[0]->GetDof();
|
||||
int dof_r0 = pel[0]->GetDof();
|
||||
|
||||
int dim = el[0]->GetDim();
|
||||
|
||||
elvec[0]->SetSize(dof_u0);
|
||||
*elvec[0]=0.0;
|
||||
int spaceDim = Tr.GetSpaceDim();
|
||||
if (dim != spaceDim)
|
||||
{
|
||||
mfem::mfem_error("ParametricLinearDiffusion::AssembleElementVector"
|
||||
" is not defined on manifold meshes");
|
||||
}
|
||||
|
||||
// shape functions
|
||||
Vector shu0(dof_u0);
|
||||
Vector shr0(dof_r0);
|
||||
DenseMatrix dsu0(dof_u0,dim);
|
||||
DenseMatrix B(dof_u0, 4);
|
||||
B=0.0;
|
||||
|
||||
real_t w;
|
||||
|
||||
Vector param(1); param=0.0;
|
||||
Vector uu(4); uu=0.0;
|
||||
Vector rr(4);
|
||||
Vector lvec; lvec.SetSize(dof_u0);
|
||||
|
||||
const IntegrationRule *ir = nullptr;
|
||||
int order= 2 * el[0]->GetOrder() + Tr.OrderGrad(el[0])
|
||||
+pel[0]->GetOrder();
|
||||
ir=&IntRules.Get(Tr.GetGeometryType(),order);
|
||||
|
||||
|
||||
for (int i = 0; i < ir->GetNPoints(); i++)
|
||||
{
|
||||
const IntegrationPoint &ip = ir->IntPoint(i);
|
||||
Tr.SetIntPoint(&ip);
|
||||
w=Tr.Weight();
|
||||
w = ip.weight * w;
|
||||
|
||||
el[0]->CalcPhysDShape(Tr,dsu0);
|
||||
el[0]->CalcPhysShape(Tr,shu0);
|
||||
pel[0]->CalcPhysShape(Tr,shr0);
|
||||
|
||||
param[0]=shr0*(*pelfun[0]);
|
||||
|
||||
// set the matrix B
|
||||
for (int jj=0; jj<dim; jj++)
|
||||
{
|
||||
B.SetCol(jj,dsu0.GetColumn(jj));
|
||||
}
|
||||
B.SetCol(3,shu0);
|
||||
B.MultTranspose(*elfun[0],uu);
|
||||
qfun.QResidual(Tr,ip,param, uu, rr);
|
||||
|
||||
B.Mult(rr,lvec);
|
||||
elvec[0]->Add(w,lvec);
|
||||
}
|
||||
}
|
||||
|
||||
void ParametricLinearDiffusion::AssembleElementGrad(const
|
||||
Array<const FiniteElement *> &el,
|
||||
const Array<const FiniteElement *> &pel,
|
||||
ElementTransformation &Tr,
|
||||
const Array<const Vector *> &elfun,
|
||||
const Array<const Vector *> &pelfun,
|
||||
const Array2D<DenseMatrix *> &elmats)
|
||||
{
|
||||
int dof_u0 = el[0]->GetDof();
|
||||
int dof_r0 = pel[0]->GetDof();
|
||||
|
||||
int dim = el[0]->GetDim();
|
||||
|
||||
DenseMatrix* K=elmats(0,0);
|
||||
K->SetSize(dof_u0,dof_u0);
|
||||
(*K)=0.0;
|
||||
|
||||
int spaceDim = Tr.GetSpaceDim();
|
||||
if (dim != spaceDim)
|
||||
{
|
||||
mfem::mfem_error("ParametricLinearDiffusion::AssembleElementGrad"
|
||||
" is not defined on manifold meshes");
|
||||
}
|
||||
|
||||
// shape functions
|
||||
Vector shu0(dof_u0);
|
||||
Vector shr0(dof_r0);
|
||||
DenseMatrix dsu0(dof_u0,dim);
|
||||
DenseMatrix B(dof_u0, 4);
|
||||
DenseMatrix A(dof_u0, 4);
|
||||
B=0.0;
|
||||
real_t w;
|
||||
|
||||
Vector param(1); param=0.0;
|
||||
Vector uu(4); uu=0.0;
|
||||
DenseMatrix hh(4,4);
|
||||
Vector lvec; lvec.SetSize(dof_u0);
|
||||
|
||||
const IntegrationRule *ir = nullptr;
|
||||
int order= 2 * el[0]->GetOrder() + Tr.OrderGrad(el[0])
|
||||
+pel[0]->GetOrder();
|
||||
ir=&IntRules.Get(Tr.GetGeometryType(),order);
|
||||
|
||||
for (int i = 0; i < ir->GetNPoints(); i++)
|
||||
{
|
||||
const IntegrationPoint &ip = ir->IntPoint(i);
|
||||
Tr.SetIntPoint(&ip);
|
||||
w = Tr.Weight();
|
||||
w = ip.weight * w;
|
||||
|
||||
el[0]->CalcPhysDShape(Tr,dsu0);
|
||||
el[0]->CalcPhysShape(Tr,shu0);
|
||||
pel[0]->CalcPhysShape(Tr,shr0);
|
||||
|
||||
param[0]=shr0*(*pelfun[0]);
|
||||
|
||||
// set the matrix B
|
||||
for (int jj=0; jj<dim; jj++)
|
||||
{
|
||||
B.SetCol(jj,dsu0.GetColumn(jj));
|
||||
}
|
||||
B.SetCol(3,shu0);
|
||||
B.MultTranspose(*elfun[0],uu);
|
||||
qfun.QGradResidual(Tr,ip,param,uu,hh);
|
||||
Mult(B,hh,A);
|
||||
AddMult_a_ABt(w,A,B,*K);
|
||||
}
|
||||
}
|
||||
|
||||
void ParametricLinearDiffusion::AssemblePrmElementVector(
|
||||
const Array<const FiniteElement *> &el,
|
||||
const Array<const FiniteElement *> &pel,
|
||||
ElementTransformation &Tr,
|
||||
const Array<const Vector *> &elfun,
|
||||
const Array<const Vector *> &alfun,
|
||||
const Array<const Vector *> &pelfun,
|
||||
const Array<Vector *> &elvec)
|
||||
{
|
||||
int dof_u0 = el[0]->GetDof();
|
||||
int dof_r0 = pel[0]->GetDof();
|
||||
|
||||
int dim = el[0]->GetDim();
|
||||
Vector& e0 = *(elvec[0]);
|
||||
|
||||
e0.SetSize(dof_r0);
|
||||
e0=0.0;
|
||||
|
||||
int spaceDim = Tr.GetSpaceDim();
|
||||
if (dim != spaceDim)
|
||||
{
|
||||
mfem::mfem_error("ParametricLinearDiffusion::AssemblePrmElementVector"
|
||||
" is not defined on manifold meshes");
|
||||
}
|
||||
|
||||
// shape functions
|
||||
Vector shu0(dof_u0);
|
||||
Vector shr0(dof_r0);
|
||||
DenseMatrix dsu0(dof_u0,dim);
|
||||
DenseMatrix B(dof_u0, 4);
|
||||
B=0.0;
|
||||
|
||||
real_t w;
|
||||
|
||||
Vector param(1); param=0.0;
|
||||
Vector uu(4); uu=0.0;
|
||||
Vector aa(4); aa=0.0;
|
||||
Vector rr(1);
|
||||
Vector lvec0; lvec0.SetSize(dof_r0);
|
||||
|
||||
const IntegrationRule *ir;
|
||||
{
|
||||
int order= 2 * el[0]->GetOrder() + Tr.OrderGrad(el[0])
|
||||
+pel[0]->GetOrder();
|
||||
ir=&IntRules.Get(Tr.GetGeometryType(),order);
|
||||
}
|
||||
|
||||
for (int i = 0; i < ir->GetNPoints(); i++)
|
||||
{
|
||||
const IntegrationPoint &ip = ir->IntPoint(i);
|
||||
Tr.SetIntPoint(&ip);
|
||||
w=Tr.Weight();
|
||||
w = ip.weight * w;
|
||||
|
||||
el[0]->CalcPhysDShape(Tr,dsu0);
|
||||
el[0]->CalcPhysShape(Tr,shu0);
|
||||
pel[0]->CalcPhysShape(Tr,shr0);
|
||||
|
||||
param[0]=shr0*(*pelfun[0]);
|
||||
|
||||
// set the matrix B
|
||||
for (int jj=0; jj<dim; jj++)
|
||||
{
|
||||
B.SetCol(jj,dsu0.GetColumn(jj));
|
||||
}
|
||||
B.SetCol(3,shu0);
|
||||
B.MultTranspose(*elfun[0],uu);
|
||||
B.MultTranspose(*alfun[0],aa);
|
||||
|
||||
qfun.AQResidual(Tr, ip, param, uu, aa, rr);
|
||||
|
||||
lvec0=shr0;
|
||||
lvec0*=rr[0];
|
||||
|
||||
e0.Add(w,lvec0);
|
||||
}
|
||||
}
|
||||
|
||||
real_t DiffusionObjIntegrator::GetElementEnergy(const
|
||||
Array<const FiniteElement *> &el,
|
||||
ElementTransformation &Tr,
|
||||
const Array<const Vector *> &elfun)
|
||||
{
|
||||
int dof_u0 = el[0]->GetDof();
|
||||
int dim = el[0]->GetDim();
|
||||
int spaceDim = Tr.GetSpaceDim();
|
||||
if (dim != spaceDim)
|
||||
{
|
||||
mfem::mfem_error("DiffusionObjIntegrator::GetElementEnergy"
|
||||
" is not defined on manifold meshes");
|
||||
}
|
||||
|
||||
// shape functions
|
||||
Vector shu0(dof_u0);
|
||||
|
||||
real_t w;
|
||||
real_t val;
|
||||
|
||||
real_t energy = 0.0;
|
||||
|
||||
const IntegrationRule *ir;
|
||||
{
|
||||
int order= 2 * el[0]->GetOrder() + Tr.OrderGrad(el[0]);
|
||||
ir=&IntRules.Get(Tr.GetGeometryType(),order);
|
||||
}
|
||||
|
||||
for (int i = 0; i < ir->GetNPoints(); i++)
|
||||
{
|
||||
const IntegrationPoint &ip = ir->IntPoint(i);
|
||||
Tr.SetIntPoint(&ip);
|
||||
w=Tr.Weight();
|
||||
|
||||
w = ip.weight * w;
|
||||
|
||||
el[0]->CalcPhysShape(Tr,shu0);
|
||||
|
||||
val=shu0*(*elfun[0]);
|
||||
energy=energy + w * val * val;
|
||||
}
|
||||
return 0.5*energy;
|
||||
}
|
||||
|
||||
void DiffusionObjIntegrator::AssembleElementVector(const
|
||||
Array<const FiniteElement *> &el,
|
||||
ElementTransformation &Tr,
|
||||
const Array<const Vector *> &elfun,
|
||||
const Array<Vector *> &elvec)
|
||||
{
|
||||
int dof_u0 = el[0]->GetDof();
|
||||
int dim = el[0]->GetDim();
|
||||
int spaceDim = Tr.GetSpaceDim();
|
||||
|
||||
elvec[0]->SetSize(dof_u0);
|
||||
*elvec[0]=0.0;
|
||||
|
||||
if (dim != spaceDim)
|
||||
{
|
||||
mfem::mfem_error("DiffusionObjIntegrator::GetElementEnergy"
|
||||
" is not defined on manifold meshes");
|
||||
}
|
||||
|
||||
// shape functions
|
||||
Vector shu0(dof_u0);
|
||||
|
||||
real_t w;
|
||||
real_t val;
|
||||
|
||||
const IntegrationRule *ir;
|
||||
{
|
||||
int order= 2 * el[0]->GetOrder() + Tr.OrderGrad(el[0]);
|
||||
ir=&IntRules.Get(Tr.GetGeometryType(),order);
|
||||
}
|
||||
|
||||
for (int i = 0; i < ir->GetNPoints(); i++)
|
||||
{
|
||||
const IntegrationPoint &ip = ir->IntPoint(i);
|
||||
Tr.SetIntPoint(&ip);
|
||||
w=Tr.Weight();
|
||||
|
||||
w = ip.weight * w;
|
||||
|
||||
el[0]->CalcPhysShape(Tr,shu0);
|
||||
|
||||
val=shu0*(*elfun[0]);
|
||||
|
||||
elvec[0]->Add(w*val,shu0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // end mfem namespace
|
||||
@@ -0,0 +1,233 @@
|
||||
// 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.
|
||||
|
||||
#ifndef MTOPINTEGRATORS_HPP
|
||||
#define MTOPINTEGRATORS_HPP
|
||||
|
||||
#include "mfem.hpp"
|
||||
#include "paramnonlinearform.hpp"
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
|
||||
/// Base class for representing function at integration points.
|
||||
class BaseQFunction
|
||||
{
|
||||
public:
|
||||
virtual ~BaseQFunction() {}
|
||||
|
||||
/// Returns a user defined string identifying the function.
|
||||
virtual std::string GetType()=0;
|
||||
|
||||
// Returns the energy at an integration point.
|
||||
virtual
|
||||
real_t QEnergy(ElementTransformation &T, const IntegrationPoint &ip,
|
||||
mfem::Vector &dd, mfem::Vector &uu)
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// Returns the residual at an integration point.
|
||||
virtual
|
||||
void QResidual(ElementTransformation &T, const IntegrationPoint &ip,
|
||||
mfem::Vector &dd, mfem::Vector &uu, mfem::Vector &rr)=0;
|
||||
|
||||
/// Returns the gradient of the residual at a integration point.
|
||||
virtual
|
||||
void QGradResidual(ElementTransformation &T, const IntegrationPoint &ip,
|
||||
mfem::Vector &dd, mfem::Vector &uu, mfem::DenseMatrix &hh)=0;
|
||||
|
||||
/// Returns the gradient of the residual with respect to the design
|
||||
/// parameters, multiplied by the adjoint.
|
||||
virtual
|
||||
void AQResidual(ElementTransformation &T, const IntegrationPoint &ip,
|
||||
mfem::Vector &dd, mfem::Vector &uu,
|
||||
mfem::Vector &aa, mfem::Vector &rr)=0;
|
||||
|
||||
};
|
||||
|
||||
/* QLinearDiffusion implements methods for computing the energy, the residual,
|
||||
* gradient of the residual and the product of the adjoint fields with the
|
||||
* derivative of the residual with respect to the parameters. All computations
|
||||
* are performed at a integration point. Therefore the vectors (vv,uu,aa,rr ..)
|
||||
* hold the fields' values and the fields' derivatives at the integration
|
||||
* point. For example for a single scalar parametric field representing the
|
||||
* density in topology optimization the vector dd will have size one and the
|
||||
* element will be the density at the integration point. The map between state
|
||||
* and parameter is not fixed and depends on the implementation of the QFunction
|
||||
* class. */
|
||||
class QLinearDiffusion:public BaseQFunction
|
||||
{
|
||||
public:
|
||||
QLinearDiffusion(mfem::Coefficient& diffco, mfem::Coefficient& hsrco,
|
||||
real_t pp=1.0, real_t minrho=1e-7, real_t betac=4.0, real_t etac=0.5):
|
||||
diff(diffco),load(hsrco), powerc(pp), rhomin(minrho), beta(betac), eta(etac)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
std::string GetType() override
|
||||
{
|
||||
return "QLinearDiffusion";
|
||||
}
|
||||
|
||||
real_t QEnergy(ElementTransformation &T, const IntegrationPoint &ip,
|
||||
Vector &dd, Vector &uu) override
|
||||
{
|
||||
// dd[0] - density
|
||||
// uu[0] - grad_x
|
||||
// uu[1] - grad_y
|
||||
// uu[2] - grad_z
|
||||
// uu[3] - temperature/scalar field
|
||||
|
||||
real_t di=diff.Eval(T,ip);
|
||||
real_t ll=load.Eval(T,ip);
|
||||
// Computes the physical density using projection.
|
||||
real_t rz=0.5+0.5*std::tanh(beta*(dd[0]-eta)); //projection
|
||||
// Computes the diffusion coefficient at the integration point.
|
||||
real_t fd=di*(std::pow(rz,powerc)+rhomin);
|
||||
// Computes the sum of the energy and the product of the temperature and
|
||||
// the external input at the integration point.
|
||||
real_t rez = 0.5*(uu[0]*uu[0]+uu[1]*uu[1]+uu[2]*uu[2])*fd-uu[3]*ll;
|
||||
return rez;
|
||||
}
|
||||
|
||||
/// Returns the derivative of QEnergy with respect to the state vector uu.
|
||||
void QResidual(ElementTransformation &T, const IntegrationPoint &ip,
|
||||
Vector &dd, Vector &uu, Vector &rr) override
|
||||
{
|
||||
real_t di=diff.Eval(T,ip);
|
||||
real_t ll=load.Eval(T,ip);
|
||||
real_t rz=0.5+0.5*std::tanh(beta*(dd[0]-eta));
|
||||
real_t fd=di*(std::pow(rz,powerc)+rhomin);
|
||||
|
||||
rr[0]=uu[0]*fd;
|
||||
rr[1]=uu[1]*fd;
|
||||
rr[2]=uu[2]*fd;
|
||||
rr[3]=-ll;
|
||||
}
|
||||
|
||||
|
||||
// Returns the derivative, with respect to the density, of the product of
|
||||
// the adjoint field with the residual at the integration point ip.
|
||||
void AQResidual(ElementTransformation &T, const IntegrationPoint &ip,
|
||||
Vector &dd, Vector &uu, Vector &aa, Vector &rr) override
|
||||
{
|
||||
real_t di=diff.Eval(T,ip);
|
||||
real_t tt=std::tanh(beta*(dd[0]-eta));
|
||||
real_t rz=0.5+0.5*tt;
|
||||
real_t fd=di*powerc*std::pow(rz,powerc-1.0)*0.5*(1.0-tt*tt)*beta;
|
||||
|
||||
rr[0] = -(aa[0]*uu[0]+aa[1]*uu[1]+aa[2]*uu[2])*fd;
|
||||
}
|
||||
|
||||
// Returns the gradient of the residual with respect to the state vector at
|
||||
// the integration point ip.
|
||||
void QGradResidual(ElementTransformation &T, const IntegrationPoint &ip,
|
||||
Vector &dd, Vector &uu, DenseMatrix &hh) override
|
||||
{
|
||||
real_t di=diff.Eval(T,ip);
|
||||
real_t tt=std::tanh(beta*(dd[0]-eta));
|
||||
real_t rz=0.5+0.5*tt;
|
||||
real_t fd=di*(std::pow(rz,powerc)+rhomin);
|
||||
hh=0.0;
|
||||
|
||||
hh(0,0)=fd;
|
||||
hh(1,1)=fd;
|
||||
hh(2,2)=fd;
|
||||
hh(3,3)=0.0;
|
||||
}
|
||||
|
||||
private:
|
||||
mfem::Coefficient& diff; //diffusion coefficient
|
||||
mfem::Coefficient& load; //load coefficient
|
||||
real_t powerc; //penalization coefficient
|
||||
real_t rhomin; //lower bound for the density
|
||||
real_t beta; //controls the sharpness of the projection
|
||||
real_t eta; //projection threshold for tanh
|
||||
};
|
||||
|
||||
/// Provides implementation of an integrator for linear diffusion with
|
||||
/// parametrization provided by a density field. The setup is standard for
|
||||
/// topology optimization problems.
|
||||
class ParametricLinearDiffusion: public ParametricBNLFormIntegrator
|
||||
{
|
||||
public:
|
||||
ParametricLinearDiffusion(BaseQFunction& qfunm): qfun(qfunm)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// Computes the local energy.
|
||||
real_t GetElementEnergy(const Array<const FiniteElement *> &el,
|
||||
const Array<const FiniteElement *> &pel,
|
||||
ElementTransformation &Tr,
|
||||
const Array<const Vector *> &elfun,
|
||||
const Array<const Vector *> &pelfun) override;
|
||||
|
||||
/// Computes the element's residual.
|
||||
void AssembleElementVector(const Array<const FiniteElement *> &el,
|
||||
const Array<const FiniteElement *> &pel,
|
||||
ElementTransformation &Tr,
|
||||
const Array<const Vector *> &elfun,
|
||||
const Array<const Vector *> &pelfun,
|
||||
const Array<Vector *> &elvec) override;
|
||||
|
||||
/// Computes the stiffness/tangent matrix.
|
||||
void AssembleElementGrad(const Array<const FiniteElement *> &el,
|
||||
const Array<const FiniteElement *> &pel,
|
||||
ElementTransformation &Tr,
|
||||
const Array<const Vector *> &elfun,
|
||||
const Array<const Vector *> &pelfun,
|
||||
const Array2D<DenseMatrix *> &elmats) override;
|
||||
|
||||
/// Computes the product of the adjoint solution and the derivative of the
|
||||
/// residual with respect to the parametric fields.
|
||||
void AssemblePrmElementVector(const Array<const FiniteElement *> &el,
|
||||
const Array<const FiniteElement *> &pel,
|
||||
ElementTransformation &Tr,
|
||||
const Array<const Vector *> &elfun,
|
||||
const Array<const Vector *> &alfun,
|
||||
const Array<const Vector *> &pelfun,
|
||||
const Array<Vector *> &elvec) override;
|
||||
private:
|
||||
BaseQFunction& qfun;
|
||||
};
|
||||
|
||||
|
||||
/// Computes an example of nonlinear objective
|
||||
/// $\int \rm{field}*\rm{field}*\rm{weight})\rm{d}\Omega_e$.
|
||||
class DiffusionObjIntegrator:public BlockNonlinearFormIntegrator
|
||||
{
|
||||
public:
|
||||
|
||||
DiffusionObjIntegrator()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// Returns the objective contribution at element level.
|
||||
real_t GetElementEnergy(const Array<const FiniteElement *> &el,
|
||||
ElementTransformation &Tr,
|
||||
const Array<const Vector *> &elfun) override;
|
||||
|
||||
/// Returns the gradient of the objective contribution at element level.
|
||||
void AssembleElementVector(const Array<const FiniteElement *> &el,
|
||||
ElementTransformation &Tr,
|
||||
const Array<const Vector *> &elfun,
|
||||
const Array<Vector *> &elvec) override;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,339 +0,0 @@
|
||||
// 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 <memory>
|
||||
|
||||
#include "mtop_solvers.hpp"
|
||||
|
||||
using namespace mfem;
|
||||
|
||||
StokesSolver::StokesSolver(ParMesh* mesh, int order_, bool zero_mean_press_):
|
||||
pmesh(mesh),
|
||||
order(order_),
|
||||
dim(mesh->SpaceDimension()),
|
||||
zero_mean_press(zero_mean_press_)
|
||||
{
|
||||
if (order_<2) { order=2;}
|
||||
|
||||
vfec=new H1_FECollection(order, pmesh->Dimension());
|
||||
pfec=new H1_FECollection(order-1, pmesh->Dimension());
|
||||
vfes=new ParFiniteElementSpace(pmesh, vfec, pmesh->Dimension());
|
||||
pfes=new ParFiniteElementSpace(pmesh, pfec);
|
||||
|
||||
vel.SetSpace(vfes); vel=0.0;
|
||||
pre.SetSpace(pfes); pre=0.0;
|
||||
|
||||
avel.SetSpace(vfes); avel=0.0;
|
||||
apre.SetSpace(pfes); apre=0.0;
|
||||
|
||||
brink.reset();
|
||||
visc.reset(new ConstantCoefficient(0.001));
|
||||
|
||||
onecoeff.constant = 1.0;
|
||||
zerocoef.constant = 0.0;
|
||||
|
||||
siz_u=vfes->TrueVSize();
|
||||
siz_p=pfes->TrueVSize();
|
||||
|
||||
block_true_offsets.SetSize(3);
|
||||
block_true_offsets[0] = 0;
|
||||
block_true_offsets[1] = siz_u;
|
||||
block_true_offsets[2] = siz_p;
|
||||
block_true_offsets.PartialSum();
|
||||
//set the width and the height of the operator
|
||||
this->width= block_true_offsets[2];
|
||||
this->height= block_true_offsets[2];
|
||||
|
||||
|
||||
sol.Update(block_true_offsets); sol=0.0;
|
||||
rhs.Update(block_true_offsets); rhs=0.0;
|
||||
adj.Update(block_true_offsets); adj=0.0;
|
||||
|
||||
ess_tdofv.SetSize(0);
|
||||
|
||||
bf11.reset();
|
||||
bf12.reset();
|
||||
bf21.reset();
|
||||
|
||||
SetLinearSolver();
|
||||
}
|
||||
|
||||
StokesSolver::~StokesSolver()
|
||||
{
|
||||
delete pfes;
|
||||
delete vfes;
|
||||
delete pfec;
|
||||
delete vfec;
|
||||
}
|
||||
|
||||
void StokesSolver::SetEssTDofsV(mfem::Array<int>& ess_dofs)
|
||||
{
|
||||
// Set the essential boundary conditions
|
||||
ess_dofs.DeleteAll();
|
||||
|
||||
Array<int> ess_bdr(pmesh->bdr_attributes.Max());
|
||||
ess_bdr=0;
|
||||
for (auto it=vel_bcs.begin(); it!=vel_bcs.end(); ++it)
|
||||
{
|
||||
int attr = it->first;
|
||||
ess_bdr[attr-1] = 1;
|
||||
}
|
||||
vfes->GetEssentialTrueDofs(ess_bdr,ess_dofs);
|
||||
}
|
||||
|
||||
void StokesSolver::SetEssTDofsV(Vector& v) const
|
||||
{
|
||||
for (auto it=vel_bcs.begin(); it!=vel_bcs.end(); ++it)
|
||||
{
|
||||
int attr = it->first;
|
||||
std::shared_ptr<VectorCoefficient> coeff = it->second;
|
||||
coeff->SetTime(real_t(0.0));
|
||||
|
||||
Array<int> ess_bdr(pmesh->bdr_attributes.Max());
|
||||
ess_bdr=0;
|
||||
ess_bdr[attr-1] = 1;
|
||||
|
||||
mfem::Array<int> loc_tdofs;
|
||||
vfes->GetEssentialTrueDofs(ess_bdr,loc_tdofs);
|
||||
vel.ProjectBdrCoefficient(*coeff,ess_bdr);
|
||||
vel.SetTrueVector();
|
||||
|
||||
// copy values to v
|
||||
Vector &tvel=vel.GetTrueVector();
|
||||
//vel.GetTrueDofs(tvel);
|
||||
for (int j=0; j<loc_tdofs.Size(); j++)
|
||||
{
|
||||
v[loc_tdofs[j]]=tvel[loc_tdofs[j]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StokesSolver::SetEssVBC(ParGridFunction& pgf)
|
||||
{
|
||||
|
||||
for (auto it=vel_bcs.begin(); it!=vel_bcs.end(); ++it)
|
||||
{
|
||||
int attr = it->first;
|
||||
std::shared_ptr<VectorCoefficient> coeff = it->second;
|
||||
coeff->SetTime(real_t(0.0));
|
||||
Array<int> ess_bdr(pmesh->bdr_attributes.Max());
|
||||
ess_bdr=0;
|
||||
ess_bdr[attr-1] = 1;
|
||||
|
||||
pgf.ProjectBdrCoefficient(*coeff,ess_bdr);
|
||||
}
|
||||
}
|
||||
|
||||
void StokesSolver::DeleteBC()
|
||||
{
|
||||
vel_bcs.clear();
|
||||
ess_tdofv.DeleteAll();
|
||||
//delete allocated matrices and forms
|
||||
|
||||
bf11.reset();
|
||||
bf12.reset();
|
||||
bf21.reset();
|
||||
|
||||
A11.reset();
|
||||
A12.reset();
|
||||
A21.reset();
|
||||
|
||||
A11e.reset();
|
||||
A12e.reset();
|
||||
A21e.reset();
|
||||
|
||||
bop.reset();
|
||||
ls.reset();
|
||||
prec.reset();
|
||||
}
|
||||
|
||||
void StokesSolver::Assemble()
|
||||
{
|
||||
//set BC
|
||||
vel=real_t(0.0);
|
||||
pre=real_t(0.0);
|
||||
SetEssVBC(vel);
|
||||
SetEssTDofsV(ess_tdofv);
|
||||
|
||||
//assemble block 11
|
||||
bf11.reset(new ParBilinearForm(vfes));
|
||||
bf11->AddDomainIntegrator(new ElasticityIntegrator(zerocoef,*visc));
|
||||
if (nullptr!=brink.get())
|
||||
{
|
||||
bf11->AddDomainIntegrator(new VectorMassIntegrator(*brink));
|
||||
}
|
||||
bf11->Assemble(0);
|
||||
bf11->Finalize(0);
|
||||
A11.reset(bf11->ParallelAssemble());
|
||||
|
||||
//assemble block 12
|
||||
bf12.reset(new ParMixedBilinearForm(pfes, vfes));
|
||||
//bf12->AddDomainIntegrator(new GradientIntegrator());
|
||||
bf12->AddDomainIntegrator(
|
||||
new TransposeIntegrator(
|
||||
new VectorDivergenceIntegrator()));
|
||||
bf12->Assemble(0);
|
||||
bf12->Finalize(0);
|
||||
A12.reset(bf12->ParallelAssemble());
|
||||
|
||||
//assemble block 21
|
||||
bf21.reset(new ParMixedBilinearForm(vfes, pfes));
|
||||
bf21->AddDomainIntegrator(
|
||||
new VectorDivergenceIntegrator());
|
||||
bf21->Assemble(0);
|
||||
bf21->Finalize(0);
|
||||
A21.reset(bf21->ParallelAssemble());
|
||||
|
||||
//set BC to the operators
|
||||
A11e.reset(A11->EliminateRowsCols(ess_tdofv));
|
||||
A12->EliminateRows(ess_tdofv);
|
||||
A21e.reset(A21->EliminateCols(ess_tdofv));
|
||||
|
||||
//set the block operator
|
||||
bop.reset(new BlockOperator(block_true_offsets));
|
||||
bop->SetBlock(0,0,A11.get());
|
||||
bop->SetBlock(0,1,A12.get());
|
||||
bop->SetBlock(1,0,A21.get());
|
||||
|
||||
if (zero_mean_press)
|
||||
{
|
||||
V.SetSize(pfes->GetTrueVSize()); V=0.0;
|
||||
ParLinearForm lf(pfes);
|
||||
lf.AddDomainIntegrator(new DomainLFIntegrator(onecoeff));
|
||||
lf.Assemble();
|
||||
lf.ParallelAssemble(V);
|
||||
}
|
||||
|
||||
//set the solver to GMRES
|
||||
{
|
||||
//GMRESSolver* gmres=new GMRESSolver(pmesh->GetComm());
|
||||
|
||||
//MINRESSolver* gmres=new MINRESSolver(pmesh->GetComm());
|
||||
FGMRESSolver* gmres=new FGMRESSolver(pmesh->GetComm());
|
||||
gmres->SetKDim(100);
|
||||
gmres->SetRelTol(linear_rtol);
|
||||
gmres->SetAbsTol(linear_atol);
|
||||
gmres->SetMaxIter(linear_iter);
|
||||
gmres->SetOperator(*bop);
|
||||
gmres->SetPrintLevel(1);
|
||||
|
||||
//prec.reset(new DLSCPrec(A11.get(),A21.get(),A12.get(), zero_mean_press));
|
||||
|
||||
LSCStokesPrec* lsc=new LSCStokesPrec(vfes,pfes,visc,brink,ess_tdofv,
|
||||
A11.get(),A12.get(),A21.get(),zero_mean_press);
|
||||
|
||||
prec.reset(lsc);
|
||||
prec->SetMaxIter(100);
|
||||
prec->SetAbsTol(1e-12);
|
||||
prec->SetRelTol(1e-5);
|
||||
gmres->SetPreconditioner(*prec);
|
||||
|
||||
ls.reset(gmres);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void StokesSolver::FSolve()
|
||||
{
|
||||
Vector& vsol=sol.GetBlock(0);
|
||||
Vector& psol=sol.GetBlock(1);
|
||||
|
||||
Vector& vrhs=rhs.GetBlock(0);
|
||||
Vector& prhs=rhs.GetBlock(1);
|
||||
|
||||
//assemble the RHS
|
||||
rhs=0.0;
|
||||
if (nullptr!=vol_force.get())
|
||||
{
|
||||
ParLinearForm lf(vfes);
|
||||
lf.AddDomainIntegrator(new VectorDomainLFIntegrator(*vol_force));
|
||||
lf.Assemble();
|
||||
lf.ParallelAssemble(vrhs);
|
||||
}
|
||||
|
||||
//set the velocity BCs
|
||||
SetEssTDofsV(vsol);
|
||||
|
||||
|
||||
//modify the rhs
|
||||
A21e->Mult(-1.0,vsol,1.0,prhs);
|
||||
A11->EliminateBC(*A11e,ess_tdofv,vsol,vrhs);
|
||||
|
||||
//solve the linear system
|
||||
ls->Mult(rhs,sol);
|
||||
|
||||
}
|
||||
|
||||
void StokesSolver::ASolve(mfem::Vector &rhs)
|
||||
{
|
||||
MultTranspose(rhs,adj);
|
||||
}
|
||||
|
||||
void StokesSolver::Mult(const mfem::Vector &x, mfem::Vector &y) const
|
||||
{
|
||||
//copy x to rhs
|
||||
{
|
||||
int N = x.Size();
|
||||
const real_t *xp = x.Read();
|
||||
real_t *rp = rhs.ReadWrite();
|
||||
forall(N, [=] MFEM_HOST_DEVICE(int i) { rp[i] = xp[i]; });
|
||||
}
|
||||
|
||||
BlockVector yb(y, block_true_offsets);
|
||||
Vector& vsol=yb.GetBlock(0);
|
||||
Vector& psol=yb.GetBlock(1);
|
||||
|
||||
Vector& vrhs=rhs.GetBlock(0);
|
||||
Vector& prhs=rhs.GetBlock(1);
|
||||
|
||||
//set the velocity BCs
|
||||
SetEssTDofsV(vsol);
|
||||
|
||||
//modify the rhs
|
||||
A21e->Mult(-1.0,vsol,1.0,prhs);
|
||||
A11->EliminateBC(*A11e,ess_tdofv,vsol,vrhs);
|
||||
|
||||
//solve the linear system
|
||||
ls->Mult(rhs,yb);
|
||||
}
|
||||
|
||||
void StokesSolver::MultTranspose(const mfem::Vector &x, mfem::Vector &y) const
|
||||
{
|
||||
//copy x to rhs
|
||||
{
|
||||
int N = x.Size();
|
||||
const real_t *xp = x.Read();
|
||||
real_t *rp = rhs.ReadWrite();
|
||||
forall(N, [=] MFEM_HOST_DEVICE(int i) { rp[i] = xp[i]; });
|
||||
}
|
||||
|
||||
BlockVector yb(y, block_true_offsets);
|
||||
Vector& vsol=yb.GetBlock(0);
|
||||
Vector& psol=yb.GetBlock(1);
|
||||
|
||||
Vector& vrhs=rhs.GetBlock(0);
|
||||
Vector& prhs=rhs.GetBlock(1);
|
||||
|
||||
//set zero velocity bc
|
||||
{
|
||||
int N = ess_tdofv.Size();
|
||||
real_t *yp = vsol.ReadWrite();
|
||||
const int *ep = ess_tdofv.Read();
|
||||
forall(N, [=] MFEM_HOST_DEVICE(int i) { yp[ep[i]] = 0.0; });
|
||||
}
|
||||
|
||||
//modify the rhs
|
||||
//A21e->Mult(-1.0,vsol,1.0,prhs); // vsol is zero at the BC
|
||||
A11->EliminateBC(*A11e,ess_tdofv,vsol,vrhs);
|
||||
|
||||
//solve the linear system
|
||||
ls->Mult(rhs,yb);
|
||||
}
|
||||
@@ -1,432 +0,0 @@
|
||||
// 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.
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "mfem.hpp"
|
||||
|
||||
using real_t = mfem::real_t;
|
||||
|
||||
class StokesSolver:public mfem::Operator
|
||||
{
|
||||
public:
|
||||
StokesSolver(mfem::ParMesh* mesh, int order_, bool zero_mean_press_=false);
|
||||
|
||||
virtual
|
||||
~StokesSolver();
|
||||
|
||||
/// Set the Linear Solver
|
||||
void SetLinearSolver(const real_t rtol = 1e-8,
|
||||
const real_t atol = 1e-12,
|
||||
const int miter = 200)
|
||||
{
|
||||
linear_atol=atol;
|
||||
linear_rtol=rtol;
|
||||
linear_iter=miter;
|
||||
}
|
||||
|
||||
/// Sets BC dofs, bilinear form, preconditioner and solver.
|
||||
/// Should be called before calling Mult of MultTranspose
|
||||
virtual void Assemble();
|
||||
|
||||
/// Sets Brinkman coefficient
|
||||
void SetBrink(std::shared_ptr<mfem::Coefficient> br_){
|
||||
brink=br_;
|
||||
}
|
||||
|
||||
/// Sets viscosity
|
||||
void SetVisc(std::shared_ptr<mfem::Coefficient> vs_){
|
||||
visc=vs_;
|
||||
}
|
||||
|
||||
/// Solves the forward problem.
|
||||
void FSolve();
|
||||
|
||||
/// Solves the adjoint with the provided rhs.
|
||||
void ASolve(mfem::Vector &rhs);
|
||||
|
||||
/// Clear all BC
|
||||
void DeleteBC();
|
||||
|
||||
/// Set the values of the volumetric force.
|
||||
void SetVolForce(real_t fx, real_t fy, real_t fz = 0.0);
|
||||
|
||||
//Set zero mean pressure BC
|
||||
void SetZeroMeanPressure(bool val_=true)
|
||||
{
|
||||
zero_mean_press=val_;
|
||||
}
|
||||
|
||||
//velocity boundary conditions
|
||||
void AddVelocityBC(int id, std::shared_ptr<mfem::VectorCoefficient> val)
|
||||
{
|
||||
vel_bcs[id]=val;
|
||||
}
|
||||
|
||||
/// Set the Velocity BC on a given ParGridFunction.
|
||||
void SetEssVBC(mfem::ParGridFunction& pgf);
|
||||
|
||||
/// Extracts the true boundary doffs of the velocity
|
||||
void SetEssTDofsV(mfem::Array<int>& ess_dofs);
|
||||
|
||||
/// Set the Velocity BC on a given true vector.
|
||||
void SetEssTDofsV(mfem::Vector& v) const;
|
||||
|
||||
/// Forward solve with given RHS. x is the RHS vector.
|
||||
/// The BC are set in the Mult operation.
|
||||
void Mult(const mfem::Vector &x, mfem::Vector &y) const override;
|
||||
|
||||
/// Adjoint solve with given RHS. x is the RHS vector.
|
||||
/// The BC are set to zero in the MultTranspose operator.
|
||||
void MultTranspose(const mfem::Vector &x, mfem::Vector &y) const override;
|
||||
|
||||
/// Return velocity grid function
|
||||
mfem::ParGridFunction& GetVelocity()
|
||||
{
|
||||
vel.SetFromTrueDofs(sol.GetBlock(0));
|
||||
return vel;
|
||||
}
|
||||
|
||||
/// Return pressure grid function
|
||||
mfem::ParGridFunction& GetPressure()
|
||||
{
|
||||
pre.SetFromTrueDofs(sol.GetBlock(1));
|
||||
return pre;
|
||||
}
|
||||
|
||||
/// Return velocity grid function
|
||||
mfem::ParGridFunction& GetAdjVelocity()
|
||||
{
|
||||
avel.SetFromTrueDofs(adj.GetBlock(0));
|
||||
return avel;
|
||||
}
|
||||
|
||||
/// Return pressure grid function
|
||||
mfem::ParGridFunction& GetAdjPressure()
|
||||
{
|
||||
apre.SetFromTrueDofs(adj.GetBlock(1));
|
||||
return apre;
|
||||
}
|
||||
|
||||
/// Return velocty FES
|
||||
mfem::ParFiniteElementSpace* GetVelocitySpace() {return vfes;}
|
||||
|
||||
/// Return pressure FES
|
||||
mfem::ParFiniteElementSpace* GetPressureSpace() {return pfes;}
|
||||
|
||||
/// Return the block offset for providing RHS vectors for
|
||||
/// Mult and MultTranspose.
|
||||
mfem::Array<int>& GetTrueBlockOffsets() {return block_true_offsets;}
|
||||
|
||||
|
||||
private:
|
||||
|
||||
int myrank;
|
||||
|
||||
bool zero_mean_press;
|
||||
|
||||
/// The parallel mesh.
|
||||
mfem::ParMesh *pmesh = nullptr;
|
||||
|
||||
/// The order of the velocity space.
|
||||
int order;
|
||||
|
||||
/// linear system solvers parameters
|
||||
real_t linear_atol;
|
||||
real_t linear_rtol;
|
||||
int linear_iter;
|
||||
int dim;
|
||||
|
||||
std::shared_ptr<mfem::Coefficient> visc; //viscosity
|
||||
std::shared_ptr<mfem::Coefficient> brink; //Brinkman penalization
|
||||
|
||||
mfem::H1_FECollection* vfec; //velocity collections
|
||||
mfem::FiniteElementCollection* pfec; //pressure collecation
|
||||
mfem::ParFiniteElementSpace* vfes;
|
||||
mfem::ParFiniteElementSpace* pfes;
|
||||
|
||||
std::unique_ptr<mfem::IterativeSolver> ls;
|
||||
std::unique_ptr<mfem::IterativeSolver> prec;
|
||||
|
||||
//boundary conditions
|
||||
std::map<int, std::shared_ptr<mfem::VectorCoefficient>> vel_bcs;
|
||||
|
||||
// holds the velocity constrained DOFs
|
||||
mfem::Array<int> ess_tdofv;
|
||||
|
||||
// Volume force coefficient
|
||||
std::shared_ptr<mfem::VectorCoefficient> vol_force;
|
||||
|
||||
mfem::Array<int> block_true_offsets;
|
||||
int siz_u;
|
||||
int siz_p;
|
||||
|
||||
mfem::ConstantCoefficient onecoeff;
|
||||
mfem::ConstantCoefficient zerocoef;
|
||||
|
||||
mutable mfem::ParGridFunction vel; //velocity
|
||||
mutable mfem::ParGridFunction pre; //pressure
|
||||
|
||||
mfem::ParGridFunction avel; //velocity
|
||||
mfem::ParGridFunction apre; //pressure
|
||||
|
||||
std::unique_ptr<mfem::HypreParMatrix> A11;
|
||||
std::unique_ptr<mfem::HypreParMatrix> A12;
|
||||
std::unique_ptr<mfem::HypreParMatrix> A21;
|
||||
|
||||
std::unique_ptr<mfem::HypreParMatrix> A11e;
|
||||
std::unique_ptr<mfem::HypreParMatrix> A12e;
|
||||
std::unique_ptr<mfem::HypreParMatrix> A21e;
|
||||
|
||||
std::unique_ptr<mfem::ParBilinearForm> bf11;
|
||||
std::unique_ptr<mfem::ParMixedBilinearForm> bf12;
|
||||
std::unique_ptr<mfem::ParMixedBilinearForm> bf21;
|
||||
|
||||
std::unique_ptr<mfem::BlockOperator> bop;
|
||||
mutable mfem::BlockVector rhs;
|
||||
mutable mfem::BlockVector sol;
|
||||
mutable mfem::BlockVector adj;
|
||||
|
||||
mfem::Vector V; //used for removing the mean pressure
|
||||
};
|
||||
|
||||
class LSCStokesPrec:public mfem::IterativeSolver
|
||||
{
|
||||
public:
|
||||
LSCStokesPrec(mfem::ParFiniteElementSpace* vfes_,
|
||||
mfem::ParFiniteElementSpace* pfes_,
|
||||
std::shared_ptr<mfem::Coefficient> visc_,
|
||||
std::shared_ptr<mfem::Coefficient> brink_,
|
||||
mfem::Array<int>& ess_vdofs_,
|
||||
const mfem::Operator* O11_,
|
||||
const mfem::Operator* O12_,
|
||||
const mfem::Operator* O21_,
|
||||
bool zero_mean_press_=false)
|
||||
:O11(O11_),O12(O12_),O21(O21_),zero_mean_press(zero_mean_press_)
|
||||
|
||||
{
|
||||
// set the preconditioner for the upper block
|
||||
mfem::ConstantCoefficient lambda(0.00);
|
||||
std::unique_ptr<mfem::ParLORDiscretization>
|
||||
lor_discr(new mfem::ParLORDiscretization(*vfes_));
|
||||
mfem::ParFiniteElementSpace& vlor=lor_discr->GetParFESpace();
|
||||
std::unique_ptr<mfem::ParBilinearForm>
|
||||
b11(new mfem::ParBilinearForm(&vlor));
|
||||
//b11(new mfem::ParBilinearForm(vfes_));
|
||||
b11->AddDomainIntegrator(new mfem::ElasticityIntegrator(lambda,*visc_));
|
||||
//b11->AddDomainIntegrator(new mfem::VectorDiffusionIntegrator(*visc_));
|
||||
if (nullptr!=brink_.get())
|
||||
{
|
||||
b11->AddDomainIntegrator(new mfem::VectorMassIntegrator(*brink_));
|
||||
}
|
||||
|
||||
b11->Assemble(0);
|
||||
b11->Finalize(0);
|
||||
A11.reset(b11->ParallelAssemble());
|
||||
std::unique_ptr<mfem::HypreParMatrix> Ae(A11->EliminateRowsCols(ess_vdofs_));
|
||||
|
||||
std::cout<<"A11 assembled"<<std::endl;
|
||||
|
||||
amg11.reset(new mfem::HypreBoomerAMG());
|
||||
if (mfem::Ordering::Type::byNODES==vfes_->GetOrdering())
|
||||
{
|
||||
int dim=vlor.GetParMesh()->Dimension();
|
||||
amg11->SetSystemsOptions(dim,true);
|
||||
//amg11->SetElasticityOptions(&vlor);
|
||||
}
|
||||
amg11->SetOperator(*A11);
|
||||
|
||||
cg11.reset(new mfem::CGSolver(vlor.GetComm()));
|
||||
cg11->SetOperator(*O11);
|
||||
cg11->SetPreconditioner(*amg11);
|
||||
cg11->SetMaxIter(10);
|
||||
cg11->SetRelTol(1e-12);
|
||||
cg11->SetAbsTol(1e-12);
|
||||
cg11->SetPrintLevel(0);
|
||||
|
||||
//assemble diagonal mass matrix on the velocity space
|
||||
{
|
||||
std::unique_ptr<mfem::ParBilinearForm>
|
||||
q11(new mfem::ParBilinearForm(vfes_));
|
||||
q11->AddDomainIntegrator(
|
||||
new mfem::LumpedIntegrator(
|
||||
//new mfem::VectorMassIntegrator(*brink_)));
|
||||
new mfem::VectorMassIntegrator()));
|
||||
q11->Assemble(0);
|
||||
q11->Finalize(0);
|
||||
Qv.reset(q11->ParallelAssemble());
|
||||
amgv.reset(new mfem::HypreBoomerAMG());
|
||||
amgv->SetOperator(*Qv);
|
||||
}
|
||||
|
||||
const mfem::HypreParMatrix* m21=dynamic_cast<const mfem::HypreParMatrix*>(O21);
|
||||
const mfem::HypreParMatrix* m12=dynamic_cast<const mfem::HypreParMatrix*>(O12);
|
||||
|
||||
if ((nullptr!=m12)&&(nullptr!=m21))
|
||||
{
|
||||
|
||||
mfem::HypreParVector Sd(vfes_->GetComm(),
|
||||
Qv->GetGlobalNumRows(),
|
||||
Qv->GetRowStarts());
|
||||
Qv->GetDiag(Sd);
|
||||
|
||||
mfem::HypreParMatrix T(*m12);
|
||||
T.InvScaleRows(Sd);
|
||||
A.reset(ParMult(m21,&T));
|
||||
}
|
||||
else
|
||||
{
|
||||
//Construct the discrete approximations for O12 and O21
|
||||
std::unique_ptr<mfem::HypreParMatrix> A12, A21;
|
||||
std::unique_ptr<mfem::ParMixedBilinearForm>
|
||||
bf12(new mfem::ParMixedBilinearForm(pfes_, vfes_));
|
||||
bf12->AddDomainIntegrator(
|
||||
new mfem::TransposeIntegrator(
|
||||
new mfem::VectorDivergenceIntegrator()));
|
||||
bf12->Assemble(0);
|
||||
bf12->Finalize(0);
|
||||
A12.reset(bf12->ParallelAssemble());
|
||||
A12->EliminateRows(ess_vdofs_);
|
||||
|
||||
std::unique_ptr<mfem::ParMixedBilinearForm>
|
||||
bf21(new mfem::ParMixedBilinearForm(vfes_, pfes_));
|
||||
bf21->AddDomainIntegrator(
|
||||
new mfem::VectorDivergenceIntegrator());
|
||||
bf21->Assemble(0);
|
||||
bf21->Finalize(0);
|
||||
A21.reset(bf21->ParallelAssemble());
|
||||
std::unique_ptr<mfem::HypreParMatrix> A21e(A21->EliminateCols(ess_vdofs_));
|
||||
|
||||
mfem::HypreParVector Sd(vfes_->GetComm(),
|
||||
Qv->GetGlobalNumRows(),
|
||||
Qv->GetRowStarts());
|
||||
Qv->GetDiag(Sd);
|
||||
|
||||
A12->InvScaleRows(Sd);
|
||||
A.reset(ParMult(A21.get(),A12.get()));
|
||||
}
|
||||
|
||||
amga.reset(new mfem::HypreBoomerAMG());
|
||||
amga->SetOperator(*A);
|
||||
|
||||
if (zero_mean_press)
|
||||
{
|
||||
//use GMRES
|
||||
cga.reset(new mfem::GMRESSolver(vfes_->GetComm()));
|
||||
cga->SetOperator(*A);
|
||||
}
|
||||
else
|
||||
{
|
||||
//use CG
|
||||
cga.reset(new mfem::CGSolver(vfes_->GetComm()));
|
||||
cga->SetOperator(*A);
|
||||
}
|
||||
|
||||
cga->SetPreconditioner(*amga);
|
||||
cga->SetPrintLevel(0);
|
||||
cga->SetMaxIter(20);
|
||||
cga->SetRelTol(1e-12);
|
||||
cga->SetAbsTol(1e-12);
|
||||
|
||||
siz_u=O11->NumRows();
|
||||
siz_p=O21->NumRows();
|
||||
|
||||
block_true_offsets.SetSize(3);
|
||||
block_true_offsets[0] = 0;
|
||||
block_true_offsets[1] = siz_u;
|
||||
block_true_offsets[2] = siz_p;
|
||||
block_true_offsets.PartialSum();
|
||||
//set the width and the height of the operator
|
||||
this->width= block_true_offsets[2];
|
||||
this->height= block_true_offsets[2];
|
||||
|
||||
v1.SetSize(siz_p); v1=0.0;
|
||||
v2.SetSize(siz_u); v2=0.0;
|
||||
v3.SetSize(siz_u); v3=0.0;
|
||||
v4.SetSize(siz_p); v4=0.0;
|
||||
|
||||
myrank=vfes_->GetMyRank();
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// Operator application
|
||||
virtual
|
||||
void Mult (const mfem::Vector & x, mfem::Vector & y) const override
|
||||
{
|
||||
mfem::BlockVector xb,yb;
|
||||
|
||||
cga->SetMaxIter(mfem::IterativeSolver::max_iter);
|
||||
cga->SetAbsTol(IterativeSolver::abs_tol);
|
||||
cga->SetRelTol(IterativeSolver::rel_tol);
|
||||
//cga->iterative_mode=true;
|
||||
|
||||
cg11->SetMaxIter(mfem::IterativeSolver::max_iter);
|
||||
cg11->SetAbsTol(IterativeSolver::abs_tol);
|
||||
cg11->SetRelTol(IterativeSolver::rel_tol);
|
||||
//cg11->iterative_mode=true;
|
||||
|
||||
|
||||
xb.Update(const_cast<mfem::Vector&>(x), block_true_offsets);
|
||||
yb.Update(y, block_true_offsets);
|
||||
|
||||
if(0==myrank){std::cout<<"Schur complement solve";}
|
||||
cga->Mult(xb.GetBlock(1),v1);
|
||||
O12->Mult(v1,v2);
|
||||
amgv->Mult(v2,v3);
|
||||
O11->Mult(v3,v2);
|
||||
amgv->Mult(v2,v3);
|
||||
O21->Mult(v3,v4);
|
||||
cga->Mult(v4,yb.GetBlock(1));
|
||||
yb.GetBlock(1).Neg();
|
||||
|
||||
//construct modification of the rhs for block 0
|
||||
O12->Mult(yb.GetBlock(1),v2);
|
||||
add(xb.GetBlock(0), -1, v2, v3);
|
||||
|
||||
//multiply the upper block
|
||||
if(0==myrank){std::cout<<"Upper block solve";}
|
||||
cg11->Mult(v3,yb.GetBlock(0));
|
||||
//amg11->Mult(v3,yb.GetBlock(0));
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
mutable mfem::Vector v1;
|
||||
mutable mfem::Vector v2;
|
||||
mutable mfem::Vector v3;
|
||||
mutable mfem::Vector v4;
|
||||
|
||||
const mfem::Operator* O11;
|
||||
const mfem::Operator* O12;
|
||||
const mfem::Operator* O21;
|
||||
|
||||
std::unique_ptr<mfem::HypreParMatrix> A11;
|
||||
std::unique_ptr<mfem::HypreBoomerAMG> amg11;
|
||||
std::unique_ptr<mfem::CGSolver> cg11;
|
||||
|
||||
std::unique_ptr<mfem::HypreParMatrix> A;
|
||||
std::unique_ptr<mfem::HypreBoomerAMG> amga;
|
||||
std::unique_ptr<mfem::IterativeSolver> cga;
|
||||
|
||||
|
||||
int siz_u;
|
||||
int siz_p;
|
||||
|
||||
mfem::Array<int> block_true_offsets;
|
||||
|
||||
std::unique_ptr<mfem::HypreParMatrix> Qv;
|
||||
std::unique_ptr<mfem::HypreBoomerAMG> amgv;
|
||||
|
||||
bool zero_mean_press;
|
||||
int myrank;
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,300 @@
|
||||
// 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.
|
||||
|
||||
#ifndef MFEM_PRMNONLINEARFORM
|
||||
#define MFEM_PRMNONLINEARFORM
|
||||
|
||||
#include "mfem.hpp"
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
|
||||
/** The abstract base class ParametricBNLFormIntegrator is a generalization of
|
||||
the BlockNonlinearFormIntegrator class suitable for block state and
|
||||
parameter vectors. */
|
||||
class ParametricBNLFormIntegrator
|
||||
{
|
||||
public:
|
||||
/// Compute the local energy
|
||||
virtual real_t GetElementEnergy(const Array<const FiniteElement *>&el,
|
||||
const Array<const FiniteElement *>&pel,
|
||||
ElementTransformation &Tr,
|
||||
const Array<const Vector *>&elfun,
|
||||
const Array<const Vector *>&pelfun);
|
||||
|
||||
/// Perform the local action of the BlockNonlinearFormIntegrator
|
||||
virtual void AssembleElementVector(const Array<const FiniteElement *> &el,
|
||||
const Array<const FiniteElement *>&pel,
|
||||
ElementTransformation &Tr,
|
||||
const Array<const Vector *> &elfun,
|
||||
const Array<const Vector *>&pelfun,
|
||||
const Array<Vector *> &elvec);
|
||||
|
||||
/// Perform the local action of the BlockNonlinearFormIntegrator on element
|
||||
/// faces
|
||||
virtual void AssembleFaceVector(const Array<const FiniteElement *> &el1,
|
||||
const Array<const FiniteElement *> &el2,
|
||||
const Array<const FiniteElement *> &pel1,
|
||||
const Array<const FiniteElement *> &pel2,
|
||||
FaceElementTransformations &Tr,
|
||||
const Array<const Vector *> &elfun,
|
||||
const Array<const Vector *>&pelfun,
|
||||
const Array<Vector *> &elvect);
|
||||
|
||||
/// Perform the local action on the parameters of the BNLFormIntegrator
|
||||
virtual void AssemblePrmElementVector(const Array<const FiniteElement *> &el,
|
||||
const Array<const FiniteElement *>&pel,
|
||||
ElementTransformation &Tr,
|
||||
const Array<const Vector *> &elfun,
|
||||
const Array<const Vector *> &alfun,
|
||||
const Array<const Vector *>&pelfun,
|
||||
const Array<Vector *> &pelvec);
|
||||
|
||||
/// Perform the local action on the parameters of the BNLFormIntegrator on
|
||||
/// faces
|
||||
virtual void AssemblePrmFaceVector(const Array<const FiniteElement *> &el1,
|
||||
const Array<const FiniteElement *> &el2,
|
||||
const Array<const FiniteElement *> &pel1,
|
||||
const Array<const FiniteElement *> &pel2,
|
||||
FaceElementTransformations &Tr,
|
||||
const Array<const Vector *> &elfun,
|
||||
const Array<const Vector *> &alfun,
|
||||
const Array<const Vector *>&pelfun,
|
||||
const Array<Vector *> &pelvect);
|
||||
|
||||
/// Assemble the local gradient matrix
|
||||
virtual void AssembleElementGrad(const Array<const FiniteElement*> &el,
|
||||
const Array<const FiniteElement *>&pel,
|
||||
ElementTransformation &Tr,
|
||||
const Array<const Vector *> &elfun,
|
||||
const Array<const Vector *>&pelfun,
|
||||
const Array2D<DenseMatrix *> &elmats);
|
||||
|
||||
/// Assemble the local gradient matrix on faces of the elements
|
||||
virtual void AssembleFaceGrad(const Array<const FiniteElement *>&el1,
|
||||
const Array<const FiniteElement *>&el2,
|
||||
const Array<const FiniteElement *> &pel1,
|
||||
const Array<const FiniteElement *> &pel2,
|
||||
FaceElementTransformations &Tr,
|
||||
const Array<const Vector *> &elfun,
|
||||
const Array<const Vector *>&pelfun,
|
||||
const Array2D<DenseMatrix *> &elmats);
|
||||
|
||||
|
||||
virtual ~ParametricBNLFormIntegrator() { }
|
||||
};
|
||||
|
||||
|
||||
/** @brief A class representing a general parametric block nonlinear operator
|
||||
defined on the Cartesian product of multiple FiniteElementSpace%s. */
|
||||
class ParametricBNLForm : public Operator
|
||||
{
|
||||
protected:
|
||||
/// FE spaces on which the form lives.
|
||||
Array<FiniteElementSpace*> fes;
|
||||
|
||||
/// FE spaces for the parametric fields
|
||||
Array<FiniteElementSpace*> paramfes;
|
||||
|
||||
int paramheight;
|
||||
int paramwidth;
|
||||
|
||||
/// Set of Domain Integrators to be assembled (added).
|
||||
Array<ParametricBNLFormIntegrator*> dnfi;
|
||||
|
||||
/// Set of interior face Integrators to be assembled (added).
|
||||
Array<ParametricBNLFormIntegrator*> fnfi;
|
||||
|
||||
/// Set of Boundary Face Integrators to be assembled (added).
|
||||
Array<ParametricBNLFormIntegrator*> bfnfi;
|
||||
Array<Array<int>*> bfnfi_marker;
|
||||
|
||||
/** Auxiliary block-vectors for wrapping input and output vectors or holding
|
||||
GridFunction-like block-vector data (e.g. in parallel). */
|
||||
mutable BlockVector xs, ys;
|
||||
mutable BlockVector prmxs, prmys;
|
||||
|
||||
/** Auxiliary block-vectors for holding GridFunction-like block-vector data
|
||||
(e.g. in parallel). */
|
||||
mutable BlockVector xsv;
|
||||
|
||||
/** Auxiliary block-vectors for holding GridFunction-like block-vector data
|
||||
for the parameter fields (e.g. in parallel). */
|
||||
mutable BlockVector xdv;
|
||||
/** Auxiliary block-vectors for holding GridFunction-like block-vector data
|
||||
for the adjoint fields (e.g. in parallel). */
|
||||
mutable BlockVector adv;
|
||||
|
||||
mutable Array2D<SparseMatrix*> Grads, cGrads;
|
||||
mutable BlockOperator *BlockGrad;
|
||||
|
||||
// A list of the offsets
|
||||
Array<int> block_offsets;
|
||||
Array<int> block_trueOffsets;
|
||||
// A list with the offsets for the parametric fields
|
||||
Array<int> paramblock_offsets;
|
||||
Array<int> paramblock_trueOffsets;
|
||||
|
||||
// Array of Arrays of tdofs for each space in 'fes'
|
||||
Array<Array<int> *> ess_tdofs;
|
||||
|
||||
// Array of Arrays of tdofs for each space in 'paramfes'
|
||||
Array<Array<int> *> paramess_tdofs;
|
||||
|
||||
/// Array of pointers to the prolongation matrix of fes, may be NULL
|
||||
Array<const Operator *> P;
|
||||
|
||||
/// Array of pointers to the prolongation matrix of paramfes, may be NULL
|
||||
Array<const Operator *> Pparam;
|
||||
|
||||
/// Array of results of dynamic-casting P to SparseMatrix pointer
|
||||
Array<const SparseMatrix *> cP;
|
||||
|
||||
/// Array of results of dynamic-casting Pparam to SparseMatrix pointer
|
||||
Array<const SparseMatrix *> cPparam;
|
||||
|
||||
/// Indicator if the Operator is part of a parallel run
|
||||
bool is_serial = true;
|
||||
|
||||
/// Indicator if the Operator needs prolongation on assembly
|
||||
bool needs_prolongation = false;
|
||||
|
||||
/// Indicator if the Operator needs prolongation on assembly
|
||||
bool prmneeds_prolongation = false;
|
||||
|
||||
mutable BlockVector aux1, aux2;
|
||||
|
||||
mutable BlockVector prmaux1, prmaux2;
|
||||
|
||||
const BlockVector &Prolongate(const BlockVector &bx) const;
|
||||
|
||||
const BlockVector &ParamProlongate(const BlockVector &bx) const;
|
||||
|
||||
real_t GetEnergyBlocked(const BlockVector &bx, const BlockVector &dx) const;
|
||||
|
||||
|
||||
/// Specialized version of Mult() for BlockVector%s
|
||||
/// Block L-Vector to Block L-Vector
|
||||
void MultBlocked(const BlockVector &bx, const BlockVector &dx,
|
||||
BlockVector &by) const;
|
||||
|
||||
/// Specialized version of Mult() for BlockVector%s
|
||||
/// Block L-Vector to Block L-Vector
|
||||
/// bx - state vector, ax - adjoint vector, dx - parametric fields
|
||||
/// dy = ax' d(residual(bx))/d(dx)
|
||||
void MultParamBlocked(const BlockVector &bx, const BlockVector & ax,
|
||||
const BlockVector &dx, BlockVector &dy) const;
|
||||
|
||||
|
||||
/// Specialized version of GetGradient() for BlockVector
|
||||
void ComputeGradientBlocked(const BlockVector &bx, const BlockVector &dx) const;
|
||||
|
||||
public:
|
||||
/// Construct an empty BlockNonlinearForm. Initialize with SetSpaces().
|
||||
ParametricBNLForm();
|
||||
|
||||
/// Construct a BlockNonlinearForm on the given set of FiniteElementSpace%s.
|
||||
ParametricBNLForm(Array<FiniteElementSpace *> &statef,
|
||||
Array<FiniteElementSpace *> ¶mf);
|
||||
|
||||
/// Return the @a k-th FE space of the ParametricBNLForm.
|
||||
FiniteElementSpace *FESpace(int k) { return fes[k]; }
|
||||
|
||||
/// Return the @a k-th parametric FE space of the ParametricBNLForm.
|
||||
FiniteElementSpace *ParamFESpace(int k) { return paramfes[k]; }
|
||||
|
||||
|
||||
/// Return the @a k-th FE space of the BlockNonlinearForm (const version).
|
||||
const FiniteElementSpace *FESpace(int k) const { return fes[k]; }
|
||||
|
||||
/// Return the @a k-th parametric FE space of the BlockNonlinearForm (const
|
||||
/// version).
|
||||
const FiniteElementSpace *ParamFESpace(int k) const { return paramfes[k]; }
|
||||
|
||||
/// Return the integrators
|
||||
Array<ParametricBNLFormIntegrator*>& GetDNFI() { return dnfi;}
|
||||
|
||||
|
||||
/// (Re)initialize the ParametricBNLForm.
|
||||
/** After a call to SetSpaces(), the essential b.c. must be set again. */
|
||||
void SetSpaces(Array<FiniteElementSpace *> &statef,
|
||||
Array<FiniteElementSpace *> ¶mf);
|
||||
|
||||
/// Return the regular dof offsets.
|
||||
const Array<int> &GetBlockOffsets() const { return block_offsets; }
|
||||
|
||||
/// Return the true-dof offsets.
|
||||
const Array<int> &GetBlockTrueOffsets() const { return block_trueOffsets; }
|
||||
|
||||
/// Return the regular dof offsets for the parameters.
|
||||
const Array<int> &ParamGetBlockOffsets() const { return paramblock_offsets; }
|
||||
|
||||
/// Return the true-dof offsets for the parameters.
|
||||
const Array<int> &ParamGetBlockTrueOffsets() const { return paramblock_trueOffsets; }
|
||||
|
||||
/// Adds new Domain Integrator.
|
||||
void AddDomainIntegrator(ParametricBNLFormIntegrator *nlfi)
|
||||
{ dnfi.Append(nlfi); }
|
||||
|
||||
/// Adds new Interior Face Integrator.
|
||||
void AddInteriorFaceIntegrator(ParametricBNLFormIntegrator *nlfi)
|
||||
{ fnfi.Append(nlfi); }
|
||||
|
||||
/// Adds new Boundary Face Integrator.
|
||||
void AddBdrFaceIntegrator(ParametricBNLFormIntegrator *nlfi)
|
||||
{ bfnfi.Append(nlfi); bfnfi_marker.Append(NULL); }
|
||||
|
||||
/** @brief Adds new Boundary Face Integrator, restricted to specific boundary
|
||||
attributes. */
|
||||
void AddBdrFaceIntegrator(ParametricBNLFormIntegrator *nlfi,
|
||||
Array<int> &bdr_marker);
|
||||
|
||||
/// Set the essential boundary conditions.
|
||||
virtual void SetEssentialBC(const Array<Array<int> *>&bdr_attr_is_ess,
|
||||
Array<Vector *> &rhs);
|
||||
|
||||
/// Set the essential boundary conditions on the parametric fields.
|
||||
virtual void SetParamEssentialBC(const Array<Array<int> *>&bdr_attr_is_ess,
|
||||
Array<Vector *> &rhs);
|
||||
|
||||
|
||||
/// Computes the energy for a state vector x.
|
||||
virtual real_t GetEnergy(const Vector &x) const;
|
||||
|
||||
/// Method is only called in serial, the parallel version calls MultBlocked
|
||||
/// directly.
|
||||
void Mult(const Vector &x, Vector &y) const override;
|
||||
|
||||
/// Method is only called in serial, the parallel version calls MultBlocked
|
||||
/// directly.
|
||||
virtual void ParamMult(const Vector &x, Vector &y) const;
|
||||
|
||||
/// Method is only called in serial, the parallel version calls
|
||||
/// GetGradientBlocked directly.
|
||||
BlockOperator &GetGradient(const Vector &x) const override;
|
||||
|
||||
/// Set the state fields
|
||||
virtual void SetStateFields(const Vector &xv) const;
|
||||
|
||||
/// Set the adjoint fields
|
||||
virtual void SetAdjointFields(const Vector &av) const;
|
||||
|
||||
/// Set the parameters/design fields
|
||||
virtual void SetParamFields(const Vector &dv) const;
|
||||
|
||||
/// Destructor.
|
||||
virtual ~ParametricBNLForm();
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,354 @@
|
||||
// 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.
|
||||
//
|
||||
// ----------------------------------------------------------------
|
||||
// ParHeat Miniapp: Gradients of PDE constrained objective function
|
||||
// ----------------------------------------------------------------
|
||||
// (Parallel Version)
|
||||
//
|
||||
// The following example computes the gradients of a specified objective
|
||||
// function with respect to parametric fields. The objective function is having
|
||||
// the following form f(u(\rho)) where u(\rho) is a solution of a specific state
|
||||
// problem (in the example that is the diffusion equation), and \rho is a
|
||||
// parametric field discretized by finite elements. The parametric field (also
|
||||
// called density in topology optimization) controls the coefficients of the
|
||||
// state equation. For the considered case, the density controls the diffusion
|
||||
// coefficient within the computational domain.
|
||||
//
|
||||
// For more information, the users are referred to:
|
||||
//
|
||||
// Hinze, M.; Pinnau, R.; Ulbrich, M. & Ulbrich, S.
|
||||
// Optimization with PDE Constraints
|
||||
// Springer Netherlands, 2009
|
||||
//
|
||||
// Bendsøe, M. P. & Sigmund, O.
|
||||
// Topology Optimization - Theory, Methods and Applications
|
||||
// Springer Verlag, Berlin Heidelberg, 2003
|
||||
//
|
||||
// Compile with: make parheat
|
||||
//
|
||||
// Sample runs:
|
||||
//
|
||||
// mpirun -np 4 parheat --visualization
|
||||
// mpirun -np 4 parheat --visualization -m ../../data/beam-quad.mesh
|
||||
|
||||
#include "mfem.hpp"
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#include "pparamnonlinearform.hpp"
|
||||
#include "mtop_integrators.hpp"
|
||||
|
||||
using namespace mfem;
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
// 1. Initialize MPI and HYPRE.
|
||||
mfem::Mpi::Init(argc, argv);
|
||||
int myrank = mfem::Mpi::WorldRank();
|
||||
mfem::Hypre::Init();
|
||||
|
||||
// Parse command-line options.
|
||||
const char *mesh_file = "../../data/star.mesh";
|
||||
int order = 1;
|
||||
bool static_cond = false;
|
||||
int ser_ref_levels = 1;
|
||||
int par_ref_levels = 1;
|
||||
real_t newton_rel_tol = 1e-7;
|
||||
real_t newton_abs_tol = 1e-12;
|
||||
int newton_iter = 10;
|
||||
int print_level = 1;
|
||||
bool visualization = false;
|
||||
|
||||
mfem::OptionsParser args(argc, argv);
|
||||
args.AddOption(&mesh_file, "-m", "--mesh",
|
||||
"Mesh file to use.");
|
||||
args.AddOption(&ser_ref_levels,
|
||||
"-rs",
|
||||
"--refine-serial",
|
||||
"Number of times to refine the mesh uniformly in serial.");
|
||||
args.AddOption(&par_ref_levels,
|
||||
"-rp",
|
||||
"--refine-parallel",
|
||||
"Number of times to refine the mesh uniformly in parallel.");
|
||||
args.AddOption(&order, "-o", "--order",
|
||||
"Finite element order (polynomial degree) or -1 for"
|
||||
" isoparametric space.");
|
||||
args.AddOption(&visualization,
|
||||
"-vis",
|
||||
"--visualization",
|
||||
"-no-vis",
|
||||
"--no-visualization",
|
||||
"Enable or disable GLVis visualization.");
|
||||
args.AddOption(&static_cond, "-sc", "--static-condensation", "-no-sc",
|
||||
"--no-static-condensation", "Enable static condensation.");
|
||||
args.AddOption(&newton_rel_tol,
|
||||
"-rel",
|
||||
"--relative-tolerance",
|
||||
"Relative tolerance for the Newton solve.");
|
||||
args.AddOption(&newton_abs_tol,
|
||||
"-abs",
|
||||
"--absolute-tolerance",
|
||||
"Absolute tolerance for the Newton solve.");
|
||||
args.AddOption(&newton_iter,
|
||||
"-it",
|
||||
"--newton-iterations",
|
||||
"Maximum iterations for the Newton solve.");
|
||||
args.Parse();
|
||||
if (!args.Good())
|
||||
{
|
||||
if (myrank == 0)
|
||||
{
|
||||
args.PrintUsage(std::cout);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (myrank == 0)
|
||||
{
|
||||
args.PrintOptions(std::cout);
|
||||
}
|
||||
|
||||
// Read the (serial) mesh from the given mesh file on all processors. We
|
||||
// can handle triangular, quadrilateral, tetrahedral, hexahedral, surface
|
||||
// and volume meshes with the same code.
|
||||
mfem::Mesh mesh(mesh_file, 1, 1);
|
||||
int dim = mesh.Dimension();
|
||||
|
||||
// Refine the serial mesh on all processors to increase the resolution. In
|
||||
// this example we do 'ref_levels' of uniform refinement. We choose
|
||||
// 'ref_levels' to be the largest number that gives a final mesh with no
|
||||
// more than 10,000 elements.
|
||||
{
|
||||
int ref_levels =
|
||||
(int)floor(log(10000./mesh.GetNE())/log(2.)/dim);
|
||||
for (int l = 0; l < ref_levels; l++)
|
||||
{
|
||||
mesh.UniformRefinement();
|
||||
}
|
||||
}
|
||||
|
||||
// Define a parallel mesh by a partitioning of the serial mesh. Refine
|
||||
// this mesh further in parallel to increase the resolution. Once the
|
||||
// parallel mesh is defined, the serial mesh can be deleted.
|
||||
mfem::ParMesh pmesh(MPI_COMM_WORLD, mesh);
|
||||
mesh.Clear();
|
||||
{
|
||||
for (int l = 0; l < par_ref_levels; l++)
|
||||
{
|
||||
pmesh.UniformRefinement();
|
||||
}
|
||||
}
|
||||
|
||||
// Define the Diffusion coefficient.
|
||||
mfem::ConstantCoefficient* diffco=new mfem::ConstantCoefficient(1.0);
|
||||
// Define the Heat source.
|
||||
mfem::ConstantCoefficient* loadco=new mfem::ConstantCoefficient(1.0);
|
||||
// Define the q-function.
|
||||
mfem::QLinearDiffusion* qfun=new mfem::QLinearDiffusion(*diffco,*loadco,1.0,
|
||||
1e-7,4.0,0.5);
|
||||
|
||||
// Define FE collection and space for the state solution.
|
||||
mfem::H1_FECollection sfec(order, dim);
|
||||
mfem::ParFiniteElementSpace* sfes=new mfem::ParFiniteElementSpace(&pmesh,&sfec,
|
||||
1);
|
||||
// Define FE collection and space for the density field.
|
||||
mfem::L2_FECollection pfec(order, dim);
|
||||
mfem::ParFiniteElementSpace* pfes=new mfem::ParFiniteElementSpace(&pmesh,&pfec,
|
||||
1);
|
||||
|
||||
// Define the arrays for the nonlinear form.
|
||||
mfem::Array<mfem::ParFiniteElementSpace*> asfes;
|
||||
mfem::Array<mfem::ParFiniteElementSpace*> apfes;
|
||||
|
||||
asfes.Append(sfes);
|
||||
apfes.Append(pfes);
|
||||
|
||||
// Define parametric block nonlinear form using single scalar H1 field
|
||||
// and L2 scalar density field.
|
||||
mfem::ParParametricBNLForm* nf=new mfem::ParParametricBNLForm(asfes,apfes);
|
||||
// Add a parametric integrator.
|
||||
nf->AddDomainIntegrator(new mfem::ParametricLinearDiffusion(*qfun));
|
||||
|
||||
// Define true block vectors for state, adjoint, resudual.
|
||||
mfem::BlockVector solbv; solbv.Update(nf->GetBlockTrueOffsets()); solbv=0.0;
|
||||
mfem::BlockVector adjbv; adjbv.Update(nf->GetBlockTrueOffsets()); adjbv=0.0;
|
||||
mfem::BlockVector resbv; resbv.Update(nf->GetBlockTrueOffsets()); resbv=0.0;
|
||||
// Define true block vectors for parametric field and gradients.
|
||||
mfem::BlockVector prmbv; prmbv.Update(nf->ParamGetBlockTrueOffsets());
|
||||
prmbv=0.0;
|
||||
mfem::BlockVector grdbv; grdbv.Update(nf->ParamGetBlockTrueOffsets());
|
||||
grdbv=0.0;
|
||||
|
||||
// Set the BCs for the physics.
|
||||
mfem::Array<mfem::Array<int> *> ess_bdr;
|
||||
mfem::Array<mfem::Vector*> ess_rhs;
|
||||
ess_bdr.Append(new mfem::Array<int>(pmesh.bdr_attributes.Max()));
|
||||
ess_rhs.Append(nullptr);
|
||||
(*ess_bdr[0]) = 1;
|
||||
nf->SetEssentialBC(ess_bdr,ess_rhs);
|
||||
delete ess_bdr[0];
|
||||
|
||||
// Set the density field to 0.5.
|
||||
prmbv=0.5;
|
||||
// Set the density as parametric field in the parametric BNLForm.
|
||||
nf->SetParamFields(prmbv); //set the density
|
||||
|
||||
// Compute the stiffness/tangent matrix for density prmbv=0.5.
|
||||
mfem::BlockOperator *A = &nf->GetGradient(solbv);
|
||||
mfem::HypreBoomerAMG* prec=new mfem::HypreBoomerAMG();
|
||||
prec->SetPrintLevel(print_level);
|
||||
// Use only block (0,0) as in this case we have a single field.
|
||||
prec->SetOperator(A->GetBlock(0,0));
|
||||
|
||||
// Construct block preconditioner for the BNLForm.
|
||||
mfem::BlockDiagonalPreconditioner *blpr = new mfem::BlockDiagonalPreconditioner(
|
||||
nf->GetBlockTrueOffsets());
|
||||
blpr->SetDiagonalBlock(0,prec);
|
||||
|
||||
// Define the solvers.
|
||||
mfem::GMRESSolver *gmres;
|
||||
gmres = new mfem::GMRESSolver(MPI_COMM_WORLD);
|
||||
gmres->SetAbsTol(newton_abs_tol/10);
|
||||
gmres->SetRelTol(newton_rel_tol/10);
|
||||
gmres->SetMaxIter(100);
|
||||
gmres->SetPrintLevel(print_level);
|
||||
gmres->SetPreconditioner(*blpr);
|
||||
gmres->SetOperator(*A);
|
||||
|
||||
|
||||
// Solve the problem.
|
||||
solbv=0.0;
|
||||
nf->Mult(solbv,resbv); resbv.Neg(); //compute RHS
|
||||
gmres->Mult(resbv, solbv);
|
||||
|
||||
// Compute the energy of the state system.
|
||||
real_t energy = nf->GetEnergy(solbv);
|
||||
if (myrank==0)
|
||||
{
|
||||
std::cout << "energy =" << energy << std::endl;
|
||||
}
|
||||
|
||||
// Define the block nonlinear form utilized for representing the objective -
|
||||
// use the state array from the BNLForm.
|
||||
mfem::ParBlockNonlinearForm* ob=new mfem::ParBlockNonlinearForm(asfes);
|
||||
// Add the integrator for the objective.
|
||||
ob->AddDomainIntegrator(new mfem::DiffusionObjIntegrator());
|
||||
|
||||
// Compute the objective.
|
||||
real_t obj=ob->GetEnergy(solbv);
|
||||
if (myrank==0)
|
||||
{
|
||||
std::cout << "Objective =" << obj << std::endl;
|
||||
}
|
||||
|
||||
// Solve the adjoint.
|
||||
{
|
||||
mfem::BlockVector adjrhs; adjrhs.Update(nf->GetBlockTrueOffsets()); adjrhs=0.0;
|
||||
// Compute the RHS for the adjoint, i.e., the gradients with respect to
|
||||
// the parametric fields.
|
||||
ob->Mult(solbv, adjrhs);
|
||||
// Get the tangent matrix from the state problem. We do not need to
|
||||
// transpose the operator for diffusion. Compute the adjoint solution.
|
||||
gmres->Mult(adjrhs, adjbv);
|
||||
}
|
||||
|
||||
// Compute gradients.
|
||||
// First set the adjoint field.
|
||||
nf->SetAdjointFields(adjbv);
|
||||
// Set the state field.
|
||||
nf->SetStateFields(solbv);
|
||||
// Call the parametric Mult.
|
||||
nf->ParamMult(prmbv, grdbv);
|
||||
|
||||
// Dump out the data.
|
||||
if (visualization)
|
||||
{
|
||||
mfem::ParaViewDataCollection *dacol=new mfem::ParaViewDataCollection("ParHeat",
|
||||
&pmesh);
|
||||
mfem::ParGridFunction gfgrd(pfes); gfgrd.SetFromTrueDofs(grdbv.GetBlock(0));
|
||||
mfem::ParGridFunction gfdns(pfes); gfdns.SetFromTrueDofs(prmbv.GetBlock(0));
|
||||
// Define state grid function.
|
||||
mfem::ParGridFunction gfsol(sfes); gfsol.SetFromTrueDofs(solbv.GetBlock(0));
|
||||
mfem::ParGridFunction gfadj(sfes); gfadj.SetFromTrueDofs(adjbv.GetBlock(0));
|
||||
|
||||
dacol->SetLevelsOfDetail(order);
|
||||
dacol->RegisterField("sol", &gfsol);
|
||||
dacol->RegisterField("adj", &gfadj);
|
||||
dacol->RegisterField("dns", &gfdns);
|
||||
dacol->RegisterField("grd", &gfgrd);
|
||||
|
||||
dacol->SetTime(1.0);
|
||||
dacol->SetCycle(1);
|
||||
dacol->Save();
|
||||
|
||||
delete dacol;
|
||||
}
|
||||
|
||||
// FD check
|
||||
{
|
||||
mfem::BlockVector prtbv;
|
||||
mfem::BlockVector tmpbv;
|
||||
prtbv.Update(nf->ParamGetBlockTrueOffsets());
|
||||
tmpbv.Update(nf->ParamGetBlockTrueOffsets());
|
||||
prtbv.GetBlock(0).Randomize();
|
||||
prtbv*=1.0;
|
||||
real_t lsc=1.0;
|
||||
|
||||
real_t gQoI=ob->GetEnergy(solbv);
|
||||
real_t lQoI;
|
||||
|
||||
real_t nd=mfem::InnerProduct(MPI_COMM_WORLD,prtbv,prtbv);
|
||||
real_t td=mfem::InnerProduct(MPI_COMM_WORLD,prtbv,grdbv);
|
||||
td=td/nd;
|
||||
|
||||
for (int l = 0; l < 10; l++)
|
||||
{
|
||||
lsc/=10.0;
|
||||
prtbv/=10.0;
|
||||
add(prmbv,prtbv,tmpbv);
|
||||
nf->SetParamFields(tmpbv);
|
||||
// Solve the physics.
|
||||
solbv=0.0;
|
||||
nf->Mult(solbv,resbv); resbv.Neg(); //compute RHS
|
||||
A = &nf->GetGradient(solbv);
|
||||
prec->SetPrintLevel(0);
|
||||
prec->SetOperator(A->GetBlock(0,0));
|
||||
gmres->SetOperator(*A);
|
||||
gmres->SetPrintLevel(0);
|
||||
gmres->Mult(resbv,solbv);
|
||||
// Compute the objective.
|
||||
lQoI=ob->GetEnergy(solbv);
|
||||
real_t ld=(lQoI-gQoI)/lsc;
|
||||
if (myrank==0)
|
||||
{
|
||||
std::cout << "dx=" << lsc <<" FD approximation=" << ld/nd
|
||||
<< " adjoint gradient=" << td
|
||||
<< " err=" << std::fabs(ld/nd-td) << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delete ob;
|
||||
delete gmres;
|
||||
delete blpr;
|
||||
delete prec;
|
||||
|
||||
delete nf;
|
||||
delete pfes;
|
||||
delete sfes;
|
||||
|
||||
delete qfun;
|
||||
delete loadco;
|
||||
delete diffco;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
|
||||
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
|
||||
// LICENSE and NOTICE for details. LLNL-CODE-806117.
|
||||
//
|
||||
// This file is part of the MFEM library. For more information and source code
|
||||
// availability visit https://mfem.org.
|
||||
//
|
||||
// MFEM is free software; you can redistribute it and/or modify it under the
|
||||
// terms of the BSD-3 license. We welcome feedback and contributions, see file
|
||||
// CONTRIBUTING.md for details.
|
||||
|
||||
#include "mfem.hpp"
|
||||
#include "pparamnonlinearform.hpp"
|
||||
|
||||
#ifdef MFEM_USE_MPI
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
|
||||
ParParametricBNLForm::ParParametricBNLForm(Array<ParFiniteElementSpace *>
|
||||
&statef,
|
||||
Array<ParFiniteElementSpace *> ¶mf)
|
||||
:ParametricBNLForm()
|
||||
{
|
||||
pBlockGrad = nullptr;
|
||||
SetParSpaces(statef,paramf);
|
||||
}
|
||||
|
||||
void ParParametricBNLForm::SetParSpaces(Array<ParFiniteElementSpace *> &statef,
|
||||
Array<ParFiniteElementSpace *> ¶mf)
|
||||
{
|
||||
delete pBlockGrad;
|
||||
pBlockGrad = nullptr;
|
||||
|
||||
for (int s1=0; s1<fes.Size(); ++s1)
|
||||
{
|
||||
for (int s2=0; s2<fes.Size(); ++s2)
|
||||
{
|
||||
delete phBlockGrad(s1,s2);
|
||||
}
|
||||
}
|
||||
|
||||
Array<FiniteElementSpace *> serialSpaces(statef.Size());
|
||||
Array<FiniteElementSpace *> prmserialSpaces(paramf.Size());
|
||||
for (int s=0; s<statef.Size(); s++)
|
||||
{
|
||||
serialSpaces[s] = (FiniteElementSpace *) statef[s];
|
||||
}
|
||||
for (int s=0; s<paramf.Size(); s++)
|
||||
{
|
||||
prmserialSpaces[s] = (FiniteElementSpace *) paramf[s];
|
||||
}
|
||||
|
||||
SetSpaces(serialSpaces,prmserialSpaces);
|
||||
|
||||
phBlockGrad.SetSize(fes.Size(), fes.Size());
|
||||
|
||||
for (int s1=0; s1<fes.Size(); ++s1)
|
||||
{
|
||||
for (int s2=0; s2<fes.Size(); ++s2)
|
||||
{
|
||||
phBlockGrad(s1,s2) = new OperatorHandle(Operator::Hypre_ParCSR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ParFiniteElementSpace * ParParametricBNLForm::ParFESpace(int k)
|
||||
{
|
||||
return (ParFiniteElementSpace *)fes[k];
|
||||
}
|
||||
|
||||
const ParFiniteElementSpace *ParParametricBNLForm::ParFESpace(int k) const
|
||||
{
|
||||
return (const ParFiniteElementSpace *)fes[k];
|
||||
}
|
||||
|
||||
|
||||
ParFiniteElementSpace * ParParametricBNLForm::ParParamFESpace(int k)
|
||||
{
|
||||
return (ParFiniteElementSpace *)paramfes[k];
|
||||
}
|
||||
|
||||
const ParFiniteElementSpace *ParParametricBNLForm::ParParamFESpace(int k) const
|
||||
{
|
||||
return (const ParFiniteElementSpace *)paramfes[k];
|
||||
}
|
||||
|
||||
// Here, rhs is a true dof vector
|
||||
void ParParametricBNLForm::SetEssentialBC(const
|
||||
Array<Array<int> *>&bdr_attr_is_ess,
|
||||
Array<Vector *> &rhs)
|
||||
{
|
||||
Array<Vector *> nullarray(fes.Size());
|
||||
nullarray = NULL;
|
||||
|
||||
ParametricBNLForm::SetEssentialBC(bdr_attr_is_ess, nullarray);
|
||||
|
||||
for (int s = 0; s < fes.Size(); ++s)
|
||||
{
|
||||
if (rhs[s])
|
||||
{
|
||||
rhs[s]->SetSubVector(*ess_tdofs[s], 0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ParParametricBNLForm::SetParamEssentialBC(const
|
||||
Array<Array<int> *>&bdr_attr_is_ess,
|
||||
Array<Vector *> &rhs)
|
||||
{
|
||||
Array<Vector *> nullarray(fes.Size());
|
||||
nullarray = NULL;
|
||||
|
||||
ParametricBNLForm::SetParamEssentialBC(bdr_attr_is_ess, nullarray);
|
||||
|
||||
for (int s = 0; s < paramfes.Size(); ++s)
|
||||
{
|
||||
if (rhs[s])
|
||||
{
|
||||
rhs[s]->SetSubVector(*paramess_tdofs[s], 0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
real_t ParParametricBNLForm::GetEnergy(const Vector &x) const
|
||||
{
|
||||
xs_true.Update(const_cast<Vector&>(x), block_trueOffsets);
|
||||
xs.Update(block_offsets);
|
||||
|
||||
for (int s = 0; s < fes.Size(); ++s)
|
||||
{
|
||||
fes[s]->GetProlongationMatrix()->Mult(xs_true.GetBlock(s), xs.GetBlock(s));
|
||||
}
|
||||
|
||||
real_t enloc = ParametricBNLForm::GetEnergyBlocked(xs,xdv);
|
||||
real_t englo = 0.0;
|
||||
|
||||
MPI_Allreduce(&enloc, &englo, 1, MPITypeMap<real_t>::mpi_type, MPI_SUM,
|
||||
ParFESpace(0)->GetComm());
|
||||
|
||||
return englo;
|
||||
}
|
||||
|
||||
void ParParametricBNLForm::Mult(const Vector &x, Vector &y) const
|
||||
{
|
||||
xs_true.Update(const_cast<Vector&>(x), block_trueOffsets);
|
||||
ys_true.Update(y, block_trueOffsets);
|
||||
xs.Update(block_offsets);
|
||||
ys.Update(block_offsets);
|
||||
|
||||
for (int s=0; s<fes.Size(); ++s)
|
||||
{
|
||||
fes[s]->GetProlongationMatrix()->Mult(
|
||||
xs_true.GetBlock(s), xs.GetBlock(s));
|
||||
}
|
||||
|
||||
ParametricBNLForm::MultBlocked(xs, xdv, ys);
|
||||
|
||||
if (fnfi.Size() > 0)
|
||||
{
|
||||
MFEM_ABORT("TODO: assemble contributions from shared face terms");
|
||||
}
|
||||
|
||||
for (int s=0; s<fes.Size(); ++s)
|
||||
{
|
||||
fes[s]->GetProlongationMatrix()->MultTranspose(
|
||||
ys.GetBlock(s), ys_true.GetBlock(s));
|
||||
|
||||
ys_true.GetBlock(s).SetSubVector(*ess_tdofs[s], 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
/// Block T-Vector to Block T-Vector
|
||||
void ParParametricBNLForm::ParamMult(const Vector &x, Vector &y) const
|
||||
{
|
||||
xs_true.Update(const_cast<Vector&>(x), paramblock_trueOffsets);
|
||||
ys_true.Update(y, paramblock_trueOffsets);
|
||||
prmxs.Update(paramblock_offsets);
|
||||
prmys.Update(paramblock_offsets);
|
||||
|
||||
for (int s=0; s<paramfes.Size(); ++s)
|
||||
{
|
||||
paramfes[s]->GetProlongationMatrix()->Mult(
|
||||
xs_true.GetBlock(s), prmxs.GetBlock(s));
|
||||
}
|
||||
|
||||
ParametricBNLForm::MultParamBlocked(xsv,adv,xdv,prmys);
|
||||
|
||||
if (fnfi.Size() > 0)
|
||||
{
|
||||
MFEM_ABORT("TODO: assemble contributions from shared face terms");
|
||||
}
|
||||
|
||||
for (int s=0; s<paramfes.Size(); ++s)
|
||||
{
|
||||
paramfes[s]->GetProlongationMatrix()->MultTranspose(
|
||||
prmys.GetBlock(s), ys_true.GetBlock(s));
|
||||
|
||||
ys_true.GetBlock(s).SetSubVector(*paramess_tdofs[s], 0.0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Return the local gradient matrix for the given true-dof vector x
|
||||
const BlockOperator & ParParametricBNLForm::GetLocalGradient(
|
||||
const Vector &x) const
|
||||
{
|
||||
xs_true.Update(const_cast<Vector&>(x), block_trueOffsets);
|
||||
xs.Update(block_offsets);
|
||||
|
||||
for (int s=0; s<fes.Size(); ++s)
|
||||
{
|
||||
fes[s]->GetProlongationMatrix()->Mult(
|
||||
xs_true.GetBlock(s), xs.GetBlock(s));
|
||||
}
|
||||
|
||||
ParametricBNLForm::ComputeGradientBlocked(xs,
|
||||
xdv); // (re)assemble Grad with b.c.
|
||||
|
||||
delete BlockGrad;
|
||||
BlockGrad = new BlockOperator(block_offsets);
|
||||
|
||||
for (int i = 0; i < fes.Size(); ++i)
|
||||
{
|
||||
for (int j = 0; j < fes.Size(); ++j)
|
||||
{
|
||||
BlockGrad->SetBlock(i, j, Grads(i, j));
|
||||
}
|
||||
}
|
||||
return *BlockGrad;
|
||||
}
|
||||
|
||||
// Set the operator type id for the parallel gradient matrix/operator.
|
||||
void ParParametricBNLForm::SetGradientType(Operator::Type tid)
|
||||
{
|
||||
for (int s1=0; s1<fes.Size(); ++s1)
|
||||
{
|
||||
for (int s2=0; s2<fes.Size(); ++s2)
|
||||
{
|
||||
phBlockGrad(s1,s2)->SetType(tid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BlockOperator & ParParametricBNLForm::GetGradient(const Vector &x) const
|
||||
{
|
||||
if (pBlockGrad == NULL)
|
||||
{
|
||||
pBlockGrad = new BlockOperator(block_trueOffsets);
|
||||
}
|
||||
|
||||
Array<const ParFiniteElementSpace *> pfes(fes.Size());
|
||||
|
||||
for (int s1=0; s1<fes.Size(); ++s1)
|
||||
{
|
||||
pfes[s1] = ParFESpace(s1);
|
||||
|
||||
for (int s2=0; s2<fes.Size(); ++s2)
|
||||
{
|
||||
phBlockGrad(s1,s2)->Clear();
|
||||
}
|
||||
}
|
||||
|
||||
GetLocalGradient(x); // gradients are stored in 'Grads'
|
||||
|
||||
if (fnfi.Size() > 0)
|
||||
{
|
||||
MFEM_ABORT("TODO: assemble contributions from shared face terms");
|
||||
}
|
||||
|
||||
for (int s1=0; s1<fes.Size(); ++s1)
|
||||
{
|
||||
for (int s2=0; s2<fes.Size(); ++s2)
|
||||
{
|
||||
OperatorHandle dA(phBlockGrad(s1,s2)->Type()),
|
||||
Ph(phBlockGrad(s1,s2)->Type()),
|
||||
Rh(phBlockGrad(s1,s2)->Type());
|
||||
|
||||
if (s1 == s2)
|
||||
{
|
||||
dA.MakeSquareBlockDiag(pfes[s1]->GetComm(), pfes[s1]->GlobalVSize(),
|
||||
pfes[s1]->GetDofOffsets(), Grads(s1,s1));
|
||||
Ph.ConvertFrom(pfes[s1]->Dof_TrueDof_Matrix());
|
||||
phBlockGrad(s1,s1)->MakePtAP(dA, Ph);
|
||||
|
||||
OperatorHandle Ae;
|
||||
Ae.EliminateRowsCols(*phBlockGrad(s1,s1), *ess_tdofs[s1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
dA.MakeRectangularBlockDiag(pfes[s1]->GetComm(),
|
||||
pfes[s1]->GlobalVSize(),
|
||||
pfes[s2]->GlobalVSize(),
|
||||
pfes[s1]->GetDofOffsets(),
|
||||
pfes[s2]->GetDofOffsets(),
|
||||
Grads(s1,s2));
|
||||
Rh.ConvertFrom(pfes[s1]->Dof_TrueDof_Matrix());
|
||||
Ph.ConvertFrom(pfes[s2]->Dof_TrueDof_Matrix());
|
||||
|
||||
phBlockGrad(s1,s2)->MakeRAP(Rh, dA, Ph);
|
||||
|
||||
phBlockGrad(s1,s2)->EliminateRows(*ess_tdofs[s1]);
|
||||
phBlockGrad(s1,s2)->EliminateCols(*ess_tdofs[s2]);
|
||||
}
|
||||
|
||||
pBlockGrad->SetBlock(s1, s2, phBlockGrad(s1,s2)->Ptr());
|
||||
}
|
||||
}
|
||||
|
||||
return *pBlockGrad;
|
||||
}
|
||||
|
||||
ParParametricBNLForm::~ParParametricBNLForm()
|
||||
{
|
||||
delete pBlockGrad;
|
||||
for (int s1=0; s1<fes.Size(); ++s1)
|
||||
{
|
||||
for (int s2=0; s2<fes.Size(); ++s2)
|
||||
{
|
||||
delete phBlockGrad(s1,s2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ParParametricBNLForm::SetStateFields(const Vector &xv) const
|
||||
{
|
||||
xs_true.Update(const_cast<Vector&>(xv), block_trueOffsets);
|
||||
xsv.Update(block_offsets);
|
||||
for (int s=0; s<fes.Size(); ++s)
|
||||
{
|
||||
fes[s]->GetProlongationMatrix()->Mult(
|
||||
xs_true.GetBlock(s), xsv.GetBlock(s));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ParParametricBNLForm::SetAdjointFields(const Vector &av) const
|
||||
{
|
||||
xs_true.Update(const_cast<Vector&>(av), block_trueOffsets);
|
||||
adv.Update(block_offsets);
|
||||
for (int s=0; s<fes.Size(); ++s)
|
||||
{
|
||||
fes[s]->GetProlongationMatrix()->Mult(
|
||||
xs_true.GetBlock(s), adv.GetBlock(s));
|
||||
}
|
||||
}
|
||||
|
||||
void ParParametricBNLForm::SetParamFields(const Vector &dv) const
|
||||
{
|
||||
xs_true.Update(const_cast<Vector&>(dv),paramblock_trueOffsets);
|
||||
xdv.Update(paramblock_offsets);
|
||||
for (int s=0; s<paramfes.Size(); ++s)
|
||||
{
|
||||
paramfes[s]->GetProlongationMatrix()->Mult(
|
||||
xs_true.GetBlock(s), xdv.GetBlock(s));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,114 @@
|
||||
// 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.
|
||||
|
||||
#ifndef MFEM_PPRMNONLINEARFORM
|
||||
#define MFEM_PPRMNONLINEARFORM
|
||||
|
||||
|
||||
#ifdef MFEM_USE_MPI
|
||||
|
||||
#include "mfem.hpp"
|
||||
#include "paramnonlinearform.hpp"
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
|
||||
/** @brief A class representing a general parametric parallel block nonlinear
|
||||
operator defined on the Cartesian product of multiple
|
||||
ParFiniteElementSpace%s. */
|
||||
/** The ParParametricBNLForm takes as input, and returns as output, vectors on
|
||||
the true dofs. */
|
||||
class ParParametricBNLForm : public ParametricBNLForm
|
||||
{
|
||||
protected:
|
||||
mutable BlockVector xs_true, ys_true;
|
||||
mutable Array2D<OperatorHandle *> phBlockGrad;
|
||||
mutable BlockOperator *pBlockGrad;
|
||||
|
||||
public:
|
||||
/// Computes the energy of the system
|
||||
real_t GetEnergy(const Vector &x) const override;
|
||||
|
||||
/// Construct an empty ParParametricBNLForm. Initialize with SetParSpaces().
|
||||
ParParametricBNLForm() : pBlockGrad(nullptr) { }
|
||||
|
||||
/** @brief Construct a ParParametricBNLForm on the given set of
|
||||
parametric and state ParFiniteElementSpace%s. */
|
||||
ParParametricBNLForm(Array<ParFiniteElementSpace *> &statef,
|
||||
Array<ParFiniteElementSpace *> ¶mf);
|
||||
|
||||
/// Return the @a k-th parallel FE state space of the ParParametricBNLForm.
|
||||
ParFiniteElementSpace *ParFESpace(int k);
|
||||
/** @brief Return the @a k-th parallel FE state space of the
|
||||
ParParametricBNLForm (const version). */
|
||||
const ParFiniteElementSpace *ParFESpace(int k) const;
|
||||
|
||||
/// Return the @a k-th parallel FE parameters space of the
|
||||
/// ParParametricBNLForm.
|
||||
ParFiniteElementSpace *ParParamFESpace(int k);
|
||||
/** @brief Return the @a k-th parallel FE parameters space of the
|
||||
ParParametricBNLForm (const version). */
|
||||
const ParFiniteElementSpace *ParParamFESpace(int k) const;
|
||||
|
||||
/** @brief Set the parallel FE spaces for the state and the parametric
|
||||
* fields. After a call to SetParSpaces(), the essential b.c. and the
|
||||
* gradient-type (if different from the default) must be set again. */
|
||||
void SetParSpaces(Array<ParFiniteElementSpace *> &statef,
|
||||
Array<ParFiniteElementSpace *> ¶mf);
|
||||
|
||||
/// Set the state essential BCs. Here, rhs is a true dof vector!
|
||||
void SetEssentialBC(const Array<Array<int> *>&bdr_attr_is_ess,
|
||||
Array<Vector *> &rhs) override;
|
||||
|
||||
// Set the essential BCs for the parametric fields. Here, rhs is a true dof
|
||||
// vector!
|
||||
void SetParamEssentialBC(const Array<Array<int> *>&bdr_attr_is_ess,
|
||||
Array<Vector *> &rhs) override;
|
||||
|
||||
|
||||
/** @brief Calculates the residual for a state input given by block T-Vector.
|
||||
* The result is Block T-Vector! The parametric fields should be set in
|
||||
* advance by calling SetParamFields(). */
|
||||
void Mult(const Vector &x, Vector &y) const override;
|
||||
|
||||
/** @brief Calculates the product of the adjoint field and the derivative of
|
||||
* the state residual with respect to the parametric fields. The adjoint and
|
||||
* the state fields should be set in advance by calling SetAdjointFields()
|
||||
* and SetStateFields(). The input and the result are block T-Vectors!*/
|
||||
void ParamMult(const Vector &x, Vector &y) const override;
|
||||
|
||||
/// Return the local block gradient matrix for the given true-dof vector x
|
||||
const BlockOperator &GetLocalGradient(const Vector &x) const;
|
||||
|
||||
/// Return the block gradient matrix for the given true-dof vector x
|
||||
BlockOperator &GetGradient(const Vector &x) const override;
|
||||
|
||||
/** @brief Set the operator type id for the blocks of the parallel gradient
|
||||
matrix/operator. The default type is Operator::Hypre_ParCSR. */
|
||||
void SetGradientType(Operator::Type tid);
|
||||
|
||||
/// Destructor.
|
||||
virtual ~ParParametricBNLForm();
|
||||
|
||||
/// Set the state fields
|
||||
void SetStateFields(const Vector &xv) const override;
|
||||
|
||||
/// Set the adjoint fields
|
||||
void SetAdjointFields(const Vector &av) const override;
|
||||
|
||||
/// Set the parameters/design fields
|
||||
void SetParamFields(const Vector &dv) const override;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,308 @@
|
||||
// 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.
|
||||
//
|
||||
// ----------------------------------------------------------------
|
||||
// SeqHeat Miniapp: Gradients of PDE constrained objective function
|
||||
// ----------------------------------------------------------------
|
||||
// (Sequential Version)
|
||||
//
|
||||
// The following example computes the gradients of a specified objective
|
||||
// function with respect to parametric fields. The objective function is having
|
||||
// the following form f(u(\rho)) where u(\rho) is a solution of a specific state
|
||||
// problem (in the example that is the diffusion equation), and \rho is a
|
||||
// parametric field discretized by finite elements. The parametric field (also
|
||||
// called density in topology optimization) controls the coefficients of the
|
||||
// state equation. For the considered case, the density controls the diffusion
|
||||
// coefficient within the computational domain.
|
||||
//
|
||||
// For more information, the users are referred to:
|
||||
//
|
||||
// Hinze, M.; Pinnau, R.; Ulbrich, M. & Ulbrich, S.
|
||||
// Optimization with PDE Constraints
|
||||
// Springer Netherlands, 2009
|
||||
//
|
||||
// Bendsøe, M. P. & Sigmund, O.
|
||||
// Topology Optimization - Theory, Methods and Applications
|
||||
// Springer Verlag, Berlin Heidelberg, 2003
|
||||
//
|
||||
// Compile with: make seqheat
|
||||
//
|
||||
// Sample runs:
|
||||
//
|
||||
// seqheat -m ../../data/star-mixed.mesh
|
||||
// seqheat --visualization
|
||||
|
||||
#include "mfem.hpp"
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#include "mtop_integrators.hpp"
|
||||
|
||||
using namespace mfem;
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
const char *mesh_file = "../../data/star.vtk";
|
||||
int ser_ref_levels = 1;
|
||||
int order = 2;
|
||||
bool visualization = false;
|
||||
real_t newton_rel_tol = 1e-4;
|
||||
real_t newton_abs_tol = 1e-6;
|
||||
int newton_iter = 10;
|
||||
int print_level = 0;
|
||||
|
||||
mfem::OptionsParser args(argc, argv);
|
||||
args.AddOption(&mesh_file, "-m", "--mesh", "Mesh file to use.");
|
||||
args.AddOption(&ser_ref_levels,
|
||||
"-rs",
|
||||
"--refine-serial",
|
||||
"Number of times to refine the mesh uniformly in serial.");
|
||||
args.AddOption(&order,
|
||||
"-o",
|
||||
"--order",
|
||||
"Order (degree) of the finite elements.");
|
||||
args.AddOption(&visualization,
|
||||
"-vis",
|
||||
"--visualization",
|
||||
"-no-vis",
|
||||
"--no-visualization",
|
||||
"Enable or disable GLVis visualization.");
|
||||
args.AddOption(&newton_rel_tol,
|
||||
"-rel",
|
||||
"--relative-tolerance",
|
||||
"Relative tolerance for the Newton solve.");
|
||||
args.AddOption(&newton_abs_tol,
|
||||
"-abs",
|
||||
"--absolute-tolerance",
|
||||
"Absolute tolerance for the Newton solve.");
|
||||
args.AddOption(&newton_iter,
|
||||
"-it",
|
||||
"--newton-iterations",
|
||||
"Maximum iterations for the Newton solve.");
|
||||
args.Parse();
|
||||
if (!args.Good())
|
||||
{
|
||||
args.PrintUsage(std::cout);
|
||||
return 1;
|
||||
}
|
||||
args.PrintOptions(std::cout);
|
||||
|
||||
// Read the (serial) mesh from the given mesh file on all processors. We
|
||||
// can handle triangular, quadrilateral, tetrahedral and hexahedral meshes
|
||||
// with the same code.
|
||||
mfem::Mesh *mesh = new mfem::Mesh(mesh_file, 1, 1);
|
||||
int dim = mesh->Dimension();
|
||||
|
||||
// Refine the mesh in serial to increase the resolution. In this example
|
||||
// we do 'ser_ref_levels' of uniform refinement, where 'ser_ref_levels' is
|
||||
// a command-line parameter.
|
||||
for (int lev = 0; lev < ser_ref_levels; lev++)
|
||||
{
|
||||
mesh->UniformRefinement();
|
||||
}
|
||||
|
||||
// Diffusion coefficient
|
||||
mfem::ConstantCoefficient* diffco=new mfem::ConstantCoefficient(1.0);
|
||||
// Heat source
|
||||
mfem::ConstantCoefficient* loadco=new mfem::ConstantCoefficient(1.0);
|
||||
// Define the q-function
|
||||
mfem::QLinearDiffusion* qfun=new mfem::QLinearDiffusion(*diffco,*loadco,1.0,
|
||||
1e-7,4.0,0.5);
|
||||
|
||||
// Define FE collection and space for the state solution
|
||||
mfem::H1_FECollection sfec(order, dim);
|
||||
mfem::FiniteElementSpace* sfes=new mfem::FiniteElementSpace(mesh,&sfec,1);
|
||||
// Define FE collection and space for the density field
|
||||
mfem::L2_FECollection pfec(order, dim);
|
||||
mfem::FiniteElementSpace* pfes=new mfem::FiniteElementSpace(mesh,&pfec,1);
|
||||
|
||||
// Define the arrays for the nonlinear form
|
||||
mfem::Array<mfem::FiniteElementSpace*> asfes;
|
||||
mfem::Array<mfem::FiniteElementSpace*> apfes;
|
||||
|
||||
asfes.Append(sfes);
|
||||
apfes.Append(pfes);
|
||||
// Define parametric block nonlinear form using single scalar H1 field
|
||||
// and L2 scalar density field
|
||||
mfem::ParametricBNLForm* nf=new mfem::ParametricBNLForm(asfes,apfes);
|
||||
// Add the parametric integrator
|
||||
nf->AddDomainIntegrator(new mfem::ParametricLinearDiffusion(*qfun));
|
||||
|
||||
// Define true block vectors for state, adjoint, residual
|
||||
mfem::BlockVector solbv; solbv.Update(nf->GetBlockTrueOffsets()); solbv=0.0;
|
||||
mfem::BlockVector adjbv; adjbv.Update(nf->GetBlockTrueOffsets()); adjbv=0.0;
|
||||
mfem::BlockVector resbv; resbv.Update(nf->GetBlockTrueOffsets()); resbv=0.0;
|
||||
// Define true block vectors for parametric field and gradients
|
||||
mfem::BlockVector prmbv; prmbv.Update(nf->ParamGetBlockTrueOffsets());
|
||||
prmbv=0.0;
|
||||
mfem::BlockVector grdbv; grdbv.Update(nf->ParamGetBlockTrueOffsets());
|
||||
grdbv=0.0;
|
||||
|
||||
// Set the BC for the physics
|
||||
mfem::Array<mfem::Array<int> *> ess_bdr;
|
||||
mfem::Array<mfem::Vector*> ess_rhs;
|
||||
ess_bdr.Append(new mfem::Array<int>(mesh->bdr_attributes.Max()));
|
||||
ess_rhs.Append(nullptr);
|
||||
(*ess_bdr[0]) = 1;
|
||||
nf->SetEssentialBC(ess_bdr,ess_rhs);
|
||||
delete ess_bdr[0];
|
||||
|
||||
// Define the linear solvers
|
||||
mfem::GMRESSolver *gmres;
|
||||
gmres = new mfem::GMRESSolver();
|
||||
gmres->SetAbsTol(newton_abs_tol/10);
|
||||
gmres->SetRelTol(newton_rel_tol/10);
|
||||
gmres->SetMaxIter(300);
|
||||
gmres->SetPrintLevel(print_level);
|
||||
|
||||
// Define the Newton solver
|
||||
mfem::NewtonSolver *ns;
|
||||
ns = new mfem::NewtonSolver();
|
||||
ns->iterative_mode = true;
|
||||
ns->SetSolver(*gmres);
|
||||
ns->SetOperator(*nf);
|
||||
ns->SetPrintLevel(print_level);
|
||||
ns->SetRelTol(newton_rel_tol);
|
||||
ns->SetAbsTol(newton_abs_tol);
|
||||
ns->SetMaxIter(newton_iter);
|
||||
|
||||
// Solve the problem
|
||||
// Set the density to 0.5
|
||||
prmbv=0.5;
|
||||
nf->SetParamFields(prmbv); // Set the density
|
||||
// Define the RHS
|
||||
mfem::Vector b;
|
||||
solbv=0.0;
|
||||
// Newton solve
|
||||
ns->Mult(b, solbv);
|
||||
|
||||
// Compute the residual
|
||||
nf->Mult(solbv,resbv);
|
||||
std::cout<<"Norm residual="<<resbv.Norml2()<<std::endl;
|
||||
|
||||
// Compute the energy of the state system
|
||||
real_t energy = nf->GetEnergy(solbv);
|
||||
std::cout<<"energy ="<< energy<<std::endl;
|
||||
|
||||
// Define the block nonlinear form utilized for representing the
|
||||
// objective. The input is the state array asfes defined earlier.
|
||||
mfem::BlockNonlinearForm* ob=new mfem::BlockNonlinearForm(asfes);
|
||||
|
||||
// Add the integrator for the objective
|
||||
ob->AddDomainIntegrator(new mfem::DiffusionObjIntegrator());
|
||||
|
||||
// Compute the objective
|
||||
real_t obj=ob->GetEnergy(solbv);
|
||||
std::cout<<"Objective ="<<obj<<std::endl;
|
||||
|
||||
// Solve the adjoint
|
||||
{
|
||||
mfem::BlockVector adjrhs; adjrhs.Update(nf->GetBlockTrueOffsets()); adjrhs=0.0;
|
||||
// Compute the RHS for the adjoint
|
||||
ob->Mult(solbv, adjrhs);
|
||||
// Get the tangent matrix from the state problem
|
||||
mfem::BlockOperator& A=nf->GetGradient(solbv);
|
||||
// We do not need to transpose the operator for diffusion
|
||||
gmres->SetOperator(A.GetBlock(0,0));
|
||||
// Compute the adjoint solution
|
||||
gmres->Mult(adjrhs.GetBlock(0), adjbv.GetBlock(0));
|
||||
}
|
||||
|
||||
// Compute gradients
|
||||
nf->SetAdjointFields(adjbv);
|
||||
nf->SetStateFields(solbv);
|
||||
nf->ParamMult(prmbv, grdbv);
|
||||
|
||||
// Dump out the data
|
||||
if (visualization)
|
||||
{
|
||||
mfem::ParaViewDataCollection *dacol=new mfem::ParaViewDataCollection("SeqHeat",
|
||||
mesh);
|
||||
mfem::GridFunction gfgrd(pfes); gfgrd.SetFromTrueDofs(grdbv.GetBlock(0));
|
||||
mfem::GridFunction gfdns(pfes); gfdns.SetFromTrueDofs(prmbv.GetBlock(0));
|
||||
// Define state grid function
|
||||
mfem::GridFunction gfsol(sfes); gfsol.SetFromTrueDofs(solbv.GetBlock(0));
|
||||
mfem::GridFunction gfadj(sfes); gfadj.SetFromTrueDofs(adjbv.GetBlock(0));
|
||||
|
||||
dacol->SetLevelsOfDetail(order);
|
||||
dacol->RegisterField("sol", &gfsol);
|
||||
dacol->RegisterField("adj", &gfadj);
|
||||
dacol->RegisterField("dns", &gfdns);
|
||||
dacol->RegisterField("grd", &gfgrd);
|
||||
|
||||
dacol->SetTime(1.0);
|
||||
dacol->SetCycle(1);
|
||||
dacol->Save();
|
||||
|
||||
delete dacol;
|
||||
}
|
||||
|
||||
// FD check
|
||||
{
|
||||
// Perturbation vector
|
||||
mfem::BlockVector prtbv;
|
||||
mfem::BlockVector tmpbv;
|
||||
prtbv.Update(nf->ParamGetBlockTrueOffsets());
|
||||
tmpbv.Update(nf->ParamGetBlockTrueOffsets());
|
||||
// Generate the perturbation
|
||||
prtbv.GetBlock(0).Randomize();
|
||||
prtbv*=1.0;
|
||||
// Scaling parameter
|
||||
real_t lsc=1.0;
|
||||
|
||||
// Compute initial objective
|
||||
real_t gQoI=ob->GetEnergy(solbv);
|
||||
real_t lQoI;
|
||||
|
||||
// Norm of the perturbation
|
||||
real_t nd=mfem::InnerProduct(prtbv,prtbv);
|
||||
// Projection of the adjoint gradient on the perturbation
|
||||
real_t td=mfem::InnerProduct(prtbv,grdbv);
|
||||
// Normalize the directional derivative
|
||||
td=td/nd;
|
||||
|
||||
for (int l = 0; l < 10; l++)
|
||||
{
|
||||
lsc/=10.0;
|
||||
// Scale the perturbation
|
||||
prtbv/=10.0;
|
||||
// Add the perturbation to the original density
|
||||
add(prmbv,prtbv,tmpbv);
|
||||
nf->SetParamFields(tmpbv);
|
||||
// Solve the physics
|
||||
ns->Mult(b,solbv);
|
||||
// Compute the objective
|
||||
lQoI=ob->GetEnergy(solbv);
|
||||
// FD approximation
|
||||
real_t ld=(lQoI-gQoI)/lsc;
|
||||
std::cout << "dx=" << lsc << " FD gradient=" << ld/nd
|
||||
<< " adjoint gradient=" << td
|
||||
<< " err=" << std::fabs(ld/nd-td) << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
delete ob;
|
||||
|
||||
delete ns;
|
||||
delete gmres;
|
||||
|
||||
delete nf;
|
||||
delete pfes;
|
||||
delete sfes;
|
||||
|
||||
delete qfun;
|
||||
delete loadco;
|
||||
delete diffco;
|
||||
|
||||
delete mesh;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
#include "mfem.hpp"
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#include "mtop_solvers.hpp"
|
||||
#include "grain_reader.hpp"
|
||||
|
||||
using namespace mfem;
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
// 1. Initialize MPI and HYPRE.
|
||||
mfem::Mpi::Init(argc, argv);
|
||||
int myrank = mfem::Mpi::WorldRank();
|
||||
mfem::Hypre::Init();
|
||||
|
||||
// Parse command-line options.
|
||||
const char *mesh_file = "./mini_flow2d_ball.msh";
|
||||
int order = 1;
|
||||
bool static_cond = false;
|
||||
int ser_ref_levels = 0;
|
||||
int par_ref_levels = 1;
|
||||
real_t newton_rel_tol = 1e-7;
|
||||
real_t newton_abs_tol = 1e-12;
|
||||
int newton_iter = 10;
|
||||
int print_level = 1;
|
||||
bool visualization = false;
|
||||
|
||||
mfem::OptionsParser args(argc, argv);
|
||||
args.AddOption(&mesh_file, "-m", "--mesh",
|
||||
"Mesh file to use.");
|
||||
args.AddOption(&ser_ref_levels,
|
||||
"-rs",
|
||||
"--refine-serial",
|
||||
"Number of times to refine the mesh uniformly in serial.");
|
||||
args.AddOption(&par_ref_levels,
|
||||
"-rp",
|
||||
"--refine-parallel",
|
||||
"Number of times to refine the mesh uniformly in parallel.");
|
||||
args.AddOption(&order, "-o", "--order",
|
||||
"Finite element order (polynomial degree) or -1 for"
|
||||
" isoparametric space.");
|
||||
args.AddOption(&visualization,
|
||||
"-vis",
|
||||
"--visualization",
|
||||
"-no-vis",
|
||||
"--no-visualization",
|
||||
"Enable or disable GLVis visualization.");
|
||||
args.AddOption(&static_cond, "-sc", "--static-condensation", "-no-sc",
|
||||
"--no-static-condensation", "Enable static condensation.");
|
||||
args.AddOption(&newton_rel_tol,
|
||||
"-rel",
|
||||
"--relative-tolerance",
|
||||
"Relative tolerance for the Newton solve.");
|
||||
args.AddOption(&newton_abs_tol,
|
||||
"-abs",
|
||||
"--absolute-tolerance",
|
||||
"Absolute tolerance for the Newton solve.");
|
||||
args.AddOption(&newton_iter,
|
||||
"-it",
|
||||
"--newton-iterations",
|
||||
"Maximum iterations for the Newton solve.");
|
||||
args.Parse();
|
||||
if (!args.Good())
|
||||
{
|
||||
if (myrank == 0)
|
||||
{
|
||||
args.PrintUsage(std::cout);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (myrank == 0)
|
||||
{
|
||||
args.PrintOptions(std::cout);
|
||||
}
|
||||
|
||||
// Read the (serial) mesh from the given mesh file on all processors. We
|
||||
// can handle triangular, quadrilateral, tetrahedral, hexahedral, surface
|
||||
// and volume meshes with the same code.
|
||||
// mfem::Mesh mesh(mesh_file, 1, 1);
|
||||
|
||||
double meshOffsetX = -12.0;
|
||||
double meshOffsetY = -12.0;
|
||||
double meshOffsetZ = -0.5;
|
||||
|
||||
|
||||
double Lx = 24.0; double Ly = 24.0; double Lz = 150.5;
|
||||
int NX = 48; int NY = 48; int NZ = 301;
|
||||
mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D(NX, NY, NZ, mfem::Element::HEXAHEDRON, Lx, Ly, Lz, true);
|
||||
int dim = mesh.Dimension();
|
||||
|
||||
int tNumVertices = mesh.GetNV();
|
||||
for (int i = 0; i < tNumVertices; ++i)
|
||||
{
|
||||
double * Coords = mesh.GetVertex(i);
|
||||
|
||||
Coords[ 0 ] = Coords[ 0 ] + meshOffsetX;
|
||||
Coords[ 1 ] = Coords[ 1 ] + meshOffsetY;
|
||||
Coords[ 2 ] = Coords[ 2 ] + meshOffsetZ;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Refine the serial mesh on all processors to increase the resolution. In
|
||||
// this example we do 'ref_levels' of uniform refinement. We choose
|
||||
// 'ref_levels' to be the largest number that gives a final mesh with no
|
||||
// more than 10,000 elements.
|
||||
{
|
||||
for (int l = 0; l < ser_ref_levels; l++)
|
||||
{
|
||||
mesh.UniformRefinement();
|
||||
}
|
||||
}
|
||||
|
||||
// Define a parallel mesh by a partitioning of the serial mesh. Refine
|
||||
// this mesh further in parallel to increase the resolution. Once the
|
||||
// parallel mesh is defined, the serial mesh can be deleted.
|
||||
mfem::ParMesh pmesh(MPI_COMM_WORLD, mesh);
|
||||
mesh.Clear();
|
||||
{
|
||||
for (int l = 0; l < par_ref_levels; l++)
|
||||
{
|
||||
pmesh.UniformRefinement();
|
||||
}
|
||||
}
|
||||
|
||||
std::cout<<"My rank="<<pmesh.GetMyRank()<<std::endl;
|
||||
std::string tStringWeight = "./singleparticlesdiameter.txt";
|
||||
::mfem::H1_FECollection FECol_H1(order, dim);
|
||||
::mfem::ParFiniteElementSpace FESpace_H1(&pmesh, &FECol_H1, 1, mfem::Ordering::byNODES);
|
||||
|
||||
GrainReader grain( &pmesh, tStringWeight );
|
||||
grain.computeGridFunction( FESpace_H1);
|
||||
::mfem::ParGridFunction grainLSField = grain.getGrainGridFunction();
|
||||
|
||||
//dump the solution
|
||||
{
|
||||
ParaViewDataCollection paraview_dc("grain_test", &pmesh);
|
||||
paraview_dc.SetPrefixPath("ParaView");
|
||||
paraview_dc.SetLevelsOfDetail(order);
|
||||
paraview_dc.SetDataFormat(VTKFormat::BINARY);
|
||||
paraview_dc.SetHighOrderOutput(true);
|
||||
paraview_dc.SetCycle(0);
|
||||
paraview_dc.SetTime(0.0);
|
||||
paraview_dc.RegisterField("level_set",&grainLSField);
|
||||
|
||||
paraview_dc.Save();
|
||||
}
|
||||
|
||||
|
||||
MPI::Finalize();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,203 +0,0 @@
|
||||
#include "mfem.hpp"
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#include "mtop_solvers.hpp"
|
||||
|
||||
using namespace mfem;
|
||||
|
||||
class BrinkCoeff :public Coefficient
|
||||
{
|
||||
public:
|
||||
BrinkCoeff(real_t penal_=10.0):penalty(penal_)
|
||||
{
|
||||
}
|
||||
|
||||
virtual real_t Eval(ElementTransformation &T,
|
||||
const IntegrationPoint &ip) override
|
||||
{
|
||||
real_t x[3];
|
||||
Vector transip(x, 3);
|
||||
T.Transform(ip, transip);
|
||||
|
||||
real_t c[3]={0.2,0.2,0.0};
|
||||
real_t r=0.0;
|
||||
real_t d;
|
||||
for(int i=0;i<3;i++){
|
||||
d=x[i]-c[i];
|
||||
r=r+d*d;
|
||||
}
|
||||
|
||||
r=sqrt(r);
|
||||
if(r>0.05){return 0.0;}
|
||||
else{ return penalty;}
|
||||
}
|
||||
|
||||
private:
|
||||
real_t penalty;
|
||||
};
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
// 1. Initialize MPI and HYPRE.
|
||||
mfem::Mpi::Init(argc, argv);
|
||||
int myrank = mfem::Mpi::WorldRank();
|
||||
mfem::Hypre::Init();
|
||||
|
||||
// Parse command-line options.
|
||||
const char *mesh_file = "./dfg_bench_flow_tri.msh";
|
||||
int order = 1;
|
||||
bool static_cond = false;
|
||||
int ser_ref_levels = 1;
|
||||
int par_ref_levels = 1;
|
||||
real_t newton_rel_tol = 1e-7;
|
||||
real_t newton_abs_tol = 1e-12;
|
||||
int newton_iter = 10;
|
||||
int print_level = 1;
|
||||
bool visualization = false;
|
||||
|
||||
mfem::OptionsParser args(argc, argv);
|
||||
args.AddOption(&mesh_file, "-m", "--mesh",
|
||||
"Mesh file to use.");
|
||||
args.AddOption(&ser_ref_levels,
|
||||
"-rs",
|
||||
"--refine-serial",
|
||||
"Number of times to refine the mesh uniformly in serial.");
|
||||
args.AddOption(&par_ref_levels,
|
||||
"-rp",
|
||||
"--refine-parallel",
|
||||
"Number of times to refine the mesh uniformly in parallel.");
|
||||
args.AddOption(&order, "-o", "--order",
|
||||
"Finite element order (polynomial degree) or -1 for"
|
||||
" isoparametric space.");
|
||||
args.AddOption(&visualization,
|
||||
"-vis",
|
||||
"--visualization",
|
||||
"-no-vis",
|
||||
"--no-visualization",
|
||||
"Enable or disable GLVis visualization.");
|
||||
args.AddOption(&static_cond, "-sc", "--static-condensation", "-no-sc",
|
||||
"--no-static-condensation", "Enable static condensation.");
|
||||
args.AddOption(&newton_rel_tol,
|
||||
"-rel",
|
||||
"--relative-tolerance",
|
||||
"Relative tolerance for the Newton solve.");
|
||||
args.AddOption(&newton_abs_tol,
|
||||
"-abs",
|
||||
"--absolute-tolerance",
|
||||
"Absolute tolerance for the Newton solve.");
|
||||
args.AddOption(&newton_iter,
|
||||
"-it",
|
||||
"--newton-iterations",
|
||||
"Maximum iterations for the Newton solve.");
|
||||
args.Parse();
|
||||
if (!args.Good())
|
||||
{
|
||||
if (myrank == 0)
|
||||
{
|
||||
args.PrintUsage(std::cout);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (myrank == 0)
|
||||
{
|
||||
args.PrintOptions(std::cout);
|
||||
}
|
||||
|
||||
// Read the (serial) mesh from the given mesh file on all processors. We
|
||||
// can handle triangular, quadrilateral, tetrahedral, hexahedral, surface
|
||||
// and volume meshes with the same code.
|
||||
mfem::Mesh mesh(mesh_file, 1, 1);
|
||||
int dim = mesh.Dimension();
|
||||
|
||||
// Refine the serial mesh on all processors to increase the resolution. In
|
||||
// this example we do 'ref_levels' of uniform refinement. We choose
|
||||
// 'ref_levels' to be the largest number that gives a final mesh with no
|
||||
// more than 10,000 elements.
|
||||
{
|
||||
int ref_levels =
|
||||
(int)floor(log(1000./mesh.GetNE())/log(2.)/dim);
|
||||
for (int l = 0; l < ref_levels; l++)
|
||||
{
|
||||
mesh.UniformRefinement();
|
||||
}
|
||||
}
|
||||
|
||||
// Define a parallel mesh by a partitioning of the serial mesh. Refine
|
||||
// this mesh further in parallel to increase the resolution. Once the
|
||||
// parallel mesh is defined, the serial mesh can be deleted.
|
||||
mfem::ParMesh pmesh(MPI_COMM_WORLD, mesh);
|
||||
mesh.Clear();
|
||||
{
|
||||
for (int l = 0; l < par_ref_levels; l++)
|
||||
{
|
||||
pmesh.UniformRefinement();
|
||||
}
|
||||
}
|
||||
|
||||
std::cout<<"My rank="<<pmesh.GetMyRank()<<std::endl;
|
||||
|
||||
StokesSolver* solver=new StokesSolver(&pmesh,2);
|
||||
|
||||
|
||||
mfem::Vector bci(dim); bci=1.0; bci(1)=0.0;
|
||||
mfem::Vector zvi(dim); zvi=0.0;
|
||||
std::shared_ptr<VectorCoefficient> cvci;
|
||||
cvci.reset(new VectorConstantCoefficient(bci));
|
||||
std::shared_ptr<VectorCoefficient> zvci;
|
||||
zvci.reset(new VectorConstantCoefficient(zvi));
|
||||
|
||||
solver->AddVelocityBC(1,cvci);
|
||||
solver->AddVelocityBC(2,cvci);
|
||||
solver->AddVelocityBC(3,cvci);
|
||||
//solver->AddVelocityBC(4,cvci);
|
||||
//solver->AddVelocityBC(5,zvci);
|
||||
|
||||
std::shared_ptr<Coefficient> brink;
|
||||
brink.reset(new BrinkCoeff(1000.0));
|
||||
|
||||
solver->SetBrink(brink);
|
||||
|
||||
ParGridFunction pg(solver->GetVelocitySpace()); pg=0.0;
|
||||
ParGridFunction ng(solver->GetVelocitySpace()); ng=0.0;
|
||||
ng.SetTrueVector();
|
||||
solver->SetEssVBC(pg);
|
||||
Vector pgv(ng.GetTrueVector());
|
||||
|
||||
solver->SetEssTDofsV(pgv);
|
||||
|
||||
ng.SetFromTrueDofs(pgv);
|
||||
|
||||
//solver->SetZeroMeanPressure(true);
|
||||
solver->SetLinearSolver(1e-8,1e-12,550);
|
||||
|
||||
solver->Assemble();
|
||||
solver->FSolve();
|
||||
|
||||
//dump the solution
|
||||
{
|
||||
ParGridFunction& vel=solver->GetVelocity();
|
||||
ParGridFunction& pre=solver->GetPressure();
|
||||
|
||||
ParaViewDataCollection paraview_dc("stokes_flow", &pmesh);
|
||||
paraview_dc.SetPrefixPath("ParaView");
|
||||
paraview_dc.SetLevelsOfDetail(order);
|
||||
paraview_dc.SetDataFormat(VTKFormat::BINARY);
|
||||
paraview_dc.SetHighOrderOutput(true);
|
||||
paraview_dc.SetCycle(0);
|
||||
paraview_dc.SetTime(0.0);
|
||||
paraview_dc.RegisterField("vel",&vel);
|
||||
paraview_dc.RegisterField("pres",&pre);
|
||||
paraview_dc.RegisterField("pg",&pg);
|
||||
paraview_dc.RegisterField("ng",&ng);
|
||||
paraview_dc.Save();
|
||||
}
|
||||
|
||||
delete solver;
|
||||
|
||||
MPI::Finalize();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -461,7 +461,7 @@ void ConductionOperator::Mult(const Vector &u, Vector &du_dt) const
|
||||
|
||||
Kmat.Mult(u, z);
|
||||
z.Neg(); // z = -z
|
||||
K->EliminateVDofsInRHS(ess_tdof_list, u, z);
|
||||
K->ParallelEliminateTDofsInRHS(ess_tdof_list, u, z);
|
||||
|
||||
M_solver.Mult(z, du_dt);
|
||||
du_dt.Print();
|
||||
@@ -483,7 +483,7 @@ void ConductionOperator::ImplicitSolve(const real_t dt,
|
||||
MFEM_VERIFY(dt == current_dt, ""); // SDIRK methods use the same dt
|
||||
Kmat.Mult(u, z);
|
||||
z.Neg();
|
||||
K->EliminateVDofsInRHS(ess_tdof_list, u, z);
|
||||
K->ParallelEliminateTDofsInRHS(ess_tdof_list, u, z);
|
||||
|
||||
T_solver.Mult(z, du_dt);
|
||||
du_dt.SetSubVector(ess_tdof_list, 0.0);
|
||||
|
||||
@@ -87,6 +87,8 @@
|
||||
//
|
||||
// Problem 4: level set: Union of doughnut and swiss cheese shapes
|
||||
// mpirun -np 4 distance -m ../../data/inline-hex.mesh -rs 3 -o 2 -t 1.0 -p 4
|
||||
// Problem 5: point source in mfem mesh.
|
||||
// mpirun -np 4 distance -m ../../data/mfem.mesh -p 5 -rs 3 -t 300.0
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
@@ -233,7 +235,8 @@ int main(int argc, char *argv[])
|
||||
"1: Circle / sphere level set in 2D / 3D\n\t"
|
||||
"2: 2D sine-looking level set\n\t"
|
||||
"3: Gyroid level set in 2D or 3D\n\t"
|
||||
"4: Combo of a doughnut and swiss cheese shapes in 3D.");
|
||||
"4: Combo of a doughnut and swiss cheese shapes in 3D.\n\t"
|
||||
"5: Point source in MFEM mesh.");
|
||||
args.AddOption(&rs_levels, "-rs", "--refine-serial",
|
||||
"Number of times to refine the mesh uniformly in serial.");
|
||||
args.AddOption(&order, "-o", "--order",
|
||||
@@ -299,6 +302,11 @@ int main(int argc, char *argv[])
|
||||
ls_coeff = new FunctionCoefficient(doughnut_cheese);
|
||||
smooth_steps = 0;
|
||||
}
|
||||
else if (problem == 5)
|
||||
{
|
||||
ls_coeff = new DeltaCoefficient(0.0, 0.0, 1000.0);
|
||||
smooth_steps = 0;
|
||||
}
|
||||
else { MFEM_ABORT("Unrecognized -problem option."); }
|
||||
|
||||
const real_t dx = AvgElementSize(pmesh);
|
||||
@@ -306,7 +314,7 @@ int main(int argc, char *argv[])
|
||||
if (solver_type == 0)
|
||||
{
|
||||
auto ds = new HeatDistanceSolver(t_param * dx * dx);
|
||||
if (problem == 0)
|
||||
if (problem == 0 || problem == 5)
|
||||
{
|
||||
ds->transform = false;
|
||||
}
|
||||
@@ -334,7 +342,7 @@ int main(int argc, char *argv[])
|
||||
// Smooth-out Gibbs oscillations from the input level set. The smoothing
|
||||
// parameter here is specified to be mesh dependent with length scale dx.
|
||||
ParGridFunction filt_gf(&pfes_s);
|
||||
if (problem != 0)
|
||||
if (problem != 0 && problem != 5)
|
||||
{
|
||||
real_t filter_weight = dx;
|
||||
// The normalization-based solver needs a more diffused input.
|
||||
|
||||
@@ -17,10 +17,6 @@ SRC = $(if $(MFEM_DIR:../..=),$(MFEM_DIR)/miniapps/shifted/,)
|
||||
CONFIG_MK = $(or $(wildcard $(MFEM_BUILD_DIR)/config/config.mk),\
|
||||
$(wildcard $(MFEM_INSTALL_DIR)/share/mfem/config.mk))
|
||||
|
||||
# Include defaults.mk to get XLINKER
|
||||
DEFAULTS_MK = $(MFEM_DIR)/config/defaults.mk
|
||||
include $(DEFAULTS_MK)
|
||||
|
||||
MFEM_LIB_FILE = mfem_is_not_built
|
||||
-include $(CONFIG_MK)
|
||||
|
||||
@@ -46,8 +42,7 @@ COMMON_LIB = -L$(MFEM_BUILD_DIR)/miniapps/common -lmfem-common
|
||||
|
||||
# If MFEM_SHARED is set, add the ../common rpath
|
||||
COMMON_LIB += $(if $(MFEM_SHARED:YES=),,\
|
||||
$(if $(MFEM_USE_CUDA:YES=),$(CXX_XLINKER),$(CUDA_XLINKER))-rpath,$(abspath\
|
||||
$(MFEM_BUILD_DIR)/miniapps/common))
|
||||
$(MFEM_XLINKER)-rpath,$(abspath $(MFEM_BUILD_DIR)/miniapps/common))
|
||||
|
||||
.SUFFIXES:
|
||||
.SUFFIXES: .o .cpp .mk
|
||||
|
||||
@@ -109,7 +109,7 @@ int main(int argc, char *argv[])
|
||||
else if (string(fe) == "l") { L2 = true; }
|
||||
else { MFEM_ABORT("Bad FE type. Must be 'h', 'n', 'r', or 'l'."); }
|
||||
|
||||
real_t kappa = (order+1)*(order+1); // Penalty used for DG discretizations
|
||||
real_t kappa = 10*(order+1)*(order+1); // Penalty used for DG discretizations
|
||||
|
||||
Mesh mesh(mesh_file, 1, 1);
|
||||
const int dim = mesh.Dimension();
|
||||
@@ -156,7 +156,7 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
// Partial assembly not currently supported for DG or for surface meshes with
|
||||
// vector finite elements (ND or RT).
|
||||
if (!L2 && (H1 || sdim == dim)) { a.SetAssemblyLevel(AssemblyLevel::PARTIAL); }
|
||||
if (H1 || sdim == dim) { a.SetAssemblyLevel(AssemblyLevel::PARTIAL); }
|
||||
a.Assemble();
|
||||
|
||||
LinearForm b(&fes);
|
||||
@@ -167,6 +167,7 @@ int main(int argc, char *argv[])
|
||||
// DG boundary conditions are enforced weakly with this integrator.
|
||||
b.AddBdrFaceIntegrator(new DGDirichletLFIntegrator(u_coeff, -1.0, kappa));
|
||||
}
|
||||
if (H1) { b.UseFastAssembly(true); }
|
||||
b.Assemble();
|
||||
|
||||
GridFunction x(&fes);
|
||||
|
||||
@@ -112,7 +112,7 @@ int main(int argc, char *argv[])
|
||||
else if (string(fe) == "l") { L2 = true; }
|
||||
else { MFEM_ABORT("Bad FE type. Must be 'h', 'n', 'r', or 'l'."); }
|
||||
|
||||
real_t kappa = (order+1)*(order+1); // Penalty used for DG discretizations
|
||||
real_t kappa = 10*(order+1)*(order+1); // Penalty used for DG discretizations
|
||||
|
||||
Mesh serial_mesh(mesh_file, 1, 1);
|
||||
const int dim = serial_mesh.Dimension();
|
||||
@@ -166,7 +166,7 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
// Partial assembly not currently supported for DG or for surface meshes with
|
||||
// vector finite elements (ND or RT).
|
||||
if (!L2 && (H1 || sdim == dim)) { a.SetAssemblyLevel(AssemblyLevel::PARTIAL); }
|
||||
if (H1 || sdim == dim) { a.SetAssemblyLevel(AssemblyLevel::PARTIAL); }
|
||||
a.Assemble();
|
||||
|
||||
ParLinearForm b(&fes);
|
||||
@@ -177,6 +177,7 @@ int main(int argc, char *argv[])
|
||||
// DG boundary conditions are enforced weakly with this integrator.
|
||||
b.AddBdrFaceIntegrator(new DGDirichletLFIntegrator(u_coeff, -1.0, kappa));
|
||||
}
|
||||
if (H1) { b.UseFastAssembly(true); }
|
||||
b.Assemble();
|
||||
|
||||
ParGridFunction x(&fes);
|
||||
|
||||
@@ -17,10 +17,6 @@ SRC = $(if $(MFEM_DIR:../..=),$(MFEM_DIR)/miniapps/spde/,)
|
||||
CONFIG_MK = $(or $(wildcard $(MFEM_BUILD_DIR)/config/config.mk),\
|
||||
$(wildcard $(MFEM_INSTALL_DIR)/share/mfem/config.mk))
|
||||
|
||||
# Include defaults.mk to get XLINKER
|
||||
DEFAULTS_MK = $(MFEM_DIR)/config/defaults.mk
|
||||
include $(DEFAULTS_MK)
|
||||
|
||||
MFEM_LIB_FILE = mfem_is_not_built
|
||||
-include $(CONFIG_MK)
|
||||
|
||||
|
||||
@@ -17,10 +17,6 @@ SRC = $(if $(MFEM_DIR:../..=),$(MFEM_DIR)/miniapps/tools/,)
|
||||
CONFIG_MK = $(or $(wildcard $(MFEM_BUILD_DIR)/config/config.mk),\
|
||||
$(wildcard $(MFEM_INSTALL_DIR)/share/mfem/config.mk))
|
||||
|
||||
# Include defaults.mk to get XLINKER
|
||||
DEFAULTS_MK = $(MFEM_DIR)/config/defaults.mk
|
||||
include $(DEFAULTS_MK)
|
||||
|
||||
MFEM_LIB_FILE = mfem_is_not_built
|
||||
-include $(CONFIG_MK)
|
||||
|
||||
@@ -44,8 +40,7 @@ COMMON_LIB = -L$(MFEM_BUILD_DIR)/miniapps/common -lmfem-common
|
||||
|
||||
# If MFEM_SHARED is set, add the ../common rpath
|
||||
COMMON_LIB += $(if $(MFEM_SHARED:YES=),,\
|
||||
$(if $(MFEM_USE_CUDA:YES=),$(CXX_XLINKER),$(CUDA_XLINKER))-rpath,$(abspath\
|
||||
$(MFEM_BUILD_DIR)/miniapps/common))
|
||||
$(MFEM_XLINKER)-rpath,$(abspath $(MFEM_BUILD_DIR)/miniapps/common))
|
||||
|
||||
all: $(MINIAPPS)
|
||||
|
||||
|
||||
@@ -17,10 +17,6 @@ SRC = $(if $(MFEM_DIR:../..=),$(MFEM_DIR)/miniapps/toys/,)
|
||||
CONFIG_MK = $(or $(wildcard $(MFEM_BUILD_DIR)/config/config.mk),\
|
||||
$(wildcard $(MFEM_INSTALL_DIR)/share/mfem/config.mk))
|
||||
|
||||
# Include defaults.mk to get XLINKER
|
||||
DEFAULTS_MK = $(MFEM_DIR)/config/defaults.mk
|
||||
include $(DEFAULTS_MK)
|
||||
|
||||
MFEM_LIB_FILE = mfem_is_not_built
|
||||
-include $(CONFIG_MK)
|
||||
|
||||
@@ -41,8 +37,7 @@ COMMON_LIB = -L$(MFEM_BUILD_DIR)/miniapps/common -lmfem-common
|
||||
|
||||
# If MFEM_SHARED is set, add the ../common rpath
|
||||
COMMON_LIB += $(if $(MFEM_SHARED:YES=),,\
|
||||
$(if $(MFEM_USE_CUDA:YES=),$(CXX_XLINKER),$(CUDA_XLINKER))-rpath,$(abspath\
|
||||
$(MFEM_BUILD_DIR)/miniapps/common))
|
||||
$(MFEM_XLINKER)-rpath,$(abspath $(MFEM_BUILD_DIR)/miniapps/common))
|
||||
|
||||
all: $(MINIAPPS)
|
||||
|
||||
|
||||
@@ -65,6 +65,9 @@ set(UNIT_TESTS_SRCS
|
||||
mesh/test_psubmesh.cpp
|
||||
mesh/test_submesh.cpp
|
||||
mesh/test_vtu.cpp
|
||||
mesh/test_nurbs.cpp
|
||||
mesh/test_exodus_writer.cpp
|
||||
fem/make_permuted_mesh.cpp
|
||||
fem/test_1d_bilininteg.cpp
|
||||
fem/test_2d_bilininteg.cpp
|
||||
fem/test_3d_bilininteg.cpp
|
||||
@@ -111,6 +114,7 @@ set(UNIT_TESTS_SRCS
|
||||
fem/test_linear_fes.cpp
|
||||
fem/test_linearform_ext.cpp
|
||||
fem/test_lor_batched.cpp
|
||||
fem/test_lor_dg.cpp
|
||||
fem/test_lor.cpp
|
||||
fem/test_nonlinearform.cpp
|
||||
fem/test_operatorjacobismoother.cpp
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
// 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 "make_permuted_mesh.hpp"
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
|
||||
Mesh Mesh2D_Orientation(int face_perm_1, int face_perm_2)
|
||||
{
|
||||
static const int dim = 2;
|
||||
static const int nv = 6;
|
||||
static const int nel = 2;
|
||||
Mesh mesh(dim, nv, nel);
|
||||
real_t x[dim];
|
||||
x[0] = 0.0; x[1] = 0.0;
|
||||
mesh.AddVertex(x);
|
||||
x[0] = 1.0; x[1] = 0.0;
|
||||
mesh.AddVertex(x);
|
||||
x[0] = 2.0; x[1] = 0.0;
|
||||
mesh.AddVertex(x);
|
||||
x[0] = 0.0; x[1] = 1.0;
|
||||
mesh.AddVertex(x);
|
||||
x[0] = 1.0; x[1] = 1.0;
|
||||
mesh.AddVertex(x);
|
||||
x[0] = 2.0; x[1] = 1.0;
|
||||
mesh.AddVertex(x);
|
||||
int el[4];
|
||||
el[0] = 0;
|
||||
el[1] = 1;
|
||||
el[2] = 4;
|
||||
el[3] = 3;
|
||||
std::rotate(&el[0], &el[face_perm_1], &el[3] + 1);
|
||||
|
||||
mesh.AddQuad(el);
|
||||
|
||||
el[0] = 1;
|
||||
el[1] = 2;
|
||||
el[2] = 5;
|
||||
el[3] = 4;
|
||||
std::rotate(&el[0], &el[face_perm_2], &el[3] + 1);
|
||||
mesh.AddQuad(el);
|
||||
|
||||
mesh.FinalizeQuadMesh(true);
|
||||
mesh.GenerateBoundaryElements();
|
||||
mesh.Finalize();
|
||||
return mesh;
|
||||
}
|
||||
|
||||
void Rotation3DVertices(int *v, int ref_face, int rot)
|
||||
{
|
||||
std::vector<int> face_1, face_2;
|
||||
|
||||
switch (ref_face/2)
|
||||
{
|
||||
case 0:
|
||||
face_1 = {v[0], v[1], v[2], v[3]};
|
||||
face_2 = {v[4], v[5], v[6], v[7]};
|
||||
break;
|
||||
case 1:
|
||||
face_1 = {v[1], v[5], v[6], v[2]};
|
||||
face_2 = {v[0], v[4], v[7], v[3]};
|
||||
break;
|
||||
case 2:
|
||||
face_1 = {v[4], v[5], v[1], v[0]};
|
||||
face_2 = {v[7], v[6], v[2], v[3]};
|
||||
break;
|
||||
}
|
||||
if (ref_face % 2 == 0)
|
||||
{
|
||||
std::reverse(face_1.begin(), face_1.end());
|
||||
std::reverse(face_2.begin(), face_2.end());
|
||||
std::swap(face_1, face_2);
|
||||
}
|
||||
|
||||
std::rotate(face_1.begin(), face_1.begin() + rot, face_1.end());
|
||||
std::rotate(face_2.begin(), face_2.begin() + rot, face_2.end());
|
||||
|
||||
for (int i=0; i<4; ++i)
|
||||
{
|
||||
v[i] = face_1[i];
|
||||
v[i+4] = face_2[i];
|
||||
}
|
||||
}
|
||||
|
||||
Mesh Mesh3D_Orientation(int face_perm_1, int face_perm_2)
|
||||
{
|
||||
static const int dim = 3;
|
||||
static const int nv = 12;
|
||||
static const int nel = 2;
|
||||
Mesh mesh(dim, nv, nel);
|
||||
real_t x[dim];
|
||||
x[0] = 0.0; x[1] = 0.0; x[2] = 0.0;
|
||||
mesh.AddVertex(x);
|
||||
x[0] = 1.0; x[1] = 0.0; x[2] = 0.0;
|
||||
mesh.AddVertex(x);
|
||||
x[0] = 2.0; x[1] = 0.0; x[2] = 0.0;
|
||||
mesh.AddVertex(x);
|
||||
x[0] = 0.0; x[1] = 1.0; x[2] = 0.0;
|
||||
mesh.AddVertex(x);
|
||||
x[0] = 1.0; x[1] = 1.0; x[2] = 0.0;
|
||||
mesh.AddVertex(x);
|
||||
x[0] = 2.0; x[1] = 1.0; x[2] = 0.0;
|
||||
mesh.AddVertex(x);
|
||||
x[0] = 0.0; x[1] = 0.0; x[2] = 1.0;
|
||||
mesh.AddVertex(x);
|
||||
x[0] = 1.0; x[1] = 0.0; x[2] = 1.0;
|
||||
mesh.AddVertex(x);
|
||||
x[0] = 2.0; x[1] = 0.0; x[2] = 1.0;
|
||||
mesh.AddVertex(x);
|
||||
x[0] = 0.0; x[1] = 1.0; x[2] = 1.0;
|
||||
mesh.AddVertex(x);
|
||||
x[0] = 1.0; x[1] = 1.0; x[2] = 1.0;
|
||||
mesh.AddVertex(x);
|
||||
x[0] = 2.0; x[1] = 1.0; x[2] = 1.0;
|
||||
mesh.AddVertex(x);
|
||||
|
||||
int el[8];
|
||||
|
||||
el[0] = 0;
|
||||
el[1] = 1;
|
||||
el[2] = 4;
|
||||
el[3] = 3;
|
||||
el[4] = 6;
|
||||
el[5] = 7;
|
||||
el[6] = 10;
|
||||
el[7] = 9;
|
||||
Rotation3DVertices(el, face_perm_1/4, face_perm_1%4);
|
||||
mesh.AddHex(el);
|
||||
|
||||
el[0] = 1;
|
||||
el[1] = 2;
|
||||
el[2] = 5;
|
||||
el[3] = 4;
|
||||
el[4] = 7;
|
||||
el[5] = 8;
|
||||
el[6] = 11;
|
||||
el[7] = 10;
|
||||
Rotation3DVertices(el, face_perm_2/4, face_perm_2%4);
|
||||
mesh.AddHex(el);
|
||||
|
||||
mesh.FinalizeHexMesh(true);
|
||||
mesh.Finalize();
|
||||
return mesh;
|
||||
}
|
||||
|
||||
Mesh MeshOrientation(int dim, int o1, int o2)
|
||||
{
|
||||
if (dim == 2) { return Mesh2D_Orientation(o1, o2); }
|
||||
else if (dim == 3) { return Mesh3D_Orientation(o1, o2); }
|
||||
else { MFEM_ABORT("Unsupported dimension."); }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// 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.
|
||||
|
||||
#ifndef MFEM_MAKE_PERMUTED_MESH_HPP
|
||||
#define MFEM_MAKE_PERMUTED_MESH_HPP
|
||||
|
||||
#include "mfem.hpp"
|
||||
#include "unit_tests.hpp"
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
|
||||
Mesh MeshOrientation(int dim, int o1, int o2);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -11,148 +11,10 @@
|
||||
|
||||
#include "mfem.hpp"
|
||||
#include "unit_tests.hpp"
|
||||
#include "make_permuted_mesh.hpp"
|
||||
|
||||
using namespace mfem;
|
||||
|
||||
Mesh *mesh_2d_orientation(int face_perm_1, int face_perm_2)
|
||||
{
|
||||
static const int dim = 2;
|
||||
static const int nv = 6;
|
||||
static const int nel = 2;
|
||||
Mesh *mesh = new Mesh(dim, nv, nel);
|
||||
real_t x[dim];
|
||||
x[0] = 0.0; x[1] = 0.0;
|
||||
mesh->AddVertex(x);
|
||||
x[0] = 1.0; x[1] = 0.0;
|
||||
mesh->AddVertex(x);
|
||||
x[0] = 2.0; x[1] = 0.0;
|
||||
mesh->AddVertex(x);
|
||||
x[0] = 0.0; x[1] = 1.0;
|
||||
mesh->AddVertex(x);
|
||||
x[0] = 1.0; x[1] = 1.0;
|
||||
mesh->AddVertex(x);
|
||||
x[0] = 2.0; x[1] = 1.0;
|
||||
mesh->AddVertex(x);
|
||||
int el[4];
|
||||
el[0] = 0;
|
||||
el[1] = 1;
|
||||
el[2] = 4;
|
||||
el[3] = 3;
|
||||
std::rotate(&el[0], &el[face_perm_1], &el[3] + 1);
|
||||
|
||||
mesh->AddQuad(el);
|
||||
|
||||
el[0] = 1;
|
||||
el[1] = 2;
|
||||
el[2] = 5;
|
||||
el[3] = 4;
|
||||
std::rotate(&el[0], &el[face_perm_2], &el[3] + 1);
|
||||
mesh->AddQuad(el);
|
||||
|
||||
mesh->FinalizeQuadMesh(true);
|
||||
mesh->GenerateBoundaryElements();
|
||||
mesh->Finalize();
|
||||
return mesh;
|
||||
}
|
||||
|
||||
void rotate_3d_vertices(int *v, int ref_face, int rot)
|
||||
{
|
||||
std::vector<int> face_1, face_2;
|
||||
|
||||
switch (ref_face/2)
|
||||
{
|
||||
case 0:
|
||||
face_1 = {v[0], v[1], v[2], v[3]};
|
||||
face_2 = {v[4], v[5], v[6], v[7]};
|
||||
break;
|
||||
case 1:
|
||||
face_1 = {v[1], v[5], v[6], v[2]};
|
||||
face_2 = {v[0], v[4], v[7], v[3]};
|
||||
break;
|
||||
case 2:
|
||||
face_1 = {v[4], v[5], v[1], v[0]};
|
||||
face_2 = {v[7], v[6], v[2], v[3]};
|
||||
break;
|
||||
}
|
||||
if (ref_face % 2 == 0)
|
||||
{
|
||||
std::reverse(face_1.begin(), face_1.end());
|
||||
std::reverse(face_2.begin(), face_2.end());
|
||||
std::swap(face_1, face_2);
|
||||
}
|
||||
|
||||
std::rotate(face_1.begin(), face_1.begin() + rot, face_1.end());
|
||||
std::rotate(face_2.begin(), face_2.begin() + rot, face_2.end());
|
||||
|
||||
for (int i=0; i<4; ++i)
|
||||
{
|
||||
v[i] = face_1[i];
|
||||
v[i+4] = face_2[i];
|
||||
}
|
||||
}
|
||||
|
||||
Mesh *mesh_3d_orientation(int face_perm_1, int face_perm_2)
|
||||
{
|
||||
static const int dim = 3;
|
||||
static const int nv = 12;
|
||||
static const int nel = 2;
|
||||
Mesh *mesh = new Mesh(dim, nv, nel);
|
||||
real_t x[dim];
|
||||
x[0] = 0.0; x[1] = 0.0; x[2] = 0.0;
|
||||
mesh->AddVertex(x);
|
||||
x[0] = 1.0; x[1] = 0.0; x[2] = 0.0;
|
||||
mesh->AddVertex(x);
|
||||
x[0] = 2.0; x[1] = 0.0; x[2] = 0.0;
|
||||
mesh->AddVertex(x);
|
||||
x[0] = 0.0; x[1] = 1.0; x[2] = 0.0;
|
||||
mesh->AddVertex(x);
|
||||
x[0] = 1.0; x[1] = 1.0; x[2] = 0.0;
|
||||
mesh->AddVertex(x);
|
||||
x[0] = 2.0; x[1] = 1.0; x[2] = 0.0;
|
||||
mesh->AddVertex(x);
|
||||
x[0] = 0.0; x[1] = 0.0; x[2] = 1.0;
|
||||
mesh->AddVertex(x);
|
||||
x[0] = 1.0; x[1] = 0.0; x[2] = 1.0;
|
||||
mesh->AddVertex(x);
|
||||
x[0] = 2.0; x[1] = 0.0; x[2] = 1.0;
|
||||
mesh->AddVertex(x);
|
||||
x[0] = 0.0; x[1] = 1.0; x[2] = 1.0;
|
||||
mesh->AddVertex(x);
|
||||
x[0] = 1.0; x[1] = 1.0; x[2] = 1.0;
|
||||
mesh->AddVertex(x);
|
||||
x[0] = 3.0; x[1] = 1.0; x[2] = 1.0;
|
||||
mesh->AddVertex(x);
|
||||
|
||||
int el[8];
|
||||
|
||||
el[0] = 0;
|
||||
el[1] = 1;
|
||||
el[2] = 4;
|
||||
el[3] = 3;
|
||||
el[4] = 6;
|
||||
el[5] = 7;
|
||||
el[6] = 10;
|
||||
el[7] = 9;
|
||||
rotate_3d_vertices(el, face_perm_1/4, face_perm_1%4);
|
||||
mesh->AddHex(el);
|
||||
|
||||
el[0] = 1;
|
||||
el[1] = 2;
|
||||
el[2] = 5;
|
||||
el[3] = 4;
|
||||
el[4] = 7;
|
||||
el[5] = 8;
|
||||
el[6] = 11;
|
||||
el[7] = 10;
|
||||
rotate_3d_vertices(el, face_perm_2/4, face_perm_2%4);
|
||||
mesh->AddHex(el);
|
||||
|
||||
mesh->FinalizeHexMesh(true);
|
||||
mesh->GenerateBoundaryElements();
|
||||
mesh->Finalize();
|
||||
return mesh;
|
||||
}
|
||||
|
||||
real_t x_fn(const Vector &xvec) { return xvec[0]; }
|
||||
real_t y_fn(const Vector &xvec) { return xvec[1]; }
|
||||
real_t z_fn(const Vector &xvec) { return xvec[2]; }
|
||||
@@ -214,10 +76,9 @@ TEST_CASE("2D Face Permutation", "[Face Permutation]")
|
||||
{
|
||||
for (int fp1=0; fp1<4; ++fp1)
|
||||
{
|
||||
Mesh *mesh = mesh_2d_orientation(fp1, fp2);
|
||||
real_t error = TestFaceRestriction(*mesh, order);
|
||||
Mesh mesh = MeshOrientation(2, fp1, fp2);
|
||||
real_t error = TestFaceRestriction(mesh, order);
|
||||
max_err = std::max(max_err, error);
|
||||
delete mesh;
|
||||
}
|
||||
}
|
||||
REQUIRE(max_err < 1e-15);
|
||||
@@ -231,10 +92,9 @@ TEST_CASE("3D Face Permutation", "[Face Permutation]")
|
||||
{
|
||||
for (int fp1=0; fp1<24; ++fp1)
|
||||
{
|
||||
Mesh *mesh = mesh_3d_orientation(fp1, fp2);
|
||||
real_t error = TestFaceRestriction(*mesh, order);
|
||||
Mesh mesh = MeshOrientation(3, fp1, fp2);
|
||||
real_t error = TestFaceRestriction(mesh, order);
|
||||
max_err = std::max(max_err, error);
|
||||
delete mesh;
|
||||
}
|
||||
}
|
||||
REQUIRE(max_err < 1e-15);
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include "mfem.hpp"
|
||||
#include "unit_tests.hpp"
|
||||
#include "../linalg/test_same_matrices.hpp"
|
||||
#include "../../fem/lor/lor_ads.hpp"
|
||||
#include "../../fem/lor/lor_ams.hpp"
|
||||
#include <memory>
|
||||
@@ -25,47 +26,6 @@ using namespace mfem;
|
||||
namespace lor_batched
|
||||
{
|
||||
|
||||
void TestSameMatrices(SparseMatrix &A1, const SparseMatrix &A2,
|
||||
HYPRE_BigInt *cmap1=nullptr,
|
||||
std::unordered_map<HYPRE_BigInt,int> *cmap2inv=nullptr)
|
||||
{
|
||||
REQUIRE(A1.Height() == A2.Height());
|
||||
int n = A1.Height();
|
||||
|
||||
const int *I1 = A1.HostReadI();
|
||||
const int *J1 = A1.HostReadJ();
|
||||
const real_t *V1 = A1.HostReadData();
|
||||
|
||||
A2.HostReadI();
|
||||
A2.HostReadJ();
|
||||
A2.HostReadData();
|
||||
|
||||
real_t error = 0.0;
|
||||
|
||||
for (int i=0; i<n; ++i)
|
||||
{
|
||||
for (int jj=I1[i]; jj<I1[i+1]; ++jj)
|
||||
{
|
||||
int j = J1[jj];
|
||||
if (cmap1)
|
||||
{
|
||||
if (cmap2inv->count(cmap1[j]) > 0)
|
||||
{
|
||||
j = (*cmap2inv)[cmap1[j]];
|
||||
}
|
||||
else
|
||||
{
|
||||
error = std::max(error, std::fabs(V1[jj]));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
error = std::max(error, std::fabs(V1[jj] - A2(i,j)));
|
||||
}
|
||||
}
|
||||
|
||||
REQUIRE(error == MFEM_Approx(0.0, 1e-10));
|
||||
}
|
||||
|
||||
template <typename FE_COLL>
|
||||
FE_COLL *NewLOR_FE_Collection(int order, int dim)
|
||||
{
|
||||
@@ -157,30 +117,6 @@ TEST_CASE("LOR Batched RT", "[LOR][BatchedLOR][GPU]")
|
||||
|
||||
#ifdef MFEM_USE_MPI
|
||||
|
||||
void TestSameMatrices(HypreParMatrix &A1, const HypreParMatrix &A2)
|
||||
{
|
||||
HYPRE_BigInt *cmap1, *cmap2;
|
||||
SparseMatrix diag1, offd1, diag2, offd2;
|
||||
|
||||
A1.GetDiag(diag1);
|
||||
A2.GetDiag(diag2);
|
||||
A1.GetOffd(offd1, cmap1);
|
||||
A2.GetOffd(offd2, cmap2);
|
||||
|
||||
TestSameMatrices(diag1, diag2);
|
||||
|
||||
if (cmap1)
|
||||
{
|
||||
std::unordered_map<HYPRE_BigInt,int> cmap2inv;
|
||||
for (int i=0; i<offd2.Width(); ++i) { cmap2inv[cmap2[i]] = i; }
|
||||
TestSameMatrices(offd1, offd2, cmap1, &cmap2inv);
|
||||
}
|
||||
else
|
||||
{
|
||||
TestSameMatrices(offd1, offd2);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename FE_COLL, typename INTEG_1, typename INTEG_2>
|
||||
void ParTestBatchedLOR()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,382 @@
|
||||
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
|
||||
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
|
||||
// LICENSE and NOTICE for details. LLNL-CODE-806117.
|
||||
//
|
||||
// This file is part of the MFEM library. For more information and source code
|
||||
// availability visit https://mfem.org.
|
||||
//
|
||||
// MFEM is free software; you can redistribute it and/or modify it under the
|
||||
// terms of the BSD-3 license. We welcome feedback and contributions, see file
|
||||
// CONTRIBUTING.md for details.
|
||||
|
||||
#include "mfem.hpp"
|
||||
#include "unit_tests.hpp"
|
||||
#include "make_permuted_mesh.hpp"
|
||||
#include "../linalg/test_same_matrices.hpp"
|
||||
|
||||
using namespace mfem;
|
||||
|
||||
class DG_LOR_DiffusionPreconditioner : public BilinearFormIntegrator
|
||||
{
|
||||
Mesh &mesh;
|
||||
double kappa;
|
||||
int p;
|
||||
IntegrationRule gl_p, gl_pp1;
|
||||
Vector shape1, shape2, nor;
|
||||
|
||||
public:
|
||||
DG_LOR_DiffusionPreconditioner(Mesh &mesh_, int p_, double kappa_)
|
||||
: mesh(mesh_), kappa(kappa_), p(p_)
|
||||
{
|
||||
QuadratureFunctions1D::GaussLobatto(p+1, &gl_p);
|
||||
QuadratureFunctions1D::GaussLobatto(p+2, &gl_pp1);
|
||||
}
|
||||
|
||||
double PenaltyFactor(int idx1, int idx2)
|
||||
{
|
||||
int pp1 = p + 1;
|
||||
|
||||
int x1 = idx1 % pp1;
|
||||
int y1 = (idx1 / pp1) % pp1;
|
||||
int z1 = (idx1 / pp1) / pp1;
|
||||
|
||||
int x2 = idx2 % pp1;
|
||||
int y2 = (idx2 / pp1) % pp1;
|
||||
int z2 = (idx2 / pp1) / pp1;
|
||||
|
||||
int dim = mesh.Dimension();
|
||||
|
||||
auto compute_factor = [&](int i1, int i2)
|
||||
{
|
||||
int j = std::min(i1, i2);
|
||||
if (i1 == i2)
|
||||
{
|
||||
double w = gl_p[j].weight;
|
||||
double k = gl_pp1[i1+1].x - gl_pp1[i1].x;
|
||||
return w/k;
|
||||
}
|
||||
else
|
||||
{
|
||||
double h = gl_p[j+1].x - gl_p[j].x;
|
||||
double k1 = gl_pp1[i1+1].x - gl_pp1[i1].x;
|
||||
double k2 = gl_pp1[i2+1].x - gl_pp1[i2].x;
|
||||
double avg = 0.5*k1 + 0.5*k2;
|
||||
return avg/h;
|
||||
}
|
||||
};
|
||||
|
||||
double factor = compute_factor(x1, x2);
|
||||
if (dim >= 2) { factor *= compute_factor(y1, y2); }
|
||||
if (dim == 3) { factor *= compute_factor(z1, z2); }
|
||||
|
||||
return factor;
|
||||
}
|
||||
|
||||
double BdrPenaltyFactor(int idx, int f)
|
||||
{
|
||||
int pp1 = p+1;
|
||||
|
||||
int x = idx % pp1;
|
||||
int y = (idx / pp1) % pp1;
|
||||
int z = (idx / pp1) / pp1;
|
||||
|
||||
int dim = mesh.Dimension();
|
||||
|
||||
auto subcell_size = [&](int i)
|
||||
{
|
||||
return gl_pp1[i+1].x - gl_pp1[i].x;
|
||||
};
|
||||
|
||||
double factor = (p+1)*(p+1);
|
||||
if (dim == 1)
|
||||
{
|
||||
factor *= subcell_size(x);
|
||||
}
|
||||
else if (dim == 2)
|
||||
{
|
||||
int ni, nj;
|
||||
ni = (f == 1 || f == 3) ? x : y;
|
||||
nj = (f == 1 || f == 3) ? y : x;
|
||||
factor *= subcell_size(ni)/subcell_size(nj)*gl_p[nj].weight;
|
||||
}
|
||||
else if (dim == 3)
|
||||
{
|
||||
int ni, nj, nk;
|
||||
if (f == 2 || f == 4) { ni = x; nj = y; nk = z; }
|
||||
else if (f == 1 || f == 3) { ni = y; nj = x; nk = z; }
|
||||
else { ni = z; nj = x; nk = y; }
|
||||
factor *= subcell_size(ni)/subcell_size(nj)/subcell_size(nk);
|
||||
factor *= gl_p[nj].weight*gl_p[nk].weight;
|
||||
}
|
||||
|
||||
return factor;
|
||||
}
|
||||
|
||||
using BilinearFormIntegrator::AssembleFaceMatrix;
|
||||
virtual void AssembleFaceMatrix(const FiniteElement &el1,
|
||||
const FiniteElement &el2,
|
||||
FaceElementTransformations &Trans,
|
||||
DenseMatrix &elmat) override
|
||||
{
|
||||
int dim, ndof1, ndof2, ndofs;
|
||||
double w, wq = 0.0;
|
||||
|
||||
dim = el1.GetDim();
|
||||
ndof1 = el1.GetDof();
|
||||
|
||||
nor.SetSize(dim);
|
||||
|
||||
shape1.SetSize(ndof1);
|
||||
if (Trans.Elem2No >= 0)
|
||||
{
|
||||
ndof2 = el2.GetDof();
|
||||
shape2.SetSize(ndof2);
|
||||
}
|
||||
else
|
||||
{
|
||||
ndof2 = 0;
|
||||
}
|
||||
|
||||
int face_no;
|
||||
if (ndof2) { face_no = Trans.ElementNo; }
|
||||
else { face_no = mesh.GetBdrElementFaceIndex(Trans.ElementNo); }
|
||||
|
||||
int info1, info2;
|
||||
mesh.GetFaceInfos(face_no, &info1, &info2);
|
||||
int local_face = info1/64;
|
||||
|
||||
const CoarseFineTransformations &cftr = mesh.GetRefinementTransforms();
|
||||
|
||||
double factor;
|
||||
bool interior = false;
|
||||
if (Trans.Elem2No >= 0 && Trans.Elem2No < mesh.GetNE())
|
||||
{
|
||||
int parent_el1 = cftr.embeddings[Trans.Elem1No].parent;
|
||||
int parent_el2 = cftr.embeddings[Trans.Elem2No].parent;
|
||||
if (parent_el1 == parent_el2)
|
||||
{
|
||||
interior = true;
|
||||
factor = PenaltyFactor(cftr.embeddings[Trans.Elem1No].matrix,
|
||||
cftr.embeddings[Trans.Elem2No].matrix);
|
||||
}
|
||||
}
|
||||
if (!interior)
|
||||
{
|
||||
factor = kappa*BdrPenaltyFactor(cftr.embeddings[Trans.Elem1No].matrix,
|
||||
local_face);
|
||||
}
|
||||
|
||||
ndofs = ndof1 + ndof2;
|
||||
elmat.SetSize(ndofs);
|
||||
elmat = 0.0;
|
||||
|
||||
const IntegrationRule *ir = IntRule;
|
||||
if (ir == NULL) { ir = &IntRules.Get(Trans.GetGeometryType(), 1); }
|
||||
|
||||
for (int q = 0; q < ir->GetNPoints(); q++)
|
||||
{
|
||||
const IntegrationPoint &ip = ir->IntPoint(q);
|
||||
Trans.SetAllIntPoints(&ip);
|
||||
const IntegrationPoint &eip1 = Trans.GetElement1IntPoint();
|
||||
const IntegrationPoint &eip2 = Trans.GetElement2IntPoint();
|
||||
|
||||
if (dim == 1) { nor(0) = 2*eip1.x - 1.0; }
|
||||
else { CalcOrtho(Trans.Jacobian(), nor); }
|
||||
|
||||
el1.CalcShape(eip1, shape1);
|
||||
w = ip.weight;
|
||||
|
||||
double h_face = nor.Norml2();
|
||||
double h_el = Trans.Elem1->Weight();
|
||||
double h = h_el/h_face; // perpendicular element size
|
||||
|
||||
if (ndof2)
|
||||
{
|
||||
el2.CalcShape(eip2, shape2);
|
||||
double h_el_2 = Trans.Elem2->Weight();
|
||||
h = 0.5*h + 0.5*h_el_2/h_face; // average both element sizes
|
||||
}
|
||||
|
||||
if (interior)
|
||||
{
|
||||
wq = w*factor*h_face/h;
|
||||
}
|
||||
else
|
||||
{
|
||||
wq = w*factor*h_face/h;
|
||||
}
|
||||
for (int i = 0; i < ndof1; i++)
|
||||
{
|
||||
const double wsi = wq*shape1(i);
|
||||
for (int j = 0; j < ndof1; j++)
|
||||
{
|
||||
elmat(i, j) += wsi * shape1(j);
|
||||
}
|
||||
}
|
||||
if (ndof2)
|
||||
{
|
||||
for (int i = 0; i < ndof2; i++)
|
||||
{
|
||||
const double wsi = wq*shape2(i);
|
||||
for (int j = 0; j < ndof1; j++)
|
||||
{
|
||||
elmat(ndof1 + i, j) -= wsi * shape1(j);
|
||||
elmat(j, ndof1 + i) -= wsi * shape1(j);
|
||||
}
|
||||
for (int j = 0; j < ndof2; j++)
|
||||
{
|
||||
elmat(ndof1 + i, ndof1 + j) += wsi * shape2(j);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class DG_LOR_MassPreconditioner : public BilinearFormIntegrator
|
||||
{
|
||||
Mesh &mesh_ho, &mesh_lor;
|
||||
const int p;
|
||||
IntegrationRule gll;
|
||||
Coefficient *Q;
|
||||
|
||||
public:
|
||||
DG_LOR_MassPreconditioner(Mesh &mesh_ho_,
|
||||
Mesh &mesh_lor_,
|
||||
int p_,
|
||||
Coefficient *Q_)
|
||||
: mesh_ho(mesh_ho_),
|
||||
mesh_lor(mesh_lor_),
|
||||
p(p_),
|
||||
Q(Q_)
|
||||
{
|
||||
QuadratureFunctions1D::GaussLobatto(p+1, &gll);
|
||||
}
|
||||
|
||||
void AssembleElementMatrix(const FiniteElement &el,
|
||||
ElementTransformation &Tr,
|
||||
DenseMatrix &elmat) override
|
||||
{
|
||||
const CoarseFineTransformations &cftr = mesh_lor.GetRefinementTransforms();
|
||||
const int parent_el = cftr.embeddings[Tr.ElementNo].parent;
|
||||
// We use the point matrix index to identify the local LOR element index
|
||||
// within the high-order coarse element.
|
||||
const int lor_index = cftr.embeddings[Tr.ElementNo].matrix;
|
||||
|
||||
// Assuming piecewise constant
|
||||
elmat.SetSize(1);
|
||||
|
||||
const int dim = mesh_ho.Dimension();
|
||||
IntegrationPoint ip;
|
||||
if (dim == 2)
|
||||
{
|
||||
const int iy = lor_index / (p + 1);
|
||||
const int ix = lor_index % (p + 1);
|
||||
ip.x = gll[ix].x;
|
||||
ip.y = gll[iy].x;
|
||||
|
||||
elmat(0,0) = gll[ix].weight * gll[iy].weight;
|
||||
}
|
||||
else if (dim == 3)
|
||||
{
|
||||
const int iz = lor_index / (p + 1) / (p + 1);
|
||||
const int iy = (lor_index / (p + 1)) % (p + 1);
|
||||
const int ix = lor_index % (p + 1);
|
||||
|
||||
ip.x = gll[ix].x;
|
||||
ip.y = gll[iy].x;
|
||||
ip.z = gll[iz].x;
|
||||
|
||||
elmat(0,0) = gll[ix].weight * gll[iy].weight * gll[iz].weight;
|
||||
}
|
||||
|
||||
ElementTransformation &Tr_ho = *mesh_ho.GetElementTransformation(parent_el);
|
||||
Tr_ho.SetIntPoint(&ip);
|
||||
const real_t detJ = Tr_ho.Weight();
|
||||
elmat(0,0) *= detJ;
|
||||
|
||||
if (Q)
|
||||
{
|
||||
elmat(0,0) *= Q->Eval(Tr_ho, ip);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static void TestBatchedLOR_DG(Mesh &mesh, int order)
|
||||
{
|
||||
DG_FECollection fec(order, mesh.Dimension(), BasisType::GaussLobatto);
|
||||
FiniteElementSpace fespace(&mesh, &fec);
|
||||
|
||||
// Set up some coefficients using H1 grid functions
|
||||
H1_FECollection h1fec(2, mesh.Dimension());
|
||||
FiniteElementSpace h1fes(&mesh, &h1fec);
|
||||
GridFunction gf1(&h1fes), gf2(&h1fes);
|
||||
gf1.Randomize(1);
|
||||
gf2.Randomize(2);
|
||||
GridFunctionCoefficient mass_coeff(&gf1);
|
||||
GridFunctionCoefficient diff_coeff(&gf2);
|
||||
|
||||
ConstantCoefficient one(1.0);
|
||||
constexpr real_t sigma = -1.0;
|
||||
const int eta = 2;
|
||||
const int kappa = eta * (order + 1) * (order + 1);
|
||||
BilinearForm a(&fespace);
|
||||
a.AddDomainIntegrator(new DiffusionIntegrator);
|
||||
a.AddInteriorFaceIntegrator(new DGDiffusionIntegrator(one, sigma, kappa));
|
||||
a.AddBdrFaceIntegrator(new DGDiffusionIntegrator(one, sigma, kappa));
|
||||
|
||||
Array<int> ess_dofs; // Empty
|
||||
LORDiscretization lor(fespace);
|
||||
lor.AssembleSystem(a, ess_dofs);
|
||||
SparseMatrix &A1 = lor.GetAssembledMatrix();
|
||||
|
||||
FiniteElementSpace &fes_lor = lor.GetFESpace();
|
||||
Mesh &mesh_lor = *fes_lor.GetMesh();
|
||||
BilinearForm a_lor(&fes_lor);
|
||||
a_lor.AddBdrFaceIntegrator(new DG_LOR_DiffusionPreconditioner(
|
||||
mesh_lor, order, eta));
|
||||
a_lor.AddInteriorFaceIntegrator(new DG_LOR_DiffusionPreconditioner(
|
||||
mesh_lor, order, eta));
|
||||
|
||||
a_lor.Assemble();
|
||||
a_lor.Finalize();
|
||||
SparseMatrix &A2 = a_lor.SpMat();
|
||||
|
||||
TestSameMatrices(A1, A2);
|
||||
}
|
||||
|
||||
TEST_CASE("LOR Batched DG Orientation", "[LOR][BatchedLOR][CUDA]")
|
||||
{
|
||||
const int order = 3;
|
||||
const int dim = launch_all_non_regression_tests ? GENERATE(2, 3) : 2;
|
||||
const int orientation1 = GENERATE_COPY(range(0, dim == 2 ? 4 : 24));
|
||||
const int orientation2 = GENERATE_COPY(range(0, dim == 2 ? 4 : 24));
|
||||
|
||||
CAPTURE(order, dim, orientation1, orientation2);
|
||||
|
||||
Mesh mesh = MeshOrientation(dim, orientation1, orientation2);
|
||||
TestBatchedLOR_DG(mesh, order);
|
||||
}
|
||||
|
||||
TEST_CASE("LOR Batched DG", "[LOR][BatchedLOR][CUDA]")
|
||||
{
|
||||
const int order = 3;
|
||||
const auto mesh_fname = GENERATE(
|
||||
"../../data/beam-quad.mesh",
|
||||
"../../data/l-shape.mesh",
|
||||
"../../data/beam-hex.mesh",
|
||||
"../../data/fichera.mesh"
|
||||
);
|
||||
CAPTURE(mesh_fname);
|
||||
Mesh mesh = Mesh::LoadFromFile(mesh_fname);
|
||||
|
||||
mesh.Transform([](const Vector &xin, Vector &xout)
|
||||
{
|
||||
for (int d = 0; d < xin.Size(); ++d)
|
||||
{
|
||||
xout[d] = xin[d] * (1.0 + d / 3.0);
|
||||
}
|
||||
});
|
||||
|
||||
TestBatchedLOR_DG(mesh, order);
|
||||
}
|
||||
@@ -22,7 +22,6 @@ using namespace mfem;
|
||||
|
||||
TEST_CASE("Reduce Sum", "[Reduction],[GPU]")
|
||||
{
|
||||
Array<int> workspace;
|
||||
Array<int> a(1000);
|
||||
a.HostReadWrite();
|
||||
for (int i = 0; i < a.Size(); ++i)
|
||||
@@ -36,7 +35,7 @@ TEST_CASE("Reduce Sum", "[Reduction],[GPU]")
|
||||
int res = 0;
|
||||
mfem::reduce(
|
||||
a.Size(), res, [=] MFEM_HOST_DEVICE(int i, int &r) { r += dptr[i]; },
|
||||
SumReducer<int> {}, use_dev, workspace);
|
||||
SumReducer<int> {}, use_dev);
|
||||
// correct for even-length summations
|
||||
int expected = (AsConst(a)[0] + AsConst(a)[a.Size() - 1]) * a.Size() / 2;
|
||||
CAPTURE(use_dev);
|
||||
@@ -46,7 +45,6 @@ TEST_CASE("Reduce Sum", "[Reduction],[GPU]")
|
||||
|
||||
TEST_CASE("Reduce Mult", "[Reduction],[GPU]")
|
||||
{
|
||||
Array<long long> workspace;
|
||||
Array<long long> a(64);
|
||||
a.HostReadWrite();
|
||||
for (int i = 0; i < a.Size(); ++i)
|
||||
@@ -64,7 +62,7 @@ TEST_CASE("Reduce Mult", "[Reduction],[GPU]")
|
||||
mfem::reduce(
|
||||
a.Size(), res,
|
||||
[=] MFEM_HOST_DEVICE(int i, long long &r) { r *= dptr[i]; },
|
||||
MultReducer<long long> {}, use_dev, workspace);
|
||||
MultReducer<long long> {}, use_dev);
|
||||
long long expected = 0;
|
||||
CAPTURE(use_dev);
|
||||
REQUIRE(res == expected);
|
||||
@@ -76,7 +74,7 @@ TEST_CASE("Reduce Mult", "[Reduction],[GPU]")
|
||||
mfem::reduce(
|
||||
a.Size(), res,
|
||||
[=] MFEM_HOST_DEVICE(int i, long long &r) { r *= dptr[i]; },
|
||||
MultReducer<long long> {}, use_dev, workspace);
|
||||
MultReducer<long long> {}, use_dev);
|
||||
long long expected = 21936950640377856;
|
||||
CAPTURE(use_dev);
|
||||
REQUIRE(res == expected);
|
||||
@@ -86,7 +84,6 @@ TEST_CASE("Reduce Mult", "[Reduction],[GPU]")
|
||||
|
||||
TEST_CASE("Reduce BAnd", "[Reduction],[GPU]")
|
||||
{
|
||||
Array<unsigned> workspace;
|
||||
Array<unsigned> a(10);
|
||||
SECTION("{ Bit unset }")
|
||||
{
|
||||
@@ -108,7 +105,7 @@ TEST_CASE("Reduce BAnd", "[Reduction],[GPU]")
|
||||
mfem::reduce(
|
||||
a.Size(), res,
|
||||
[=] MFEM_HOST_DEVICE(int i, unsigned &r) { r &= dptr[i]; },
|
||||
BAndReducer<unsigned> {}, use_dev, workspace);
|
||||
BAndReducer<unsigned> {}, use_dev);
|
||||
CAPTURE(use_dev);
|
||||
REQUIRE(res == ((~1u) & ~(1u << unset_bit)));
|
||||
REQUIRE((res & (1u << unset_bit)) == 0);
|
||||
@@ -132,7 +129,7 @@ TEST_CASE("Reduce BAnd", "[Reduction],[GPU]")
|
||||
mfem::reduce(
|
||||
a.Size(), res,
|
||||
[=] MFEM_HOST_DEVICE(int i, unsigned &r) { r &= dptr[i]; },
|
||||
BAndReducer<unsigned> {}, use_dev, workspace);
|
||||
BAndReducer<unsigned> {}, use_dev);
|
||||
CAPTURE(use_dev);
|
||||
REQUIRE(res == (1u << set_bit));
|
||||
}
|
||||
@@ -141,7 +138,6 @@ TEST_CASE("Reduce BAnd", "[Reduction],[GPU]")
|
||||
|
||||
TEST_CASE("Reduce BOr", "[Reduction],[GPU]")
|
||||
{
|
||||
Array<unsigned> workspace;
|
||||
Array<unsigned> a(0x210);
|
||||
a.HostReadWrite();
|
||||
for (int i = 0; i < a.Size(); ++i)
|
||||
@@ -157,7 +153,7 @@ TEST_CASE("Reduce BOr", "[Reduction],[GPU]")
|
||||
mfem::reduce(
|
||||
a.Size(), res,
|
||||
[=] MFEM_HOST_DEVICE(int i, unsigned &r) { r |= dptr[i]; },
|
||||
BOrReducer<unsigned> {}, use_dev, workspace);
|
||||
BOrReducer<unsigned> {}, use_dev);
|
||||
CAPTURE(use_dev);
|
||||
REQUIRE(res == 0x3ffu);
|
||||
}
|
||||
@@ -165,7 +161,6 @@ TEST_CASE("Reduce BOr", "[Reduction],[GPU]")
|
||||
|
||||
TEST_CASE("Reduce Min", "[Reduction],[GPU]")
|
||||
{
|
||||
Array<int> workspace;
|
||||
Array<int> a(1000);
|
||||
auto hptr = a.HostReadWrite();
|
||||
for (int i = 0; i < a.Size(); ++i)
|
||||
@@ -190,7 +185,7 @@ TEST_CASE("Reduce Min", "[Reduction],[GPU]")
|
||||
r = dptr[i];
|
||||
}
|
||||
},
|
||||
MinReducer<int> {}, use_dev, workspace);
|
||||
MinReducer<int> {}, use_dev);
|
||||
CAPTURE(use_dev);
|
||||
REQUIRE(res == -10);
|
||||
}
|
||||
@@ -198,7 +193,6 @@ TEST_CASE("Reduce Min", "[Reduction],[GPU]")
|
||||
|
||||
TEST_CASE("Reduce Max", "[Reduction],[GPU]")
|
||||
{
|
||||
Array<int> workspace;
|
||||
Array<int> a(1000);
|
||||
auto hptr = a.HostReadWrite();
|
||||
for (int i = 0; i < a.Size(); ++i)
|
||||
@@ -223,7 +217,7 @@ TEST_CASE("Reduce Max", "[Reduction],[GPU]")
|
||||
r = dptr[i];
|
||||
}
|
||||
},
|
||||
MaxReducer<int> {}, use_dev, workspace);
|
||||
MaxReducer<int> {}, use_dev);
|
||||
CAPTURE(use_dev);
|
||||
REQUIRE(res == 999 - 10);
|
||||
}
|
||||
@@ -231,7 +225,6 @@ TEST_CASE("Reduce Max", "[Reduction],[GPU]")
|
||||
|
||||
TEST_CASE("Reduce MinMax", "[Reduction],[GPU]")
|
||||
{
|
||||
Array<DevicePair<int, int>> workspace;
|
||||
Array<int> a(1000);
|
||||
auto hptr = a.HostReadWrite();
|
||||
for (int i = 0; i < a.Size(); ++i)
|
||||
@@ -262,7 +255,7 @@ TEST_CASE("Reduce MinMax", "[Reduction],[GPU]")
|
||||
r.second = dptr[i];
|
||||
}
|
||||
},
|
||||
MinMaxReducer<int> {}, use_dev, workspace);
|
||||
MinMaxReducer<int> {}, use_dev);
|
||||
CAPTURE(use_dev);
|
||||
REQUIRE(res.first == -10);
|
||||
REQUIRE(res.second == a.Size() - 11);
|
||||
@@ -271,7 +264,6 @@ TEST_CASE("Reduce MinMax", "[Reduction],[GPU]")
|
||||
|
||||
TEST_CASE("Reduce ArgMin", "[Reduction],[GPU]")
|
||||
{
|
||||
Array<DevicePair<double, int>> workspace;
|
||||
Array<double> a(1000);
|
||||
auto hptr = a.HostReadWrite();
|
||||
for (int i = 0; i < a.Size(); ++i)
|
||||
@@ -297,7 +289,7 @@ TEST_CASE("Reduce ArgMin", "[Reduction],[GPU]")
|
||||
r.second = i;
|
||||
}
|
||||
},
|
||||
ArgMinReducer<double, int> {}, use_dev, workspace);
|
||||
ArgMinReducer<double, int> {}, use_dev);
|
||||
CAPTURE(use_dev);
|
||||
REQUIRE(res.first == -10);
|
||||
REQUIRE(res.second >= 0);
|
||||
@@ -308,7 +300,6 @@ TEST_CASE("Reduce ArgMin", "[Reduction],[GPU]")
|
||||
|
||||
TEST_CASE("Reduce ArgMax", "[Reduction],[GPU]")
|
||||
{
|
||||
Array<DevicePair<double, int>> workspace;
|
||||
Array<double> a(1000);
|
||||
|
||||
auto hptr = a.HostReadWrite();
|
||||
@@ -337,7 +328,7 @@ TEST_CASE("Reduce ArgMax", "[Reduction],[GPU]")
|
||||
r.second = i;
|
||||
}
|
||||
},
|
||||
ArgMaxReducer<double, int> {}, use_dev, workspace);
|
||||
ArgMaxReducer<double, int> {}, use_dev);
|
||||
CAPTURE(use_dev);
|
||||
REQUIRE(res.first == a.Size() - 11);
|
||||
REQUIRE(res.second >= 0);
|
||||
@@ -348,7 +339,6 @@ TEST_CASE("Reduce ArgMax", "[Reduction],[GPU]")
|
||||
|
||||
TEST_CASE("Reduce ArgMinMax", "[Reduction],[GPU]")
|
||||
{
|
||||
Array<MinMaxLocScalar<double, int>> workspace;
|
||||
Array<double> a(1000);
|
||||
auto hptr = a.HostReadWrite();
|
||||
for (int i = 0; i < a.Size(); ++i)
|
||||
@@ -383,7 +373,7 @@ TEST_CASE("Reduce ArgMinMax", "[Reduction],[GPU]")
|
||||
r.max_loc = i;
|
||||
}
|
||||
},
|
||||
ArgMinMaxReducer<double, int> {}, use_dev, workspace);
|
||||
ArgMinMaxReducer<double, int> {}, use_dev);
|
||||
CAPTURE(use_dev);
|
||||
REQUIRE(res.min_val == -10);
|
||||
REQUIRE(res.min_loc >= 0);
|
||||
|
||||
@@ -373,6 +373,7 @@ TEST_CASE("Batched Linear Algebra",
|
||||
const int n_rhs = 2;
|
||||
|
||||
DenseTensor A_batch(n, n, n_mat);
|
||||
DenseTensor A_inv_batch(n, n, n_mat);
|
||||
Vector x_batch(n * n_rhs * n_mat), y_batch(n * n_rhs * n_mat);
|
||||
std::vector<DenseMatrix> As;
|
||||
std::vector<DenseMatrix> xs, ys;
|
||||
@@ -404,6 +405,7 @@ TEST_CASE("Batched Linear Algebra",
|
||||
ys.back() = 0.0;
|
||||
AddMult_a(1.5, As.back(), xs.back(), ys.back());
|
||||
A_batch(i) = As.back();
|
||||
A_inv_batch(i) = As.back();
|
||||
}
|
||||
|
||||
// Test batched matrix-vector products
|
||||
@@ -463,6 +465,33 @@ TEST_CASE("Batched Linear Algebra",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test batched matrix inverse
|
||||
BatchedLinAlg::Get(backend).Invert(A_inv_batch);
|
||||
A_inv_batch.HostReadWrite();
|
||||
Vector output_col(n);
|
||||
Vector col;
|
||||
for (int i = 0; i < n_mat; ++i)
|
||||
{
|
||||
DenseMatrix Ai_inv(A_inv_batch(i));
|
||||
for (int j = 0; j < n; ++j)
|
||||
{
|
||||
output_col = 0.0;
|
||||
As[i].GetColumnReference(j, col);
|
||||
Ai_inv.Mult(col, output_col);
|
||||
for (int k = 0; k < n; ++k)
|
||||
{
|
||||
if (j == k)
|
||||
{
|
||||
REQUIRE(output_col(k) == MFEM_Approx(1.0));
|
||||
}
|
||||
else
|
||||
{
|
||||
REQUIRE(output_col(k) == MFEM_Approx(0.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("DenseTensor copy", "[DenseMatrix][DenseTensor]")
|
||||
|
||||
@@ -240,4 +240,54 @@ TEST_CASE("SparseMatrix printing", "[SparseMatrix]")
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("SparseMatrix cuSPARSE Bug", "[SparseMatrix][GPU]")
|
||||
{
|
||||
// This test case ensures that we have a functioning workaround for the bug
|
||||
// CUSPARSE-1897. In versions of cuSPARSE before 12.8, the internal buffer
|
||||
// used for cusparseSpMV must be the same when it is called with the same
|
||||
// matrix.
|
||||
//
|
||||
// By default, MFEM uses one buffer, that is shared by all sparse matrices.
|
||||
// In the code below, a buffer is created for A, then modified for B, then
|
||||
// used again for A. Without the workaround, this fails with cuSPARSE version
|
||||
// earlier than 12.8 (confirmed to fail with 12.4).
|
||||
|
||||
const int n = 100;
|
||||
SparseMatrix A(n, n);
|
||||
Vector d(n);
|
||||
d.Randomize(1);
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
A.Set(i, i, d[i]);
|
||||
}
|
||||
A.Finalize();
|
||||
|
||||
Vector x(n);
|
||||
x = 1.0;
|
||||
|
||||
Vector y(n);
|
||||
A.Mult(x, y);
|
||||
|
||||
{
|
||||
SparseMatrix B(20, 20);
|
||||
for (int i = 0; i < 20; ++i)
|
||||
{
|
||||
for (int j = 0; j < 20; ++j)
|
||||
{
|
||||
B.Set(i, j, 1.0);
|
||||
}
|
||||
}
|
||||
B.Finalize();
|
||||
Vector u(20);
|
||||
u = 1.0;
|
||||
Vector v(20);
|
||||
B.Mult(u, v);
|
||||
}
|
||||
|
||||
A.Mult(x, y);
|
||||
|
||||
y -= d;
|
||||
REQUIRE(y.Normlinf() == MFEM_Approx(0.0));
|
||||
}
|
||||
|
||||
} // namespace mfem
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
// 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.
|
||||
|
||||
#ifndef MFEM_TEST_SAME_MATRICES_HPP
|
||||
#define MFEM_TEST_SAME_MATRICES_HPP
|
||||
|
||||
#include "mfem.hpp"
|
||||
#include "unit_tests.hpp"
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
|
||||
#ifndef MFEM_USE_MPI
|
||||
#define HYPRE_BigInt int
|
||||
#endif // MFEM_USE_MPI
|
||||
|
||||
inline void TestSameMatrices(
|
||||
SparseMatrix &A1, const SparseMatrix &A2,
|
||||
HYPRE_BigInt *cmap1=nullptr,
|
||||
std::unordered_map<HYPRE_BigInt,int> *cmap2inv=nullptr)
|
||||
{
|
||||
REQUIRE(A1.Height() == A2.Height());
|
||||
int n = A1.Height();
|
||||
|
||||
const int *I1 = A1.HostReadI();
|
||||
const int *J1 = A1.HostReadJ();
|
||||
const real_t *V1 = A1.HostReadData();
|
||||
|
||||
A2.HostReadI();
|
||||
A2.HostReadJ();
|
||||
A2.HostReadData();
|
||||
|
||||
real_t error = 0.0;
|
||||
|
||||
for (int i=0; i<n; ++i)
|
||||
{
|
||||
for (int jj=I1[i]; jj<I1[i+1]; ++jj)
|
||||
{
|
||||
int j = J1[jj];
|
||||
if (cmap1)
|
||||
{
|
||||
if (cmap2inv->count(cmap1[j]) > 0)
|
||||
{
|
||||
j = (*cmap2inv)[cmap1[j]];
|
||||
}
|
||||
else
|
||||
{
|
||||
error = std::max(error, std::fabs(V1[jj]));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
error = std::max(error, std::fabs(V1[jj] - A2(i,j)));
|
||||
}
|
||||
}
|
||||
|
||||
REQUIRE(error == MFEM_Approx(0.0, 1e-10));
|
||||
}
|
||||
|
||||
#ifdef MFEM_USE_MPI
|
||||
|
||||
inline void TestSameMatrices(HypreParMatrix &A1, const HypreParMatrix &A2)
|
||||
{
|
||||
HYPRE_BigInt *cmap1, *cmap2;
|
||||
SparseMatrix diag1, offd1, diag2, offd2;
|
||||
|
||||
A1.GetDiag(diag1);
|
||||
A2.GetDiag(diag2);
|
||||
A1.GetOffd(offd1, cmap1);
|
||||
A2.GetOffd(offd2, cmap2);
|
||||
|
||||
TestSameMatrices(diag1, diag2);
|
||||
|
||||
if (cmap1)
|
||||
{
|
||||
std::unordered_map<HYPRE_BigInt,int> cmap2inv;
|
||||
for (int i=0; i<offd2.Width(); ++i) { cmap2inv[cmap2[i]] = i; }
|
||||
TestSameMatrices(offd1, offd2, cmap1, &cmap2inv);
|
||||
}
|
||||
else
|
||||
{
|
||||
TestSameMatrices(offd1, offd2);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // MFEM_USE_MPI
|
||||
|
||||
} // namespace mfem
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user