Compare commits

...
Author SHA1 Message Date
Julian Andrej 368e36a060 remove print 2025-06-24 08:43:05 -07:00
Julian Andrej e8ea798077 Merge branch 'master' into dfem-functional 2025-06-24 08:37:57 -07:00
Julian Andrej 69498877af add missing pieces for functional computation
add derivative call

commit to check compatibility on CI

dual type for non enzyme ci

change dependent variable

add dual transformation

back to non generic lambda for nvcc

check constexpr mechanism with CI

testing out lambda-less approach

cleanup

Revert "testing out lambda-less approach"

This reverts commit 6808fe0693c2589abf3c0c929e0a10b9a5050d4d.

# Conflicts:
#	tests/unit/dfem/test_functional.cpp

simpler approach
2025-06-24 08:30:03 -07:00
Veselin Dobrev e9782cf437 Merge pull request #4667 from mfem/hughcars/array-back-inserter
Allow std::back_inserter with Array<T>
2025-06-23 10:20:26 -07:00
John Camier 9a9f64d8bb Merge branch 'master' into hughcars/array-back-inserter 2025-06-22 20:26:26 -07:00
Veselin Dobrev 0eb3001197 Merge pull request #4902 from mfem/doftrans-fix
Fix bug relating to DofTransformation
2025-06-20 23:32:07 -07:00
Will Pazner 71f25f5e16 Fix bug relating to DofTransformation
In FiniteElementSpace::GetElementDofs and related functions, set the
DofTransformation object to identity if no transformation is needed.
2025-06-18 16:53:32 -07:00
Veselin Dobrev 43c50376eb Merge pull request #4857 from mfem/hughcars/ho-make-simplicial-dev
Add higher order support to `MakeSimplicial`
2025-06-17 21:13:07 -07:00
Veselin Dobrev e4a066edf4 Merge branch 'master' into hughcars/ho-make-simplicial-dev 2025-06-17 20:17:51 -07:00
Veselin Dobrev 886a4c89e4 Merge pull request #4850 from mfem/bugfix/l2pos_p0_pyramid
Adding p=0 special case to L2Pos_PyramidElement
2025-06-17 20:13:26 -07:00
Veselin Dobrev b36ed0e9e9 Merge pull request #4897 from mfem/deterministic-output
Deterministic miniapp output
2025-06-17 20:12:03 -07:00
Tzanio Kolev e72ed572c4 Merge pull request #4877 from mfem/doftrans-three-arg
Avoid using internal DofTransformation object
2025-06-17 08:10:04 -07:00
Tzanio Kolev bfa68b0b18 Merge pull request #4889 from mfem/device-doxygen-fix
Fix the doxygen formatting of Device::Configure
2025-06-17 08:08:53 -07:00
Will Pazner 1181253c61 Disable LOR device sample runs
Results can be non-determinstic on device because of atomics in the matrix
assembly algorithm.
2025-06-16 12:12:52 -07:00
Dylan Copeland d379628b54 Deterministic error output for phpref miniapp. 2025-06-16 11:10:33 -07:00
John Camier ec45731607 Merge branch 'master' into hughcars/array-back-inserter 2025-06-16 08:40:13 -07:00
John Camier 19b57a28b7 Merge branch 'master' into device-doxygen-fix 2025-06-16 08:33:30 -07:00
Tzanio Kolev 46627b8655 Merge pull request #4804 from mfem/dfem-phase1-dev
dFEM Phase 1
2025-06-13 12:58:02 -07:00
Tzanio Kolev 0fe2887b1c Merge branch 'master' into dfem-phase1-dev 2025-06-13 10:50:41 -07:00
Tzanio Kolev 03ad5fbae5 Merge pull request #4878 from mfem/minor-clean-pfindpts
Minor cleanups to `pfindpts.cpp` [minor-clean-pfindpts]
2025-06-13 10:50:30 -07:00
John Camier a018c66a9e Merge branch 'master' into device-doxygen-fix 2025-06-13 08:47:30 -07:00
Veselin Dobrev 14be9236bc Small formatting edits in dfem-minimal-surface.cpp 2025-06-12 17:39:41 -07:00
Veselin Dobrev 45d38fd1ba Add fem/dfem to doxygen and fix the doxygen warnings there 2025-06-12 16:06:18 -07:00
Tzanio Kolev 734ae021f0 Merge branch 'master' into dfem-phase1-dev 2025-06-12 07:47:38 -07:00
Julian Andrej 2c23f5b0bd fixing UB 2025-06-11 20:57:54 -07:00
Tzanio Kolev a17fb5b587 Merge pull request #4891 from mfem/lor-fixes
LOR bug fixes
2025-06-11 17:32:45 -07:00
Julian Andrej bc404c0f85 more compiler warnings 2025-06-11 14:02:23 -07:00
Will Pazner 8e59709ab8 Explicitly compare against nullptr 2025-06-11 09:30:31 -07:00
Will Pazner f4d0cef65c Compute PtAP when needed in batched LOR assembly
In serial on AMR meshes, the P matrix will not be identity.
2025-06-11 09:24:06 -07:00
Will Pazner 1bef281b45 Fix bug in LORBase::AddIntegratorsAndMarkers
Was previously potentially dereferencing null pointer
2025-06-11 09:21:38 -07:00
Veselin Dobrev 95a86d2bd8 Merge pull request #4869 from mfem/fix-sample-runs
Fix sample runs
2025-06-10 22:13:30 -07:00
Veselin Dobrev ccfee841cb Fix the doxygen formatting of Device::Configure 2025-06-10 17:43:38 -07:00
Julian Andrej 7f9cba0daa address warnings 2025-06-10 13:25:05 -07:00
Veselin Dobrev 1ae0d6bef6 Adjust the sample runs in miniapps/electromagnetics/maxwell.cpp 2025-06-10 12:36:06 -07:00
Joseph SignorelliandTzanio Kolev 6031b7b2a3 Update miniapps/gslib/pfindpts.cpp
Co-authored-by: Tzanio Kolev <tzanio@llnl.gov>
2025-06-09 09:32:22 -07:00
Joseph SignorelliandTzanio Kolev 3efda06ca8 Update miniapps/gslib/pfindpts.cpp
Co-authored-by: Tzanio Kolev <tzanio@llnl.gov>
2025-06-09 09:32:08 -07:00
Julian Andrej 55c7f5821e Merge branch 'dfem-phase1-dev' of github.com:mfem/mfem into dfem-phase1-dev 2025-06-09 07:58:41 -07:00
Julian Andrej 4ffa0434f7 Revert "forall dispatch fix"
This reverts commit 3f097ae869.
2025-06-09 07:58:20 -07:00
Veselin Dobrev 2a30ba157f Fix a bug introduced in the last commit 2025-06-08 22:46:07 -07:00
Veselin Dobrev 1d1a33b365 Attempt to fix warnings from the intel-classic compiler 2025-06-08 22:17:28 -07:00
Veselin Dobrev 6c8349dbbe Add to GNU make the hypre dependency to cusolver when built with CUDA 2025-06-08 17:55:33 -10:00
Julian Andrej e4b760a30b exclude hip runs for autotest 2025-06-08 13:36:20 -07:00
Julian Andrej 18ca7ab2c9 collect norm for v in fdjacobian 2025-06-07 13:43:23 -07:00
Julian Andrej 3f097ae869 forall dispatch fix 2025-06-07 11:59:33 -07:00
Julian Andrej f912b347a2 Merge branch 'dfem-phase1-dev' of github.com:mfem/mfem into dfem-phase1-dev 2025-06-07 09:03:51 -07:00
Julian Andrej 90a6dc7c99 collect norm in parallel for fd 2025-06-07 09:03:39 -07:00
Julian AndrejandTom Stitt 17455e7b08 Update INSTALL
Co-authored-by: Tom Stitt <stitt4@llnl.gov>
2025-06-06 11:03:43 -07:00
Joseph Signorelli 81f209953e Clarifications + formatting fixes to -h 2025-06-06 10:27:21 -07:00
Joseph Signorelli b3d4579618 Fix searched total points 2025-06-06 10:27:11 -07:00
Andrew Ho 47855ff8cf fix base case for not compiling with cuda 2025-06-05 12:31:19 -07:00
Andrew Ho 115a11bf79 Merge remote-tracking branch 'base/dfem-phase1-dev' into dfem-phase1-dev 2025-06-05 11:34:52 -07:00
Andrew Ho b81cce6f45 new hypre links to cusolver by default now 2025-06-05 11:33:20 -07:00
Andrew Ho 550ca6b702 rename beta to beta_
something is conflicting with beta, possibly std::beta imported into the global namespace?
2025-06-05 11:32:35 -07:00
Andrew Ho 5be6c032db fix for old nvcc 2025-06-05 11:32:06 -07:00
Julian Andrej 1eafad0af9 make it easier for old compilers 2025-06-05 09:58:40 -07:00
Julian Andrej e61ac08b8d make ancient compilers happy 2025-06-05 09:09:00 -07:00
Julian Andrej 37b22e4e48 specifically add array header 2025-06-05 08:15:10 -07:00
Julian Andrej 767f4bde18 remove fallback return 2025-06-04 20:36:32 -07:00
Julian Andrej e09d02ff2f missing const 2025-06-04 17:54:55 -07:00
Julian Andrej da9a797b46 make objects all std::function 2025-06-04 17:36:33 -07:00
Julian Andrej 92d8e7f46b try different return types 2025-06-04 17:10:40 -07:00
Julian Andrej a147b61e98 maybe fix tuple 2025-06-04 16:00:27 -07:00
Julian Andrej 14323a6da5 really fix tuple 2025-06-04 15:14:26 -07:00
Julian Andrej b30809a663 tuple fixes 2025-06-04 15:10:03 -07:00
Julian Andrej e68e96cd00 Merge branch 'dfem-phase1-dev' of github.com:mfem/mfem into dfem-phase1-dev 2025-06-04 14:43:47 -07:00
Julian Andrej d9e7737354 address nvcc warnings 2025-06-04 14:43:37 -07:00
Hugh Carson 01479824f5 Address MR feedback
- Remove static_cast to void, rely on future usage of [[nodiscard]] for dangerous discards
- Restore explicit to prevent implicit conversion of initializer_list
2025-06-04 16:16:02 -04:00
Joseph SignorelliandKetan Mittal 389f1ef5ce Update miniapps/gslib/pfindpts.cpp
Co-authored-by: Ketan Mittal <ketan.mittal@gmail.com>
2025-06-04 12:30:01 -07:00
Andrew Ho 346f5b28c9 Added --expt-relaxed-constexpr for cmake 2025-06-03 10:07:12 -07:00
Andrew Ho 3867b49d3b qfunction lambda needs MFEM_HOST_DEVICE for nvcc
C++17 lambdas are supposed to be automatically constexpr if possible,
but nvcc doesn't appear to do this
2025-06-03 10:00:43 -07:00
Joseph Signorelli e4a32db1dc style 2025-06-03 06:50:52 -07:00
Joseph Signorelli 84cb244bc2 Remove unused code + clarify points are random, not equidistant 2025-06-03 06:44:13 -07:00
Will Pazner 11b878612b Use three-argument version of GetElementDofs…
…and related functions that return a pointer to an internal DofTransformation
object that may be invalidated unexpectedly.
2025-06-02 21:04:31 -07:00
Julian Andrej dc45953e0d change return statement position 2025-06-02 08:17:39 -07:00
camierjs b4ebf12ff8 Adjust dFEM diffusion tests default q1d 2025-06-01 19:39:57 -07:00
camierjs f9cc9c6c15 Clarify unused input when not in 'solutions' 2025-06-01 19:27:34 -07:00
Julian Andrej 28094ba952 Revert "higher order diffusion integration rule"
This reverts commit 72c7405b08.
2025-06-01 15:22:01 -07:00
Julian Andrej 72c7405b08 higher order diffusion integration rule 2025-06-01 14:56:05 -07:00
Julian Andrej 2ce3063b2c fix for internal tests 2025-06-01 14:29:03 -07:00
Julian Andrej dbd90b50cb fix divergence test 2025-06-01 14:25:25 -07:00
camierjs b640cbe587 [dFEM] Add VectorDiffusion tests, Wip VectorDivergence 2025-06-01 10:58:13 -07:00
Tzanio Kolev 5a4ba178bb minor 2025-05-30 17:44:38 -07:00
Tzanio Kolev f4c44a86bc more fixes 2025-05-30 17:18:56 -07:00
Tzanio Kolev a2e780d18d minor 2025-05-30 17:11:15 -07:00
Tzanio Kolev 2855a6daa3 Updated INSTALL for Enzyme on RHEL9. Renamed dfem-minimal-surface.cpp 2025-05-30 16:55:09 -07:00
Julian Andrej 490026763d change miniapp target name 2025-05-30 16:29:02 -07:00
Julian Andrej 087a87bf40 build fixes 2025-05-30 14:57:00 -07:00
Andrew Ho 43c5a04aae update minimal_surface example run samples documentation 2025-05-30 14:54:01 -07:00
Julian Andrej cda6ee0580 Update INSTALL 2025-05-30 14:13:34 -07:00
Tzanio Kolev a3639fe70c Merge branch 'dfem-phase1-dev' of github.com:mfem/mfem into dfem-phase1-dev 2025-05-30 13:35:28 -07:00
Tzanio Kolev 78c5f51309 Moved dFEM's minimal surface to a miniapp 2025-05-30 13:34:41 -07:00
Julian Andrej 3880a48af5 make the dfem example unambiguous 2025-05-30 12:34:33 -07:00
Tzanio Kolev 9f7f84defb Updated dFEM description in CHANGELOG 2025-05-30 10:24:40 -07:00
ce5fd63d0a dfem phase 1 changes
Co-authored-by: camierjs <camierjs@gmail.com>
Co-authored-by: Veselin Dobrev <dobrev@llnl.gov>
2025-05-29 13:02:23 -07:00
Hugh Carson e3c45310b0 Merge branch 'master' into hughcars/array-back-inserter 2025-05-29 13:24:09 -04:00
Hugh Carson fdec992469 Merge branch 'master' into hughcars/ho-make-simplicial-dev 2025-05-27 17:40:43 -04:00
Vladimir Z Tomov 3e23785a13 Removed -vis from shifted/diffusion sample runs. 2025-05-23 11:50:28 -07:00
Will Pazner 52a0337f5e Adjust LOR miniapp sample runs 2025-05-23 10:56:24 -07:00
dylan-copeland f021091e9e Update guidelines on miniapp sample run testing. 2025-05-22 10:43:48 -07:00
dylan-copeland 21b592778a Remove device sample runs for nurbs_ex24. 2025-05-22 10:27:12 -07:00
dylan-copeland c3233944ad Exclude device sample runs for nurbs_ex24. 2025-05-22 09:52:36 -07:00
dylan-copeland dd097f2cf5 Clean up output file. 2025-05-21 18:06:53 -07:00
dylan-copeland a8eba4053f Speed up sample runs for nurbs_ex24. 2025-05-21 18:05:30 -07:00
dylan-copeland 2df1b13811 Fixing some miniapp sample runs. 2025-05-21 17:04:00 -07:00
Hugh Carson 1d28cfe699 Address Feedback
- Use IsDGSpace()
- Rewrap comments at 80 characters
2025-05-21 11:18:32 -04:00
Hugh Carson 5957053d85 Update CHANGELOG 2025-05-20 12:11:31 -04:00
Hugh Carson 4ba8a2e148 Move Nodes construction into separate helper method, to allow building communication pattern first in ParMesh
- Silence unused variable warning in non-debug builds
2025-05-20 12:11:31 -04:00
Hugh Carson e494dc8255 Fix warning about missing (void*) 2025-05-20 12:11:31 -04:00
Hugh Carson 9fec24c86c Simplify linear algebra and add support for byNODES ordering of the Nodes vector 2025-05-20 12:11:31 -04:00
Hugh Carson 664d0a543a Add no-op tests, and fall through 2025-05-20 12:11:31 -04:00
Hugh Carson 20c3c30c87 Clean up printing, and generate quadratic and cubic tests 2025-05-20 12:11:31 -04:00
Hugh Carson 083c4845db Fix bug where origin of reference coordinates was being assumed as (0,0,0) 2025-05-20 12:11:31 -04:00
Hugh Carson a23c634e37 Map point_matrix back into ordering of Nodes() 2025-05-20 12:11:31 -04:00
Hugh Carson 556f6d72f5 Exploit that the map between reference spaces is always linear to assmble PointMatrix 2025-05-20 12:11:31 -04:00
Tzanio Kolev 3ebb7a7b65 Merge branch 'master' into bugfix/l2pos_p0_pyramid 2025-05-19 16:51:07 -07:00
Veselin Dobrev 02136f8f5b In the unit test test_fe_pos.cpp, add a check for positivity of the
basis functions.
2025-05-19 12:25:21 -07:00
Stowell, Mark L. 37adfbc405 Fixing memory leaks in positive basis unit tests 2025-05-07 14:14:29 -07:00
Stowell, Mark L. 0a142864e9 Merge branch 'bugfix/l2pos_p0_pyramid' of github.com:mfem/mfem into bugfix/l2pos_p0_pyramid 2025-05-07 12:54:12 -07:00
Stowell, Mark L. f975c36e49 Adding new unit test to CMakeList.txt 2025-05-07 12:53:55 -07:00
Mark L. Stowell 471bc1f10d Merge branch 'master' into bugfix/l2pos_p0_pyramid 2025-05-07 11:46:56 -07:00
Stowell, Mark L. 8f52418e5c Adding basic unit tests for positive basis functions 2025-05-07 11:46:14 -07:00
Stowell, Mark L. 41a095e972 Fixing typo in unit test comment 2025-05-07 11:45:09 -07:00
Stowell, Mark L. effc07cdbf Fixing basis function computations for order=0 2025-05-07 11:44:45 -07:00
Stowell, Mark L. 46ac9d937f Adding p=0 special case to L2Pos_PyramidElement 2025-05-05 18:02:28 -07:00
Hugh Carson 2af3577482 Merge branch 'master' into hughcars/array-back-inserter 2025-04-16 11:09:26 -04:00
Hugh Carson fb5e610ca5 Add support and test for back_inserter along with some more stl-style interactions 2025-02-24 10:40:07 -05:00
104 changed files with 9093 additions and 887 deletions
+42 -6
View File
@@ -58,6 +58,7 @@ jobs:
build-system: [make, cmake]
hypre-target: [int32]
precision: [fp64]
enzyme: [false]
exclude:
- os: ubuntu-latest
build-system: cmake
@@ -80,15 +81,17 @@ jobs:
codecov: YES
- os: ubuntu-latest
target: dbg
config-opts: 'CPPFLAGS+=-Og'
config-opts: "CPPFLAGS+=-Og"
- os: macos-latest
codecov: NO
- os: windows-latest
codecov: NO
# config-opts: '-G "Ninja Multi-Config"'
- os: windows-latest
target: opt
mpi: par
config-opts: '-DBUILD_SHARED_LIBS=ON'
config-opts: "-DBUILD_SHARED_LIBS=ON"
# config-opts: '-DBUILD_SHARED_LIBS=ON -G "Ninja Multi-Config"'
- os: ubuntu-latest
target: opt
codecov: NO
@@ -96,7 +99,7 @@ jobs:
build-system: cmake
hypre-target: int32
precision: fp64
config-opts: '-DCMAKE_INSTALL_PREFIX=../cmake-install'
config-opts: "-DCMAKE_INSTALL_PREFIX=../cmake-install"
# This option can be set to pass additional configuration options to
# the MFEM configuration command.
# config-opts: '-DCMAKE_VERBOSE_MAKEFILE=ON'
@@ -121,7 +124,17 @@ jobs:
build-system: make
hypre-target: int32
precision: fp32
name: ${{ matrix.os }}-${{ matrix.build-system }}-${{ matrix.target }}-${{ matrix.mpi }}-${{ matrix.hypre-target }}-${{ matrix.precision }}
- os: macos-latest
target: opt
codecov: NO
mpi: par
build-system: make
hypre-target: int32
precision: fp64
enzyme: true
config-opts: MFEM_USE_ENZYME=YES ENZYME_DIR=$(brew --prefix enzyme)
name: ${{ matrix.os }}-${{ matrix.build-system }}-${{ matrix.target }}-${{ matrix.mpi }}-${{ matrix.hypre-target }}-${{ matrix.precision }}${{ matrix.enzyme && '-enzyme' || '' }}
runs-on: ${{ matrix.os }}
@@ -131,8 +144,8 @@ jobs:
if: matrix.os == 'ubuntu-latest'
uses: easimon/maximize-build-space@v8
with:
overprovision-lvm: 'true'
remove-android: 'true'
overprovision-lvm: "true"
remove-android: "true"
# Checkout MFEM in "mfem" subdirectory. Final path:
# /home/runner/work/mfem/mfem/mfem
@@ -144,6 +157,17 @@ jobs:
# Fetch the complete history for codecov to access commits ID
fetch-depth: 0
- name: Windows environment - PowerShell [debug]
if: matrix.os == 'windows-latest'
run: |
ls env: | fl
- name: Windows environment - Bash [debug]
if: matrix.os == 'windows-latest'
run: |
env
shell: bash
- name: Xcode version setup (MacOS)
if: matrix.os == 'macos-latest'
run: |
@@ -258,6 +282,18 @@ jobs:
run: |
vcpkg install metis-mfem --triplet=x64-windows-static --overlay-ports=${{ env.MFEM_TOP_DIR }}/config/vcpkg/ports
# It's usually fine to build the above TPLs with a different compiler.
#
- name: install Enzyme (macOS w/ Enzyme)
if: matrix.enzyme && matrix.os == 'macos-latest'
run: |
export HOMEBREW_NO_INSTALL_CLEANUP=1
brew update
brew install llvm@19 enzyme
echo "LLVM_PREFIX=$(brew --prefix llvm@19)" >> $GITHUB_ENV
echo "OMPI_CC=$(brew --prefix llvm@19)/bin/clang" >> $GITHUB_ENV
echo "OMPI_CXX=$(brew --prefix llvm@19)/bin/clang++" >> $GITHUB_ENV
# MFEM build and test
- name: build
uses: mfem/github-actions/build-mfem@v2.5
+1 -1
View File
@@ -49,7 +49,7 @@ jobs:
library-only: false
config-options:
CXX="clang++-18"
CXXFLAGS="-g -O1 -std=c++11
CXXFLAGS="-g -O1 -std=c++17
-fsanitize=address
-fno-omit-frame-pointer
-fsanitize-address-use-after-scope"
+3
View File
@@ -201,6 +201,9 @@ examples/superlu/sol.*
miniapps/adjoint/cvsRoberts_ASAi_dns
miniapps/adjoint/adjoint_advection_diffusion
miniapps/dfem/dfem-minimal-surface
miniapps/dfem/dfem-minimal-surface-output
miniapps/electromagnetics/volta
miniapps/electromagnetics/tesla
miniapps/electromagnetics/maxwell
+29 -2
View File
@@ -10,15 +10,42 @@
Version 4.8.1 (development)
===========================
- Added support for variational resampling of H1 vector fields to ParMoonolith
integration.
Starting with this version, MFEM requires a C++17 compiler.
Discretization improvements
---------------------------
- Introduced dFEM: a new MFEM capability for Automatic Differentiation (AD) of
nonlinear finite element operators, based on Enzyme or dual numbers AD at
quadrature points. These features are part of the new mfem::future namespace
and some of the API can change in the future. See the new dFEM minimal surface
miniapp in the miniapps/dfem/ directory for illustration of dFEM's use.
- Using Enzyme for AD in MFEM is tested with clang v19 and requires clang/LLVM
built with plugin support. See INSTALL for more details.
- In the ParMoonolith integration, added support for variational resampling of
H1 vector fields.
Meshing improvements
--------------------
- Added support for higher order meshes in Mesh::MakeSimplicial and
ParMesh::MakeSimplicial.
GPU computing
-------------
- The function Vector::SetSubVector(const Array<int> &, const real_t) now
executes on device if either the vector or the array have the device flag
set. This is most often used for setting constant essential boundary
conditions. A new function Vector::SetSubVectorHost has been added in cases
where host execution is always needed (e.g. when the DOFs array is small).
API changes:
-----------
- mfem::internal::tensor and mfem::internal::dual have been moved to
mfem::future::tensor and mfem::future::dual.
Version 4.8, released on Apr 9, 2025
====================================
+27 -6
View File
@@ -18,8 +18,8 @@ message(STATUS "CMake version: ${CMAKE_VERSION}")
set(USER_CONFIG "${CMAKE_CURRENT_SOURCE_DIR}/config/user.cmake" CACHE PATH
"Path to optional user configuration file.")
# Require C++11 and disable compiler-specific extensions
set(CMAKE_CXX_STANDARD 11 CACHE STRING "C++ standard to use.")
# Require C++17 and disable compiler-specific extensions
set(CMAKE_CXX_STANDARD 17 CACHE STRING "C++ standard to use.")
set(CMAKE_CXX_STANDARD_REQUIRED ON CACHE BOOL
"Force the use of the chosen C++ standard.")
set(CMAKE_CXX_EXTENSIONS OFF CACHE BOOL "Enable C++ standard extensions.")
@@ -133,7 +133,6 @@ if (MFEM_USE_CUDA)
if (NOT CMAKE_CUDA_HOST_COMPILER)
set(CMAKE_CUDA_HOST_COMPILER ${CMAKE_CXX_COMPILER})
endif()
set(CUDA_FLAGS "--expt-extended-lambda")
if (CMAKE_VERSION VERSION_LESS 3.18.0)
set(CUDA_FLAGS "-arch=${CUDA_ARCH} ${CUDA_FLAGS}")
elseif (NOT CMAKE_CUDA_ARCHITECTURES)
@@ -148,6 +147,20 @@ if (MFEM_USE_CUDA)
endif()
message(STATUS "Using CUDA architecture: ${CUDA_ARCH}")
enable_language(CUDA)
if (CMAKE_VERSION VERSION_LESS 3.18.0)
# backup try to detect if this is clang or nvcc
if(CMAKE_CUDA_COMPILER MATCHES "nvcc$")
# nvcc
set(MFEM_CUDA_COMPILER_IS_NVCC ON)
set(CUDA_FLAGS "${CUDA_FLAGS} --expt-extended-lambda --expt-relaxed-constexpr")
endif()
else()
if (CMAKE_CUDA_COMPILER_ID STREQUAL "NVIDIA")
# nvcc
set(MFEM_CUDA_COMPILER_IS_NVCC ON)
set(CUDA_FLAGS "${CUDA_FLAGS} --expt-extended-lambda --expt-relaxed-constexpr")
endif()
endif()
set(CMAKE_CUDA_STANDARD ${CMAKE_CXX_STANDARD} CACHE STRING
"CUDA standard to use.")
set(CMAKE_CUDA_STANDARD_REQUIRED ON CACHE BOOL
@@ -256,7 +269,11 @@ if (MFEM_USE_OPENMP OR MFEM_USE_LEGACY_OPENMP)
if (OPENMP_FOUND)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
if (MFEM_USE_CUDA)
set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -Xcompiler=${OpenMP_CXX_FLAGS}")
if(MFEM_CUDA_COMPILER_IS_NVCC)
set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -Xcompiler=${OpenMP_CXX_FLAGS}")
else()
set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} ${OpenMP_CXX_FLAGS}")
endif()
endif()
endif()
endif()
@@ -532,9 +549,10 @@ if (MFEM_USE_TRIBOL)
endif()
endif()
# Enzyme
if (MFEM_USE_ENZYME)
find_package(ENZYME REQUIRED)
find_package(Enzyme REQUIRED HINTS ${ENZYME_DIR})
message(STATUS "Enzyme found in ${ENZYME_DIR}.")
set(ENZYME_INCLUDE_DIRS ${ENZYME_DIR}/include)
endif()
# MFEM_TIMER_TYPE
@@ -686,6 +704,9 @@ if (MFEM_USE_MPI)
target_link_libraries(mfem PUBLIC ${MPI_CXX_LINK_FLAGS})
endif()
endif()
if (MFEM_USE_ENZYME)
target_link_libraries(mfem PUBLIC ClangEnzymeFlags)
endif()
set_target_properties(mfem PROPERTIES VERSION "${mfem_VERSION}")
set_target_properties(mfem PROPERTIES SOVERSION "${mfem_VERSION}")
+5 -1
View File
@@ -120,6 +120,7 @@ The MFEM source code has the following structure:
| └── superlu
├── fem
│ ├── ceed
│ ├── dfem
│ ├── eltrans
│ ├── fe
│ ├── gslib
@@ -138,6 +139,7 @@ The MFEM source code has the following structure:
│ ├── adjoint
│ ├── autodiff
│ ├── common
│ ├── dfem
│ ├── dpg
│ ├── electromagnetics
│ ├── gslib
@@ -549,6 +551,8 @@ Before a PR can be merged, it should satisfy the following:
- [ ] Add a short description of the example in the "Extensive Examples" section of `features.md`.
- [ ] New miniapps:
- [ ] All sample runs at the top of the miniapp source file work.
- [ ] Add to internal testing repo, if sample runs should be included in nightly tests [internally](#tests-at-llnl).
- [ ] Exclude long sample runs from automated testing, with `* ` (one space) before the command.
- [ ] Update top-level `makefile` and `makefile` in corresponding miniapp directory.
- [ ] Add the miniapp binary and any files generated by it to the top-level `.gitignore` file.
- [ ] Update CMake build system:
@@ -743,7 +747,7 @@ and debug build is performed with a simple run of `ex1` to verify the executable
- We mirror the `master` and `next` branches internally (to `gh-master` and
`gh-next`) and run longer nightly tests via cron. On the weekends, a more
extensive test is run which extracts and executes all the different sample
runs from each example.
runs from each example and most miniapps.
- We also mirror PRs on the LLNL GitLab instance. PR mirroring can only be
triggered by _LLNL developers_, but test status is publicly available. Only
+79 -8
View File
@@ -263,7 +263,7 @@ See the configuration file config/defaults.mk for the default settings.
Compilers:
CXX - C++ compiler, serial build
MPICXX - MPI C++ compiler, parallel build
CUDA_CXX - The CUDA compiler, 'nvcc'
CUDA_CXX - The CUDA compiler, 'nvcc' or 'clang++'
Compiler options:
OPTIM_FLAGS - Options for optimized build
@@ -608,11 +608,12 @@ MFEM_USE_TRIBOL = YES/NO
MFEM_USE_ENZYME = YES/NO
Enables automatic differentiation support through the LLVM plugin Enzyme.
This requires the compiler to be set to clang (>=14.0.0). We also advise to
use the link time optimization (LTO) plugin, to enable functions that you
define over multiple files (compilation units) and want to be differentiated
automatically, to work. This requires to also use LLVM/LLD for linking.
Recommended options are in config/defaults.mk.
This requires the compiler to be set to clang (>=14.0.0). We also advise the
use of the link time optimization (LTO) plugin, so functions defined over
multiple files (compilation units) can be differentiated automatically. This
requires to also use LLVM/LLD for linking. The recommended options are in
config/defaults.mk. For more detailed instructions, see the section "Specific
options for Enzyme" below.
MFEM_BUILD_TAG = (any value)
An optional tag to characterize the build. Exported to config/config.mk.
@@ -835,7 +836,7 @@ The specific libraries and their options are:
- CUDA (optional), used when MFEM_USE_CUDA = YES.
URL: https://developer.nvidia.com/cuda-toolkit
Options: CUDA_CXX, CUDA_ARCH, CUDA_OPT, CUDA_LIB.
Options: CUDA_CXX, CUDA_ARCH, CUDA_OPT, CUDA_LIB, CUDA_DIR (when CUDA_CXX=clang++).
Versions: CUDA >= 10.1.168.
- HIP (optional), used when MFEM_USE_HIP = YES.
@@ -911,7 +912,7 @@ The specific libraries and their options are:
- Enzyme, used when MFEM_USE_ENZYME = YES. Requires LLVM/Clang >= 14.0.0.
URL: https://github.com/EnzymeAD/Enzyme
Options: ENZYME_DIR, ENZYME_OPT, ENZYME_LIB.
Versions: Enzyme >= v0.0.33.
Versions: Enzyme >= v0.0.176.
Building with CMake
@@ -1190,3 +1191,73 @@ the older HIP C++ library build/linkage. To ensure proper build and linkage
check that `CMAKE_CXX_COMPILER` and `CMAKE_HIP_COMPILER` are set to the same
compiler. This is especially important when using an MPI compiler (for example
crayCC) where some linker flags may get dropped if these two are not identical.
Specific options for Enzyme
===========================
To work properly, MFEM and Enzyme need to use the same LLVM/Clang configuration.
For example, on macOS this can be done by using Homebrew: first install Enzyme,
which in turn installs LLVM as a dependency (as of May 2025, this is LLVM 19):
brew install enzyme
In order to ensure the correct compiler choice for the MFEM makefile build, set
CXX = $(shell brew --prefix llvm@19)/bin/clang++
in the user.mk file (adapted from config/defaults.mk, see the section "Building
with GNU make" above). With MPI, it is convenient to set
MPICXX = OMPI_CXX=$(CXX) mpicxx
for OpenMPI and
MPICXX = MPICH_CXX=$(CXX) mpicxx
for MPICH.
Additionally, the Enzyme directory needs to be set in user.mk as follows:
ENZYME_DIR = $(shell brew --prefix enzyme)
Specifically, a full build on a Mac can be tested by adding the following
user.mk file in the config/ directory
MFEM_USE_ENZYME = YES
ENZYME_DIR = $(shell brew --prefix enzyme)
LLVM_DIR = $(shell brew --prefix llvm@19)
CXX = $(LLVM_DIR)/bin/clang++
MFEM_USE_MPI = YES
MPICXX = OMPI_CXX=$(CXX) mpicxx
and running
make config
make -j
cd miniapps/dfem
make
./dfem-minimal-surface
On Linux systems, for example Ubuntu 24.04, use the package manager to install
the Enzyme dependencies
sudo apt install libclang-dev libzstd-dev llvm-dev clang
and then clone and build Enzyme
cd $HOME
git clone https://github.com/EnzymeAD/Enzyme.git
cd Enzyme/enzyme && mkdir build && cd build
CC=clang CXX=clang++ cmake .. -DLLVM_DIR=/usr/lib/llvm-18/lib/cmake -DCMAKE_INSTALL_PREFIX=$HOME/Enzyme/enzyme/build
make -j
make install
From here, one can proceed in the same way using the following user.mk settings
MFEM_USE_ENZYME = YES
ENZYME_DIR = $(HOME)/Enzyme/enzyme/build
CXX = clang++
MFEM_USE_MPI = YES
MPICXX = OMPI_CXX=$(CXX) mpicxx
On other Linux systems the LLVM packages may have different names, for example
on RHEL9, one needs to "sudo yum install llvm-devel libzstd clang-devel".
-27
View File
@@ -1,27 +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.
message(STATUS "Looking for ENZYME ...")
message(STATUS " in ENZYME_DIR = ${ENZYME_DIR}")
# Make sure the directory and version combination works. Do nothing otherwise.
if(EXISTS "${ENZYME_DIR}/ClangEnzyme-${ENZYME_VERSION}.so")
message(STATUS "Found ENZYME: ${ENZYME_DIR}/ClangEnzyme-${ENZYME_VERSION}.so")
# Set ENZYME_FOUND
set(ENZYME_FOUND TRUE CACHE BOOL "ENZYME was found." FORCE)
# Set CXX flags to accommodate the Enzyme Clang plugin
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Xclang -load -Xclang ${ENZYME_DIR}/ClangEnzyme-${ENZYME_VERSION}.so -mllvm -enzyme-loose-types=1")
set(MFEM_USE_ENZYME YES)
else()
endif()
+2 -1
View File
@@ -86,8 +86,9 @@ if (HYPRE_FOUND AND HYPRE_USING_CUDA)
mfem_culib_set_libraries(CUSPARSE cusparse)
mfem_culib_set_libraries(CURAND curand)
mfem_culib_set_libraries(CUBLAS cublas)
mfem_culib_set_libraries(CUSOLVER cusolver)
list(APPEND HYPRE_LIBRARIES ${CUSPARSE_LIBRARIES} ${CURAND_LIBRARIES}
${CUBLAS_LIBRARIES})
${CUBLAS_LIBRARIES} ${CUSOLVER_LIBRARIES})
set(HYPRE_LIBRARIES ${HYPRE_LIBRARIES} CACHE STRING
"HYPRE libraries + dependencies." FORCE)
message(STATUS "Updated HYPRE_LIBRARIES: ${HYPRE_LIBRARIES}")
@@ -125,7 +125,9 @@ macro(add_mfem_miniapp MFEM_EXE_NAME)
if (MFEM_USE_CUDA)
set_source_files_properties(${MAIN_LIST} ${EXTRA_SOURCES_LIST}
PROPERTIES LANGUAGE CUDA)
list(TRANSFORM EXTRA_OPTIONS_LIST PREPEND "-Xcompiler=")
if (MFEM_CUDA_COMPILER_IS_NVCC)
list(TRANSFORM EXTRA_OPTIONS_LIST PREPEND "-Xcompiler=")
endif()
endif()
# Actually add the executable
+1 -1
View File
@@ -98,7 +98,7 @@ MFEM_MPIEXEC_NP = @MFEM_MPIEXEC_NP@
MFEM_MPI_NP = @MFEM_MPI_NP@
# The NVCC compiler cannot link with -x=cu
MFEM_LINK_FLAGS := $(filter-out -x=cu -xhip, $(MFEM_FLAGS))
MFEM_LINK_FLAGS := $(filter-out -x=cu -xcuda -xhip, $(MFEM_FLAGS))
# Optional extra configuration
@MFEM_CONFIG_EXTRA@
+2
View File
@@ -268,6 +268,8 @@ set(TRIBOL_DIR "${MFEM_DIR}/../tribol" CACHE PATH "Path to Tribol")
set(Tribol_REQUIRED_PACKAGES "Axom/core/mint/slam/slic" CACHE STRING
"Additional packages required by Tribol")
set(ENZYME_DIR "${MFEM_DIR}/../enzyme" CACHE PATH "Path to Enzyme")
set(BLAS_INCLUDE_DIRS "" CACHE STRING "Path to BLAS headers.")
set(BLAS_LIBRARIES "" CACHE STRING "The BLAS library.")
set(LAPACK_INCLUDE_DIRS "" CACHE STRING "Path to LAPACK headers.")
+34 -29
View File
@@ -24,7 +24,7 @@ EGREP_BIN = $(shell command -v egrep 2> /dev/null)
CXX = g++
MPICXX = mpicxx
BASE_FLAGS = -std=c++11
BASE_FLAGS = -std=c++17
OPTIM_FLAGS = -O3 $(BASE_FLAGS)
DEBUG_FLAGS = -g $(XCOMPILER)-Wall $(BASE_FLAGS)
@@ -43,12 +43,23 @@ SHARED = NO
# CUDA configuration options
#
# If you set MFEM_USE_ENZYME=YES, CUDA_CXX has to be configured to use cuda with
# clang as its host compiler.
# If you set MFEM_USE_ENZYME=YES, must use CUDA_CXX=clang++
CUDA_CXX = nvcc
CUDA_ARCH = sm_60
CUDA_FLAGS = -x=cu --expt-extended-lambda -arch=$(CUDA_ARCH)
# Prefixes for passing flags to the host compiler and linker when using CUDA_CXX
# Base CUDA install directory, only needed if building with clang+cuda:
# The default setting is:
# 1. If CUDA_HOME is defined and non-empty, use that.
# 2. If nvcc is in the path, use the directory two levels up from that.
# 3. Use /usr/local/cuda
CUDA_DIR = $(or $(CUDA_HOME),$(patsubst %/,%,$(dir \
$(patsubst %/,%,$(dir $(shell command -v nvcc))))),/usr/local/cuda)
# flags for clang+cuda
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)
# Prefixes for passing flags to the host compiler and linker when using
# CUDA_CXX=nvcc
CUDA_XCOMPILER = -Xcompiler=
CUDA_XLINKER = -Xlinker=
@@ -227,7 +238,7 @@ HYPRE_OPT = -I$(HYPRE_DIR)/include
HYPRE_LIB = -L$(HYPRE_DIR)/lib -lHYPRE
ifeq (YES,$(MFEM_USE_CUDA))
# This is only necessary when hypre is built with cuda:
HYPRE_LIB += -lcusparse -lcurand -lcublas
HYPRE_LIB += -lcusolver -lcusparse -lcurand -lcublas
endif
ifeq (YES,$(MFEM_USE_HIP))
# This is only necessary when hypre is built with hip:
@@ -242,7 +253,7 @@ ifeq ($(MFEM_USE_SUPERLU)$(MFEM_USE_STRUMPACK)$(MFEM_USE_MUMPS),NONONO)
METIS_OPT =
METIS_LIB = -L$(METIS_DIR) -lmetis
else
METIS_DIR = @MFEM_DIR@/../metis-5.0
METIS_DIR = @MFEM_DIR@/../metis-5.1.0
METIS_OPT = -I$(METIS_DIR)/include
METIS_LIB = -L$(METIS_DIR)/lib -lmetis
endif
@@ -511,6 +522,9 @@ GSLIB_LIB = -L$(GSLIB_DIR)/lib -lgs
# CUDA library configuration
CUDA_OPT =
CUDA_LIB = -lcusparse -lcublas
CLANG_CUDA_LIB = -L$(CUDA_DIR)/lib64 -L$(CUDA_DIR)/lib \
$(XLINKER)-rpath,$(CUDA_DIR)/lib64,-rpath,$(CUDA_DIR)/lib \
-lcudart -ldl -lrt -pthread
# HIP library configuration
HIP_OPT =
@@ -610,29 +624,20 @@ TRIBOL_LIB = -L$(TRIBOL_DIR)/lib -ltribol -lredecomp -L$(AXOM_DIR)/lib -laxom_mi
-laxom_slam -laxom_slic -laxom_core
# Enzyme configuration
# If you want to enable automatic differentiation at compile time, use the
# options below, adapted to your configuration. To be more flexible, we
# recommend using the Enzyme plugin during link time optimization. One option is
# to add your options to the global compiler/linker flags like
#
# BASE_FLAGS += -flto
# CXX_XLINKER += -fuse-ld=lld -Wl,--lto-legacy-pass-manager\
# -Wl,-mllvm=-load=$(ENZYME_DIR)/LLDEnzyme-$(ENZYME_VERSION).so -Wl,
#
ENZYME_DIR ?= @MFEM_DIR@/../enzyme
ENZYME_VERSION ?= 14
ENZYME_OPT = -fno-experimental-new-pass-manager -Xclang -load -Xclang $(ENZYME_DIR)/ClangEnzyme-$(ENZYME_VERSION).so
ENZYME_LIB = ""
# Google Benchmark, SUNDIALS >= 6.4.0, STRUMPACK, RAJA, UMPIRE, and Tribol require C++14:
ifneq ($(filter YES,$(MFEM_USE_BENCHMARK) $(MFEM_USE_SUNDIALS) $(MFEM_USE_STRUMPACK) $(MFEM_USE_RAJA) $(MFEM_USE_UMPIRE) $(MFEM_USE_TRIBOL)),)
BASE_FLAGS = -std=c++14
endif
# Ginkgo requires C++17:
ifeq ($(MFEM_USE_GINKGO),YES)
BASE_FLAGS = -std=c++17
ENZYME_DIR = @MFEM_DIR@/../enzyme
ENZYME_PLUGIN = $(abspath $(wildcard $(subst \
@MFEM_DIR@,$(MFEM_DIR),$(ENZYME_DIR))/lib/ClangEnzyme-*.$(SO_EXT)))
ifeq ($(MAKECMDGOALS)-$(MFEM_USE_ENZYME),config-YES)
ifeq ($(ENZYME_PLUGIN),)
$(error Unable to find the Enzyme pluging! Please set ENZYME_DIR)
endif
ifneq ($(words $(ENZYME_PLUGIN)),1)
$(error Multiple versions of the Enzyme pluging found! \
Please set ENZYME_PLUGIN directly)
endif
endif
ENZYME_OPT = -fplugin=$(ENZYME_PLUGIN)
ENZYME_LIB =
# If YES, enable some informational messages
VERBOSE = NO
+2
View File
@@ -951,6 +951,7 @@ INPUT = @MFEM_SOURCE_DIR@/doc/CodeDocumentation.dox \
@MFEM_SOURCE_DIR@/fem/ceed/integrators/nlconvection \
@MFEM_SOURCE_DIR@/fem/ceed/interface \
@MFEM_SOURCE_DIR@/fem/ceed/solvers \
@MFEM_SOURCE_DIR@/fem/dfem \
@MFEM_SOURCE_DIR@/fem/eltrans \
@MFEM_SOURCE_DIR@/fem/fe \
@MFEM_SOURCE_DIR@/fem/gslib \
@@ -972,6 +973,7 @@ INPUT = @MFEM_SOURCE_DIR@/doc/CodeDocumentation.dox \
@MFEM_SOURCE_DIR@/miniapps/adjoint \
@MFEM_SOURCE_DIR@/miniapps/autodiff \
@MFEM_SOURCE_DIR@/miniapps/common \
@MFEM_SOURCE_DIR@/miniapps/dfem \
@MFEM_SOURCE_DIR@/miniapps/dpg \
@MFEM_SOURCE_DIR@/miniapps/dpg/util \
@MFEM_SOURCE_DIR@/miniapps/electromagnetics \
+9
View File
@@ -64,6 +64,7 @@ set(SRCS
datacollection.cpp
dgmassinv.cpp
doftrans.cpp
dfem/doperator.cpp
eltrans.cpp
batchitrans.cpp
estimators.cpp
@@ -180,6 +181,14 @@ set(HDRS
dgmassinv.hpp
dgmassinv_kernels.hpp
doftrans.hpp
dfem/doperator.hpp
dfem/fieldoperator.hpp
dfem/integrate.hpp
dfem/parameterspace.hpp
dfem/qfunction_apply.hpp
dfem/qfunction_transform.hpp
dfem/tuple.hpp
dfem/util.hpp
eltrans.hpp
estimators.hpp
fe.hpp
+19 -33
View File
@@ -466,7 +466,6 @@ void BilinearForm::Assemble(int skip_zeros)
}
ElementTransformation *eltrans;
DofTransformation * doftrans;
Mesh *mesh = fes -> GetMesh();
DenseMatrix elmat, *elmat_p;
@@ -503,13 +502,14 @@ void BilinearForm::Assemble(int skip_zeros)
}
}
DofTransformation doftrans;
// Element-wise integration
for (int i = 0; i < fes -> GetNE(); i++)
{
// Set both doftrans (potentially needed to assemble the element
// matrix) and vdofs, which is also needed when the element matrices
// are pre-assembled.
doftrans = fes->GetElementVDofs(i, vdofs);
fes->GetElementVDofs(i, vdofs, doftrans);
if (element_matrices)
{
elmat_p = &(*element_matrices)(i);
@@ -547,10 +547,7 @@ void BilinearForm::Assemble(int skip_zeros)
{
elmat_p = &elmat;
}
if (doftrans)
{
doftrans->TransformDual(elmat);
}
doftrans.TransformDual(elmat);
elmat_p = &elmat;
}
if (static_cond)
@@ -628,13 +625,14 @@ void BilinearForm::Assemble(int skip_zeros)
}
}
DofTransformation doftrans;
for (int i = 0; i < fes -> GetNBE(); i++)
{
const int bdr_attr = mesh->GetBdrAttribute(i);
if (bdr_attr_marker[bdr_attr-1] == 0) { continue; }
const FiniteElement &be = *fes->GetBE(i);
doftrans = fes -> GetBdrElementVDofs (i, vdofs);
fes -> GetBdrElementVDofs (i, vdofs, doftrans);
eltrans = fes -> GetBdrElementTransformation (i);
int k = 0;
for (; k < boundary_integs.Size(); k++)
@@ -654,10 +652,7 @@ void BilinearForm::Assemble(int skip_zeros)
boundary_integs[k]->AssembleElementMatrix(be, *eltrans, elemmat);
elmat += elemmat;
}
if (doftrans)
{
doftrans->TransformDual(elmat);
}
doftrans.TransformDual(elmat);
elmat_p = &elmat;
if (!static_cond)
{
@@ -1530,8 +1525,6 @@ void MixedBilinearForm::Assemble(int skip_zeros)
}
ElementTransformation *eltrans;
DofTransformation * dom_dof_trans;
DofTransformation * ran_dof_trans;
DenseMatrix elmat;
Mesh *mesh = test_fes -> GetMesh();
@@ -1554,11 +1547,12 @@ void MixedBilinearForm::Assemble(int skip_zeros)
}
}
DofTransformation dom_dof_trans, ran_dof_trans;
for (int i = 0; i < test_fes -> GetNE(); i++)
{
const int elem_attr = mesh->GetAttribute(i);
dom_dof_trans = trial_fes -> GetElementVDofs (i, trial_vdofs);
ran_dof_trans = test_fes -> GetElementVDofs (i, test_vdofs);
trial_fes->GetElementVDofs (i, trial_vdofs, dom_dof_trans);
test_fes->GetElementVDofs (i, test_vdofs, ran_dof_trans);
eltrans = test_fes -> GetElementTransformation (i);
elmat.SetSize(test_vdofs.Size(), trial_vdofs.Size());
@@ -1574,10 +1568,7 @@ void MixedBilinearForm::Assemble(int skip_zeros)
elmat += elemmat;
}
}
if (ran_dof_trans || dom_dof_trans)
{
TransformDual(ran_dof_trans, dom_dof_trans, elmat);
}
TransformDual(ran_dof_trans, dom_dof_trans, elmat);
mat -> AddSubMatrix (test_vdofs, trial_vdofs, elmat, skip_zeros);
}
}
@@ -1605,13 +1596,14 @@ void MixedBilinearForm::Assemble(int skip_zeros)
}
}
DofTransformation dom_dof_trans, ran_dof_trans;
for (int i = 0; i < test_fes -> GetNBE(); i++)
{
const int bdr_attr = mesh->GetBdrAttribute(i);
if (bdr_attr_marker[bdr_attr-1] == 0) { continue; }
dom_dof_trans = trial_fes -> GetBdrElementVDofs (i, trial_vdofs);
ran_dof_trans = test_fes -> GetBdrElementVDofs (i, test_vdofs);
trial_fes->GetBdrElementVDofs (i, trial_vdofs, dom_dof_trans);
test_fes->GetBdrElementVDofs (i, test_vdofs, ran_dof_trans);
eltrans = test_fes -> GetBdrElementTransformation (i);
elmat.SetSize(test_vdofs.Size(), trial_vdofs.Size());
@@ -1626,10 +1618,7 @@ void MixedBilinearForm::Assemble(int skip_zeros)
*eltrans, elemmat);
elmat += elemmat;
}
if (ran_dof_trans || dom_dof_trans)
{
TransformDual(ran_dof_trans, dom_dof_trans, elmat);
}
TransformDual(ran_dof_trans, dom_dof_trans, elmat);
mat -> AddSubMatrix (test_vdofs, trial_vdofs, elmat, skip_zeros);
}
}
@@ -2407,8 +2396,6 @@ void DiscreteLinearOperator::Assemble(int skip_zeros)
}
ElementTransformation *eltrans;
DofTransformation * dom_dof_trans;
DofTransformation * ran_dof_trans;
DenseMatrix elmat;
Mesh *mesh = test_fes->GetMesh();
@@ -2431,11 +2418,13 @@ void DiscreteLinearOperator::Assemble(int skip_zeros)
}
}
DofTransformation dom_dof_trans;
DofTransformation ran_dof_trans;
for (int i = 0; i < test_fes->GetNE(); i++)
{
const int elem_attr = mesh->GetAttribute(i);
dom_dof_trans = trial_fes->GetElementVDofs(i, trial_vdofs);
ran_dof_trans = test_fes->GetElementVDofs(i, test_vdofs);
trial_fes->GetElementVDofs(i, trial_vdofs, dom_dof_trans);
test_fes->GetElementVDofs(i, test_vdofs, ran_dof_trans);
eltrans = test_fes->GetElementTransformation(i);
elmat.SetSize(test_vdofs.Size(), trial_vdofs.Size());
@@ -2451,10 +2440,7 @@ void DiscreteLinearOperator::Assemble(int skip_zeros)
elmat += elemmat;
}
}
if (ran_dof_trans || dom_dof_trans)
{
TransformPrimal(ran_dof_trans, dom_dof_trans, elemmat);
}
TransformPrimal(ran_dof_trans, dom_dof_trans, elemmat);
mat->SetSubMatrix(test_vdofs, trial_vdofs, elemmat, skip_zeros);
}
}
+54
View File
@@ -0,0 +1,54 @@
// 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 "doperator.hpp"
#ifdef MFEM_USE_MPI
using namespace mfem;
using namespace mfem::future;
void DifferentiableOperator::SetParameters(std::vector<Vector *> p) const
{
MFEM_ASSERT(parameters.size() == p.size(),
"number of parameters doesn't match descriptors");
for (size_t i = 0; i < parameters.size(); i++)
{
p[i]->Read();
parameters_l[i] = *p[i];
}
}
DifferentiableOperator::DifferentiableOperator(
const std::vector<FieldDescriptor> &solutions,
const std::vector<FieldDescriptor> &parameters,
const ParMesh &mesh) :
mesh(mesh),
solutions(solutions),
parameters(parameters)
{
fields.resize(solutions.size() + parameters.size());
fields_e.resize(fields.size());
solutions_l.resize(solutions.size());
parameters_l.resize(parameters.size());
for (size_t i = 0; i < solutions.size(); i++)
{
fields[i] = solutions[i];
}
for (size_t i = 0; i < parameters.size(); i++)
{
fields[i + solutions.size()] = parameters[i];
}
}
#endif // MFEM_USE_MPI
+819
View File
@@ -0,0 +1,819 @@
// 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 <type_traits>
#include <utility>
#include "../../config/config.hpp"
#ifdef MFEM_USE_MPI
#include "../fespace.hpp"
#include "util.hpp"
#include "interpolate.hpp"
#include "integrate.hpp"
#include "qfunction_apply.hpp"
namespace mfem::future
{
/// @brief Type alias for a function that computes the action of an operator
using action_t =
std::function<void(std::vector<Vector> &, const std::vector<Vector> &, Vector &)>;
/// @brief Type alias for a function that computes the action of a derivative
using derivative_action_t =
std::function<void(std::vector<Vector> &, const Vector &, Vector &)>;
/// @brief Type alias for a function that assembles the sparse matrix of a
/// derivative operator
using assemble_derivative_hypreparmatrix_callback_t =
std::function<void(std::vector<Vector> &, HypreParMatrix &)>;
/// @brief Type alias for a function that applies the appropriate restriction to
/// the solution and parameters
using restriction_callback_t =
std::function<void(std::vector<Vector> &,
const std::vector<Vector> &,
std::vector<Vector> &)>;
/// Class representing the derivative (Jacobian) operator of a
/// DifferentiableOperator.
///
/// This class implements a derivative operator that computes directional
/// derivatives for a given set of solution and parameter fields. It supports
/// both forward and transpose operations, as well as assembly into sparse
/// matrices.
///
/// @note The derivative operator uses only forward mode differentiation in Mult
/// and MultTranspose. It does not support reverse mode differentiation. The
/// MultTranspose operation is achieved by using the transpose of the derivative
/// actions on each quadrature point.
///
/// @see DifferentiableOperator
class DerivativeOperator : public Operator
{
public:
/// Constructor for the DerivativeOperator class.
///
/// This is usually not called directly from a user. A DifferentiableOperator
/// calls this constructor when using
/// DifferentiableOperator::GetDerivative().
DerivativeOperator(
const int &height,
const int &width,
const std::vector<derivative_action_t> &derivative_actions,
const FieldDescriptor &direction,
const int &daction_l_size,
const std::vector<derivative_action_t> &derivative_actions_transpose,
const FieldDescriptor &transpose_direction,
const int &daction_transpose_l_size,
const std::vector<Vector *> &solutions_l,
const std::vector<Vector *> &parameters_l,
const restriction_callback_t &restriction_callback,
const std::function<void(Vector &, Vector &)> &prolongation_transpose,
const std::vector<assemble_derivative_hypreparmatrix_callback_t>
&assemble_derivative_hypreparmatrix_callbacks) :
Operator(height, width),
derivative_actions(derivative_actions),
direction(direction),
daction_l(daction_l_size),
daction_l_size(daction_l_size),
derivative_actions_transpose(derivative_actions_transpose),
transpose_direction(transpose_direction),
prolongation_transpose(prolongation_transpose),
assemble_derivative_hypreparmatrix_callbacks(
assemble_derivative_hypreparmatrix_callbacks)
{
std::vector<Vector> s_l(solutions_l.size());
for (size_t i = 0; i < s_l.size(); i++)
{
s_l[i] = *solutions_l[i];
}
std::vector<Vector> p_l(parameters_l.size());
for (size_t i = 0; i < p_l.size(); i++)
{
p_l[i] = *parameters_l[i];
}
fields_e.resize(solutions_l.size() + parameters_l.size());
restriction_callback(s_l, p_l, fields_e);
}
/// @brief Compute the action of the derivative operator on a given vector.
///
/// @param direction_t The direction vector in which to compute the
/// derivative. This has to be a T-dof vector.
/// @param result_t Result vector of the action of the derivative on
/// direction_t on T-dofs.
void Mult(const Vector &direction_t, Vector &result_t) const override
{
daction_l.SetSize(daction_l_size);
daction_l = 0.0;
prolongation(direction, direction_t, direction_l);
for (const auto &f : derivative_actions)
{
f(fields_e, direction_l, daction_l);
}
prolongation_transpose(daction_l, result_t);
};
/// @brief Compute the transpose of the derivative operator on a given
/// vector.
///
/// This function computes the transpose of the derivative operator on a
/// given vector by transposing the quadrature point local forward derivative
/// action. It does not use reverse mode automatic differentiation.
///
/// @param direction_t The direction vector in which to compute the
/// derivative. This has to be a T-dof vector.
/// @param result_t Result vector of the transpose action of the derivative on
/// direction_t on T-dofs.
void MultTranspose(const Vector &direction_t, Vector &result_t) const override
{
MFEM_ASSERT(!derivative_actions_transpose.empty(),
"derivative can't be used to be multiplied in transpose mode");
daction_l.SetSize(width);
daction_l = 0.0;
prolongation(transpose_direction, direction_t, direction_l);
for (const auto &f : derivative_actions_transpose)
{
f(fields_e, direction_l, daction_l);
}
prolongation_transpose(daction_l, result_t);
};
/// @brief Assemble the derivative operator into a HypreParMatrix.
///
/// @param A The HypreParMatrix to assemble the derivative operator into. Can
/// be an uninitialized object.
void Assemble(HypreParMatrix &A)
{
MFEM_ASSERT(!assemble_derivative_hypreparmatrix_callbacks.empty(),
"derivative can't be assembled into a matrix");
for (const auto &f : assemble_derivative_hypreparmatrix_callbacks)
{
f(fields_e, A);
}
}
private:
/// Derivative action callbacks. Depending on the requested derivatives in
/// DifferentiableOperator the callbacks represent certain combinations of
/// actions of derivatives of the forward operator.
std::vector<derivative_action_t> derivative_actions;
FieldDescriptor direction;
mutable Vector daction_l;
const int daction_l_size;
/// Transpose Derivative action callbacks. Depending on the requested
/// derivatives in DifferentiableOperator the callbacks represent certain
/// combinations of actions of derivatives of the forward operator.
std::vector<derivative_action_t> derivative_actions_transpose;
FieldDescriptor transpose_direction;
mutable std::vector<Vector> fields_e;
mutable Vector direction_l;
std::function<void(Vector &, Vector &)> prolongation_transpose;
/// Callbacks that assemble derivatives into a HypreParMatrix.
std::vector<assemble_derivative_hypreparmatrix_callback_t>
assemble_derivative_hypreparmatrix_callbacks;
};
/// Class representing a differentiable operator which acts on solution and
/// parameter fields to compute residuals.
///
/// This class provides functionality to define differentiable operators by
/// composing functions that compute values at quadrature points. It supports
/// automatic differentiation to compute derivatives with respect to solutions
/// (Jacobians) and parameter fields (general derivative operators).
///
/// The operator is constructed with solution fields that it will act on and
/// parameter fields that define coefficients. Quadrature functions are added by
/// e.g. using AddDomainIntegrator() which specify how the operator evaluates f
/// those functionas and parameters at quadrature points.
///
/// Derivatives can be computed by obtaining a DerivativeOperator using
/// GetDerivative().
///
/// @see DerivativeOperator
class DifferentiableOperator : public Operator
{
public:
/// Constructor for the DifferentiableOperator class.
///
/// @param solutions The solution fields that the operator will act on.
/// @param parameters The parameter fields that define coefficients.
/// @param mesh The mesh on which the operator is defined.
DifferentiableOperator(
const std::vector<FieldDescriptor> &solutions,
const std::vector<FieldDescriptor> &parameters,
const ParMesh &mesh);
/// @brief Compute the action of the operator on a given vector.
///
/// @param solutions_t The solution vector in which to compute the action.
/// This has to be a T-dof vector.
/// @param result_t Result vector of the action of the operator on
/// solutions_t. The result is a T-dof vector.
void Mult(const Vector &solutions_t, Vector &result_t) const override
{
MFEM_ASSERT(!action_callbacks.empty(), "no integrators have been set");
prolongation(solutions, solutions_t, solutions_l);
for (auto &action : action_callbacks)
{
action(solutions_l, parameters_l, residual_l);
}
prolongation_transpose(residual_l, result_t);
}
/// @brief Add a domain integrator to the operator.
///
/// @param qfunc The quadrature function to be added.
/// @param inputs Tuple of FieldOperators for the inputs of the quadrature
/// function.
/// @param outputs Tuple of FieldOperators for the outputs of the quadrature
/// function.
/// @param integration_rule IntegrationRule to use with this integrator.
/// @param domain_attributes Domain attributes marker array indicating over
/// which attributes this integrator will integrate over.
/// @param derivative_ids Derivatives to be made available for this
/// integrator.
template <
typename qfunc_t,
typename input_t,
typename output_t,
typename derivative_ids_t = decltype(std::make_index_sequence<0> {})>
void AddDomainIntegrator(
qfunc_t &qfunc,
input_t inputs,
output_t outputs,
const IntegrationRule &integration_rule,
const Array<int> &domain_attributes,
derivative_ids_t derivative_ids = std::make_index_sequence<0> {});
/// @brief Set the parameters for the operator.
///
/// This has to be called before using Mult() or MultTranspose().
///
/// @param p The parameters to be set. This should be a vector of pointers to
/// the parameter vectors. The vectors have to be L-vectors (e.g.
/// GridFunctions).
void SetParameters(std::vector<Vector *> p) const;
/// @brief Disable the use of tensor product structure.
///
/// This function disables the use of tensor product structure for the
/// operator. Usually, DifferentiableOperator creates callbacks based on
/// heuristics that achieve good performance for each element type. Some
/// functionality is not implemented for these performant algorithms but only
/// for generic assembly. Therefore the user can decide to use fallback
/// methods.
void DisableTensorProductStructure(bool disable = true)
{
use_tensor_product_structure = !disable;
}
/// @brief Get the derivative operator for a given derivative ID.
///
/// This function returns a shared pointer to a DerivativeOperator that
/// computes the derivative of the operator with respect to the given
/// derivative ID. The derivative ID is used to identify the specific
/// derivative action to be performed.
///
/// @param derivative_id The ID of the derivative to be computed.
/// @param sol_l The solution vectors to be used for the derivative
/// computation. This should be a vector of pointers to the solution
/// vectors. The vectors have to be L-vectors (e.g. GridFunctions).
/// @param par_l The parameter vectors to be used for the derivative
/// computation. This should be a vector of pointers to the parameter
/// vectors. The vectors have to be L-vectors (e.g. GridFunctions).
/// @return A shared pointer to the DerivativeOperator.
std::shared_ptr<DerivativeOperator> GetDerivative(
size_t derivative_id, std::vector<Vector *> sol_l, std::vector<Vector *> par_l)
{
MFEM_ASSERT(derivative_action_callbacks.find(derivative_id) !=
derivative_action_callbacks.end(),
"no derivative action has been found for ID " << derivative_id);
MFEM_ASSERT(sol_l.size() == solutions.size(),
"wrong number of solutions");
MFEM_ASSERT(par_l.size() == parameters.size(),
"wrong number of parameters");
const size_t derivative_idx = FindIdx(derivative_id, fields);
return std::make_shared<DerivativeOperator>(
height,
GetTrueVSize(fields[derivative_idx]),
derivative_action_callbacks[derivative_id],
fields[derivative_idx],
residual_l.Size(),
daction_transpose_callbacks[derivative_id],
fields[test_space_field_idx],
GetVSize(fields[test_space_field_idx]),
sol_l,
par_l,
restriction_callback,
prolongation_transpose,
assemble_derivative_hypreparmatrix_callbacks[derivative_id]);
}
private:
const ParMesh &mesh;
std::vector<action_t> action_callbacks;
std::map<size_t,
std::vector<derivative_action_t>> derivative_action_callbacks;
std::map<size_t,
std::vector<derivative_action_t>> daction_transpose_callbacks;
std::map<size_t,
std::vector<assemble_derivative_hypreparmatrix_callback_t>>
assemble_derivative_hypreparmatrix_callbacks;
std::vector<FieldDescriptor> solutions;
std::vector<FieldDescriptor> parameters;
// solutions and parameters
std::vector<FieldDescriptor> fields;
mutable std::vector<Vector> solutions_l;
mutable std::vector<Vector> parameters_l;
mutable Vector residual_l;
mutable std::vector<Vector> fields_e;
mutable Vector residual_e;
std::function<void(Vector &, Vector &)> prolongation_transpose;
// TODO: This can probably be removed, as it is only used in
// the callbacks so they can copy it during the capture.
std::function<void(Vector &, Vector &)> output_restriction_transpose;
restriction_callback_t restriction_callback;
std::map<size_t, std::function<void(const Vector &, Vector &)>>
daction_prolongation_transpose;
std::map<size_t, size_t> assembled_vector_sizes;
bool use_tensor_product_structure = true;
size_t test_space_field_idx = SIZE_MAX;
};
template <
typename qfunc_t,
typename input_t,
typename output_t,
typename derivative_ids_t>
void DifferentiableOperator::AddDomainIntegrator(
qfunc_t &qfunc,
input_t inputs,
output_t outputs,
const IntegrationRule &integration_rule,
const Array<int> &domain_attributes,
derivative_ids_t derivative_ids)
{
using entity_t = Entity::Element;
static constexpr size_t num_inputs =
tuple_size<decltype(inputs)>::value;
static constexpr size_t num_outputs =
tuple_size<decltype(outputs)>::value;
using qf_signature =
typename create_function_signature<decltype(&qfunc_t::operator())>::type;
using qf_param_ts = typename qf_signature::parameter_ts;
using qf_output_t = typename qf_signature::return_t;
// Consistency checks
if constexpr (num_outputs > 1)
{
static_assert(dfem::always_false<qfunc_t>,
"more than one output per quadrature functions is not supported right now");
}
if constexpr (std::is_same_v<qf_output_t, void>)
{
static_assert(dfem::always_false<qfunc_t>,
"quadrature function has no return value");
}
constexpr size_t num_qfinputs = tuple_size<qf_param_ts>::value;
static_assert(num_qfinputs == num_inputs,
"quadrature function inputs and descriptor inputs have to match");
constexpr size_t num_qf_outputs = tuple_size<qf_output_t>::value;
static_assert(num_qf_outputs == num_outputs,
"quadrature function outputs and descriptor outputs have to match");
constexpr auto inout_tuple =
merge_mfem_tuples_as_empty_std_tuple(inputs, outputs);
constexpr auto filtered_inout_tuple = filter_fields(inout_tuple);
static constexpr size_t num_fields =
count_unique_field_ids(filtered_inout_tuple);
MFEM_ASSERT(num_fields == solutions.size() + parameters.size(),
"Total number of fields doesn't match sum of solutions and parameters."
" This indicates that some fields are not used in the integrator,"
" which currently is not supported.");
auto dependency_map = make_dependency_map(inputs);
// pretty_print(dependency_map);
auto input_to_field =
create_descriptors_to_fields_map<entity_t>(fields, inputs);
auto output_to_field =
create_descriptors_to_fields_map<entity_t>(fields, outputs);
// TODO: factor out
std::vector<int> inputs_vdim(num_inputs);
for_constexpr<num_inputs>([&](auto i)
{
inputs_vdim[i] = get<i>(inputs).vdim;
});
Array<int> elem_attributes;
elem_attributes.SetSize(mesh.GetNE());
for (int i = 0; i < mesh.GetNE(); ++i)
{
elem_attributes[i] = mesh.GetAttribute(i);
}
const auto output_fop = get<0>(outputs);
test_space_field_idx = FindIdx(output_fop.GetFieldId(), fields);
bool use_sum_factorization = false;
auto entity_element_type =
Element::TypeFromGeometry(mesh.GetTypicalElementGeometry());
if ((entity_element_type == Element::QUADRILATERAL ||
entity_element_type == Element::HEXAHEDRON) &&
use_tensor_product_structure == true)
{
use_sum_factorization = true;
}
ElementDofOrdering element_dof_ordering = ElementDofOrdering::NATIVE;
DofToQuad::Mode doftoquad_mode = DofToQuad::Mode::FULL;
if (use_sum_factorization)
{
element_dof_ordering = ElementDofOrdering::LEXICOGRAPHIC;
doftoquad_mode = DofToQuad::Mode::TENSOR;
}
auto [output_rt,
output_e_sz] = get_restriction_transpose<entity_t>
(fields[test_space_field_idx],
element_dof_ordering, output_fop);
auto &output_e_size = output_e_sz;
output_restriction_transpose = output_rt;
residual_e.SetSize(output_e_size);
// The explicit captures are necessary to avoid dependency on
// the specific instance of this class (this pointer).
restriction_callback =
[=, solutions = this->solutions, parameters = this->parameters]
(std::vector<Vector> &sol,
const std::vector<Vector> &par,
std::vector<Vector> &f)
{
restriction<entity_t>(solutions, sol, f,
element_dof_ordering);
restriction<entity_t>(parameters, par, f,
element_dof_ordering,
solutions.size());
};
prolongation_transpose = get_prolongation_transpose(
fields[test_space_field_idx], output_fop, mesh.GetComm());
const int dimension = mesh.Dimension();
[[maybe_unused]] const int num_elements = GetNumEntities<Entity::Element>(mesh);
const int num_entities = GetNumEntities<entity_t>(mesh);
const int num_qp = integration_rule.GetNPoints();
if constexpr (is_sum_fop<std::remove_cv_t<decltype(output_fop)>>::value)
{
residual_l.SetSize(1);
height = 1;
}
else
{
const int residual_lsize = GetVSize(fields[test_space_field_idx]);
residual_l.SetSize(residual_lsize);
height = GetTrueVSize(fields[test_space_field_idx]);
}
// TODO: Is this a hack?
width = GetTrueVSize(fields[0]);
std::vector<const DofToQuad*> dtq;
for (const auto &field : fields)
{
dtq.emplace_back(GetDofToQuad<entity_t>(
field,
integration_rule,
doftoquad_mode));
}
const int q1d = (int)floor(std::pow(num_qp, 1.0/dimension) + 0.5);
const int residual_size_on_qp =
GetSizeOnQP<entity_t>(output_fop,
fields[test_space_field_idx]);
auto input_dtq_maps = create_dtq_maps<entity_t>(inputs, dtq, input_to_field);
auto output_dtq_maps = create_dtq_maps<entity_t>(outputs, dtq, output_to_field);
const int test_vdim = output_fop.vdim;
const int test_op_dim = output_fop.size_on_qp / output_fop.vdim;
int num_test_dof = 0;
if (num_entities)
{
if constexpr (is_sum_fop<std::decay_t<decltype(output_fop)>>::value)
{
num_test_dof = 1;
}
else
{
num_test_dof = output_e_size / output_fop.vdim / num_entities;
}
}
auto ir_weights = Reshape(integration_rule.GetWeights().Read(), num_qp);
auto input_size_on_qp =
get_input_size_on_qp(inputs, std::make_index_sequence<num_inputs> {});
auto action_shmem_info =
get_shmem_info<entity_t, num_fields, num_inputs, num_outputs>
(input_dtq_maps, output_dtq_maps, fields, num_entities, inputs, num_qp,
input_size_on_qp, residual_size_on_qp, element_dof_ordering);
Vector shmem_cache(action_shmem_info.total_size);
// print_shared_memory_info(action_shmem_info);
ThreadBlocks thread_blocks;
if (dimension == 3)
{
if (use_sum_factorization)
{
thread_blocks.x = q1d;
thread_blocks.y = q1d;
thread_blocks.z = q1d;
}
}
else if (dimension == 2)
{
if (use_sum_factorization)
{
thread_blocks.x = q1d;
thread_blocks.y = q1d;
thread_blocks.z = 1;
}
}
action_callbacks.push_back(
// Explicitly capture everything we need, so we can make explicit choice
// how to capture every variable, by copy or by ref.
[
// capture by copy:
dimension, // int
num_entities, // int
num_test_dof, // int
num_qp, // int
q1d, // int
residual_size_on_qp, // int
test_vdim, // int (= output_fop.vdim)
test_op_dim, // int (derived from output_fop)
inputs, // mfem::future::tuple
domain_attributes, // Array<int>
ir_weights, // DeviceTensor
use_sum_factorization, // bool
input_dtq_maps, // std::array<DofToQuadMap, num_fields>
output_dtq_maps, // std::array<DofToQuadMap, num_fields>
input_to_field, // std::array<int, s>
output_fop, // class derived from FieldOperator
qfunc, // qfunc_t
thread_blocks, // ThreadBlocks
shmem_cache, // Vector (local)
action_shmem_info, // SharedMemoryInfo
// TODO: make this Array<int> a member of the DifferentiableOperator
// and capture it by ref.
elem_attributes, // Array<int>
// capture by ref:
&restriction_cb = this->restriction_callback,
&fields_e = this->fields_e,
&residual_e = this->residual_e,
&output_restriction_transpose = this->output_restriction_transpose
]
(std::vector<Vector> &sol, const std::vector<Vector> &par, Vector &res)
mutable // mutable: needed to modify 'shmem_cache'
{
restriction_cb(sol, par, fields_e);
residual_e = 0.0;
auto ye = Reshape(residual_e.ReadWrite(), test_vdim, num_test_dof, num_entities);
auto wrapped_fields_e = wrap_fields(fields_e,
action_shmem_info.field_sizes,
num_entities);
const bool has_attr = domain_attributes.Size() > 0;
const auto d_domain_attr = domain_attributes.Read();
const auto d_elem_attr = elem_attributes.Read();
forall([=] MFEM_HOST_DEVICE (int e, void *shmem)
{
if (has_attr && !d_domain_attr[d_elem_attr[e] - 1]) { return; }
auto [input_dtq_shmem, output_dtq_shmem, fields_shmem, input_shmem,
residual_shmem, scratch_shmem] =
unpack_shmem(shmem, action_shmem_info, input_dtq_maps, output_dtq_maps,
wrapped_fields_e, num_qp, e);
map_fields_to_quadrature_data(
input_shmem, fields_shmem, input_dtq_shmem, input_to_field, inputs, ir_weights,
scratch_shmem, dimension, use_sum_factorization);
call_qfunction<qf_param_ts>(
qfunc, input_shmem, residual_shmem,
residual_size_on_qp, num_qp, q1d, dimension, use_sum_factorization);
auto fhat = Reshape(&residual_shmem(0, 0), test_vdim, test_op_dim, num_qp);
auto y = Reshape(&ye(0, 0, e), num_test_dof, test_vdim);
map_quadrature_data_to_fields(
y, fhat, output_fop, output_dtq_shmem[0],
scratch_shmem, dimension, use_sum_factorization);
}, num_entities, thread_blocks, action_shmem_info.total_size, shmem_cache.ReadWrite());
output_restriction_transpose(residual_e, res);
});
// Without this compile-time check, some valid instantiations of this method
// will fail.
if constexpr (derivative_ids_t::size() != 0)
{
// Create the action of the derivatives
for_constexpr([&,
&or_transpose = this->output_restriction_transpose,
&dapr_transpose = this->daction_prolongation_transpose]
(const std::size_t derivative_id)
{
const size_t d_field_idx = FindIdx(derivative_id, fields);
const auto direction = fields[d_field_idx];
dapr_transpose[derivative_id] = get_generic_prolongation_transpose(direction);
const int da_size_on_qp =
GetSizeOnQP<entity_t>(output_fop, fields[test_space_field_idx]);
auto shmem_info =
get_shmem_info<entity_t, num_fields, num_inputs, num_outputs>(
input_dtq_maps, output_dtq_maps, fields, num_entities, inputs,
num_qp, input_size_on_qp, residual_size_on_qp,
element_dof_ordering, d_field_idx);
Vector shmem_cache(shmem_info.total_size);
// print_shared_memory_info(shmem_info);
Vector direction_e;
Vector derivative_action_e(output_e_size);
derivative_action_e = 0.0;
// Lookup the derivative_id key in the dependency map
auto it = dependency_map.find(derivative_id);
if (it == dependency_map.end())
{
MFEM_ABORT("Derivative ID not found in dependency map");
}
const auto input_is_dependent = it->second;
derivative_action_callbacks[derivative_id].push_back(
[
// capture by copy:
dimension, // int
num_entities, // int
num_test_dof, // int
num_qp, // int
q1d, // int
test_vdim, // int (= output_fop.vdim)
test_op_dim, // int (derived from output_fop)
inputs, // mfem::future::tuple
domain_attributes, // Array<int>
ir_weights, // DeviceTensor
use_sum_factorization, // bool
input_dtq_maps, // std::array<DofToQuadMap, num_fields>
output_dtq_maps, // std::array<DofToQuadMap, num_fields>
input_to_field, // std::array<int, s>
output_fop, // class derived from FieldOperator
qfunc, // qfunc_t
thread_blocks, // ThreadBlocks
shmem_cache, // Vector (local)
shmem_info, // SharedMemoryInfo
// TODO: make this Array<int> a member of the DifferentiableOperator
// and capture it by ref.
elem_attributes, // Array<int>
input_is_dependent, // std::array<bool, num_inputs>
direction, // FieldDescriptor
direction_e, // Vector
derivative_action_e, // Vector
element_dof_ordering, // ElementDofOrdering
da_size_on_qp, // int
// capture by ref:
&or_transpose
](
std::vector<Vector> &f_e, const Vector &dir_l,
Vector &der_action_l) mutable
{
restriction<entity_t>(direction, dir_l, direction_e,
element_dof_ordering);
auto ye = Reshape(derivative_action_e.ReadWrite(), num_test_dof,
test_vdim, num_entities);
auto wrapped_fields_e = wrap_fields(f_e, shmem_info.field_sizes,
num_entities);
auto wrapped_direction_e = Reshape(direction_e.ReadWrite(),
shmem_info.direction_size,
num_entities);
const auto d_elem_attr = elem_attributes.Read();
const bool has_attr = domain_attributes.Size() > 0;
const auto d_domain_attr = domain_attributes.Read();
derivative_action_e = 0.0;
forall([=] MFEM_HOST_DEVICE (int e, real_t *shmem)
{
if (has_attr && !d_domain_attr[d_elem_attr[e] - 1]) { return; }
auto [input_dtq_shmem, output_dtq_shmem, fields_shmem,
direction_shmem, input_shmem,
shadow_shmem_, residual_shmem,
scratch_shmem] =
unpack_shmem(shmem, shmem_info, input_dtq_maps, output_dtq_maps,
wrapped_fields_e, wrapped_direction_e, num_qp, e);
auto &shadow_shmem = shadow_shmem_;
map_fields_to_quadrature_data(
input_shmem, fields_shmem, input_dtq_shmem, input_to_field,
inputs, ir_weights, scratch_shmem, dimension,
use_sum_factorization);
// TODO: Probably redundant
set_zero(shadow_shmem);
map_direction_to_quadrature_data_conditional(
shadow_shmem, direction_shmem, input_dtq_shmem, inputs,
ir_weights, scratch_shmem, input_is_dependent, dimension,
use_sum_factorization);
call_qfunction_derivative_action<qf_param_ts>(
qfunc, input_shmem, shadow_shmem, residual_shmem,
da_size_on_qp, num_qp, q1d, dimension, use_sum_factorization);
auto fhat = Reshape(&residual_shmem(0, 0), test_vdim,
test_op_dim, num_qp);
auto y = Reshape(&ye(0, 0, e), num_test_dof, test_vdim);
map_quadrature_data_to_fields(
y, fhat, output_fop, output_dtq_shmem[0],
scratch_shmem, dimension, use_sum_factorization);
}, num_entities, thread_blocks, shmem_info.total_size,
shmem_cache.ReadWrite());
or_transpose(derivative_action_e, der_action_l);
});
}, derivative_ids);
}
}
} // namespace mfem::future
#endif
+144
View File
@@ -0,0 +1,144 @@
// 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 <type_traits>
namespace mfem::future
{
/// @brief Base class for FieldOperators.
///
/// This class serves as a base for different FieldOperator types which can be
/// applied to fields that are used with inputs to a quadrature point function.
/// See DifferentialOperator.
template <int FIELD_ID = -1>
class FieldOperator
{
public:
/// @brief Constructor for the FieldOperator.
///
/// This constructor initializes the FieldOperator with it's size on
/// quadrature points. The size on quadrature points has to be determined by
/// the FieldOperator type, the dimension and the vector dimension (number
/// of components). See the following examples
///
/// Scalar FiniteElementSpace with Value FieldOperator:
/// size = vdim x dim x 1 = 1 x dim x 1 = dim
///
/// Vector FiniteElementSpace with Gradient FieldOperator:
/// size = vdim x dim x dim = vdim x dim x dim = vdim * dim^2
///
/// ParameterSpace with Identity FieldOperator:
/// size = vdim = vdim
constexpr FieldOperator(int size_on_qp = 0) :
size_on_qp(size_on_qp) {};
/// @brief Get the field id this FieldOperator is attached to.
static constexpr int GetFieldId() { return FIELD_ID; }
/// @brief Get the size on quadrature point for this FieldOperator.
int size_on_qp = -1;
/// @brief Get the dimension of the FieldOperator.
int dim = -1;
/// @brief Get the vector dimension (number of components)
/// of the FieldOperator.
int vdim = -1;
};
/// @brief Identity FieldOperator.
///
/// This FieldOperator does nothing to the field. The field (usually a
/// ParametricFunction) transfers the values to the quadrature point data and
/// Identity can be viewed as an identity operation.
template <int FIELD_ID = -1>
class Identity : public FieldOperator<FIELD_ID>
{
public:
constexpr Identity() : FieldOperator<FIELD_ID>() {}
};
template< typename T >
struct is_identity_fop : std::false_type {};
template <int FIELD_ID>
struct is_identity_fop<Identity<FIELD_ID>> : std::true_type {};
/// @brief Weight FieldOperator.
///
/// This FieldOperator is used to signal that this field contains the quadrature
/// point weights.
class Weight : public FieldOperator<-1>
{
public:
constexpr Weight() : FieldOperator<-1>() {};
};
template< typename T >
struct is_weight_fop : std::false_type {};
template <>
struct is_weight_fop<Weight> : std::true_type {};
/// @brief Value FieldOperator.
///
/// This FieldOperator is used to signal that the field contains the
/// interpolated values of the degrees of freedom at the quadrature points.
template <int FIELD_ID = -1>
class Value : public FieldOperator<FIELD_ID>
{
public:
constexpr Value() : FieldOperator<FIELD_ID>() {};
};
template< typename T >
struct is_value_fop : std::false_type {};
template <int FIELD_ID>
struct is_value_fop<Value<FIELD_ID>> : std::true_type {};
/// @brief Gradient FieldOperator.
///
/// This FieldOperator is used to signal that the field contains the
/// interpolated gradients of the degrees of freedom at the quadrature points.
template <int FIELD_ID = -1>
class Gradient : public FieldOperator<FIELD_ID>
{
public:
constexpr Gradient() : FieldOperator<FIELD_ID>() {};
};
template< typename T >
struct is_gradient_fop : std::false_type {};
template <int FIELD_ID>
struct is_gradient_fop<Gradient<FIELD_ID>> : std::true_type {};
/// @brief Sum FieldOperator.
///
/// This FieldOperator is commonly used to signal that an output of a quadrature
/// function should be summed.
template <int FIELD_ID = -1>
class Sum : public FieldOperator<FIELD_ID>
{
public:
constexpr Sum() : FieldOperator<FIELD_ID>() {};
};
template< typename T >
struct is_sum_fop : std::false_type {};
template <int FIELD_ID>
struct is_sum_fop<Sum<FIELD_ID>> : std::true_type {};
} // namespace mfem::future
+472
View File
@@ -0,0 +1,472 @@
// 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 "util.hpp"
namespace mfem::future
{
template <typename output_t>
MFEM_HOST_DEVICE
void map_quadrature_data_to_fields_impl(
DeviceTensor<2, real_t> &y,
const DeviceTensor<3, real_t> &f,
const output_t &output,
const DofToQuadMap &dtq)
{
[[maybe_unused]] auto B = dtq.B;
[[maybe_unused]] auto G = dtq.G;
// assuming the quadrature point residual has to "play nice with
// the test function"
if constexpr (is_value_fop<std::decay_t<output_t>>::value)
{
const auto [num_qp, cdim, num_dof] = B.GetShape();
const int vdim = output.vdim > 0 ? output.vdim : cdim ;
for (int dof = 0; dof < num_dof; dof++)
{
for (int vd = 0; vd < vdim; vd++)
{
real_t acc = 0.0;
for (int qp = 0; qp < num_qp; qp++)
{
acc += B(qp, 0, dof) * f(vd, 0, qp);
}
y(dof, vd) += acc;
}
}
}
else if constexpr (
is_gradient_fop<std::decay_t<output_t>>::value)
{
const auto [num_qp, dim, num_dof] = G.GetShape();
const int vdim = output.vdim;
for (int dof = 0; dof < num_dof; dof++)
{
for (int vd = 0; vd < vdim; vd++)
{
real_t acc = 0.0;
for (int d = 0; d < dim; d++)
{
for (int qp = 0; qp < num_qp; qp++)
{
acc += G(qp, d, dof) * f(vd, d, qp);
}
}
y(dof, vd) += acc;
}
}
}
else if constexpr (is_sum_fop<std::decay_t<output_t>>::value)
{
// This is the "integral over all quadrature points type" applying
// B = 1 s.t. B^T * C \in R^1.
const auto [num_qp, unused, unused1] = B.GetShape();
auto cc = Reshape(&f(0, 0, 0), num_qp);
for (int i = 0; i < num_qp; i++)
{
y(0, 0) += cc(i);
}
}
else if constexpr (is_identity_fop<std::decay_t<output_t>>::value)
{
const auto [num_qp, unused, num_dof] = B.GetShape();
const auto vdim = output.vdim;
auto cc = Reshape(&f(0, 0, 0), num_qp * vdim);
auto yy = Reshape(&y(0, 0), num_qp * vdim);
for (int i = 0; i < num_qp * vdim; i++)
{
yy(i) = cc(i);
}
}
else
{
MFEM_ABORT("quadrature data mapping to field is not implemented for"
" this field descriptor");
}
}
template <typename output_t>
MFEM_HOST_DEVICE
void map_quadrature_data_to_fields_tensor_impl_2d(
DeviceTensor<2, real_t> &y,
const DeviceTensor<3, real_t> &f,
const output_t &output,
const DofToQuadMap &dtq,
std::array<DeviceTensor<1>, 6> &scratch_mem)
{
[[maybe_unused]] auto B = dtq.B;
[[maybe_unused]] auto G = dtq.G;
if constexpr (is_value_fop<std::decay_t<output_t>>::value)
{
const auto [q1d, unused, d1d] = B.GetShape();
const int vdim = output.vdim;
const int test_dim = output.size_on_qp / vdim;
auto fqp = Reshape(&f(0, 0, 0), vdim, test_dim, q1d, q1d);
auto yd = Reshape(&y(0, 0), d1d, d1d, vdim);
auto s0 = Reshape(&scratch_mem[0](0), q1d, d1d);
for (int vd = 0; vd < vdim; vd++)
{
MFEM_FOREACH_THREAD(qy, y, q1d)
{
MFEM_FOREACH_THREAD(dx, x, d1d)
{
real_t acc = 0.0;
for (int qx = 0; qx < q1d; qx++)
{
acc += fqp(vd, 0, qx, qy) * B(qx, 0, dx);
}
s0(qy, dx) = acc;
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(dy, y, d1d)
{
MFEM_FOREACH_THREAD(dx, x, d1d)
{
real_t acc = 0.0;
for (int qy = 0; qy < q1d; qy++)
{
acc += s0(qy, dx) * B(qy, 0, dy);
}
yd(dx, dy, vd) += acc;
}
}
MFEM_SYNC_THREAD;
}
}
else if constexpr (is_gradient_fop<std::decay_t<output_t>>::value)
{
const auto [q1d, unused, d1d] = G.GetShape();
const int vdim = output.vdim;
const int test_dim = output.size_on_qp / vdim;
auto fqp = Reshape(&f(0, 0, 0), vdim, test_dim, q1d, q1d);
auto yd = Reshape(&y(0, 0), d1d, d1d, vdim);
auto s0 = Reshape(&scratch_mem[0](0), q1d, d1d);
auto s1 = Reshape(&scratch_mem[1](0), q1d, d1d);
for (int vd = 0; vd < vdim; vd++)
{
MFEM_FOREACH_THREAD(qy, y, q1d)
{
MFEM_FOREACH_THREAD(dx, x, d1d)
{
real_t uv[2] = {0.0, 0.0};
for (int qx = 0; qx < q1d; qx++)
{
uv[0] += fqp(vd, 0, qx, qy) * G(qx, 0, dx);
uv[1] += fqp(vd, 1, qx, qy) * B(qx, 0, dx);
}
s0(qy, dx) = uv[0];
s1(qy, dx) = uv[1];
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(dy, y, d1d)
{
MFEM_FOREACH_THREAD(dx, x, d1d)
{
real_t uv[2] = {0.0, 0.0};
for (int qy = 0; qy < q1d; qy++)
{
uv[0] += s0(qy, dx) * B(qy, 0, dy);
uv[1] += s1(qy, dx) * G(qy, 0, dy);
}
yd(dx, dy, vd) += uv[0] + uv[1];
}
}
MFEM_SYNC_THREAD;
}
}
else if constexpr (is_identity_fop<std::decay_t<output_t>>::value)
{
const auto [q1d, unused, d1d] = B.GetShape();
// // TODO: Check if this is the right fix for all cases
// auto fqp = Reshape(&f(0, 0, 0), output.size_on_qp, q1d);
// auto yqp = Reshape(&y(0, 0), output.size_on_qp, q1d);
// for (int sq = 0; sq < output.size_on_qp; sq++)
// {
// MFEM_FOREACH_THREAD(qx, x, q1d)
// {
// yqp(sq, qx) = fqp(sq, qx);
// }
// MFEM_SYNC_THREAD;
// }
auto fqp = Reshape(&f(0, 0, 0), output.size_on_qp, q1d, q1d);
auto yqp = Reshape(&y(0, 0), output.size_on_qp, q1d, q1d);
for (int sq = 0; sq < output.size_on_qp; sq++)
{
MFEM_FOREACH_THREAD(qx, x, q1d)
{
MFEM_FOREACH_THREAD(qy, y, q1d)
{
yqp(sq, qx, qy) = fqp(sq, qx, qy);
}
}
MFEM_SYNC_THREAD;
}
}
else if constexpr (is_sum_fop<std::decay_t<output_t>>::value)
{
const auto [q1d, unused, d1d] = B.GetShape();
auto fqp = Reshape(&f(0, 0, 0), q1d * q1d);
auto yqp = Reshape(&y(0, 0), output.size_on_qp);
for (int i = 0; i < q1d * q1d; i++)
{
yqp(0) += fqp(i);
}
MFEM_SYNC_THREAD;
}
else
{
MFEM_ABORT("quadrature data mapping to field is not implemented for"
" this field descriptor with sum factorization on tensor product elements");
}
}
template <typename output_t>
MFEM_HOST_DEVICE
void map_quadrature_data_to_fields_tensor_impl_3d(
DeviceTensor<2, real_t> &y,
const DeviceTensor<3, real_t> &f,
const output_t &output,
const DofToQuadMap &dtq,
std::array<DeviceTensor<1>, 6> &scratch_mem)
{
[[maybe_unused]] auto B = dtq.B;
[[maybe_unused]] auto G = dtq.G;
if constexpr (is_value_fop<std::decay_t<output_t>>::value)
{
const auto [q1d, unused, d1d] = B.GetShape();
const int vdim = output.vdim;
const int test_dim = output.size_on_qp / vdim;
auto fqp = Reshape(&f(0, 0, 0), vdim, test_dim, q1d, q1d, q1d);
auto yd = Reshape(&y(0, 0), d1d, d1d, d1d, vdim);
auto s0 = Reshape(&scratch_mem[0](0), q1d, q1d, d1d);
auto s1 = Reshape(&scratch_mem[1](0), q1d, d1d, d1d);
for (int vd = 0; vd < vdim; vd++)
{
MFEM_FOREACH_THREAD(qy, y, q1d)
{
MFEM_FOREACH_THREAD(dx, x, d1d)
{
MFEM_FOREACH_THREAD(qz, z, q1d)
{
real_t acc = 0.0;
for (int qx = 0; qx < q1d; qx++)
{
acc += fqp(vd, 0, qx, qy, qz) * B(qx, 0, dx);
}
s0(qz, qy, dx) = acc;
}
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(dy, y, d1d)
{
MFEM_FOREACH_THREAD(dx, x, d1d)
{
MFEM_FOREACH_THREAD(qz, z, q1d)
{
real_t acc = 0.0;
for (int qy = 0; qy < q1d; qy++)
{
acc += s0(qz, qy, dx) * B(qy, 0, dy);
}
s1(qz, dy, dx) = acc;
}
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(dy, y, d1d)
{
MFEM_FOREACH_THREAD(dx, x, d1d)
{
MFEM_FOREACH_THREAD(dz, z, d1d)
{
real_t acc = 0.0;
for (int qz = 0; qz < q1d; qz++)
{
acc += s1(qz, dy, dx) * B(qz, 0, dz);
}
yd(dx, dy, dz, vd) += acc;
}
}
}
MFEM_SYNC_THREAD;
}
}
else if constexpr (is_gradient_fop<std::decay_t<output_t>>::value)
{
const auto [q1d, unused, d1d] = G.GetShape();
const int vdim = output.vdim;
const int test_dim = output.size_on_qp / vdim;
auto fqp = Reshape(&f(0, 0, 0), vdim, test_dim, q1d, q1d, q1d);
auto yd = Reshape(&y(0, 0), d1d, d1d, d1d, vdim);
auto s0 = Reshape(&scratch_mem[0](0), q1d, q1d, d1d);
auto s1 = Reshape(&scratch_mem[1](0), q1d, q1d, d1d);
auto s2 = Reshape(&scratch_mem[2](0), q1d, q1d, d1d);
auto s3 = Reshape(&scratch_mem[3](0), q1d, d1d, d1d);
auto s4 = Reshape(&scratch_mem[4](0), q1d, d1d, d1d);
auto s5 = Reshape(&scratch_mem[5](0), q1d, d1d, d1d);
for (int vd = 0; vd < vdim; vd++)
{
MFEM_FOREACH_THREAD(qz, z, q1d)
{
MFEM_FOREACH_THREAD(qy, y, q1d)
{
MFEM_FOREACH_THREAD(dx, x, d1d)
{
real_t uvw[3] = {0.0, 0.0, 0.0};
for (int qx = 0; qx < q1d; qx++)
{
uvw[0] += fqp(vd, 0, qx, qy, qz) * G(qx, 0, dx);
uvw[1] += fqp(vd, 1, qx, qy, qz) * B(qx, 0, dx);
uvw[2] += fqp(vd, 2, qx, qy, qz) * B(qx, 0, dx);
}
s0(qz, qy, dx) = uvw[0];
s1(qz, qy, dx) = uvw[1];
s2(qz, qy, dx) = uvw[2];
}
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(qz, z, q1d)
{
MFEM_FOREACH_THREAD(dy, y, d1d)
{
MFEM_FOREACH_THREAD(dx, x, d1d)
{
real_t uvw[3] = {0.0, 0.0, 0.0};
for (int qy = 0; qy < q1d; qy++)
{
uvw[0] += s0(qz, qy, dx) * B(qy, 0, dy);
uvw[1] += s1(qz, qy, dx) * G(qy, 0, dy);
uvw[2] += s2(qz, qy, dx) * B(qy, 0, dy);
}
s3(qz, dy, dx) = uvw[0];
s4(qz, dy, dx) = uvw[1];
s5(qz, dy, dx) = uvw[2];
}
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(dz, z, d1d)
{
MFEM_FOREACH_THREAD(dy, y, d1d)
{
MFEM_FOREACH_THREAD(dx, x, d1d)
{
real_t uvw[3] = {0.0, 0.0, 0.0};
for (int qz = 0; qz < q1d; qz++)
{
uvw[0] += s3(qz, dy, dx) * B(qz, 0, dz);
uvw[1] += s4(qz, dy, dx) * B(qz, 0, dz);
uvw[2] += s5(qz, dy, dx) * G(qz, 0, dz);
}
yd(dx, dy, dz, vd) += uvw[0] + uvw[1] + uvw[2];
}
}
}
MFEM_SYNC_THREAD;
}
}
else if constexpr (is_identity_fop<std::decay_t<output_t>>::value)
{
const auto [q1d, unused, d1d] = B.GetShape();
auto fqp = Reshape(&f(0, 0, 0), output.size_on_qp, q1d, q1d, q1d);
auto yqp = Reshape(&y(0, 0), output.size_on_qp, q1d, q1d, q1d);
for (int sq = 0; sq < output.size_on_qp; sq++)
{
MFEM_FOREACH_THREAD(qx, x, q1d)
{
MFEM_FOREACH_THREAD(qy, y, q1d)
{
MFEM_FOREACH_THREAD(qz, z, q1d)
{
yqp(sq, qx, qy, qz) = fqp(sq, qx, qy, qz);
}
}
}
MFEM_SYNC_THREAD;
}
}
else if constexpr (is_sum_fop<std::decay_t<output_t>>::value)
{
const auto [q1d, unused, d1d] = B.GetShape();
auto fqp = Reshape(&f(0, 0, 0), q1d * q1d * q1d);
auto yqp = Reshape(&y(0, 0), output.size_on_qp);
for (int i = 0; i < q1d * q1d * q1d; i++)
{
yqp(0) += fqp(i);
}
MFEM_SYNC_THREAD;
}
else
{
MFEM_ABORT("quadrature data mapping to field is not implemented for"
" this field descriptor with sum factorization on tensor product elements");
}
}
template <typename output_t>
MFEM_HOST_DEVICE
void map_quadrature_data_to_fields(
DeviceTensor<2, real_t> &y,
const DeviceTensor<3, real_t> &f,
const output_t &output,
const DofToQuadMap &dtq,
std::array<DeviceTensor<1>, 6> &scratch_mem,
const int &dimension,
const bool &use_sum_factorization)
{
if (use_sum_factorization)
{
if (dimension == 2)
{
map_quadrature_data_to_fields_tensor_impl_2d(y, f, output, dtq, scratch_mem);
}
else if (dimension == 3)
{
map_quadrature_data_to_fields_tensor_impl_3d(y, f, output, dtq, scratch_mem);
}
else { MFEM_ABORT_KERNEL("dimension not supported"); }
}
else
{
map_quadrature_data_to_fields_impl(y, f, output, dtq);
}
}
} // namespace mfem::future
+573
View File
@@ -0,0 +1,573 @@
// 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 "util.hpp"
namespace mfem::future
{
template <typename field_operator_t>
MFEM_HOST_DEVICE inline
void map_field_to_quadrature_data_tensor_product_3d(
DeviceTensor<2> &field_qp,
const DofToQuadMap &dtq,
const DeviceTensor<1> &field_e,
const field_operator_t &input,
const DeviceTensor<1, const real_t> &integration_weights,
const std::array<DeviceTensor<1>, 6> &scratch_mem)
{
[[maybe_unused]] auto B = dtq.B;
[[maybe_unused]] auto G = dtq.G;
if constexpr (is_value_fop<std::decay_t<field_operator_t>>::value)
{
auto [q1d, unused, d1d] = B.GetShape();
const int vdim = input.vdim;
const auto field = Reshape(&field_e[0], d1d, d1d, d1d, vdim);
auto fqp = Reshape(&field_qp[0], vdim, q1d, q1d, q1d);
auto s0 = Reshape(&scratch_mem[0](0), d1d, d1d, q1d);
auto s1 = Reshape(&scratch_mem[1](0), d1d, q1d, q1d);
for (int vd = 0; vd < vdim; vd++)
{
MFEM_FOREACH_THREAD(dz, z, d1d)
{
MFEM_FOREACH_THREAD(dy, y, d1d)
{
MFEM_FOREACH_THREAD(qx, x, q1d)
{
real_t acc = 0.0;
for (int dx = 0; dx < d1d; dx++)
{
acc += B(qx, 0, dx) * field(dx, dy, dz, vd);
}
s0(dz, dy, qx) = acc;
}
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(dz, z, d1d)
{
MFEM_FOREACH_THREAD(qx, x, q1d)
{
MFEM_FOREACH_THREAD(qy, y, q1d)
{
real_t acc = 0.0;
for (int dy = 0; dy < d1d; dy++)
{
acc += s0(dz, dy, qx) * B(qy, 0, dy);
}
s1(dz, qy, qx) = acc;
}
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(qz, z, q1d)
{
MFEM_FOREACH_THREAD(qy, y, q1d)
{
MFEM_FOREACH_THREAD(qx, x, q1d)
{
real_t acc = 0.0;
for (int dz = 0; dz < d1d; dz++)
{
acc += s1(dz, qy, qx) * B(qz, 0, dz);
}
fqp(vd, qx, qy, qz) = acc;
}
}
}
MFEM_SYNC_THREAD;
}
}
else if constexpr (
is_gradient_fop<std::decay_t<field_operator_t>>::value)
{
const auto [q1d, unused, d1d] = B.GetShape();
const int vdim = input.vdim;
const int dim = input.dim;
const auto field = Reshape(&field_e[0], d1d, d1d, d1d, vdim);
auto fqp = Reshape(&field_qp[0], vdim, dim, q1d, q1d, q1d);
auto s0 = Reshape(&scratch_mem[0](0), d1d, d1d, q1d);
auto s1 = Reshape(&scratch_mem[1](0), d1d, d1d, q1d);
auto s2 = Reshape(&scratch_mem[2](0), d1d, q1d, q1d);
auto s3 = Reshape(&scratch_mem[3](0), d1d, q1d, q1d);
auto s4 = Reshape(&scratch_mem[4](0), d1d, q1d, q1d);
for (int vd = 0; vd < vdim; vd++)
{
MFEM_FOREACH_THREAD(dz, z, d1d)
{
MFEM_FOREACH_THREAD(dy, y, d1d)
{
MFEM_FOREACH_THREAD(qx, x, q1d)
{
real_t uv[2] = {0.0, 0.0};
for (int dx = 0; dx < d1d; dx++)
{
const real_t f = field(dx, dy, dz, vd);
uv[0] += f * B(qx, 0, dx);
uv[1] += f * G(qx, 0, dx);
}
s0(dz, dy, qx) = uv[0];
s1(dz, dy, qx) = uv[1];
}
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(dz, z, d1d)
{
MFEM_FOREACH_THREAD(qy, y, q1d)
{
MFEM_FOREACH_THREAD(qx, x, q1d)
{
real_t uvw[3] = {0.0, 0.0, 0.0};
for (int dy = 0; dy < d1d; dy++)
{
const real_t s0i = s0(dz, dy, qx);
uvw[0] += s1(dz, dy, qx) * B(qy, 0, dy);
uvw[1] += s0i * G(qy, 0, dy);
uvw[2] += s0i * B(qy, 0, dy);
}
s2(dz, qy, qx) = uvw[0];
s3(dz, qy, qx) = uvw[1];
s4(dz, qy, qx) = uvw[2];
}
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(qz, z, q1d)
{
MFEM_FOREACH_THREAD(qy, y, q1d)
{
MFEM_FOREACH_THREAD(qx, x, q1d)
{
real_t uvw[3] = {0.0, 0.0, 0.0};
for (int dz = 0; dz < d1d; dz++)
{
uvw[0] += s2(dz, qy, qx) * B(qz, 0, dz);
uvw[1] += s3(dz, qy, qx) * B(qz, 0, dz);
uvw[2] += s4(dz, qy, qx) * G(qz, 0, dz);
}
fqp(vd, 0, qx, qy, qz) = uvw[0];
fqp(vd, 1, qx, qy, qz) = uvw[1];
fqp(vd, 2, qx, qy, qz) = uvw[2];
}
}
}
MFEM_SYNC_THREAD;
}
}
// TODO: Create separate function for clarity
else if constexpr (
std::is_same_v<std::decay_t<field_operator_t>, Weight>)
{
const int num_qp = integration_weights.GetShape()[0];
// TODO: eeek
const int q1d = (int)floor(std::pow(num_qp, 1.0/input.dim) + 0.5);
auto w = Reshape(&integration_weights[0], q1d, q1d, q1d);
auto f = Reshape(&field_qp[0], q1d, q1d, q1d);
MFEM_FOREACH_THREAD(qx, x, q1d)
{
MFEM_FOREACH_THREAD(qy, y, q1d)
{
MFEM_FOREACH_THREAD(qz, z, q1d)
{
f(qx, qy, qz) = w(qx, qy, qz);
}
}
}
MFEM_SYNC_THREAD;
}
else if constexpr (is_identity_fop<std::decay_t<field_operator_t>>::value)
{
const int q1d = B.GetShape()[0];
auto field = Reshape(&field_e[0], input.size_on_qp, q1d * q1d * q1d);
field_qp = field;
}
else
{
static_assert(dfem::always_false<std::decay_t<field_operator_t>>,
"can't map field to quadrature data");
}
}
template <typename field_operator_t>
MFEM_HOST_DEVICE inline
void map_field_to_quadrature_data_tensor_product_2d(
DeviceTensor<2> &field_qp,
const DofToQuadMap &dtq,
const DeviceTensor<1> &field_e,
const field_operator_t &input,
const DeviceTensor<1, const real_t> &integration_weights,
const std::array<DeviceTensor<1>, 6> &scratch_mem)
{
[[maybe_unused]] auto B = dtq.B;
[[maybe_unused]] auto G = dtq.G;
if constexpr (is_value_fop<std::decay_t<field_operator_t>>::value)
{
auto [q1d, unused, d1d] = B.GetShape();
const int vdim = input.vdim;
const auto field = Reshape(&field_e[0], d1d, d1d, vdim);
auto fqp = Reshape(&field_qp[0], vdim, q1d, q1d);
auto s0 = Reshape(&scratch_mem[0](0), d1d, q1d);
for (int vd = 0; vd < vdim; vd++)
{
MFEM_FOREACH_THREAD(dy, y, d1d)
{
MFEM_FOREACH_THREAD(qx, x, q1d)
{
real_t acc = 0.0;
for (int dx = 0; dx < d1d; dx++)
{
acc += B(qx, 0, dx) * field(dx, dy, vd);
}
s0(dy, qx) = acc;
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(qx, x, q1d)
{
MFEM_FOREACH_THREAD(qy, y, q1d)
{
real_t acc = 0.0;
for (int dy = 0; dy < d1d; dy++)
{
acc += s0(dy, qx) * B(qy, 0, dy);
}
fqp(vd, qx, qy) = acc;
}
}
MFEM_SYNC_THREAD;
}
}
else if constexpr (
is_gradient_fop<std::decay_t<field_operator_t>>::value)
{
const auto [q1d, unused, d1d] = B.GetShape();
const int vdim = input.vdim;
const int dim = input.dim;
const auto field = Reshape(&field_e[0], d1d, d1d, vdim);
auto fqp = Reshape(&field_qp[0], vdim, dim, q1d, q1d);
auto s0 = Reshape(&scratch_mem[0](0), d1d, q1d);
auto s1 = Reshape(&scratch_mem[1](0), d1d, q1d);
for (int vd = 0; vd < vdim; vd++)
{
MFEM_FOREACH_THREAD(dy, y, d1d)
{
MFEM_FOREACH_THREAD(qx, x, q1d)
{
real_t uv[2] = {0.0, 0.0};
for (int dx = 0; dx < d1d; dx++)
{
const real_t f = field(dx, dy, vd);
uv[0] += f * B(qx, 0, dx);
uv[1] += f * G(qx, 0, dx);
}
s0(dy, qx) = uv[0];
s1(dy, qx) = uv[1];
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(qy, y, q1d)
{
MFEM_FOREACH_THREAD(qx, x, q1d)
{
real_t uv[2] = {0.0, 0.0};
for (int dy = 0; dy < d1d; dy++)
{
const real_t s0i = s0(dy, qx);
uv[0] += s1(dy, qx) * B(qy, 0, dy);
uv[1] += s0i * G(qy, 0, dy);
}
fqp(vd, 0, qx, qy) = uv[0];
fqp(vd, 1, qx, qy) = uv[1];
}
}
MFEM_SYNC_THREAD;
}
}
// TODO: Create separate function for clarity
else if constexpr (
std::is_same_v<std::decay_t<field_operator_t>, Weight>)
{
const int num_qp = integration_weights.GetShape()[0];
// TODO: eeek
const int q1d = (int)floor(std::pow(num_qp, 1.0/input.dim) + 0.5);
auto w = Reshape(&integration_weights[0], q1d, q1d);
auto f = Reshape(&field_qp[0], q1d, q1d);
MFEM_FOREACH_THREAD(qx, x, q1d)
{
MFEM_FOREACH_THREAD(qy, y, q1d)
{
f(qx, qy) = w(qx, qy);
}
}
MFEM_SYNC_THREAD;
}
else if constexpr (is_identity_fop<std::decay_t<field_operator_t>>::value)
{
const int q1d = B.GetShape()[0];
auto field = Reshape(&field_e[0], input.size_on_qp, q1d * q1d);
field_qp = field;
}
else
{
static_assert(dfem::always_false<std::decay_t<field_operator_t>>,
"can't map field to quadrature data");
}
}
template <typename field_operator_t>
MFEM_HOST_DEVICE
void map_field_to_quadrature_data(
DeviceTensor<2> field_qp,
const DofToQuadMap &dtq,
const DeviceTensor<1> &field_e,
const field_operator_t &input,
const DeviceTensor<1, const real_t> &integration_weights)
{
[[maybe_unused]] auto B = dtq.B;
[[maybe_unused]] auto G = dtq.G;
if constexpr (is_value_fop<field_operator_t>::value)
{
auto [num_qp, dim, num_dof] = B.GetShape();
const int vdim = input.vdim;
const auto field = Reshape(&field_e(0), num_dof, vdim);
for (int vd = 0; vd < vdim; vd++)
{
for (int qp = 0; qp < num_qp; qp++)
{
real_t acc = 0.0;
for (int dof = 0; dof < num_dof; dof++)
{
acc += B(qp, 0, dof) * field(dof, vd);
}
field_qp(vd, qp) = acc;
}
}
}
else if constexpr (is_gradient_fop<field_operator_t>::value)
{
const auto [num_qp, dim, num_dof] = G.GetShape();
const int vdim = input.vdim;
const auto field = Reshape(&field_e(0), num_dof, vdim);
auto f = Reshape(&field_qp[0], vdim, dim, num_qp);
for (int vd = 0; vd < vdim; vd++)
{
for (int qp = 0; qp < num_qp; qp++)
{
for (int d = 0; d < dim; d++)
{
real_t acc = 0.0;
for (int dof = 0; dof < num_dof; dof++)
{
acc += G(qp, d, dof) * field(dof, vd);
}
f(vd, d, qp) = acc;
}
}
}
}
else if constexpr (std::is_same_v<field_operator_t, Weight>)
{
const int num_qp = integration_weights.GetShape()[0];
auto f = Reshape(&field_qp[0], num_qp);
for (int qp = 0; qp < num_qp; qp++)
{
f(qp) = integration_weights(qp);
}
}
else if constexpr (is_identity_fop<field_operator_t>::value)
{
auto [num_qp, unused, num_dof] = B.GetShape();
const int size_on_qp = input.size_on_qp;
const auto field = Reshape(&field_e[0], size_on_qp * num_qp);
auto f = Reshape(&field_qp[0], size_on_qp * num_qp);
for (int i = 0; i < size_on_qp * num_qp; i++)
{
f(i) = field(i);
}
}
else
{
static_assert(dfem::always_false<field_operator_t>,
"can't map field to quadrature data");
}
}
template <typename field_operator_ts, size_t num_inputs, size_t num_fields>
MFEM_HOST_DEVICE inline
void map_fields_to_quadrature_data(
std::array<DeviceTensor<2>, num_inputs> &fields_qp,
const std::array<DeviceTensor<1>, num_fields> &fields_e,
const std::array<DofToQuadMap, num_inputs> &dtqmaps,
const std::array<int, num_inputs> &input_to_field,
const field_operator_ts &fops,
const DeviceTensor<1, const real_t> &integration_weights,
const std::array<DeviceTensor<1>, 6> &scratch_mem,
const int &dimension,
const bool &use_sum_factorization = false)
{
// When the input_to_field map returns -1, this means the requested input
// is the integration weight. Weights don't have a user defined field
// attached to them and we create a dummy field which is not accessed
// inside the functions it is passed to.
const auto dummy_field_weight = DeviceTensor<1>(nullptr, 0);
for_constexpr<num_inputs>([&](auto i)
{
const DeviceTensor<1> &field_e =
(input_to_field[i] == -1) ? dummy_field_weight : fields_e[input_to_field[i]];
if (use_sum_factorization)
{
if (dimension == 2)
{
map_field_to_quadrature_data_tensor_product_2d(
fields_qp[i], dtqmaps[i], field_e, get<i>(fops),
integration_weights, scratch_mem);
}
else if (dimension == 3)
{
map_field_to_quadrature_data_tensor_product_3d(
fields_qp[i], dtqmaps[i], field_e, get<i>(fops),
integration_weights, scratch_mem);
}
else
{
#if !(defined(MFEM_USE_CUDA) || defined(MFEM_USE_HIP))
MFEM_ABORT("unsupported dimension");
#endif
}
}
else
{
map_field_to_quadrature_data(
fields_qp[i], dtqmaps[i], field_e, get<i>(fops),
integration_weights);
}
});
}
template <typename field_operator_t>
MFEM_HOST_DEVICE
void map_field_to_quadrature_data_conditional(
DeviceTensor<2> &field_qp,
const DeviceTensor<1> &field_e,
const DofToQuadMap &dtqmap,
field_operator_t &fop,
const DeviceTensor<1, const real_t> &integration_weights,
const std::array<DeviceTensor<1>, 6> &scratch_mem,
const bool &condition,
const int &dimension,
const bool &use_sum_factorization = false)
{
if (condition)
{
if (use_sum_factorization)
{
if (dimension == 2)
{
map_field_to_quadrature_data_tensor_product_3d(
field_qp, dtqmap, field_e, fop, integration_weights, scratch_mem);
}
else if (dimension == 3)
{
map_field_to_quadrature_data_tensor_product_2d(
field_qp, dtqmap, field_e, fop, integration_weights, scratch_mem);
}
}
else
{
map_field_to_quadrature_data(
field_qp, dtqmap, field_e, fop, integration_weights);
}
}
}
template <size_t num_fields, size_t num_inputs, typename field_operator_ts>
MFEM_HOST_DEVICE
void map_fields_to_quadrature_data_conditional(
std::array<DeviceTensor<2>, num_inputs> &fields_qp,
const std::array<DeviceTensor<1, const real_t>, num_fields> &fields_e,
const std::array<DofToQuadMap, num_inputs> &dtqmaps,
field_operator_ts fops,
const DeviceTensor<1, const real_t> &integration_weights,
const std::array<DeviceTensor<1>, 6> &scratch_mem,
const std::array<bool, num_inputs> &conditions,
const bool &use_sum_factorization = false)
{
for_constexpr<num_inputs>([&](auto i)
{
map_field_to_quadrature_data_conditional(
fields_qp[i], fields_e[i], dtqmaps[i], get<i>(fops), integration_weights,
scratch_mem, conditions[i], use_sum_factorization);
});
}
template <size_t num_inputs, typename field_operator_ts>
MFEM_HOST_DEVICE
void map_direction_to_quadrature_data_conditional(
std::array<DeviceTensor<2>, num_inputs> &directions_qp,
const DeviceTensor<1> &direction_e,
const std::array<DofToQuadMap, num_inputs> &dtqmaps,
field_operator_ts fops,
const DeviceTensor<1, const real_t> &integration_weights,
const std::array<DeviceTensor<1>, 6> &scratch_mem,
const std::array<bool, num_inputs> &conditions,
const int &dimension,
const bool &use_sum_factorization = false)
{
for_constexpr<num_inputs>([&](auto i)
{
if (conditions[i])
{
if (use_sum_factorization)
{
if (dimension == 2)
{
map_field_to_quadrature_data_tensor_product_2d(
directions_qp[i], dtqmaps[i], direction_e, get<i>(fops),
integration_weights, scratch_mem);
}
else if (dimension == 3)
{
map_field_to_quadrature_data_tensor_product_3d(
directions_qp[i], dtqmaps[i], direction_e, get<i>(fops),
integration_weights, scratch_mem);
}
}
else
{
map_field_to_quadrature_data(
directions_qp[i], dtqmaps[i], direction_e, get<i>(fops),
integration_weights);
}
}
});
}
}
+153
View File
@@ -0,0 +1,153 @@
// 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 "../fe/fe_base.hpp"
#include "../../fem/fespace.hpp"
namespace mfem::future
{
/// Base class for parametric spaces
class ParameterSpace
{
public:
ParameterSpace(int vdim = 1) : vdim(vdim) {}
/// @brief Get vector dimension at each point
///
/// This is the number of components at each point in the parametric space.
int GetVDim() const { return vdim; }
/// Get DofToQuad information
const DofToQuad& GetDofToQuad() const { return dtq; }
/// Get total size of the space (T-vector size)
///
/// returns the true size vsize of the space
virtual int GetTrueVSize() const = 0;
/// Get local vector size (L-vector size)
///
/// returns the local size of the space
virtual int GetVSize() const = 0;
/// Get spatial dimension
///
/// returns always 1.
int Dimension() const
{
return 1;
}
/// @brief Get T-vector to L-vector transformation
///
/// returns identity by default that is lazy evaluated.
virtual const Operator* GetProlongationMatrix() const
{
if (!prolongation)
{
prolongation.reset(new IdentityOperator(GetTrueVSize()));
}
return prolongation.get();
}
/// @brief Get L-vector to E-vector transformation
/// @note This is a mock call to replicate interface of FiniteElementSpace.
/// It should not be used by a user.
///
/// returns identity by default that is lazy evaluated.
virtual const Operator* GetElementRestriction(ElementDofOrdering o) const
{
if (!elem_restr)
{
elem_restr.reset(new IdentityOperator(GetVSize()));
}
return elem_restr.get();
}
protected:
int vdim;
DofToQuad dtq;
mutable std::unique_ptr<Operator> prolongation;
mutable std::unique_ptr<Operator> elem_restr;
};
/// @brief Uniform parameter space
class UniformParameterSpace : public ParameterSpace
{
public:
/// @brief Constructor for a uniform parameter space
///
/// @param mesh The mesh to determine dimension and number of elements.
/// @param ir The integration rule to determine the number of quadrature points.
/// @param vdim The vector dimension at each point.
/// @param used_in_tensor_product If true, the number of quadrature points is
/// calculated as the nth root of the number of points in the integration rule,
/// where n is the mesh dimension. If false, the number of quadrature points is
/// taken directly from the integration rule.
UniformParameterSpace(Mesh &mesh, const IntegrationRule &ir, int vdim,
bool used_in_tensor_product = true) :
ParameterSpace(vdim)
{
// Setup DofToQuad information
dtq.nqpt = (int)floor(std::pow(ir.GetNPoints(), 1.0 / mesh.Dimension()) + 0.5);
dtq.ndof = dtq.nqpt;
// Calculate sizes
const int num_qp = used_in_tensor_product ?
std::pow(dtq.nqpt, mesh.Dimension()) :
ir.GetNPoints();
tsize = vdim * num_qp * mesh.GetNE();
lsize = tsize;
}
int GetTrueVSize() const override
{
return tsize;
}
int GetVSize() const override
{
return lsize;
}
private:
/// T-vector size
int tsize;
/// L-vector size
int lsize;
};
class ParameterFunction : public Vector
{
public:
ParameterFunction(ParameterSpace &space) :
Vector(space.GetTrueVSize()),
space(space)
{}
/// @brief Get the ParameterSpace
const ParameterSpace& GetParameterSpace() const
{
return space;
}
using Vector::operator=;
private:
/// the parametric space
ParameterSpace &space;
};
} // namespace mfem::future
+298
View File
@@ -0,0 +1,298 @@
// 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 "util.hpp"
#include "qfunction_transform.hpp"
namespace mfem::future
{
/// @brief Call a qfunction with the given parameters.
///
/// @param qfunc the qfunction to call.
/// @param input_shmem the input shared memory.
/// @param residual_shmem the residual shared memory.
/// @param rs_qp the size of the residual.
/// @param num_qp the number of quadrature points.
/// @param q1d the number of quadrature points in 1D.
/// @param dimension the spatial dimension.
/// @param use_sum_factorization whether to use sum factorization.
/// @tparam qf_param_ts the tuple type of the qfunction parameters.
template <
typename qf_param_ts,
typename qfunc_t,
std::size_t num_fields>
MFEM_HOST_DEVICE inline
void call_qfunction(
qfunc_t &qfunc,
const std::array<DeviceTensor<2>, num_fields> &input_shmem,
DeviceTensor<2> &residual_shmem,
const int &rs_qp,
const int &num_qp,
const int &q1d,
const int &dimension,
const bool &use_sum_factorization)
{
if (use_sum_factorization)
{
if (dimension == 2)
{
MFEM_FOREACH_THREAD(qx, x, q1d)
{
MFEM_FOREACH_THREAD(qy, y, q1d)
{
const int q = qx + q1d * qy;
auto qf_args = decay_tuple<qf_param_ts> {};
auto r = Reshape(&residual_shmem(0, q), rs_qp);
apply_kernel(r, qfunc, qf_args, input_shmem, q);
}
}
}
else if (dimension == 3)
{
MFEM_FOREACH_THREAD(qx, x, q1d)
{
MFEM_FOREACH_THREAD(qy, y, q1d)
{
MFEM_FOREACH_THREAD(qz, z, q1d)
{
const int q = qx + q1d * (qy + q1d * qz);
auto qf_args = decay_tuple<qf_param_ts> {};
auto r = Reshape(&residual_shmem(0, q), rs_qp);
apply_kernel(r, qfunc, qf_args, input_shmem, q);
}
}
}
}
else
{
#if !(defined(MFEM_USE_CUDA) || defined(MFEM_USE_HIP))
MFEM_ABORT("unsupported dimension for sum factorization");
#endif
}
MFEM_SYNC_THREAD;
}
else
{
MFEM_FOREACH_THREAD(q, x, num_qp)
{
auto qf_args = decay_tuple<qf_param_ts> {};
auto r = Reshape(&residual_shmem(0, q), rs_qp);
apply_kernel(r, qfunc, qf_args, input_shmem, q);
}
}
}
/// @brief Call a qfunction with the given parameters and
/// compute it's derivative action.
///
/// @param qfunc the qfunction to call.
/// @param input_shmem the input shared memory.
/// @param shadow_shmem the shadow shared memory.
/// @param residual_shmem the residual shared memory.
/// @param das_qp the size of the derivative action.
/// @param num_qp the number of quadrature points.
/// @param q1d the number of quadrature points in 1D.
/// @param dimension the spatial dimension.
/// @param use_sum_factorization whether to use sum factorization.
/// @tparam qf_param_ts the tuple type of the qfunction parameters.
template <
typename qf_param_ts,
typename qfunc_t,
std::size_t num_fields>
MFEM_HOST_DEVICE inline
void call_qfunction_derivative_action(
qfunc_t &qfunc,
const std::array<DeviceTensor<2>, num_fields> &input_shmem,
const std::array<DeviceTensor<2>, num_fields> &shadow_shmem,
DeviceTensor<2> &residual_shmem,
const int &das_qp,
const int &num_qp,
const int &q1d,
const int &dimension,
const bool &use_sum_factorization)
{
if (use_sum_factorization)
{
if (dimension == 2)
{
MFEM_FOREACH_THREAD(qx, x, q1d)
{
MFEM_FOREACH_THREAD(qy, y, q1d)
{
const int q = qx + q1d * qy;
auto r = Reshape(&residual_shmem(0, q), das_qp);
auto qf_args = decay_tuple<qf_param_ts> {};
#ifdef MFEM_USE_ENZYME
auto qf_shadow_args = decay_tuple<qf_param_ts> {};
apply_kernel_fwddiff_enzyme(r, qfunc, qf_args, qf_shadow_args, input_shmem,
shadow_shmem, q);
#else
apply_kernel_native_dual(r, qfunc, qf_args, input_shmem, shadow_shmem, q);
#endif
}
}
}
else if (dimension == 3)
{
MFEM_FOREACH_THREAD(qx, x, q1d)
{
MFEM_FOREACH_THREAD(qy, y, q1d)
{
MFEM_FOREACH_THREAD(qz, z, q1d)
{
const int q = qx + q1d * (qy + q1d * qz);
auto r = Reshape(&residual_shmem(0, q), das_qp);
auto qf_args = decay_tuple<qf_param_ts> {};
#ifdef MFEM_USE_ENZYME
auto qf_shadow_args = decay_tuple<qf_param_ts> {};
apply_kernel_fwddiff_enzyme(r, qfunc, qf_args, qf_shadow_args, input_shmem,
shadow_shmem, q);
#else
apply_kernel_native_dual(r, qfunc, qf_args, input_shmem, shadow_shmem, q);
#endif
}
}
}
}
MFEM_SYNC_THREAD;
}
else
{
MFEM_FOREACH_THREAD(q, x, num_qp)
{
auto r = Reshape(&residual_shmem(0, q), das_qp);
auto qf_args = decay_tuple<qf_param_ts> {};
#ifdef MFEM_USE_ENZYME
auto qf_shadow_args = decay_tuple<qf_param_ts> {};
apply_kernel_fwddiff_enzyme(r, qfunc, qf_args, qf_shadow_args, input_shmem,
shadow_shmem, q);
#else
apply_kernel_native_dual(r, qfunc, qf_args, input_shmem, shadow_shmem, q);
#endif
}
MFEM_SYNC_THREAD;
}
}
template <typename qfunc_t, typename args_ts, size_t num_args>
MFEM_HOST_DEVICE inline
void apply_kernel(
DeviceTensor<1, real_t> &f_qp,
const qfunc_t &qfunc,
args_ts &args,
const std::array<DeviceTensor<2>, num_args> &u,
int qp)
{
process_qf_args(u, args, qp);
process_qf_result(f_qp, get<0>(apply(qfunc, args)));
}
template <typename qfunc_t, typename arg_ts, size_t num_args>
MFEM_HOST_DEVICE inline
void apply_kernel_native_dual(
DeviceTensor<1, real_t> &f_qp,
const qfunc_t &qfunc,
arg_ts &args,
const std::array<DeviceTensor<2>, num_args> &u,
const std::array<DeviceTensor<2>, num_args> &v,
const int &qp_idx)
{
process_qf_args(u, v, args, qp_idx);
auto r = get<0>(apply(qfunc, args));
process_derivative_from_native_dual(f_qp, r);
}
#ifdef MFEM_USE_ENZYME
template <typename func_t, typename... arg_ts>
MFEM_HOST_DEVICE inline
auto qfunction_wrapper(const func_t &f, arg_ts &&...args)
{
return f(args...);
}
// Version for active function arguments only
//
// This is an Enzyme regression and can be removed in later versions.
template <typename qfunc_t, typename arg_ts, std::size_t... Is,
typename inactive_arg_ts>
MFEM_HOST_DEVICE inline
auto fwddiff_apply_enzyme_indexed(qfunc_t &qfunc, arg_ts &&args,
arg_ts &&shadow_args,
std::index_sequence<Is...>,
inactive_arg_ts &&inactive_args,
std::index_sequence<>)
{
using qf_return_t = typename create_function_signature<
decltype(&qfunc_t::operator())>::type::return_t;
return __enzyme_fwddiff<qf_return_t>(
qfunction_wrapper<qfunc_t, decltype(get<Is>(args))...>, enzyme_const,
(void *)&qfunc, enzyme_dup, &get<Is>(args)..., enzyme_interleave,
&get<Is>(shadow_args)...);
}
// Interleave function arguments for enzyme
template <typename qfunc_t, typename arg_ts, std::size_t... Is,
typename inactive_arg_ts, std::size_t... Js>
MFEM_HOST_DEVICE inline
auto fwddiff_apply_enzyme_indexed(qfunc_t &qfunc, arg_ts &&args,
arg_ts &&shadow_args,
std::index_sequence<Is...>,
inactive_arg_ts &&inactive_args,
std::index_sequence<Js...>)
{
using qf_return_t = typename create_function_signature<
decltype(&qfunc_t::operator())>::type::return_t;
return __enzyme_fwddiff<qf_return_t>(
qfunction_wrapper<qfunc_t, decltype(get<Is>(args))...,
decltype(get<Js>(inactive_args))...>,
enzyme_const, (void *)&qfunc, enzyme_dup, &get<Is>(args)...,
enzyme_const, &get<Js>(inactive_args)..., enzyme_interleave,
&get<Is>(shadow_args)...);
}
template <typename qfunc_t, typename arg_ts, typename inactive_arg_ts>
MFEM_HOST_DEVICE inline
auto fwddiff_apply_enzyme(qfunc_t &qfunc, arg_ts &&args,
arg_ts &&shadow_args,
inactive_arg_ts &&inactive_args)
{
auto arg_indices = std::make_index_sequence<
tuple_size<std::remove_reference_t<arg_ts>>::value> {};
auto inactive_arg_indices = std::make_index_sequence<
tuple_size<std::remove_reference_t<inactive_arg_ts>>::value> {};
return fwddiff_apply_enzyme_indexed(qfunc, args, shadow_args, arg_indices,
inactive_args, inactive_arg_indices);
}
template <typename qfunc_t, typename arg_ts, size_t num_args>
MFEM_HOST_DEVICE inline
void apply_kernel_fwddiff_enzyme(
DeviceTensor<1, real_t> &f_qp,
qfunc_t &qfunc,
arg_ts &args,
arg_ts &shadow_args,
const std::array<DeviceTensor<2>, num_args> &u,
const std::array<DeviceTensor<2>, num_args> &v,
int qp_idx)
{
process_qf_args(u, args, qp_idx);
process_qf_args(v, shadow_args, qp_idx);
process_qf_result(f_qp,
get<0>(fwddiff_apply_enzyme(qfunc, args, shadow_args, tuple<> {})));
}
#endif // MFEM_USE_ENZYME
} // namespace mfem::future
+346
View File
@@ -0,0 +1,346 @@
// 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 "util.hpp"
#include "../../linalg/tensor.hpp"
namespace mfem::future
{
template <typename T0, typename T1, typename T2>
MFEM_HOST_DEVICE
void process_qf_arg(const T0 &, const T1 &, T2 &)
{
static_assert(dfem::always_false<T0, T1, T2>,
"process_qf_arg not implemented for arg type");
}
template <typename T>
MFEM_HOST_DEVICE
void process_qf_arg(
const DeviceTensor<1, T> &u,
const DeviceTensor<1, T> &v,
T &arg)
{
arg = u(0);
}
template <typename T, int n, int m>
MFEM_HOST_DEVICE inline
void process_qf_arg(
const DeviceTensor<1> &u,
tensor<dual<T, T>, n, m> &arg)
{
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n; j++)
{
arg(j, i).value = u((i * m) + j);
}
}
}
template <typename T>
MFEM_HOST_DEVICE inline
void process_qf_arg(
const DeviceTensor<1> &u,
dual<T, T> &arg)
{
arg.value = u(0);
}
template <typename T>
MFEM_HOST_DEVICE inline
void process_qf_arg(
const DeviceTensor<1> &u,
const DeviceTensor<1> &v,
dual<T, T> &arg)
{
arg.value = u(0);
arg.gradient = v(0);
}
template <typename T, int n>
MFEM_HOST_DEVICE inline
void process_qf_arg(
const DeviceTensor<1> &u,
const DeviceTensor<1> &v,
tensor<dual<T, T>, n> &arg)
{
for (int i = 0; i < n; i++)
{
arg(i).value = u(i);
arg(i).gradient = v(i);
}
}
template <typename T, int n, int m>
MFEM_HOST_DEVICE inline
void process_qf_arg(
const DeviceTensor<1> &u,
const DeviceTensor<1> &v,
tensor<dual<T, T>, n, m> &arg)
{
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n; j++)
{
arg(j, i).value = u((i * m) + j);
arg(j, i).gradient = v((i * m) + j);
}
}
}
template <typename T, int n>
MFEM_HOST_DEVICE inline
void process_qf_result(
DeviceTensor<1, T> &r,
const tensor<dual<T, T>, n> &x)
{
for (size_t i = 0; i < n; i++)
{
r(i) = x(i).value;
}
}
template <typename T, int n, int m>
MFEM_HOST_DEVICE inline
void process_qf_result(
DeviceTensor<1, T> &r,
const tensor<dual<T, T>, n, m> &x)
{
for (size_t i = 0; i < n; i++)
{
for (size_t j = 0; j < m; j++)
{
r(i + n * j) = x(i, j).value;
}
}
}
template <typename arg_type>
MFEM_HOST_DEVICE inline
void process_qf_arg(
const DeviceTensor<2> &u,
const DeviceTensor<2> &v,
arg_type &arg,
const int &qp)
{
const auto u_qp = Reshape(&u(0, qp), u.GetShape()[0]);
const auto v_qp = Reshape(&v(0, qp), v.GetShape()[0]);
process_qf_arg(u_qp, v_qp, arg);
}
template <size_t num_fields, typename qf_args>
MFEM_HOST_DEVICE inline
void process_qf_args(
const std::array<DeviceTensor<2>, num_fields> &u,
const std::array<DeviceTensor<2>, num_fields> &v,
qf_args &args,
const int &qp)
{
for_constexpr<tuple_size<qf_args>::value>([&](auto i)
{
process_qf_arg(u[i], v[i], get<i>(args), qp);
});
}
template <typename T, int n, int m>
MFEM_HOST_DEVICE inline
void process_derivative_from_native_dual(
DeviceTensor<1, T> &r,
const tensor<dual<T, T>, n, m> &x)
{
for (size_t i = 0; i < n; i++)
{
for (size_t j = 0; j < m; j++)
{
r(i + n * j) = x(i, j).gradient;
}
}
}
template <typename T, int n>
MFEM_HOST_DEVICE inline
void process_derivative_from_native_dual(
DeviceTensor<1, T> &r,
const tensor<dual<T, T>, n> &x)
{
for (size_t i = 0; i < n; i++)
{
r(i) = x(i).gradient;
}
}
template <typename T>
MFEM_HOST_DEVICE inline
void process_derivative_from_native_dual(
DeviceTensor<1, T> &r,
const dual<T, T> &x)
{
r(0) = x.gradient;
}
template <typename T0, typename T1>
MFEM_HOST_DEVICE inline
void process_qf_arg(const T0 &, T1 &)
{
static_assert(dfem::always_false<T0, T1>,
"process_qf_arg not implemented for arg type");
}
template <typename T>
MFEM_HOST_DEVICE inline
void process_qf_arg(
const DeviceTensor<1, T> &u,
T &arg)
{
arg = u(0);
}
template <typename T>
MFEM_HOST_DEVICE inline
void process_qf_arg(
const DeviceTensor<1, T> &u,
tensor<T> &arg)
{
arg(0) = u(0);
}
template <typename T, int n>
MFEM_HOST_DEVICE inline
void process_qf_arg(
const DeviceTensor<1> &u,
tensor<T, n> &arg)
{
for (int i = 0; i < n; i++)
{
arg(i) = u(i);
}
}
template <typename T, int n, int m>
MFEM_HOST_DEVICE inline
void process_qf_arg(
const DeviceTensor<1> &u,
tensor<T, n, m> &arg)
{
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n; j++)
{
arg(j, i) = u((i * m) + j);
}
}
}
template <typename arg_type>
MFEM_HOST_DEVICE inline
void process_qf_arg(const DeviceTensor<2> &u, arg_type &arg, int qp)
{
const auto u_qp = Reshape(&u(0, qp), u.GetShape()[0]);
process_qf_arg(u_qp, arg);
}
template <size_t num_fields, typename qf_args>
MFEM_HOST_DEVICE inline
void process_qf_args(
const std::array<DeviceTensor<2>, num_fields> &u,
qf_args &args,
const int &qp)
{
for_constexpr<tuple_size<qf_args>::value>([&](auto i)
{
process_qf_arg(u[i], get<i>(args), qp);
});
}
template <typename T0, typename T1>
MFEM_HOST_DEVICE inline
Vector process_qf_result(T0, T1)
{
static_assert(dfem::always_false<T0, T1>,
"process_qf_result not implemented for result type");
return Vector{};
}
template <typename T>
MFEM_HOST_DEVICE inline
void process_qf_result(
DeviceTensor<1, T> &r,
const T &x)
{
r(0) = x;
}
template <typename T>
MFEM_HOST_DEVICE inline
void process_qf_result(
DeviceTensor<1> &r,
const dual<T, T> &x)
{
r(0) = x.value;
}
template <typename T>
MFEM_HOST_DEVICE inline
void process_qf_result(
DeviceTensor<1, T> &r,
const tensor<T> &x)
{
r(0) = x(0);
}
template <typename T, int n>
MFEM_HOST_DEVICE inline
void process_qf_result(
DeviceTensor<1, T> &r,
const tensor<T, n> &x)
{
for (size_t i = 0; i < n; i++)
{
r(i) = x(i);
}
}
template <typename T, int n, int m>
MFEM_HOST_DEVICE inline
void process_qf_result(
DeviceTensor<1, T> &r,
const tensor<T, n, m> &x)
{
for (size_t i = 0; i < n; i++)
{
for (size_t j = 0; j < m; j++)
{
r(i + n * j) = x(i, j);
}
}
}
template <typename T, int n, int m>
MFEM_HOST_DEVICE inline
void process_qf_arg(
const DeviceTensor<1, T> &u,
const DeviceTensor<1, T> &v,
tensor<T, n, m> &arg)
{
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n; j++)
{
arg(j, i) = u((i * m) + j);
}
}
}
} // namespace mfem::future
+885
View File
@@ -0,0 +1,885 @@
// 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
// This is serac's tuple implementation
#include <ostream>
#include "../../config/config.hpp"
#include <utility>
// Define a portable unreachable macro
#if defined(__GNUC__) || defined(__clang__)
#if defined(__CUDACC_VER_MAJOR__)
#if __CUDACC_VER_MAJOR__ <= 11 && __CUDACC_VER_MINOR__ < 3
// nvcc didn't add __builtin_unreachable() until cuda 11.3
#define MFEM_UNREACHABLE()
#else
// nvcc >= 11.3
#define MFEM_UNREACHABLE() __builtin_unreachable()
#endif
#else
// host-only version
#define MFEM_UNREACHABLE() __builtin_unreachable()
#endif
#elif defined(_MSC_VER)
#define MFEM_UNREACHABLE() __assume(0)
#endif
namespace mfem::future
{
/**
* @tparam T the types stored in the tuple
* @brief This is a class that mimics most of std::tuple's interface,
* except that it is usable in CUDA kernels and admits some arithmetic operator overloads.
*
* see https://en.cppreference.com/w/cpp/utility/tuple for more information about std::tuple
*/
template <typename... T>
struct tuple
{
};
/**
* @brief Type that mimics std::tuple
*
* @tparam T0 The first type stored in the tuple
*/
template <typename T0>
struct tuple<T0>
{
T0 v0; ///< The first member of the tuple
};
/**
* @brief Type that mimics std::tuple
*
* @tparam T0 The first type stored in the tuple
* @tparam T1 The second type stored in the tuple
*/
template <typename T0, typename T1>
struct tuple<T0, T1>
{
T0 v0; ///< The first member of the tuple
T1 v1; ///< The second member of the tuple
};
/**
* @brief Type that mimics std::tuple
*
* @tparam T0 The first type stored in the tuple
* @tparam T1 The second type stored in the tuple
* @tparam T2 The third type stored in the tuple
*/
template <typename T0, typename T1, typename T2>
struct tuple<T0, T1, T2>
{
T0 v0; ///< The first member of the tuple
T1 v1; ///< The second member of the tuple
T2 v2; ///< The third member of the tuple
};
/**
* @brief Type that mimics std::tuple
*
* @tparam T0 The first type stored in the tuple
* @tparam T1 The second type stored in the tuple
* @tparam T2 The third type stored in the tuple
* @tparam T3 The fourth type stored in the tuple
*/
template <typename T0, typename T1, typename T2, typename T3>
struct tuple<T0, T1, T2, T3>
{
T0 v0; ///< The first member of the tuple
T1 v1; ///< The second member of the tuple
T2 v2; ///< The third member of the tuple
T3 v3; ///< The fourth member of the tuple
};
/**
* @brief Type that mimics std::tuple
*
* @tparam T0 The first type stored in the tuple
* @tparam T1 The second type stored in the tuple
* @tparam T2 The third type stored in the tuple
* @tparam T3 The fourth type stored in the tuple
* @tparam T4 The fifth type stored in the tuple
*/
template <typename T0, typename T1, typename T2, typename T3, typename T4>
struct tuple<T0, T1, T2, T3, T4>
{
T0 v0; ///< The first member of the tuple
T1 v1; ///< The second member of the tuple
T2 v2; ///< The third member of the tuple
T3 v3; ///< The fourth member of the tuple
T4 v4; ///< The fifth member of the tuple
};
/**
* @brief Type that mimics std::tuple
*
* @tparam T0 The first type stored in the tuple
* @tparam T1 The second type stored in the tuple
* @tparam T2 The third type stored in the tuple
* @tparam T3 The fourth type stored in the tuple
* @tparam T4 The fifth type stored in the tuple
* @tparam T5 The sixth type stored in the tuple
*/
template <typename T0, typename T1, typename T2, typename T3, typename T4, typename T5>
struct tuple<T0, T1, T2, T3, T4, T5>
{
T0 v0; ///< The first member of the tuple
T1 v1; ///< The second member of the tuple
T2 v2; ///< The third member of the tuple
T3 v3; ///< The fourth member of the tuple
T4 v4; ///< The fifth member of the tuple
T5 v5; ///< The sixth member of the tuple
};
/**
* @brief Type that mimics std::tuple
*
* @tparam T0 The first type stored in the tuple
* @tparam T1 The second type stored in the tuple
* @tparam T2 The third type stored in the tuple
* @tparam T3 The fourth type stored in the tuple
* @tparam T4 The fifth type stored in the tuple
* @tparam T5 The sixth type stored in the tuple
* @tparam T6 The seventh type stored in the tuple
*/
template <typename T0, typename T1, typename T2, typename T3, typename T4, typename T5, typename T6>
struct tuple<T0, T1, T2, T3, T4, T5, T6>
{
T0 v0; ///< The first member of the tuple
T1 v1; ///< The second member of the tuple
T2 v2; ///< The third member of the tuple
T3 v3; ///< The fourth member of the tuple
T4 v4; ///< The fifth member of the tuple
T5 v5; ///< The sixth member of the tuple
T6 v6; ///< The seventh member of the tuple
};
/**
* @brief Type that mimics std::tuple
*
* @tparam T0 The first type stored in the tuple
* @tparam T1 The second type stored in the tuple
* @tparam T2 The third type stored in the tuple
* @tparam T3 The fourth type stored in the tuple
* @tparam T4 The fifth type stored in the tuple
* @tparam T5 The sixth type stored in the tuple
* @tparam T6 The seventh type stored in the tuple
* @tparam T7 The eighth type stored in the tuple
*/
template <typename T0, typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7>
struct tuple<T0, T1, T2, T3, T4, T5, T6, T7>
{
T0 v0; ///< The first member of the tuple
T1 v1; ///< The second member of the tuple
T2 v2; ///< The third member of the tuple
T3 v3; ///< The fourth member of the tuple
T4 v4; ///< The fifth member of the tuple
T5 v5; ///< The sixth member of the tuple
T6 v6; ///< The seventh member of the tuple
T7 v7; ///< The eighth member of the tuple
};
template <typename T0, typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7, typename T8>
struct tuple<T0, T1, T2, T3, T4, T5, T6, T7, T8>
{
T0 v0; ///< The first member of the tuple
T1 v1; ///< The second member of the tuple
T2 v2; ///< The third member of the tuple
T3 v3; ///< The fourth member of the tuple
T4 v4; ///< The fifth member of the tuple
T5 v5; ///< The sixth member of the tuple
T6 v6; ///< The seventh member of the tuple
T7 v7; ///< The eighth member of the tuple
T8 v8;
};
/**
* @brief Class template argument deduction rule for tuples
* @tparam T The variadic template parameter for tuple types
*/
template <typename... T>
MFEM_HOST_DEVICE
tuple(T...) -> tuple<T...>;
/**
* @brief helper function for combining a list of values into a tuple
* @tparam T types of the values to be tuple-d
* @param args the actual values to be put into a tuple
*/
template <typename... T>
MFEM_HOST_DEVICE tuple<T...> make_tuple(const T&... args)
{
return tuple<T...> {args...};
}
template <class... Types>
struct tuple_size
{
};
template <class... Types>
struct tuple_size<tuple<Types...>> :
std::integral_constant<std::size_t, sizeof...(Types)>
{
};
/**
* @tparam i the tuple index to access
* @tparam T the types stored in the tuple
* @brief return a reference to the ith tuple entry
*/
template <int i, typename... T>
MFEM_HOST_DEVICE constexpr auto& get(tuple<T...>& values)
{
static_assert(i < sizeof...(T));
if constexpr (i == 0)
{
return values.v0;
}
if constexpr (i == 1)
{
return values.v1;
}
if constexpr (i == 2)
{
return values.v2;
}
if constexpr (i == 3)
{
return values.v3;
}
if constexpr (i == 4)
{
return values.v4;
}
if constexpr (i == 5)
{
return values.v5;
}
if constexpr (i == 6)
{
return values.v6;
}
if constexpr (i == 7)
{
return values.v7;
}
if constexpr (i == 8)
{
return values.v8;
}
MFEM_UNREACHABLE();
}
/**
* @tparam i the tuple index to access
* @tparam T the types stored in the tuple
* @brief return a copy of the ith tuple entry
*/
template <int i, typename... T>
MFEM_HOST_DEVICE constexpr const auto& get(const tuple<T...>& values)
{
static_assert(i < sizeof...(T));
if constexpr (i == 0)
{
return values.v0;
}
if constexpr (i == 1)
{
return values.v1;
}
if constexpr (i == 2)
{
return values.v2;
}
if constexpr (i == 3)
{
return values.v3;
}
if constexpr (i == 4)
{
return values.v4;
}
if constexpr (i == 5)
{
return values.v5;
}
if constexpr (i == 6)
{
return values.v6;
}
if constexpr (i == 7)
{
return values.v7;
}
if constexpr (i == 8)
{
return values.v8;
}
MFEM_UNREACHABLE();
}
/**
* @brief a function intended to be used for extracting the ith type from a tuple.
*
* @note type<i>(my_tuple) returns a value, whereas get<i>(my_tuple) returns a reference
*
* @tparam i the index of the tuple to query
* @tparam T the types stored in the tuple
* @param values the tuple of values
* @return a copy of the ith entry of the input
*/
template <int i, typename... T>
MFEM_HOST_DEVICE constexpr auto type(const tuple<T...>& values)
{
static_assert(i < sizeof...(T));
if constexpr (i == 0)
{
return values.v0;
}
if constexpr (i == 1)
{
return values.v1;
}
if constexpr (i == 2)
{
return values.v2;
}
if constexpr (i == 3)
{
return values.v3;
}
if constexpr (i == 4)
{
return values.v4;
}
if constexpr (i == 5)
{
return values.v5;
}
if constexpr (i == 6)
{
return values.v6;
}
if constexpr (i == 7)
{
return values.v7;
}
if constexpr (i == 8)
{
return values.v8;
}
}
/**
* @brief A helper function for the + operator of tuples
*
* @tparam S the types stored in the tuple x
* @tparam T the types stored in the tuple y
* @tparam i The integer sequence to i
* @param x tuple of values
* @param y tuple of values
* @return the returned tuple sum
*/
template <typename... S, typename... T, int... i>
MFEM_HOST_DEVICE constexpr auto plus_helper(const tuple<S...>& x,
const tuple<T...>& y,
std::integer_sequence<int, i...>)
{
return tuple{get<i>(x) + get<i>(y)...};
}
/**
* @tparam S the types stored in the tuple x
* @tparam T the types stored in the tuple y
* @param x a tuple of values
* @param y a tuple of values
* @brief return a tuple of values defined by elementwise sum of x and y
*/
template <typename... S, typename... T>
MFEM_HOST_DEVICE constexpr auto operator+(const tuple<S...>& x,
const tuple<T...>& y)
{
static_assert(sizeof...(S) == sizeof...(T));
return plus_helper(x, y,
std::make_integer_sequence<int, static_cast<int>(sizeof...(S))>());
}
/**
* @brief A helper function for the += operator of tuples
*
* @tparam T the types stored in the tuples x and y
* @tparam i integer sequence used to index the tuples
* @param x tuple of values to be incremented
* @param y tuple of increment values
*/
template <typename... T, int... i>
MFEM_HOST_DEVICE constexpr void plus_equals_helper(tuple<T...>& x,
const tuple<T...>& y,
std::integer_sequence<int, i...>)
{
((get<i>(x) += get<i>(y)), ...);
}
/**
* @tparam T the types stored in the tuples x and y
* @param x a tuple of values
* @param y a tuple of values
* @brief add values contained in y, to the tuple x
*/
template <typename... T>
MFEM_HOST_DEVICE constexpr auto operator+=(tuple<T...>& x,
const tuple<T...>& y)
{
return plus_equals_helper(x, y,
std::make_integer_sequence<int, static_cast<int>(sizeof...(T))>());
}
/**
* @brief A helper function for the -= operator of tuples
*
* @tparam T the types stored in the tuples x and y
* @tparam i integer sequence used to index the tuples
* @param x tuple of values to be subracted from
* @param y tuple of values to subtract from x
*/
template <typename... T, int... i>
MFEM_HOST_DEVICE constexpr void minus_equals_helper(tuple<T...>& x,
const tuple<T...>& y,
std::integer_sequence<int, i...>)
{
((get<i>(x) -= get<i>(y)), ...);
}
/**
* @tparam T the types stored in the tuples x and y
* @param x a tuple of values
* @param y a tuple of values
* @brief add values contained in y, to the tuple x
*/
template <typename... T>
MFEM_HOST_DEVICE constexpr auto operator-=(tuple<T...>& x,
const tuple<T...>& y)
{
return minus_equals_helper(x, y,
std::make_integer_sequence<int, static_cast<int>(sizeof...(T))>());
}
/**
* @brief A helper function for the - operator of tuples
*
* @tparam S the types stored in the tuple x
* @tparam T the types stored in the tuple y
* @tparam i The integer sequence to i
* @param x tuple of values
* @param y tuple of values
* @return the returned tuple difference
*/
template <typename... S, typename... T, int... i>
MFEM_HOST_DEVICE constexpr auto minus_helper(const tuple<S...>& x,
const tuple<T...>& y,
std::integer_sequence<int, i...>)
{
return tuple{get<i>(x) - get<i>(y)...};
}
/**
* @tparam S the types stored in the tuple x
* @tparam T the types stored in the tuple y
* @param x a tuple of values
* @param y a tuple of values
* @brief return a tuple of values defined by elementwise difference of x and y
*/
template <typename... S, typename... T>
MFEM_HOST_DEVICE constexpr auto operator-(const tuple<S...>& x,
const tuple<T...>& y)
{
static_assert(sizeof...(S) == sizeof...(T));
return minus_helper(x, y,
std::make_integer_sequence<int, static_cast<int>(sizeof...(S))>());
}
/**
* @brief A helper function for the - operator of tuples
*
* @tparam T the types stored in the tuple y
* @tparam i The integer sequence to i
* @param x tuple of values
* @return the returned tuple difference
*/
template <typename... T, int... i>
MFEM_HOST_DEVICE constexpr auto unary_minus_helper(const tuple<T...>& x,
std::integer_sequence<int, i...>)
{
return tuple{-get<i>(x)...};
}
/**
* @tparam T the types stored in the tuple y
* @param x a tuple of values
* @brief return a tuple of values defined by applying the unary minus operator to each element of x
*/
template <typename... T>
MFEM_HOST_DEVICE constexpr auto operator-(const tuple<T...>& x)
{
return unary_minus_helper(x,
std::make_integer_sequence<int, static_cast<int>(sizeof...(T))>());
}
/**
* @brief A helper function for the / operator of tuples
*
* @tparam S the types stored in the tuple x
* @tparam T the types stored in the tuple y
* @tparam i The integer sequence to i
* @param x tuple of values
* @param y tuple of values
* @return the returned tuple ratio
*/
template <typename... S, typename... T, int... i>
MFEM_HOST_DEVICE constexpr auto div_helper(const tuple<S...>& x,
const tuple<T...>& y,
std::integer_sequence<int, i...>)
{
return tuple{get<i>(x) / get<i>(y)...};
}
/**
* @tparam S the types stored in the tuple x
* @tparam T the types stored in the tuple y
* @param x a tuple of values
* @param y a tuple of values
* @brief return a tuple of values defined by elementwise division of x by y
*/
template <typename... S, typename... T>
MFEM_HOST_DEVICE constexpr auto operator/(const tuple<S...>& x,
const tuple<T...>& y)
{
static_assert(sizeof...(S) == sizeof...(T));
return div_helper(x, y,
std::make_integer_sequence<int, static_cast<int>(sizeof...(S))>());
}
/**
* @brief A helper function for the / operator of tuples
*
* @tparam T the types stored in the tuple y
* @tparam i The integer sequence to i
* @param x tuple of values
* @param a the constant numerator
* @return the returned tuple ratio
*/
template <typename... T, int... i>
MFEM_HOST_DEVICE constexpr auto div_helper(const real_t a,
const tuple<T...>& x, std::integer_sequence<int, i...>)
{
return tuple{a / get<i>(x)...};
}
/**
* @brief A helper function for the / operator of tuples
*
* @tparam T the types stored in the tuple y
* @tparam i The integer sequence to i
* @param x tuple of values
* @param a the constant denomenator
* @return the returned tuple ratio
*/
template <typename... T, int... i>
MFEM_HOST_DEVICE constexpr auto div_helper(const tuple<T...>& x,
const real_t a, std::integer_sequence<int, i...>)
{
return tuple{get<i>(x) / a...};
}
/**
* @tparam T the types stored in the tuple x
* @param a the numerator
* @param x a tuple of denominator values
* @brief return a tuple of values defined by division of a by the elements of x
*/
template <typename... T>
MFEM_HOST_DEVICE constexpr auto operator/(const real_t a, const tuple<T...>& x)
{
return div_helper(a, x,
std::make_integer_sequence<int, static_cast<int>(sizeof...(T))>());
}
/**
* @tparam T the types stored in the tuple y
* @param x a tuple of numerator values
* @param a a denominator
* @brief return a tuple of values defined by elementwise division of x by a
*/
template <typename... T>
MFEM_HOST_DEVICE constexpr auto operator/(const tuple<T...>& x, const real_t a)
{
return div_helper(x, a,
std::make_integer_sequence<int, static_cast<int>(sizeof...(T))>());
}
/**
* @brief A helper function for the * operator of tuples
*
* @tparam S the types stored in the tuple x
* @tparam T the types stored in the tuple y
* @tparam i The integer sequence to i
* @param x tuple of values
* @param y tuple of values
* @return the returned tuple product
*/
template <typename... S, typename... T, int... i>
MFEM_HOST_DEVICE constexpr auto mult_helper(const tuple<S...>& x,
const tuple<T...>& y,
std::integer_sequence<int, i...>)
{
return tuple{get<i>(x) * get<i>(y)...};
}
/**
* @tparam S the types stored in the tuple x
* @tparam T the types stored in the tuple y
* @param x a tuple of values
* @param y a tuple of values
* @brief return a tuple of values defined by elementwise multiplication of x and y
*/
template <typename... S, typename... T>
MFEM_HOST_DEVICE constexpr auto operator*(const tuple<S...>& x,
const tuple<T...>& y)
{
static_assert(sizeof...(S) == sizeof...(T));
return mult_helper(x, y,
std::make_integer_sequence<int, static_cast<int>(sizeof...(S))>());
}
/**
* @brief A helper function for the * operator of tuples
*
* @tparam T the types stored in the tuple y
* @tparam i The integer sequence to i
* @param x tuple of values
* @param a a constant multiplier
* @return the returned tuple product
*/
template <typename... T, int... i>
MFEM_HOST_DEVICE constexpr auto mult_helper(const real_t a,
const tuple<T...>& x, std::integer_sequence<int, i...>)
{
return tuple{a * get<i>(x)...};
}
/**
* @brief A helper function for the * operator of tuples
*
* @tparam T the types stored in the tuple y
* @tparam i The integer sequence to i
* @param x tuple of values
* @param a a constant multiplier
* @return the returned tuple product
*/
template <typename... T, int... i>
MFEM_HOST_DEVICE constexpr auto mult_helper(const tuple<T...>& x,
const real_t a, std::integer_sequence<int, i...>)
{
return tuple{get<i>(x) * a...};
}
/**
* @tparam T the types stored in the tuple
* @param a a scaling factor
* @param x the tuple object
* @brief multiply each component of x by the value a on the left
*/
template <typename... T>
MFEM_HOST_DEVICE constexpr auto operator*(const real_t a, const tuple<T...>& x)
{
return mult_helper(a, x,
std::make_integer_sequence<int, static_cast<int>(sizeof...(T))>());
}
/**
* @tparam T the types stored in the tuple
* @param x the tuple object
* @param a a scaling factor
* @brief multiply each component of x by the value a on the right
*/
template <typename... T>
MFEM_HOST_DEVICE constexpr auto operator*(const tuple<T...>& x, const real_t a)
{
return mult_helper(x, a,
std::make_integer_sequence<int, static_cast<int>(sizeof...(T))>());
}
/**
* @tparam T the types stored in the tuple
* @tparam i a list of indices used to acces each element of the tuple
* @param out the ostream to write the output to
* @param A the tuple of values
* @brief helper used to implement printing a tuple of values
*/
template <typename... T, std::size_t... i>
auto& print_helper(std::ostream& out, const tuple<T...>& A,
std::integer_sequence<size_t, i...>)
{
out << "tuple{";
(..., (out << (i == 0 ? "" : ", ") << get<i>(A)));
out << "}";
return out;
}
/**
* @tparam T the types stored in the tuple
* @param out the ostream to write the output to
* @param A the tuple of values
* @brief print a tuple of values
*/
template <typename... T>
auto& operator<<(std::ostream& out, const tuple<T...>& A)
{
return print_helper(out, A, std::make_integer_sequence<size_t, sizeof...(T)>());
}
/**
* @brief A helper to apply a lambda to a tuple
*
* @tparam lambda The functor type
* @tparam T The tuple types
* @tparam i The integer sequence to i
* @param f The functor to apply to the tuple
* @param args The input tuple
* @return The functor output
*/
template <typename lambda, typename... T, int... i>
MFEM_HOST_DEVICE auto apply_helper(lambda f, tuple<T...>& args,
std::integer_sequence<int, i...>)
{
return f(get<i>(args)...);
}
/**
* @tparam lambda a callable type
* @tparam T the types of arguments to be passed in to f
* @param f the callable object
* @param args a tuple of arguments
* @brief a way of passing an n-tuple to a function that expects n separate arguments
*
* e.g. foo(bar, baz) is equivalent to apply(foo, mfem::tuple(bar,baz));
*/
template <typename lambda, typename... T>
MFEM_HOST_DEVICE auto apply(lambda f, tuple<T...>& args)
{
return apply_helper(f, std::move(args),
std::make_integer_sequence<int, static_cast<int>(sizeof...(T))>());
}
/**
* @overload
*/
template <typename lambda, typename... T, int... i>
MFEM_HOST_DEVICE auto apply_helper(lambda f, const tuple<T...>& args,
std::integer_sequence<int, i...>)
{
return f(get<i>(args)...);
}
/**
* @tparam lambda a callable type
* @tparam T the types of arguments to be passed in to f
* @param f the callable object
* @param args a tuple of arguments
* @brief a way of passing an n-tuple to a function that expects n separate arguments
*
* e.g. foo(bar, baz) is equivalent to apply(foo, mfem::tuple(bar,baz));
*/
template <typename lambda, typename... T>
MFEM_HOST_DEVICE auto apply(lambda f, const tuple<T...>& args)
{
return apply_helper(f, std::move(args),
std::make_integer_sequence<int, static_cast<int>(sizeof...(T))>());
}
/**
* @brief a struct used to determine the type at index I of a tuple
*
* @note see: https://en.cppreference.com/w/cpp/utility/tuple/tuple_element
*
* @tparam I the index of the desired type
* @tparam T a tuple of different types
*/
template <size_t I, class T>
struct tuple_element;
// recursive case
/// @overload
template <size_t I, class Head, class... Tail>
struct tuple_element<I, tuple<Head, Tail...>> : tuple_element<I - 1,
tuple<Tail...>>
{
};
// base case
/// @overload
template <class Head, class... Tail>
struct tuple_element<0, tuple<Head, Tail...>>
{
using type = Head; ///< the type at the specified index
};
/**
* @brief Trait for checking if a type is a @p mfem::tuple
*/
template <typename T>
struct is_tuple : std::false_type
{
};
/// @overload
template <typename... T>
struct is_tuple<tuple<T...>> : std::true_type
{
};
/**
* @brief Trait for checking if a type if a @p mfem::tuple containing only @p mfem::tuple
*/
template <typename T>
struct is_tuple_of_tuples : std::false_type
{
};
/**
* @brief Trait for checking if a type if a @p mfem::tuple containing only @p mfem::tuple
*/
template <typename... T>
struct is_tuple_of_tuples<tuple<T...>>
{
static constexpr bool value = (is_tuple<T>::value &&
...); ///< true/false result of type check
};
/** @brief Auxiliary template function that merges (concatenates) two
mfem::future::tuple types into a single std::tuple that is empty, i.e. it is
value initialized. */
template <typename... T1s, typename... T2s>
constexpr auto merge_mfem_tuples_as_empty_std_tuple(
const mfem::future::tuple<T1s...> &,
const mfem::future::tuple<T2s...> &)
{
return std::tuple<T1s..., T2s...> {};
}
} // namespace mfem::future
+2298
View File
File diff suppressed because it is too large Load Diff
+16 -24
View File
@@ -16,9 +16,7 @@ namespace mfem
void DofTransformation::TransformPrimal(real_t *v) const
{
MFEM_ASSERT(dof_trans_,
"DofTransformation has no local transformation, call "
"SetDofTransformation first!");
if (IsIdentity()) { return; }
int size = dof_trans_->Size();
if (vdim_ == 1 || (Ordering::Type)ordering_ == Ordering::byNODES)
@@ -48,9 +46,7 @@ void DofTransformation::TransformPrimal(real_t *v) const
void DofTransformation::InvTransformPrimal(real_t *v) const
{
MFEM_ASSERT(dof_trans_,
"DofTransformation has no local transformation, call "
"SetDofTransformation first!");
if (IsIdentity()) { return; }
int size = dof_trans_->Height();
if (vdim_ == 1 || (Ordering::Type)ordering_ == Ordering::byNODES)
@@ -80,9 +76,7 @@ void DofTransformation::InvTransformPrimal(real_t *v) const
void DofTransformation::TransformDual(real_t *v) const
{
MFEM_ASSERT(dof_trans_,
"DofTransformation has no local transformation, call "
"SetDofTransformation first!");
if (IsIdentity()) { return; }
int size = dof_trans_->Size();
if (vdim_ == 1 || (Ordering::Type)ordering_ == Ordering::byNODES)
@@ -112,9 +106,7 @@ void DofTransformation::TransformDual(real_t *v) const
void DofTransformation::InvTransformDual(real_t *v) const
{
MFEM_ASSERT(dof_trans_,
"DofTransformation has no local transformation, call "
"SetDofTransformation first!");
if (IsIdentity()) { return; }
int size = dof_trans_->Size();
if (vdim_ == 1 || (Ordering::Type)ordering_ == Ordering::byNODES)
@@ -142,33 +134,33 @@ void DofTransformation::InvTransformDual(real_t *v) const
}
}
void TransformPrimal(const DofTransformation *ran_dof_trans,
const DofTransformation *dom_dof_trans,
void TransformPrimal(const DofTransformation &ran_dof_trans,
const DofTransformation &dom_dof_trans,
DenseMatrix &elmat)
{
// No action if both transformations are NULL
if (ran_dof_trans)
if (!ran_dof_trans.IsIdentity())
{
ran_dof_trans->TransformPrimalCols(elmat);
ran_dof_trans.TransformPrimalCols(elmat);
}
if (dom_dof_trans)
if (!dom_dof_trans.IsIdentity())
{
dom_dof_trans->TransformDualRows(elmat);
dom_dof_trans.TransformDualRows(elmat);
}
}
void TransformDual(const DofTransformation *ran_dof_trans,
const DofTransformation *dom_dof_trans,
void TransformDual(const DofTransformation &ran_dof_trans,
const DofTransformation &dom_dof_trans,
DenseMatrix &elmat)
{
// No action if both transformations are NULL
if (ran_dof_trans)
if (!ran_dof_trans.IsIdentity())
{
ran_dof_trans->TransformDualCols(elmat);
ran_dof_trans.TransformDualCols(elmat);
}
if (dom_dof_trans)
if (!dom_dof_trans.IsIdentity())
{
dom_dof_trans->TransformDualRows(elmat);
dom_dof_trans.TransformDualRows(elmat);
}
}
+9 -7
View File
@@ -201,19 +201,19 @@ public:
inline int NumRows() const { return dof_trans_->NumRows(); }
inline int Width() const { return dof_trans_->Width(); }
inline int NumCols() const { return dof_trans_->NumCols(); }
inline bool IsIdentity() const { return dof_trans_->IsIdentity(); }
inline bool IsIdentity() const { return !dof_trans_ || dof_trans_->IsIdentity(); }
/** Transform local DoFs to align with the global DoFs. For example, this
transformation can be used to map the local vector computed by
FiniteElement::Project() to the transformed vector stored within a
GridFunction object. */
void TransformPrimal(real_t *v) const;
inline void TransformPrimal(Vector &v) const
{ TransformPrimal(v.GetData()); }
inline void TransformPrimal(Vector &v) const { TransformPrimal(v.GetData()); }
/// Transform groups of DoFs stored as dense matrices
inline void TransformPrimalCols(DenseMatrix &V) const
{
if (IsIdentity()) { return; }
for (int c=0; c<V.Width(); c++)
{
TransformPrimal(V.GetColumn(c));
@@ -251,6 +251,7 @@ public:
/// Transform rows of a dense matrix containing dual DoFs
inline void TransformDualRows(DenseMatrix &V) const
{
if (IsIdentity()) { return; }
Vector row;
for (int r=0; r<V.Height(); r++)
{
@@ -263,6 +264,7 @@ public:
/// Transform columns of a dense matrix containing dual DoFs
inline void TransformDualCols(DenseMatrix &V) const
{
if (IsIdentity()) { return; }
for (int c=0; c<V.Width(); c++)
{
TransformDual(V.GetColumn(c));
@@ -274,16 +276,16 @@ public:
computed by a DiscreteInterpolator before copying into a
DiscreteLinearOperator.
*/
void TransformPrimal(const DofTransformation *ran_dof_trans,
const DofTransformation *dom_dof_trans,
void TransformPrimal(const DofTransformation &ran_dof_trans,
const DofTransformation &dom_dof_trans,
DenseMatrix &elmat);
/** Transform a matrix of dual DoFs entries from different finite element spaces
as computed by a BilinearFormIntegrator before summing into a
MixedBilinearForm object.
*/
void TransformDual(const DofTransformation *ran_dof_trans,
const DofTransformation *dom_dof_trans,
void TransformDual(const DofTransformation &ran_dof_trans,
const DofTransformation &dom_dof_trans,
DenseMatrix &elmat);
/** Abstract base class for high-order Nedelec spaces on elements with
+30 -24
View File
@@ -1891,31 +1891,38 @@ L2Pos_PyramidElement::L2Pos_PyramidElement(const int p)
Index idx;
// interior
for (int o = 0, k = 0; k <= p; k++)
for (int j = 0; j + k <= p; j++)
{
int i1 = p - j - k;
int i2 = 0;
int i3 = -1;
int i4 = j + 1;
const int i5 = k;
if (p == 0)
{
dof_map[idx(0,0,0,0,0)] = 0;
Nodes.IntPoint(0).Set3(0.375, 0.375, 0.25);
}
else
{
for (int o = 0, k = 0; k <= p; k++)
for (int j = 0; j + k <= p; j++)
{
int i1 = p - j - k;
int i2 = 0;
int i3 = -1;
int i4 = j + 1;
const int i5 = k;
for (int i = 0; i <= j; i++)
{
i3++;
i4--;
dof_map[idx(i1,i2,i3,i4,i5)] = o;
Nodes.IntPoint(o++).Set3(real_t(i)/p, real_t(j)/p, 0);
for (int i = 0; i <= j; i++)
{
i3++;
i4--;
dof_map[idx(i1,i2,i3,i4,i5)] = o;
Nodes.IntPoint(o++).Set3(real_t(i)/p, real_t(j)/p, 0);
}
for (int i = j + 1; i + k <= p; i++)
{
i1--;
i2++;
dof_map[idx(i1,i2,i3,i4,i5)] = o;
Nodes.IntPoint(o++).Set3(real_t(i)/p, real_t(j)/p, 0);
}
}
for (int i = j + 1; i + k <= p; i++)
{
i1--;
i2++;
dof_map[idx(i1,i2,i3,i4,i5)] = o;
Nodes.IntPoint(o++).Set3(real_t(i)/p, real_t(j)/p, 0);
}
}
}
}
// static method
@@ -2197,7 +2204,6 @@ void L2Pos_PyramidElement::CalcDShape(const IntegrationPoint &ip,
{
dshape(it.second, d) = m_dshape(it.first, d);
}
}
}
+2
View File
@@ -50,6 +50,8 @@
#include "dgmassinv.hpp"
#include "hyperbolic.hpp"
#include "dfem/doperator.hpp"
#ifdef MFEM_USE_MPI
#include "pfespace.hpp"
#include "pgridfunc.hpp"
+16 -14
View File
@@ -331,7 +331,6 @@ void FiniteElementSpace::GetElementVDofs(int i, Array<int> &vdofs,
DofTransformation *
FiniteElementSpace::GetElementVDofs(int i, Array<int> &vdofs) const
{
DoFTrans.SetDofTransformation(NULL);
GetElementVDofs(i, vdofs, DoFTrans);
return DoFTrans.GetDofTransformation() ? &DoFTrans : NULL;
}
@@ -347,7 +346,6 @@ void FiniteElementSpace::GetBdrElementVDofs(int i, Array<int> &vdofs,
DofTransformation *
FiniteElementSpace::GetBdrElementVDofs(int i, Array<int> &vdofs) const
{
DoFTrans.SetDofTransformation(NULL);
GetBdrElementVDofs(i, vdofs, DoFTrans);
return DoFTrans.GetDofTransformation() ? &DoFTrans : NULL;
}
@@ -1936,6 +1934,7 @@ void FiniteElementSpace::RefinementOperator::Mult(const Vector &x,
DenseMatrix eP;
IsoparametricTransformation isotr;
DofTransformation doftrans;
for (int k = 0; k < mesh_ref->GetNE(); k++)
{
@@ -1956,10 +1955,10 @@ void FiniteElementSpace::RefinementOperator::Mult(const Vector &x,
subY.SetSize(lP.Height());
DofTransformation *doftrans = fespace->GetElementDofs(k, dofs);
fespace->GetElementDofs(k, dofs, doftrans);
old_elem_dof->GetRow(emb.parent, old_dofs);
if (!doftrans)
if (doftrans.IsIdentity())
{
for (int vd = 0; vd < rvdim; vd++)
{
@@ -1979,7 +1978,7 @@ void FiniteElementSpace::RefinementOperator::Mult(const Vector &x,
old_DoFTrans.SetDofTransformation(*old_DoFTransArray[geom]);
old_DoFTrans.SetFaceOrientations(old_Fo);
doftrans->SetVDim();
doftrans.SetVDim();
for (int vd = 0; vd < rvdim; vd++)
{
dofs.Copy(vdofs);
@@ -1990,10 +1989,10 @@ void FiniteElementSpace::RefinementOperator::Mult(const Vector &x,
x.GetSubVector(old_vdofs, subX);
old_DoFTrans.InvTransformPrimal(subX);
lP.Mult(subX, subY);
doftrans->TransformPrimal(subY);
doftrans.TransformPrimal(subY);
y.SetSubVector(vdofs, subY);
}
doftrans->SetVDim(rvdim, fespace->GetOrdering());
doftrans.SetVDim(rvdim, fespace->GetOrdering());
}
}
}
@@ -2020,6 +2019,7 @@ void FiniteElementSpace::RefinementOperator::MultTranspose(const Vector &x,
DenseMatrix eP;
IsoparametricTransformation isotr;
const FiniteElement *fe = nullptr;
DofTransformation doftrans;
for (int k = 0; k < mesh_ref->GetNE(); k++)
{
@@ -2040,10 +2040,10 @@ void FiniteElementSpace::RefinementOperator::MultTranspose(const Vector &x,
const DenseMatrix &lP = (fespace->IsVariableOrder()) ? eP : localP[geom](
emb.matrix);
DofTransformation *doftrans = fespace->GetElementDofs(k, f_dofs);
fespace->GetElementDofs(k, f_dofs, doftrans);
old_elem_dof->GetRow(emb.parent, c_dofs);
if (!doftrans)
if (doftrans.IsIdentity())
{
subY.SetSize(lP.Width());
@@ -2074,7 +2074,7 @@ void FiniteElementSpace::RefinementOperator::MultTranspose(const Vector &x,
old_DoFTrans.SetDofTransformation(*old_DoFTransArray[geom]);
old_DoFTrans.SetFaceOrientations(old_Fo);
doftrans->SetVDim();
doftrans.SetVDim();
for (int vd = 0; vd < rvdim; vd++)
{
f_dofs.Copy(f_vdofs);
@@ -2083,7 +2083,7 @@ void FiniteElementSpace::RefinementOperator::MultTranspose(const Vector &x,
fespace->DofsToVDofs(vd, c_vdofs, old_ndofs);
x.GetSubVector(f_vdofs, subX);
doftrans->InvTransformDual(subX);
doftrans.InvTransformDual(subX);
for (int p = 0; p < f_dofs.Size(); ++p)
{
if (processed[DecodeDof(f_dofs[p])])
@@ -2095,7 +2095,7 @@ void FiniteElementSpace::RefinementOperator::MultTranspose(const Vector &x,
old_DoFTrans.TransformDual(subYt);
y.AddElementVector(c_vdofs, subYt);
}
doftrans->SetVDim(rvdim, fespace->GetOrdering());
doftrans.SetVDim(rvdim, fespace->GetOrdering());
}
for (int p = 0; p < f_dofs.Size(); ++p)
@@ -3407,6 +3407,8 @@ void FiniteElementSpace::GetElementDofs(int elem, Array<int> &dofs,
{
MFEM_VERIFY(!orders_changed, msg_orders_changed);
doftrans.SetDofTransformation(nullptr);
if (elem_dof)
{
elem_dof->GetRow(elem, dofs);
@@ -3513,7 +3515,6 @@ void FiniteElementSpace::GetElementDofs(int elem, Array<int> &dofs,
DofTransformation *FiniteElementSpace::GetElementDofs(int elem,
Array<int> &dofs) const
{
DoFTrans.SetDofTransformation(NULL);
GetElementDofs(elem, dofs, DoFTrans);
return DoFTrans.GetDofTransformation() ? &DoFTrans : NULL;
}
@@ -3523,6 +3524,8 @@ void FiniteElementSpace::GetBdrElementDofs(int bel, Array<int> &dofs,
{
MFEM_VERIFY(!orders_changed, msg_orders_changed);
doftrans.SetDofTransformation(nullptr);
if (bdr_elem_dof)
{
bdr_elem_dof->GetRow(bel, dofs);
@@ -3617,7 +3620,6 @@ void FiniteElementSpace::GetBdrElementDofs(int bel, Array<int> &dofs,
DofTransformation *FiniteElementSpace::GetBdrElementDofs(int bel,
Array<int> &dofs) const
{
DoFTrans.SetDofTransformation(NULL);
GetBdrElementDofs(bel, dofs, DoFTrans);
return DoFTrans.GetDofTransformation() ? &DoFTrans : NULL;
}
+42 -30
View File
@@ -946,8 +946,8 @@ public:
/// could be used to produce the appropriate offsets from these local dofs.
///@{
/// @brief Returns indices of degrees of freedom of element 'elem'.
/// The returned indices are offsets into an @ref ldof vector. See also
/// @brief Returns indices of degrees of freedom of element 'elem'. The
/// returned indices are offsets into an @ref ldof vector. See also
/// GetElementVDofs().
///
/// @note In many cases the returned DofTransformation object will be NULL.
@@ -957,15 +957,18 @@ public:
/// needed for Nedelec basis functions of order 2 and above on 3D elements
/// with triangular faces.
///
/// @note The returned object should NOT be deleted by the caller.
/// @deprecated Use of the returned object is deprecated. The returned object
/// should @b not be deleted by the caller. If the DofTransformation is
/// needed, use GetElementDofs(int, Array<int> &, DofTransformation &)
/// instead.
DofTransformation *GetElementDofs(int elem, Array<int> &dofs) const;
/// @brief The same as GetElementDofs(), but with a user-allocated
/// DofTransformation object. @a doftrans must be allocated in advance and
/// will be owned by the caller. The user can use the
/// DofTransformation::GetDofTransformation method on the returned
/// @a doftrans object to detect if the DofTransformation should actually be
/// used.
/// @brief The same as GetElementDofs(), but with a user-provided
/// DofTransformation object.
///
/// The user can use DofTransformation::IsIdentity on the returned @a
/// doftrans object to determine if the DofTransformation needs to actually
/// be used.
virtual void GetElementDofs(int elem, Array<int> &dofs,
DofTransformation &doftrans) const;
@@ -980,15 +983,18 @@ public:
/// needed for Nedelec basis functions of order 2 and above on 3D elements
/// with triangular faces.
///
/// @note The returned object should NOT be deleted by the caller.
/// @deprecated Use of the returned object is deprecated. The returned object
/// should @b not be deleted by the caller. If the DofTransformation is
/// needed, use GetBdrElementDofs(int, Array<int> &, DofTransformation &)
/// instead.
DofTransformation *GetBdrElementDofs(int bel, Array<int> &dofs) const;
/// @brief The same as GetBdrElementDofs(), but with a user-allocated
/// DofTransformation object. @a doftrans must be allocated in advance and
/// will be owned by the caller. The user can use the
/// DofTransformation::GetDofTransformation method on the returned
/// @a doftrans object to detect if the DofTransformation should actually be
/// used.
/// @brief The same as GetBdrElementDofs(), but with a user-provided
/// DofTransformation object.
///
/// The user can use DofTransformation::IsIdentity on the returned @a
/// doftrans object to determine if the DofTransformation needs to actually
/// be used.
virtual void GetBdrElementDofs(int bel, Array<int> &dofs,
DofTransformation &doftrans) const;
@@ -1192,15 +1198,18 @@ public:
/// needed for Nedelec basis functions of order 2 and above on 3D elements
/// with triangular faces.
///
/// @note The returned object should NOT be deleted by the caller.
/// @deprecated Use of the returned object is deprecated. The returned object
/// should @b not be deleted by the caller. If the DofTransformation is
/// needed, use GetElementVDofs(int, Array<int> &, DofTransformation &)
/// instead.
DofTransformation *GetElementVDofs(int i, Array<int> &vdofs) const;
/// @brief The same as GetElementVDofs(), but with a user-allocated
/// DofTransformation object. @a doftrans must be allocated in advance and
/// will be owned by the caller. The user can use the
/// DofTransformation::GetDofTransformation method on the returned
/// @a doftrans object to detect if the DofTransformation should actually be
/// used.
/// @brief The same as GetElementVDofs(), but with a user-provided
/// DofTransformation object.
///
/// The user can use DofTransformation::IsIdentity on the returned @a
/// doftrans object to determine if the DofTransformation needs to actually
/// be used.
void GetElementVDofs(int i, Array<int> &vdofs,
DofTransformation &doftrans) const;
@@ -1216,15 +1225,18 @@ public:
/// needed for Nedelec basis functions of order 2 and above on 3D elements
/// with triangular faces.
///
/// @note The returned object should NOT be deleted by the caller.
/// @deprecated Use of the returned object is deprecated. The returned object
/// should @b not be deleted by the caller. If the DofTransformation is
/// needed, use GetBdrElementVDofs(int, Array<int> &, DofTransformation &)
/// instead.
DofTransformation *GetBdrElementVDofs(int i, Array<int> &vdofs) const;
/// @brief The same as GetBdrElementVDofs(), but with a user-allocated
/// DofTransformation object. @a doftrans must be allocated in advance and
/// will be owned by the caller. The user can use the
/// DofTransformation::GetDofTransformation method on the returned
/// @a doftrans object to detect if the DofTransformation should actually be
/// used.
/// @brief The same as GetBdrElementVDofs(), but with a user-provided
/// DofTransformation object.
///
/// The user can use DofTransformation::IsIdentity on the returned @a
/// doftrans object to determine if the DofTransformation needs to actually
/// be used.
void GetBdrElementVDofs(int i, Array<int> &vdofs,
DofTransformation &doftrans) const;
+102 -173
View File
@@ -288,8 +288,6 @@ void GridFunction::SumFluxAndCount(BilinearFormIntegrator &blfi,
GridFunction &u = *this;
ElementTransformation *Transf;
DofTransformation *udoftrans;
DofTransformation *fdoftrans;
FiniteElementSpace *ufes = u.FESpace();
FiniteElementSpace *ffes = flux.FESpace();
@@ -302,6 +300,7 @@ void GridFunction::SumFluxAndCount(BilinearFormIntegrator &blfi,
flux = 0.0;
count = 0;
DofTransformation udoftrans, fdoftrans;
for (int i = 0; i < nfe; i++)
{
if (subdomain >= 0 && ufes->GetAttribute(i) != subdomain)
@@ -309,23 +308,17 @@ void GridFunction::SumFluxAndCount(BilinearFormIntegrator &blfi,
continue;
}
udoftrans = ufes->GetElementVDofs(i, udofs);
fdoftrans = ffes->GetElementVDofs(i, fdofs);
ufes->GetElementVDofs(i, udofs, udoftrans);
ffes->GetElementVDofs(i, fdofs, fdoftrans);
u.GetSubVector(udofs, ul);
if (udoftrans)
{
udoftrans->InvTransformPrimal(ul);
}
udoftrans.InvTransformPrimal(ul);
Transf = ufes->GetElementTransformation(i);
blfi.ComputeElementFlux(*ufes->GetFE(i), *Transf, ul,
*ffes->GetFE(i), fl, wcoef);
if (fdoftrans)
{
fdoftrans->TransformPrimal(fl);
}
fdoftrans.TransformPrimal(fl);
flux.AddElementVector(fdofs, fl);
FiniteElementSpace::AdjustVDofs(fdofs);
@@ -353,12 +346,23 @@ void GridFunction::ComputeFlux(BilinearFormIntegrator &blfi,
int GridFunction::VectorDim() const
{
return fes->GetVectorDim();
const FiniteElement *fe = fes->GetTypicalFE();
if (!fe || fe->GetRangeType() == FiniteElement::SCALAR)
{
return fes->GetVDim();
}
return fes->GetVDim()*std::max(fes->GetMesh()->SpaceDimension(),
fe->GetRangeDim());
}
int GridFunction::CurlDim() const
{
return fes->GetCurlDim();
const FiniteElement *fe = fes->GetTypicalFE();
if (!fe || fe->GetRangeType() == FiniteElement::SCALAR)
{
return 2 * fes->GetMesh()->SpaceDimension() - 3;
}
return fes->GetVDim()*fe->GetCurlDim();
}
void GridFunction::GetTrueDofs(Vector &tv) const
@@ -394,7 +398,8 @@ void GridFunction::GetNodalValues(int i, Array<real_t> &nval, int vdim) const
{
Array<int> vdofs;
DofTransformation * doftrans = fes->GetElementVDofs(i, vdofs);
DofTransformation doftrans;
fes->GetElementVDofs(i, vdofs, doftrans);
const FiniteElement *FElem = fes->GetFE(i);
const IntegrationRule *ElemVert =
Geometries.GetVertices(FElem->GetGeomType());
@@ -404,10 +409,7 @@ void GridFunction::GetNodalValues(int i, Array<real_t> &nval, int vdim) const
vdim--;
Vector loc_data;
GetSubVector(vdofs, loc_data);
if (doftrans)
{
doftrans->InvTransformPrimal(loc_data);
}
doftrans.InvTransformPrimal(loc_data);
if (FElem->GetRangeType() == FiniteElement::SCALAR)
{
@@ -448,7 +450,8 @@ real_t GridFunction::GetValue(int i, const IntegrationPoint &ip, int vdim)
const
{
Array<int> dofs;
DofTransformation * doftrans = fes->GetElementDofs(i, dofs);
DofTransformation doftrans;
fes->GetElementDofs(i, dofs, doftrans);
fes->DofsToVDofs(vdim-1, dofs);
Vector DofVal(dofs.Size()), LocVec;
const FiniteElement *fe = fes->GetFE(i);
@@ -463,10 +466,7 @@ const
fe->CalcPhysShape(*Tr, DofVal);
}
GetSubVector(dofs, LocVec);
if (doftrans)
{
doftrans->InvTransformPrimal(LocVec);
}
doftrans.InvTransformPrimal(LocVec);
return (DofVal * LocVec);
}
@@ -477,13 +477,11 @@ void GridFunction::GetVectorValue(int i, const IntegrationPoint &ip,
const FiniteElement *FElem = fes->GetFE(i);
int dof = FElem->GetDof();
Array<int> vdofs;
DofTransformation * doftrans = fes->GetElementVDofs(i, vdofs);
DofTransformation doftrans;
fes->GetElementVDofs(i, vdofs, doftrans);
Vector loc_data;
GetSubVector(vdofs, loc_data);
if (doftrans)
{
doftrans->InvTransformPrimal(loc_data);
}
doftrans.InvTransformPrimal(loc_data);
if (FElem->GetRangeType() == FiniteElement::SCALAR)
{
Vector shape(dof);
@@ -517,22 +515,19 @@ void GridFunction::GetVectorValue(int i, const IntegrationPoint &ip,
}
void GridFunction::GetValues(int i, const IntegrationRule &ir, Vector &vals,
int vdim)
const
int vdim) const
{
Array<int> dofs;
int n = ir.GetNPoints();
vals.SetSize(n);
DofTransformation * doftrans = fes->GetElementDofs(i, dofs);
DofTransformation doftrans;
fes->GetElementDofs(i, dofs, doftrans);
fes->DofsToVDofs(vdim-1, dofs);
const FiniteElement *FElem = fes->GetFE(i);
int dof = FElem->GetDof();
Vector DofVal(dof), loc_data(dof);
GetSubVector(dofs, loc_data);
if (doftrans)
{
doftrans->InvTransformPrimal(loc_data);
}
doftrans.InvTransformPrimal(loc_data);
if (FElem->GetMapType() == FiniteElement::VALUE)
{
for (int k = 0; k < n; k++)
@@ -865,12 +860,12 @@ void GridFunction::GetVectorValue(ElementTransformation &T,
Array<int> vdofs;
const FiniteElement *fe = NULL;
DofTransformation * doftrans = NULL;
DofTransformation doftrans;
switch (T.ElementType)
{
case ElementTransformation::ELEMENT:
doftrans = fes->GetElementVDofs(T.ElementNo, vdofs);
fes->GetElementVDofs(T.ElementNo, vdofs, doftrans);
fe = fes->GetFE(T.ElementNo);
break;
case ElementTransformation::EDGE:
@@ -960,10 +955,7 @@ void GridFunction::GetVectorValue(ElementTransformation &T,
int dof = fe->GetDof();
Vector loc_data;
GetSubVector(vdofs, loc_data);
if (doftrans)
{
doftrans->InvTransformPrimal(loc_data);
}
doftrans.InvTransformPrimal(loc_data);
if (fe->GetRangeType() == FiniteElement::SCALAR)
{
Vector shape(dof);
@@ -1007,13 +999,11 @@ void GridFunction::GetVectorValues(ElementTransformation &T,
int dof = FElem->GetDof();
Array<int> vdofs;
DofTransformation * doftrans = fes->GetElementVDofs(T.ElementNo, vdofs);
DofTransformation doftrans;
fes->GetElementVDofs(T.ElementNo, vdofs, doftrans);
Vector loc_data;
GetSubVector(vdofs, loc_data);
if (doftrans)
{
doftrans->InvTransformPrimal(loc_data);
}
doftrans.InvTransformPrimal(loc_data);
int nip = ir.GetNPoints();
@@ -1104,23 +1094,19 @@ void GridFunction::GetValuesFrom(const GridFunction &orig_func)
// Without averaging ...
const FiniteElementSpace *orig_fes = orig_func.FESpace();
DofTransformation * doftrans;
DofTransformation * orig_doftrans;
Array<int> vdofs, orig_vdofs;
Vector shape, loc_values, orig_loc_values;
int i, j, d, ne, dof, odof, vdim;
ne = fes->GetNE();
vdim = fes->GetVDim();
DofTransformation doftrans, orig_doftrans;
for (i = 0; i < ne; i++)
{
doftrans = fes->GetElementVDofs(i, vdofs);
orig_doftrans = orig_fes->GetElementVDofs(i, orig_vdofs);
fes->GetElementVDofs(i, vdofs, doftrans);
orig_fes->GetElementVDofs(i, orig_vdofs, orig_doftrans);
orig_func.GetSubVector(orig_vdofs, orig_loc_values);
if (orig_doftrans)
{
orig_doftrans->InvTransformPrimal(orig_loc_values);
}
orig_doftrans.InvTransformPrimal(orig_loc_values);
const FiniteElement *fe = fes->GetFE(i);
const FiniteElement *orig_fe = orig_fes->GetFE(i);
dof = fe->GetDof();
@@ -1137,10 +1123,7 @@ void GridFunction::GetValuesFrom(const GridFunction &orig_func)
loc_values(d*dof+j) = shape * (&orig_loc_values[d * odof]);
}
}
if (doftrans)
{
doftrans->TransformPrimal(loc_values);
}
doftrans.TransformPrimal(loc_values);
SetSubVector(vdofs, loc_values);
}
}
@@ -1150,8 +1133,6 @@ void GridFunction::GetBdrValuesFrom(const GridFunction &orig_func)
// Without averaging ...
const FiniteElementSpace *orig_fes = orig_func.FESpace();
// DofTransformation * doftrans;
// DofTransformation * orig_doftrans;
Array<int> vdofs, orig_vdofs;
Vector shape, loc_values, loc_values_t, orig_loc_values, orig_loc_values_t;
int i, j, d, nbe, dof, odof, vdim;
@@ -1191,7 +1172,8 @@ void GridFunction::GetVectorFieldValues(
ElementTransformation *transf;
const int n = ir.GetNPoints();
DofTransformation * doftrans = fes->GetElementVDofs(i, vdofs);
DofTransformation doftrans;
fes->GetElementVDofs(i, vdofs, doftrans);
const FiniteElement *fe = fes->GetFE(i);
const int dof = fe->GetDof();
const int sdim = fes->GetMesh()->SpaceDimension();
@@ -1203,10 +1185,7 @@ void GridFunction::GetVectorFieldValues(
DenseMatrix vshape(dof, vdim);
Vector loc_data, val(vdim);
GetSubVector(vdofs, loc_data);
if (doftrans)
{
doftrans->InvTransformPrimal(loc_data);
}
doftrans.InvTransformPrimal(loc_data);
for (int k = 0; k < n; k++)
{
const IntegrationPoint &ip = ir.IntPoint(k);
@@ -1397,6 +1376,7 @@ void GridFunction::GetVectorGradientHat(
real_t GridFunction::GetDivergence(ElementTransformation &T) const
{
DofTransformation doftrans;
switch (T.ElementType)
{
case ElementTransformation::ELEMENT:
@@ -1424,13 +1404,10 @@ real_t GridFunction::GetDivergence(ElementTransformation &T) const
{
// Assuming RT-type space
Array<int> dofs;
DofTransformation * doftrans = fes->GetElementDofs(elNo, dofs);
fes->GetElementDofs(elNo, dofs, doftrans);
Vector loc_data, divshape(fe->GetDof());
GetSubVector(dofs, loc_data);
if (doftrans)
{
doftrans->InvTransformPrimal(loc_data);
}
doftrans.InvTransformPrimal(loc_data);
fe->CalcDivShape(T.GetIntPoint(), divshape);
return (loc_data * divshape) / T.Weight();
}
@@ -1483,6 +1460,7 @@ real_t GridFunction::GetDivergence(ElementTransformation &T) const
void GridFunction::GetCurl(ElementTransformation &T, Vector &curl) const
{
DofTransformation doftrans;
switch (T.ElementType)
{
case ElementTransformation::ELEMENT:
@@ -1517,13 +1495,10 @@ void GridFunction::GetCurl(ElementTransformation &T, Vector &curl) const
{
// Assuming ND-type space
Array<int> dofs;
DofTransformation * doftrans = fes->GetElementDofs(elNo, dofs);
fes->GetElementDofs(elNo, dofs, doftrans);
Vector loc_data;
GetSubVector(dofs, loc_data);
if (doftrans)
{
doftrans->InvTransformPrimal(loc_data);
}
doftrans.InvTransformPrimal(loc_data);
DenseMatrix curl_shape(fe->GetDof(), fe->GetCurlDim());
curl.SetSize(curl_shape.Width());
fe->CalcPhysCurlShape(T, curl_shape);
@@ -1724,11 +1699,10 @@ void GridFunction::GetElementAverages(GridFunction &avgs) const
{
MassIntegrator Mi;
DenseMatrix loc_mass;
DofTransformation * te_doftrans;
DofTransformation * tr_doftrans;
Array<int> te_dofs, tr_dofs;
Vector loc_avgs, loc_this;
Vector int_psi(avgs.Size());
DofTransformation tr_doftrans, te_doftrans;
avgs = 0.0;
int_psi = 0.0;
@@ -1736,19 +1710,13 @@ void GridFunction::GetElementAverages(GridFunction &avgs) const
{
Mi.AssembleElementMatrix2(*fes->GetFE(i), *avgs.FESpace()->GetFE(i),
*fes->GetElementTransformation(i), loc_mass);
tr_doftrans = fes->GetElementDofs(i, tr_dofs);
te_doftrans = avgs.FESpace()->GetElementDofs(i, te_dofs);
fes->GetElementDofs(i, tr_dofs, tr_doftrans);
avgs.FESpace()->GetElementDofs(i, te_dofs, te_doftrans);
GetSubVector(tr_dofs, loc_this);
if (tr_doftrans)
{
tr_doftrans->InvTransformPrimal(loc_this);
}
tr_doftrans.InvTransformPrimal(loc_this);
loc_avgs.SetSize(te_dofs.Size());
loc_mass.Mult(loc_this, loc_avgs);
if (te_doftrans)
{
te_doftrans->TransformPrimal(loc_avgs);
}
te_doftrans.TransformPrimal(loc_avgs);
avgs.AddElementVector(te_dofs, loc_avgs);
loc_this = 1.0; // assume the local basis for 'this' sums to 1
loc_mass.Mult(loc_this, loc_avgs);
@@ -1763,12 +1731,10 @@ void GridFunction::GetElementAverages(GridFunction &avgs) const
void GridFunction::GetElementDofValues(int el, Vector &dof_vals) const
{
Array<int> dof_idx;
DofTransformation * doftrans = fes->GetElementVDofs(el, dof_idx);
DofTransformation doftrans;
fes->GetElementVDofs(el, dof_idx, doftrans);
GetSubVector(dof_idx, dof_vals);
if (doftrans)
{
doftrans->InvTransformPrimal(dof_vals);
}
doftrans.InvTransformPrimal(dof_vals);
}
void GridFunction::ProjectGridFunction(const GridFunction &src)
@@ -1793,6 +1759,7 @@ void GridFunction::ProjectGridFunction(const GridFunction &src)
Array<int> src_vdofs, dest_vdofs;
Vector src_lvec, dest_lvec(vdim*P.Height());
DofTransformation src_doftrans, doftrans;
for (int i = 0; i < mesh->GetNE(); i++)
{
// Assuming the projection matrix P depends only on the element geometry
@@ -1804,21 +1771,15 @@ void GridFunction::ProjectGridFunction(const GridFunction &src)
cached_geom = geom;
}
DofTransformation * src_doftrans = src.fes->GetElementVDofs(i, src_vdofs);
src.fes->GetElementVDofs(i, src_vdofs, src_doftrans);
src.GetSubVector(src_vdofs, src_lvec);
if (src_doftrans)
{
src_doftrans->InvTransformPrimal(src_lvec);
}
src_doftrans.InvTransformPrimal(src_lvec);
for (int vd = 0; vd < vdim; vd++)
{
P.Mult(&src_lvec[vd*P.Width()], &dest_lvec[vd*P.Height()]);
}
DofTransformation * doftrans = fes->GetElementVDofs(i, dest_vdofs);
if (doftrans)
{
doftrans->TransformPrimal(dest_lvec);
}
fes->GetElementVDofs(i, dest_vdofs, doftrans);
doftrans.TransformPrimal(dest_lvec);
SetSubVector(dest_vdofs, dest_lvec);
}
}
@@ -1827,15 +1788,13 @@ void GridFunction::ImposeBounds(int i, const Vector &weights,
const Vector &lo_, const Vector &hi_)
{
Array<int> vdofs;
DofTransformation * doftrans = fes->GetElementVDofs(i, vdofs);
DofTransformation doftrans;
fes->GetElementVDofs(i, vdofs, doftrans);
int size = vdofs.Size();
Vector vals, new_vals(size);
GetSubVector(vdofs, vals);
if (doftrans)
{
doftrans->InvTransformPrimal(vals);
}
doftrans.InvTransformPrimal(vals);
MFEM_ASSERT(weights.Size() == size, "Different # of weights and dofs.");
MFEM_ASSERT(lo_.Size() == size, "Different # of lower bounds and dofs.");
@@ -1852,10 +1811,7 @@ void GridFunction::ImposeBounds(int i, const Vector &weights,
slbqp.SetPrintLevel(0); // print messages only if not converged
slbqp.Mult(vals, new_vals);
if (doftrans)
{
doftrans->TransformPrimal(new_vals);
}
doftrans.TransformPrimal(new_vals);
SetSubVector(vdofs, new_vals);
}
@@ -1863,14 +1819,12 @@ void GridFunction::ImposeBounds(int i, const Vector &weights,
real_t min_, real_t max_)
{
Array<int> vdofs;
DofTransformation * doftrans = fes->GetElementVDofs(i, vdofs);
DofTransformation doftrans;
fes->GetElementVDofs(i, vdofs, doftrans);
int size = vdofs.Size();
Vector vals, new_vals(size);
GetSubVector(vdofs, vals);
if (doftrans)
{
doftrans->InvTransformPrimal(vals);
}
doftrans.InvTransformPrimal(vals);
real_t max_val = vals.Max();
real_t min_val = vals.Min();
@@ -1878,10 +1832,7 @@ void GridFunction::ImposeBounds(int i, const Vector &weights,
if (max_val <= min_)
{
new_vals = min_;
if (doftrans)
{
doftrans->TransformPrimal(new_vals);
}
doftrans.TransformPrimal(new_vals);
SetSubVector(vdofs, new_vals);
return;
}
@@ -1913,7 +1864,6 @@ void GridFunction::RestrictConforming()
void GridFunction::GetNodalValues(Vector &nval, int vdim) const
{
int i, j;
Array<int> vertices;
Array<real_t> values;
Array<int> overlap(fes->GetNV());
@@ -1921,17 +1871,17 @@ void GridFunction::GetNodalValues(Vector &nval, int vdim) const
nval = 0.0;
overlap = 0;
nval.HostReadWrite();
for (i = 0; i < fes->GetNE(); i++)
for (int i = 0; i < fes->GetNE(); i++)
{
fes->GetElementVertices(i, vertices);
GetNodalValues(i, values, vdim);
for (j = 0; j < vertices.Size(); j++)
for (int j = 0; j < vertices.Size(); j++)
{
nval(vertices[j]) += values[j];
overlap[vertices[j]]++;
}
}
for (i = 0; i < overlap.Size(); i++)
for (int i = 0; i < overlap.Size(); i++)
{
nval(i) /= overlap[i];
}
@@ -2211,6 +2161,7 @@ void GridFunction::AccumulateAndCountBdrTangentValues(
ElementTransformation *T;
Array<int> dofs;
Vector lvec;
DofTransformation dof_tr;
values_counter.SetSize(Size());
values_counter = 0;
@@ -2225,10 +2176,10 @@ void GridFunction::AccumulateAndCountBdrTangentValues(
}
fe = fes->GetBE(i);
T = fes->GetBdrElementTransformation(i);
DofTransformation *dof_tr = fes->GetBdrElementDofs(i, dofs);
fes->GetBdrElementDofs(i, dofs, dof_tr);
lvec.SetSize(fe->GetDof());
fe->Project(vcoeff, *T, lvec);
if (dof_tr) { dof_tr->TransformPrimal(lvec); }
dof_tr.TransformPrimal(lvec);
accumulate_dofs(dofs, lvec, *this, values_counter);
}
@@ -2333,6 +2284,8 @@ void GridFunction::ProjectDeltaCoefficient(DeltaCoefficient &delta_coeff,
DenseMatrix loc_mass;
Array<int> vdofs, vertices;
Vector vals, loc_mass_vals;
DofTransformation doftrans;
for (int i = 0; i < mesh->GetNE(); i++)
{
mesh->GetElementVertices(i, vertices);
@@ -2344,11 +2297,8 @@ void GridFunction::ProjectDeltaCoefficient(DeltaCoefficient &delta_coeff,
loc_mass);
vals.SetSize(fe->GetDof());
fe->ProjectDelta(j, vals);
const DofTransformation* const doftrans = fes->GetElementVDofs(i, vdofs);
if (doftrans)
{
doftrans->TransformPrimal(vals);
}
fes->GetElementVDofs(i, vdofs, doftrans);
doftrans.TransformPrimal(vals);
SetSubVector(vdofs, vals);
loc_mass_vals.SetSize(vals.Size());
loc_mass.Mult(vals, loc_mass_vals);
@@ -2361,7 +2311,7 @@ void GridFunction::ProjectDeltaCoefficient(DeltaCoefficient &delta_coeff,
void GridFunction::ProjectCoefficient(Coefficient &coeff)
{
DeltaCoefficient *delta_c = dynamic_cast<DeltaCoefficient *>(&coeff);
DofTransformation * doftrans = NULL;
DofTransformation doftrans;
if (delta_c == NULL)
{
@@ -2372,13 +2322,10 @@ void GridFunction::ProjectCoefficient(Coefficient &coeff)
for (int i = 0; i < fes->GetNE(); i++)
{
doftrans = fes->GetElementVDofs(i, vdofs);
fes->GetElementVDofs(i, vdofs, doftrans);
vals.SetSize(vdofs.Size());
fes->GetFE(i)->Project(coeff, *fes->GetElementTransformation(i), vals);
if (doftrans)
{
doftrans->TransformPrimal(vals);
}
doftrans.TransformPrimal(vals);
SetSubVector(vdofs, vals);
}
}
@@ -2445,23 +2392,19 @@ void GridFunction::ProjectCoefficient(
void GridFunction::ProjectCoefficient(VectorCoefficient &vcoeff)
{
DofTransformation doftrans;
if (fes->GetNURBSext() == NULL)
{
int i;
Array<int> vdofs;
Vector vals;
DofTransformation * doftrans = NULL;
for (i = 0; i < fes->GetNE(); i++)
{
doftrans = fes->GetElementVDofs(i, vdofs);
fes->GetElementVDofs(i, vdofs, doftrans);
vals.SetSize(vdofs.Size());
fes->GetFE(i)->Project(vcoeff, *fes->GetElementTransformation(i), vals);
if (doftrans)
{
doftrans->TransformPrimal(vals);
}
doftrans.TransformPrimal(vals);
SetSubVector(vdofs, vals);
}
}
@@ -2528,8 +2471,7 @@ void GridFunction::ProjectCoefficient(VectorCoefficient &vcoeff, int attribute)
int i;
Array<int> vdofs;
Vector vals;
DofTransformation * doftrans = NULL;
DofTransformation doftrans;
for (i = 0; i < fes->GetNE(); i++)
{
@@ -2538,13 +2480,10 @@ void GridFunction::ProjectCoefficient(VectorCoefficient &vcoeff, int attribute)
continue;
}
doftrans = fes->GetElementVDofs(i, vdofs);
fes->GetElementVDofs(i, vdofs, doftrans);
vals.SetSize(vdofs.Size());
fes->GetFE(i)->Project(vcoeff, *fes->GetElementTransformation(i), vals);
if (doftrans)
{
doftrans->TransformPrimal(vals);
}
doftrans.TransformPrimal(vals);
SetSubVector(vdofs, vals);
}
}
@@ -2555,7 +2494,6 @@ void GridFunction::ProjectCoefficient(Coefficient *coeff[])
real_t val;
const FiniteElement *fe;
ElementTransformation *transf;
// DofTransformation * doftrans;
Array<int> vdofs;
vdim = fes->GetVDim();
@@ -2738,6 +2676,7 @@ void GridFunction::ProjectBdrCoefficientNormal(
Array<int> dofs;
int dim = vcoeff.GetVDim();
Vector vc(dim), nor(dim), lvec;
DofTransformation doftrans;
for (int i = 0; i < fes->GetNBE(); i++)
{
@@ -2757,11 +2696,8 @@ void GridFunction::ProjectBdrCoefficientNormal(
CalcOrtho(T->Jacobian(), nor);
lvec(j) = (vc * nor);
}
const DofTransformation* const doftrans = fes->GetBdrElementDofs(i, dofs);
if (doftrans)
{
doftrans->TransformPrimal(lvec);
}
fes->GetBdrElementDofs(i, dofs, doftrans);
doftrans.TransformPrimal(lvec);
SetSubVector(dofs, lvec);
}
#endif
@@ -4059,6 +3995,7 @@ real_t ZZErrorEstimator(BilinearFormIntegrator &blfi,
FiniteElementSpace *ufes = u.FESpace();
FiniteElementSpace *ffes = flux.FESpace();
ElementTransformation *Transf;
DofTransformation utrans, ftrans;
int dim = ufes->GetMesh()->Dimension();
int nfe = ufes->GetNE();
@@ -4090,19 +4027,13 @@ real_t ZZErrorEstimator(BilinearFormIntegrator &blfi,
{
if (with_subdomains && ufes->GetAttribute(i) != s) { continue; }
const DofTransformation* const utrans = ufes->GetElementVDofs(i, udofs);
const DofTransformation* const ftrans = ffes->GetElementVDofs(i, fdofs);
ufes->GetElementVDofs(i, udofs, utrans);
ffes->GetElementVDofs(i, fdofs, ftrans);
u.GetSubVector(udofs, ul);
flux.GetSubVector(fdofs, fla);
if (utrans)
{
utrans->InvTransformPrimal(ul);
}
if (ftrans)
{
ftrans->InvTransformPrimal(fla);
}
utrans.InvTransformPrimal(ul);
ftrans.InvTransformPrimal(fla);
Transf = ufes->GetElementTransformation(i);
blfi.ComputeElementFlux(*ufes->GetFE(i), *Transf, ul,
@@ -4317,6 +4248,7 @@ real_t LSZZErrorEstimator(BilinearFormIntegrator &blfi, // input
MFEM_VERIFY(tichonov_coeff >= 0.0, "tichonov_coeff cannot be negative");
FiniteElementSpace *ufes = u.FESpace();
ElementTransformation *Transf;
DofTransformation utrans;
Mesh *mesh = ufes->GetMesh();
int dim = mesh->Dimension();
@@ -4398,12 +4330,9 @@ real_t LSZZErrorEstimator(BilinearFormIntegrator &blfi, // input
flux_order));
int num_integration_pts = ir->GetNPoints();
const DofTransformation* const utrans = ufes->GetElementVDofs(ielem, udofs);
ufes->GetElementVDofs(ielem, udofs, utrans);
u.GetSubVector(udofs, ul);
if (utrans)
{
utrans->InvTransformPrimal(ul);
}
utrans.InvTransformPrimal(ul);
Transf = ufes->GetElementTransformation(ielem);
FiniteElement *dummy = nullptr;
blfi.ComputeElementFlux(*ufes->GetFE(ielem), *Transf, ul,
+1 -1
View File
@@ -200,7 +200,7 @@ void PADiffusionSetup2D<3>(const int Q1D,
const real_t E = J11*J11 + J21*J21 + J31*J31;
const real_t G = J12*J12 + J22*J22 + J32*J32;
const real_t F = J11*J12 + J21*J22 + J31*J32;
const real_t iw = 1.0 / sqrt(E*G - F*F);
const real_t iw = 1.0 / std::sqrt(E*G - F*F);
const real_t coeff = const_c ? C(0,0,0) : C(qx,qy,e);
const real_t alpha = wq * coeff * iw;
D(qx,qy,0,e) = alpha * G; // 1,1
@@ -147,6 +147,11 @@ void ElasticityAddMultPA_(const int nDofs, const FiniteElementSpace &fespace,
const GeometricFactors &geom, const DofToQuad &maps, const Vector &x,
QuadratureFunction &QVec, Vector &y)
{
using future::tensor;
using future::make_tensor;
using future::det;
using future::inv;
static_assert((i_block < 0) == (j_block < 0),
"i_block and j_block must both be non-negative or strictly negative.");
static constexpr int d = dim;
@@ -277,6 +282,11 @@ void ElasticityAssembleDiagonalPA_(const int nDofs,
const CoefficientVector &mu, const GeometricFactors &geom,
const DofToQuad &maps, QuadratureFunction &QVec, Vector &diag)
{
using future::tensor;
using future::make_tensor;
using future::det;
using future::inv;
// Assuming all elements are the same
const auto &ir = QVec.GetIntRule(0);
static constexpr int d = dim;
@@ -361,6 +371,11 @@ void ElasticityAssembleEA_(const int i_block,
const DofToQuad &maps,
Vector &emat)
{
using future::tensor;
using future::make_tensor;
using future::det;
using future::inv;
// Assuming all elements are the same
static constexpr int d = dim;
const int numPoints = ir.GetNPoints();
+6 -12
View File
@@ -173,7 +173,6 @@ void LinearForm::Assemble()
{
Array<int> vdofs;
ElementTransformation *eltrans;
DofTransformation *doftrans;
Vector elemvect;
Vector::operator=(0.0);
@@ -198,6 +197,7 @@ void LinearForm::Assemble()
}
}
DofTransformation doftrans;
for (int i = 0; i < fes -> GetNE(); i++)
{
int elem_attr = fes->GetMesh()->GetAttribute(i);
@@ -207,14 +207,11 @@ void LinearForm::Assemble()
if (markers) { markers->HostRead(); }
if ( markers == NULL || (*markers)[elem_attr-1] == 1 )
{
doftrans = fes -> GetElementVDofs (i, vdofs);
fes -> GetElementVDofs (i, vdofs, doftrans);
eltrans = fes -> GetElementTransformation (i);
domain_integs[k]->AssembleRHSElementVect(*fes->GetFE(i),
*eltrans, elemvect);
if (doftrans)
{
doftrans->TransformDual(elemvect);
}
doftrans.TransformDual(elemvect);
AddElementVector (vdofs, elemvect);
}
}
@@ -247,11 +244,12 @@ void LinearForm::Assemble()
}
}
DofTransformation doftrans;
for (int i = 0; i < fes -> GetNBE(); i++)
{
const int bdr_attr = mesh->GetBdrAttribute(i);
if (bdr_attr_marker[bdr_attr-1] == 0) { continue; }
doftrans = fes -> GetBdrElementVDofs (i, vdofs);
fes -> GetBdrElementVDofs (i, vdofs, doftrans);
eltrans = fes -> GetBdrElementTransformation (i);
for (int k=0; k < boundary_integs.Size(); k++)
{
@@ -260,11 +258,7 @@ void LinearForm::Assemble()
boundary_integs[k]->AssembleRHSElementVect(*fes->GetBE(i),
*eltrans, elemvect);
if (doftrans)
{
doftrans->TransformDual(elemvect);
}
doftrans.TransformDual(elemvect);
AddElementVector (vdofs, elemvect);
}
}
+1 -1
View File
@@ -48,7 +48,7 @@ void LORBase::AddIntegratorsAndMarkers(BilinearForm &a_from,
for (int i=0; i<integrators->Size(); ++i)
{
BilinearFormIntegrator *integrator = (*integrators)[i];
if (*markers[i])
if (markers[i] != nullptr)
{
(a_to.*add_integrator_marker)(integrator, *markers[i]);
}
+10 -3
View File
@@ -485,10 +485,17 @@ void BatchedLORAssembly::Assemble(
#endif
AssembleWithoutBC(a, A);
SparseMatrix *A_mat = A.As<SparseMatrix>();
A_mat->EliminateBC(ess_dofs,
Operator::DiagonalPolicy::DIAG_KEEP);
const SparseMatrix *P = fes_ho.GetConformingProlongation();
if (P)
{
std::unique_ptr<SparseMatrix> R(Transpose(*P));
std::unique_ptr<SparseMatrix> RA(mfem::Mult(*R, *A.As<SparseMatrix>()));
A.Reset(mfem::Mult(*RA, *P));
}
A.As<SparseMatrix>()->EliminateBC(ess_dofs,
Operator::DiagonalPolicy::DIAG_KEEP);
}
BatchedLORAssembly::BatchedLORAssembly(FiniteElementSpace &fes_ho_)
+43 -44
View File
@@ -96,7 +96,6 @@ real_t NonlinearForm::GetGridFunctionEnergy(const Vector &x) const
Vector el_x;
const FiniteElement *fe;
ElementTransformation *T;
DofTransformation *doftrans;
Mesh *mesh = fes->GetMesh();
real_t energy = 0.0;
@@ -123,16 +122,17 @@ real_t NonlinearForm::GetGridFunctionEnergy(const Vector &x) const
}
}
DofTransformation doftrans;
for (int i = 0; i < fes->GetNE(); i++)
{
const int attr = mesh->GetAttribute(i);
if (attr_marker[attr-1] == 0) { continue; }
fe = fes->GetFE(i);
doftrans = fes->GetElementVDofs(i, vdofs);
fes->GetElementVDofs(i, vdofs, doftrans);
T = fes->GetElementTransformation(i);
x.GetSubVector(vdofs, el_x);
if (doftrans) {doftrans->InvTransformPrimal(el_x); }
doftrans.InvTransformPrimal(el_x);
for (int k = 0; k < dnfi.Size(); k++)
{
if (dnfi_marker[k] &&
@@ -166,16 +166,17 @@ real_t NonlinearForm::GetGridFunctionEnergy(const Vector &x) const
}
}
DofTransformation doftrans;
for (int i = 0; i < fes->GetNBE(); i++)
{
const int bdr_attr = mesh->GetBdrAttribute(i);
if (bdr_attr_marker[bdr_attr-1] == 0) { continue; }
fe = fes->GetBE(i);
doftrans = fes->GetBdrElementVDofs(i, vdofs);
fes->GetBdrElementVDofs(i, vdofs, doftrans);
T = fes->GetBdrElementTransformation(i);
x.GetSubVector(vdofs, el_x);
if (doftrans) {doftrans->InvTransformPrimal(el_x); }
doftrans.InvTransformPrimal(el_x);
for (int k = 0; k < bnfi.Size(); k++)
{
if (bnfi_marker[k] &&
@@ -240,7 +241,6 @@ void NonlinearForm::Mult(const Vector &x, Vector &y) const
Vector el_x, el_y;
const FiniteElement *fe;
ElementTransformation *T;
DofTransformation *doftrans;
Mesh *mesh = fes->GetMesh();
py = 0.0;
@@ -268,23 +268,24 @@ void NonlinearForm::Mult(const Vector &x, Vector &y) const
}
}
DofTransformation doftrans;
for (int i = 0; i < fes->GetNE(); i++)
{
const int attr = mesh->GetAttribute(i);
if (attr_marker[attr-1] == 0) { continue; }
fe = fes->GetFE(i);
doftrans = fes->GetElementVDofs(i, vdofs);
fes->GetElementVDofs(i, vdofs, doftrans);
T = fes->GetElementTransformation(i);
px.GetSubVector(vdofs, el_x);
if (doftrans) {doftrans->InvTransformPrimal(el_x); }
doftrans.InvTransformPrimal(el_x);
for (int k = 0; k < dnfi.Size(); k++)
{
if (dnfi_marker[k] &&
(*dnfi_marker[k])[attr-1] == 0) { continue; }
dnfi[k]->AssembleElementVector(*fe, *T, el_x, el_y);
if (doftrans) {doftrans->TransformDual(el_y); }
doftrans.TransformDual(el_y);
py.AddElementVector(vdofs, el_y);
}
}
@@ -313,23 +314,25 @@ void NonlinearForm::Mult(const Vector &x, Vector &y) const
}
}
DofTransformation doftrans;
for (int i = 0; i < fes->GetNBE(); i++)
{
const int bdr_attr = mesh->GetBdrAttribute(i);
if (bdr_attr_marker[bdr_attr-1] == 0) { continue; }
fe = fes->GetBE(i);
doftrans = fes->GetBdrElementVDofs(i, vdofs);
fes->GetBdrElementVDofs(i, vdofs, doftrans);
T = fes->GetBdrElementTransformation(i);
px.GetSubVector(vdofs, el_x);
if (doftrans) {doftrans->InvTransformPrimal(el_x); }
doftrans.InvTransformPrimal(el_x);
for (int k = 0; k < bnfi.Size(); k++)
{
if (bnfi_marker[k] &&
(*bnfi_marker[k])[bdr_attr-1] == 0) { continue; }
bnfi[k]->AssembleElementVector(*fe, *T, el_x, el_y);
if (doftrans) {doftrans->TransformDual(el_y); }
doftrans.TransformDual(el_y);
py.AddElementVector(vdofs, el_y);
}
}
@@ -453,7 +456,6 @@ Operator &NonlinearForm::GetGradient(const Vector &x) const
DenseMatrix elmat;
const FiniteElement *fe;
ElementTransformation *T;
DofTransformation *doftrans;
Mesh *mesh = fes->GetMesh();
const Vector &px = Prolongate(x);
@@ -489,23 +491,24 @@ Operator &NonlinearForm::GetGradient(const Vector &x) const
}
}
DofTransformation doftrans;
for (int i = 0; i < fes->GetNE(); i++)
{
const int attr = mesh->GetAttribute(i);
if (attr_marker[attr-1] == 0) { continue; }
fe = fes->GetFE(i);
doftrans = fes->GetElementVDofs(i, vdofs);
fes->GetElementVDofs(i, vdofs, doftrans);
T = fes->GetElementTransformation(i);
px.GetSubVector(vdofs, el_x);
if (doftrans) {doftrans->InvTransformPrimal(el_x); }
doftrans.InvTransformPrimal(el_x);
for (int k = 0; k < dnfi.Size(); k++)
{
if (dnfi_marker[k] &&
(*dnfi_marker[k])[attr-1] == 0) { continue; }
dnfi[k]->AssembleElementGrad(*fe, *T, el_x, elmat);
if (doftrans) { doftrans->TransformDual(elmat); }
doftrans.TransformDual(elmat);
Grad->AddSubMatrix(vdofs, vdofs, elmat, skip_zeros);
// Grad->AddSubMatrix(vdofs, vdofs, elmat, 1);
}
@@ -535,23 +538,24 @@ Operator &NonlinearForm::GetGradient(const Vector &x) const
}
}
DofTransformation doftrans;
for (int i = 0; i < fes->GetNBE(); i++)
{
const int bdr_attr = mesh->GetBdrAttribute(i);
if (bdr_attr_marker[bdr_attr-1] == 0) { continue; }
fe = fes->GetBE(i);
doftrans = fes->GetBdrElementVDofs(i, vdofs);
fes->GetBdrElementVDofs(i, vdofs, doftrans);
T = fes->GetBdrElementTransformation(i);
px.GetSubVector(vdofs, el_x);
if (doftrans) {doftrans->InvTransformPrimal(el_x); }
doftrans.InvTransformPrimal(el_x);
for (int k = 0; k < bnfi.Size(); k++)
{
if (bnfi_marker[k] &&
(*bnfi_marker[k])[bdr_attr-1] == 0) { continue; }
bnfi[k]->AssembleElementGrad(*fe, *T, el_x, elmat);
if (doftrans) { doftrans->TransformDual(elmat); }
doftrans.TransformDual(elmat);
Grad->AddSubMatrix(vdofs, vdofs, elmat, skip_zeros);
}
}
@@ -806,7 +810,6 @@ real_t BlockNonlinearForm::GetEnergyBlocked(const BlockVector &bx) const
Array<const Vector *> el_x_const(fes.Size());
Array<const FiniteElement *> fe(fes.Size());
ElementTransformation *T;
DofTransformation *doftrans;
Mesh *mesh = fes[0]->GetMesh();
real_t energy = 0.0;
@@ -839,6 +842,7 @@ real_t BlockNonlinearForm::GetEnergyBlocked(const BlockVector &bx) const
}
}
DofTransformation doftrans;
for (int i = 0; i < fes[0]->GetNE(); ++i)
{
const int attr = mesh->GetAttribute(i);
@@ -848,9 +852,9 @@ real_t BlockNonlinearForm::GetEnergyBlocked(const BlockVector &bx) const
for (int s=0; s<fes.Size(); ++s)
{
fe[s] = fes[s]->GetFE(i);
doftrans = fes[s]->GetElementVDofs(i, *vdofs[s]);
fes[s]->GetElementVDofs(i, *vdofs[s], doftrans);
bx.GetBlock(s).GetSubVector(*vdofs[s], *el_x[s]);
if (doftrans) {doftrans->InvTransformPrimal(*el_x[s]); }
doftrans.InvTransformPrimal(*el_x[s]);
}
for (int k = 0; k < dnfi.Size(); ++k)
@@ -886,6 +890,7 @@ real_t BlockNonlinearForm::GetEnergyBlocked(const BlockVector &bx) const
}
}
DofTransformation doftrans;
for (int i = 0; i < mesh->GetNBE(); i++)
{
const int bdr_attr = mesh->GetBdrAttribute(i);
@@ -895,9 +900,9 @@ real_t BlockNonlinearForm::GetEnergyBlocked(const BlockVector &bx) const
for (int s = 0; s < fes.Size(); ++s)
{
fe[s] = fes[s]->GetBE(i);
doftrans = fes[s]->GetBdrElementVDofs(i, *(vdofs[s]));
fes[s]->GetBdrElementVDofs(i, *(vdofs[s]), doftrans);
bx.GetBlock(s).GetSubVector(*(vdofs[s]), *el_x[s]);
if (doftrans) {doftrans->InvTransformPrimal(*el_x[s]); }
doftrans.InvTransformPrimal(*el_x[s]);
}
for (int k = 0; k < bnfi.Size(); k++)
@@ -947,7 +952,7 @@ void BlockNonlinearForm::MultBlocked(const BlockVector &bx,
Array<const FiniteElement *> fe(fes.Size());
Array<const FiniteElement *> fe2(fes.Size());
ElementTransformation *T;
Array<DofTransformation *> doftrans(fes.Size()); doftrans = nullptr;
std::vector<DofTransformation> doftrans(fes.Size());
Mesh *mesh = fes[0]->GetMesh();
by.UseDevice(true);
@@ -992,10 +997,10 @@ void BlockNonlinearForm::MultBlocked(const BlockVector &bx,
T = fes[0]->GetElementTransformation(i);
for (int s = 0; s < fes.Size(); ++s)
{
doftrans[s] = fes[s]->GetElementVDofs(i, *(vdofs[s]));
fes[s]->GetElementVDofs(i, *(vdofs[s]), doftrans[s]);
fe[s] = fes[s]->GetFE(i);
bx.GetBlock(s).GetSubVector(*(vdofs[s]), *el_x[s]);
if (doftrans[s]) {doftrans[s]->InvTransformPrimal(*el_x[s]); }
doftrans[s].InvTransformPrimal(*el_x[s]);
}
for (int k = 0; k < dnfi.Size(); ++k)
@@ -1009,7 +1014,7 @@ void BlockNonlinearForm::MultBlocked(const BlockVector &bx,
for (int s=0; s<fes.Size(); ++s)
{
if (el_y[s]->Size() == 0) { continue; }
if (doftrans[s]) {doftrans[s]->TransformDual(*el_y[s]); }
doftrans[s].TransformDual(*el_y[s]);
by.GetBlock(s).AddElementVector(*(vdofs[s]), *el_y[s]);
}
}
@@ -1047,10 +1052,10 @@ void BlockNonlinearForm::MultBlocked(const BlockVector &bx,
T = fes[0]->GetBdrElementTransformation(i);
for (int s = 0; s < fes.Size(); ++s)
{
doftrans[s] = fes[s]->GetBdrElementVDofs(i, *(vdofs[s]));
fes[s]->GetBdrElementVDofs(i, *(vdofs[s]), doftrans[s]);
fe[s] = fes[s]->GetBE(i);
bx.GetBlock(s).GetSubVector(*(vdofs[s]), *el_x[s]);
if (doftrans[s]) {doftrans[s]->InvTransformPrimal(*el_x[s]); }
doftrans[s].InvTransformPrimal(*el_x[s]);
}
for (int k = 0; k < bnfi.Size(); k++)
@@ -1063,7 +1068,7 @@ void BlockNonlinearForm::MultBlocked(const BlockVector &bx,
for (int s=0; s<fes.Size(); ++s)
{
if (el_y[s]->Size() == 0) { continue; }
if (doftrans[s]) {doftrans[s]->TransformDual(*el_y[s]); }
doftrans[s].TransformDual(*el_y[s]);
by.GetBlock(s).AddElementVector(*(vdofs[s]), *el_y[s]);
}
}
@@ -1231,7 +1236,7 @@ void BlockNonlinearForm::ComputeGradientBlocked(const BlockVector &bx) const
Array<const FiniteElement *>fe(fes.Size());
Array<const FiniteElement *>fe2(fes.Size());
ElementTransformation * T;
Array<DofTransformation *> doftrans(fes.Size()); doftrans = nullptr;
std::vector<DofTransformation> doftrans(fes.Size());
Mesh *mesh = fes[0]->GetMesh();
for (int i=0; i<fes.Size(); ++i)
@@ -1293,9 +1298,9 @@ void BlockNonlinearForm::ComputeGradientBlocked(const BlockVector &bx) const
for (int s = 0; s < fes.Size(); ++s)
{
fe[s] = fes[s]->GetFE(i);
doftrans[s] = fes[s]->GetElementVDofs(i, *vdofs[s]);
fes[s]->GetElementVDofs(i, *vdofs[s], doftrans[s]);
bx.GetBlock(s).GetSubVector(*vdofs[s], *el_x[s]);
if (doftrans[s]) {doftrans[s]->InvTransformPrimal(*el_x[s]); }
doftrans[s].InvTransformPrimal(*el_x[s]);
}
for (int k = 0; k < dnfi.Size(); ++k)
@@ -1310,10 +1315,7 @@ void BlockNonlinearForm::ComputeGradientBlocked(const BlockVector &bx) const
for (int l=0; l<fes.Size(); ++l)
{
if (elmats(j,l)->Height() == 0) { continue; }
if (doftrans[j] || doftrans[l])
{
TransformDual(doftrans[j], doftrans[l], *elmats(j,l));
}
TransformDual(doftrans[j], doftrans[l], *elmats(j,l));
Grads(j,l)->AddSubMatrix(*vdofs[j], *vdofs[l],
*elmats(j,l), skip_zeros);
}
@@ -1354,9 +1356,9 @@ void BlockNonlinearForm::ComputeGradientBlocked(const BlockVector &bx) const
for (int s = 0; s < fes.Size(); ++s)
{
fe[s] = fes[s]->GetBE(i);
doftrans[s] = fes[s]->GetBdrElementVDofs(i, *(vdofs[s]));
fes[s]->GetBdrElementVDofs(i, *(vdofs[s]), doftrans[s]);
bx.GetBlock(s).GetSubVector(*(vdofs[s]), *el_x[s]);
if (doftrans[s]) {doftrans[s]->InvTransformPrimal(*el_x[s]); }
doftrans[s].InvTransformPrimal(*el_x[s]);
}
for (int k = 0; k < bnfi.Size(); k++)
@@ -1371,10 +1373,7 @@ void BlockNonlinearForm::ComputeGradientBlocked(const BlockVector &bx) const
for (int l=0; l<fes.Size(); ++l)
{
if (elmats(j,l)->Height() == 0) { continue; }
if (doftrans[j] || doftrans[l])
{
TransformDual(doftrans[j], doftrans[l], *elmats(j,l));
}
TransformDual(doftrans[j], doftrans[l], *elmats(j,l));
Grads(j,l)->AddSubMatrix(*vdofs[j], *vdofs[l],
*elmats(j,l), skip_zeros);
}
+6 -1
View File
@@ -561,6 +561,8 @@ void ParFiniteElementSpace::ApplyLDofSigns(Table &el_dof) const
void ParFiniteElementSpace::GetElementDofs(int i, Array<int> &dofs,
DofTransformation &doftrans) const
{
doftrans.SetDofTransformation(nullptr);
if (elem_dof)
{
elem_dof->GetRow(i, dofs);
@@ -586,6 +588,8 @@ void ParFiniteElementSpace::GetElementDofs(int i, Array<int> &dofs,
void ParFiniteElementSpace::GetBdrElementDofs(int i, Array<int> &dofs,
DofTransformation &doftrans) const
{
doftrans.SetDofTransformation(nullptr);
if (bdr_elem_dof)
{
bdr_elem_dof->GetRow(i, dofs);
@@ -1687,6 +1691,8 @@ void ParFiniteElementSpace::ExchangeFaceNbrData()
void ParFiniteElementSpace::GetFaceNbrElementVDofs(
int i, Array<int> &vdofs, DofTransformation &doftrans) const
{
doftrans.SetDofTransformation(nullptr);
face_nbr_element_dof.GetRow(i, vdofs);
if (DoFTransArray[GetFaceNbrFE(i)->GetGeomType()])
@@ -1703,7 +1709,6 @@ void ParFiniteElementSpace::GetFaceNbrElementVDofs(
DofTransformation *ParFiniteElementSpace::GetFaceNbrElementVDofs(
int i, Array<int> &vdofs) const
{
DoFTrans.SetDofTransformation(NULL);
GetFaceNbrElementVDofs(i, vdofs, DoFTrans);
return DoFTrans.GetDofTransformation() ? &DoFTrans : NULL;
}
+24 -50
View File
@@ -55,22 +55,16 @@ ParGridFunction::ParGridFunction(ParMesh *pmesh, const GridFunction *gf,
int element_counter = 0;
const int MyRank = pfes->GetMyRank();
const int glob_ne = glob_fes->GetNE();
DofTransformation ltrans, gtrans;
for (int i = 0; i < glob_ne; i++)
{
if (partitioning[i] == MyRank)
{
const DofTransformation* const ltrans = pfes->GetElementVDofs(element_counter,
lvdofs);
const DofTransformation* const gtrans = glob_fes->GetElementVDofs(i, gvdofs);
pfes->GetElementVDofs(element_counter, lvdofs, ltrans);
glob_fes->GetElementVDofs(i, gvdofs, gtrans);
gf->GetSubVector(gvdofs, lnodes);
if (gtrans)
{
gtrans->InvTransformPrimal(lnodes);
}
if (ltrans)
{
ltrans->TransformPrimal(lnodes);
}
gtrans.InvTransformPrimal(lnodes);
ltrans.TransformPrimal(lnodes);
SetSubVector(lvdofs, lnodes);
element_counter++;
}
@@ -279,11 +273,11 @@ const
Array<int> dofs;
Vector DofVal, LocVec;
const int nbr_el_no = i - pfes->GetParMesh()->GetNE();
DofTransformation doftrans;
if (nbr_el_no >= 0)
{
int fes_vdim = pfes->GetVDim();
const DofTransformation* const doftrans = pfes->GetFaceNbrElementVDofs(
nbr_el_no, dofs);
pfes->GetFaceNbrElementVDofs(nbr_el_no, dofs, doftrans);
// Choose fe to be of the order whose number of DOFs matches dofs.Size(),
// in the variable order case.
const int ndofs = pfes->IsVariableOrder() ? dofs.Size() : 0;
@@ -302,10 +296,7 @@ const
face_nbr_data.GetSubVector(dofs, LocVec);
DofVal.SetSize(dofs.Size());
}
if (doftrans)
{
doftrans->InvTransformPrimal(LocVec);
}
doftrans.InvTransformPrimal(LocVec);
if (fe->GetMapType() == FiniteElement::VALUE)
{
@@ -321,7 +312,7 @@ const
}
else
{
const DofTransformation* const doftrans = fes->GetElementDofs(i, dofs);
fes->GetElementDofs(i, dofs, doftrans);
fes->DofsToVDofs(vdim-1, dofs);
DofVal.SetSize(dofs.Size());
const FiniteElement *fe = fes->GetFE(i);
@@ -336,10 +327,7 @@ const
fe->CalcPhysShape(*Tr, DofVal);
}
GetSubVector(dofs, LocVec);
if (doftrans)
{
doftrans->InvTransformPrimal(LocVec);
}
doftrans.InvTransformPrimal(LocVec);
}
return (DofVal * LocVec);
@@ -352,15 +340,11 @@ void ParGridFunction::GetVectorValue(int i, const IntegrationPoint &ip,
if (nbr_el_no >= 0)
{
Array<int> dofs;
const DofTransformation* const doftrans = pfes->GetFaceNbrElementVDofs(
nbr_el_no,
dofs);
DofTransformation doftrans;
pfes->GetFaceNbrElementVDofs(nbr_el_no, dofs, doftrans);
Vector loc_data;
face_nbr_data.GetSubVector(dofs, loc_data);
if (doftrans)
{
doftrans->InvTransformPrimal(loc_data);
}
doftrans.InvTransformPrimal(loc_data);
const FiniteElement *FElem = pfes->GetFaceNbrFE(nbr_el_no);
int dof = FElem->GetDof();
if (FElem->GetRangeType() == FiniteElement::SCALAR)
@@ -428,8 +412,8 @@ real_t ParGridFunction::GetValue(ElementTransformation &T,
Array<int> dofs;
const FiniteElement * fe = pfes->GetFaceNbrFE(nbr_el_no);
const DofTransformation* const doftrans = pfes->GetFaceNbrElementVDofs(
nbr_el_no, dofs);
DofTransformation doftrans;
pfes->GetFaceNbrElementVDofs(nbr_el_no, dofs, doftrans);
pfes->DofsToVDofs(comp-1, dofs);
Vector DofVal(dofs.Size()), LocVec;
@@ -442,10 +426,7 @@ real_t ParGridFunction::GetValue(ElementTransformation &T,
fe->CalcPhysShape(T, DofVal);
}
face_nbr_data.GetSubVector(dofs, LocVec);
if (doftrans)
{
doftrans->InvTransformPrimal(LocVec);
}
doftrans.InvTransformPrimal(LocVec);
return (DofVal * LocVec);
@@ -476,13 +457,11 @@ void ParGridFunction::GetVectorValue(ElementTransformation &T,
}
Array<int> vdofs;
DofTransformation * doftrans = pfes->GetFaceNbrElementVDofs(nbr_el_no, vdofs);
DofTransformation doftrans;
pfes->GetFaceNbrElementVDofs(nbr_el_no, vdofs, doftrans);
Vector loc_data;
face_nbr_data.GetSubVector(vdofs, loc_data);
if (doftrans)
{
doftrans->InvTransformPrimal(loc_data);
}
doftrans.InvTransformPrimal(loc_data);
const FiniteElement *fe = pfes->GetFaceNbrFE(nbr_el_no);
const int dof = fe->GetDof();
@@ -1335,25 +1314,20 @@ real_t L2ZZErrorEstimator(BilinearFormIntegrator &flux_integrator,
ParFiniteElementSpace *xfes = x.ParFESpace();
Array<int> xdofs, fdofs;
Vector el_x, el_f;
DofTransformation xtrans, ftrans;
for (int i = 0; i < xfes->GetNE(); i++)
{
const DofTransformation* const xtrans = xfes->GetElementVDofs(i, xdofs);
xfes->GetElementVDofs(i, xdofs, xtrans);
x.GetSubVector(xdofs, el_x);
if (xtrans)
{
xtrans->InvTransformPrimal(el_x);
}
xtrans.InvTransformPrimal(el_x);
ElementTransformation *Transf = xfes->GetElementTransformation(i);
flux_integrator.ComputeElementFlux(*xfes->GetFE(i), *Transf, el_x,
*flux_fes.GetFE(i), el_f, false);
const DofTransformation* const ftrans = flux_fes.GetElementVDofs(i, fdofs);
if (ftrans)
{
ftrans->TransformPrimal(el_f);
}
flux_fes.GetElementVDofs(i, fdofs, ftrans);
ftrans.TransformPrimal(el_f);
flux.SetSubVector(fdofs, el_f);
}
+4
View File
@@ -300,12 +300,16 @@ public:
real_t ComputeL1Error(Coefficient *exsol[],
const IntegrationRule *irs[] = NULL) const override
{
#ifdef MFEM_HAVE_GCC_PRAGMA_DIAGNOSTIC
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
real_t glb_err = GlobalLpNorm(1.0,
GridFunction::ComputeL1Error(exsol, irs),
pfes->GetComm());
#ifdef MFEM_HAVE_GCC_PRAGMA_DIAGNOSTIC
#pragma GCC diagnostic pop
#endif
return glb_err;
}
+1 -1
View File
@@ -200,7 +200,7 @@ static void Det2DSurface(const int NE,
const real_t E = J(0,0)*J(0,0) + J(1,0)*J(1,0) + J(2,0)*J(2,0);
const real_t F = J(0,0)*J(0,1) + J(1,0)*J(1,1) + J(2,0)*J(2,1);
const real_t G = J(0,1)*J(0,1) + J(1,1)*J(1,1) + J(2,1)*J(2,1);
Y(qx,qy,e) = sqrt(E*G - F*F);
Y(qx,qy,e) = std::sqrt(E*G - F*F);
}
}
});
+1 -1
View File
@@ -326,7 +326,7 @@ static void Eval2D(const int NE,
const double E = j(0,0)*j(0,0) + j(1,0)*j(1,0) + j(2,0)*j(2,0);
const double F = j(0,0)*j(0,1) + j(1,0)*j(1,1) + j(2,0)*j(2,1);
const double G = j(0,1)*j(0,1) + j(1,1)*j(1,1) + j(2,1)*j(2,1);
det(q,e) = sqrt(E*G - F*F);
det(q,e) = std::sqrt(E*G - F*F);
}
}
}
+2 -2
View File
@@ -22,9 +22,9 @@ namespace mfem
/* AD related definitions below ========================================*/
/// MFEM native AD-type for first derivatives
using AD1Type = internal::dual<real_t, real_t>;
using AD1Type = future::dual<real_t, real_t>;
/// MFEM native AD-type for second derivatives
using AD2Type = internal::dual<AD1Type, AD1Type>;
using AD2Type = future::dual<AD1Type, AD1Type>;
/*
Functions for 2x2 DenseMatrix cast as std::vector<type>, assuming column-major storage
+11 -20
View File
@@ -2100,10 +2100,11 @@ void PRefinementTransferOperator::Mult(const Vector& x, Vector& y) const
y = 0.0;
DofTransformation doftrans_h, doftrans_l;
for (int i = 0; i < mesh->GetNE(); i++)
{
DofTransformation * doftrans_h = hFESpace.GetElementDofs(i, h_dofs);
DofTransformation * doftrans_l = lFESpace.GetElementDofs(i, l_dofs);
hFESpace.GetElementDofs(i, h_dofs, doftrans_h);
lFESpace.GetElementDofs(i, l_dofs, doftrans_l);
const Geometry::Type geom = mesh->GetElementBaseGeometry(i);
if (geom != cached_geom || isvar_order)
@@ -2123,15 +2124,9 @@ void PRefinementTransferOperator::Mult(const Vector& x, Vector& y) const
h_dofs.Copy(h_vdofs);
hFESpace.DofsToVDofs(vd, h_vdofs);
x.GetSubVector(l_vdofs, subX);
if (doftrans_l)
{
doftrans_l->InvTransformPrimal(subX);
}
doftrans_l.InvTransformPrimal(subX);
loc_prol.Mult(subX, subY);
if (doftrans_h)
{
doftrans_h->TransformPrimal(subY);
}
doftrans_h.TransformPrimal(subY);
y.SetSubVector(h_vdofs, subY);
}
}
@@ -2157,10 +2152,12 @@ void PRefinementTransferOperator::MultTranspose(const Vector& x,
int vdim = lFESpace.GetVDim();
DofTransformation doftrans_h, doftrans_l;
for (int i = 0; i < mesh->GetNE(); i++)
{
DofTransformation * doftrans_h = hFESpace.GetElementDofs(i, h_dofs);
DofTransformation * doftrans_l = lFESpace.GetElementDofs(i, l_dofs);
hFESpace.GetElementDofs(i, h_dofs, doftrans_h);
lFESpace.GetElementDofs(i, l_dofs, doftrans_l);
const Geometry::Type geom = mesh->GetElementBaseGeometry(i);
if (geom != cached_geom || isvar_order)
@@ -2182,10 +2179,7 @@ void PRefinementTransferOperator::MultTranspose(const Vector& x,
hFESpace.DofsToVDofs(vd, h_vdofs);
x.GetSubVector(h_vdofs, subX);
if (doftrans_h)
{
doftrans_h->InvTransformDual(subX);
}
doftrans_h.InvTransformDual(subX);
for (int p = 0; p < h_dofs.Size(); ++p)
{
if (processed[lFESpace.DecodeDof(h_dofs[p])])
@@ -2195,10 +2189,7 @@ void PRefinementTransferOperator::MultTranspose(const Vector& x,
}
loc_prol.Mult(subX, subY);
if (doftrans_l)
{
doftrans_l->TransformDual(subY);
}
doftrans_l.TransformDual(subY);
y.AddElementVector(l_vdofs, subY);
}
+7
View File
@@ -56,6 +56,10 @@ protected:
static_assert(std::is_trivial<T>::value, "type T must be trivial");
public:
using value_type = T; ///< Type alias for stl.
using reference = T&; ///< Type alias for stl.
using const_reference = const T&; ///< Type alias for stl.
friend void Swap<T>(Array<T> &, Array<T> &);
/// Creates an empty array
@@ -174,6 +178,9 @@ public:
/// Append element 'el' to array, resize if necessary.
inline int Append(const T & el);
/// STL-like push_back. Append element 'el' to array, resize if necessary.
void push_back(const T &el) { Append(el); }
/// Append another array to this array, resize if necessary.
inline int Append(const T *els, int nels);
+2 -2
View File
@@ -186,7 +186,7 @@ void Device::Configure(const std::string &device, const int device_id)
// and avoid the 'singleton_device' to destroy the mm.
if (device_env)
{
std::memcpy(this, &Get(), sizeof(Device));
std::memcpy((void*)this, &Get(), sizeof(Device));
Get().destroy_mm = false;
return;
}
@@ -243,7 +243,7 @@ void Device::Configure(const std::string &device, const int device_id)
Get().UpdateMemoryTypeAndClass(device_option);
// Copy all data members from the global 'singleton_device' into '*this'.
if (this != &Get()) { std::memcpy(this, &Get(), sizeof(Device)); }
if (this != &Get()) { std::memcpy((void*)this, &Get(), sizeof(Device)); }
// Only '*this' will call the MemoryManager::Destroy() method.
destroy_mm = true;
+13 -13
View File
@@ -187,33 +187,33 @@ public:
/** The string parameter @a device must be a comma-separated list of backend
string names (see below). The @a device_id argument specifies the ID of
the actual devices (e.g. GPU) to use.
* The available backends are described by the Backend class.
* The string name of a backend is the lowercase version of the
- The available backends are described by the Backend class.
- The string name of a backend is the lowercase version of the
Backend::Id enumeration constant with '_' replaced by '-', e.g. the
string name of 'RAJA_CPU' is 'raja-cpu'. The string name of the debug
backend (Backend::Id 'DEBUG_DEVICE') is exceptionally set to 'debug'.
* The 'cpu' backend is always enabled with lowest priority.
* The current backend priority from highest to lowest is:
- The 'cpu' backend is always enabled with lowest priority.
- The current backend priority from highest to lowest is:
'ceed-cuda', 'occa-cuda', 'raja-cuda', 'cuda',
'ceed-hip', 'hip', 'debug',
'occa-omp', 'raja-omp', 'omp',
'ceed-cpu', 'occa-cpu', 'raja-cpu', 'cpu'.
* Multiple backends can be configured at the same time.
* Only one 'occa-*' backend can be configured at a time.
* The backend 'occa-cuda' enables the 'cuda' backend unless 'raja-cuda'
- Multiple backends can be configured at the same time.
- Only one 'occa-*' backend can be configured at a time.
- The backend 'occa-cuda' enables the 'cuda' backend unless 'raja-cuda'
is already enabled.
* The backend 'occa-omp' enables the 'omp' backend (if MFEM was built
- The backend 'occa-omp' enables the 'omp' backend (if MFEM was built
with MFEM_USE_OPENMP=YES) unless 'raja-omp' is already enabled.
* Only one 'ceed-*' backend can be configured at a time.
* The backend 'ceed-cpu' delegates to a libCEED CPU backend the setup and
- Only one 'ceed-*' backend can be configured at a time.
- The backend 'ceed-cpu' delegates to a libCEED CPU backend the setup and
evaluation of the operator.
* The backend 'ceed-cuda' delegates to a libCEED CUDA backend the setup
- The backend 'ceed-cuda' delegates to a libCEED CUDA backend the setup
and evaluation of operators and enables the 'cuda' backend to avoid
transfers between host and device.
* The backend 'ceed-hip' delegates to a libCEED HIP backend the setup
- The backend 'ceed-hip' delegates to a libCEED HIP backend the setup
and evaluation of operators and enables the 'hip' backend to avoid
transfers between host and device.
* The 'debug' backend should not be combined with other device backends.
- The 'debug' backend should not be combined with other device backends.
@note If the device is actually enabled, this method will also update the
current host/device MemoryType and MemoryClass. */
+18
View File
@@ -9,6 +9,7 @@
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#include "../config/config.hpp"
#ifndef MFEM_ENZYME_HPP
#define MFEM_ENZYME_HPP
@@ -25,11 +26,28 @@ extern int enzyme_dup;
extern int enzyme_dupnoneed;
extern int enzyme_out;
extern int enzyme_const;
extern int enzyme_interleave;
#if defined(MFEM_USE_CUDA) || defined(MFEM_USE_HIP)
#define MFEM_DEVICE_EXTERN_STMT(name) extern __device__ int name;
#else
#define MFEM_DEVICE_EXTERN_STMT(name)
#endif
MFEM_DEVICE_EXTERN_STMT(enzyme_dup)
MFEM_DEVICE_EXTERN_STMT(enzyme_dupnoneed)
MFEM_DEVICE_EXTERN_STMT(enzyme_out)
MFEM_DEVICE_EXTERN_STMT(enzyme_const)
MFEM_DEVICE_EXTERN_STMT(enzyme_interleave)
// warning: if inlined, triggers function '__enzyme_autodiff' is not defined
template <typename return_type, typename... Args>
MFEM_HOST_DEVICE
return_type __enzyme_autodiff(Args...);
// warning: if inlined, triggers function '__enzyme_fwddiff' is not defined
template <typename return_type, typename... Args>
MFEM_HOST_DEVICE
return_type __enzyme_fwddiff(Args...);
#define MFEM_ENZYME_INACTIVENOFREE __attribute__((enzyme_inactive, enzyme_nofree))
+5 -1
View File
@@ -162,7 +162,11 @@ private:
// MFEM_UNROLL pragma macro that can be used inside MFEM_FORALL macros.
#if defined(MFEM_USE_CUDA) && defined(__CUDA_ARCH__)
#ifdef __NVCC__
#define MFEM_UNROLL(N) MFEM_PRAGMA(unroll(N))
#else // Assuming Clang CUDA
#define MFEM_UNROLL(N) MFEM_PRAGMA(unroll N)
#endif
#else
#define MFEM_UNROLL(N)
#endif
@@ -876,7 +880,7 @@ template<class B, class R> struct reduction_kernel
/// helper for computing the reduction block size
static int block_log2(unsigned N)
{
#if defined(__GNUC__) or defined(__clang__)
#if defined(__GNUC__) || defined(__clang__)
return N ? (sizeof(unsigned) * 8 - __builtin_clz(N)) : 0;
#elif defined(_MSC_VER)
return sizeof(unsigned) * 8 - __lzclz(N);
+6 -3
View File
@@ -73,9 +73,6 @@ const char *GetConfigStr()
#ifdef MFEM_USE_AMGX
"MFEM_USE_AMGX\n"
#endif
#ifdef MFEM_USE_MAGMA
"MFEM_USE_MAGMA\n"
#endif
#ifdef MFEM_USE_CEED
"MFEM_USE_CEED\n"
#endif
@@ -88,6 +85,9 @@ const char *GetConfigStr()
#ifdef MFEM_USE_DOUBLE
"MFEM_USE_DOUBLE\n"
#endif
#ifdef MFEM_USE_ENZYME
"MFEM_USE_ENZYME\n"
#endif
#ifdef MFEM_USE_EXCEPTIONS
"MFEM_USE_EXCEPTIONS\n"
#endif
@@ -118,6 +118,9 @@ const char *GetConfigStr()
#ifdef MFEM_USE_LIBUNWIND
"MFEM_USE_LIBUNWIND\n"
#endif
#ifdef MFEM_USE_MAGMA
"MFEM_USE_MAGMA\n"
#endif
#ifdef MFEM_USE_MEMALLOC
"MFEM_USE_MEMALLOC\n"
#endif
+9 -2
View File
@@ -13,6 +13,7 @@
#define MFEM_DTENSOR
#include "../general/backends.hpp"
#include <array>
namespace mfem
{
@@ -42,7 +43,8 @@ public:
static inline int result(const int* sizes, T first, Args... args)
{
#if !(defined(MFEM_USE_CUDA) || defined(MFEM_USE_HIP))
MFEM_ASSERT(first<sizes[Dim-1],"Trying to access out of boundary.");
MFEM_ASSERT(first<static_cast<T>(sizes[Dim-1]),
"Trying to access out of boundary.");
#endif
return static_cast<int>(first);
}
@@ -87,7 +89,9 @@ protected:
public:
/// Default constructor
DeviceTensor() = delete;
// DeviceTensor() = delete;
MFEM_HOST_DEVICE
DeviceTensor() {}
/// Constructor to initialize a tensor from the Scalar array data_
template <typename... Args> MFEM_HOST_DEVICE
@@ -122,6 +126,9 @@ public:
{
return data[i];
}
/// Returns the shape of the tensor.
MFEM_HOST_DEVICE inline auto &GetShape() const { return sizes; }
};
+7 -9
View File
@@ -15,8 +15,7 @@
* @brief This file contains the declaration of a dual number class
*/
#ifndef MFEM_INTERNAL_DUAL_HPP
#define MFEM_INTERNAL_DUAL_HPP
#pragma once
#include <type_traits> // for is_arithmetic
#include <cmath>
@@ -24,7 +23,7 @@
namespace mfem
{
namespace internal
namespace future
{
/**
@@ -40,8 +39,9 @@ struct dual
/// the partial derivatives of value w.r.t. some other quantity
gradient_type gradient;
/** @brief assignment of a double to a value of a dual. Promotes a double to
/** @brief assignment of a real_t to a value of a dual. Promotes a real_t to
* a dual with a zero gradient value. */
MFEM_HOST_DEVICE
auto operator=(real_t a) -> dual<value_type, gradient_type>&
{
value = a;
@@ -258,7 +258,7 @@ dual<value_type, gradient_type>& operator-=(dual<value_type, gradient_type>& a,
return a;
}
/** @brief compound assignment (+) for dual numbers with `double` righthand side */
/** @brief compound assignment (+) for dual numbers with `real_t` righthand side */
template <typename value_type, typename gradient_type> MFEM_HOST_DEVICE
dual<value_type, gradient_type>& operator+=(dual<value_type, gradient_type>& a,
real_t b)
@@ -267,7 +267,7 @@ dual<value_type, gradient_type>& operator+=(dual<value_type, gradient_type>& a,
return a;
}
/** @brief compound assignment (-) for dual numbers with `double` righthand side */
/** @brief compound assignment (-) for dual numbers with `real_t` righthand side */
template <typename value_type, typename gradient_type> MFEM_HOST_DEVICE
dual<value_type, gradient_type>& operator-=(dual<value_type, gradient_type>& a,
real_t b)
@@ -435,7 +435,5 @@ MFEM_HOST_DEVICE gradient_type get_gradient(dual<value_type, gradient_type> arg)
return arg.gradient;
}
} // namespace internal
} // namespace future
} // namespace mfem
#endif
+1
View File
@@ -36,6 +36,7 @@
#include "batched/batched.hpp"
#include "batched/gpu_blas.hpp"
#include "batched/solver.hpp"
#include "tensor.hpp"
#ifdef MFEM_USE_AMGX
#include "amgxsolver.hpp"
+231 -50
View File
@@ -15,27 +15,18 @@
* @brief Implementation of the tensor class
*/
#ifndef MFEM_INTERNAL_TENSOR_HPP
#define MFEM_INTERNAL_TENSOR_HPP
#pragma once
#include "../general/backends.hpp"
#include "dual.hpp"
#include <limits>
#include <type_traits> // for std::false_type
namespace mfem
{
namespace internal
namespace future
{
#if defined(__CUDACC__)
#if __CUDAVER__ >= 75000
#define MFEM_SUPPRESS_NVCC_HOSTDEVICE_WARNING #pragma nv_exec_check_disable
#else
#define MFEM_SUPPRESS_NVCC_HOSTDEVICE_WARNING #pragma hd_warning_disable
#endif
#else //__CUDACC__
#define MFEM_SUPPRESS_NVCC_HOSTDEVICE_WARNING
#endif
template <typename T, int... n>
struct tensor;
@@ -149,7 +140,7 @@ struct tensor<T, n0, n1, n2, n3, n4>
*/
struct zero
{
/** @brief `zero` is implicitly convertible to double with value 0.0 */
/** @brief `zero` is implicitly convertible to real_t with value 0.0 */
MFEM_HOST_DEVICE operator real_t() { return 0.0; }
/** @brief `zero` is implicitly convertible to a tensor of any shape */
@@ -304,7 +295,6 @@ using reduced_tensor = typename std::conditional<
* @note the different cases of 0D, 1D, 2D, 3D, and 4D are implemented separately
* to work around a limitation in nvcc involving __host__ __device__ lambdas with `auto` parameters.
*/
MFEM_SUPPRESS_NVCC_HOSTDEVICE_WARNING
template <typename lambda_type>
MFEM_HOST_DEVICE constexpr auto make_tensor(lambda_type f) ->
tensor<decltype(f())>
@@ -323,7 +313,6 @@ tensor<decltype(f())>
* @note the different cases of 0D, 1D, 2D, 3D, and 4D are implemented separately
* to work around a limitation in nvcc involving __host__ __device__ lambdas with `auto` parameters.
*/
MFEM_SUPPRESS_NVCC_HOSTDEVICE_WARNING
template <int n1, typename lambda_type>
MFEM_HOST_DEVICE auto make_tensor(lambda_type f) ->
tensor<decltype(f(n1)), n1>
@@ -349,7 +338,6 @@ tensor<decltype(f(n1)), n1>
* @note the different cases of 0D, 1D, 2D, 3D, and 4D are implemented separately
* to work around a limitation in nvcc involving __host__ __device__ lambdas with `auto` parameters.
*/
MFEM_SUPPRESS_NVCC_HOSTDEVICE_WARNING
template <int n1, int n2, typename lambda_type>
MFEM_HOST_DEVICE auto make_tensor(lambda_type f) ->
tensor<decltype(f(n1, n2)), n1, n2>
@@ -379,7 +367,6 @@ tensor<decltype(f(n1, n2)), n1, n2>
* @note the different cases of 0D, 1D, 2D, 3D, and 4D are implemented separately
* to work around a limitation in nvcc involving __host__ __device__ lambdas with `auto` parameters.
*/
MFEM_SUPPRESS_NVCC_HOSTDEVICE_WARNING
template <int n1, int n2, int n3, typename lambda_type>
MFEM_HOST_DEVICE auto make_tensor(lambda_type f) ->
tensor<decltype(f(n1, n2, n3)), n1, n2, n3>
@@ -413,7 +400,6 @@ tensor<decltype(f(n1, n2, n3)), n1, n2, n3>
* @note the different cases of 0D, 1D, 2D, 3D, and 4D are implemented separately
* to work around a limitation in nvcc involving __host__ __device__ lambdas with `auto` parameters.
*/
MFEM_SUPPRESS_NVCC_HOSTDEVICE_WARNING
template <int n1, int n2, int n3, int n4, typename lambda_type>
MFEM_HOST_DEVICE auto make_tensor(lambda_type f) ->
tensor<decltype(f(n1, n2, n3, n4)), n1, n2, n3, n4>
@@ -436,6 +422,23 @@ tensor<decltype(f(n1, n2, n3, n4)), n1, n2, n3, n4>
return A;
}
// needs to be generalized
template <typename T, int m, int n> MFEM_HOST_DEVICE
tensor<T, n> get_col(tensor<T, m, n> A, int j)
{
tensor<T, n> c{};
c(0) = A[0][j];
c(1) = A[1][j];
return c;
}
/// @overload
template <typename T> MFEM_HOST_DEVICE
tensor<T, 1> get_col(tensor<T, 1, 1> A, int j)
{
return tensor<T, 1> {A[0][0]};
}
/**
* @brief return the sum of two tensors
* @tparam S the underlying type of the lefthand argument
@@ -497,7 +500,7 @@ tensor<decltype(S {} + T{}), n...>
/**
* @brief multiply a tensor by a scalar value
* @tparam S the scalar value type. Must be arithmetic (e.g. float, double, int) or a dual number
* @tparam S the scalar value type. Must be arithmetic (e.g. float, real_t, int) or a dual number
* @tparam T the underlying type of the tensor (righthand) argument
* @tparam n integers describing the tensor shape
* @param[in] scale The scaling factor
@@ -519,7 +522,7 @@ tensor<decltype(S {} * T{}), n...>
/**
* @brief multiply a tensor by a scalar value
* @tparam S the scalar value type. Must be arithmetic (e.g. float, double, int) or a dual number
* @tparam S the scalar value type. Must be arithmetic (e.g. float, real_t, int) or a dual number
* @tparam T the underlying type of the tensor (righthand) argument
* @tparam n integers describing the tensor shape
* @param[in] A The tensor to be scaled
@@ -541,7 +544,7 @@ tensor<decltype(T {} * S{}), n...>
/**
* @brief divide a scalar by each element in a tensor
* @tparam S the scalar value type. Must be arithmetic (e.g. float, double, int) or a dual number
* @tparam S the scalar value type. Must be arithmetic (e.g. float, real_t, int) or a dual number
* @tparam T the underlying type of the tensor (righthand) argument
* @tparam n integers describing the tensor shape
* @param[in] scale The numerator
@@ -563,7 +566,7 @@ tensor<decltype(S {} * T{}), n...>
/**
* @brief divide a tensor by a scalar
* @tparam S the scalar value type. Must be arithmetic (e.g. float, double, int) or a dual number
* @tparam S the scalar value type. Must be arithmetic (e.g. float, real_t, int) or a dual number
* @tparam T the underlying type of the tensor (righthand) argument
* @tparam n integers describing the tensor shape
* @param[in] A The tensor of numerators
@@ -697,6 +700,20 @@ auto outer(S A, T B) -> decltype(A * B)
return A * B;
}
template <typename T, int n, int m> MFEM_HOST_DEVICE
tensor<T, n + m> flatten(tensor<T, n, m> A)
{
tensor<T, n + m> B{};
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
B(i + j * m) = A(i, j);
}
}
return B;
}
/**
* @overload
* @note this overload implements the case where the left argument is a scalar, and the right argument is a tensor
@@ -1051,13 +1068,25 @@ decltype(S {} * T{})
return AB;
}
template <typename S, typename T, int m, int... n> MFEM_HOST_DEVICE
auto dot(const tensor<S, m>& A, const tensor<T, m, n...>& B) ->
tensor<decltype(S {} * T{}), n...>
template <typename T, int m> MFEM_HOST_DEVICE
auto dot(const tensor<T, m>& A, const tensor<T, m>& B) ->
decltype(T {})
{
constexpr int dimensions[] = {n...};
tensor<decltype(S{} * T{}), n...> AB{};
for (int i = 0; i < dimensions[0]; i++)
decltype(T{}) AB{};
for (int i = 0; i < m; i++)
{
AB += A[i] * B[i];
}
return AB;
}
template <typename S, typename T, int m, int n0, int n1, int... n>
MFEM_HOST_DEVICE
auto dot(const tensor<S, m>& A, const tensor<T, m, n0, n1, n...>& B) ->
tensor<decltype(S {} * T{}), n0, n1, n...>
{
tensor<decltype(S{} * T{}), n0, n1, n...> AB{};
for (int i = 0; i < n0; i++)
{
for (int j = 0; j < m; j++)
{
@@ -1088,7 +1117,7 @@ decltype(S {} * T{} * U{})
}
/**
* @brief double dot product, contracting over the two "middle" indices
* @brief real_t dot product, contracting over the two "middle" indices
* @tparam S the underlying type of the tensor (lefthand) argument
* @tparam T the underlying type of the tensor (righthand) argument
* @tparam m first dimension of A
@@ -1285,7 +1314,7 @@ tensor<T, n, n> dev(const tensor<T, n, n>& A)
* @return I_dim
*/
template <int dim>
MFEM_HOST_DEVICE tensor<real_t, dim, dim> Identity()
MFEM_HOST_DEVICE tensor<real_t, dim, dim> IdentityMatrix()
{
tensor<real_t, dim, dim> I{};
for (int i = 0; i < dim; i++)
@@ -1321,6 +1350,12 @@ tensor<T, n, m> transpose(const tensor<T, m, n>& A)
* @param[in] A The matrix to obtain the determinant of
*/
template <typename T> MFEM_HOST_DEVICE
T det(const tensor<T, 1, 1>& A)
{
return A[0][0];
}
/// @overload
template <typename T> MFEM_HOST_DEVICE
T det(const tensor<T, 2, 2>& A)
{
return A[0][0] * A[1][1] - A[0][1] * A[1][0];
@@ -1335,6 +1370,145 @@ T det(const tensor<T, 3, 3>& A)
A[2][0];
}
template <typename T> MFEM_HOST_DEVICE
std::tuple<tensor<T, 1>, tensor<T, 1, 1>> eig(tensor<T, 1, 1> &A)
{
return {tensor<T, 1>{A[0][0]}, tensor<T, 1, 1>{{{1.0}}}};
}
template <typename T> MFEM_HOST_DEVICE
std::tuple<tensor<T, 2>, tensor<T, 2, 2>> eig(tensor<T, 2, 2> &A)
{
tensor<T, 2> e;
tensor<T, 2, 2> v;
real_t d0 = A(0, 0);
real_t d2 = A(0, 1);
real_t d3 = A(1, 1);
real_t c, s;
if (d2 == 0.0)
{
c = 1.0;
s = 0.0;
}
else
{
real_t t;
const real_t zeta = (d3 - d0) / (2.0 * d2);
const real_t azeta = fabs(zeta);
if (azeta < std::sqrt(1.0/std::numeric_limits<T>::epsilon()))
{
t = copysign(1./(azeta + std::sqrt(1. + zeta*zeta)), zeta);
}
else
{
t = copysign(0.5/azeta, zeta);
}
c = std::sqrt(1./(1. + t*t));
s = c*t;
t *= d2;
d0 -= t;
d3 += t;
}
if (d0 <= d3)
{
e(0) = d0;
e(1) = d3;
v(0, 0) = c;
v(1, 0) = -s;
v(0, 1) = s;
v(1, 1) = c;
}
else
{
e(0) = d3;
e(1) = d0;
v(0, 0) = s;
v(1, 0) = c;
v(0, 1) = c;
v(1, 1) = -s;
}
return {e, v};
}
template <typename T> MFEM_HOST_DEVICE
void GetScalingFactor(const T &d_max, T &mult)
{
int d_exp;
if (d_max > 0.)
{
mult = frexp(d_max, &d_exp);
if (d_exp == std::numeric_limits<T>::max_exponent)
{
mult *= std::numeric_limits<T>::radix;
}
mult = d_max/mult;
}
else
{
mult = 1.;
}
}
template <typename T> MFEM_HOST_DEVICE
T calcsv(const tensor<T, 1, 1> A, const int i)
{
return A[0][0];
}
/**
* @brief Compute the i-th singular value of a 2x2 matrix A
*/
template <typename T> MFEM_HOST_DEVICE
T calcsv(const tensor<T, 2, 2> A, const int i)
{
real_t mult;
real_t d0, d1, d2, d3;
d0 = A(0, 0);
d1 = A(1, 0);
d2 = A(0, 1);
d3 = A(1, 1);
real_t d_max = fabs(d0);
if (d_max < fabs(d1)) { d_max = fabs(d1); }
if (d_max < fabs(d2)) { d_max = fabs(d2); }
if (d_max < fabs(d3)) { d_max = fabs(d3); }
GetScalingFactor(d_max, mult);
d0 /= mult;
d1 /= mult;
d2 /= mult;
d3 /= mult;
real_t t = 0.5*((d0+d2)*(d0-d2)+(d1-d3)*(d1+d3));
real_t s = d0*d2 + d1*d3;
s = std::sqrt(0.5*(d0*d0 + d1*d1 + d2*d2 + d3*d3) + std::sqrt(t*t + s*s));
if (s == 0.0)
{
return 0.0;
}
t = fabs(d0*d3 - d1*d2) / s;
if (t > s)
{
if (i == 0)
{
return t*mult;
}
return s*mult;
}
if (i == 0)
{
return s*mult;
}
return t*mult;
}
/**
* @brief Return whether a square rank 2 tensor is symmetric
*
@@ -1474,13 +1648,20 @@ tensor<T, n> linear_solve(tensor<T, n, n> A, const tensor<T, n> b)
/**
* @brief Inverts a matrix
* @param[in] A The matrix to invert
* @note Uses a shortcut for inverting a 2-by-2 matrix
* @note Uses a shortcut for inverting a 1x1, 2x2 and 3x3 matrix
*/
inline MFEM_HOST_DEVICE tensor<real_t, 2, 2> inv(const tensor<real_t, 2, 2>& A)
template <typename T>
inline MFEM_HOST_DEVICE tensor<T, 1, 1> inv(const tensor<T, 1, 1>& A)
{
real_t inv_detA(1.0 / det(A));
return tensor<T, 1, 1> {{{T{1.0} / A[0][0]}}};
}
tensor<real_t, 2, 2> invA{};
template <typename T>
inline MFEM_HOST_DEVICE tensor<T, 2, 2> inv(const tensor<T, 2, 2>& A)
{
T inv_detA(1.0 / det(A));
tensor<T, 2, 2> invA{};
invA[0][0] = A[1][1] * inv_detA;
invA[0][1] = -A[0][1] * inv_detA;
@@ -1494,11 +1675,12 @@ inline MFEM_HOST_DEVICE tensor<real_t, 2, 2> inv(const tensor<real_t, 2, 2>& A)
* @overload
* @note Uses a shortcut for inverting a 3-by-3 matrix
*/
inline MFEM_HOST_DEVICE tensor<real_t, 3, 3> inv(const tensor<real_t, 3, 3>& A)
template <typename T>
inline MFEM_HOST_DEVICE tensor<T, 3, 3> inv(const tensor<T, 3, 3>& A)
{
real_t inv_detA(1.0 / det(A));
T inv_detA(1.0 / det(A));
tensor<real_t, 3, 3> invA{};
tensor<T, 3, 3> invA{};
invA[0][0] = (A[1][1] * A[2][2] - A[1][2] * A[2][1]) * inv_detA;
invA[0][1] = (A[0][2] * A[2][1] - A[0][1] * A[2][2]) * inv_detA;
@@ -1517,10 +1699,12 @@ inline MFEM_HOST_DEVICE tensor<real_t, 3, 3> inv(const tensor<real_t, 3, 3>& A)
* @note For N-by-N matrices with N > 3, requires Gaussian elimination
* with partial pivoting
*/
template <typename T, int n> MFEM_HOST_DEVICE
tensor<T, n, n> inv(const tensor<T, n, n>& A)
template <typename T, int n>
MFEM_HOST_DEVICE
typename std::enable_if<(n > 3), tensor<T, n, n>>::type
inv(const tensor<T, n, n>& A)
{
auto abs = [](real_t x) { return (x < 0) ? -x : x; };
auto abs = [](T x) { return (x < 0) ? -x : x; };
auto swap = [](tensor<T, n>& x, tensor<T, n>& y)
{
auto tmp = x;
@@ -1528,12 +1712,12 @@ tensor<T, n, n> inv(const tensor<T, n, n>& A)
y = tmp;
};
tensor<real_t, n, n> B = Identity<n>();
tensor<T, n, n> B = IdentityMatrix<n>();
for (int i = 0; i < n; i++)
{
// Search for maximum in this column
real_t max_val = abs(A[i][i]);
T max_val = abs(A[i][i]);
int max_row = i;
for (int j = i + 1; j < n; j++)
@@ -1553,7 +1737,7 @@ tensor<T, n, n> inv(const tensor<T, n, n>& A)
{
if (A[j][i] != 0.0)
{
real_t c = -A[j][i] / A[i][i];
T c = -A[j][i] / A[i][i];
A[j] += c * A[i];
B[j] += c * B[i];
A[j][i] = 0;
@@ -1716,7 +1900,7 @@ template <typename T1, typename T2>
using outer_product_t = typename detail::outer_prod<T1, T2>::type;
/**
* @brief Retrieves the gradient component of a double (which is nothing)
* @brief Retrieves the gradient component of a real_t (which is nothing)
* @return The sentinel, @see zero
*/
inline MFEM_HOST_DEVICE zero get_gradient(real_t /* arg */) { return zero{}; }
@@ -2046,11 +2230,8 @@ auto ddot(const isotropic_tensor<S, m, m, m, m>& I,
const tensor<T, m, m>& A)
-> tensor<decltype(S {} * T{}), m, m>
{
return I.c1 * tr(A) * Identity<m>() + I.c2 * sym(A) + I.c3 * antisym(A);
return I.c1 * tr(A) * IdentityMatrix<m>() + I.c2 * sym(A) + I.c3 * antisym(A);
}
} // namespace internal
} // namespace future
} // namespace mfem
#endif
+22 -9
View File
@@ -124,8 +124,8 @@ EXAMPLE_DIRS := examples $(addprefix examples/,$(EXAMPLE_SUBDIRS))
EXAMPLE_TEST_DIRS := examples
MINIAPP_SUBDIRS = common electromagnetics meshing navier performance tools \
toys nurbs gslib adjoint solvers shifted mtop parelag tribol autodiff hooke \
multidomain dpg hdiv-linear-solver spde
toys nurbs gslib adjoint solvers shifted mtop parelag tribol autodiff dfem \
hooke multidomain dpg hdiv-linear-solver spde
MINIAPP_DIRS := $(addprefix miniapps/,$(MINIAPP_SUBDIRS))
MINIAPP_TEST_DIRS := $(filter-out %/common,$(MINIAPP_DIRS))
MINIAPP_USE_COMMON := $(addprefix miniapps/,electromagnetics meshing tools \
@@ -243,10 +243,23 @@ endif
ifeq ($(MFEM_USE_CUDA),YES)
MFEM_CXX ?= $(CUDA_CXX)
MFEM_HOST_CXX ?= $(HOST_CXX)
CXXFLAGS += $(CUDA_FLAGS) -ccbin $(MFEM_HOST_CXX)
XCOMPILER = $(CUDA_XCOMPILER)
XLINKER = $(CUDA_XLINKER)
ifeq ($(shell $(MFEM_CXX) --version 2>&1 | grep "NVIDIA"),)
# assume clang
MFEM_HOST_CXX ?= $(MFEM_CXX)
CXXFLAGS += $(CLANG_CUDA_FLAGS)
XCOMPILER = $(CXX_XCOMPILER)
XLINKER = $(CXX_XLINKER)
CUDA_LIB := $(CLANG_CUDA_LIB) $(CUDA_LIB)
else
ifeq ($(MFEM_USE_ENZYME),YES)
$(error Cannot use nvcc with Enzyme! Set CUDA_CXX to CUDA-enabled \
clang++ or an MPI wrapper of that)
endif
MFEM_HOST_CXX ?= $(HOST_CXX)
CXXFLAGS += $(NVCC_FLAGS) -ccbin $(MFEM_HOST_CXX)
XCOMPILER = $(CUDA_XCOMPILER)
XLINKER = $(CUDA_XLINKER)
endif
# CUDA_OPT and CUDA_LIB are added below
# Compatibility test against MFEM_USE_HIP
ifeq ($(MFEM_USE_HIP),YES)
@@ -285,7 +298,7 @@ ifeq ($(MFEM_USE_LEGACY_OPENMP),YES)
endif
# List of MFEM dependencies, that require the *_LIB variable to be non-empty
MFEM_REQ_LIB_DEPS = ENZYME SUPERLU MUMPS METIS FMS CONDUIT SIDRE LAPACK SUNDIALS\
MFEM_REQ_LIB_DEPS = SUPERLU MUMPS METIS FMS CONDUIT SIDRE LAPACK SUNDIALS\
SUITESPARSE STRUMPACK GINKGO GNUTLS HDF5 NETCDF SLEPC PETSC MPFR PUMI HIOP\
GSLIB OCCA CEED RAJA UMPIRE MKL_CPARDISO MKL_PARDISO AMGX MAGMA CALIPER PARELAG\
TRIBOL BENCHMARK MOONOLITH ALGOIM
@@ -307,7 +320,7 @@ ifeq ($(MAKECMDGOALS),config)
endif
# List of MFEM dependencies, processed below
MFEM_DEPENDENCIES = $(MFEM_REQ_LIB_DEPS) LIBUNWIND OPENMP CUDA HIP
MFEM_DEPENDENCIES = ENZYME $(MFEM_REQ_LIB_DEPS) LIBUNWIND OPENMP CUDA HIP
# List of deprecated MFEM dependencies, processed below
MFEM_LEGACY_DEPENDENCIES = OPENMP
@@ -429,7 +442,7 @@ DIRS = general linalg linalg/batched linalg/simd mesh mesh/submesh fem \
fem/ceed/integrators/mass fem/ceed/integrators/convection \
fem/ceed/integrators/diffusion fem/ceed/integrators/nlconvection \
fem/ceed/interface fem/ceed/solvers fem/eltrans fem/fe fem/gslib \
fem/integ fem/lor fem/moonolith fem/qinterp fem/tmop
fem/integ fem/lor fem/moonolith fem/qinterp fem/tmop fem/dfem
ifeq ($(MFEM_USE_MOONOLITH),YES)
MFEM_CXXFLAGS += $(MOONOLITH_CXX_FLAGS)
+159 -14
View File
@@ -32,6 +32,7 @@
#include <cstring>
#include <ctime>
#include <functional>
#include <numeric>
#include <unordered_map>
#include <unordered_set>
@@ -1553,6 +1554,7 @@ Geometry::Type Mesh::GetTypicalElementGeometry() const
return geom;
}
void Mesh::GetExteriorFaceMarker(Array<int> & face_marker) const
{
const int num_faces = GetNumFaces();
@@ -5402,11 +5404,15 @@ void Mesh::MakeRefined_(Mesh &orig_mesh, const Array<int> &ref_factors,
Mesh Mesh::MakeSimplicial(const Mesh &orig_mesh)
{
Mesh mesh;
mesh.MakeSimplicial_(orig_mesh, NULL);
auto parent_elements = mesh.MakeSimplicial_(orig_mesh, NULL);
if (orig_mesh.GetNodes() != nullptr)
{
mesh.MakeHigherOrderSimplicial_(orig_mesh, parent_elements);
}
return mesh;
}
void Mesh::MakeSimplicial_(const Mesh &orig_mesh, int *vglobal)
Array<int> Mesh::MakeSimplicial_(const Mesh &orig_mesh, int *vglobal)
{
MFEM_VERIFY(const_cast<Mesh&>(orig_mesh).CheckElementOrientation(false) == 0,
"Mesh::MakeSimplicial requires a properly oriented input mesh");
@@ -5420,7 +5426,9 @@ void Mesh::MakeSimplicial_(const Mesh &orig_mesh, int *vglobal)
{
Mesh copy(orig_mesh);
Swap(copy, true);
return;
Array<int> parent_elements(GetNE());
std::iota(parent_elements.begin(), parent_elements.end(), 0);
return parent_elements;
}
int nv = orig_mesh.GetNV();
@@ -5462,15 +5470,20 @@ void Mesh::MakeSimplicial_(const Mesh &orig_mesh, int *vglobal)
// global vertex number). Use the supplied global numbering, if it is
// non-NULL, otherwise use the local numbering.
Array<int> vglobal_id;
if (vglobal == NULL)
if (vglobal == nullptr)
{
vglobal_id.SetSize(nv);
for (int i=0; i<nv; ++i) { vglobal_id[i] = i; }
std::iota(vglobal_id.begin(), vglobal_id.end(), 0);
vglobal = vglobal_id.GetData();
}
// Number of vertices per element
constexpr int nv_tri = 3, nv_quad = 4, nv_tet = 4, nv_prism = 6, nv_hex = 8;
constexpr int quad_ntris = 2, prism_ntets = 3;
constexpr int quad_ntris = 2; // NTriangles per quad
constexpr int prism_ntets = 3; // NTets per prism
// Map verts of quad to verts of tri, in two possible configurations.
// quad_trimap[i][0,2,4] is the first triangle, and quad_trimap[i][1,3,5] is
// the second, for each configuration.
static const int quad_trimap[2][nv_tri*quad_ntris] =
{
{
@@ -5555,8 +5568,9 @@ void Mesh::MakeSimplicial_(const Mesh &orig_mesh, int *vglobal)
hex_tetmap0, hex_tetmap1, hex_tetmap2, hex_tetmap3
};
auto find_min = [](const int*a, int n) { return std::min_element(a,a+n)-a; };
auto find_min = [](const int *a, int n) { return std::min_element(a,a+n)-a; };
Array<int> parent_elems;
for (int i=0; i<ne; ++i)
{
const int *v = orig_mesh.elements[i]->GetVertices();
@@ -5565,13 +5579,14 @@ void Mesh::MakeSimplicial_(const Mesh &orig_mesh, int *vglobal)
if (num_subdivisions[orig_geom] == 1)
{
// (num_subdivisions[orig_geom] == 1) implies that the element does
// not need to be further split (it is either a segment, triangle,
// or tetrahedron), and so it is left unchanged.
// (num_subdivisions[orig_geom] == 1) implies that the element does not
// need to be further split (it is either a segment, triangle, or
// tetrahedron), and so it is left unchanged.
Element *e = NewElement(orig_geom);
e->SetAttribute(attrib);
e->SetVertices(v);
AddElement(e);
parent_elems.Append(i);
}
else if (orig_geom == Geometry::SQUARE)
{
@@ -5585,6 +5600,7 @@ void Mesh::MakeSimplicial_(const Mesh &orig_mesh, int *vglobal)
v2[iv] = v[quad_trimap[0][itri + iv*quad_ntris]];
}
AddElement(e);
parent_elems.Append(i);
}
}
else if (orig_geom == Geometry::PRISM)
@@ -5614,6 +5630,7 @@ void Mesh::MakeSimplicial_(const Mesh &orig_mesh, int *vglobal)
v2[iv] = vg[tetmap[itet + iv*prism_ntets]];
}
AddElement(e);
parent_elems.Append(i);
}
}
else if (orig_geom == Geometry::CUBE)
@@ -5677,6 +5694,7 @@ void Mesh::MakeSimplicial_(const Mesh &orig_mesh, int *vglobal)
v2[iv] = vg[tetmap[itet + iv*ntets]];
}
AddElement(e);
parent_elems.Append(i);
}
}
}
@@ -5726,8 +5744,134 @@ void Mesh::MakeSimplicial_(const Mesh &orig_mesh, int *vglobal)
MFEM_ASSERT(CheckElementOrientation(false) == 0, "");
MFEM_ASSERT(CheckBdrElementOrientation(false) == 0, "");
return parent_elems;
}
void Mesh::MakeHigherOrderSimplicial_(const Mesh &orig_mesh, const Array<int> &parent_elements)
{
// Higher order associated to vertices are unchanged, and those for
// previously existing edges. DOFs associated to new elements need to be set.
const int sdim = orig_mesh.SpaceDimension();
auto *orig_fespace = orig_mesh.GetNodes()->FESpace();
SetCurvature(orig_fespace->GetMaxElementOrder(), orig_fespace->IsDGSpace(),
orig_mesh.SpaceDimension(), orig_fespace->GetOrdering());
// The dofs associated with vertices are unchanged, but there can be new dofs
// associated to edges, faces and volumes. Additionally, because we know that
// the set of vertices is unchanged by the splitting operation, we can use
// the vertices to map local coordinates of the "child" elements (the new
// simplices introduced), from the "parent" element (the quad, prism, hex
// that was split).
// For segment, triangle and tetrahedron, the dof values are copied directly.
// For the others, we have to construct a map from the Node locations in the
// new simplex to the parent non-simplex element. This could be sped up by
// not repeatedly access the original FE as the accesses will be coherent
// (i.e. all child elems are consecutive).
Array<int> edofs; // element dofs in new element
Array<int> parent_vertices, child_vertices; // vertices of parent and child.
Array<int> node_map; // node indices of parent from child.
Vector edofvals; // values of elements dofs in original element
// Storage for evaluating node function on parent element, at node locations
// of child element
DenseMatrix shape; // ndof_coarse x nnode_refined.
DenseMatrix point_matrix; // sdim x nnode_refined
IntegrationRule child_nodes_in_parent; // The parent nodes that correspond to the child nodes
for (int i = 0; i < parent_elements.Size(); i++)
{
const int ip = parent_elements[i];
const Geometry::Type orig_geom = orig_mesh.GetElementBaseGeometry(ip);
orig_mesh.GetNodes()->GetElementDofValues(ip, edofvals);
switch (orig_geom)
{
case Geometry::Type::SEGMENT : // fall through
case Geometry::Type::TRIANGLE : // fall through
case Geometry::Type::TETRAHEDRON :
GetNodes()->FESpace()->GetElementVDofs(i, edofs);
GetNodes()->SetSubVector(edofs, edofvals);
break;
case Geometry::Type::CUBE : // fall through
case Geometry::Type::PRISM : // fall through
case Geometry::Type::PYRAMID : // fall through
case Geometry::Type::SQUARE :
{
// Extract the vertices of parent and child, can then form the
// map from child reference coordinates to parent reference
// coordinates. Exploit the fact that for Nodes, the vertex
// entries come first, and their indexing matches the vertex
// numbering. Thus we have already have an inverse index map.
orig_mesh.GetElementVertices(ip, parent_vertices);
GetElementVertices(i, child_vertices);
node_map.SetSize(0);
for (auto cv : child_vertices)
for (int ipv = 0; ipv < parent_vertices.Size(); ipv++)
if (cv == parent_vertices[ipv])
{
node_map.Append(ipv);
break;
}
MFEM_ASSERT(node_map.Size() == Geometry::NumVerts[GetElementBaseGeometry(i)], "!");
// node_map now says which of the parent vertex nodes map to each
// of the child vertex nodes. Using this can build a basis in the
// parent element from child Node values, exploit the linearity
// to then transform all nodes.
child_nodes_in_parent.SetSize(0);
const auto *orig_FE = orig_mesh.GetNodes()->FESpace()->GetFE(ip);
for (auto pn : node_map)
{
child_nodes_in_parent.Append(orig_FE->GetNodes()[pn]);
}
const auto *simplex_FE = GetNodes()->FESpace()->GetFE(i);
shape.SetSize(orig_FE->GetDof(), simplex_FE->GetDof()); // One set of evaluations per simplex dof.
Vector col;
for (int j = 0; j < simplex_FE->GetNodes().Size(); j++)
{
const auto &simplex_node = simplex_FE->GetNodes()[j];
IntegrationPoint simplex_node_in_orig;
// Handle the 2D vs 3D case by multiplying .z by zero.
simplex_node_in_orig.Set3(
child_nodes_in_parent[0].x +
simplex_node.x * (child_nodes_in_parent[1].x - child_nodes_in_parent[0].x)
+ simplex_node.y * (child_nodes_in_parent[2].x - child_nodes_in_parent[0].x)
+ simplex_node.z * (child_nodes_in_parent[(sdim > 2) ? 3 : 0].x - child_nodes_in_parent[0].x),
child_nodes_in_parent[0].y +
simplex_node.x * (child_nodes_in_parent[1].y - child_nodes_in_parent[0].y)
+ simplex_node.y * (child_nodes_in_parent[2].y - child_nodes_in_parent[0].y)
+ simplex_node.z * (child_nodes_in_parent[(sdim > 2) ? 3 : 0].y - child_nodes_in_parent[0].y),
child_nodes_in_parent[0].z +
simplex_node.x * (child_nodes_in_parent[1].z - child_nodes_in_parent[0].z)
+ simplex_node.y * (child_nodes_in_parent[2].z - child_nodes_in_parent[0].z)
+ simplex_node.z * (child_nodes_in_parent[(sdim > 2) ? 3 : 0].z - child_nodes_in_parent[0].z));
shape.GetColumnReference(j, col);
orig_FE->CalcShape(simplex_node_in_orig, col);
}
// All the non-simplex basis functions have now been evaluated at
// all the simplex basis function node locations. Now evaluate
// the summations and place back into the Nodes vector.
orig_mesh.GetNodes()->GetElementDofValues(ip, edofvals);
// Dof values are always returned as
// [[x_1,x_2,x_3,...],
// [y_1,y_2,y_3,...],
// [z_1,z_2,z_3,...]]
DenseMatrix edofvals_mat(edofvals.GetData(), orig_FE->GetDof(), sdim);
point_matrix.SetSize(simplex_FE->GetDof(), sdim);
MultAtB(shape, edofvals_mat, point_matrix);
GetNodes()->FESpace()->GetElementVDofs(i, edofs);
GetNodes()->SetSubVector(edofs, point_matrix.GetData());
}
break;
case Geometry::Type::POINT : // fall through
case Geometry::Type::INVALID :
case Geometry::Type::NUM_GEOMETRIES :
MFEM_ABORT("Internal Error!");
}
}
}
Mesh Mesh::MakePeriodic(const Mesh &orig_mesh, const std::vector<int> &v2v)
{
Mesh periodic_mesh(orig_mesh, true); // Make a copy of the original mesh
@@ -14518,11 +14662,12 @@ MeshPartitioner::ExtractGridFunction(const MeshPart &mesh_part,
for (int loc_elem_id = 0; loc_elem_id < num_elems; loc_elem_id++)
{
const int glob_elem_id = elem_list[loc_elem_id];
auto glob_dt = global_gf.FESpace()->GetElementVDofs(glob_elem_id, gvdofs);
DofTransformation glob_dt, local_dt;
global_gf.FESpace()->GetElementVDofs(glob_elem_id, gvdofs, glob_dt);
global_gf.GetSubVector(gvdofs, loc_vals);
if (glob_dt) { glob_dt->InvTransformPrimal(loc_vals); }
auto local_dt = local_fespace.GetElementVDofs(loc_elem_id, lvdofs);
if (local_dt) { local_dt->TransformPrimal(loc_vals); }
glob_dt.InvTransformPrimal(loc_vals);
local_fespace.GetElementVDofs(loc_elem_id, lvdofs, local_dt);
local_dt.TransformPrimal(loc_vals);
local_gf->SetSubVector(lvdofs, loc_vals);
}
return local_gf;
+27 -1
View File
@@ -662,8 +662,34 @@ protected:
Array<int> &elem_vtx, Array<int> &attr) const;
// Internal helper used in MakeSimplicial (and ParMesh::MakeSimplicial).
void MakeSimplicial_(const Mesh &orig_mesh, int *vglobal);
/**
* @brief Internal helper user in MakeSimplicial (and
* ParMesh::MakeSimplicial). Optional return is used in assembling a higher
* order mesh.
* @details The construction of the higher order nodes must be separated out
* because the
*
* @param orig_mesh The mesh from to create the simplices
* @param vglobal An optional global ordering of vertices. Necessary for
* parallel splitting.
* @return Array<int> parent elements from the orig_mesh for each split
* element
*/
Array<int> MakeSimplicial_(const Mesh &orig_mesh, int *vglobal);
/**
* @brief Helper function for constructing higher order nodes from a mesh
* transformed into simplices. Only to be called as part of MakeSimplicial
* or ParMesh::MakeSimplicial.
*
* @param orig_mesh The mesh that was used to transform this mesh into
* simplices.
* @param parent_elements parent_elements[i] gives the element in orig_mesh
* split to give element i.
*/
void MakeHigherOrderSimplicial_(const Mesh &orig_mesh,
const Array<int> &parent_elements);
public:
/// @anchor mfem_Mesh_ctors
+6 -1
View File
@@ -1400,7 +1400,7 @@ ParMesh ParMesh::MakeSimplicial(ParMesh &orig_mesh)
{
vglobal[iv] = fes.GetGlobalTDofNumber(iv);
}
mesh.MakeSimplicial_(orig_mesh, vglobal);
auto parent_elements = mesh.MakeSimplicial_(orig_mesh, vglobal);
// count the number of entries in each row of group_s{vert,edge,face}
mesh.group_svert.MakeI(mesh.GetNGroups()-1); // exclude the local group 0
@@ -1517,6 +1517,11 @@ ParMesh ParMesh::MakeSimplicial(ParMesh &orig_mesh)
mesh.FinalizeParTopo();
if (orig_mesh.GetNodes() != nullptr)
{
mesh.MakeHigherOrderSimplicial_(orig_mesh, parent_elements);
}
return mesh;
}
+1
View File
@@ -37,3 +37,4 @@ add_subdirectory(tribol)
add_subdirectory(hooke)
add_subdirectory(dpg)
add_subdirectory(hdiv-linear-solver)
add_subdirectory(dfem)
+4 -4
View File
@@ -438,7 +438,7 @@ namespace mfem
namespace ad
{
/// MFEM native forward AD-type
typedef internal::dual<real_t, real_t> ADFloatType;
typedef future::dual<real_t, real_t> ADFloatType;
/// Vector type for AD-type numbers
typedef TAutoDiffVector<ADFloatType> ADVectorType;
/// Matrix type for AD-type numbers
@@ -532,7 +532,7 @@ class QVectorFuncAutoDiff
{
private:
/// MFEM native forward AD-type
typedef internal::dual<real_t, real_t> ADFType;
typedef future::dual<real_t, real_t> ADFType;
/// Vector type for AD-type numbers
typedef TAutoDiffVector<ADFType> ADFVector;
/// Matrix type for AD-type numbers
@@ -617,13 +617,13 @@ class QFunctionAutoDiff
{
private:
/// MFEM native AD-type for first derivatives
typedef internal::dual<real_t, real_t> ADFType;
typedef future::dual<real_t, real_t> ADFType;
/// Vector type for AD-numbers(first derivatives)
typedef TAutoDiffVector<ADFType> ADFVector;
/// Matrix type for AD-numbers(first derivatives)
typedef TAutoDiffDenseMatrix<ADFType> ADFDenseMatrix;
/// MFEM native AD-type for second derivatives
typedef internal::dual<ADFType, ADFType> ADSType;
typedef future::dual<ADFType, ADFType> ADSType;
/// Vector type for AD-numbers (second derivatives)
typedef TAutoDiffVector<ADSType> ADSVector;
/// Vector type for AD-numbers (second derivatives)
+24
View File
@@ -0,0 +1,24 @@
# 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.
if (MFEM_USE_MPI)
add_mfem_miniapp(dfem-minimal-surface
MAIN dfem-minimal-surface.cpp
LIBRARIES mfem)
# Add parallel tests.
if (MFEM_ENABLE_TESTING)
add_test(NAME dfem-minimal-surface=${MFEM_MPI_NP}
COMMAND ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} ${MFEM_MPI_NP}
${MPIEXEC_PREFLAGS} $<TARGET_FILE:dfem-minimal-surface> -der 0 -o 1 -r 2 -no-vis
${MPIEXEC_POSTFLAGS})
endif()
endif()
+566
View File
@@ -0,0 +1,566 @@
// 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.
//
// -------------------------------------
// Minimal Surface 2D Problem with dFEM
// -------------------------------------
//
// Compile with: make dfem-minimal-surface
//
// Sample runs: mpirun -np 4 dfem-minimal-surface -der 0
// mpirun -np 4 dfem-minimal-surface -der 0 -o 2
// mpirun -np 4 dfem-minimal-surface -der 0 -r 1
// mpirun -np 4 dfem-minimal-surface -der 1
// mpirun -np 4 dfem-minimal-surface -der 2
//
// Device sample runs:
// mpirun -np 4 dfem-minimal-surface -der 0 -r 1 -o 2 -d cuda
// mpirun -np 4 dfem-minimal-surface -der 1 -r 1 -o 2 -d cuda
// * mpirun -np 4 dfem-minimal-surface -der 0 -r 1 -o 2 -d hip
// * mpirun -np 4 dfem-minimal-surface -der 1 -r 1 -o 2 -d hip
//
// Description: This example code demonstrates the use of MFEM to solve the
// minimal surface problem in 2D:
//
// $ \min \left( -\nabla \cdot (1 / \sqrt(1 + |\nabla u|^2) \nabla u) \right) $
//
// with Dirichlet boundary conditions. The nonlinear problem is
// solved using Newton's method, where the necessary derivatives
// are computed in one of three ways (controlled by -der command
// line parameter):
//
// -der 0 = Automatic differentiation using Enzyme or dual type
// (default)
// -der 1 = Hand-coded derivatives
// -der 2 = Finite differences
//
// The example demonstrates the use of MFEM's nonlinear solvers,
// automatic differentiation capabilities, and GLVis/ParaView
// visualization.
#include "mfem.hpp"
using namespace mfem;
// This example code demonstrates the use of new features in MFEM that are in
// development but exposed through the mfem::future namespace. All features
// under this namespace might change their interface or behavior in upcoming
// releases until they have stabilized.
using namespace mfem::future;
using mfem::future::tensor;
// Derivative type enum
// This enum is used to specify the type of derivative computation.
// Possibilities are:
// - AUTODIFF, which uses automatic differentiation (Enzyme or dual type),
// - HANDCODED, which uses a manually implemented derivative, and
// - FD, finite difference.
enum DerivativeType
{
AUTODIFF,
HANDCODED,
FD
};
// Minimal surface operator.
//
// This class implements the minimal surface equation, which is a nonlinear
// operator that provides the residual.
template <typename dscalar_t, int dim = 2>
class MinimalSurface : public Operator
{
private:
static constexpr int SOLUTION_U = 1;
static constexpr int MESH_NODES = 2;
template <typename T>
MFEM_HOST_DEVICE inline
static auto coeff(const tensor<T, dim> &a)
{
return 1_r / sqrt(1_r + sqnorm(a));
}
// The 'nvcc' compiler needs MFApply and ManualDerivativeApply to be public.
public:
// Matrix-Free version of the pointwise residual form for the minimal
// surface equation.
struct MFApply
{
// Using DifferentiableOperator, we can define the residual form as a
// matrix-free operation. This allows us to compute the residual without
// explicitly forming any matrices or other large, temporary data
// structures.
//
// The inputs are the gradient of the solution in *reference coordinates*,
// the Jacobian of the coordinates, and the integration rule weights.
//
// The output is the residual in *physical coordinates* which also
// includes the necessary transformation from reference to physical
// coordinates for the gradient of the test function.
//
// Due to the description of how this pointwise operation is used in
// DifferentiableOperator, we know it is applied to the gradient of the
// test function in reference coordinates e.g.
// $ \int coeff(\nabla_x u) (\nabla_x u) J^{-T} \det(J) w
// (\nabla_{\xi} v) d\xi $
MFEM_HOST_DEVICE inline
auto operator()(
const tensor<dscalar_t, dim> &dudxi,
const tensor<real_t, dim, dim> &J,
const real_t &w) const
{
const auto invJ = inv(J);
const auto dudx = dudxi * invJ;
return tuple{coeff(dudx) * dudx * transpose(invJ) * det(J) * w};
}
};
// This is the derivative of the residual form with respect to the
// solution $u$.
//
// The inputs and outputs follow the same rules as the MFApply operator.
struct ManualDerivativeApply
{
MFEM_HOST_DEVICE inline
auto operator()(
const tensor<real_t, dim> &ddelta_udxi,
const tensor<real_t, dim> &dudxi,
const tensor<real_t, dim, dim> &J,
const real_t &w) const
{
const auto invJ = inv(J);
const auto dudx = dudxi * invJ;
const auto ddelta_udx = ddelta_udxi * invJ;
const auto c = coeff(dudx);
const auto term1 = c * ddelta_udx;
const auto term2 = c * c * c * dot(dudx, ddelta_udx) * dudx;
return tuple{(term1 - term2) * transpose(invJ) * det(J) * w};
}
};
private:
// This class implements the Jacobian of the minimal surface operator. It
// mostly acts as a wrapper to retrieve the Jacobian and apply essential
// boundary conditions appropriately.
class MinimalSurfaceJacobian : public Operator
{
public:
MinimalSurfaceJacobian(const MinimalSurface *minsurface,
const Vector &x) :
Operator(minsurface->Height()),
minsurface(minsurface),
z(minsurface->Height())
{
minsurface->u.SetFromTrueDofs(x);
auto mesh_nodes = static_cast<ParGridFunction*>
(minsurface->H1.GetParMesh()->GetNodes());
// One can retrieve the derivative of a DifferentiableOperator wrt a
// field variable if the derivative has been requested during the
// DifferentiableOperator::AddDomainIntegrator call.
dres_du = minsurface->res->GetDerivative(
SOLUTION_U, {&minsurface->u}, {mesh_nodes});
}
void Mult(const Vector &x, Vector &y) const override
{
z = x;
z.SetSubVector(minsurface->ess_tdofs, 0.0);
dres_du->Mult(z, y);
auto d_y = y.HostReadWrite();
const auto d_x = x.HostRead();
for (int i = 0; i < minsurface->ess_tdofs.Size(); i++)
{
d_y[minsurface->ess_tdofs[i]] = d_x[minsurface->ess_tdofs[i]];
}
}
// Pointer to the wrapped MinimalSurface operator
const MinimalSurface *minsurface = nullptr;
// Pointer to the DifferentiableOperator that computes the Jacobian
std::shared_ptr<DerivativeOperator> dres_du;
// Temporary vector
mutable Vector z;
};
// This class implements the Jacobian of the minimal surface operator using
// manually computed derivatives.
class MinimalSurfaceHandcodedJacobian : public Operator
{
// For the Jacobian action we need another field ID for the direction
// of u, called du, in dR/du = J * du.
static constexpr int DIRECTION_U = 3;
public:
MinimalSurfaceHandcodedJacobian(const MinimalSurface *minsurface,
const Vector &x) :
Operator(minsurface->Height()),
minsurface(minsurface),
z(minsurface->Height())
{
Array<int> all_domain_attr(minsurface->H1.GetMesh()->attributes.Max());
all_domain_attr = 1;
auto &mesh_nodes = *static_cast<ParGridFunction *>
(minsurface->H1.GetParMesh()->GetNodes());
auto &mesh_nodes_fes = *mesh_nodes.ParFESpace();
std::vector<FieldDescriptor> solutions =
{
{DIRECTION_U, &minsurface->H1}
};
std::vector<FieldDescriptor> parameters =
{
{SOLUTION_U, &minsurface->H1},
{MESH_NODES, &mesh_nodes_fes}
};
dres_du = std::make_shared<DifferentiableOperator>(
solutions, parameters, *minsurface->H1.GetParMesh());
auto input_operators = tuple
{
Gradient<DIRECTION_U>{},
Gradient<SOLUTION_U>{},
Gradient<MESH_NODES>{},
Weight{}
};
auto output_operators = tuple
{
Gradient<SOLUTION_U>{}
};
ManualDerivativeApply manual_derivative_apply;
dres_du->AddDomainIntegrator(manual_derivative_apply, input_operators,
output_operators, minsurface->ir,
all_domain_attr);
minsurface->u.SetFromTrueDofs(x);
dres_du->SetParameters({&minsurface->u, &mesh_nodes});
}
void Mult(const Vector &x, Vector &y) const override
{
z = x;
z.SetSubVector(minsurface->ess_tdofs, 0.0);
dres_du->Mult(z, y);
auto d_y = y.HostReadWrite();
const auto d_x = x.HostRead();
for (int i = 0; i < minsurface->ess_tdofs.Size(); i++)
{
d_y[minsurface->ess_tdofs[i]] = d_x[minsurface->ess_tdofs[i]];
}
}
const MinimalSurface *minsurface = nullptr;
std::shared_ptr<DifferentiableOperator> dres_du;
mutable Vector z;
};
public:
MinimalSurface(ParFiniteElementSpace &H1,
const IntegrationRule &ir,
int deriv_type = AUTODIFF) :
Operator(H1.GetTrueVSize(), H1.GetTrueVSize()),
H1(H1),
ir(ir),
u(&H1),
derivative_type(deriv_type)
{
Array<int> all_domain_attr(H1.GetMesh()->attributes.Max());
all_domain_attr = 1;
auto &mesh_nodes =
*static_cast<ParGridFunction *>(H1.GetParMesh()->GetNodes());
auto &mesh_nodes_fes = *mesh_nodes.ParFESpace();
// The following section is the heart of this example. It shows how to
// create and interact with the DifferentialOperator class.
// The constructor of DifferentiableOperator takes two vectors of
// FieldDescriptors. A FieldDescriptor can be viewed as a a pair of an
// identifier (the field ID) and it's accompanying space.
std::vector<FieldDescriptor> solutions;
solutions.push_back(FieldDescriptor(SOLUTION_U, &H1));
std::vector<FieldDescriptor> parameters;
parameters.push_back(FieldDescriptor(MESH_NODES, &mesh_nodes_fes));
// Create the DifferentiableOperator on the desired mesh.
res = std::make_shared<DifferentiableOperator>(
solutions, parameters, *H1.GetParMesh());
// DifferentiableOperator::AddIntegrator consists mainly of multiple
// components. The input and output operators and the pointwise
// "quadrature function" form a description of how the inputs and outputs
// to the pointwise function have to be treated.
// The input operators tuple consists of derived FieldOperator types.
// Here, we use Gradient<FIELD_ID> to signal that we request the gradient
// on the reference coordinates of the FIELD_ID field to be interpolated
// and translated to the pointwise function as the first and second input.
// Other choices are possible, e.g. Value<FIELD_ID> to interpolate the
// pointwise funciton. `Weight` is a special field that translates the
// integration rule weights to the input of the pointwise function.
auto input_operators = tuple
{
Gradient<SOLUTION_U>{},
Gradient<MESH_NODES>{},
Weight{}
};
// The output operators tuple also consists of derived FieldOperator
// types. Currently, only _one_ output operator is allowed. One should
// think of this as an operator on the output of a pointwise function. For
// example with the above input operators and the output operator below we
// create the following operator sequence:
//
// $ B^T D(B u, B x, w) $
//
// where B is the gradient interpolation operator, D is the pointwise
// function and u and x are solution and coordinate functions,
// respectively. The output operator is the gradient of the basis of the
// solution, which completes the "diffusion" like weak form.
auto output_operators = tuple
{
Gradient<SOLUTION_U>{}
};
// The pointwise function is defined as a lambda function. Here we just
// instantiate an object for it which is passed to
// DifferentiableOperator::AddDomainIntegrator.
MFApply mf_apply_qf;
// The integeger sequence is used to specify which derivatives of the
// formed integrator should be formed. This is necessary to specify at
// compile time in order to instantiate the correct functions.
auto derivatives = std::integer_sequence<size_t, SOLUTION_U> {};
res->AddDomainIntegrator(mf_apply_qf, input_operators, output_operators,
ir, all_domain_attr, derivatives);
// Before we are able to use DifferentiableOperator::Mult, we need to call
// DifferentiableOperator::SetParameters to set the parameters of the
// operator. Here, only the mesh node function is required. We do this
// here once, because we know that the nodes won't change. If they do,
// we'd have to call SetParameters before each call to Mult. This is done
// to be mathematically consistent with fixing paramaters.
res->SetParameters({&mesh_nodes});
Array<int> ess_bdr(H1.GetParMesh()->bdr_attributes.Max());
ess_bdr = 1;
H1.GetEssentialTrueDofs(ess_bdr, ess_tdofs);
}
void Mult(const Vector &x, Vector &y) const override
{
res->Mult(x, y);
y.SetSubVector(ess_tdofs, 0.0);
}
Operator& GetGradient(const Vector &x) const override
{
switch (derivative_type)
{
case FD:
fd_jac = std::make_shared<FDJacobian>(*this, x);
return *fd_jac;
case HANDCODED:
{
man_dres_du = std::make_shared<MinimalSurfaceHandcodedJacobian>(
this, x);
return *man_dres_du;
}
case AUTODIFF:
default:
dres_du = std::make_shared<MinimalSurfaceJacobian>(this, x);
return *dres_du;
}
}
private:
ParFiniteElementSpace &H1;
const IntegrationRule &ir;
mutable ParGridFunction u;
Array<int> ess_tdofs;
std::shared_ptr<DifferentiableOperator> res;
mutable std::shared_ptr<MinimalSurfaceJacobian> dres_du;
mutable std::shared_ptr<MinimalSurfaceHandcodedJacobian> man_dres_du;
mutable std::shared_ptr<FDJacobian> fd_jac;
int derivative_type;
};
// Boundary function for the minimal surface problem described by the Scherk
// surface.
// See https://en.wikipedia.org/wiki/Scherk_surface for more details.
real_t boundary_func(const Vector &coords)
{
const real_t x = coords(0);
const real_t y = coords(1);
if (coords.Size() == 3)
{
MFEM_ABORT("internal error");
}
const real_t a = 1.0e-2;
return log(cos(a * x) / cos(a * y)) / a;
}
int main(int argc, char *argv[])
{
// 1. Initialize MPI and HYPRE
Mpi::Init();
Hypre::Init();
// 2. Parse command-line options
int order = 1;
const char *device_config = "cpu";
bool visualization = true;
int refinements = 0;
int derivative_type = AUTODIFF;
OptionsParser args(argc, argv);
args.AddOption(&order, "-o", "--order",
"Finite element order (polynomial degree).");
args.AddOption(&device_config, "-d", "--device",
"Device configuration string, see Device::Configure().");
args.AddOption(&visualization, "-vis", "--visualization", "-no-vis",
"--no-visualization",
"Enable or disable GLVis visualization.");
args.AddOption(&refinements, "-r", "--refinements", "");
args.AddOption(&derivative_type, "-der", "--derivative-type",
"Derivative computation type: 0=AutomaticDifferentiation,"
" 1=HandCoded, 2=FiniteDifference");
args.ParseCheck();
// 3. Enable hardware devices such as GPUs, and programming models such as
// CUDA
Device device(device_config);
if (Mpi::Root()) { device.Print(); }
// 4. Create a 2D mesh on the square domain [-π/2,π/2]^2
Mesh mesh = Mesh::MakeCartesian2D(4, 4, Element::QUADRILATERAL);
mesh.SetCurvature(order);
auto transform_mesh = [](const Vector &cold, Vector &cnew)
{
cnew = cold;
cnew -= 0.5;
cnew *= M_PI;
};
mesh.Transform(transform_mesh);
// 5. Refine the mesh to increase the resolution
for (int i = 0; i < refinements; i++)
{
mesh.UniformRefinement();
}
int dim = mesh.Dimension();
// 6. Define a parallel mesh
ParMesh pmesh(MPI_COMM_WORLD, mesh);
mesh.Clear();
// 7. Define a parallel finite element space on the parallel mesh
H1_FECollection fec(order, dim);
ParFiniteElementSpace H1(&pmesh, &fec);
// 8. Set up the integration rule
const auto *ir = &IntRules.Get(pmesh.GetTypicalElementGeometry(),
2 * order + 1);
ParGridFunction u(&H1);
Vector X(H1.GetTrueVSize());
// 9. Create the nonlinear operator for the minimal surface equation
std::unique_ptr<Operator> minsurface;
#ifdef MFEM_USE_ENZYME
// When Enzyme is available, use it for automatic differentiation
minsurface = std::make_unique<MinimalSurface<real_t>>(H1, *ir,
derivative_type);
#else
// When Enzyme is not available, use the dual type for automatic
// differentiation
using mfem::future::dual;
using dual_t = dual<real_t, real_t>;
minsurface = std::make_unique<MinimalSurface<dual_t>>(H1, *ir,
derivative_type);
#endif
// 10. Set up and apply the boundary conditions
Array<int> ess_bdr(H1.GetParMesh()->bdr_attributes.Max());
ess_bdr = 1;
// 11. Set up the essential boundary conditions and initial condition
FunctionCoefficient boundary_coeff(boundary_func);
u.ProjectCoefficient(boundary_coeff);
u *= 1e-2;
u.ProjectBdrCoefficient(boundary_coeff, ess_bdr);
// 12. Set up the linear solver to be used within Newton's method
CGSolver krylov(MPI_COMM_WORLD);
krylov.SetAbsTol(0.0);
krylov.SetRelTol(1e-4);
krylov.SetMaxIter(500);
krylov.SetPrintLevel(2);
// 13. Set up the nonlinear solver (Newton) for the minimal surface equation
NewtonSolver newton(MPI_COMM_WORLD);
newton.SetOperator(*minsurface);
newton.SetAbsTol(0.0);
newton.SetRelTol(1e-6);
newton.SetMaxIter(10);
newton.SetSolver(krylov);
newton.SetPrintLevel(1);
// 14. Solve the nonlinear system using Newton's method
H1.GetRestrictionMatrix()->Mult(u, X);
Vector zero;
newton.Mult(zero, X);
H1.GetProlongationMatrix()->Mult(X, u);
// 15. Send the solution by socket to a GLVis server.
if (visualization)
{
char vishost[] = "localhost";
int visport = 19916;
socketstream sol_sock(vishost, visport);
sol_sock << "parallel "
<< Mpi::WorldSize() << " " << Mpi::WorldRank() << "\n";
sol_sock.precision(8);
sol_sock << "solution\n" << pmesh << u << std::flush;
}
// 16. Save the solution in parallel using ParaView format
ParaViewDataCollection dc("dfem-minimal-surface-output", &pmesh);
dc.SetHighOrderOutput(true);
dc.SetLevelsOfDetail(order);
dc.RegisterField("solution", &u);
dc.SetCycle(0);
dc.Save();
return 0;
}
+67
View File
@@ -0,0 +1,67 @@
# 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.
# Use the MFEM build directory
MFEM_DIR ?= ../..
MFEM_BUILD_DIR ?= ../..
MFEM_INSTALL_DIR ?= ../../mfem
SRC = $(if $(MFEM_DIR:../..=),$(MFEM_DIR)/miniapps/dfem/,)
CONFIG_MK = $(or $(wildcard $(MFEM_BUILD_DIR)/config/config.mk),\
$(wildcard $(MFEM_INSTALL_DIR)/share/mfem/config.mk))
MFEM_LIB_FILE = mfem_is_not_built
-include $(CONFIG_MK)
SEQ_EXAMPLES =
PAR_EXAMPLES = dfem-minimal-surface
ifeq ($(MFEM_USE_MPI),NO)
EXAMPLES = $(SEQ_EXAMPLES)
else
EXAMPLES = $(PAR_EXAMPLES) $(SEQ_EXAMPLES)
endif
.SUFFIXES:
.SUFFIXES: .o .cpp .mk
.PHONY: all clean clean-build clean-exec
# Remove built-in rule
%: %.cpp
# Replace the default implicit rule for *.cpp files
%: $(SRC)%.cpp $(MFEM_LIB_FILE) $(CONFIG_MK)
$(MFEM_CXX) $(MFEM_FLAGS) $< -o $@ $(MFEM_LIBS)
all: $(EXAMPLES)
MFEM_TESTS = EXAMPLES
include $(MFEM_TEST_MK)
# Testing: Parallel vs. serial runs
RUN_MPI = $(MFEM_MPIEXEC) $(MFEM_MPIEXEC_NP) $(MFEM_MPI_NP)
%-test-par: %
@$(call mfem-test,$<, $(RUN_MPI), Parallel example)
%-test-seq: %
@$(call mfem-test,$<,, Serial example)
# Testing: "test" target and mfem-test* variables are defined in config/test.mk
# Generate an error message if the MFEM library is not built and exit
$(MFEM_LIB_FILE):
$(error The MFEM library is not built)
clean: clean-build clean-exec
clean-build:
rm -f *.o *~ $(SEQ_EXAMPLES) $(PAR_EXAMPLES)
rm -rf *.dSYM *.TVD.*breakpoints
clean-exec:
@rm -rf dfem-minimal-surface-output
+12 -12
View File
@@ -79,7 +79,7 @@ enum prob_type
};
prob_type prob;
Vector beta;
Vector beta_;
real_t epsilon;
real_t exact_u(const Vector & X);
@@ -120,7 +120,7 @@ int main(int argc, char *argv[])
"Theta parameter for AMR");
args.AddOption(&iprob, "-prob", "--problem", "Problem case"
" 0: manufactured, 1: Erickson-Johnson ");
args.AddOption(&beta, "-beta", "--beta",
args.AddOption(&beta_, "-beta", "--beta",
"Vector Coefficient beta");
args.AddOption(&static_cond, "-sc", "--static-condensation", "-no-sc",
"--no-static-condensation", "Enable static condensation.");
@@ -147,11 +147,11 @@ int main(int argc, char *argv[])
int dim = mesh.Dimension();
MFEM_VERIFY(dim > 1, "Dimension = 1 is not supported in this example");
if (beta.Size() == 0)
if (beta_.Size() == 0)
{
beta.SetSize(dim);
beta = 0.0;
beta[0] = 1.;
beta_.SetSize(dim);
beta_ = 0.0;
beta_[0] = 1.;
}
args.PrintOptions(std::cout);
@@ -199,10 +199,10 @@ int main(int argc, char *argv[])
ConstantCoefficient eps2(1/(epsilon*epsilon));
ConstantCoefficient negeps(-epsilon);
VectorConstantCoefficient betacoeff(beta);
Vector negbeta = beta; negbeta.Neg();
DenseMatrix bbt(beta.Size());
MultVVt(beta, bbt);
VectorConstantCoefficient betacoeff(beta_);
Vector negbeta = beta_; negbeta.Neg();
DenseMatrix bbt(beta_.Size());
MultVVt(beta_, bbt);
MatrixConstantCoefficient bbtcoeff(bbt);
VectorConstantCoefficient negbetacoeff(negbeta);
@@ -598,7 +598,7 @@ void exact_hatf(const Vector & X, Vector & hatf)
hatf.SetSize(X.Size());
for (int i = 0; i<hatf.Size(); i++)
{
hatf[i] = beta[i] * u - sigma[i];
hatf[i] = beta_[i] * u - sigma[i];
}
}
@@ -612,7 +612,7 @@ real_t f_exact(const Vector & X)
real_t s = 0;
for (int i = 0; i<du.Size(); i++)
{
s += beta[i] * du[i];
s += beta_[i] * du[i];
}
return -epsilon * d2u + s;
}
+10 -10
View File
@@ -91,7 +91,7 @@ static const char *enum_str[] =
};
prob_type prob;
Vector beta;
Vector beta_;
real_t epsilon;
real_t exact_u(const Vector & X);
@@ -141,7 +141,7 @@ int main(int argc, char *argv[])
"Theta parameter for AMR");
args.AddOption(&iprob, "-prob", "--problem", "Problem case"
" 0: lshape, 1: General");
args.AddOption(&beta, "-beta", "--beta",
args.AddOption(&beta_, "-beta", "--beta",
"Vector Coefficient beta");
args.AddOption(&static_cond, "-sc", "--static-condensation", "-no-sc",
"--no-static-condensation", "Enable static condensation.");
@@ -181,19 +181,19 @@ int main(int argc, char *argv[])
case sinusoidal:
case EJ:
{
if (beta.Size() == 0)
if (beta_.Size() == 0)
{
beta.SetSize(dim);
beta = 0.0;
beta[0] = 1.;
beta_.SetSize(dim);
beta_ = 0.0;
beta_[0] = 1.;
}
break;
}
case bdr_layer:
{
beta.SetSize(dim);
beta[0] = 1.;
beta[1] = 2.;
beta_.SetSize(dim);
beta_[0] = 1.;
beta_[1] = 2.;
exact_known = false;
}
break;
@@ -846,7 +846,7 @@ void beta_function(const Vector & X, Vector & beta_val)
}
else
{
beta_val = beta;
beta_val = beta_;
}
}
+7 -13
View File
@@ -431,7 +431,7 @@ void BlockStaticCondensation::AssembleReducedSystem(int el,
}
// Assemble global mat and rhs
DofTransformation * doftrans_i, *doftrans_j;
DofTransformation doftrans_i, doftrans_j;
Array<int> faces, ori;
int dim = mesh->Dimension();
@@ -459,7 +459,7 @@ void BlockStaticCondensation::AssembleReducedSystem(int el,
{
if (!tr_fes[i]) { continue; }
Array<int> vdofs_i;
doftrans_i = nullptr;
doftrans_i.SetDofTransformation(nullptr);
if (IsTraceSpace[i])
{
Array<int> face_vdofs;
@@ -472,14 +472,14 @@ void BlockStaticCondensation::AssembleReducedSystem(int el,
}
else
{
doftrans_i = tr_fes[i]->GetElementVDofs(el, vdofs_i);
tr_fes[i]->GetElementVDofs(el, vdofs_i, doftrans_i);
}
int skip_j=0;
for (int j = 0; j<tr_fes.Size(); j++)
{
if (!tr_fes[j]) { continue; }
Array<int> vdofs_j;
doftrans_j = nullptr;
doftrans_j.SetDofTransformation(nullptr);
if (IsTraceSpace[j])
{
@@ -493,16 +493,13 @@ void BlockStaticCondensation::AssembleReducedSystem(int el,
}
else
{
doftrans_j = tr_fes[j]->GetElementVDofs(el, vdofs_j);
tr_fes[j]->GetElementVDofs(el, vdofs_j, doftrans_j);
}
DenseMatrix Ae;
rmatptr->GetSubMatrix(offsets[i],offsets[i+1],
offsets[j],offsets[j+1], Ae);
if (doftrans_i || doftrans_j)
{
TransformDual(doftrans_i, doftrans_j, Ae);
}
TransformDual(doftrans_i, doftrans_j, Ae);
S->GetBlock(skip_i,skip_j).AddSubMatrix(vdofs_i,vdofs_j, Ae);
skip_j++;
}
@@ -513,10 +510,7 @@ void BlockStaticCondensation::AssembleReducedSystem(int el,
// ref subvector
vec1.SetDataAndSize(&data[offsets[i]],
offsets[i+1]-offsets[i]);
if (doftrans_i)
{
doftrans_i->TransformDual(vec1);
}
doftrans_i.TransformDual(vec1);
y->GetBlock(skip_i).AddElementVector(vdofs_i,vec1);
skip_i++;
}
+9 -16
View File
@@ -470,8 +470,7 @@ void ComplexBlockStaticCondensation::AssembleReducedSystem(int el,
}
// Assemble global mat and rhs
DofTransformation * doftrans_i, *doftrans_j;
DofTransformation doftrans_i, doftrans_j;
Array<int> faces, ori;
int dim = mesh->Dimension();
@@ -499,7 +498,7 @@ void ComplexBlockStaticCondensation::AssembleReducedSystem(int el,
{
if (!tr_fes[i]) { continue; }
Array<int> vdofs_i;
doftrans_i = nullptr;
doftrans_i.SetDofTransformation(nullptr);
if (IsTraceSpace[i])
{
Array<int> face_vdofs;
@@ -512,14 +511,14 @@ void ComplexBlockStaticCondensation::AssembleReducedSystem(int el,
}
else
{
doftrans_i = tr_fes[i]->GetElementVDofs(el, vdofs_i);
tr_fes[i]->GetElementVDofs(el, vdofs_i, doftrans_i);
}
int skip_j=0;
for (int j = 0; j<tr_fes.Size(); j++)
{
if (!tr_fes[j]) { continue; }
Array<int> vdofs_j;
doftrans_j = nullptr;
doftrans_j.SetDofTransformation(nullptr);
if (IsTraceSpace[j])
{
@@ -533,7 +532,7 @@ void ComplexBlockStaticCondensation::AssembleReducedSystem(int el,
}
else
{
doftrans_j = tr_fes[j]->GetElementVDofs(el, vdofs_j);
tr_fes[j]->GetElementVDofs(el, vdofs_j, doftrans_j);
}
DenseMatrix Ae_r, Ae_i;
@@ -541,11 +540,8 @@ void ComplexBlockStaticCondensation::AssembleReducedSystem(int el,
offsets[j],offsets[j+1], Ae_r);
rmat->imag().GetSubMatrix(offsets[i],offsets[i+1],
offsets[j],offsets[j+1], Ae_i);
if (doftrans_i || doftrans_j)
{
TransformDual(doftrans_i, doftrans_j, Ae_r);
TransformDual(doftrans_i, doftrans_j, Ae_i);
}
TransformDual(doftrans_i, doftrans_j, Ae_r);
TransformDual(doftrans_i, doftrans_j, Ae_i);
S_r->GetBlock(skip_i,skip_j).AddSubMatrix(vdofs_i,vdofs_j, Ae_r);
S_i->GetBlock(skip_i,skip_j).AddSubMatrix(vdofs_i,vdofs_j, Ae_i);
skip_j++;
@@ -555,11 +551,8 @@ void ComplexBlockStaticCondensation::AssembleReducedSystem(int el,
Vector vec1_r(*rvecptr_real, offsets[i], offsets[i+1]-offsets[i]);
Vector vec1_i(*rvecptr_imag, offsets[i], offsets[i+1]-offsets[i]);
// ref subvector
if (doftrans_i)
{
doftrans_i->TransformDual(vec1_r);
doftrans_i->TransformDual(vec1_i);
}
doftrans_i.TransformDual(vec1_r);
doftrans_i.TransformDual(vec1_i);
y_r->GetBlock(skip_i).AddElementVector(vdofs_i,vec1_r);
y_i->GetBlock(skip_i).AddElementVector(vdofs_i,vec1_i);
skip_i++;
+15 -23
View File
@@ -320,7 +320,7 @@ void ComplexDPGWeakForm::Assemble(int skip_zeros)
ElementTransformation *eltrans;
Array<int> faces, ori;
DofTransformation * doftrans_i, *doftrans_j;
DofTransformation doftrans_i, doftrans_j;
if (mat_r == NULL)
{
AllocMat();
@@ -540,7 +540,7 @@ void ComplexDPGWeakForm::Assemble(int skip_zeros)
for (int i = 0; i<trial_fes.Size(); i++)
{
Array<int> vdofs_i;
doftrans_i = nullptr;
doftrans_i.SetDofTransformation(nullptr);
if (IsTraceFes[i])
{
Array<int> face_vdofs;
@@ -553,12 +553,12 @@ void ComplexDPGWeakForm::Assemble(int skip_zeros)
}
else
{
doftrans_i = trial_fes[i]->GetElementVDofs(iel, vdofs_i);
trial_fes[i]->GetElementVDofs(iel, vdofs_i, doftrans_i);
}
for (int j = 0; j < trial_fes.Size(); j++)
{
Array<int> vdofs_j;
doftrans_j = nullptr;
doftrans_j.SetDofTransformation(nullptr);
if (IsTraceFes[j])
{
@@ -572,7 +572,7 @@ void ComplexDPGWeakForm::Assemble(int skip_zeros)
}
else
{
doftrans_j = trial_fes[j]->GetElementVDofs(iel, vdofs_j);
trial_fes[j]->GetElementVDofs(iel, vdofs_j, doftrans_j);
}
DenseMatrix Ae_r, Ae_i;
@@ -580,11 +580,8 @@ void ComplexDPGWeakForm::Assemble(int skip_zeros)
trial_offs[j],trial_offs[j+1], Ae_r);
A->imag().GetSubMatrix(trial_offs[i],trial_offs[i+1],
trial_offs[j],trial_offs[j+1], Ae_i);
if (doftrans_i || doftrans_j)
{
TransformDual(doftrans_i, doftrans_j, Ae_r);
TransformDual(doftrans_i, doftrans_j, Ae_i);
}
TransformDual(doftrans_i, doftrans_j, Ae_r);
TransformDual(doftrans_i, doftrans_j, Ae_i);
if (!mat_r)
{
mfem::out << "null matrix " << std::endl;
@@ -597,11 +594,9 @@ void ComplexDPGWeakForm::Assemble(int skip_zeros)
Vector vec1_r(b_r,trial_offs[i],trial_offs[i+1]-trial_offs[i]);
Vector vec1_i(b_i,trial_offs[i],trial_offs[i+1]-trial_offs[i]);
if (doftrans_i)
{
doftrans_i->TransformDual(vec1_r);
doftrans_i->TransformDual(vec1_i);
}
doftrans_i.TransformDual(vec1_r);
doftrans_i.TransformDual(vec1_i);
y_r->GetBlock(i).AddElementVector(vdofs_i,vec1_r);
y_i->GetBlock(i).AddElementVector(vdofs_i,vec1_i);
}
@@ -980,11 +975,11 @@ Vector & ComplexDPGWeakForm::ComputeResidual(const Vector & x)
int nn = trial_offs.Last();
u.SetSize(2*nn);
DofTransformation * doftrans = nullptr;
DofTransformation doftrans;
for (int i = 0; i<trial_fes.Size(); i++)
{
vdofs.SetSize(0);
doftrans = nullptr;
doftrans.SetDofTransformation(nullptr);
if (IsTraceFes[i])
{
Array<int> face_vdofs;
@@ -997,7 +992,7 @@ Vector & ComplexDPGWeakForm::ComputeResidual(const Vector & x)
}
else
{
doftrans = trial_fes[i]->GetElementVDofs(iel, vdofs);
trial_fes[i]->GetElementVDofs(iel, vdofs, doftrans);
}
Vector vec1_r;
Vector vec1_i;
@@ -1005,11 +1000,8 @@ Vector & ComplexDPGWeakForm::ComputeResidual(const Vector & x)
vec1_i.MakeRef(u, trial_offs[i]+nn, trial_offs[i+1]-trial_offs[i]);
x_r.GetBlock(i).GetSubVector(vdofs,vec1_r);
x_i.GetBlock(i).GetSubVector(vdofs,vec1_i);
if (doftrans)
{
doftrans->InvTransformPrimal(vec1_r);
doftrans->InvTransformPrimal(vec1_i);
}
doftrans.InvTransformPrimal(vec1_r);
doftrans.InvTransformPrimal(vec1_i);
} // end of loop through trial spaces
// residual
+11 -20
View File
@@ -249,7 +249,7 @@ void DPGWeakForm::Assemble(int skip_zeros)
ElementTransformation *eltrans;
Array<int> faces, ori;
DofTransformation * doftrans_i, *doftrans_j;
DofTransformation doftrans_i, doftrans_j;
if (mat == NULL)
{
AllocMat();
@@ -412,7 +412,7 @@ void DPGWeakForm::Assemble(int skip_zeros)
for (int i = 0; i<trial_fes.Size(); i++)
{
Array<int> vdofs_i;
doftrans_i = nullptr;
doftrans_i.SetDofTransformation(nullptr);
if (IsTraceFes[i])
{
Array<int> face_vdofs;
@@ -425,12 +425,12 @@ void DPGWeakForm::Assemble(int skip_zeros)
}
else
{
doftrans_i = trial_fes[i]->GetElementVDofs(iel, vdofs_i);
trial_fes[i]->GetElementVDofs(iel, vdofs_i, doftrans_i);
}
for (int j = 0; j<trial_fes.Size(); j++)
{
Array<int> vdofs_j;
doftrans_j = nullptr;
doftrans_j.SetDofTransformation(nullptr);
if (IsTraceFes[j])
{
@@ -444,16 +444,13 @@ void DPGWeakForm::Assemble(int skip_zeros)
}
else
{
doftrans_j = trial_fes[j]->GetElementVDofs(iel, vdofs_j);
trial_fes[j]->GetElementVDofs(iel, vdofs_j, doftrans_j);
}
DenseMatrix Ae;
A.GetSubMatrix(trial_offs[i],trial_offs[i+1],
trial_offs[j],trial_offs[j+1], Ae);
if (doftrans_i || doftrans_j)
{
TransformDual(doftrans_i, doftrans_j, Ae);
}
TransformDual(doftrans_i, doftrans_j, Ae);
mat->GetBlock(i,j).AddSubMatrix(vdofs_i,vdofs_j, Ae);
}
@@ -463,10 +460,7 @@ void DPGWeakForm::Assemble(int skip_zeros)
// ref subvector
vec1.SetDataAndSize(&data[trial_offs[i]],
trial_offs[i+1]-trial_offs[i]);
if (doftrans_i)
{
doftrans_i->TransformDual(vec1);
}
doftrans_i.TransformDual(vec1);
y->GetBlock(i).AddElementVector(vdofs_i,vec1);
}
}
@@ -771,11 +765,11 @@ Vector & DPGWeakForm::ComputeResidual(const BlockVector & x)
u.SetSize(trial_offs.Last());
real_t * data = u.GetData();
DofTransformation * doftrans = nullptr;
DofTransformation doftrans;
for (int i = 0; i<trial_fes.Size(); i++)
{
vdofs.SetSize(0);
doftrans = nullptr;
doftrans.SetDofTransformation(nullptr);
if (IsTraceFes[i])
{
Array<int> face_vdofs;
@@ -788,16 +782,13 @@ Vector & DPGWeakForm::ComputeResidual(const BlockVector & x)
}
else
{
doftrans = trial_fes[i]->GetElementVDofs(iel, vdofs);
trial_fes[i]->GetElementVDofs(iel, vdofs, doftrans);
}
Vector vec1;
vec1.SetDataAndSize(&data[trial_offs[i]],
trial_offs[i+1]-trial_offs[i]);
x.GetBlock(i).GetSubVector(vdofs,vec1);
if (doftrans)
{
doftrans->InvTransformPrimal(vec1);
}
doftrans.InvTransformPrimal(vec1);
} // end of loop through trial spaces
Vector v(Bmat[iel]->Height());
+5 -16
View File
@@ -49,30 +49,19 @@
// Sample runs:
//
// Current source in a sphere with absorbing boundary conditions:
// mpirun -np 4 maxwell -m ../../data/ball-nurbs.mesh -rs 2
// -abcs '-1'
// -dp '-0.3 0.0 0.0 0.3 0.0 0.0 0.1 1 .5 .5'
// mpirun -np 4 maxwell -m ../../data/ball-nurbs.mesh -rs 2 -abcs '-1' -dp '-0.3 0.0 0.0 0.3 0.0 0.0 0.1 1 .5 .5'
//
// Current source in a metal sphere with dielectric and conducting materials:
// mpirun -np 4 maxwell -m ../../data/ball-nurbs.mesh -rs 2
// -dbcs '-1'
// -dp '-0.3 0.0 0.0 0.3 0.0 0.0 0.1 1 .5 .5'
// -cs '0.0 0.0 -0.5 .2 3e6'
// -ds '0.0 0.0 0.5 .2 10'
// mpirun -np 4 maxwell -m ../../data/ball-nurbs.mesh -rs 2 -dbcs '-1' -dp '-0.3 0.0 0.0 0.3 0.0 0.0 0.1 1 .5 .5' -cs '0.0 0.0 -0.5 .2 3e6' -ds '0.0 0.0 0.5 .2 10'
//
// Current source in a metal box:
// mpirun -np 4 maxwell -m ../../data/fichera.mesh -rs 3
// -ts 0.25 -tf 10 -dbcs '-1'
// -dp '-0.5 -0.5 0.0 -0.5 -0.5 1.0 0.1 1 .5 1'
// mpirun -np 4 maxwell -m ../../data/fichera.mesh -rs 3 -ts 0.25 -tf 10 -dbcs '-1' -dp '-0.5 -0.5 0.0 -0.5 -0.5 1.0 0.1 1 .5 1'
//
// Current source with a mixture of absorbing and reflecting boundaries:
// mpirun -np 4 maxwell -m ../../data/fichera.mesh -rs 3
// -ts 0.25 -tf 10
// -dp '-0.5 -0.5 0.0 -0.5 -0.5 1.0 0.1 1 .5 1'
// -dbcs '4 8 19 21' -abcs '5 18'
// mpirun -np 4 maxwell -m ../../data/fichera.mesh -rs 3 -ts 0.25 -tf 10 -dp '-0.5 -0.5 0.0 -0.5 -0.5 1.0 0.1 1 .5 1' -dbcs '4 8 19 21' -abcs '5 18'
//
// By default the sources and fields are all zero:
// mpirun -np 4 maxwell
// * mpirun -np 4 maxwell
#include "maxwell_solver.hpp"
#include <fstream>
+10 -19
View File
@@ -122,7 +122,8 @@ int main (int argc, char *argv[])
"Enable or disable GLVis visualization.");
args.AddOption(&search_on_rank_0, "-sr0", "--search-on-r0", "-no-sr0",
"--no-search-on-r0",
"Enable search only on rank 0 (disable to search points on all tasks).");
"Enable search only on rank 0 (disable to search points on all tasks). "
"All points added by other procs are ignored.");
args.AddOption(&hrefinement, "-hr", "--h-refinement", "-no-hr",
"--no-h-refinement",
"Do random h refinements to mesh (does not work for pyramids).");
@@ -130,15 +131,15 @@ int main (int argc, char *argv[])
"Ordering of points to be found."
"0 (default): byNodes, 1: byVDIM");
args.AddOption(&gf_ordering, "-gfo", "--gridfunc-ordering",
"Ordering of fespace that will be used for grid function to be interpolated."
"Ordering of fespace that will be used for grid function to be interpolated. "
"0 (default): byNodes, 1: byVDIM");
args.AddOption(&devopt, "-d", "--device",
"Device configuration string, see Device::Configure().");
args.AddOption(&randomization, "-random", "--random",
"0: generate points randomly in the bounding box of domain,"
"0: generate points randomly in the bounding box of domain, "
"1: generate points randomly inside each element in mesh.");
args.AddOption(&npt, "-npt", "--npt",
"# points per proc or element");
"# points / rank initialized on entire mesh (random = 0) or every element (random = 1).");
args.AddOption(&visport, "-p", "--send-port", "Socket for GLVis.");
args.Parse();
@@ -167,9 +168,6 @@ int main (int argc, char *argv[])
randomization = 0;
}
Vector xmin, xmax;
mesh->GetBoundingBox(xmin, xmax);
if (myid == 0)
{
cout << "Mesh curvature of the original mesh: ";
@@ -184,7 +182,7 @@ int main (int argc, char *argv[])
mesh->GetBoundingBox(pos_min, pos_max, mesh_poly_deg);
if (myid == 0)
{
cout << "--- Generating equidistant point for:\n"
cout << "--- Generating points for:\n"
<< "x in [" << pos_min(0) << ", " << pos_max(0) << "]\n"
<< "y in [" << pos_min(1) << ", " << pos_max(1) << "]" << std::endl;
if (dim == 3)
@@ -283,9 +281,8 @@ int main (int argc, char *argv[])
}
}
// Generate equidistant points in physical coordinates over the whole mesh.
// Note that some points might be outside, if the mesh is not a box. Note
// also that all tasks search the same points (not mandatory).
// Generate random points in physical coordinates over the whole mesh.
// Note that some points might be outside if the mesh is not a box.
int pts_cnt = npt;
Vector vxyz;
vxyz.UseDevice(!cpu_mode);
@@ -370,13 +367,6 @@ int main (int argc, char *argv[])
// finder.SetGPUtoCPUFallback(true);
finder.FindPoints(vxyz, point_ordering);
Array<unsigned int> code_out1 = finder.GetCode();
Array<unsigned int> el_out1 = finder.GetGSLIBElem();
Vector ref_rst1 = finder.GetGSLIBReferencePosition();
Vector ref_rst0 = finder.GetReferencePosition();
Vector dist1 = finder.GetDist();
Array<unsigned int> proc_out1 = finder.GetProc();
finder.Interpolate(field_vals, interp_vals);
if (interp_vals.UseDevice())
{
@@ -442,7 +432,8 @@ int main (int argc, char *argv[])
cout << setprecision(16)
<< "Total number of elements: " << nelemglob
<< "\nTotal number of procs: " << num_procs
<< "\nSearched total points: " << pts_cnt*num_procs
<< "\nSearched total points: " << (search_on_rank_0 ? pts_cnt :
pts_cnt*num_procs)
<< "\nFound locally on ranks: " << found_loc
<< "\nFound on other tasks: " << found_away
<< "\nPoints not found: " << not_found
@@ -15,8 +15,8 @@
#include "kernel_helpers.hpp"
#include "linalg/vector.hpp"
using mfem::internal::tensor;
using mfem::internal::make_tensor;
using mfem::future::tensor;
using mfem::future::make_tensor;
namespace mfem
{
+2 -2
View File
@@ -16,14 +16,14 @@
#include "general/forall.hpp"
#include "linalg/tensor.hpp"
using mfem::internal::tensor;
namespace mfem
{
namespace KernelHelpers
{
using mfem::future::tensor;
// MFEM_SHARED_3D_BLOCK_TENSOR definition
// Should be moved in backends/cuda/hip header files.
#if defined(__CUDA_ARCH__)
+4 -4
View File
@@ -15,7 +15,7 @@
#include "linalg/tensor.hpp"
#include "mfem.hpp"
using mfem::internal::tensor;
using mfem::future::tensor;
/** @brief Linear elastic material.
*
@@ -33,7 +33,7 @@ template <int dim> struct LinearElasticMaterial
tensor<mfem::real_t, dim, dim>
MFEM_HOST_DEVICE stress(const tensor<mfem::real_t, dim, dim> &dudx) const
{
constexpr auto I = mfem::internal::IsotropicIdentity<dim>();
constexpr auto I = mfem::future::IsotropicIdentity<dim>();
auto epsilon = sym(dudx);
return lambda * tr(epsilon) * I + 2.0 * mu * epsilon;
}
@@ -61,8 +61,8 @@ template <int dim> struct LinearElasticMaterial
tensor<mfem::real_t, dim, dim, dim, dim>
MFEM_HOST_DEVICE gradient(tensor<mfem::real_t, dim, dim> /* dudx */) const
{
return mfem::internal::make_tensor<dim, dim, dim, dim>([&](int i, int j, int k,
int l)
return mfem::future::make_tensor<dim, dim, dim, dim>([&](int i, int j, int k,
int l)
{
return lambda * (i == j) * (k == l) +
mu * ((i == l) * (j == k) + (i == k) * (j == l));
+6 -6
View File
@@ -17,8 +17,8 @@
#include "linalg/tensor.hpp"
#include "mfem.hpp"
using mfem::internal::tensor;
using mfem::internal::make_tensor;
using mfem::future::tensor;
using mfem::future::make_tensor;
/**
* @brief Neo-Hookean material
@@ -51,7 +51,7 @@ struct NeoHookeanMaterial
MFEM_HOST_DEVICE tensor<T, dim, dim>
stress(const tensor<T, dim, dim> &dudx) const
{
static constexpr auto I = mfem::internal::IsotropicIdentity<dim>();
static constexpr auto I = mfem::future::IsotropicIdentity<dim>();
T J = det(I + dudx);
T p = -2.0 * D1 * J * (J - 1);
auto devB = dev(dudx + transpose(dudx) + dot(dudx, transpose(dudx)));
@@ -89,7 +89,7 @@ struct NeoHookeanMaterial
MFEM_HOST_DEVICE tensor<mfem::real_t, dim, dim, dim, dim>
gradient(tensor<mfem::real_t, dim, dim> dudx) const
{
static constexpr auto I = mfem::internal::IsotropicIdentity<dim>();
static constexpr auto I = mfem::future::IsotropicIdentity<dim>();
tensor<mfem::real_t, dim, dim> F = I + dudx;
tensor<mfem::real_t, dim, dim> invF = inv(F);
@@ -153,7 +153,7 @@ struct NeoHookeanMaterial
{
auto sigma = stress(make_tensor<dim, dim>([&](int i, int j)
{
return mfem::internal::dual<mfem::real_t, mfem::real_t> {dudx[i][j], ddudx[i][j]};
return mfem::future::dual<mfem::real_t, mfem::real_t> {dudx[i][j], ddudx[i][j]};
}));
return make_tensor<dim, dim>(
[&](int i, int j) { return sigma[i][j].gradient; });
@@ -210,7 +210,7 @@ struct NeoHookeanMaterial
action_of_gradient_symbolic(const tensor<mfem::real_t, dim, dim> &du_dx,
const tensor<mfem::real_t, dim, dim> &ddu_dx) const
{
static constexpr auto I = mfem::internal::IsotropicIdentity<dim>();
static constexpr auto I = mfem::future::IsotropicIdentity<dim>();
tensor<mfem::real_t, dim, dim> F = I + du_dx;
tensor<mfem::real_t, dim, dim> invFT = inv(transpose(F));
@@ -13,7 +13,7 @@
#include "general/forall.hpp"
#include "linalg/tensor.hpp"
using mfem::internal::tensor;
using mfem::future::tensor;
namespace mfem
{
+5 -2
View File
@@ -276,7 +276,7 @@ int main(int argc, char *argv[])
if (fespaceDim == 1)
{
const real_t h1error = CheckH1Continuity(x);
cout << myid << ": H1 continuity error " << h1error << endl;
if (myid == 0) { cout << "H1 continuity error " << h1error << endl; }
MFEM_VERIFY(h1error < 1.0e-12, "H1 continuity is not satisfied");
}
@@ -420,7 +420,10 @@ real_t CheckH1Continuity(ParGridFunction & x)
}
}
return errorMax;
real_t errorMaxGlobal = 0.0;
MPI_Allreduce(&errorMax, &errorMaxGlobal, 1, MFEM_MPI_REAL_T, MPI_MAX,
fes->GetComm());
return errorMaxGlobal;
}
void f_exact(const Vector &x, Vector &f)
+1 -1
View File
@@ -181,4 +181,4 @@ clean-build:
clean-exec:
@rm -f refined.mesh sin-fit.mesh ex5.mesh exsol.mesh mesh.* sol.* mode_* naca-cmesh.mesh sol_?.gf
@rm -rf Example1* Example3* Example5* Solenoidal_* ParaView
@rm -rf CurveInt Naca_cmesh glvis_naca-cmesh.mesh
@rm -rf CurveInt Naca_cmesh glvis_naca-cmesh.mesh solution.dat
+2 -2
View File
@@ -15,8 +15,8 @@
//
// Compile with: make nurbs_curveint
//
// Sample runs: ./nurbs_curveint -uw -n 9
// ./nurbs_curveint -nw -n 9
// Sample runs: nurbs_curveint -uw -n 9
// nurbs_curveint -nw -n 9
//
// Description: This example code demonstrates the use of MFEM to interpolate a
// curve in a NURBS patch. We first define a square shaped NURBS
+3 -8
View File
@@ -4,9 +4,9 @@
//
// Sample runs: nurbs_ex24 -m ../../data/pipe-nurbs-2d.mesh -o 2
// nurbs_ex24 -m ../../data/pipe-nurbs-2d.mesh -p 2
// nurbs_ex24 -m ../../data/cube-nurbs.mesh -o 2
// nurbs_ex24 -m ../../data/cube-nurbs.mesh -o 2 -p 1
// nurbs_ex24 -m ../../data/cube-nurbs.mesh -o 2 -p 2
// nurbs_ex24 -m ../../data/cube-nurbs.mesh -o 2 -r 3
// nurbs_ex24 -m ../../data/cube-nurbs.mesh -o 2 -p 1 -r 3
// nurbs_ex24 -m ../../data/cube-nurbs.mesh -o 2 -p 2 -r 3
// nurbs_ex24 -m ../../data/escher.mesh
// nurbs_ex24 -m ../../data/escher.mesh -o 2
// nurbs_ex24 -m ../../data/fichera.mesh
@@ -15,11 +15,6 @@
// nurbs_ex24 -m ../../data/amr-quad.mesh -o 2
// nurbs_ex24 -m ../../data/amr-hex.mesh
//
// Device sample runs -- do not work for NURBS:
// nurbs_ex24 -m ../../data/escher.mesh -pa -d cuda
// nurbs_ex24 -m ../../data/escher.mesh -pa -d raja-cuda
// nurbs_ex24 -m ../../data/escher.mesh -pa -d raja-omp
//
// Description: This example code illustrates usage of mixed finite element
// spaces, with three variants:
//
-5
View File
@@ -36,11 +36,6 @@
//
// We recommend viewing examples 1-4 before viewing this example.
// Sample runs: nurbs_ex3 -m ../../data/square-nurbs.mesh
// nurbs_ex3 -m ../../data/square-nurbs.mesh -o 2
// nurbs_ex3 -m ../../data/cube-nurbs.mesh
#include "mfem.hpp"
#include <fstream>
#include <iostream>
+1 -2
View File
@@ -11,8 +11,7 @@
//
// Compile with: make nurbs_naca_cmesh
//
// Sample run: ./nurbs_naca_cmesh -ntail 80 -nbnd 80 -ntip 20 -nwake 40
// -sw 2.0 -sbnd 2.5 -stip 1.1 -aoa 3
// Sample run: nurbs_naca_cmesh -ntail 80 -nbnd 80 -ntip 20 -nwake 40 -sw 2.0 -sbnd 2.5 -stip 1.1 -aoa 3
//
// Description: This example code demonstrates the use of MFEM to create a
// C-mesh around a NACA-foil section. The foil section is defined
+7 -1
View File
@@ -68,7 +68,13 @@ MFEM_PERF_CXXFLAGS = $$(MFEM_PERF_CXXFLAGS_$$(MFEM_PERF_SW))
# Add MFEM_PERF_CXXFLAGS to MFEM_CXXFLAGS:
ifeq (YES,$$(MFEM_USE_CUDA))
ifneq (,$$(MFEM_PERF_CXXFLAGS))
MFEM_CXXFLAGS += -Xcompiler="$$(MFEM_PERF_CXXFLAGS)"
ifneq ($$(MFEM_HOST_CXX),$$(MFEM_CXX))
# using nvcc
MFEM_CXXFLAGS += -Xcompiler="$$(MFEM_PERF_CXXFLAGS)"
else
# using clang+cuda
MFEM_CXXFLAGS += $$(MFEM_PERF_CXXFLAGS)
endif
endif
else
MFEM_CXXFLAGS += $$(MFEM_PERF_CXXFLAGS)
+7 -7
View File
@@ -34,19 +34,19 @@
// Problem 1: Circular hole of radius 0.2 at the center of the domain.
// Solves -nabla^2 u = 1 with homogeneous boundary conditions.
// Dirichlet boundary condition
// mpirun -np 4 diffusion -rs 3 -o 1 -vis -lst 1
// mpirun -np 4 diffusion -m ../../data/inline-hex.mesh -rs 2 -o 2 -vis -lst 1 -ho 1 -alpha 10
// mpirun -np 4 diffusion -rs 3 -o 1 -lst 1
// mpirun -np 4 diffusion -m ../../data/inline-hex.mesh -rs 2 -o 2 -lst 1 -ho 1 -alpha 10
// Neumann boundary condition
// mpirun -np 4 diffusion -rs 3 -o 1 -vis -nlst 1 -ho 1
// mpirun -np 4 diffusion -rs 3 -o 1 -nlst 1 -ho 1
//
// Problem 2: Circular hole of radius 0.2 at the center of the domain.
// Solves -nabla^2 u = f with inhomogeneous boundary conditions,
// and f is setup such that u = x^p + y^p, where p = 2 by default.
// This is a 2D convergence test.
// Dirichlet BC
// mpirun -np 4 diffusion -rs 2 -o 2 -vis -lst 2
// mpirun -np 4 diffusion -rs 2 -o 2 -lst 2
// Neumann BC (inhomogeneous condition derived using exact solution)
// mpirun -np 4 diffusion -rs 2 -o 2 -vis -nlst 2 -ho 1
// mpirun -np 4 diffusion -rs 2 -o 2 -nlst 2 -ho 1
//
// Problem 3: Domain is y = [0, 1] but mesh is shifted to [-1.e-4, 1].
// Solves -nabla^2 u = f with inhomogeneous boundary conditions,
@@ -57,7 +57,7 @@
// the finite element space and the number of high-order terms
// (-ho) to be included from the Taylor expansion used to enforce
// the boundary conditions.
// mpirun -np 4 diffusion -rs 2 -o 1 -vis -lst 3
// mpirun -np 4 diffusion -rs 2 -o 1 -lst 3
//
// Problem 4: Complex 2D / 3D shapes:
// Solves -nabla^2 u = 1 with homogeneous boundary conditions.
@@ -69,7 +69,7 @@
// Problem 5: Circular hole with homogeneous Neumann, triangular hole with
// inhomogeneous Dirichlet, and a square hole with homogeneous
// Dirichlet boundary condition.
// mpirun -np 4 diffusion -rs 3 -o 1 -vis -lst 5 -ho 1 -nlst 7 -alpha 10.0 -dc
// mpirun -np 4 diffusion -rs 3 -o 1 -lst 5 -ho 1 -nlst 7 -alpha 10.0 -dc
#include "mfem.hpp"
#include "../common/mfem-common.hpp"
+10 -10
View File
@@ -65,17 +65,17 @@
// latter may only work for order 1 on GPUs.
//
// Sample runs:
// lor_elast -m ../../data/beam-tri.mesh
// lor_elast -m ../../data/beam-quad.mesh
// lor_elast -m ../../data/beam-hex.mesh
// mpirun -np 4 lor_elast -m ../../data/beam-hex.mesh -l 3 -vdim
// mpirun -np 4 lor_elast -m ../../data/beam-hex.mesh -l 3 -vdim -elast
//
// ./lor_elast -m ../../data/beam-tri.mesh
// ./lor_elast -m ../../data/beam-quad.mesh
// ./lor_elast -m ../../data/beam-hex.mesh
// mpirun -np 4 ./lor_elast -m ../../data/beam-hex.mesh -l 5 -vdim
// mpirun -np 4 ./lor_elast -m ../../data/beam-hex.mesh -l 5 -vdim -elast
// ./lor_elast --device cuda -m ../../data/beam-hex.mesh -l 4 -o 2 -pa
// ./lor_elast --device cuda -m ../../data/beam-hex.mesh -l 4 -o 2 -pa -pv
// ./lor_elast --device cuda -m ../../data/beam-hex.mesh -l 4 -o 2 -pa -ss
// ./lor_elast --device cuda -m ../../data/beam-hex.mesh -l 4 -o 2 -pa -ca
// ./lor_elast --device cuda -m ../../data/beam-hex.mesh -l 5 -ca
// Device sample runs:
// lor_elast --device cuda -m ../../data/beam-hex.mesh -l 3 -o 2 -pa
// lor_elast --device cuda -m ../../data/beam-hex.mesh -l 3 -o 2 -pa -ss
// lor_elast --device cuda -m ../../data/beam-hex.mesh -l 3 -o 2 -pa -ca
// lor_elast --device cuda -m ../../data/beam-hex.mesh -l 3 -ca
//
// References:
// [1] Mihajlović, M.D. and Mijalković, S., "A component decomposition
+4 -5
View File
@@ -56,16 +56,15 @@
// lor_solvers -m ../../data/amr-quad.mesh -fe h
// lor_solvers -m ../../data/amr-quad.mesh -fe n
// lor_solvers -m ../../data/amr-quad.mesh -fe r
// lor_solvers -m ../../data/amr-quad.mesh -fe l
// lor_solvers -m ../../data/star-surf.mesh -fe h
// lor_solvers -m ../../data/star-surf.mesh -fe n
// lor_solvers -m ../../data/star-surf.mesh -fe r
//
// Device sample runs:
// lor_solvers -fe h -d cuda
// lor_solvers -fe n -d cuda
// lor_solvers -fe r -d cuda
// lor_solvers -fe l -d cuda
// * lor_solvers -fe h -d cuda
// * lor_solvers -fe n -d cuda
// * lor_solvers -fe r -d cuda
// * lor_solvers -fe l -d cuda
#include "mfem.hpp"
#include <fstream>
+4 -5
View File
@@ -54,16 +54,15 @@
// mpirun -np 4 plor_solvers -m ../../data/fichera.mesh -fe r
// mpirun -np 4 plor_solvers -m ../../data/fichera.mesh -fe l
// mpirun -np 4 plor_solvers -m ../../data/amr-hex.mesh -fe h -rs 0 -o 2
// mpirun -np 4 plor_solvers -m ../../data/amr-hex.mesh -fe l -rs 0 -o 2
// mpirun -np 4 plor_solvers -m ../../data/star-surf.mesh -fe h
// mpirun -np 4 plor_solvers -m ../../data/star-surf.mesh -fe n
// mpirun -np 4 plor_solvers -m ../../data/star-surf.mesh -fe r
//
// Device sample runs:
// mpirun -np 4 plor_solvers -m ../../data/fichera.mesh -fe h -d cuda
// mpirun -np 4 plor_solvers -m ../../data/fichera.mesh -fe n -d cuda
// mpirun -np 4 plor_solvers -m ../../data/fichera.mesh -fe r -d cuda
// mpirun -np 4 plor_solvers -m ../../data/fichera.mesh -fe l -d cuda
// * mpirun -np 4 plor_solvers -m ../../data/fichera.mesh -fe h -d cuda
// * mpirun -np 4 plor_solvers -m ../../data/fichera.mesh -fe n -d cuda
// * mpirun -np 4 plor_solvers -m ../../data/fichera.mesh -fe r -d cuda
// * mpirun -np 4 plor_solvers -m ../../data/fichera.mesh -fe l -d cuda
#include "mfem.hpp"
#include <fstream>
+3 -3
View File
@@ -37,9 +37,9 @@
// plor-transfer -m ../../data/star-q2.mesh -lref 5 -p 4
// plor-transfer -m ../../data/star-mixed.mesh -lref 3 -p 2
// plor-transfer -lref 4 -o 4 -lo 0 -p 1
// plor-transfer -lref 5 -o 4 -lo 0 -p 1
// plor-transfer -lref 5 -o 4 -lo 3 -p 2
// plor-transfer -lref 5 -o 4 -lo 0 -p 3
// plor-transfer -lref 4 -o 4 -lo 0 -p 1
// plor-transfer -lref 4 -o 4 -lo 2 -p 2
// plor-transfer -lref 4 -o 4 -lo 0 -p 3
#include "mfem.hpp"
#include <fstream>
+4
View File
@@ -17,6 +17,9 @@ include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR})
# The following list can be updated using (in bash):
# for d in general linalg mesh fem enzyme; do ls -1 $d/*.cpp; done
set(UNIT_TESTS_SRCS
dfem/test_diffusion.cpp
dfem/test_divergence.cpp
dfem/test_mass.cpp
general/test_array.cpp
general/test_reduction.cpp
general/test_arrays_by_name.cpp
@@ -89,6 +92,7 @@ set(UNIT_TESTS_SRCS
fem/test_fe.cpp
fem/test_fe_compatibility.cpp
fem/test_fe_fixed.cpp
fem/test_fe_pos.cpp
fem/test_fe_symmetry.cpp
fem/test_get_value.cpp
fem/test_getderivative.cpp
+319
View File
@@ -0,0 +1,319 @@
// 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 "../unit_tests.hpp"
#include "mfem.hpp"
#include <utility>
#ifdef MFEM_USE_MPI
using namespace mfem;
using namespace mfem::future;
using mfem::future::tensor;
#ifdef MFEM_USE_ENZYME
using dscalar_t = real_t;
#else
using mfem::future::dual;
using dscalar_t = dual<real_t, real_t>;
#endif
using DOperator = DifferentiableOperator;
namespace dfem_pa_kernels
{
template <int DIM> struct Diffusion
{
using dvecd_t = tensor<dscalar_t, DIM>;
using matd_t = tensor<real_t, DIM, DIM>;
struct MFApply
{
MFEM_HOST_DEVICE inline auto operator()(const dvecd_t &dudxi,
const real_t &rho,
const matd_t &J,
const real_t &w) const
{
const auto invJ = inv(J), TinJ = transpose(invJ);
return tuple{ (dudxi * invJ) * TinJ * det(J) * w * rho };
}
};
struct PASetup
{
MFEM_HOST_DEVICE inline auto operator()(const real_t u,
const real_t &rho,
const matd_t &J,
const real_t &w) const
{
return tuple{ inv(J) * transpose(inv(J)) * det(J) * w * rho };
}
};
struct PAApply
{
MFEM_HOST_DEVICE inline auto operator()(const dvecd_t &dudxi,
const matd_t &q) const
{
return tuple{ q * dudxi };
};
};
};
template <int DIM>
void DFemDiffusion(const char *filename, int p, const int r)
{
CAPTURE(filename, DIM, p, r);
Mesh smesh(filename);
ParMesh pmesh(MPI_COMM_WORLD, smesh);
MFEM_VERIFY(pmesh.Dimension() == DIM, "Mesh dimension mismatch");
pmesh.EnsureNodes();
auto *nodes = static_cast<ParGridFunction *>(pmesh.GetNodes());
p = std::max(p, pmesh.GetNodalFESpace()->GetMaxElementOrder());
smesh.Clear();
Array<int> all_domain_attr;
if (pmesh.attributes.Size() > 0)
{
all_domain_attr.SetSize(pmesh.attributes.Max());
all_domain_attr = 1;
}
H1_FECollection fec(p, DIM);
ParFiniteElementSpace pfes(&pmesh, &fec);
ParFiniteElementSpace *mfes = nodes->ParFESpace();
const int NE = pfes.GetNE(), d1d(p + 1), q = 2 * p + r;
const auto *ir = &IntRules.Get(pmesh.GetTypicalElementGeometry(), q);
const int q1d(IntRules.Get(Geometry::SEGMENT, ir->GetOrder()).GetNPoints());
MFEM_VERIFY(d1d <= q1d, "q1d should be >= d1d");
ParGridFunction x(&pfes), y(&pfes), z(&pfes);
Vector X(pfes.GetTrueVSize()), Y(pfes.GetTrueVSize()), Z(pfes.GetTrueVSize());
X.Randomize(1);
x.SetFromTrueDofs(X);
auto rho = [](const Vector &xyz)
{
const real_t x = xyz(0), y = xyz(1), z = DIM == 3 ? xyz(2) : 0.0;
real_t r = M_PI * pow(x, 2);
if (DIM >= 2) { r += pow(y, 3); }
if (DIM >= 3) { r += pow(z, 4); }
return r;
};
FunctionCoefficient rho_coeff(rho);
ParBilinearForm blf_fa(&pfes);
blf_fa.AddDomainIntegrator(new DiffusionIntegrator(rho_coeff, ir));
blf_fa.Assemble();
blf_fa.Finalize();
SECTION("[Partial assembly] Diffusion")
{
ParBilinearForm blf_pa(&pfes);
blf_pa.AddDomainIntegrator(new DiffusionIntegrator(rho_coeff, ir));
blf_pa.SetAssemblyLevel(AssemblyLevel::PARTIAL);
blf_pa.Assemble();
blf_pa.Mult(x, z);
blf_fa.Mult(x, y);
y -= z;
REQUIRE(y.Normlinf() == MFEM_Approx(0.0));
MPI_Barrier(MPI_COMM_WORLD);
}
QuadratureSpace qs(pmesh, *ir);
CoefficientVector rho_coeff_cv(rho_coeff, qs);
MFEM_VERIFY(rho_coeff_cv.GetVDim() == 1, "Coefficient should be scalar");
MFEM_VERIFY(rho_coeff_cv.Size() == q1d * q1d * (DIM == 3 ? q1d : 1) * NE, "");
UniformParameterSpace rho_ps(pmesh, *ir, 1);
static constexpr int U = 0, Coords = 1, Rho = 3;
const auto sol = std::vector{ FieldDescriptor{ U, &pfes } };
SECTION("[dFEM Matrix free] Diffusion")
{
DOperator dop_mf(sol, {{Rho, &rho_ps}, {Coords, mfes}}, pmesh);
typename Diffusion<DIM>::MFApply mf_apply_qf;
dop_mf.AddDomainIntegrator(mf_apply_qf,
tuple{ Gradient<U>{}, Identity<Rho>{},
Gradient<Coords>{}, Weight{} },
tuple{ Gradient<U>{} }, *ir,
all_domain_attr);
dop_mf.SetParameters({ &rho_coeff_cv, nodes });
pfes.GetRestrictionMatrix()->Mult(x, X);
dop_mf.Mult(X, Z);
blf_fa.Mult(x, y);
pfes.GetProlongationMatrix()->MultTranspose(y, Y);
Y -= Z;
real_t norm_global = 0.0;
real_t norm_local = Y.Normlinf();
MPI_Allreduce(&norm_local, &norm_global, 1, MPI_DOUBLE, MPI_MAX,
pmesh.GetComm());
REQUIRE(norm_global == MFEM_Approx(0.0));
MPI_Barrier(MPI_COMM_WORLD);
}
SECTION("[dFEM Partial assembly] Diffusion")
{
static constexpr int QData = 2;
UniformParameterSpace qd_ps(pmesh, *ir, DIM * DIM);
ParameterFunction qdata(qd_ps);
qdata.UseDevice(true);
DOperator dSetup(sol, {{Rho, &rho_ps}, {Coords, mfes}, {QData, &qd_ps}}, pmesh);
typename Diffusion<DIM>::PASetup pa_setup_qf;
dSetup.AddDomainIntegrator(
pa_setup_qf,
tuple{ Value<U>{}, Identity<Rho>{}, Gradient<Coords>{}, Weight{} },
tuple{ Identity<QData>{} }, *ir, all_domain_attr);
dSetup.SetParameters({ &rho_coeff_cv, nodes, &qdata });
pfes.GetRestrictionMatrix()->Mult(x, X);
dSetup.Mult(X, qdata);
DOperator dop_pa(sol, { { QData, &qd_ps } }, pmesh);
typename Diffusion<DIM>::PAApply pa_apply_qf;
dop_pa.AddDomainIntegrator(pa_apply_qf,
tuple{ Gradient<U>{}, Identity<QData>{} },
tuple{ Gradient<U>{} },
*ir, all_domain_attr);
dop_pa.SetParameters({ &qdata });
pfes.GetRestrictionMatrix()->Mult(x, X);
dop_pa.Mult(X, Z);
blf_fa.Mult(x, y);
pfes.GetProlongationMatrix()->MultTranspose(y, Y);
Y -= Z;
real_t norm_global = 0.0;
real_t norm_local = Y.Normlinf();
MPI_Allreduce(&norm_local, &norm_global, 1, MPI_DOUBLE, MPI_MAX,
pmesh.GetComm());
REQUIRE(norm_global == MFEM_Approx(0.0));
MPI_Barrier(MPI_COMM_WORLD);
}
SECTION("[dFEM Linearization] Diffusion")
{
DOperator dop_mf(sol, {{Rho, &rho_ps}, {Coords, mfes}}, pmesh);
typename Diffusion<DIM>::MFApply mf_apply_qf;
auto derivatives = std::integer_sequence<size_t, U> {};
dop_mf.AddDomainIntegrator(mf_apply_qf,
tuple{ Gradient<U>{}, Identity<Rho>{},
Gradient<Coords>{}, Weight{} },
tuple{ Gradient<U>{} }, *ir,
all_domain_attr, derivatives);
dop_mf.SetParameters({ &rho_coeff_cv, nodes });
auto dRdU = dop_mf.GetDerivative(U, {&x}, {&rho_coeff_cv, nodes});
pfes.GetRestrictionMatrix()->Mult(x, X);
dop_mf.Mult(X, Z);
blf_fa.Mult(x, y);
pfes.GetProlongationMatrix()->MultTranspose(y, Y);
Y -= Z;
real_t norm_global = 0.0;
real_t norm_local = Y.Normlinf();
MPI_Allreduce(&norm_local, &norm_global, 1, MPI_DOUBLE, MPI_MAX,
pmesh.GetComm());
REQUIRE(norm_global == MFEM_Approx(0.0));
MPI_Barrier(MPI_COMM_WORLD);
}
SECTION("[dFEM Matrix free] vector diffusion")
{
ParFiniteElementSpace vpfes(&pmesh, &fec, DIM);
ParGridFunction vx(&vpfes), vy(&vpfes);
Vector vX(vpfes.GetTrueVSize()), vY(vpfes.GetTrueVSize()),
vZ(vpfes.GetTrueVSize());
vX.Randomize(1), vx.SetFromTrueDofs(vX);
{
const auto vsol = std::vector{ FieldDescriptor{ U, &vpfes } };
DOperator dop_mf(vsol, {{Coords, mfes}}, pmesh);
const auto mf_vector_diffusion_qf =
[] MFEM_HOST_DEVICE (const tensor<dscalar_t, DIM, DIM> &dudxi,
const tensor<real_t, DIM, DIM> &J,
const real_t &w)
{
const auto invJ = inv(J), TinJ = transpose(invJ);
return tuple{ (dudxi * invJ) * TinJ * det(J) * w };
};
dop_mf.AddDomainIntegrator(mf_vector_diffusion_qf,
tuple{ Gradient<U>{}, Gradient<Coords>{}, Weight{} },
tuple{ Gradient<U>{} },
*ir, all_domain_attr);
dop_mf.SetParameters({ nodes });
vpfes.GetRestrictionMatrix()->Mult(vx, vX), dop_mf.Mult(vX, vZ);
}
{
ConstantCoefficient one(1.0);
ParBilinearForm vblf_fa(&vpfes);
vblf_fa.AddDomainIntegrator(new VectorDiffusionIntegrator(one, ir));
vblf_fa.Assemble(), vblf_fa.Finalize();
vblf_fa.Mult(vx, vy), vpfes.GetProlongationMatrix()->MultTranspose(vy, vY);
}
vY -= vZ;
real_t norm_global = 0.0, norm_local = vY.Normlinf();
MPI_Allreduce(&norm_local, &norm_global, 1, MPI_DOUBLE, MPI_MAX,
pmesh.GetComm());
REQUIRE(norm_global == MFEM_Approx(0.0));
MPI_Barrier(MPI_COMM_WORLD);
}
}
TEST_CASE("DFEM Diffusion", "[Parallel][DFEM]")
{
const bool all_tests = launch_all_non_regression_tests;
const auto p = !all_tests ? 2 : GENERATE(1, 2, 3);
const auto r = !all_tests ? 2 : GENERATE(0, 1, 2, 3);
SECTION("2D p=" + std::to_string(p) + " r=" + std::to_string(r))
{
const auto filename =
GENERATE("../../data/star.mesh",
"../../data/star-q3.mesh",
"../../data/rt-2d-q3.mesh",
"../../data/inline-quad.mesh",
"../../data/periodic-square.mesh");
DFemDiffusion<2>(filename, p, r);
}
SECTION("3D p=" + std::to_string(p) + " r=" + std::to_string(r))
{
const auto filename =
GENERATE("../../data/fichera.mesh",
"../../data/fichera-q3.mesh",
"../../data/inline-hex.mesh",
"../../data/toroid-hex.mesh",
"../../data/periodic-cube.mesh");
DFemDiffusion<3>(filename, p, r);
}
}
} // namespace dfem_pa_kernels
#endif // MFEM_USE_MPI
+157
View File
@@ -0,0 +1,157 @@
// 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 "../unit_tests.hpp"
#include "mfem.hpp"
#ifdef MFEM_USE_MPI
using namespace mfem;
using namespace mfem::future;
using mfem::future::tensor;
#ifdef MFEM_USE_ENZYME
using dscalar_t = real_t;
#else
using mfem::future::dual;
using dscalar_t = dual<real_t, real_t>;
#endif
namespace dfem_pa_kernels
{
template <int DIM>
void dFemVectorDivergence(const char *filename, int p)
{
CAPTURE(filename, DIM, p);
Mesh smesh(filename);
ParMesh pmesh(MPI_COMM_WORLD, smesh);
MFEM_VERIFY(pmesh.Dimension() == DIM, "Mesh dimension mismatch");
pmesh.EnsureNodes();
auto *nodes = static_cast<ParGridFunction *>(pmesh.GetNodes());
p = std::max(p, pmesh.GetNodalFESpace()->GetMaxElementOrder());
smesh.Clear();
Array<int> all_domain_attr;
if (pmesh.attributes.Size() > 0)
{
all_domain_attr.SetSize(pmesh.attributes.Max());
all_domain_attr = 1;
}
H1_FECollection fec(p, DIM);
ParFiniteElementSpace psfes(&pmesh, &fec);
ParFiniteElementSpace pvfes(&pmesh, &fec, DIM);
const int d1d(p + 1), q = 3 * p + 1;
const auto *ir = &IntRules.Get(pmesh.GetTypicalElementGeometry(), q);
const int q1d(IntRules.Get(Geometry::SEGMENT, ir->GetOrder()).GetNPoints());
MFEM_VERIFY(d1d <= q1d, "q1d should be >= d1d");
ParGridFunction vx(&pvfes);
ParGridFunction sy(&psfes), sz(&psfes);
Vector vX(pvfes.GetTrueVSize());
Vector sY(psfes.GetTrueVSize()), sZ(psfes.GetTrueVSize());
vX.Randomize(1), vx.SetFromTrueDofs(vX);
MixedBilinearForm mblf_fa(&pvfes, &psfes);
mblf_fa.AddDomainIntegrator(new VectorDivergenceIntegrator);
mblf_fa.Assemble(), mblf_fa.Finalize();
mblf_fa.Mult(vx, sy);
MixedBilinearForm mblf_pa(&pvfes, &psfes);
mblf_pa.AddDomainIntegrator(new VectorDivergenceIntegrator);
mblf_pa.SetAssemblyLevel(AssemblyLevel::PARTIAL);
mblf_pa.Assemble();
mblf_pa.Mult(vx, sz);
sy -= sz;
REQUIRE(sy.Normlinf() == MFEM_Approx(0.0));
MPI_Barrier(MPI_COMM_WORLD);
{
static constexpr int P = 0, V = 1, Coords = 2;
ParFiniteElementSpace *mfes = nodes->ParFESpace();
const auto solutions = std::vector{ FieldDescriptor{ P, &psfes } };
const auto parameters = std::vector
{
FieldDescriptor{ V, &pvfes },
FieldDescriptor{ Coords, mfes }
};
DifferentiableOperator dop_mf(solutions, parameters, pmesh);
const auto mf_vector_divergence_qf =
[] MFEM_HOST_DEVICE(const tensor<dscalar_t, DIM, DIM> &dudxi,
const tensor<mfem::real_t, DIM, DIM> &J,
const real_t &w)
{
const auto invJ = inv(J);
const auto dudx = dudxi * invJ;
return tuple{ tr(dudx) * det(J) * w };
};
dop_mf.AddDomainIntegrator(mf_vector_divergence_qf,
tuple{ Gradient<V>{}, Gradient<Coords>{}, Weight{} },
tuple{ Value<P>{} },
*ir, all_domain_attr);
dop_mf.SetParameters({ &vx, nodes });
Vector unused(pvfes.GetTrueVSize());
dop_mf.Mult(unused, sZ);
mblf_fa.Mult(vx, sy);
psfes.GetProlongationMatrix()->MultTranspose(sy, sY);
sY -= sZ;
real_t norm_global = M_PI, norm_local = sY.Normlinf();
MPI_Allreduce(&norm_local, &norm_global, 1, MPI_DOUBLE, MPI_MAX,
pmesh.GetComm());
REQUIRE(norm_global == MFEM_Approx(0.0));
MPI_Barrier(MPI_COMM_WORLD);
}
}
TEST_CASE("dFEM VectorDivergence", "[Parallel][DFEM][VectorDivergence]")
{
const bool all_tests = launch_all_non_regression_tests;
const auto p = !all_tests ? 2 : GENERATE(1, 2, 3);
SECTION("2D p=" + std::to_string(p))
{
const auto filename =
GENERATE("../../data/star.mesh",
"../../data/star-q3.mesh",
"../../data/rt-2d-q3.mesh",
"../../data/inline-quad.mesh",
"../../data/periodic-square.mesh");
dFemVectorDivergence<2>(filename, p);
}
SECTION("3D p=" + std::to_string(p))
{
const auto filename =
GENERATE("../../data/fichera.mesh",
"../../data/fichera-q3.mesh",
"../../data/inline-hex.mesh",
"../../data/toroid-hex.mesh",
"../../data/periodic-cube.mesh");
dFemVectorDivergence<3>(filename, p);
}
}
} // namespace dfem_pa_kernels
#endif // MFEM_USE_MPI
+134
View File
@@ -0,0 +1,134 @@
// 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 "../unit_tests.hpp"
#include "mfem.hpp"
#ifdef MFEM_USE_MPI
using namespace mfem;
using namespace mfem::future;
using mfem::future::tensor;
namespace dfem_pa_kernels
{
template <int DIM>
void dfem_functional(const char *filename, int p, const int r)
{
CAPTURE(filename, DIM, p, r);
Mesh smesh(filename);
ParMesh pmesh(MPI_COMM_WORLD, smesh);
pmesh.EnsureNodes();
auto *nodes = static_cast<ParGridFunction *>(pmesh.GetNodes());
p = std::max(p, pmesh.GetNodalFESpace()->GetMaxElementOrder());
smesh.Clear();
Array<int> all_domain_attr;
if (pmesh.attributes.Size() > 0)
{
all_domain_attr.SetSize(pmesh.attributes.Max());
all_domain_attr = 1;
}
H1_FECollection fec(p, DIM);
ParFiniteElementSpace fes(&pmesh, &fec);
const auto *ir = &IntRules.Get(pmesh.GetTypicalElementGeometry(), 2 * p + r);
ParGridFunction x(&fes), y(&fes), z(&fes);
Vector X(fes.GetTrueVSize()), Y(fes.GetTrueVSize()), Z(fes.GetTrueVSize());
X = 1.0;
x.SetFromTrueDofs(X);
ConstantCoefficient one(1.0);
ParBilinearForm blf(&fes);
blf.AddDomainIntegrator(new MassIntegrator(one, ir));
blf.SetAssemblyLevel(AssemblyLevel::PARTIAL);
blf.Assemble();
blf.Mult(x, y);
fes.GetProlongationMatrix()->MultTranspose(y, Y);
real_t sum_g, sum_l = Y.Sum();
MPI_Allreduce(&sum_l, &sum_g, 1, MPI_DOUBLE, MPI_SUM, pmesh.GetComm());
static constexpr int U = 0, Coords = 1;
const auto sol = std::vector{ FieldDescriptor{ U, &fes } };
DifferentiableOperator dop(sol, {{Coords, nodes->ParFESpace()}}, pmesh);
const auto functional_qf =
[] MFEM_HOST_DEVICE(
#ifdef MFEM_USE_ENZYME
const real_t &u,
#else
const dual<real_t, real_t> &u,
#endif
const tensor<real_t, DIM, DIM> &J,
const real_t &w)
{
return tuple{u * w * det(J)};
};
auto derivatives = std::integer_sequence<size_t, U> {};
dop.AddDomainIntegrator(functional_qf,
tuple{ Value<U>{}, Gradient<Coords>{}, Weight{} },
tuple{ Sum<U>{} },
*ir, all_domain_attr, derivatives);
dop.SetParameters({ nodes });
fes.GetRestrictionMatrix()->Mult(x, X);
Vector sum(1);
dop.Mult(X, sum);
REQUIRE(MFEM_Approx(0.0) == std::abs(sum_g - sum(0)));
auto dRdu = dop.GetDerivative(U, {&x}, {nodes});
Vector d(1);
dRdu->Mult(X, d);
REQUIRE(MFEM_Approx(0.0) == std::abs(d(0) - sum(0)));
MPI_Barrier(MPI_COMM_WORLD);
}
TEST_CASE("DFEM Functional", "[Parallel][DFEM][Functional]")
{
const bool all_tests = launch_all_non_regression_tests;
const auto p = !all_tests ? 2 : GENERATE(1, 2, 3);
const auto r = !all_tests ? 1 : GENERATE(0, 1, 2, 3);
SECTION("2D p=" + std::to_string(p) + " r=" + std::to_string(r))
{
const auto filename =
GENERATE("../../data/star.mesh",
"../../data/star-q3.mesh",
"../../data/rt-2d-q3.mesh",
"../../data/inline-quad.mesh",
"../../data/periodic-square.mesh");
dfem_functional<2>(filename, p, r);
}
SECTION("3D p=" + std::to_string(p) + " r=" + std::to_string(r))
{
const auto filename =
GENERATE("../../data/fichera.mesh",
"../../data/fichera-q3.mesh",
"../../data/inline-hex.mesh",
"../../data/toroid-hex.mesh",
"../../data/periodic-cube.mesh");
dfem_functional<3>(filename, p, r);
}
}
} // namespace dfem_pa_kernels
#endif // MFEM_USE_MPI
+116
View File
@@ -0,0 +1,116 @@
// 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 "../unit_tests.hpp"
#include "mfem.hpp"
#ifdef MFEM_USE_MPI
using namespace mfem;
using namespace mfem::future;
using mfem::future::tensor;
namespace dfem_pa_kernels
{
template <int DIM>
void DFemMass(const char *filename, int p, const int r)
{
CAPTURE(filename, DIM, p, r);
Mesh smesh(filename);
ParMesh pmesh(MPI_COMM_WORLD, smesh);
pmesh.EnsureNodes();
auto *nodes = static_cast<ParGridFunction *>(pmesh.GetNodes());
p = std::max(p, pmesh.GetNodalFESpace()->GetMaxElementOrder());
smesh.Clear();
Array<int> all_domain_attr;
if (pmesh.attributes.Size() > 0)
{
all_domain_attr.SetSize(pmesh.attributes.Max());
all_domain_attr = 1;
}
H1_FECollection fec(p, DIM);
ParFiniteElementSpace fes(&pmesh, &fec);
const auto *ir = &IntRules.Get(pmesh.GetTypicalElementGeometry(), 2 * p + r);
ParGridFunction x(&fes), y(&fes), z(&fes);
Vector X(fes.GetTrueVSize()), Y(fes.GetTrueVSize()), Z(fes.GetTrueVSize());
X.Randomize(1);
x.SetFromTrueDofs(X);
ConstantCoefficient one(1.0);
ParBilinearForm blf(&fes);
blf.AddDomainIntegrator(new MassIntegrator(one, ir));
blf.SetAssemblyLevel(AssemblyLevel::PARTIAL);
blf.Assemble();
blf.Mult(x, y);
fes.GetProlongationMatrix()->MultTranspose(y, Y);
static constexpr int U = 0, Coords = 1;
const auto sol = std::vector{ FieldDescriptor{ U, &fes } };
DifferentiableOperator dop(sol, {{Coords, nodes->ParFESpace()}}, pmesh);
const auto mf_mass_qf =
[] MFEM_HOST_DEVICE(const real_t &dudxi,
const tensor<real_t, DIM, DIM> &J, const real_t &w)
{ return tuple{dudxi * w * det(J)}; };
dop.AddDomainIntegrator(mf_mass_qf,
tuple{ Value<U>{}, Gradient<Coords>{}, Weight{} },
tuple{ Value<U>{} },
*ir, all_domain_attr);
dop.SetParameters({ nodes });
fes.GetRestrictionMatrix()->Mult(x, X);
dop.Mult(X, Z);
Y -= Z;
real_t norm_g, norm_l = Y.Normlinf();
MPI_Allreduce(&norm_l, &norm_g, 1, MPI_DOUBLE, MPI_MAX, pmesh.GetComm());
REQUIRE(norm_g == MFEM_Approx(0.0));
MPI_Barrier(MPI_COMM_WORLD);
}
TEST_CASE("DFEM Mass", "[Parallel][DFEM]")
{
const bool all_tests = launch_all_non_regression_tests;
const auto p = !all_tests ? 2 : GENERATE(1, 2, 3);
const auto r = !all_tests ? 1 : GENERATE(0, 1, 2, 3);
SECTION("2D p=" + std::to_string(p) + " r=" + std::to_string(r))
{
const auto filename =
GENERATE("../../data/star.mesh",
"../../data/star-q3.mesh",
"../../data/rt-2d-q3.mesh",
"../../data/inline-quad.mesh",
"../../data/periodic-square.mesh");
DFemMass<2>(filename, p, r);
}
SECTION("3D p=" + std::to_string(p) + " r=" + std::to_string(r))
{
const auto filename =
GENERATE("../../data/fichera.mesh",
"../../data/fichera-q3.mesh",
"../../data/inline-hex.mesh",
"../../data/toroid-hex.mesh",
"../../data/periodic-cube.mesh");
DFemMass<3>(filename, p, r);
}
}
} // namespace dfem_pa_kernels
#endif // MFEM_USE_MPI
-2
View File
@@ -14,8 +14,6 @@
#ifdef MFEM_USE_ENZYME
#include "../../../general/enzyme.hpp"
template<typename VectorT>
void square(const VectorT& v, double& y)
{
+1 -1
View File
@@ -83,7 +83,7 @@ void TestCalcShape(FiniteElement* fe, int res, double tol=1e-12)
Vector weights( fe->GetDof() );
// Get a uniform grid or integration points
// Get a uniform grid of integration points
RefinedGeometry* ref = GlobGeometryRefiner.Refine( fe->GetGeomType(), res);
const IntegrationRule& intRule = ref->RefPts;
+1 -1
View File
@@ -381,4 +381,4 @@ TEST_CASE("ParaView VTKHDF restart mode", "[ParaView]")
REQUIRE(remove("ParaView.vtkhdf") == 0);
}
#endif
#endif // MFEM_USE_HDF5
+12 -8
View File
@@ -280,10 +280,12 @@ TEST_CASE("DoF Transformation Functions",
DenseMatrix At;
DenseMatrix tAt;
nAn = A; TransformPrimal(NULL, NULL, nAn);
At = A; TransformPrimal(NULL, &Tq, At);
tA = A; TransformPrimal( &Tp, NULL, tA);
tAt = A; TransformPrimal( &Tp, &Tq, tAt);
DofTransformation id;
nAn = A; TransformPrimal(id, id, nAn);
At = A; TransformPrimal(id, Tq, At);
tA = A; TransformPrimal( Tp, id, tA);
tAt = A; TransformPrimal( Tp, Tq, tAt);
double fAv = A.InnerProduct(v, f);
@@ -312,10 +314,12 @@ TEST_CASE("DoF Transformation Functions",
DenseMatrix At;
DenseMatrix tAt;
nAn = A; TransformDual(NULL, NULL, nAn);
At = A; TransformDual(NULL, &Tq, At);
tA = A; TransformDual( &Tp, NULL, tA);
tAt = A; TransformDual( &Tp, &Tq, tAt);
DofTransformation id;
nAn = A; TransformDual(id, id, nAn);
At = A; TransformDual(id, Tq, At);
tA = A; TransformDual(Tp, id, tA);
tAt = A; TransformDual(Tp, Tq, tAt);
double uAv = A.InnerProduct(v, u);
+211
View File
@@ -0,0 +1,211 @@
// 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"
using namespace mfem;
FiniteElement * GetH1PosFiniteElement(Geometry::Type type, int order)
{
FiniteElement *fe = NULL;
switch (type)
{
case Geometry::SEGMENT:
fe = new H1Pos_SegmentElement(order);
break;
case Geometry::TRIANGLE:
fe = new H1Pos_TriangleElement(order);
break;
case Geometry::SQUARE:
fe = new H1Pos_QuadrilateralElement(order);
break;
case Geometry::TETRAHEDRON:
fe = new H1Pos_TetrahedronElement(order);
break;
case Geometry::CUBE:
fe = new H1Pos_HexahedronElement(order);
break;
case Geometry::PRISM:
fe = new H1Pos_WedgeElement(order);
break;
case Geometry::PYRAMID:
fe = new H1Pos_PyramidElement(order);
break;
default:
break;
}
return fe;
}
FiniteElement * GetL2PosFiniteElement(Geometry::Type type, int order)
{
FiniteElement *fe = NULL;
switch (type)
{
case Geometry::SEGMENT:
fe = new L2Pos_SegmentElement(order);
break;
case Geometry::TRIANGLE:
fe = new L2Pos_TriangleElement(order);
break;
case Geometry::SQUARE:
fe = new L2Pos_QuadrilateralElement(order);
break;
case Geometry::TETRAHEDRON:
fe = new L2Pos_TetrahedronElement(order);
break;
case Geometry::CUBE:
fe = new L2Pos_HexahedronElement(order);
break;
case Geometry::PRISM:
fe = new L2Pos_WedgeElement(order);
break;
case Geometry::PYRAMID:
fe = new L2Pos_PyramidElement(order);
break;
default:
break;
}
return fe;
}
TEST_CASE("Positive H1 Bases",
"[H1Pos_SegmentElement]"
"[H1Pos_TriangleElement]"
"[H1Pos_QuadrilateralElement]"
"[H1Pos_TetrahedronElement]"
"[H1Pos_HexahedronElement]"
"[H1Pos_WedgeElement]"
"[H1Pos_PyramidElement]")
{
const int maxOrder = 5;
const int resolution = 10;
auto geom = GENERATE(Geometry::SEGMENT,
Geometry::TRIANGLE, Geometry::SQUARE,
Geometry::TETRAHEDRON, Geometry::CUBE,
Geometry::PRISM, Geometry::PYRAMID);
auto p = GENERATE_COPY(range(1, maxOrder + 1));
CAPTURE(geom);
CAPTURE(p);
SECTION("H1 Basis Summation")
{
FiniteElement *fe = GetH1PosFiniteElement(geom, p);
int dim = fe->GetDim();
int ndof = fe->GetDof();
Vector ones(ndof); ones = 1.0;
Vector zeros(dim);
Vector shape(ndof);
DenseMatrix dshape(ndof, dim);
// Get a uniform grid of integration points
RefinedGeometry* ref = GlobGeometryRefiner.Refine( fe->GetGeomType(),
resolution);
const IntegrationRule& intRule = ref->RefPts;
int npoints = intRule.GetNPoints();
for (int i=0; i < npoints; ++i)
{
// Get the current integration point from intRule
IntegrationPoint pt = intRule.IntPoint(i);
fe->CalcShape(pt, shape);
// Verify that the basis functions are non-negative
REQUIRE(shape.Min() >= -2*std::numeric_limits<real_t>::epsilon());
// Verify that the basis functions sum to one
REQUIRE(shape * ones == MFEM_Approx(1.0));
// Verify that the basis functions are non-negative
REQUIRE(shape.Norml1() == MFEM_Approx(1.0));
fe->CalcDShape(pt, dshape);
dshape.MultTranspose(ones, zeros);
// Verify that the gradients sum to zero
REQUIRE(zeros.Norml2() == MFEM_Approx(0.0));
}
delete fe;
}
}
TEST_CASE("Positive L2 Bases",
"[L2Pos_SegmentElement]"
"[L2Pos_TriangleElement]"
"[L2Pos_QuadrilateralElement]"
"[L2Pos_TetrahedronElement]"
"[L2Pos_HexahedronElement]"
"[L2Pos_WedgeElement]"
"[L2Pos_PyramidElement]")
{
const int maxOrder = 5;
const int resolution = 10;
auto geom = GENERATE(Geometry::SEGMENT,
Geometry::TRIANGLE, Geometry::SQUARE,
Geometry::TETRAHEDRON, Geometry::CUBE,
Geometry::PRISM, Geometry::PYRAMID);
auto p = GENERATE_COPY(range(0, maxOrder + 1));
CAPTURE(geom);
CAPTURE(p);
SECTION("L2 Basis Summation")
{
FiniteElement *fe = GetL2PosFiniteElement(geom, p);
int dim = fe->GetDim();
int ndof = fe->GetDof();
Vector ones(ndof); ones = 1.0;
Vector zeros(dim);
Vector shape(ndof);
DenseMatrix dshape(ndof, dim);
// Get a uniform grid of integration points
RefinedGeometry* ref = GlobGeometryRefiner.Refine( fe->GetGeomType(),
resolution);
const IntegrationRule& intRule = ref->RefPts;
int npoints = intRule.GetNPoints();
for (int i=0; i < npoints; ++i)
{
// Get the current integration point from intRule
IntegrationPoint pt = intRule.IntPoint(i);
fe->CalcShape(pt, shape);
// Verify that the basis functions are non-negative
REQUIRE(shape.Min() >= -2*std::numeric_limits<real_t>::epsilon());
// Verify that the basis functions sum to one
REQUIRE(shape * ones == MFEM_Approx(1.0));
// Verify that the basis functions are non-negative
REQUIRE(shape.Norml1() == MFEM_Approx(1.0));
fe->CalcDShape(pt, dshape);
dshape.MultTranspose(ones, zeros);
// Verify that the gradients sum to zero
REQUIRE(zeros.Norml2() == MFEM_Approx(0.0));
}
delete fe;
}
}

Some files were not shown because too many files have changed in this diff Show More