Compare commits
205
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
681daa4233 | ||
|
|
2b95f816b9 | ||
|
|
ffed90f0ac | ||
|
|
b80ade9530 | ||
|
|
4fdd244f34 | ||
|
|
2d57401575 | ||
|
|
7f788e83b9 | ||
|
|
2581974c91 | ||
|
|
4f80d4c50d | ||
|
|
e3c7ec6e61 | ||
|
|
7e2c36788e | ||
|
|
ae59c4096f | ||
|
|
f0cb31088c | ||
|
|
f407ca7756 | ||
|
|
ab00472c5d | ||
|
|
c4a3d31289 | ||
|
|
20e77be88a | ||
|
|
97a568af13 | ||
|
|
cd2715680f | ||
|
|
62997e9dc7 | ||
|
|
91cdbeee7d | ||
|
|
0466ee74f2 | ||
|
|
50b8873f20 | ||
|
|
cd7dfe2188 | ||
|
|
1bf00d3761 | ||
|
|
b2a0f48001 | ||
|
|
fb200e4b98 | ||
|
|
da852fd0e4 | ||
|
|
c2657d20a8 | ||
|
|
47e9b29c0f | ||
|
|
cbcae09fe8 | ||
|
|
b75c6e96e6 | ||
|
|
a54c5d2105 | ||
|
|
cd5d098c95 | ||
|
|
30506b52d9 | ||
|
|
3f78374d2a | ||
|
|
bd7281bdfa | ||
|
|
1299d05d21 | ||
|
|
39944d8dfb | ||
|
|
6df8ae80cb | ||
|
|
0730fa304a | ||
|
|
6dfbe25c07 | ||
|
|
a8e251c28e | ||
|
|
9df402b565 | ||
|
|
5ddbaed1f4 | ||
|
|
02edb95a31 | ||
|
|
2d6cb3f0e5 | ||
|
|
d06f52461e | ||
|
|
3853cb6860 | ||
|
|
f5d0db2a33 | ||
|
|
a102daf621 | ||
|
|
597cba8d37 | ||
|
|
075a160187 | ||
|
|
6d8a96a8fb | ||
|
|
8d9579057f | ||
|
|
76d65f5866 | ||
|
|
720fb72372 | ||
|
|
bbde976378 | ||
|
|
58ad1c79cd | ||
|
|
447cd0f500 | ||
|
|
2d68b877f2 | ||
|
|
cce301f92b | ||
|
|
46ccaee493 | ||
|
|
4cc9b5c6f6 | ||
|
|
7aa327174a | ||
|
|
769f672ac1 | ||
|
|
dca2a24af2 | ||
|
|
019194d42d | ||
|
|
8c861b4602 | ||
|
|
267082d6f2 | ||
|
|
dd6c207534 | ||
|
|
71af00ab6d | ||
|
|
2d5f00635f | ||
|
|
03da0c870c | ||
|
|
3f447cb11c | ||
|
|
e629502dca | ||
|
|
ce434a9c82 | ||
|
|
48dd78b154 | ||
|
|
dc08fc65ce | ||
|
|
3276cb955f | ||
|
|
bcdbda1597 | ||
|
|
0239b86304 | ||
|
|
aca8d51f95 | ||
|
|
368724d456 | ||
|
|
a99efcdb63 | ||
|
|
b5a7cf6470 | ||
|
|
1ac7837dc0 | ||
|
|
e6fc7893be | ||
|
|
78df8dffb2 | ||
|
|
d0f5cb73e7 | ||
|
|
056152aaa6 | ||
|
|
db55be84ad | ||
|
|
02424a41a9 | ||
|
|
091c6272fc | ||
|
|
101af72047 | ||
|
|
01990d4605 | ||
|
|
4a3cd44695 | ||
|
|
3b059c3995 | ||
|
|
89dc5ce5c0 | ||
|
|
eb205caaa2 | ||
|
|
f50601f1aa | ||
|
|
529d6e3bc2 | ||
|
|
b289def58a | ||
|
|
ef29b1dd30 | ||
|
|
33b3ef6045 | ||
|
|
657f6ef5fe | ||
|
|
96cfef783c | ||
|
|
603f43e775 | ||
|
|
932508ad1b | ||
|
|
0e30ee1e15 | ||
|
|
dd236b8796 | ||
|
|
9a47f4391d | ||
|
|
ccc006febf | ||
|
|
a2e33109eb | ||
|
|
b277a0cb2d | ||
|
|
f42334cb4d | ||
|
|
ac26026ff1 | ||
|
|
d439781751 | ||
|
|
16c9123f98 | ||
|
|
cc86e59167 | ||
|
|
52eaf0deb7 | ||
|
|
e73ea1fb77 | ||
|
|
ea6c104475 | ||
|
|
e7762d126e | ||
|
|
c61d0e07d3 | ||
|
|
720051d641 | ||
|
|
5c25fa49a7 | ||
|
|
d9ef117ee9 | ||
|
|
b7a8cf6290 | ||
|
|
bed1823d72 | ||
|
|
93a3648712 | ||
|
|
01eeccee7f | ||
|
|
39b6112e69 | ||
|
|
c70d2b7309 | ||
|
|
c98e8bfe07 | ||
|
|
ef9137bd1f | ||
|
|
8e0c42a132 | ||
|
|
d755037904 | ||
|
|
b577d2322e | ||
|
|
6f99204d37 | ||
|
|
c6e93f5474 | ||
|
|
21b6ade630 | ||
|
|
2e8685d80a | ||
|
|
6d8c32db80 | ||
|
|
4adb51df50 | ||
|
|
6cec49e220 | ||
|
|
9006171327 | ||
|
|
9e10cd87b1 | ||
|
|
3f7c551d66 | ||
|
|
a7a5ce5cb1 | ||
|
|
4218c11b41 | ||
|
|
44f58a169c | ||
|
|
3c4a860f3a | ||
|
|
e64300f84b | ||
|
|
3854184620 | ||
|
|
4a09b7a1fc | ||
|
|
1ea8e59f69 | ||
|
|
b6aa248fc9 | ||
|
|
6857e4b277 | ||
|
|
639a0347fb | ||
|
|
a02f7165d8 | ||
|
|
4fb48b4e41 | ||
|
|
751dbd0ed9 | ||
|
|
c2c6a94d42 | ||
|
|
088ec0f461 | ||
|
|
7e1be9da75 | ||
|
|
be29d4a3d4 | ||
|
|
be34c5a6bd | ||
|
|
8bd3569f6d | ||
|
|
e8908ad646 | ||
|
|
6c7673df1c | ||
|
|
1f7f394f4e | ||
|
|
bfc6b0708d | ||
|
|
3d5c6d58e8 | ||
|
|
6efbd4cc27 | ||
|
|
369b9365ad | ||
|
|
b35ff2270a | ||
|
|
7a54a543b0 | ||
|
|
15420f7049 | ||
|
|
dd20722733 | ||
|
|
5577edf446 | ||
|
|
230a345ef3 | ||
|
|
89f1dce705 | ||
|
|
7f17c1f049 | ||
|
|
7e6edcddec | ||
|
|
d89b01e24c | ||
|
|
33f1150c34 | ||
|
|
c6a05d371b | ||
|
|
f97a29f6e8 | ||
|
|
85ae72fada | ||
|
|
27a5bd71ee | ||
|
|
643db22bef | ||
|
|
97fb6be029 | ||
|
|
9587ef212d | ||
|
|
35462b8645 | ||
|
|
4f0f353720 | ||
|
|
a3156023d0 | ||
|
|
7794c291be | ||
|
|
7ca49cb200 | ||
|
|
e48d41c9dc | ||
|
|
0ead1d9c77 | ||
|
|
e2168f9348 | ||
|
|
65516eca23 | ||
|
|
79d9314560 | ||
|
|
d91d39bc00 |
@@ -132,12 +132,14 @@ jobs:
|
||||
hypre-target: int32
|
||||
precision: fp64
|
||||
enzyme: true
|
||||
config-opts: MFEM_USE_ENZYME=YES ENZYME_DIR=$(brew --prefix enzyme)
|
||||
config-opts: MFEM_USE_ENZYME=YES ENZYME_DIR=$(brew --prefix enzyme) LDFLAGS=-L$LLVM_PREFIX/lib/c++
|
||||
|
||||
name: ${{ matrix.os }}-${{ matrix.build-system }}-${{ matrix.target }}-${{ matrix.mpi }}-${{ matrix.hypre-target }}-${{ matrix.precision }}${{ matrix.enzyme && '-enzyme' || '' }}
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
continue-on-error: ${{ matrix.enzyme && true || false }}
|
||||
|
||||
steps:
|
||||
# Fix 'No space left on device' errors for Ubuntu builds.
|
||||
- name: Run Actions Cleaner
|
||||
@@ -168,10 +170,13 @@ jobs:
|
||||
env
|
||||
shell: bash
|
||||
|
||||
# For info on Xcode see:
|
||||
# - https://github.com/actions/runner-images/issues/12541
|
||||
# - https://github.com/actions/runner-images/blob/releases/macos-15-arm64/20250811/images/macos/macos-15-arm64-Readme.md#xcode
|
||||
- name: Xcode version setup (MacOS)
|
||||
if: matrix.os == 'macos-latest'
|
||||
run: |
|
||||
XCODE_PATH="/Applications/Xcode_15.3.app"
|
||||
XCODE_PATH="/Applications/Xcode_16.4.app"
|
||||
echo "> sudo xcode-select -s ${XCODE_PATH}"
|
||||
sudo xcode-select -s ${XCODE_PATH}
|
||||
echo "> g++ -v"
|
||||
@@ -289,10 +294,12 @@ jobs:
|
||||
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
|
||||
brew install enzyme
|
||||
ENZYME_LLVM=$(brew info enzyme | sed -n 's/^Required:.*\(llvm[^ ]*\).*/\1/p')
|
||||
LLVM_PREFIX=$(brew --prefix $ENZYME_LLVM)
|
||||
echo "LLVM_PREFIX=$LLVM_PREFIX" >> $GITHUB_ENV
|
||||
echo "OMPI_CC=$LLVM_PREFIX/bin/clang" >> $GITHUB_ENV
|
||||
echo "OMPI_CXX=$LLVM_PREFIX/bin/clang++" >> $GITHUB_ENV
|
||||
|
||||
# MFEM build and test
|
||||
- name: build
|
||||
|
||||
+4
-4
@@ -211,7 +211,7 @@ miniapps/electromagnetics/joule
|
||||
miniapps/electromagnetics/Volta-AMR*
|
||||
miniapps/electromagnetics/Tesla-AMR*
|
||||
miniapps/electromagnetics/Maxwell-Parallel*
|
||||
miniapps/electromagnetics/Joule_*
|
||||
miniapps/electromagnetics/Joule_[0-9]*
|
||||
|
||||
miniapps/gslib/field-diff
|
||||
miniapps/gslib/field-interp
|
||||
@@ -267,9 +267,9 @@ miniapps/meshing/bounding-box*
|
||||
miniapps/meshing/jacobian-determinant*
|
||||
|
||||
miniapps/mtop/parheat
|
||||
miniapps/mtop/ParHeat*
|
||||
miniapps/mtop/ParHeat/*
|
||||
miniapps/mtop/seqheat
|
||||
miniapps/mtop/SeqHeat*
|
||||
miniapps/mtop/SeqHeat/*
|
||||
|
||||
miniapps/autodiff/paradiff
|
||||
miniapps/autodiff/seqadiff
|
||||
@@ -277,7 +277,7 @@ miniapps/autodiff/seqtest
|
||||
miniapps/autodiff/par_example
|
||||
miniapps/autodiff/seq_example
|
||||
miniapps/autodiff/seq_test
|
||||
miniapps/autodiff/Exampl*
|
||||
miniapps/autodiff/Example/*
|
||||
|
||||
miniapps/navier/navier_mms
|
||||
miniapps/navier/navier_kovasznay
|
||||
|
||||
+5
-5
@@ -22,7 +22,7 @@ include:
|
||||
# the "needs" keyword and express the DAG of jobs for more efficiency.
|
||||
# - We use setup and setup_baseline phases to download content outside of mfem
|
||||
# directory.
|
||||
# - Allocate/Release is where ruby resource are allocated/released once for all.
|
||||
# - Allocate/Release is where Dane resource are allocated/released once for all.
|
||||
# - Build and Test is where we build and MFEM for multiple toolchains.
|
||||
# - Baseline_checks gathers baseline-type test suites execution
|
||||
# - Baseline_publish, only available on master, allows to update baseline
|
||||
@@ -53,7 +53,7 @@ variables:
|
||||
AUTOTEST_COMMIT: "YES"
|
||||
|
||||
# Trigger subpipelines:
|
||||
ruby-build-and-test:
|
||||
dane-build-and-test:
|
||||
stage: sub-pipelines
|
||||
variables:
|
||||
# Explicitly pass down values that we want to be able to set when triggering
|
||||
@@ -61,10 +61,10 @@ ruby-build-and-test:
|
||||
AUTOTEST: "${AUTOTEST}"
|
||||
AUTOTEST_COMMIT: "${AUTOTEST_COMMIT}"
|
||||
trigger:
|
||||
include: .gitlab/ruby-build-and-test.yml
|
||||
include: .gitlab/dane-build-and-test.yml
|
||||
strategy: depend
|
||||
|
||||
ruby-baseline:
|
||||
dane-baseline:
|
||||
stage: sub-pipelines
|
||||
variables:
|
||||
# Explicitly pass down values that we want to be able to set when triggering
|
||||
@@ -73,7 +73,7 @@ ruby-baseline:
|
||||
AUTOTEST: "${AUTOTEST}"
|
||||
AUTOTEST_COMMIT: "${AUTOTEST_COMMIT}"
|
||||
trigger:
|
||||
include: .gitlab/ruby-baseline.yml
|
||||
include: .gitlab/dane-baseline.yml
|
||||
strategy: depend
|
||||
|
||||
lassen-build-and-test:
|
||||
|
||||
+3
-3
@@ -24,7 +24,7 @@ and `test type`.
|
||||
|
||||
Machines typically include:
|
||||
|
||||
* Ruby: 2nd Gen Intel Xeon (Cascade Lake)
|
||||
* Dane: Intel Sapphire Rapids
|
||||
* Lassen: Power9 + Nvidia GPU
|
||||
* Corona: AMD GPU
|
||||
|
||||
@@ -76,13 +76,13 @@ with a spack spec of MFEM, within the limits permitted by the MFEM spack
|
||||
package.
|
||||
|
||||
In any build-and-test sub-pipeline a job basically consists in defining the
|
||||
spack spec to use. Adding a job on ruby for example resumes to:
|
||||
spack spec to use. Adding a job on Dane for example resumes to:
|
||||
|
||||
```yaml
|
||||
<job_name>:
|
||||
variables:
|
||||
SPEC: "<spack_spec>"
|
||||
extends: .build_and_test_on_ruby
|
||||
extends: .build_and_test_on_dane
|
||||
```
|
||||
|
||||
The remaining and non trivial work is to make sure this spec is working. To
|
||||
|
||||
@@ -24,7 +24,7 @@ variables:
|
||||
# TODO: add a clean-up mechanism
|
||||
BUILD_ROOT: ${USER_CI_TOP_DIR}/${CI_PROJECT_NAME}-${MACHINE_NAME}-pipeline-${CI_PIPELINE_ID}
|
||||
|
||||
# On LLNL's ruby, there is only one allocation shared among jobs in order to
|
||||
# On LLNL's Dane, there is only one allocation shared among jobs in order to
|
||||
# save time and resource. This allocation has to be uniquely named so that we
|
||||
# are sure to retrieve it.
|
||||
ALLOC_NAME: ${CI_PROJECT_NAME}_ci_${CI_PIPELINE_ID}
|
||||
|
||||
@@ -9,17 +9,17 @@
|
||||
# terms of the BSD-3 license. We welcome feedback and contributions, see file
|
||||
# CONTRIBUTING.md for details.
|
||||
|
||||
# GitLab pipelines configurations for the Ruby machine at LLNL
|
||||
# GitLab pipelines configurations for the Dane machine at LLNL
|
||||
variables:
|
||||
MACHINE_NAME: ruby
|
||||
MACHINE_NAME: dane
|
||||
|
||||
.on_ruby:
|
||||
.on_dane:
|
||||
tags:
|
||||
- shell
|
||||
- ruby
|
||||
- dane
|
||||
rules:
|
||||
# Don't run ruby jobs if...
|
||||
- if: '$CI_COMMIT_BRANCH =~ /_qnone/ || $ON_RUBY == "OFF"'
|
||||
# Don't run dane jobs if...
|
||||
- if: '$CI_COMMIT_BRANCH =~ /_qnone/ || $ON_DANE == "OFF"'
|
||||
when: never
|
||||
# Don't run autotest update if...
|
||||
- if: '$CI_JOB_NAME =~ /report/ && $AUTOTEST != "YES"'
|
||||
@@ -40,16 +40,17 @@ variables:
|
||||
- when: on_success
|
||||
|
||||
# Spack helped builds
|
||||
# Generic ruby build job, extending build script
|
||||
.build_and_test_on_ruby:
|
||||
extends: [.on_ruby]
|
||||
# Generic dane build job, extending build script
|
||||
.build_and_test_on_dane:
|
||||
extends: [.on_dane]
|
||||
stage: build_and_test
|
||||
script:
|
||||
# THREADS is used by 'tests/gitlab/build_and_test', run below
|
||||
- export THREADS=16
|
||||
# Dane has 224 threads/node and we run 7 separate jobs: 224=7*32
|
||||
- export THREADS=28
|
||||
- echo ${ALLOC_NAME}
|
||||
- export JOBID=$(squeue -h --name=${ALLOC_NAME} --format=%A)
|
||||
- echo ${JOBID}
|
||||
- echo ${MFEM_DATA_DIR}
|
||||
- echo ${SPEC}
|
||||
- srun $( [[ -n "${JOBID}" ]] && echo "--jobid=${JOBID}" ) --reservation=ci -t 45 -N 1 tests/gitlab/build_and_test --spec "${SPEC}" --data-dir "${MFEM_DATA_DIR}" --data
|
||||
- srun $( [[ -n "${JOBID}" ]] && echo "--jobid=${JOBID}" ) --reservation=ci -t 60 -N 1 tests/gitlab/build_and_test --spec "${SPEC}" --data-dir "${MFEM_DATA_DIR}" --data
|
||||
@@ -18,7 +18,7 @@
|
||||
setup_baseline:
|
||||
tags:
|
||||
- shell
|
||||
- ruby
|
||||
- dane
|
||||
stage: setup
|
||||
variables:
|
||||
GIT_STRATEGY: none
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
setup:
|
||||
tags:
|
||||
- shell
|
||||
- ruby
|
||||
- dane
|
||||
stage: setup
|
||||
variables:
|
||||
GIT_STRATEGY: none
|
||||
|
||||
@@ -19,8 +19,8 @@ stages:
|
||||
- cleanup
|
||||
- baseline_publish
|
||||
|
||||
baselinecheck_mfem_intel_ruby:
|
||||
extends: [.on_ruby]
|
||||
baselinecheck_mfem_intel_dane:
|
||||
extends: [.on_dane]
|
||||
stage: baseline_check
|
||||
variables:
|
||||
# TPLS_DIR is used in .gitlab/scripts/baseline to provide the tpls location
|
||||
@@ -31,8 +31,8 @@ baselinecheck_mfem_intel_ruby:
|
||||
script:
|
||||
- echo ${BUILD_ROOT}
|
||||
- echo ${TPLS_DIR}
|
||||
# Used by the tests in MFEM/tests:
|
||||
- export MFEM_TEST_NP=48
|
||||
# Used by the tests in MFEM/tests, dane has 224 threads/node:
|
||||
- export MFEM_TEST_NP=192
|
||||
# The next script uses the following environment variables:
|
||||
# * BASELINE_TEST, SYS_TYPE, CI_PROJECT_DIR, ARTIFACTS_DIR,
|
||||
# * BUILD_ROOT, TPLS_DIR, MACHINE_NAME
|
||||
@@ -44,7 +44,7 @@ baselinecheck_mfem_intel_ruby:
|
||||
allow_failure: true
|
||||
|
||||
cleanup:
|
||||
extends: .on_ruby
|
||||
extends: .on_dane
|
||||
stage: cleanup
|
||||
variables:
|
||||
GIT_STRATEGY: none
|
||||
@@ -53,7 +53,7 @@ cleanup:
|
||||
- rm -rf "${BUILD_ROOT}" || true
|
||||
|
||||
report_baseline:
|
||||
extends: [.on_ruby]
|
||||
extends: [.on_dane]
|
||||
stage: baseline_report
|
||||
script:
|
||||
- echo ${MACHINE_NAME}
|
||||
@@ -113,8 +113,8 @@ report_baseline:
|
||||
exit $err
|
||||
) 9> autotest.lock
|
||||
|
||||
baselinepublish_mfem_ruby:
|
||||
extends: [.on_ruby]
|
||||
baselinepublish_mfem_dane:
|
||||
extends: [.on_dane]
|
||||
stage: baseline_publish
|
||||
rules:
|
||||
# - if: '$CI_COMMIT_BRANCH == "master" || $REBASELINE == "YES"'
|
||||
@@ -129,5 +129,5 @@ baselinepublish_mfem_ruby:
|
||||
|
||||
include:
|
||||
- local: .gitlab/configs/common.yml
|
||||
- local: .gitlab/configs/ruby-config.yml
|
||||
- local: .gitlab/configs/dane-config.yml
|
||||
- local: .gitlab/configs/setup-baseline.yml
|
||||
@@ -19,54 +19,54 @@ stages:
|
||||
allocate_resource:
|
||||
variables:
|
||||
GIT_STRATEGY: none
|
||||
extends: .on_ruby
|
||||
extends: .on_dane
|
||||
stage: allocate_resource
|
||||
script:
|
||||
- echo ${ALLOC_NAME}
|
||||
- salloc --exclusive --nodes=1 --reservation=ci --time=60 --no-shell --job-name=${ALLOC_NAME}
|
||||
timeout: 6h
|
||||
|
||||
# GitLab jobs for the Ruby machine at LLNL
|
||||
# GitLab jobs for the Dane machine at LLNL
|
||||
debug_ser_gcc_10:
|
||||
variables:
|
||||
SPEC: "%gcc@10.3.1 +debug~mpi"
|
||||
extends: .build_and_test_on_ruby
|
||||
extends: .build_and_test_on_dane
|
||||
|
||||
debug_par_gcc_10:
|
||||
variables:
|
||||
SPEC: "%gcc@10.3.1 +debug+mpi"
|
||||
extends: .build_and_test_on_ruby
|
||||
extends: .build_and_test_on_dane
|
||||
|
||||
opt_ser_gcc_10:
|
||||
variables:
|
||||
SPEC: "%gcc@10.3.1 ~mpi"
|
||||
extends: .build_and_test_on_ruby
|
||||
extends: .build_and_test_on_dane
|
||||
|
||||
opt_par_gcc_10:
|
||||
variables:
|
||||
SPEC: "%gcc@10.3.1"
|
||||
extends: .build_and_test_on_ruby
|
||||
extends: .build_and_test_on_dane
|
||||
|
||||
opt_par_gcc_10_sundials:
|
||||
variables:
|
||||
SPEC: "%gcc@10.3.1 +sundials"
|
||||
extends: .build_and_test_on_ruby
|
||||
extends: .build_and_test_on_dane
|
||||
|
||||
opt_par_gcc_10_petsc:
|
||||
variables:
|
||||
SPEC: "%gcc@10.3.1 +petsc ^petsc+mumps~superlu-dist"
|
||||
extends: .build_and_test_on_ruby
|
||||
extends: .build_and_test_on_dane
|
||||
|
||||
opt_par_gcc_10_pumi:
|
||||
variables:
|
||||
SPEC: "%gcc@10.3.1 +pumi"
|
||||
extends: .build_and_test_on_ruby
|
||||
extends: .build_and_test_on_dane
|
||||
|
||||
# Release
|
||||
release_resource:
|
||||
variables:
|
||||
GIT_STRATEGY: none
|
||||
extends: .on_ruby
|
||||
extends: .on_dane
|
||||
stage: release_resource_and_report
|
||||
script:
|
||||
- echo ${ALLOC_NAME}
|
||||
@@ -78,17 +78,17 @@ release_resource:
|
||||
report_job_success:
|
||||
stage: release_resource_and_report
|
||||
extends:
|
||||
- .on_ruby
|
||||
- .on_dane
|
||||
- .report_job_success
|
||||
|
||||
report_job_failure:
|
||||
stage: release_resource_and_report
|
||||
extends:
|
||||
- .on_ruby
|
||||
- .on_dane
|
||||
- .report_job_failure
|
||||
|
||||
include:
|
||||
- local: .gitlab/configs/common.yml
|
||||
- local: .gitlab/configs/ruby-config.yml
|
||||
- local: .gitlab/configs/dane-config.yml
|
||||
- local: .gitlab/configs/setup-build-and-test.yml
|
||||
- local: .gitlab/configs/report-build-and-test.yml
|
||||
@@ -14,7 +14,7 @@
|
||||
# locals
|
||||
glob_err=${BASELINE_TEST}.err
|
||||
base=${BASELINE_TEST}-${SYS_TYPE}
|
||||
if [[ "${MACHINE_NAME}" == "ruby" ]]; then
|
||||
if [[ "${MACHINE_NAME}" == "dane" ]]; then
|
||||
base="${BASELINE_TEST}-${MACHINE_NAME}"
|
||||
fi
|
||||
base_diff=${base}.diff
|
||||
@@ -31,7 +31,7 @@ cd tests
|
||||
mkdir _${BASELINE_TEST} && cd _${BASELINE_TEST}
|
||||
|
||||
# run
|
||||
if [[ "${MACHINE_NAME}" == "ruby" ]]; then
|
||||
if [[ "${MACHINE_NAME}" == "dane" ]]; then
|
||||
salloc --nodes=1 --exclusive --reservation=ci ../runtest ../../mfem "${BASELINE_TEST} ${TPLS_DIR}"
|
||||
elif [[ ${MACHINE_NAME} == "corona" ]]; then
|
||||
salloc --nodes=1 -t 60 -p pbatch ../runtest ../../mfem "${BASELINE_TEST} ${TPLS_DIR}"
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
# terms of the BSD-3 license. We welcome feedback and contributions, see file
|
||||
# CONTRIBUTING.md for details.
|
||||
|
||||
# There will be collision between corona and ruby baselines.
|
||||
# There will be collision between corona and dane baselines.
|
||||
# Once the corresponding files have been generated, we can switch to machine
|
||||
# specific ref.
|
||||
ARTIFACT_PATH=${CI_PROJECT_DIR}/${ARTIFACTS_DIR}/baseline-${SYS_TYPE}
|
||||
@@ -21,7 +21,7 @@ PATCH_FILE=${ARTIFACT_PATH}.patch
|
||||
FULL_FILE=${ARTIFACT_PATH}.out
|
||||
DIFF_FILE=${ARTIFACT_PATH}.diff
|
||||
|
||||
# There will be collision between corona and ruby baselines.
|
||||
# There will be collision between corona and dane baselines.
|
||||
# Once the corresponding files have been generated, we can switch to machine
|
||||
# specific ref.
|
||||
SAVED_NAME=baseline-${SYS_TYPE}.saved
|
||||
|
||||
@@ -46,6 +46,11 @@ GPU computing
|
||||
where host execution is always needed (e.g. when the DOFs array is small).
|
||||
- Introduced MFEM_FOREACH_THREAD_DIRECT, which directly maps loop tasks to GPU
|
||||
threads, assigning one task per thread.
|
||||
- Implemented a GPU-accelerated matrix-free AMR derefinement `GridFunction`
|
||||
update operator. This supports mixed geometry meshes and variable order
|
||||
spaces, and is the default derefinement operator constructed by
|
||||
`FiniteElementSpace::Update` and `ParFiniteElementSpace::Update`.
|
||||
The operator requires `FiniteElementSpace::Nonconforming() == true`.
|
||||
|
||||
New and updated examples and miniapps
|
||||
-------------------------------------
|
||||
|
||||
+14
-5
@@ -598,14 +598,20 @@ set(MFEM_TPLS OPENMP HYPRE LAPACK BLAS SuperLUDist STRUMPACK METIS SuiteSparse
|
||||
BENCHMARK PARELAG TRIBOL MPI_CXX HIP HIPBLAS HIPSPARSE MOONOLITH BLITZ
|
||||
ALGOIM ENZYME)
|
||||
|
||||
# Add all *_FOUND libraries in the variable TPL_LIBRARIES.
|
||||
# Add all created targets and *_FOUND libraries in the variables TPL_TARGETS and
|
||||
# TPL_LIBRARIES, respectively.
|
||||
set(TPL_TARGETS)
|
||||
set(TPL_LIBRARIES "")
|
||||
set(TPL_INCLUDE_DIRS "")
|
||||
foreach(TPL IN LISTS MFEM_TPLS)
|
||||
if (${TPL}_FOUND)
|
||||
if (${TPL}_FOUND OR TARGET ${TPL})
|
||||
message(STATUS "MFEM: using package ${TPL}")
|
||||
list(APPEND TPL_LIBRARIES ${${TPL}_LIBRARIES})
|
||||
list(APPEND TPL_INCLUDE_DIRS ${${TPL}_INCLUDE_DIRS})
|
||||
if (TARGET ${TPL})
|
||||
list(APPEND TPL_TARGETS ${TPL})
|
||||
else()
|
||||
list(APPEND TPL_LIBRARIES ${${TPL}_LIBRARIES})
|
||||
list(APPEND TPL_INCLUDE_DIRS ${${TPL}_INCLUDE_DIRS})
|
||||
endif()
|
||||
endif()
|
||||
endforeach(TPL)
|
||||
list(REVERSE TPL_LIBRARIES)
|
||||
@@ -680,7 +686,10 @@ set(MFEM_INSTALL_DIR ${CMAKE_INSTALL_PREFIX})
|
||||
# Declaring the library
|
||||
mfem_add_library(mfem ${SOURCES} ${HEADERS} ${MASTER_HEADERS})
|
||||
# message(STATUS "TPL_LIBRARIES = ${TPL_LIBRARIES}")
|
||||
target_link_libraries(mfem PUBLIC ${TPL_LIBRARIES})
|
||||
target_link_libraries(mfem PUBLIC ${TPL_LIBRARIES} ${TPL_TARGETS})
|
||||
if (TPL_TARGETS)
|
||||
add_dependencies(mfem ${TPL_TARGETS})
|
||||
endif()
|
||||
if (MINGW)
|
||||
target_link_libraries(mfem PRIVATE ws2_32)
|
||||
endif()
|
||||
|
||||
@@ -121,6 +121,11 @@ Parallel build:
|
||||
make -j 4
|
||||
(For METIS 5, see https://mfem.org/building/#parallel-build-using-metis-5)
|
||||
|
||||
Parallel build with fetching of hypre and METIS:
|
||||
mkdir <mfem-buil-dir> ; cd <mfem-build-dir>
|
||||
cmake <mfem-source-dir> -DMFEM_USE_MPI=YES -DFETCH_TPLS=YES
|
||||
make -j 4
|
||||
|
||||
CUDA build:
|
||||
(this build requires CMake 3.17 or newer)
|
||||
mkdir <mfem-build-dir> ; cd <mfem-build-dir>
|
||||
@@ -663,6 +668,7 @@ The specific libraries and their options are:
|
||||
- OpenMP (optional), usually part of compiler, used when either MFEM_USE_OPENMP
|
||||
or MFEM_USE_LEGACY_OPENMP is set to YES.
|
||||
Options: OPENMP_OPT, OPENMP_LIB.
|
||||
Versions: OpenMP >= 3.1 when MFEM_USE_OPENMP=YES.
|
||||
|
||||
- High-resolution POSIX clocks: when using MFEM_TIMER_TYPE = 2, it may be
|
||||
necessary to link with a system library (e.g. librt.so).
|
||||
@@ -842,6 +848,7 @@ The specific libraries and their options are:
|
||||
- HIP (optional), used when MFEM_USE_HIP = YES.
|
||||
URL: https://rocmdocs.amd.com
|
||||
Options: HIP_CXX, HIP_ARCH, HIP_OPT, HIP_LIB.
|
||||
Versions: ROCm >= 5.6.1.
|
||||
|
||||
- OCCA (optional), used when MFEM_USE_OCCA = YES.
|
||||
URL: https://libocca.org
|
||||
@@ -1074,6 +1081,9 @@ The following options are CMake specific:
|
||||
MFEM_ENABLE_TESTING - Enable the ctest framework for testing.
|
||||
MFEM_ENABLE_EXAMPLES - Build all of the examples by default.
|
||||
MFEM_ENABLE_MINIAPPS - Build all of the miniapps by default.
|
||||
FETCH_TPLS - Enable fetching of all supported third-party libraries.
|
||||
HYPRE_FETCH - Enable fetching of hypre.
|
||||
METIS_FETCH - Enable fetching of metis.
|
||||
|
||||
External libraries (CMake):
|
||||
---------------------------
|
||||
@@ -1135,6 +1145,12 @@ The following built-in CMake packages are also used:
|
||||
set the <LIBNAME>_LIBRARIES option directly; the configuration option
|
||||
<LIBNAME>_DIR is not supported.
|
||||
|
||||
The MFEM CMake build system also provides fetching (automated building) for the
|
||||
packages/libraries listed below. Note that when fetching is enabled, any related
|
||||
auto-detection functionality is disabled.
|
||||
|
||||
- HYPRE
|
||||
- METIS
|
||||
|
||||
Building without GNU make or CMake
|
||||
==================================
|
||||
|
||||
@@ -9,15 +9,18 @@
|
||||
# terms of the BSD-3 license. We welcome feedback and contributions, see file
|
||||
# CONTRIBUTING.md for details.
|
||||
|
||||
# Defines the following variables:
|
||||
# Defines the following variables if fetching of TPLs is disabled (default):
|
||||
# - HYPRE_FOUND
|
||||
# - HYPRE_LIBRARIES
|
||||
# - HYPRE_INCLUDE_DIRS
|
||||
# - HYPRE_VERSION
|
||||
# - HYPRE_USING_CUDA (internal)
|
||||
# - HYPRE_USING_HIP (internal)
|
||||
# otherwise, the following are defined:
|
||||
# - HYPRE (imported library target)
|
||||
# - HYPRE_VERSION (cache variable)
|
||||
|
||||
if (HYPRE_FOUND)
|
||||
if (HYPRE_FOUND OR TARGET HYPRE)
|
||||
if (HYPRE_USING_CUDA)
|
||||
find_package(CUDAToolkit REQUIRED)
|
||||
endif()
|
||||
@@ -33,6 +36,55 @@ if (HYPRE_FOUND)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (HYPRE_FETCH OR FETCH_TPLS)
|
||||
set(HYPRE_FETCH_VERSION 2.33.0)
|
||||
add_library(HYPRE STATIC IMPORTED)
|
||||
# set options and associated dependencies
|
||||
set(CMAKE_OPTIONS)
|
||||
list(APPEND CMAKE_OPTIONS -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE})
|
||||
if (MFEM_USE_CUDA)
|
||||
list(APPEND CMAKE_OPTIONS -DHYPRE_WITH_CUDA:BOOL=ON)
|
||||
find_package(CUDAToolkit REQUIRED)
|
||||
target_link_libraries(HYPRE INTERFACE CUDA::cusparse CUDA::curand CUDA::cublas)
|
||||
elseif (MFEM_USE_HIP)
|
||||
list(APPEND CMAKE_OPTIONS -DHYPRE_WITH_HIP:BOOL=ON)
|
||||
find_package(rocsparse REQUIRED)
|
||||
find_package(rocrand REQUIRED)
|
||||
target_link_libraries(HYPRE INTERFACE rocsparse rocrand)
|
||||
endif()
|
||||
if (MFEM_USE_SINGLE)
|
||||
list(APPEND CMAKE_OPTIONS -DHYPRE_ENABLE_SINGLE:BOOL=ON)
|
||||
endif()
|
||||
# define external project and create future include directory so it is present
|
||||
# to pass CMake checks at end of MFEM configuration step
|
||||
message(STATUS "Will fetch HYPRE ${HYPRE_FETCH_VERSION} to be built with ${CMAKE_OPTIONS}")
|
||||
set(PREFIX ${CMAKE_BINARY_DIR}/fetch/hypre)
|
||||
include(ExternalProject)
|
||||
ExternalProject_Add(hypre
|
||||
GIT_REPOSITORY https://github.com/hypre-space/hypre.git
|
||||
GIT_TAG v${HYPRE_FETCH_VERSION}
|
||||
GIT_SHALLOW TRUE
|
||||
UPDATE_DISCONNECTED TRUE
|
||||
SOURCE_SUBDIR src
|
||||
PREFIX ${PREFIX}
|
||||
CMAKE_CACHE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${PREFIX} -DCMAKE_INSTALL_LIBDIR:PATH=lib ${CMAKE_OPTIONS})
|
||||
file(MAKE_DIRECTORY ${PREFIX}/include)
|
||||
# set imported library target properties
|
||||
add_dependencies(HYPRE hypre)
|
||||
set_target_properties(HYPRE PROPERTIES
|
||||
IMPORTED_LOCATION ${PREFIX}/lib/libHYPRE.a
|
||||
INTERFACE_INCLUDE_DIRECTORIES ${PREFIX}/include)
|
||||
# convert HYPRE version to integer
|
||||
string(REGEX MATCHALL "[0-9]+" HYPRE_SPLIT_VERSION ${HYPRE_FETCH_VERSION})
|
||||
list(GET HYPRE_SPLIT_VERSION 0 HYPRE_MAJOR_VERSION)
|
||||
list(GET HYPRE_SPLIT_VERSION 1 HYPRE_MINOR_VERSION)
|
||||
list(GET HYPRE_SPLIT_VERSION 2 HYPRE_PATCH_VERSION)
|
||||
math(EXPR HYPRE_VERSION "10000*${HYPRE_MAJOR_VERSION} + 100*${HYPRE_MINOR_VERSION} + ${HYPRE_PATCH_VERSION}")
|
||||
# set cache variables that would otherwise be set after mfem_find_package call
|
||||
set(HYPRE_VERSION ${HYPRE_VERSION} CACHE STRING "HYPRE version." FORCE)
|
||||
return()
|
||||
endif()
|
||||
|
||||
include(MfemCmakeUtilities)
|
||||
mfem_find_package(HYPRE HYPRE HYPRE_DIR "include" "HYPRE.h" "lib" "HYPRE"
|
||||
"Paths to headers required by HYPRE." "Libraries required by HYPRE."
|
||||
|
||||
@@ -9,10 +9,38 @@
|
||||
# terms of the BSD-3 license. We welcome feedback and contributions, see file
|
||||
# CONTRIBUTING.md for details.
|
||||
|
||||
# Defines the following variables:
|
||||
# Defines the following variables if fetching of TPLs is disabled (default):
|
||||
# - METIS_FOUND
|
||||
# - METIS_LIBRARIES
|
||||
# - METIS_INCLUDE_DIRS
|
||||
# - METIS_VERSION_5
|
||||
# otherwise, the following are defined:
|
||||
# - METIS (imported library target)
|
||||
# - METIS_VERSION_5 (cache variable)
|
||||
|
||||
if (METIS_FETCH OR FETCH_TPLS)
|
||||
set(METIS_FETCH_VERSION 4.0.3)
|
||||
add_library(METIS STATIC IMPORTED)
|
||||
# define external project
|
||||
message(STATUS "Will fetch METIS ${METIS_FETCH_VERSION} to be built with default options")
|
||||
set(PREFIX ${CMAKE_BINARY_DIR}/fetch/metis)
|
||||
include(ExternalProject)
|
||||
ExternalProject_Add(metis
|
||||
GIT_REPOSITORY https://github.com/mfem/tpls
|
||||
GIT_TAG b60352fbe9675d374b00828055e55be4584c7995 # tag from 1/16/25
|
||||
GIT_SHALLOW TRUE
|
||||
UPDATE_DISCONNECTED TRUE
|
||||
PREFIX ${PREFIX}
|
||||
CONFIGURE_COMMAND tar -xzf ../metis/metis-${METIS_FETCH_VERSION}-mac.tgz --strip=1
|
||||
INSTALL_COMMAND mkdir -p ${PREFIX}/lib && cp libmetis.a ${PREFIX}/lib/)
|
||||
# set imported library target properties
|
||||
add_dependencies(METIS metis)
|
||||
set_target_properties(METIS PROPERTIES
|
||||
IMPORTED_LOCATION ${PREFIX}/lib/libmetis.a)
|
||||
# set cache variables that would otherwise be set after mfem_find_package call
|
||||
set(METIS_VERSION_5 FALSE CACHE BOOL "Is METIS version 5?")
|
||||
return()
|
||||
endif()
|
||||
|
||||
include(MfemCmakeUtilities)
|
||||
mfem_find_package(METIS METIS METIS_DIR "include;Lib" "metis.h"
|
||||
|
||||
+4
-1
@@ -23,11 +23,14 @@
|
||||
#include "_config.hpp"
|
||||
#endif
|
||||
|
||||
#include <cstdint>
|
||||
#include <climits>
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
|
||||
#if (defined(MFEM_USE_CUDA) && defined(__CUDACC__)) || \
|
||||
(defined(MFEM_USE_HIP) && defined(__HIPCC__))
|
||||
(defined(MFEM_USE_HIP) && defined(__HIP__))
|
||||
#define MFEM_HOST_DEVICE __host__ __device__
|
||||
#else
|
||||
#define MFEM_HOST_DEVICE
|
||||
|
||||
@@ -89,6 +89,12 @@ option(MFEM_ENABLE_EXAMPLES "Build all of the examples" OFF)
|
||||
option(MFEM_ENABLE_MINIAPPS "Build all of the miniapps" OFF)
|
||||
option(MFEM_ENABLE_BENCHMARKS "Build all of the benchmarks" OFF)
|
||||
|
||||
# Allow a user to specify fetching of certain third-party libraries instead of
|
||||
# searching for existing installations.
|
||||
option(FETCH_TPLS "Enable fetching of all supported third-party libraries" OFF)
|
||||
option(HYPRE_FETCH "Enable fetching of hypre" OFF)
|
||||
option(METIS_FETCH "Enable fetching of METIS" OFF)
|
||||
|
||||
# Setting CXX/MPICXX on the command line or in user.cmake will overwrite the
|
||||
# autodetected C++ compiler.
|
||||
# set(CXX g++)
|
||||
|
||||
@@ -82,6 +82,8 @@ set(SRCS
|
||||
fe/fe_ser.cpp
|
||||
fe_coll.cpp
|
||||
fespace.cpp
|
||||
derefmat_op.cpp
|
||||
pderefmat_op.cpp
|
||||
geom.cpp
|
||||
gridfunc.cpp
|
||||
hybridization.cpp
|
||||
|
||||
+26
-64
@@ -266,11 +266,7 @@ void PABilinearFormExtension::SetupRestrictionOperators(const L2FaceValues m)
|
||||
|
||||
// Gather the attributes on the host from all the elements
|
||||
const Mesh &mesh = *trial_fes->GetMesh();
|
||||
elem_attributes.SetSize(mesh.GetNE());
|
||||
for (int i = 0; i < mesh.GetNE(); ++i)
|
||||
{
|
||||
elem_attributes[i] = mesh.GetAttribute(i);
|
||||
}
|
||||
elem_attributes = &mesh.GetElementAttributes();
|
||||
}
|
||||
|
||||
// Construct face restriction operators only if the bilinear form has
|
||||
@@ -329,45 +325,7 @@ void PABilinearFormExtension::SetupRestrictionOperators(const L2FaceValues m)
|
||||
bdr_face_dYdn.SetSize(bdr_face_restrict_lex->Height());
|
||||
}
|
||||
|
||||
const Mesh &mesh = *trial_fes->GetMesh();
|
||||
// See LinearFormExtension::Update for explanation of f_to_be logic.
|
||||
std::unordered_map<int,int> f_to_be;
|
||||
for (int i = 0; i < mesh.GetNBE(); ++i)
|
||||
{
|
||||
const int f = mesh.GetBdrElementFaceIndex(i);
|
||||
f_to_be[f] = i;
|
||||
}
|
||||
const int nf_bdr = trial_fes->GetNFbyType(FaceType::Boundary);
|
||||
bdr_attributes.SetSize(nf_bdr);
|
||||
int f_ind = 0;
|
||||
int missing_bdr_elems = 0;
|
||||
for (int f = 0; f < mesh.GetNumFaces(); ++f)
|
||||
{
|
||||
if (!mesh.GetFaceInformation(f).IsOfFaceType(FaceType::Boundary))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
int attribute = 1; // default value
|
||||
if (f_to_be.find(f) != f_to_be.end())
|
||||
{
|
||||
const int be = f_to_be[f];
|
||||
attribute = mesh.GetBdrAttribute(be);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If a boundary face does not correspond to the a boundary element,
|
||||
// we assign it the default attribute of 1. We also generate a
|
||||
// warning at runtime with the number of such missing elements.
|
||||
++missing_bdr_elems;
|
||||
}
|
||||
bdr_attributes[f_ind] = attribute;
|
||||
++f_ind;
|
||||
}
|
||||
if (missing_bdr_elems)
|
||||
{
|
||||
MFEM_WARNING("Missing " << missing_bdr_elems << " boundary elements "
|
||||
"for boundary faces.");
|
||||
}
|
||||
bdr_face_attributes = &trial_fes->GetMesh()->GetBdrFaceAttributes();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -429,7 +387,7 @@ void PABilinearFormExtension::AssembleDiagonal(Vector &y) const
|
||||
mfem::forall(ne, [=] MFEM_HOST_DEVICE (int e)
|
||||
{
|
||||
const int attr = d_attr[e];
|
||||
if (d_m[attr - 1] == 0)
|
||||
if (attr <= 0 || d_m[attr - 1] == 0)
|
||||
{
|
||||
for (int i = 0; i < nd; ++i)
|
||||
{
|
||||
@@ -450,7 +408,7 @@ void PABilinearFormExtension::AssembleDiagonal(Vector &y) const
|
||||
for (int i = 0; i < iSz; ++i)
|
||||
{
|
||||
assemble_diagonal_with_markers(*integrators[i], elem_markers[i],
|
||||
elem_attributes, localY);
|
||||
*elem_attributes, localY);
|
||||
}
|
||||
const ElementRestriction* H1elem_restrict =
|
||||
dynamic_cast<const ElementRestriction*>(elem_restrict);
|
||||
@@ -476,7 +434,7 @@ void PABilinearFormExtension::AssembleDiagonal(Vector &y) const
|
||||
for (int i = 0; i < iSz; ++i)
|
||||
{
|
||||
assemble_diagonal_with_markers(*integrators[i], elem_markers[i],
|
||||
elem_attributes, y);
|
||||
*elem_attributes, y);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -489,7 +447,7 @@ void PABilinearFormExtension::AssembleDiagonal(Vector &y) const
|
||||
for (int i = 0; i < n_bdr_integs; ++i)
|
||||
{
|
||||
assemble_diagonal_with_markers(*bdr_integs[i], bdr_markers[i],
|
||||
bdr_attributes, bdr_face_Y);
|
||||
*bdr_face_attributes, bdr_face_Y);
|
||||
}
|
||||
bdr_face_restrict_lex->AddAbsMultTranspose(bdr_face_Y, y);
|
||||
}
|
||||
@@ -588,7 +546,7 @@ void PABilinearFormExtension::MultInternal(const Vector &x, Vector &y,
|
||||
for (int i = 0; i < iSz; ++i)
|
||||
{
|
||||
AddMultWithMarkers(*integrators[i], localX, elem_markers[i],
|
||||
elem_attributes, false, localY, useAbs);
|
||||
*elem_attributes, false, localY, useAbs);
|
||||
}
|
||||
if (H1elem_restrict && useAbs)
|
||||
{
|
||||
@@ -690,8 +648,8 @@ void PABilinearFormExtension::MultInternal(const Vector &x, Vector &y,
|
||||
}
|
||||
for (int i = 0; i < n_bdr_integs; ++i)
|
||||
{
|
||||
AddMultWithMarkers(*bdr_integs[i], bdr_face_X, bdr_markers[i], bdr_attributes,
|
||||
false, bdr_face_Y);
|
||||
AddMultWithMarkers(*bdr_integs[i], bdr_face_X, bdr_markers[i],
|
||||
*bdr_face_attributes, false, bdr_face_Y);
|
||||
}
|
||||
for (int i = 0; i < n_bdr_face_integs; ++i)
|
||||
{
|
||||
@@ -699,12 +657,14 @@ void PABilinearFormExtension::MultInternal(const Vector &x, Vector &y,
|
||||
{
|
||||
AddMultNormalDerivativesWithMarkers(
|
||||
*bdr_face_integs[i], bdr_face_X, bdr_face_dXdn,
|
||||
bdr_face_markers[i], bdr_attributes, bdr_face_Y, bdr_face_dYdn);
|
||||
bdr_face_markers[i], *bdr_face_attributes, bdr_face_Y,
|
||||
bdr_face_dYdn);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddMultWithMarkers(*bdr_face_integs[i], bdr_face_X, bdr_face_markers[i],
|
||||
bdr_attributes, false, bdr_face_Y);
|
||||
AddMultWithMarkers(*bdr_face_integs[i], bdr_face_X,
|
||||
bdr_face_markers[i], *bdr_face_attributes, false,
|
||||
bdr_face_Y);
|
||||
}
|
||||
}
|
||||
bdr_face_restrict_lex->AddMultTransposeInPlace(bdr_face_Y, y);
|
||||
@@ -727,7 +687,7 @@ void PABilinearFormExtension::MultTranspose(const Vector &x, Vector &y) const
|
||||
localY = 0.0;
|
||||
for (int i = 0; i < iSz; ++i)
|
||||
{
|
||||
AddMultWithMarkers(*integrators[i], localX, elem_markers[i], elem_attributes,
|
||||
AddMultWithMarkers(*integrators[i], localX, elem_markers[i], *elem_attributes,
|
||||
true, localY);
|
||||
}
|
||||
elem_restrict->MultTranspose(localY, y);
|
||||
@@ -774,13 +734,14 @@ void PABilinearFormExtension::MultTranspose(const Vector &x, Vector &y) const
|
||||
bdr_face_Y = 0.0;
|
||||
for (int i = 0; i < n_bdr_integs; ++i)
|
||||
{
|
||||
AddMultWithMarkers(*bdr_integs[i], bdr_face_X, bdr_markers[i], bdr_attributes,
|
||||
true, bdr_face_Y);
|
||||
AddMultWithMarkers(*bdr_integs[i], bdr_face_X, bdr_markers[i],
|
||||
*bdr_face_attributes, true, bdr_face_Y);
|
||||
}
|
||||
for (int i = 0; i < n_bdr_face_integs; ++i)
|
||||
{
|
||||
AddMultWithMarkers(*bdr_face_integs[i], bdr_face_X, bdr_face_markers[i],
|
||||
bdr_attributes, true, bdr_face_Y);
|
||||
AddMultWithMarkers(*bdr_face_integs[i], bdr_face_X,
|
||||
bdr_face_markers[i], *bdr_face_attributes, true,
|
||||
bdr_face_Y);
|
||||
}
|
||||
bdr_face_restrict_lex->AddMultTransposeInPlace(bdr_face_Y, y);
|
||||
}
|
||||
@@ -804,7 +765,7 @@ static void AddWithMarkers_(
|
||||
mfem::forall(ne, [=] MFEM_HOST_DEVICE (int e)
|
||||
{
|
||||
const int attr = d_attr[e];
|
||||
if (d_m[attr - 1] == 0) { return; }
|
||||
if (attr <= 0 || d_m[attr - 1] == 0) { return; }
|
||||
for (int i = 0; i < nd; ++i)
|
||||
{
|
||||
d_y(i, e) += d_x(i, e);
|
||||
@@ -920,7 +881,8 @@ void EABilinearFormExtension::Assemble()
|
||||
{
|
||||
const int i = idx % sz;
|
||||
const int e = idx / sz;
|
||||
const real_t val = d_m[d_a[e] - 1] ? d_ea_1(i, e) : 0.0;
|
||||
const real_t val =
|
||||
d_a[e] > 0 ? (d_m[d_a[e] - 1] ? d_ea_1(i, e) : 0) : 0;
|
||||
if (add)
|
||||
{
|
||||
d_ea_2(i, e) += val;
|
||||
@@ -953,7 +915,7 @@ void EABilinearFormExtension::Assemble()
|
||||
ea_data_tmp.SetSize(ea_data.Size());
|
||||
integrators[i]->AssembleEA(*a->FESpace(), ea_data_tmp, false);
|
||||
add_with_markers(ea_data_tmp, ea_data, ne, *markers,
|
||||
elem_attributes, add);
|
||||
*elem_attributes, add);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -982,7 +944,7 @@ void EABilinearFormExtension::Assemble()
|
||||
ea_data_tmp.SetSize(ea_data_bdr.Size());
|
||||
bdr_integs[i]->AssembleEABoundary(*a->FESpace(), ea_data_tmp, add);
|
||||
add_with_markers(ea_data_tmp, ea_data_bdr, nf_bdr, *markers,
|
||||
bdr_attributes, add);
|
||||
*bdr_face_attributes, add);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1031,7 +993,7 @@ void EABilinearFormExtension::Assemble()
|
||||
ea_data_tmp,
|
||||
add);
|
||||
add_with_markers(ea_data_tmp, ea_data_bdr, nf_bdr, *markers,
|
||||
bdr_attributes, add);
|
||||
*bdr_face_attributes, add);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,8 @@ class PABilinearFormExtension : public BilinearFormExtension
|
||||
protected:
|
||||
const FiniteElementSpace *trial_fes, *test_fes; // Not owned
|
||||
/// Attributes of all mesh elements.
|
||||
Array<int> elem_attributes, bdr_attributes;
|
||||
const Array<int> *elem_attributes; // Not owned
|
||||
const Array<int> *bdr_face_attributes; // Not owned
|
||||
mutable Vector tmp_evec; // Work array
|
||||
mutable Vector localX, localY;
|
||||
mutable Vector int_face_X, int_face_Y;
|
||||
|
||||
+10
-10
@@ -812,7 +812,7 @@ protected:
|
||||
const FiniteElement & test_fe) const
|
||||
{
|
||||
return (trial_fe.GetDim() == 1 && test_fe.GetDim() == 1 &&
|
||||
trial_fe.GetDerivType() == mfem::FiniteElement::GRAD &&
|
||||
trial_fe.GetDerivType() == mfem::FiniteElement::GRAD &&
|
||||
test_fe.GetRangeType() == mfem::FiniteElement::SCALAR );
|
||||
}
|
||||
|
||||
@@ -884,7 +884,7 @@ protected:
|
||||
const FiniteElement & trial_fe,
|
||||
const FiniteElement & test_fe) const
|
||||
{
|
||||
return (trial_fe.GetDerivType() == mfem::FiniteElement::DIV &&
|
||||
return (trial_fe.GetDerivType() == mfem::FiniteElement::DIV &&
|
||||
test_fe.GetRangeType() == mfem::FiniteElement::SCALAR );
|
||||
}
|
||||
|
||||
@@ -919,7 +919,7 @@ protected:
|
||||
const FiniteElement & trial_fe,
|
||||
const FiniteElement & test_fe) const
|
||||
{
|
||||
return (trial_fe.GetDerivType() == mfem::FiniteElement::DIV &&
|
||||
return (trial_fe.GetDerivType() == mfem::FiniteElement::DIV &&
|
||||
test_fe.GetRangeType() == mfem::FiniteElement::VECTOR );
|
||||
}
|
||||
|
||||
@@ -1600,7 +1600,7 @@ public:
|
||||
{
|
||||
return (trial_fe.GetCurlDim() == 3 && test_fe.GetRangeDim() == 3 &&
|
||||
trial_fe.GetRangeType() == mfem::FiniteElement::VECTOR &&
|
||||
trial_fe.GetDerivType() == mfem::FiniteElement::CURL &&
|
||||
trial_fe.GetDerivType() == mfem::FiniteElement::CURL &&
|
||||
test_fe.GetRangeType() == mfem::FiniteElement::VECTOR );
|
||||
}
|
||||
|
||||
@@ -1635,7 +1635,7 @@ public:
|
||||
{
|
||||
return (trial_fe.GetDim() == 2 && test_fe.GetDim() == 2 &&
|
||||
trial_fe.GetRangeType() == mfem::FiniteElement::VECTOR &&
|
||||
trial_fe.GetDerivType() == mfem::FiniteElement::CURL &&
|
||||
trial_fe.GetDerivType() == mfem::FiniteElement::CURL &&
|
||||
test_fe.GetRangeType() == mfem::FiniteElement::VECTOR );
|
||||
}
|
||||
|
||||
@@ -1669,7 +1669,7 @@ public:
|
||||
{
|
||||
return (trial_fe.GetDim() == 2 && test_fe.GetDim() == 2 &&
|
||||
trial_fe.GetRangeType() == mfem::FiniteElement::SCALAR &&
|
||||
trial_fe.GetDerivType() == mfem::FiniteElement::GRAD &&
|
||||
trial_fe.GetDerivType() == mfem::FiniteElement::GRAD &&
|
||||
test_fe.GetRangeType() == mfem::FiniteElement::SCALAR );
|
||||
}
|
||||
|
||||
@@ -1760,7 +1760,7 @@ public:
|
||||
const FiniteElement & test_fe) const
|
||||
{
|
||||
return (trial_fe.GetRangeType() == mfem::FiniteElement::SCALAR &&
|
||||
trial_fe.GetDerivType() == mfem::FiniteElement::GRAD &&
|
||||
trial_fe.GetDerivType() == mfem::FiniteElement::GRAD &&
|
||||
test_fe.GetRangeType() == mfem::FiniteElement::SCALAR );
|
||||
}
|
||||
|
||||
@@ -1793,7 +1793,7 @@ public:
|
||||
const FiniteElement & test_fe) const
|
||||
{
|
||||
return (trial_fe.GetRangeType() == mfem::FiniteElement::SCALAR &&
|
||||
trial_fe.GetDerivType() == mfem::FiniteElement::GRAD &&
|
||||
trial_fe.GetDerivType() == mfem::FiniteElement::GRAD &&
|
||||
test_fe.GetRangeType() == mfem::FiniteElement::VECTOR &&
|
||||
test_fe.GetDerivType() == mfem::FiniteElement::DIV );
|
||||
}
|
||||
@@ -1832,7 +1832,7 @@ public:
|
||||
const FiniteElement & test_fe) const
|
||||
{
|
||||
return (trial_fe.GetRangeType() == mfem::FiniteElement::VECTOR &&
|
||||
trial_fe.GetDerivType() == mfem::FiniteElement::DIV &&
|
||||
trial_fe.GetDerivType() == mfem::FiniteElement::DIV &&
|
||||
test_fe.GetRangeType() == mfem::FiniteElement::SCALAR &&
|
||||
test_fe.GetDerivType() == mfem::FiniteElement::GRAD
|
||||
);
|
||||
@@ -1973,7 +1973,7 @@ protected:
|
||||
const FiniteElement & test_fe) const override
|
||||
{
|
||||
return (trial_fe.GetCurlDim() == 3 && test_fe.GetRangeDim() == 3 &&
|
||||
trial_fe.GetDerivType() == mfem::FiniteElement::CURL &&
|
||||
trial_fe.GetDerivType() == mfem::FiniteElement::CURL &&
|
||||
test_fe.GetRangeType() == mfem::FiniteElement::VECTOR );
|
||||
}
|
||||
|
||||
|
||||
@@ -912,7 +912,7 @@ ConduitDataCollection::GridFunctionToBlueprintField(mfem::GridFunction *gf,
|
||||
|
||||
if (vdim == 1) // scalar case
|
||||
{
|
||||
n_field["values"].set_external(gf->GetData(),
|
||||
n_field["values"].set_external(const_cast<real_t *>(gf->HostRead()),
|
||||
ndofs);
|
||||
}
|
||||
else // vector case
|
||||
@@ -925,18 +925,18 @@ ConduitDataCollection::GridFunctionToBlueprintField(mfem::GridFunction *gf,
|
||||
int vdim_stride = (ordering == Ordering::byNODES ? ndofs : 1);
|
||||
|
||||
index_t offset = 0;
|
||||
index_t stride = sizeof(double) * entry_stride;
|
||||
index_t stride = sizeof(real_t) * entry_stride;
|
||||
|
||||
for (int d = 0; d < vdim; d++)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << "v" << d;
|
||||
std::string comp_name = oss.str();
|
||||
n_field["values"][comp_name].set_external(gf->GetData(),
|
||||
n_field["values"][comp_name].set_external(const_cast<real_t *>(gf->HostRead()),
|
||||
ndofs,
|
||||
offset,
|
||||
stride);
|
||||
offset += sizeof(double) * vdim_stride;
|
||||
offset += sizeof(real_t) * vdim_stride;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,266 @@
|
||||
// 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 "derefmat_op.hpp"
|
||||
#include "fes_kernels.hpp"
|
||||
|
||||
/// \cond DO_NOT_DOCUMENT
|
||||
namespace mfem
|
||||
{
|
||||
|
||||
namespace internal
|
||||
{
|
||||
template <Ordering::Type Order, bool Atomic>
|
||||
static void DerefMultKernelImpl(const DerefineMatrixOp &op, const Vector &x,
|
||||
Vector &y)
|
||||
{
|
||||
DerefineMatrixOpMultFunctor<Order, Atomic> func;
|
||||
func.xptr = x.Read();
|
||||
y.UseDevice();
|
||||
y = 0.;
|
||||
func.yptr = y.ReadWrite();
|
||||
func.bsptr = op.block_storage.Read();
|
||||
func.boptr = op.block_offsets.Read();
|
||||
func.brptr = op.block_row_idcs_offsets.Read();
|
||||
func.bcptr = op.block_col_idcs_offsets.Read();
|
||||
func.rptr = op.row_idcs.Read();
|
||||
func.cptr = op.col_idcs.Read();
|
||||
func.vdims = op.fespace->GetVDim();
|
||||
func.nblocks = op.block_offsets.Size();
|
||||
func.width = op.Width() / func.vdims;
|
||||
func.height = op.Height() / func.vdims;
|
||||
func.Run(op.max_rows);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
||||
DerefineMatrixOp::DerefineMatrixOp(FiniteElementSpace &fespace_, int old_ndofs,
|
||||
const Table *old_elem_dof,
|
||||
const Table *old_elem_fos)
|
||||
: Operator(fespace_.GetVSize(), old_ndofs * fespace_.GetVDim()),
|
||||
fespace(&fespace_)
|
||||
{
|
||||
static Kernels kernels;
|
||||
constexpr int max_team_size = 256;
|
||||
/// TODO: Implement DofTransformation support
|
||||
|
||||
MFEM_VERIFY(fespace->Nonconforming(),
|
||||
"Not implemented for conforming meshes.");
|
||||
MFEM_VERIFY(old_ndofs, "Missing previous (finer) space.");
|
||||
MFEM_VERIFY(fespace->GetNDofs() <= old_ndofs,
|
||||
"Previous space is not finer.");
|
||||
|
||||
const CoarseFineTransformations &dtrans =
|
||||
fespace->GetMesh()->ncmesh->GetDerefinementTransforms();
|
||||
|
||||
MFEM_ASSERT(dtrans.embeddings.Size() == old_elem_dof->Size(), "");
|
||||
|
||||
const bool is_dg = fespace->FEColl()->GetContType()
|
||||
== FiniteElementCollection::DISCONTINUOUS;
|
||||
DenseMatrix localRVO; // for variable-order only
|
||||
|
||||
DenseTensor localR[Geometry::NumGeom];
|
||||
int total_rows = 0;
|
||||
int total_cols = 0;
|
||||
block_offsets.SetSize(dtrans.embeddings.Size());
|
||||
block_offsets.HostWrite();
|
||||
if (fespace->IsVariableOrder())
|
||||
{
|
||||
// TODO: any potential for some compression here?
|
||||
// determine storage size and offsets
|
||||
block_offsets[0] = 0;
|
||||
int total_size = 0;
|
||||
for (int k = 0; k < dtrans.embeddings.Size(); ++k)
|
||||
{
|
||||
const Embedding &emb = dtrans.embeddings[k];
|
||||
const FiniteElement *fe = fespace->GetFE(emb.parent);
|
||||
const int ldof = fe->GetDof();
|
||||
if (k + 1 < dtrans.embeddings.Size())
|
||||
{
|
||||
block_offsets[k + 1] = block_offsets[k] + ldof * ldof;
|
||||
}
|
||||
total_rows += ldof;
|
||||
total_cols += ldof;
|
||||
total_size += ldof * ldof;
|
||||
}
|
||||
block_storage.SetSize(total_size);
|
||||
}
|
||||
else
|
||||
{
|
||||
// compression scheme:
|
||||
// block_offsets is the start of each block, potentially repeated
|
||||
// only need to store localR for used shapes
|
||||
Mesh::GeometryList elem_geoms(*fespace->GetMesh());
|
||||
|
||||
int geom_offsets[Geometry::NumGeom];
|
||||
{
|
||||
int size = 0;
|
||||
for (int i = 0; i < elem_geoms.Size(); ++i)
|
||||
{
|
||||
fespace->GetLocalDerefinementMatrices(elem_geoms[i],
|
||||
localR[elem_geoms[i]]);
|
||||
geom_offsets[elem_geoms[i]] = size;
|
||||
size += localR[elem_geoms[i]].TotalSize();
|
||||
}
|
||||
block_storage.SetSize(size);
|
||||
// copy blocks into block_storage
|
||||
auto bs_ptr = block_storage.HostWrite();
|
||||
for (int i = 0; i < elem_geoms.Size(); ++i)
|
||||
{
|
||||
std::copy(localR[elem_geoms[i]].Data(),
|
||||
localR[elem_geoms[i]].Data()
|
||||
+ localR[elem_geoms[i]].TotalSize(),
|
||||
bs_ptr);
|
||||
bs_ptr += localR[elem_geoms[i]].TotalSize();
|
||||
}
|
||||
}
|
||||
for (int k = 0; k < dtrans.embeddings.Size(); ++k)
|
||||
{
|
||||
const Embedding &emb = dtrans.embeddings[k];
|
||||
Geometry::Type geom =
|
||||
fespace->GetMesh()->GetElementBaseGeometry(emb.parent);
|
||||
|
||||
auto size = localR[geom].SizeI() * localR[geom].SizeJ();
|
||||
total_rows += localR[geom].SizeI();
|
||||
total_cols += localR[geom].SizeJ();
|
||||
// set block offsets and sizes
|
||||
block_offsets[k] = geom_offsets[geom] + size * emb.matrix;
|
||||
}
|
||||
}
|
||||
row_idcs.SetSize(total_rows);
|
||||
row_idcs.HostWrite();
|
||||
col_idcs.SetSize(total_cols);
|
||||
col_idcs.HostWrite();
|
||||
block_row_idcs_offsets.SetSize(dtrans.embeddings.Size() + 1);
|
||||
block_row_idcs_offsets.HostWrite();
|
||||
block_col_idcs_offsets.SetSize(dtrans.embeddings.Size() + 1);
|
||||
block_col_idcs_offsets.HostWrite();
|
||||
block_row_idcs_offsets[0] = 0;
|
||||
block_col_idcs_offsets[0] = 0;
|
||||
|
||||
// compute index information
|
||||
Array<int> dofs, old_dofs;
|
||||
max_rows = 1;
|
||||
|
||||
{
|
||||
Array<int> mark(fespace->GetNDofs());
|
||||
mark = 0;
|
||||
auto bs_ptr = block_storage.HostWrite();
|
||||
int ridx = 0;
|
||||
int cidx = 0;
|
||||
int num_marked = 0;
|
||||
for (int k = 0; k < dtrans.embeddings.Size(); k++)
|
||||
{
|
||||
const Embedding &emb = dtrans.embeddings[k];
|
||||
Geometry::Type geom =
|
||||
fespace->GetMesh()->GetElementBaseGeometry(emb.parent);
|
||||
|
||||
if (fespace->IsVariableOrder())
|
||||
{
|
||||
const FiniteElement *fe = fespace->GetFE(emb.parent);
|
||||
const DenseTensor &pmats = dtrans.point_matrices[geom];
|
||||
const int ldof = fe->GetDof();
|
||||
|
||||
IsoparametricTransformation isotr;
|
||||
isotr.SetIdentityTransformation(geom);
|
||||
|
||||
localRVO.SetSize(ldof, ldof);
|
||||
isotr.SetPointMat(pmats(emb.matrix));
|
||||
// Local restriction is size ldofxldof assuming that the parent
|
||||
// and child are of same polynomial order.
|
||||
fe->GetLocalRestriction(isotr, localRVO);
|
||||
// copy block
|
||||
auto size = localRVO.Height() * localRVO.Width();
|
||||
std::copy(localRVO.Data(), localRVO.Data() + size, bs_ptr);
|
||||
bs_ptr += size;
|
||||
}
|
||||
DenseMatrix &lR =
|
||||
fespace->IsVariableOrder() ? localRVO : localR[geom](emb.matrix);
|
||||
block_row_idcs_offsets[k + 1] =
|
||||
block_row_idcs_offsets[k] + lR.Height();
|
||||
block_col_idcs_offsets[k + 1] = block_col_idcs_offsets[k] + lR.Width();
|
||||
max_rows = std::max(lR.Height(), max_rows);
|
||||
// index information
|
||||
fespace->elem_dof->GetRow(emb.parent, dofs);
|
||||
old_elem_dof->GetRow(k, old_dofs);
|
||||
MFEM_VERIFY(old_dofs.Size() == dofs.Size(),
|
||||
"Parent and child must have same #dofs.");
|
||||
for (int i = 0; i < lR.Height(); ++i, ++ridx)
|
||||
{
|
||||
if (!std::isfinite(lR(i, 0)))
|
||||
{
|
||||
row_idcs[ridx] = INT_MAX;
|
||||
continue;
|
||||
}
|
||||
int r = dofs[i];
|
||||
int m = (r >= 0) ? r : (-1 - r);
|
||||
if (is_dg || !mark[m])
|
||||
{
|
||||
row_idcs[ridx] = r;
|
||||
mark[m] = 1;
|
||||
++num_marked;
|
||||
}
|
||||
else
|
||||
{
|
||||
row_idcs[ridx] = INT_MAX;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < lR.Width(); ++i, ++cidx)
|
||||
{
|
||||
col_idcs[cidx] = old_dofs[i];
|
||||
}
|
||||
}
|
||||
if (!is_dg && !fespace->IsVariableOrder())
|
||||
{
|
||||
MFEM_VERIFY(num_marked * fespace->GetVDim() == Height(),
|
||||
"internal error: not all rows were set.");
|
||||
}
|
||||
}
|
||||
// if not using GPU, set max_rows/max_cols to zero
|
||||
if (Device::Allows(Backend::DEVICE_MASK))
|
||||
{
|
||||
max_rows = std::min(max_rows, max_team_size);
|
||||
}
|
||||
else
|
||||
{
|
||||
max_rows = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void DerefineMatrixOp::Mult(const Vector &x, Vector &y) const
|
||||
{
|
||||
const bool is_dg = fespace->FEColl()->GetContType()
|
||||
== FiniteElementCollection::DISCONTINUOUS;
|
||||
// DG needs atomic summation
|
||||
MultKernel::Run(fespace->GetOrdering(), is_dg, *this, x, y);
|
||||
}
|
||||
|
||||
DerefineMatrixOp::Kernels::Kernels()
|
||||
{
|
||||
MultKernel::Specialization<Ordering::byNODES, false>::Add();
|
||||
MultKernel::Specialization<Ordering::byVDIM, false>::Add();
|
||||
MultKernel::Specialization<Ordering::byNODES, true>::Add();
|
||||
MultKernel::Specialization<Ordering::byVDIM, true>::Add();
|
||||
}
|
||||
|
||||
template <Ordering::Type Order, bool Atomic>
|
||||
DerefineMatrixOp::MultKernelType DerefineMatrixOp::MultKernel::Kernel()
|
||||
{
|
||||
return internal::DerefMultKernelImpl<Order, Atomic>;
|
||||
}
|
||||
|
||||
DerefineMatrixOp::MultKernelType
|
||||
DerefineMatrixOp::MultKernel::Fallback(Ordering::Type, bool)
|
||||
{
|
||||
MFEM_ABORT("invalid MultKernel parameters");
|
||||
}
|
||||
} // namespace mfem
|
||||
/// \endcond DO_NOT_DOCUMENT
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
|
||||
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
|
||||
// LICENSE and NOTICE for details. LLNL-CODE-806117.
|
||||
//
|
||||
// This file is part of the MFEM library. For more information and source code
|
||||
// availability visit https://mfem.org.
|
||||
//
|
||||
// MFEM is free software; you can redistribute it and/or modify it under the
|
||||
// terms of the BSD-3 license. We welcome feedback and contributions, see file
|
||||
// CONTRIBUTING.md for details.
|
||||
|
||||
#ifndef MFEM_DEREFMAT_OP
|
||||
#define MFEM_DEREFMAT_OP
|
||||
|
||||
#include "fespace.hpp"
|
||||
|
||||
#include "kernel_dispatch.hpp"
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
|
||||
/// \cond DO_NOT_DOCUMENT
|
||||
|
||||
struct DerefineMatrixOp : public Operator
|
||||
{
|
||||
FiniteElementSpace *fespace;
|
||||
/// offsets into block_storage
|
||||
Array<int> block_offsets;
|
||||
/// offsets into row_idcs
|
||||
Array<int> block_row_idcs_offsets;
|
||||
/// offsets into col_idcs
|
||||
Array<int> block_col_idcs_offsets;
|
||||
/// mapping for row dofs, INT_MAX indicates the block row should be ignored.
|
||||
/// negative means the row data should be negated.
|
||||
Array<int> row_idcs;
|
||||
/// mapping for col dofs, negative means the col data should be negated.
|
||||
Array<int> col_idcs;
|
||||
/// dense block matrices which can be reused to construct the full matrix
|
||||
/// operation. These are stored contiguously and blocks have no restrictions
|
||||
/// on shape (can be rectangle and differ from block to block).
|
||||
Vector block_storage;
|
||||
/// maximum height of any block in block_storage for GPU
|
||||
/// parallelization, or 1 for CPU runs.
|
||||
int max_rows;
|
||||
|
||||
using MultKernelType = void (*)(const DerefineMatrixOp &, const Vector &,
|
||||
Vector &);
|
||||
/// template args: ordering, atomic
|
||||
MFEM_REGISTER_KERNELS(MultKernel, MultKernelType, (Ordering::Type, bool));
|
||||
|
||||
struct Kernels
|
||||
{
|
||||
Kernels();
|
||||
};
|
||||
|
||||
void Mult(const Vector &x, Vector &y) const;
|
||||
|
||||
DerefineMatrixOp(FiniteElementSpace &fespace_, int old_ndofs,
|
||||
const Table *old_elem_dof, const Table *old_elem_fos);
|
||||
};
|
||||
|
||||
/// \endcond DO_NOT_DOCUMENT
|
||||
|
||||
} // namespace mfem
|
||||
#endif
|
||||
+4
-4
@@ -327,8 +327,8 @@ void print_mpi_sync(const std::string& msg)
|
||||
// First gather string lengths
|
||||
size_t msg_len = msg.length();
|
||||
std::vector<size_t> lengths(nranks);
|
||||
MPI_Gather(&msg_len, 1, MPI_INT,
|
||||
lengths.data(), 1, MPI_INT,
|
||||
MPI_Gather(&msg_len, 1, MPITypeMap<size_t>::mpi_type,
|
||||
lengths.data(), 1, MPITypeMap<size_t>::mpi_type,
|
||||
0, MPI_COMM_WORLD);
|
||||
|
||||
if (myrank == 0)
|
||||
@@ -568,7 +568,7 @@ struct ThreadBlocks
|
||||
int z = 1;
|
||||
};
|
||||
|
||||
#if (defined(MFEM_USE_CUDA) || defined(MFEM_USE_HIP))
|
||||
#if defined(MFEM_USE_CUDA_OR_HIP)
|
||||
template <typename func_t>
|
||||
__global__ void forall_kernel_shmem(func_t f, int n)
|
||||
{
|
||||
@@ -591,7 +591,7 @@ void forall(func_t f,
|
||||
if (Device::Allows(Backend::CUDA_MASK) ||
|
||||
Device::Allows(Backend::HIP_MASK))
|
||||
{
|
||||
#if (defined(MFEM_USE_CUDA) || defined(MFEM_USE_HIP))
|
||||
#if defined(MFEM_USE_CUDA_OR_HIP)
|
||||
// int gridsize = (N + Z - 1) / Z;
|
||||
int num_bytes = num_shmem * sizeof(decltype(shmem));
|
||||
dim3 block_size(blocks.x, blocks.y, blocks.z);
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
|
||||
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
|
||||
// LICENSE and NOTICE for details. LLNL-CODE-806117.
|
||||
//
|
||||
// This file is part of the MFEM library. For more information and source code
|
||||
// availability visit https://mfem.org.
|
||||
//
|
||||
// MFEM is free software; you can redistribute it and/or modify it under the
|
||||
// terms of the BSD-3 license. We welcome feedback and contributions, see file
|
||||
// CONTRIBUTING.md for details.
|
||||
|
||||
#ifndef MFEM_FES_KERNELS_HPP
|
||||
#define MFEM_FES_KERNELS_HPP
|
||||
|
||||
#include "../general/forall.hpp"
|
||||
|
||||
#include <climits>
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
/// \cond DO_NOT_DOCUMENT
|
||||
namespace internal
|
||||
{
|
||||
|
||||
///
|
||||
/// Implements matrix-vector multiply $y = A x$ for a sparse matrix composed of
|
||||
/// a sum of smaller dense blocks. There is additional permutation/sign
|
||||
/// information associated with each block. The base class only implements
|
||||
/// helper routines such as computing block widths, index into x, index into y,
|
||||
/// and column in A given sub-block information.
|
||||
/// @sa DerefineMatrixOpMultFunctor
|
||||
///
|
||||
/// @tparam Order vdim ordering for x and y. Note that for Diag = false this is
|
||||
/// ignored for x as x has a special interleaved order.
|
||||
/// @tparam Base used for the curious recurring template pattern (CRTP) so the
|
||||
/// base class can access child class fields without virtual functions
|
||||
/// @tparam Diag true if this corresponds to the diagonal block (coarse element
|
||||
/// and fine element are on our rank), false otherwise (coarse element is on our
|
||||
/// rank, fine element is on a different rank).
|
||||
///
|
||||
template <Ordering::Type Order, class Base, bool Diag = true>
|
||||
struct DerefineMatrixOpFunctorBase;
|
||||
|
||||
template <class Base>
|
||||
struct DerefineMatrixOpFunctorBase<Ordering::byNODES, Base, true>
|
||||
{
|
||||
/// block column indices offsets
|
||||
const int *bcptr;
|
||||
/// column indices
|
||||
const int *cptr;
|
||||
|
||||
int MFEM_HOST_DEVICE BlockWidth(int k) const
|
||||
{
|
||||
return bcptr[k + 1] - bcptr[k];
|
||||
}
|
||||
|
||||
void MFEM_HOST_DEVICE Col(int j, int k, int &col, int &sign) const
|
||||
{
|
||||
col = cptr[bcptr[k] + j];
|
||||
if (col < 0)
|
||||
{
|
||||
col = -1 - col;
|
||||
sign = -sign;
|
||||
}
|
||||
}
|
||||
|
||||
int MFEM_HOST_DEVICE IndexX(int col, int vdim, int) const
|
||||
{
|
||||
return col + vdim * static_cast<const Base *>(this)->width;
|
||||
}
|
||||
int MFEM_HOST_DEVICE IndexY(int row, int vdim) const
|
||||
{
|
||||
return row + vdim * static_cast<const Base *>(this)->height;
|
||||
}
|
||||
};
|
||||
|
||||
template <class Base>
|
||||
struct DerefineMatrixOpFunctorBase<Ordering::byVDIM, Base, true>
|
||||
{
|
||||
/// block column indices offsets
|
||||
const int *bcptr;
|
||||
/// column indices
|
||||
const int *cptr;
|
||||
|
||||
int MFEM_HOST_DEVICE BlockWidth(int k) const
|
||||
{
|
||||
return bcptr[k + 1] - bcptr[k];
|
||||
}
|
||||
|
||||
void MFEM_HOST_DEVICE Col(int j, int k, int &col, int &sign) const
|
||||
{
|
||||
col = cptr[bcptr[k] + j];
|
||||
if (col < 0)
|
||||
{
|
||||
col = -1 - col;
|
||||
sign = -sign;
|
||||
}
|
||||
}
|
||||
|
||||
int MFEM_HOST_DEVICE IndexX(int col, int vdim, int) const
|
||||
{
|
||||
return vdim + col * static_cast<const Base *>(this)->vdims;
|
||||
}
|
||||
int MFEM_HOST_DEVICE IndexY(int row, int vdim) const
|
||||
{
|
||||
return vdim + row * static_cast<const Base *>(this)->vdims;
|
||||
}
|
||||
};
|
||||
|
||||
template <class Base>
|
||||
struct DerefineMatrixOpFunctorBase<Ordering::byNODES, Base, false>
|
||||
{
|
||||
/// receive segment offsets
|
||||
const int *segptr;
|
||||
/// receive segment index
|
||||
const int *rsptr;
|
||||
/// off-diagonal block column offsets
|
||||
const int *coptr;
|
||||
/// off-diagonal block widths
|
||||
const int *bwptr;
|
||||
|
||||
int MFEM_HOST_DEVICE BlockWidth(int k) const { return bwptr[k]; }
|
||||
|
||||
void MFEM_HOST_DEVICE Col(int j, int k, int &col, int &sign) const
|
||||
{
|
||||
col = coptr[k] + j;
|
||||
}
|
||||
|
||||
int MFEM_HOST_DEVICE IndexX(int col, int vdim, int k) const
|
||||
{
|
||||
int tmp = rsptr[k];
|
||||
int segwidth = segptr[tmp + 1] - segptr[tmp];
|
||||
return segptr[tmp] * static_cast<const Base *>(this)->vdims + col +
|
||||
vdim * segwidth;
|
||||
}
|
||||
int MFEM_HOST_DEVICE IndexY(int row, int vdim) const
|
||||
{
|
||||
return row + vdim * static_cast<const Base *>(this)->height;
|
||||
}
|
||||
};
|
||||
|
||||
template <class Base>
|
||||
struct DerefineMatrixOpFunctorBase<Ordering::byVDIM, Base, false>
|
||||
{
|
||||
/// receive segment offsets
|
||||
const int *segptr;
|
||||
/// receive segment index
|
||||
const int *rsptr;
|
||||
/// off-diagonal block column offsets
|
||||
const int *coptr;
|
||||
/// off-diagonal block widths
|
||||
const int *bwptr;
|
||||
|
||||
int MFEM_HOST_DEVICE BlockWidth(int k) const { return bwptr[k]; }
|
||||
|
||||
void MFEM_HOST_DEVICE Col(int j, int k, int &col, int &sign) const
|
||||
{
|
||||
col = coptr[k] + j;
|
||||
}
|
||||
|
||||
int MFEM_HOST_DEVICE IndexX(int col, int vdim, int k) const
|
||||
{
|
||||
int tmp = rsptr[k];
|
||||
int segwidth = segptr[tmp + 1] - segptr[tmp];
|
||||
return segptr[tmp] * static_cast<const Base *>(this)->vdims + col +
|
||||
vdim * segwidth;
|
||||
}
|
||||
int MFEM_HOST_DEVICE IndexY(int row, int vdim) const
|
||||
{
|
||||
return vdim + row * static_cast<const Base *>(this)->vdims;
|
||||
}
|
||||
};
|
||||
|
||||
/// internally used to implement the derefinement operator Mult diagonal
|
||||
/// block
|
||||
template <Ordering::Type Order, bool Atomic, bool Diag = true>
|
||||
struct DerefineMatrixOpMultFunctor
|
||||
: public DerefineMatrixOpFunctorBase<
|
||||
Order, DerefineMatrixOpMultFunctor<Order, Atomic, Diag>, Diag>
|
||||
{
|
||||
const real_t *xptr;
|
||||
real_t *yptr;
|
||||
/// block storage
|
||||
const real_t *bsptr;
|
||||
/// block offsets
|
||||
const int *boptr;
|
||||
/// block row index offsets
|
||||
const int *brptr;
|
||||
/// row indices
|
||||
const int *rptr;
|
||||
|
||||
// number of blocks
|
||||
int nblocks;
|
||||
// number of components
|
||||
int vdims;
|
||||
/// overall operator height (for vdim = 1)
|
||||
int height;
|
||||
/// overall operator width (for vdim = 1)
|
||||
int width;
|
||||
void MFEM_HOST_DEVICE operator()(int kidx) const
|
||||
{
|
||||
int k = kidx % nblocks;
|
||||
int vdim = kidx / nblocks;
|
||||
|
||||
int block_height = brptr[k + 1] - brptr[k];
|
||||
int block_width = this->BlockWidth(k);
|
||||
MFEM_FOREACH_THREAD(i, x, block_height)
|
||||
{
|
||||
int row = rptr[brptr[k] + i];
|
||||
int rsign = 1;
|
||||
if (row < 0)
|
||||
{
|
||||
row = -1 - row;
|
||||
rsign = -1;
|
||||
}
|
||||
if (row < INT_MAX)
|
||||
{
|
||||
// row not marked as unused
|
||||
real_t sum = 0;
|
||||
for (int j = 0; j < block_width; ++j)
|
||||
{
|
||||
int col, sign = rsign;
|
||||
this->Col(j, k, col, sign);
|
||||
sum += sign * bsptr[boptr[k] + i + j * block_height] *
|
||||
xptr[this->IndexX(col, vdim, k)];
|
||||
}
|
||||
#if defined(__CUDA_ARCH__) or defined(__HIP_DEVICE_COMPILE__)
|
||||
if (Atomic)
|
||||
{
|
||||
atomicAdd(yptr + this->IndexY(row, vdim), sum);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
yptr[this->IndexY(row, vdim)] += sum;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// N is the max block row size (doesn't have to be a power of 2)
|
||||
void Run(int N) const { forall_2D(nblocks * vdims, N, 1, *this); }
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
/// \endcond DO_NOT_DOCUMENT
|
||||
} // namespace mfem
|
||||
|
||||
#endif
|
||||
+13
-6
@@ -17,6 +17,9 @@
|
||||
#include "fem.hpp"
|
||||
#include "ceed/interface/util.hpp"
|
||||
|
||||
#include "derefmat_op.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdarg>
|
||||
|
||||
@@ -24,9 +27,9 @@ using namespace std;
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
|
||||
template <> void Ordering::
|
||||
DofsToVDofs<Ordering::byNODES>(int ndofs, int vdim, Array<int> &dofs)
|
||||
template <>
|
||||
void Ordering::DofsToVDofs<Ordering::byNODES>(int ndofs, int vdim,
|
||||
Array<int> &dofs)
|
||||
{
|
||||
// static method
|
||||
int size = dofs.Size();
|
||||
@@ -40,8 +43,9 @@ DofsToVDofs<Ordering::byNODES>(int ndofs, int vdim, Array<int> &dofs)
|
||||
}
|
||||
}
|
||||
|
||||
template <> void Ordering::
|
||||
DofsToVDofs<Ordering::byVDIM>(int ndofs, int vdim, Array<int> &dofs)
|
||||
template <>
|
||||
void Ordering::DofsToVDofs<Ordering::byVDIM>(int ndofs, int vdim,
|
||||
Array<int> &dofs)
|
||||
{
|
||||
// static method
|
||||
int size = dofs.Size();
|
||||
@@ -55,7 +59,6 @@ DofsToVDofs<Ordering::byVDIM>(int ndofs, int vdim, Array<int> &dofs)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
FiniteElementSpace::FiniteElementSpace()
|
||||
: mesh(NULL), fec(NULL), vdim(0), ordering(Ordering::byNODES),
|
||||
ndofs(0), nvdofs(0), nedofs(0), nfdofs(0), nbdofs(0),
|
||||
@@ -4244,7 +4247,11 @@ void FiniteElementSpace::Update(bool want_transform)
|
||||
case Mesh::DEREFINE:
|
||||
{
|
||||
BuildConformingInterpolation();
|
||||
#if 0
|
||||
Th.Reset(DerefinementMatrix(old_ndofs, old_elem_dof, old_elem_fos));
|
||||
#else
|
||||
Th.Reset(new DerefineMatrixOp(*this, old_ndofs, old_elem_dof, old_elem_fos));
|
||||
#endif
|
||||
if (IsVariableOrder())
|
||||
{
|
||||
if (cP && cR_hp)
|
||||
|
||||
+5
-1
@@ -113,7 +113,7 @@ class QuadratureSpace;
|
||||
class QuadratureInterpolator;
|
||||
class FaceQuadratureInterpolator;
|
||||
class PRefinementTransferOperator;
|
||||
|
||||
struct DerefineMatrixOp;
|
||||
|
||||
/** @brief Class FiniteElementSpace - responsible for providing FEM view of the
|
||||
mesh, mainly managing the set of degrees of freedom.
|
||||
@@ -246,6 +246,7 @@ class FiniteElementSpace
|
||||
friend class PRefinementTransferOperator;
|
||||
friend void Mesh::Swap(Mesh &, bool);
|
||||
friend class LORBase;
|
||||
friend struct DerefineMatrixOp;
|
||||
|
||||
protected:
|
||||
/// The mesh that FE space lives on (not owned).
|
||||
@@ -921,6 +922,9 @@ public:
|
||||
{ return mesh->GetBdrElementType(i); }
|
||||
|
||||
/// Returns ElementTransformation for the @a i-th element.
|
||||
/// @note The returned pointer references an object owned by the associated
|
||||
/// @a Mesh that will be modified by other calls to `GetElementTransformation`.
|
||||
/// As such, this pointer should @b not be deleted by the caller.
|
||||
ElementTransformation *GetElementTransformation(int i) const
|
||||
{ return mesh->GetElementTransformation(i); }
|
||||
|
||||
|
||||
@@ -59,26 +59,23 @@ void MassIntegrator::AssemblePA(const FiniteElementSpace &fes)
|
||||
|
||||
QuadratureSpace qs(*mesh, *ir);
|
||||
CoefficientVector coeff(Q, qs, CoefficientStorage::COMPRESSED);
|
||||
|
||||
const int NE = ne;
|
||||
const int Q1D = quad1D;
|
||||
const int NQ = static_cast<int>(std::pow(Q1D, dim));
|
||||
const bool const_c = coeff.Size() == 1;
|
||||
const bool by_val = map_type == FiniteElement::VALUE;
|
||||
const auto W = Reshape(ir->GetWeights().Read(), NQ);
|
||||
const auto J = Reshape(geom->detJ.Read(), NQ, NE);
|
||||
const auto C = const_c ? Reshape(coeff.Read(), 1, 1) :
|
||||
Reshape(coeff.Read(), NQ,NE);
|
||||
auto v = Reshape(pa_data.Write(), NQ, NE);
|
||||
mfem::forall_2D(NE, NQ, 1, [=] MFEM_HOST_DEVICE (int e)
|
||||
{
|
||||
MFEM_FOREACH_THREAD(i, x, NQ)
|
||||
const int NE = ne;
|
||||
const int NQ = nq;
|
||||
const bool const_c = coeff.Size() == 1;
|
||||
const bool by_val = map_type == FiniteElement::VALUE;
|
||||
const auto W = Reshape(ir->GetWeights().Read(), NQ);
|
||||
const auto J = Reshape(geom->detJ.Read(), NQ, NE);
|
||||
const auto C =
|
||||
const_c ? Reshape(coeff.Read(), 1, 1) : Reshape(coeff.Read(), NQ, NE);
|
||||
auto v = Reshape(pa_data.Write(), NQ, NE);
|
||||
mfem::forall(NQ, NE, [=] MFEM_HOST_DEVICE(int q, int e)
|
||||
{
|
||||
const real_t detJ = J(i,e);
|
||||
const real_t coeff = const_c ? C(0,0) : C(i,e);
|
||||
v(i,e) = W(i) * coeff * (by_val ? detJ : 1.0/detJ);
|
||||
}
|
||||
});
|
||||
const real_t detJ = J(q, e);
|
||||
const real_t coeff = const_c ? C(0, 0) : C(q, e);
|
||||
v(q, e) = W(q) * coeff * (by_val ? detJ : 1.0 / detJ);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void MassIntegrator::AssemblePABoundary(const FiniteElementSpace &fes)
|
||||
@@ -109,50 +106,22 @@ void MassIntegrator::AssemblePABoundary(const FiniteElementSpace &fes)
|
||||
CoefficientVector coeff(Q, qs, CoefficientStorage::COMPRESSED);
|
||||
|
||||
const int NE = ne;
|
||||
const int Q1D = quad1D;
|
||||
const int NQ = nq;
|
||||
const bool const_c = coeff.Size() == 1;
|
||||
const bool by_val = map_type == FiniteElement::VALUE;
|
||||
if (dim==1)
|
||||
{
|
||||
const auto W = Reshape(ir->GetWeights().Read(), Q1D);
|
||||
const auto J = Reshape(face_geom->detJ.Read(), Q1D, NE);
|
||||
const auto C = const_c ? Reshape(coeff.Read(), 1, 1) :
|
||||
Reshape(coeff.Read(), Q1D, NE);
|
||||
auto v = Reshape(pa_data.Write(), Q1D, NE);
|
||||
mfem::forall_2D(NE, Q1D, 1, [=] MFEM_HOST_DEVICE (int e)
|
||||
const auto W = Reshape(ir->GetWeights().Read(), NQ);
|
||||
const auto J = Reshape(face_geom->detJ.Read(), NQ, NE);
|
||||
const auto C = const_c ? Reshape(coeff.Read(), 1, 1)
|
||||
: Reshape(coeff.Read(), NQ, NE);
|
||||
auto v = Reshape(pa_data.Write(), NQ, NE);
|
||||
mfem::forall(NQ, NE, [=] MFEM_HOST_DEVICE(int q, int e)
|
||||
{
|
||||
MFEM_FOREACH_THREAD(qx,x,Q1D)
|
||||
{
|
||||
const real_t detJ = J(qx,e);
|
||||
const real_t coeff = const_c ? C(0,0) : C(qx,e);
|
||||
v(qx,e) = W(qx) * coeff * (by_val ? detJ : 1.0/detJ);
|
||||
}
|
||||
const real_t detJ = J(q, e);
|
||||
const real_t coeff = const_c ? C(0, 0) : C(q, e);
|
||||
v(q, e) = W(q) * coeff * (by_val ? detJ : 1.0 / detJ);
|
||||
});
|
||||
}
|
||||
else if (dim==2)
|
||||
{
|
||||
const auto W = Reshape(ir->GetWeights().Read(), Q1D,Q1D);
|
||||
const auto J = Reshape(face_geom->detJ.Read(), Q1D,Q1D,NE);
|
||||
const auto C = const_c ? Reshape(coeff.Read(), 1,1,1) :
|
||||
Reshape(coeff.Read(), Q1D,Q1D,NE);
|
||||
auto v = Reshape(pa_data.Write(), Q1D,Q1D, NE);
|
||||
mfem::forall_2D(NE, Q1D, Q1D, [=] MFEM_HOST_DEVICE (int e)
|
||||
{
|
||||
MFEM_FOREACH_THREAD(qx,x,Q1D)
|
||||
{
|
||||
MFEM_FOREACH_THREAD(qy,y,Q1D)
|
||||
{
|
||||
const real_t detJ = J(qx,qy,e);
|
||||
const real_t coeff = const_c ? C(0,0,0) : C(qx,qy,e);
|
||||
v(qx,qy,e) = W(qx,qy) * coeff * (by_val ? detJ : 1.0/detJ);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
MFEM_ABORT("Not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
void MassIntegrator::AssembleDiagonalPA(Vector &diag)
|
||||
|
||||
+29
-29
@@ -346,13 +346,13 @@ private:
|
||||
template<typename T>
|
||||
T operator() (const blitz::TinyVector<T,3>& x) const
|
||||
{
|
||||
int el_order=el->GetOrder();
|
||||
T u1[el_order+1];
|
||||
T u2[el_order+1];
|
||||
T u3[el_order+1];
|
||||
TmplPoly_1D::CalcBernstein(el_order, x[0], u1);
|
||||
TmplPoly_1D::CalcBernstein(el_order, x[1], u2);
|
||||
TmplPoly_1D::CalcBernstein(el_order, x[2], u3);
|
||||
const int el_order = el->GetOrder();
|
||||
std::vector<T> u1(el_order+1);
|
||||
std::vector<T> u2(el_order+1);
|
||||
std::vector<T> u3(el_order+1);
|
||||
TmplPoly_1D::CalcBernstein(el_order, x[0], u1.data());
|
||||
TmplPoly_1D::CalcBernstein(el_order, x[1], u2.data());
|
||||
TmplPoly_1D::CalcBernstein(el_order, x[2], u3.data());
|
||||
|
||||
const Array<int>& dof_map=el->GetDofMap();
|
||||
|
||||
@@ -370,17 +370,17 @@ private:
|
||||
template<typename T>
|
||||
blitz::TinyVector<T,3> grad(const blitz::TinyVector<T,3>& x) const
|
||||
{
|
||||
int el_order=el->GetOrder();
|
||||
T u1[el_order+1];
|
||||
T u2[el_order+1];
|
||||
T u3[el_order+1];
|
||||
T d1[el_order+1];
|
||||
T d2[el_order+1];
|
||||
T d3[el_order+1];
|
||||
const int el_order = el->GetOrder();
|
||||
std::vector<T> u1(el_order+1);
|
||||
std::vector<T> u2(el_order+1);
|
||||
std::vector<T> u3(el_order+1);
|
||||
std::vector<T> d1(el_order+1);
|
||||
std::vector<T> d2(el_order+1);
|
||||
std::vector<T> d3(el_order+1);
|
||||
|
||||
TmplPoly_1D::CalcBernstein(el_order,x[0], u1, d1);
|
||||
TmplPoly_1D::CalcBernstein(el_order,x[1], u2, d2);
|
||||
TmplPoly_1D::CalcBernstein(el_order,x[2], u3, d3);
|
||||
TmplPoly_1D::CalcBernstein(el_order,x[0], u1.data(), d1.data());
|
||||
TmplPoly_1D::CalcBernstein(el_order,x[1], u2.data(), d2.data());
|
||||
TmplPoly_1D::CalcBernstein(el_order,x[2], u3.data(), d3.data());
|
||||
|
||||
blitz::TinyVector<T,3> res(T(0.0),T(0.0),T(0.0));
|
||||
|
||||
@@ -415,11 +415,11 @@ private:
|
||||
template<typename T>
|
||||
T operator() (const blitz::TinyVector<T,2>& x) const
|
||||
{
|
||||
int el_order=el->GetOrder();
|
||||
T u1[el_order+1];
|
||||
T u2[el_order+1];
|
||||
TmplPoly_1D::CalcBernstein(el_order, x[0], u1);
|
||||
TmplPoly_1D::CalcBernstein(el_order, x[1], u2);
|
||||
const int el_order = el->GetOrder();
|
||||
std::vector<T> u1(el_order+1);
|
||||
std::vector<T> u2(el_order+1);
|
||||
TmplPoly_1D::CalcBernstein(el_order, x[0], u1.data());
|
||||
TmplPoly_1D::CalcBernstein(el_order, x[1], u2.data());
|
||||
|
||||
const Array<int>& dof_map=el->GetDofMap();
|
||||
|
||||
@@ -437,14 +437,14 @@ private:
|
||||
template<typename T>
|
||||
blitz::TinyVector<T,2> grad(const blitz::TinyVector<T,2>& x) const
|
||||
{
|
||||
int el_order=el->GetOrder();
|
||||
T u1[el_order+1];
|
||||
T u2[el_order+1];
|
||||
T d1[el_order+1];
|
||||
T d2[el_order+1];
|
||||
const int el_order = el->GetOrder();
|
||||
std::vector<T> u1(el_order+1);
|
||||
std::vector<T> u2(el_order+1);
|
||||
std::vector<T> d1(el_order+1);
|
||||
std::vector<T> d2(el_order+1);
|
||||
|
||||
TmplPoly_1D::CalcBernstein(el_order,x[0], u1, d1);
|
||||
TmplPoly_1D::CalcBernstein(el_order,x[1], u2, d2);
|
||||
TmplPoly_1D::CalcBernstein(el_order,x[0], u1.data(), d1.data());
|
||||
TmplPoly_1D::CalcBernstein(el_order,x[1], u2.data(), d2.data());
|
||||
|
||||
blitz::TinyVector<T,2> res(T(0.0),T(0.0));
|
||||
|
||||
|
||||
+10
-32
@@ -51,7 +51,7 @@ void LinearFormExtension::Assemble()
|
||||
{
|
||||
// scan the attributes to set the markers to 0 or 1
|
||||
const int NE = fes.GetNE();
|
||||
const auto attr = attributes.Read();
|
||||
const auto attr = attributes->Read();
|
||||
const auto dimk = domain_integs_marker_k->Read();
|
||||
auto markers_w = markers.Write();
|
||||
mfem::forall(NE, [=] MFEM_HOST_DEVICE (int e)
|
||||
@@ -93,13 +93,14 @@ void LinearFormExtension::Assemble()
|
||||
else
|
||||
{
|
||||
// scan the attributes to set the markers to 0 or 1
|
||||
const int NBE = bdr_attributes.Size();
|
||||
const auto attr = bdr_attributes.Read();
|
||||
const int NBE = bdr_face_attributes->Size();
|
||||
const auto attr = bdr_face_attributes->Read();
|
||||
const auto attr_markers = boundary_integs_marker_k->Read();
|
||||
auto markers_w = bdr_markers.Write();
|
||||
mfem::forall(NBE, [=] MFEM_HOST_DEVICE (int e)
|
||||
mfem::forall(NBE, [=] MFEM_HOST_DEVICE(int e)
|
||||
{
|
||||
markers_w[e] = attr_markers[attr[e]-1] == 1;
|
||||
markers_w[e] =
|
||||
attr[e] > 0 ? (attr_markers[attr[e] - 1] == 1) : false;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -125,8 +126,7 @@ void LinearFormExtension::Update()
|
||||
//markers.UseDevice(true);
|
||||
|
||||
// Gather the attributes on the host from all the elements
|
||||
attributes.SetSize(NE);
|
||||
for (int i = 0; i < NE; ++i) { attributes[i] = mesh.GetAttribute(i); }
|
||||
attributes = &mesh.GetElementAttributes();
|
||||
|
||||
elem_restrict_lex = fes.GetElementRestriction(ordering);
|
||||
MFEM_VERIFY(elem_restrict_lex, "Element restriction not available");
|
||||
@@ -136,34 +136,12 @@ void LinearFormExtension::Update()
|
||||
|
||||
if (lf->boundary_integs.Size() > 0)
|
||||
{
|
||||
const int nf_bdr = fes.GetNFbyType(FaceType::Boundary);
|
||||
bdr_face_attributes = &mesh.GetBdrFaceAttributes();
|
||||
|
||||
const int nf_bdr = bdr_face_attributes->Size();
|
||||
bdr_markers.SetSize(nf_bdr);
|
||||
// bdr_markers.UseDevice(true);
|
||||
|
||||
// The face restriction will give us "face E-vectors" on the boundary that
|
||||
// are numbered in the order of the faces of mesh. This numbering will be
|
||||
// different than the numbering of the boundary elements. We compute
|
||||
// mappings so that the array `bdr_attributes[i]` gives the boundary
|
||||
// attribute of the `i`th boundary face in the mesh face order.
|
||||
std::unordered_map<int,int> f_to_be;
|
||||
for (int i = 0; i < mesh.GetNBE(); ++i)
|
||||
{
|
||||
const int f = mesh.GetBdrElementFaceIndex(i);
|
||||
f_to_be[f] = i;
|
||||
}
|
||||
MFEM_VERIFY(size_t(nf_bdr) == f_to_be.size(), "Incompatible sizes");
|
||||
bdr_attributes.SetSize(nf_bdr);
|
||||
int f_ind = 0;
|
||||
for (int f = 0; f < mesh.GetNumFaces(); ++f)
|
||||
{
|
||||
if (f_to_be.find(f) != f_to_be.end())
|
||||
{
|
||||
const int be = f_to_be[f];
|
||||
bdr_attributes[f_ind] = mesh.GetBdrAttribute(be);
|
||||
++f_ind;
|
||||
}
|
||||
}
|
||||
|
||||
bdr_restrict_lex =
|
||||
dynamic_cast<const FaceRestriction*>(
|
||||
fes.GetFaceRestriction(ordering, FaceType::Boundary,
|
||||
|
||||
@@ -25,7 +25,8 @@ class LinearForm;
|
||||
class LinearFormExtension
|
||||
{
|
||||
/// Attributes of all mesh elements.
|
||||
Array<int> attributes, bdr_attributes;
|
||||
const Array<int> *attributes; // Not owned
|
||||
const Array<int> *bdr_face_attributes; // Not owned
|
||||
|
||||
/// Temporary markers for device kernels.
|
||||
Array<int> markers, bdr_markers;
|
||||
|
||||
@@ -0,0 +1,591 @@
|
||||
// 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 "pderefmat_op.hpp"
|
||||
|
||||
#ifdef MFEM_USE_MPI
|
||||
|
||||
#include "fes_kernels.hpp"
|
||||
/// \cond DO_NOT_DOCUMENT
|
||||
namespace mfem
|
||||
{
|
||||
namespace internal
|
||||
{
|
||||
template <Ordering::Type Order, bool Atomic>
|
||||
static void ParDerefMultKernelImpl(const ParDerefineMatrixOp &op,
|
||||
const Vector &x, Vector &y)
|
||||
{
|
||||
// pack sends
|
||||
if (op.xghost_send.Size())
|
||||
{
|
||||
auto src = x.Read();
|
||||
auto idcs = op.send_permutations.Read();
|
||||
auto dst = Device::GetGPUAwareMPI() ? op.xghost_send.Write()
|
||||
: op.xghost_send.HostWrite();
|
||||
auto vdims = op.fespace->GetVDim();
|
||||
auto sptr = op.send_segment_idcs.Read();
|
||||
auto lptr = op.send_segments.Read();
|
||||
auto old_ndofs = x.Size() / vdims;
|
||||
|
||||
forall(op.send_permutations.Size(), [=] MFEM_HOST_DEVICE(int i)
|
||||
{
|
||||
int seg = sptr[i];
|
||||
int width = lptr[seg + 1] - lptr[seg];
|
||||
auto tdst = dst + i + lptr[seg] * vdims;
|
||||
int sign = 1;
|
||||
int col = idcs[i];
|
||||
if (col < 0)
|
||||
{
|
||||
sign = -1;
|
||||
col = -1 - col;
|
||||
}
|
||||
for (int vdim = 0; vdim < vdims; ++vdim)
|
||||
{
|
||||
tdst[vdim * width] =
|
||||
sign
|
||||
* src[Order == Ordering::byNODES ? (col + vdim * old_ndofs)
|
||||
: (col * vdims + vdim)];
|
||||
}
|
||||
});
|
||||
// TODO: is this needed so we can send the packed data correctly?
|
||||
// unclear for GPU-aware MPI, definitely required otherwise
|
||||
MFEM_DEVICE_SYNC;
|
||||
}
|
||||
// initialize off-diagonal receive and send
|
||||
op.requests.clear();
|
||||
if (op.xghost_recv.Size())
|
||||
{
|
||||
auto vdims = op.fespace->GetVDim();
|
||||
auto rcv = Device::GetGPUAwareMPI() ? op.xghost_recv.Write()
|
||||
: op.xghost_recv.HostWrite();
|
||||
for (int i = 0; i < op.recv_ranks.Size(); ++i)
|
||||
{
|
||||
op.requests.emplace_back();
|
||||
MPI_Irecv(rcv + op.recv_segments[i] * vdims,
|
||||
(op.recv_segments[i + 1] - op.recv_segments[i]) * vdims,
|
||||
MPITypeMap<real_t>::mpi_type, op.recv_ranks[i],
|
||||
MessageTag::DEREFINEMENT_MATRIX_CONSTRUCTION_DATA,
|
||||
op.fespace->GetComm(), &op.requests.back());
|
||||
}
|
||||
}
|
||||
if (op.xghost_send.Size())
|
||||
{
|
||||
auto vdims = op.fespace->GetVDim();
|
||||
// only is a GPU mem ptr if GPU-aware MPI is enabled
|
||||
auto dst = Device::GetGPUAwareMPI() ? op.xghost_send.Write()
|
||||
: op.xghost_send.HostWrite();
|
||||
for (int i = 0; i < op.send_ranks.Size(); ++i)
|
||||
{
|
||||
op.requests.emplace_back();
|
||||
MPI_Isend(dst + op.send_segments[i] * vdims,
|
||||
(op.send_segments[i + 1] - op.send_segments[i]) * vdims,
|
||||
MPITypeMap<real_t>::mpi_type, op.send_ranks[i],
|
||||
MessageTag::DEREFINEMENT_MATRIX_CONSTRUCTION_DATA,
|
||||
op.fespace->GetComm(), &op.requests.back());
|
||||
}
|
||||
}
|
||||
{
|
||||
// diagonal
|
||||
DerefineMatrixOpMultFunctor<Order, Atomic, true> func;
|
||||
func.xptr = x.Read();
|
||||
y.UseDevice();
|
||||
y = 0.;
|
||||
func.yptr = y.ReadWrite();
|
||||
func.bsptr = op.block_storage.Read();
|
||||
func.boptr = op.block_offsets.Read();
|
||||
func.brptr = op.block_row_idcs_offsets.Read();
|
||||
func.bcptr = op.block_col_idcs_offsets.Read();
|
||||
func.rptr = op.row_idcs.Read();
|
||||
func.cptr = op.col_idcs.Read();
|
||||
func.vdims = op.fespace->GetVDim();
|
||||
func.nblocks = op.block_offsets.Size();
|
||||
func.width = op.Width() / func.vdims;
|
||||
func.height = op.Height() / func.vdims;
|
||||
func.Run(op.max_rows);
|
||||
}
|
||||
// wait for comm to finish, if any
|
||||
if (op.requests.size())
|
||||
{
|
||||
MPI_Waitall(op.requests.size(), op.requests.data(), MPI_STATUSES_IGNORE);
|
||||
if (op.xghost_recv.Size())
|
||||
{
|
||||
// off-diagonal kernel
|
||||
DerefineMatrixOpMultFunctor<Order, Atomic, false> func;
|
||||
// directly read from host-pinned memory if not using GPU-aware MPI
|
||||
func.xptr = Device::GetGPUAwareMPI() ? op.xghost_recv.Read()
|
||||
: op.xghost_recv.HostRead();
|
||||
func.yptr = y.ReadWrite();
|
||||
func.bsptr = op.block_storage.Read();
|
||||
func.boptr = op.off_diag_block_offsets.Read();
|
||||
func.brptr = op.block_off_diag_row_idcs_offsets.Read();
|
||||
func.rsptr = op.recv_segment_idcs.Read();
|
||||
func.segptr = op.recv_segments.Read();
|
||||
func.coptr = op.block_off_diag_col_offsets.Read();
|
||||
func.bwptr = op.block_off_diag_widths.Read();
|
||||
func.rptr = op.row_off_diag_idcs.Read();
|
||||
func.vdims = op.fespace->GetVDim();
|
||||
func.nblocks = op.off_diag_block_offsets.Size();
|
||||
func.width = op.xghost_recv.Size() / func.vdims;
|
||||
func.height = op.Height() / func.vdims;
|
||||
func.Run(op.max_rows);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace internal
|
||||
|
||||
template <Ordering::Type Order, bool Atomic>
|
||||
ParDerefineMatrixOp::MultKernelType ParDerefineMatrixOp::MultKernel::Kernel()
|
||||
{
|
||||
return internal::ParDerefMultKernelImpl<Order, Atomic>;
|
||||
}
|
||||
|
||||
ParDerefineMatrixOp::MultKernelType
|
||||
ParDerefineMatrixOp::MultKernel::Fallback(Ordering::Type, bool)
|
||||
{
|
||||
MFEM_ABORT("invalid MultKernel parameters");
|
||||
}
|
||||
|
||||
ParDerefineMatrixOp::Kernels::Kernels()
|
||||
{
|
||||
MultKernel::Specialization<Ordering::byNODES, false>::Add();
|
||||
MultKernel::Specialization<Ordering::byVDIM, false>::Add();
|
||||
MultKernel::Specialization<Ordering::byNODES, true>::Add();
|
||||
MultKernel::Specialization<Ordering::byVDIM, true>::Add();
|
||||
}
|
||||
|
||||
void ParDerefineMatrixOp::Mult(const Vector &x, Vector &y) const
|
||||
{
|
||||
const bool is_dg = fespace->FEColl()->GetContType()
|
||||
== FiniteElementCollection::DISCONTINUOUS;
|
||||
// DG needs atomic summation
|
||||
MultKernel::Run(fespace->GetOrdering(), is_dg, *this, x, y);
|
||||
// use this to prevent xghost* from being re-purposed for subsequent Mult
|
||||
// calls
|
||||
MFEM_DEVICE_SYNC;
|
||||
}
|
||||
|
||||
ParDerefineMatrixOp::ParDerefineMatrixOp(ParFiniteElementSpace &fespace_,
|
||||
int old_ndofs,
|
||||
const Table *old_elem_dof,
|
||||
const Table *old_elem_fos)
|
||||
: Operator(fespace_.GetVSize(), old_ndofs * fespace_.GetVDim()),
|
||||
fespace(&fespace_)
|
||||
{
|
||||
static Kernels kernels;
|
||||
constexpr int max_team_size = 256;
|
||||
|
||||
const int NRanks = fespace->GetNRanks();
|
||||
|
||||
const int nrk = HYPRE_AssumedPartitionCheck() ? 2 : NRanks;
|
||||
|
||||
MFEM_VERIFY(fespace->Nonconforming(),
|
||||
"Not implemented for conforming meshes.");
|
||||
MFEM_VERIFY(fespace->old_dof_offsets[nrk],
|
||||
"Missing previous (finer) space.");
|
||||
|
||||
const int MyRank = fespace->GetMyRank();
|
||||
ParNCMesh *old_pncmesh = fespace->GetParMesh()->pncmesh;
|
||||
const CoarseFineTransformations &dtrans =
|
||||
old_pncmesh->GetDerefinementTransforms();
|
||||
const Array<int> &old_ranks = old_pncmesh->GetDerefineOldRanks();
|
||||
|
||||
const bool is_dg = fespace->FEColl()->GetContType()
|
||||
== FiniteElementCollection::DISCONTINUOUS;
|
||||
DenseMatrix localRVO; // for variable-order only
|
||||
|
||||
DenseTensor localR[Geometry::NumGeom];
|
||||
int diag_rows = 0;
|
||||
int off_diag_rows = 0;
|
||||
int diag_cols = 0;
|
||||
|
||||
auto get_ldofs = [&](int k) -> int
|
||||
{
|
||||
const Embedding &emb = dtrans.embeddings[k];
|
||||
if (fespace->IsVariableOrder())
|
||||
{
|
||||
const FiniteElement *fe = fespace->GetFE(emb.parent);
|
||||
return fe->GetDof();
|
||||
}
|
||||
else
|
||||
{
|
||||
Geometry::Type geom =
|
||||
fespace->GetParMesh()->GetElementBaseGeometry(emb.parent);
|
||||
return fespace->FEColl()->FiniteElementForGeometry(geom)->GetDof();
|
||||
}
|
||||
};
|
||||
Array<int> dofs, old_dofs;
|
||||
max_rows = 1;
|
||||
// first pass:
|
||||
// - determine memory block lengths
|
||||
// - identify dofs in x we need to send/receive
|
||||
// don't need to send the indices, fine rank will re-arrange and sign
|
||||
// change x before transmitting the ghost data
|
||||
|
||||
// key: coarse rank to send to
|
||||
// value: old dofs to send (with sign)
|
||||
std::map<int, std::vector<int>> to_send;
|
||||
// key: fine rank
|
||||
// value: indices into dtrans.embeddings
|
||||
std::map<int, std::vector<int>> od_ks;
|
||||
// key: fine rank
|
||||
// value: recv segment length
|
||||
std::map<int, int> od_seg_lens;
|
||||
int send_len = 0;
|
||||
int recv_len = 0;
|
||||
// size of block_storage, if fespace->IsVariableOrder()
|
||||
// otherwise unused
|
||||
int total_size = 0;
|
||||
int num_diagonal_blocks = 0;
|
||||
int num_offdiagonal_blocks = 0;
|
||||
for (int k = 0; k < dtrans.embeddings.Size(); ++k)
|
||||
{
|
||||
const Embedding &emb = dtrans.embeddings[k];
|
||||
int fine_rank = old_ranks[k];
|
||||
int coarse_rank = (emb.parent < 0) ? (-1 - emb.parent)
|
||||
: old_pncmesh->ElementRank(emb.parent);
|
||||
if (coarse_rank != MyRank && fine_rank == MyRank)
|
||||
{
|
||||
// this rank needs to send data in x to course_rank
|
||||
old_elem_dof->GetRow(k, old_dofs);
|
||||
auto &tmp = to_send[coarse_rank];
|
||||
send_len += old_dofs.Size();
|
||||
for (int i = 0; i < old_dofs.Size(); ++i)
|
||||
{
|
||||
tmp.emplace_back(old_dofs[i]);
|
||||
}
|
||||
}
|
||||
else if (coarse_rank == MyRank && fine_rank != MyRank)
|
||||
{
|
||||
// this rank needs to receive data in x from fine_rank
|
||||
MFEM_ASSERT(emb.parent >= 0, "");
|
||||
auto ldofs = get_ldofs(k);
|
||||
off_diag_rows += ldofs;
|
||||
recv_len += ldofs;
|
||||
od_ks[fine_rank].emplace_back(k);
|
||||
od_seg_lens[fine_rank] += ldofs;
|
||||
++num_offdiagonal_blocks;
|
||||
if (fespace->IsVariableOrder())
|
||||
{
|
||||
total_size += ldofs * ldofs;
|
||||
}
|
||||
}
|
||||
else if (coarse_rank == MyRank && fine_rank == MyRank)
|
||||
{
|
||||
MFEM_ASSERT(emb.parent >= 0, "");
|
||||
// diagonal
|
||||
++num_diagonal_blocks;
|
||||
auto ldofs = get_ldofs(k);
|
||||
diag_rows += ldofs;
|
||||
diag_cols += ldofs;
|
||||
if (fespace->IsVariableOrder())
|
||||
{
|
||||
total_size += ldofs * ldofs;
|
||||
}
|
||||
}
|
||||
}
|
||||
send_segments.SetSize(to_send.size() + 1);
|
||||
send_segments.HostWrite();
|
||||
send_ranks.SetSize(to_send.size());
|
||||
send_ranks.HostWrite();
|
||||
{
|
||||
int idx = 0;
|
||||
send_segments[0] = 0;
|
||||
for (auto &tmp : to_send)
|
||||
{
|
||||
send_ranks[idx] = tmp.first;
|
||||
send_segments[idx + 1] = send_segments[idx] + tmp.second.size();
|
||||
++idx;
|
||||
}
|
||||
}
|
||||
recv_segment_idcs.SetSize(off_diag_rows);
|
||||
recv_segment_idcs.HostWrite();
|
||||
recv_segments.SetSize(od_ks.size() + 1);
|
||||
recv_segments.HostWrite();
|
||||
recv_ranks.SetSize(od_ks.size());
|
||||
recv_ranks.HostWrite();
|
||||
|
||||
// set sizes
|
||||
row_idcs.SetSize(diag_rows);
|
||||
row_idcs.HostWrite();
|
||||
row_off_diag_idcs.SetSize(off_diag_rows);
|
||||
row_off_diag_idcs.HostWrite();
|
||||
col_idcs.SetSize(diag_cols);
|
||||
col_idcs.HostWrite();
|
||||
block_row_idcs_offsets.SetSize(num_diagonal_blocks + 1);
|
||||
block_row_idcs_offsets.HostWrite();
|
||||
block_col_idcs_offsets.SetSize(num_diagonal_blocks + 1);
|
||||
block_col_idcs_offsets.HostWrite();
|
||||
block_off_diag_row_idcs_offsets.SetSize(num_offdiagonal_blocks + 1);
|
||||
block_off_diag_row_idcs_offsets.HostWrite();
|
||||
block_off_diag_col_offsets.SetSize(num_offdiagonal_blocks);
|
||||
block_off_diag_col_offsets.HostWrite();
|
||||
block_off_diag_widths.SetSize(num_offdiagonal_blocks);
|
||||
block_off_diag_widths.HostWrite();
|
||||
pack_col_idcs.SetSize(send_len);
|
||||
// memory manager doesn't appear to have a graceful fallback for
|
||||
// HOST_PINNED if not built with CUDA or HIP
|
||||
#if defined(MFEM_USE_CUDA) or defined(MFEM_USE_HIP)
|
||||
xghost_send.SetSize(send_len * fespace->GetVDim(),
|
||||
Device::GetGPUAwareMPI() ? MemoryType::DEFAULT
|
||||
: MemoryType::HOST_PINNED);
|
||||
xghost_recv.SetSize(recv_len * fespace->GetVDim(),
|
||||
Device::GetGPUAwareMPI() ? MemoryType::DEFAULT
|
||||
: MemoryType::HOST_PINNED);
|
||||
#else
|
||||
xghost_send.SetSize(send_len * fespace->GetVDim());
|
||||
xghost_recv.SetSize(recv_len * fespace->GetVDim());
|
||||
#endif
|
||||
send_permutations.SetSize(send_len);
|
||||
send_segment_idcs.SetSize(send_len);
|
||||
block_offsets.SetSize(num_diagonal_blocks);
|
||||
block_offsets.HostWrite();
|
||||
off_diag_block_offsets.SetSize(num_offdiagonal_blocks);
|
||||
off_diag_block_offsets.HostWrite();
|
||||
int geom_offsets[Geometry::NumGeom];
|
||||
real_t *bs_ptr;
|
||||
|
||||
if (fespace->IsVariableOrder())
|
||||
{
|
||||
block_storage.SetSize(total_size);
|
||||
bs_ptr = block_storage.HostWrite();
|
||||
// compute block data later
|
||||
}
|
||||
else
|
||||
{
|
||||
// compression scheme:
|
||||
// block_offsets is the start of each block, potentially repeated
|
||||
// only need to store localR for used shapes
|
||||
Mesh::GeometryList elem_geoms(*fespace->GetMesh());
|
||||
|
||||
int size = 0;
|
||||
for (int i = 0; i < elem_geoms.Size(); ++i)
|
||||
{
|
||||
fespace->GetLocalDerefinementMatrices(elem_geoms[i],
|
||||
localR[elem_geoms[i]]);
|
||||
geom_offsets[elem_geoms[i]] = size;
|
||||
size += localR[elem_geoms[i]].TotalSize();
|
||||
}
|
||||
block_storage.SetSize(size);
|
||||
bs_ptr = block_storage.HostWrite();
|
||||
// copy blocks into block_storage
|
||||
for (int i = 0; i < elem_geoms.Size(); ++i)
|
||||
{
|
||||
std::copy(localR[elem_geoms[i]].Data(),
|
||||
localR[elem_geoms[i]].Data()
|
||||
+ localR[elem_geoms[i]].TotalSize(),
|
||||
bs_ptr);
|
||||
bs_ptr += localR[elem_geoms[i]].TotalSize();
|
||||
}
|
||||
}
|
||||
|
||||
// second pass:
|
||||
// - initialize buffers
|
||||
|
||||
{
|
||||
auto ptr = send_permutations.HostWrite();
|
||||
auto ptr2 = send_segment_idcs.HostWrite();
|
||||
int i = 0;
|
||||
for (auto &v : to_send)
|
||||
{
|
||||
ptr = std::copy(v.second.begin(), v.second.end(), ptr);
|
||||
for (size_t idx = 0; idx < v.second.size(); ++idx)
|
||||
{
|
||||
*ptr2 = i;
|
||||
++ptr2;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
block_row_idcs_offsets[0] = 0;
|
||||
block_col_idcs_offsets[0] = 0;
|
||||
block_off_diag_row_idcs_offsets[0] = 0;
|
||||
Array<int> mark(fespace->GetNDofs());
|
||||
mark = 0;
|
||||
{
|
||||
int idx = 0;
|
||||
recv_segments[0] = 0;
|
||||
for (auto &v : od_seg_lens)
|
||||
{
|
||||
recv_ranks[idx] = v.first;
|
||||
recv_segments[idx + 1] = recv_segments[idx] + v.second;
|
||||
++idx;
|
||||
}
|
||||
}
|
||||
// key: index into dtrans.embeddings
|
||||
// value: off-diagonal block offset, od_ridx, seg id
|
||||
std::unordered_map<int, std::array<int, 3>> ks_map;
|
||||
{
|
||||
int od_ridx = 0;
|
||||
int seg_id = 0;
|
||||
for (auto &v1 : od_ks)
|
||||
{
|
||||
for (auto k : v1.second)
|
||||
{
|
||||
auto &tmp = ks_map[k];
|
||||
tmp[0] = ks_map.size() - 1;
|
||||
tmp[1] = od_ridx;
|
||||
tmp[2] = seg_id;
|
||||
od_ridx += get_ldofs(k);
|
||||
}
|
||||
++seg_id;
|
||||
}
|
||||
}
|
||||
int diag_idx = 0;
|
||||
int var_offset = 0;
|
||||
int ridx = 0;
|
||||
int cidx = 0;
|
||||
// can't break this up into separate diagonals/off-diagonals loops because
|
||||
// of mark
|
||||
for (int k = 0; k < dtrans.embeddings.Size(); ++k)
|
||||
{
|
||||
const Embedding &emb = dtrans.embeddings[k];
|
||||
if (emb.parent < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
int fine_rank = old_ranks[k];
|
||||
int coarse_rank = (emb.parent < 0) ? (-1 - emb.parent)
|
||||
: old_pncmesh->ElementRank(emb.parent);
|
||||
if (coarse_rank == MyRank)
|
||||
{
|
||||
// either diagonal or off-diagonal
|
||||
Geometry::Type geom =
|
||||
fespace->GetMesh()->GetElementBaseGeometry(emb.parent);
|
||||
if (fespace->IsVariableOrder())
|
||||
{
|
||||
const FiniteElement *fe = fespace->GetFE(emb.parent);
|
||||
const DenseTensor &pmats = dtrans.point_matrices[geom];
|
||||
const int ldof = fe->GetDof();
|
||||
|
||||
IsoparametricTransformation isotr;
|
||||
isotr.SetIdentityTransformation(geom);
|
||||
|
||||
localRVO.SetSize(ldof, ldof);
|
||||
isotr.SetPointMat(pmats(emb.matrix));
|
||||
// Local restriction is size ldofxldof assuming that the parent
|
||||
// and child are of same polynomial order.
|
||||
fe->GetLocalRestriction(isotr, localRVO);
|
||||
// copy block
|
||||
auto s = localRVO.Height() * localRVO.Width();
|
||||
std::copy(localRVO.Data(), localRVO.Data() + s, bs_ptr);
|
||||
bs_ptr += s;
|
||||
}
|
||||
DenseMatrix &lR =
|
||||
fespace->IsVariableOrder() ? localRVO : localR[geom](emb.matrix);
|
||||
max_rows = std::max(lR.Height(), max_rows);
|
||||
auto size = lR.Height() * lR.Width();
|
||||
fespace->elem_dof->GetRow(emb.parent, dofs);
|
||||
if (fine_rank == MyRank)
|
||||
{
|
||||
// diagonal
|
||||
old_elem_dof->GetRow(k, old_dofs);
|
||||
MFEM_VERIFY(old_dofs.Size() == dofs.Size(),
|
||||
"Parent and child must have same #dofs.");
|
||||
block_row_idcs_offsets[diag_idx + 1] =
|
||||
block_row_idcs_offsets[diag_idx] + lR.Height();
|
||||
block_col_idcs_offsets[diag_idx + 1] =
|
||||
block_col_idcs_offsets[diag_idx] + lR.Width();
|
||||
|
||||
if (fespace->IsVariableOrder())
|
||||
{
|
||||
block_offsets[diag_idx] = var_offset;
|
||||
var_offset += size;
|
||||
}
|
||||
else
|
||||
{
|
||||
block_offsets[diag_idx] = geom_offsets[geom] + size * emb.matrix;
|
||||
}
|
||||
for (int i = 0; i < lR.Height(); ++i, ++ridx)
|
||||
{
|
||||
if (!std::isfinite(lR(i, 0)))
|
||||
{
|
||||
row_idcs[ridx] = INT_MAX;
|
||||
continue;
|
||||
}
|
||||
int r = dofs[i];
|
||||
int m = (r >= 0) ? r : (-1 - r);
|
||||
if (is_dg || !mark[m])
|
||||
{
|
||||
row_idcs[ridx] = r;
|
||||
mark[m] = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
row_idcs[ridx] = INT_MAX;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < lR.Width(); ++i, ++cidx)
|
||||
{
|
||||
col_idcs[cidx] = old_dofs[i];
|
||||
}
|
||||
++diag_idx;
|
||||
}
|
||||
else
|
||||
{
|
||||
// off-diagonal
|
||||
auto &tmp = ks_map.at(k);
|
||||
auto od_idx = tmp[0];
|
||||
auto od_ridx = tmp[1];
|
||||
block_off_diag_row_idcs_offsets[od_idx + 1] =
|
||||
block_off_diag_row_idcs_offsets[od_idx] + lR.Height();
|
||||
block_off_diag_col_offsets[od_idx] = od_ridx;
|
||||
block_off_diag_widths[od_idx] = lR.Width();
|
||||
recv_segment_idcs[od_idx] = tmp[2];
|
||||
|
||||
if (fespace->IsVariableOrder())
|
||||
{
|
||||
off_diag_block_offsets[od_idx] = var_offset;
|
||||
var_offset += size;
|
||||
}
|
||||
else
|
||||
{
|
||||
off_diag_block_offsets[od_idx] =
|
||||
geom_offsets[geom] + size * emb.matrix;
|
||||
}
|
||||
for (int i = 0; i < lR.Height(); ++i, ++od_ridx)
|
||||
{
|
||||
if (!std::isfinite(lR(i, 0)))
|
||||
{
|
||||
row_off_diag_idcs[od_ridx] = INT_MAX;
|
||||
continue;
|
||||
}
|
||||
int r = dofs[i];
|
||||
int m = (r >= 0) ? r : (-1 - r);
|
||||
if (is_dg || !mark[m])
|
||||
{
|
||||
row_off_diag_idcs[od_ridx] = r;
|
||||
mark[m] = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
row_off_diag_idcs[od_ridx] = INT_MAX;
|
||||
}
|
||||
}
|
||||
++od_idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
// if not using GPU, set max_rows/max_cols to zero
|
||||
if (Device::Allows(Backend::DEVICE_MASK))
|
||||
{
|
||||
max_rows = std::min(max_rows, max_team_size);
|
||||
}
|
||||
else
|
||||
{
|
||||
max_rows = 1;
|
||||
}
|
||||
requests.reserve(recv_ranks.Size() + send_ranks.Size());
|
||||
}
|
||||
} // namespace mfem
|
||||
/// \endcond DO_NOT_DOCUMENT
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,111 @@
|
||||
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
|
||||
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
|
||||
// LICENSE and NOTICE for details. LLNL-CODE-806117.
|
||||
//
|
||||
// This file is part of the MFEM library. For more information and source code
|
||||
// availability visit https://mfem.org.
|
||||
//
|
||||
// MFEM is free software; you can redistribute it and/or modify it under the
|
||||
// terms of the BSD-3 license. We welcome feedback and contributions, see file
|
||||
// CONTRIBUTING.md for details.
|
||||
|
||||
#ifndef MFEM_PDEREFMAT_OP
|
||||
#define MFEM_PDEREFMAT_OP
|
||||
|
||||
#include "../config/config.hpp"
|
||||
|
||||
#ifdef MFEM_USE_MPI
|
||||
|
||||
#include "pfespace.hpp"
|
||||
|
||||
#include "kernel_dispatch.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
|
||||
/// \cond DO_NOT_DOCUMENT
|
||||
struct ParDerefineMatrixOp : public Operator
|
||||
{
|
||||
ParFiniteElementSpace *fespace;
|
||||
/// offsets into block_storage for diagonal
|
||||
Array<int> block_offsets;
|
||||
/// offsets into row_idcs for diagonal
|
||||
Array<int> block_row_idcs_offsets;
|
||||
/// offsets into col_idcs for diagonal
|
||||
Array<int> block_col_idcs_offsets;
|
||||
|
||||
/// offsets into block_storage for off-diagonal
|
||||
Array<int> off_diag_block_offsets;
|
||||
/// offsets into row_idcs for off-diagonal
|
||||
Array<int> block_off_diag_row_idcs_offsets;
|
||||
Array<int> block_off_diag_col_offsets;
|
||||
Array<int> block_off_diag_widths;
|
||||
/// mapping for row dofs, INT_MAX indicates the block row should be ignored.
|
||||
/// negative means the row data should be negated.
|
||||
/// only for diagonal blocks
|
||||
Array<int> row_idcs;
|
||||
/// mapping for col dofs, negative means the col data should be negated.
|
||||
/// only for diagonal blocks
|
||||
Array<int> col_idcs;
|
||||
|
||||
Array<int> pack_col_idcs;
|
||||
|
||||
/// mapping for row dofs, INT_MAX indicates the block row should be ignored.
|
||||
/// negative means the row data should be negated.
|
||||
/// only for off-diagonal blocks
|
||||
Array<int> row_off_diag_idcs;
|
||||
/// dense block matrices which can be reused to construct the full matrix
|
||||
/// operation. These are stored contiguously and blocks have no restrictions
|
||||
/// on shape (can be rectangle and differ from block to block).
|
||||
/// This is only for the diagonal block.
|
||||
Vector block_storage;
|
||||
/// maximum height of any block in block_storage for GPU
|
||||
/// parallelization, or 1 for CPU runs.
|
||||
int max_rows;
|
||||
|
||||
/// quasi Ordering::byNODES, broken into sections by ranks we need to send
|
||||
/// the data to
|
||||
mutable Vector xghost_send;
|
||||
/// quasi Ordering::byNODES, broken into sections by ranks we received
|
||||
/// the data from
|
||||
mutable Vector xghost_recv;
|
||||
/// maps off-diagonal k to segment
|
||||
Array<int> recv_segment_idcs;
|
||||
/// cumulative count of dofs which will be received from other ranks
|
||||
Array<int> recv_segments;
|
||||
/// Source rank of each recv segment
|
||||
Array<int> recv_ranks;
|
||||
/// What send segment each entry in send_permutations corresponds to
|
||||
Array<int> send_segment_idcs;
|
||||
/// cumulative count of dofs which will be sent to other ranks
|
||||
Array<int> send_segments;
|
||||
/// Destination rank of each send segment
|
||||
Array<int> send_ranks;
|
||||
/// how to permute/sign change values from our local x to send to other ranks
|
||||
Array<int> send_permutations;
|
||||
/// internal buffer for MPI requests
|
||||
mutable std::vector<MPI_Request> requests;
|
||||
|
||||
using MultKernelType = void (*)(const ParDerefineMatrixOp &, const Vector &,
|
||||
Vector &);
|
||||
/// template args: ordering, atomic
|
||||
MFEM_REGISTER_KERNELS(MultKernel, MultKernelType, (Ordering::Type, bool));
|
||||
|
||||
struct Kernels
|
||||
{
|
||||
Kernels();
|
||||
};
|
||||
|
||||
void Mult(const Vector &x, Vector &y) const;
|
||||
|
||||
ParDerefineMatrixOp(ParFiniteElementSpace &fespace_, int old_ndofs,
|
||||
const Table *old_elem_dof, const Table *old_elem_fos);
|
||||
};
|
||||
/// \endcond DO_NOT_DOCUMENT
|
||||
} // namespace mfem
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+49
-31
@@ -22,12 +22,13 @@
|
||||
#include "../mesh/mesh_headers.hpp"
|
||||
#include "../general/binaryio.hpp"
|
||||
|
||||
#include "pderefmat_op.hpp"
|
||||
|
||||
#include <limits>
|
||||
#include <list>
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
|
||||
ParFiniteElementSpace::ParFiniteElementSpace(
|
||||
const ParFiniteElementSpace &orig, ParMesh *pmesh,
|
||||
const FiniteElementCollection *fec)
|
||||
@@ -4487,13 +4488,6 @@ ParFiniteElementSpace::RebalanceMatrix(int old_ndofs,
|
||||
return M;
|
||||
}
|
||||
|
||||
|
||||
struct DerefDofMessage
|
||||
{
|
||||
std::vector<HYPRE_BigInt> dofs;
|
||||
MPI_Request request;
|
||||
};
|
||||
|
||||
HypreParMatrix*
|
||||
ParFiniteElementSpace::ParallelDerefinementMatrix(int old_ndofs,
|
||||
const Table* old_elem_dof,
|
||||
@@ -4536,7 +4530,13 @@ ParFiniteElementSpace::ParallelDerefinementMatrix(int old_ndofs,
|
||||
old_pncmesh->GetDerefinementTransforms();
|
||||
const Array<int> &old_ranks = old_pncmesh->GetDerefineOldRanks();
|
||||
|
||||
std::map<int, DerefDofMessage> messages;
|
||||
// key: other rank
|
||||
// value: send or recieve buffer
|
||||
std::map<int, std::vector<HYPRE_BigInt>> to_send;
|
||||
std::map<int, std::vector<HYPRE_BigInt>> to_recv;
|
||||
// key: index into dtrans.embeddings
|
||||
// value: [start, stop]
|
||||
std::unordered_map<int, std::array<size_t, 2>> recv_messages;
|
||||
|
||||
HYPRE_BigInt old_offset = HYPRE_AssumedPartitionCheck()
|
||||
? old_dof_offsets[0] : old_dof_offsets[MyRank];
|
||||
@@ -4556,30 +4556,46 @@ ParFiniteElementSpace::ParallelDerefinementMatrix(int old_ndofs,
|
||||
old_elem_dof->GetRow(k, dofs);
|
||||
DofsToVDofs(dofs, old_ndofs);
|
||||
|
||||
DerefDofMessage &msg = messages[k];
|
||||
msg.dofs.resize(dofs.Size());
|
||||
std::vector<HYPRE_BigInt>& send_buf = to_send[coarse_rank];
|
||||
auto pos = send_buf.size();
|
||||
send_buf.resize(pos + dofs.Size());
|
||||
for (int i = 0; i < dofs.Size(); i++)
|
||||
{
|
||||
msg.dofs[i] = old_offset + dofs[i];
|
||||
send_buf[pos + i] = old_offset + dofs[i];
|
||||
}
|
||||
|
||||
MPI_Isend(&msg.dofs[0], static_cast<int>(msg.dofs.size()), HYPRE_MPI_BIG_INT,
|
||||
coarse_rank, 291, MyComm, &msg.request);
|
||||
}
|
||||
else if (coarse_rank == MyRank && fine_rank != MyRank)
|
||||
{
|
||||
MFEM_ASSERT(emb.parent >= 0, "");
|
||||
Geometry::Type geom = mesh->GetElementBaseGeometry(emb.parent);
|
||||
|
||||
DerefDofMessage &msg = messages[k];
|
||||
msg.dofs.resize(ldof[geom]*vdim);
|
||||
|
||||
MPI_Irecv(&msg.dofs[0], ldof[geom]*vdim, HYPRE_MPI_BIG_INT,
|
||||
fine_rank, 291, MyComm, &msg.request);
|
||||
std::vector<HYPRE_BigInt>& recv_buf = to_recv[fine_rank];
|
||||
auto& msg = recv_messages[k];
|
||||
msg[0] = recv_buf.size();
|
||||
recv_buf.resize(recv_buf.size() + ldof[geom] * vdim);
|
||||
msg[1] = recv_buf.size();
|
||||
}
|
||||
// TODO: coalesce Isends/Irecvs to the same rank. Typically, on uniform
|
||||
// derefinement, there should be just one send to MyRank-1 and one recv
|
||||
// from MyRank+1
|
||||
}
|
||||
|
||||
// assume embedding orders are consistent (i.e. what we expect to receive
|
||||
// first from a given rank is sent first, etc.)
|
||||
std::vector<MPI_Request> requests;
|
||||
requests.reserve(to_send.size() + to_recv.size());
|
||||
// enqueue recvs
|
||||
for (auto &v : to_recv)
|
||||
{
|
||||
requests.emplace_back();
|
||||
MPI_Irecv(v.second.data(), v.second.size(), HYPRE_MPI_BIG_INT, v.first,
|
||||
MessageTag::DEREFINEMENT_MATRIX_CONSTRUCTION_DATA, MyComm,
|
||||
&requests.back());
|
||||
}
|
||||
// enqueue sends
|
||||
for (auto &v : to_send)
|
||||
{
|
||||
requests.emplace_back();
|
||||
MPI_Isend(v.second.data(), v.second.size(), HYPRE_MPI_BIG_INT, v.first,
|
||||
MessageTag::DEREFINEMENT_MATRIX_CONSTRUCTION_DATA, MyComm,
|
||||
&requests.back());
|
||||
}
|
||||
|
||||
DenseTensor localR[Geometry::NumGeom];
|
||||
@@ -4637,10 +4653,7 @@ ParFiniteElementSpace::ParallelDerefinementMatrix(int old_ndofs,
|
||||
diag->Finalize();
|
||||
|
||||
// wait for all sends/receives to complete
|
||||
for (auto it = messages.begin(); it != messages.end(); ++it)
|
||||
{
|
||||
MPI_Wait(&it->second.request, MPI_STATUS_IGNORE);
|
||||
}
|
||||
MPI_Waitall(requests.size(), requests.data(), MPI_STATUSES_IGNORE);
|
||||
|
||||
// create the off-diagonal part of the derefinement matrix
|
||||
SparseMatrix *offd = new SparseMatrix(ndofs*vdim, 1);
|
||||
@@ -4661,13 +4674,14 @@ ParFiniteElementSpace::ParallelDerefinementMatrix(int old_ndofs,
|
||||
|
||||
elem_dof->GetRow(emb.parent, dofs);
|
||||
|
||||
DerefDofMessage &msg = messages[k];
|
||||
MFEM_ASSERT(msg.dofs.size(), "");
|
||||
auto& odofs = to_recv.at(fine_rank);
|
||||
auto &msg = recv_messages[k];
|
||||
MFEM_ASSERT(msg[1] > msg[0], "");
|
||||
|
||||
for (int vd = 0; vd < vdim; vd++)
|
||||
{
|
||||
MFEM_ASSERT(ldof[geom], "");
|
||||
HYPRE_BigInt* remote_dofs = &msg.dofs[vd*ldof[geom]];
|
||||
HYPRE_BigInt *remote_dofs = odofs.data() + msg[0] + vd * ldof[geom];
|
||||
|
||||
for (int i = 0; i < lR.Height(); i++)
|
||||
{
|
||||
@@ -4694,7 +4708,6 @@ ParFiniteElementSpace::ParallelDerefinementMatrix(int old_ndofs,
|
||||
}
|
||||
}
|
||||
|
||||
messages.clear();
|
||||
offd->Finalize(0);
|
||||
offd->SetWidth(static_cast<int>(col_map.size()));
|
||||
|
||||
@@ -4946,8 +4959,13 @@ void ParFiniteElementSpace::Update(bool want_transform)
|
||||
|
||||
case Mesh::DEREFINE:
|
||||
{
|
||||
#if 0
|
||||
Th.Reset(ParallelDerefinementMatrix(old_ndofs, old_elem_dof,
|
||||
old_elem_fos));
|
||||
#else
|
||||
Th.Reset(new ParDerefineMatrixOp(*this, old_ndofs, old_elem_dof,
|
||||
old_elem_fos));
|
||||
#endif
|
||||
if (Nonconforming())
|
||||
{
|
||||
Th.SetOperatorOwner(false);
|
||||
|
||||
@@ -24,9 +24,12 @@
|
||||
namespace mfem
|
||||
{
|
||||
|
||||
struct ParDerefineMatrixOp;
|
||||
|
||||
/// Abstract parallel finite element space.
|
||||
class ParFiniteElementSpace : public FiniteElementSpace
|
||||
{
|
||||
friend struct ParDerefineMatrixOp;
|
||||
private:
|
||||
/// MPI data.
|
||||
MPI_Comm MyComm;
|
||||
|
||||
+10
-10
@@ -214,16 +214,16 @@ public:
|
||||
inline void QuadratureFunction::GetValues(
|
||||
int idx, Vector &values)
|
||||
{
|
||||
const int s_offset = qspace->offsets[idx];
|
||||
const int sl_size = qspace->offsets[idx+1] - s_offset;
|
||||
const int s_offset = qspace->Offset(idx);
|
||||
const int sl_size = qspace->Offset(idx + 1) - s_offset;
|
||||
values.MakeRef(*this, vdim*s_offset, vdim*sl_size);
|
||||
}
|
||||
|
||||
inline void QuadratureFunction::GetValues(
|
||||
int idx, Vector &values) const
|
||||
{
|
||||
const int s_offset = qspace->offsets[idx];
|
||||
const int sl_size = qspace->offsets[idx+1] - s_offset;
|
||||
const int s_offset = qspace->Offset(idx);
|
||||
const int sl_size = qspace->Offset(idx + 1) - s_offset;
|
||||
values.SetSize(vdim*sl_size);
|
||||
values.HostWrite();
|
||||
const real_t *q = HostRead() + vdim*s_offset;
|
||||
@@ -236,14 +236,14 @@ inline void QuadratureFunction::GetValues(
|
||||
inline void QuadratureFunction::GetValues(
|
||||
int idx, const int ip_num, Vector &values)
|
||||
{
|
||||
const int s_offset = qspace->offsets[idx] * vdim + ip_num * vdim;
|
||||
const int s_offset = qspace->Offset(idx) * vdim + ip_num * vdim;
|
||||
values.MakeRef(*this, s_offset, vdim);
|
||||
}
|
||||
|
||||
inline void QuadratureFunction::GetValues(
|
||||
int idx, const int ip_num, Vector &values) const
|
||||
{
|
||||
const int s_offset = qspace->offsets[idx] * vdim + ip_num * vdim;
|
||||
const int s_offset = qspace->Offset(idx) * vdim + ip_num * vdim;
|
||||
values.SetSize(vdim);
|
||||
values.HostWrite();
|
||||
const real_t *q = HostRead() + s_offset;
|
||||
@@ -256,8 +256,8 @@ inline void QuadratureFunction::GetValues(
|
||||
inline void QuadratureFunction::GetValues(
|
||||
int idx, DenseMatrix &values)
|
||||
{
|
||||
const int s_offset = qspace->offsets[idx];
|
||||
const int sl_size = qspace->offsets[idx+1] - s_offset;
|
||||
const int s_offset = qspace->Offset(idx);
|
||||
const int sl_size = qspace->Offset(idx + 1) - s_offset;
|
||||
// Make the values matrix memory an alias of the quadrature function memory
|
||||
Memory<real_t> &values_mem = values.GetMemory();
|
||||
values_mem.Delete();
|
||||
@@ -268,8 +268,8 @@ inline void QuadratureFunction::GetValues(
|
||||
inline void QuadratureFunction::GetValues(
|
||||
int idx, DenseMatrix &values) const
|
||||
{
|
||||
const int s_offset = qspace->offsets[idx];
|
||||
const int sl_size = qspace->offsets[idx+1] - s_offset;
|
||||
const int s_offset = qspace->Offset(idx);
|
||||
const int sl_size = qspace->Offset(idx + 1) - s_offset;
|
||||
values.SetSize(vdim, sl_size);
|
||||
values.HostWrite();
|
||||
const real_t *q = HostRead() + vdim*s_offset;
|
||||
|
||||
+24
-8
@@ -97,16 +97,31 @@ void QuadratureSpaceBase::Integrate(VectorCoefficient &coeff,
|
||||
void QuadratureSpace::ConstructOffsets()
|
||||
{
|
||||
const int num_elem = mesh.GetNE();
|
||||
offsets.SetSize(num_elem + 1);
|
||||
int offset = 0;
|
||||
for (int i = 0; i < num_elem; i++)
|
||||
ne = num_elem;
|
||||
|
||||
if (mesh.GetNumGeometries(mesh.Dimension()) == 1)
|
||||
{
|
||||
offsets[i] = offset;
|
||||
int geom = mesh.GetElementBaseGeometry(i);
|
||||
MFEM_ASSERT(int_rule[geom] != NULL, "Missing integration rule.");
|
||||
offset += int_rule[geom]->GetNPoints();
|
||||
Array<Geometry::Type> geoms;
|
||||
mesh.GetGeometries(mesh.Dimension(), geoms);
|
||||
offsets.SetSize(1);
|
||||
offsets.HostWrite();
|
||||
offsets[0] = int_rule[geoms[0]]->GetNPoints();
|
||||
size = num_elem * offsets[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
offsets.SetSize(num_elem + 1);
|
||||
int offset = 0;
|
||||
for (int i = 0; i < num_elem; i++)
|
||||
{
|
||||
offsets[i] = offset;
|
||||
const Geometry::Type geom = mesh.GetElementBaseGeometry(i);
|
||||
MFEM_ASSERT(int_rule[geom] != NULL, "Missing integration rule.");
|
||||
offset += int_rule[geom]->GetNPoints();
|
||||
}
|
||||
offsets[num_elem] = offset;
|
||||
size = offsets.Last();
|
||||
}
|
||||
offsets[num_elem] = size = offset;
|
||||
}
|
||||
|
||||
void QuadratureSpace::Construct()
|
||||
@@ -188,6 +203,7 @@ void FaceQuadratureSpace::ConstructOffsets()
|
||||
{
|
||||
face_indices.SetSize(num_faces);
|
||||
offsets.SetSize(num_faces + 1);
|
||||
ne = num_faces;
|
||||
int offset = 0;
|
||||
int f_idx = 0;
|
||||
for (int i = 0; i < mesh.GetNumFacesWithGhost(); i++)
|
||||
|
||||
+26
-4
@@ -30,13 +30,17 @@ protected:
|
||||
Mesh &mesh; ///< The underlying mesh.
|
||||
int order; ///< The order of integration rule.
|
||||
int size; ///< Total number of quadrature points.
|
||||
int ne; ///< Actual number of entities
|
||||
mutable Vector weights; ///< Integration weights.
|
||||
mutable long nodes_sequence = 0; ///< Nodes counter for cache invalidation.
|
||||
|
||||
/// @brief Entity quadrature point offset array, of size num_entities + 1.
|
||||
/// @brief Entity quadrature point offset array.
|
||||
///
|
||||
/// Supports a constant compression scheme for meshes which have a single
|
||||
/// geometry type. When compressed, will have a single value. The true offset
|
||||
/// can be computed as i * offsets[0], where i is the entity index. Otherwise
|
||||
/// has size num_entities + 1.
|
||||
///
|
||||
/// The quadrature point values for entity i are stored in the indices between
|
||||
/// offsets[i] and offsets[i+1].
|
||||
Array<int> offsets;
|
||||
/// The quadrature rules used for each geometry type.
|
||||
const IntegrationRule *int_rule[Geometry::NumGeom];
|
||||
@@ -59,6 +63,24 @@ protected:
|
||||
void ConstructWeights() const;
|
||||
|
||||
public:
|
||||
/// @brief Gets the offset for a given entity @a idx.
|
||||
///
|
||||
/// The quadrature point values for entity i are stored in the indices
|
||||
/// between Offset(i) and Offset(i+1)
|
||||
int Offset(int idx) const
|
||||
{
|
||||
return (offsets.Size() == 1) ? (idx * offsets[0]) : offsets[idx];
|
||||
}
|
||||
|
||||
/// @brief Entity quadrature point offset array.
|
||||
///
|
||||
/// Supports a constant compression scheme for meshes which have a single
|
||||
/// geometry type. When compressed, will have a single value. The true offset
|
||||
/// can be computed as i * offsets[0], where i is the entity index. Otherwise
|
||||
/// has size num_entities + 1.
|
||||
///
|
||||
const Array<int> &Offsets() const { return offsets; }
|
||||
|
||||
/// Return the total number of quadrature points.
|
||||
int GetSize() const { return size; }
|
||||
|
||||
@@ -66,7 +88,7 @@ public:
|
||||
int GetOrder() const { return order; }
|
||||
|
||||
/// Return the number of entities.
|
||||
int GetNE() const { return offsets.Size() - 1; }
|
||||
int GetNE() const { return ne; }
|
||||
|
||||
/// Returns the mesh.
|
||||
inline Mesh *GetMesh() const { return &mesh; }
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
#include "../config/config.hpp"
|
||||
|
||||
#ifdef MFEM_USE_CUDA
|
||||
#if defined(MFEM_USE_CUDA) && defined(__CUDACC__)
|
||||
#include <cusparse.h>
|
||||
#include <library_types.h>
|
||||
#include <cuda_runtime.h>
|
||||
@@ -22,7 +22,7 @@
|
||||
#endif
|
||||
#include "cuda.hpp"
|
||||
|
||||
#ifdef MFEM_USE_HIP
|
||||
#if defined(MFEM_USE_HIP) && defined(__HIP__)
|
||||
#include <hip/hip_runtime.h>
|
||||
#endif
|
||||
#include "hip.hpp"
|
||||
@@ -43,7 +43,7 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if !(defined(MFEM_USE_CUDA) || defined(MFEM_USE_HIP))
|
||||
#if !defined(MFEM_USE_CUDA_OR_HIP)
|
||||
#define MFEM_DEVICE
|
||||
#define MFEM_HOST
|
||||
#define MFEM_LAMBDA
|
||||
@@ -55,7 +55,7 @@
|
||||
#endif
|
||||
|
||||
#if !((defined(MFEM_USE_CUDA) && defined(__CUDA_ARCH__)) || \
|
||||
(defined(MFEM_USE_HIP) && defined(__HIP_DEVICE_COMPILE__)))
|
||||
(defined(MFEM_USE_HIP) && defined(__HIP_DEVICE_COMPILE__)))
|
||||
#define MFEM_SHARED
|
||||
#define MFEM_SYNC_THREAD
|
||||
#define MFEM_BLOCK_ID(k) 0
|
||||
@@ -66,7 +66,7 @@
|
||||
#endif
|
||||
|
||||
// 'double' and 'float' atomicAdd implementation for previous versions of CUDA
|
||||
#if defined(MFEM_USE_CUDA) && defined(__CUDA_ARCH__) && __CUDA_ARCH__ < 600
|
||||
#if defined(MFEM_USE_CUDA) && defined(__CUDA_ARCH__) && (__CUDA_ARCH__ < 600)
|
||||
MFEM_DEVICE inline mfem::real_t atomicAdd(mfem::real_t *add, mfem::real_t val)
|
||||
{
|
||||
unsigned long long int *ptr = (unsigned long long int *) add;
|
||||
@@ -94,7 +94,7 @@ template <typename T>
|
||||
MFEM_HOST_DEVICE T AtomicAdd(T &add, const T val)
|
||||
{
|
||||
#if ((defined(MFEM_USE_CUDA) && defined(__CUDA_ARCH__)) || \
|
||||
(defined(MFEM_USE_HIP) && defined(__HIP_DEVICE_COMPILE__)))
|
||||
(defined(MFEM_USE_HIP) && defined(__HIP_DEVICE_COMPILE__)))
|
||||
return atomicAdd(&add,val);
|
||||
#else
|
||||
T old = add;
|
||||
|
||||
@@ -347,13 +347,23 @@ void GroupTopology::Swap(GroupTopology &other)
|
||||
mfem::Swap(group_mgroup, other.group_mgroup);
|
||||
}
|
||||
|
||||
/// \cond DO_NOT_DOCUMENT
|
||||
// Initialize the static mpi_type for the specializations of MPITypeMap:
|
||||
const MPI_Datatype MPITypeMap<bool>::mpi_type = MFEM_MPI_CXX_BOOL;
|
||||
const MPI_Datatype MPITypeMap<char>::mpi_type = MPI_CHAR;
|
||||
const MPI_Datatype MPITypeMap<unsigned char>::mpi_type = MPI_UNSIGNED_CHAR;
|
||||
const MPI_Datatype MPITypeMap<short>::mpi_type = MPI_SHORT;
|
||||
const MPI_Datatype MPITypeMap<unsigned short>::mpi_type = MPI_UNSIGNED_SHORT;
|
||||
const MPI_Datatype MPITypeMap<int>::mpi_type = MPI_INT;
|
||||
const MPI_Datatype MPITypeMap<unsigned int>::mpi_type = MPI_UNSIGNED;
|
||||
const MPI_Datatype MPITypeMap<long>::mpi_type = MPI_LONG;
|
||||
const MPI_Datatype MPITypeMap<unsigned long>::mpi_type = MPI_UNSIGNED_LONG;
|
||||
const MPI_Datatype MPITypeMap<long long>::mpi_type = MPI_LONG_LONG;
|
||||
const MPI_Datatype MPITypeMap<unsigned long long>::mpi_type =
|
||||
MPI_UNSIGNED_LONG_LONG;
|
||||
const MPI_Datatype MPITypeMap<float>::mpi_type = MPI_FLOAT;
|
||||
const MPI_Datatype MPITypeMap<double>::mpi_type = MPI_DOUBLE;
|
||||
const MPI_Datatype MPITypeMap<int64_t>::mpi_type = MPI_INT64_T;
|
||||
const MPI_Datatype MPITypeMap<uint64_t>::mpi_type = MPI_UINT64_T;
|
||||
|
||||
/// \endcond DO_NOT_DOCUMENT
|
||||
|
||||
GroupCommunicator::GroupCommunicator(const GroupTopology >, Mode m)
|
||||
: gtopo(gt), mode(m)
|
||||
|
||||
+56
-17
@@ -425,16 +425,24 @@ public:
|
||||
~GroupCommunicator();
|
||||
};
|
||||
|
||||
/// General MPI message tags used by MFEM
|
||||
enum MessageTag
|
||||
{
|
||||
DEREFINEMENT_MATRIX_CONSTRUCTION_DATA =
|
||||
291, /// ParFiniteElementSpace ParallelDerefinementMatrix and
|
||||
/// ParDerefineMatrixOp
|
||||
};
|
||||
|
||||
enum VarMessageTag
|
||||
{
|
||||
NEIGHBOR_ELEMENT_RANK_VM, ///< NeighborElementRankMessage
|
||||
NEIGHBOR_ORDER_VM, ///< NeighborOrderMessage
|
||||
NEIGHBOR_DEREFINEMENT_VM, ///< NeighborDerefinementMessage
|
||||
NEIGHBOR_REFINEMENT_VM, ///< NeighborRefinementMessage
|
||||
NEIGHBOR_PREFINEMENT_VM, ///< NeighborPRefinementMessage
|
||||
NEIGHBOR_ROW_VM, ///< NeighborRowMessage
|
||||
REBALANCE_VM, ///< RebalanceMessage
|
||||
REBALANCE_DOF_VM ///< RebalanceDofMessage
|
||||
NEIGHBOR_ELEMENT_RANK_VM, ///< NeighborElementRankMessage
|
||||
NEIGHBOR_ORDER_VM, ///< NeighborOrderMessage
|
||||
NEIGHBOR_DEREFINEMENT_VM, ///< NeighborDerefinementMessage
|
||||
NEIGHBOR_REFINEMENT_VM, ///< NeighborRefinementMessage
|
||||
NEIGHBOR_PREFINEMENT_VM, ///< NeighborPRefinementMessage
|
||||
NEIGHBOR_ROW_VM, ///< NeighborRowMessage
|
||||
REBALANCE_VM, ///< RebalanceMessage
|
||||
REBALANCE_DOF_VM, ///< RebalanceDofMessage
|
||||
};
|
||||
|
||||
/// \brief Variable-length MPI message containing unspecific binary data.
|
||||
@@ -599,10 +607,50 @@ protected:
|
||||
template <typename Type> struct MPITypeMap;
|
||||
|
||||
// Specializations of MPITypeMap; mpi_type initialized in communication.cpp:
|
||||
template<> struct MPITypeMap<bool>
|
||||
{
|
||||
static MFEM_EXPORT const MPI_Datatype mpi_type;
|
||||
};
|
||||
template<> struct MPITypeMap<char>
|
||||
{
|
||||
static MFEM_EXPORT const MPI_Datatype mpi_type;
|
||||
};
|
||||
template<> struct MPITypeMap<unsigned char>
|
||||
{
|
||||
static MFEM_EXPORT const MPI_Datatype mpi_type;
|
||||
};
|
||||
template<> struct MPITypeMap<short>
|
||||
{
|
||||
static MFEM_EXPORT const MPI_Datatype mpi_type;
|
||||
};
|
||||
template<> struct MPITypeMap<unsigned short>
|
||||
{
|
||||
static MFEM_EXPORT const MPI_Datatype mpi_type;
|
||||
};
|
||||
template<> struct MPITypeMap<int>
|
||||
{
|
||||
static MFEM_EXPORT const MPI_Datatype mpi_type;
|
||||
};
|
||||
template<> struct MPITypeMap<unsigned int>
|
||||
{
|
||||
static MFEM_EXPORT const MPI_Datatype mpi_type;
|
||||
};
|
||||
template<> struct MPITypeMap<long>
|
||||
{
|
||||
static MFEM_EXPORT const MPI_Datatype mpi_type;
|
||||
};
|
||||
template<> struct MPITypeMap<unsigned long>
|
||||
{
|
||||
static MFEM_EXPORT const MPI_Datatype mpi_type;
|
||||
};
|
||||
template<> struct MPITypeMap<long long>
|
||||
{
|
||||
static MFEM_EXPORT const MPI_Datatype mpi_type;
|
||||
};
|
||||
template<> struct MPITypeMap<unsigned long long>
|
||||
{
|
||||
static MFEM_EXPORT const MPI_Datatype mpi_type;
|
||||
};
|
||||
template<> struct MPITypeMap<double>
|
||||
{
|
||||
static MFEM_EXPORT const MPI_Datatype mpi_type;
|
||||
@@ -611,15 +659,6 @@ template<> struct MPITypeMap<float>
|
||||
{
|
||||
static MFEM_EXPORT const MPI_Datatype mpi_type;
|
||||
};
|
||||
template<> struct MPITypeMap<int64_t>
|
||||
{
|
||||
static MFEM_EXPORT const MPI_Datatype mpi_type;
|
||||
};
|
||||
template<> struct MPITypeMap<uint64_t>
|
||||
{
|
||||
static MFEM_EXPORT const MPI_Datatype mpi_type;
|
||||
};
|
||||
|
||||
|
||||
/** Reorder MPI ranks to follow the Z-curve within the physical machine topology
|
||||
(provided that functions to query physical node coordinates are available).
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ void mfem_cuda_error(cudaError_t err, const char *expr, const char *func,
|
||||
const char *file, int line)
|
||||
{
|
||||
mfem::err << "\n\nCUDA error: (" << expr << ") failed with error:\n --> "
|
||||
<< cudaGetErrorString(err)
|
||||
<< cudaGetErrorString(err) << " [code: " << (int)err << ']'
|
||||
<< "\n ... in function: " << func
|
||||
<< "\n ... in file: " << file << ':' << line << '\n';
|
||||
mfem_error();
|
||||
|
||||
+5
-5
@@ -18,7 +18,7 @@
|
||||
// CUDA block size used by MFEM.
|
||||
#define MFEM_CUDA_BLOCKS 256
|
||||
|
||||
#ifdef MFEM_USE_CUDA
|
||||
#if defined(MFEM_USE_CUDA) && defined(__CUDACC__)
|
||||
#define MFEM_USE_CUDA_OR_HIP
|
||||
#define MFEM_DEVICE __device__
|
||||
#define MFEM_HOST __host__
|
||||
@@ -37,10 +37,9 @@
|
||||
__FILE__, __LINE__); \
|
||||
} \
|
||||
} while (0)
|
||||
#endif // MFEM_USE_CUDA
|
||||
|
||||
// Define the MFEM inner threading macros
|
||||
#if defined(MFEM_USE_CUDA) && defined(__CUDA_ARCH__)
|
||||
#if defined(__CUDA_ARCH__)
|
||||
#define MFEM_SHARED __shared__
|
||||
#define MFEM_SYNC_THREAD __syncthreads()
|
||||
#define MFEM_BLOCK_ID(k) blockIdx.k
|
||||
@@ -48,12 +47,13 @@
|
||||
#define MFEM_THREAD_SIZE(k) blockDim.k
|
||||
#define MFEM_FOREACH_THREAD(i,k,N) for(int i=threadIdx.k; i<N; i+=blockDim.k)
|
||||
#define MFEM_FOREACH_THREAD_DIRECT(i,k,N) if(const int i=threadIdx.k; i<N)
|
||||
#endif
|
||||
#endif // defined(__CUDA_ARCH__)
|
||||
#endif // defined(MFEM_USE_CUDA) && defined(__CUDACC__)
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
|
||||
#ifdef MFEM_USE_CUDA
|
||||
#if defined(MFEM_USE_CUDA) && defined(__CUDACC__)
|
||||
// Function used by the macro MFEM_GPU_CHECK.
|
||||
void mfem_cuda_error(cudaError_t err, const char *expr, const char *func,
|
||||
const char *file, int line);
|
||||
|
||||
+1
-1
@@ -615,7 +615,7 @@ void Device::Setup(const std::string &device_option, const int device_id)
|
||||
if (Allows(Backend::DEBUG_DEVICE)) { ngpu = 1; }
|
||||
}
|
||||
|
||||
MemoryType Device::QueryMemoryType(void *ptr)
|
||||
MemoryType Device::QueryMemoryType(const void* ptr)
|
||||
{
|
||||
// from HYPRE's hypre_GetPointerLocation
|
||||
MemoryType res = MemoryType::HOST;
|
||||
|
||||
+3
-3
@@ -297,9 +297,9 @@ public:
|
||||
/// Get the status of GPU-aware MPI flag.
|
||||
static bool GetGPUAwareMPI() { return Get().mpi_gpu_aware; }
|
||||
|
||||
/** @brief Query the device driver for what memory type a given @a ptr is
|
||||
allocated with. */
|
||||
static MemoryType QueryMemoryType(void *ptr);
|
||||
/** Query the device driver for what memory type a given @a ptr is allocated
|
||||
* with. */
|
||||
static MemoryType QueryMemoryType(const void* ptr);
|
||||
|
||||
/** @brief The number of hardware compute units/streaming multiprocessors
|
||||
available on a given compute device @a device_id. */
|
||||
|
||||
+1
-1
@@ -176,7 +176,7 @@ __device__ void abort_msg(T & msg)
|
||||
printf(__VA_ARGS__); \
|
||||
asm("trap;"); \
|
||||
}
|
||||
#elif defined(MFEM_USE_HIP)
|
||||
#elif defined(__HIP_DEVICE_COMPILE__)
|
||||
#define MFEM_ABORT_KERNEL(...) \
|
||||
{ \
|
||||
printf(__VA_ARGS__); \
|
||||
|
||||
+171
-12
@@ -158,8 +158,8 @@ private:
|
||||
#define MFEM_PRAGMA(X) _Pragma(#X)
|
||||
|
||||
// MFEM_UNROLL pragma macro that can be used inside MFEM_FORALL macros.
|
||||
#if defined(MFEM_USE_CUDA) && defined(__CUDA_ARCH__)
|
||||
#ifdef __NVCC__
|
||||
#if defined(MFEM_USE_CUDA) && defined(__CUDA_ARCH__) // Clang cuda or nvcc
|
||||
#ifdef __NVCC__ // nvcc specifically
|
||||
#define MFEM_UNROLL(N) MFEM_PRAGMA(unroll(N))
|
||||
#else // Assuming Clang CUDA
|
||||
#define MFEM_UNROLL(N) MFEM_PRAGMA(unroll N)
|
||||
@@ -169,12 +169,12 @@ private:
|
||||
#endif
|
||||
|
||||
// MFEM_GPU_FORALL: "parallel for" executed with CUDA or HIP based on the MFEM
|
||||
// build-time configuration (MFEM_USE_CUDA or MFEM_USE_HIP). If neither CUDA nor
|
||||
// HIP is enabled, this macro is a no-op.
|
||||
#if defined(MFEM_USE_CUDA)
|
||||
// build-time configuration (MFEM_USE_CUDA or MFEM_USE_HIP), and if compiling
|
||||
// with CUDA/HIP language. Otherwise, this macro is a no-op.
|
||||
#if defined(MFEM_USE_CUDA) && defined(__CUDACC__)
|
||||
#define MFEM_GPU_FORALL(i, N,...) CuWrap1D(N, [=] MFEM_DEVICE \
|
||||
(int i) {__VA_ARGS__})
|
||||
#elif defined(MFEM_USE_HIP)
|
||||
#elif defined(MFEM_USE_HIP) && defined(__HIP__)
|
||||
#define MFEM_GPU_FORALL(i, N,...) HipWrap1D(N, [=] MFEM_DEVICE \
|
||||
(int i) {__VA_ARGS__})
|
||||
#else
|
||||
@@ -225,6 +225,52 @@ void OmpWrap(const int N, HBODY &&h_body)
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename HBODY>
|
||||
void OmpWrap2D(const int Nx, const int Ny, HBODY &&h_body)
|
||||
{
|
||||
#ifdef MFEM_USE_OPENMP
|
||||
// requires OpenMP 3.1
|
||||
#pragma omp parallel for collapse(2)
|
||||
for (int j = 0; j < Ny; j++)
|
||||
{
|
||||
for (int i = 0; i < Nx; i++)
|
||||
{
|
||||
h_body(i, j);
|
||||
}
|
||||
}
|
||||
#else
|
||||
MFEM_CONTRACT_VAR(Nx);
|
||||
MFEM_CONTRACT_VAR(Ny);
|
||||
MFEM_CONTRACT_VAR(h_body);
|
||||
MFEM_ABORT("OpenMP requested for MFEM but OpenMP is not enabled!");
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename HBODY>
|
||||
void OmpWrap3D(const int Nx, const int Ny, const int Nz, HBODY &&h_body)
|
||||
{
|
||||
#ifdef MFEM_USE_OPENMP
|
||||
// requires OpenMP 3.1
|
||||
#pragma omp parallel for collapse(3)
|
||||
for (int k = 0; k < Nz; k++)
|
||||
{
|
||||
for (int j = 0; j < Ny; j++)
|
||||
{
|
||||
for (int i = 0; i < Nx; i++)
|
||||
{
|
||||
h_body(i, j, k);
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
MFEM_CONTRACT_VAR(Nx);
|
||||
MFEM_CONTRACT_VAR(Ny);
|
||||
MFEM_CONTRACT_VAR(Nz);
|
||||
MFEM_CONTRACT_VAR(h_body);
|
||||
MFEM_ABORT("OpenMP requested for MFEM but OpenMP is not enabled!");
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/// RAJA Cuda and Hip backends
|
||||
#if defined(MFEM_USE_RAJA) && defined(RAJA_ENABLE_CUDA)
|
||||
@@ -454,6 +500,42 @@ void RajaOmpWrap(const int N, HBODY &&h_body)
|
||||
RAJA::forall<RAJA::omp_parallel_for_exec>(RAJA::RangeSegment(0,N), h_body);
|
||||
}
|
||||
|
||||
template <typename HBODY>
|
||||
void RajaOmpWrap2D(const int Nx, const int Ny, HBODY &&h_body)
|
||||
{
|
||||
using omp_launch_policy = RAJA::LaunchPolicy<RAJA::omp_launch_t>;
|
||||
using global_thread_xy = RAJA::LoopPolicy<RAJA::omp_for_exec>;
|
||||
RAJA::RangeSegment xrange(0, Nx);
|
||||
RAJA::RangeSegment yrange(0, Ny);
|
||||
RAJA::launch<omp_launch_policy>(RAJA::ExecPlace::HOST, RAJA::LaunchParams(),
|
||||
[=](RAJA::LaunchContext ctx)
|
||||
{
|
||||
// contiguous in x
|
||||
RAJA::expt::loop<global_thread_xy>(ctx, xrange, yrange, [&](int i, int j)
|
||||
{
|
||||
h_body(i, j);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
template <typename HBODY>
|
||||
void RajaOmpWrap3D(const int Nx, const int Ny, const int Nz, HBODY &&h_body)
|
||||
{
|
||||
using omp_launch_policy = RAJA::LaunchPolicy<RAJA::omp_launch_t>;
|
||||
using global_thread_xyz = RAJA::LoopPolicy<RAJA::omp_for_exec>;
|
||||
RAJA::RangeSegment xrange(0, Nx);
|
||||
RAJA::RangeSegment yrange(0, Ny);
|
||||
RAJA::RangeSegment zrange(0, Nz);
|
||||
RAJA::launch<omp_launch_policy>(RAJA::ExecPlace::HOST, RAJA::LaunchParams(),
|
||||
[=](RAJA::LaunchContext ctx)
|
||||
{
|
||||
// contiguous in x
|
||||
RAJA::expt::loop<global_thread_xyz>(ctx, xrange, yrange, zrange,
|
||||
[&](int i, int j, int k)
|
||||
{ h_body(i, j, k); });
|
||||
});
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -481,7 +563,7 @@ void RajaSeqWrap(const int N, HBODY &&h_body)
|
||||
|
||||
|
||||
/// CUDA backend
|
||||
#ifdef MFEM_USE_CUDA
|
||||
#if defined(MFEM_USE_CUDA) && defined(__CUDACC__)
|
||||
|
||||
template <typename BODY> __global__ static
|
||||
void CuKernel1D(const int N, BODY body)
|
||||
@@ -573,11 +655,11 @@ struct CuWrap<3>
|
||||
}
|
||||
};
|
||||
|
||||
#endif // MFEM_USE_CUDA
|
||||
#endif // defined(MFEM_USE_CUDA) && defined(__CUDACC__)
|
||||
|
||||
|
||||
/// HIP backend
|
||||
#ifdef MFEM_USE_HIP
|
||||
#if defined(MFEM_USE_HIP) && defined(__HIP__)
|
||||
|
||||
template <typename BODY> __global__ static
|
||||
void HipKernel1D(const int N, BODY body)
|
||||
@@ -668,7 +750,7 @@ struct HipWrap<3>
|
||||
}
|
||||
};
|
||||
|
||||
#endif // MFEM_USE_HIP
|
||||
#endif // defined(MFEM_USE_HIP) && defined(__HIP__)
|
||||
|
||||
|
||||
/// The forall kernel body wrapper
|
||||
@@ -701,7 +783,7 @@ inline void ForallWrap(const bool use_dev, const int N,
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef MFEM_USE_CUDA
|
||||
#if defined(MFEM_USE_CUDA) && defined(__CUDACC__)
|
||||
// If Backend::CUDA is allowed, use it
|
||||
if (Device::Allows(Backend::CUDA))
|
||||
{
|
||||
@@ -709,7 +791,7 @@ inline void ForallWrap(const bool use_dev, const int N,
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef MFEM_USE_HIP
|
||||
#if defined(MFEM_USE_HIP) && defined(__HIP__)
|
||||
// If Backend::HIP is allowed, use it
|
||||
if (Device::Allows(Backend::HIP))
|
||||
{
|
||||
@@ -753,6 +835,83 @@ inline void ForallWrap(const bool use_dev, const int N, lambda &&body,
|
||||
template<typename lambda>
|
||||
inline void forall(int N, lambda &&body) { ForallWrap<1>(true, N, body); }
|
||||
|
||||
template<typename lambda>
|
||||
inline void forall(int Nx, int Ny, lambda &&body)
|
||||
{
|
||||
if (Device::Allows(Backend::DEVICE_MASK))
|
||||
{
|
||||
forall(Nx * Ny, [=] MFEM_HOST_DEVICE(int idx)
|
||||
{
|
||||
int j = idx / Nx;
|
||||
int i = idx % Nx;
|
||||
body(i, j);
|
||||
});
|
||||
}
|
||||
#if defined(MFEM_USE_RAJA) && defined(RAJA_ENABLE_OPENMP)
|
||||
else if (Device::Allows(Backend::RAJA_OMP))
|
||||
{
|
||||
return RajaOmpWrap2D(Nx, Ny, body);
|
||||
}
|
||||
#endif
|
||||
#ifdef MFEM_USE_OPENMP
|
||||
else if (Device::Allows(Backend::OMP))
|
||||
{
|
||||
return OmpWrap2D(Nx, Ny, body);
|
||||
}
|
||||
#endif
|
||||
else
|
||||
{
|
||||
for (int j = 0; j < Ny; ++j)
|
||||
{
|
||||
for (int i = 0; i < Nx; ++i)
|
||||
{
|
||||
body(i, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename lambda>
|
||||
inline void forall(int Nx, int Ny, int Nz, lambda &&body)
|
||||
{
|
||||
if (Device::Allows(Backend::DEVICE_MASK))
|
||||
{
|
||||
forall(Nx * Ny * Nz, [=] MFEM_HOST_DEVICE(int idx)
|
||||
{
|
||||
int i = idx % Nx;
|
||||
int j = idx / Nx;
|
||||
int k = j / Ny;
|
||||
j = j % Ny;
|
||||
body(i, j, k);
|
||||
});
|
||||
}
|
||||
#if defined(MFEM_USE_RAJA) && defined(RAJA_ENABLE_OPENMP)
|
||||
else if (Device::Allows(Backend::RAJA_OMP))
|
||||
{
|
||||
return RajaOmpWrap3D(Nx, Ny, Nz, body);
|
||||
}
|
||||
#endif
|
||||
#ifdef MFEM_USE_OPENMP
|
||||
else if (Device::Allows(Backend::OMP))
|
||||
{
|
||||
return OmpWrap3D(Nx, Ny, Nz, body);
|
||||
}
|
||||
#endif
|
||||
else
|
||||
{
|
||||
for (int k = 0; k < Nz; ++k)
|
||||
{
|
||||
for (int j = 0; j < Ny; ++j)
|
||||
{
|
||||
for (int i = 0; i < Nx; ++i)
|
||||
{
|
||||
body(i, j, k);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename lambda>
|
||||
inline void forall_switch(bool use_dev, int N, lambda &&body)
|
||||
{
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ void mfem_hip_error(hipError_t err, const char *expr, const char *func,
|
||||
const char *file, int line)
|
||||
{
|
||||
mfem::err << "\n\nHIP error: (" << expr << ") failed with error:\n --> "
|
||||
<< hipGetErrorString(err)
|
||||
<< hipGetErrorString(err) << " [code: " << (int)err << ']'
|
||||
<< "\n ... in function: " << func
|
||||
<< "\n ... in file: " << file << ':' << line << '\n';
|
||||
mfem_error();
|
||||
|
||||
+4
-4
@@ -18,7 +18,7 @@
|
||||
// HIP block size used by MFEM.
|
||||
#define MFEM_HIP_BLOCKS 256
|
||||
|
||||
#ifdef MFEM_USE_HIP
|
||||
#if defined(MFEM_USE_HIP) && defined(__HIP__)
|
||||
#define MFEM_USE_CUDA_OR_HIP
|
||||
#define MFEM_DEVICE __device__
|
||||
#define MFEM_HOST __host__
|
||||
@@ -37,10 +37,9 @@
|
||||
__FILE__, __LINE__); \
|
||||
} \
|
||||
} while (0)
|
||||
#endif // MFEM_USE_HIP
|
||||
|
||||
// Define the MFEM inner threading macros
|
||||
#if defined(MFEM_USE_HIP) && defined(__HIP_DEVICE_COMPILE__)
|
||||
#if defined(__HIP_DEVICE_COMPILE__)
|
||||
#define MFEM_SHARED __shared__
|
||||
#define MFEM_SYNC_THREAD __syncthreads()
|
||||
#define MFEM_BLOCK_ID(k) hipBlockIdx_ ##k
|
||||
@@ -50,7 +49,8 @@
|
||||
for(int i=hipThreadIdx_ ##k; i<N; i+=hipBlockDim_ ##k)
|
||||
#define MFEM_FOREACH_THREAD_DIRECT(i,k,N) \
|
||||
if(const int i=hipThreadIdx_ ##k; i<N)
|
||||
#endif
|
||||
#endif // defined(__HIP_DEVICE_COMPILE__)
|
||||
#endif // defined(MFEM_USE_HIP) && defined(__HIP__)
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
|
||||
@@ -513,10 +513,7 @@ public:
|
||||
void *HtoD(void *dst, const void *src, size_t bytes) override
|
||||
{ return HipMemcpyHtoD(dst, src, bytes); }
|
||||
void *DtoD(void* dst, const void* src, size_t bytes) override
|
||||
// Unlike cudaMemcpy(DtoD), hipMemcpy(DtoD) causes a host-side synchronization so
|
||||
// instead we use hipMemcpyAsync to get similar behavior.
|
||||
// for more info see: https://github.com/mfem/mfem/pull/2780
|
||||
{ return HipMemcpyDtoDAsync(dst, src, bytes); }
|
||||
{ return HipMemcpyDtoD(dst, src, bytes); }
|
||||
void *DtoH(void *dst, const void *src, size_t bytes) override
|
||||
{ return HipMemcpyDtoH(dst, src, bytes); }
|
||||
};
|
||||
@@ -658,10 +655,7 @@ public:
|
||||
return CuMemcpyDtoD(dst, src, bytes);
|
||||
#endif
|
||||
#ifdef MFEM_USE_HIP
|
||||
// Unlike cudaMemcpy(DtoD), hipMemcpy(DtoD) causes a host-side synchronization so
|
||||
// instead we use hipMemcpyAsync to get similar behavior.
|
||||
// for more info see: https://github.com/mfem/mfem/pull/2780
|
||||
return HipMemcpyDtoDAsync(dst, src, bytes);
|
||||
return HipMemcpyDtoD(dst, src, bytes);
|
||||
#endif
|
||||
// rm.copy(dst, const_cast<void*>(src), bytes); return dst;
|
||||
}
|
||||
|
||||
@@ -896,6 +896,7 @@ inline HYPRE_MemoryLocation GetHypreMemoryLocation()
|
||||
#elif MFEM_HYPRE_VERSION < 23100
|
||||
return HYPRE_MEMORY_DEVICE;
|
||||
#else // HYPRE_USING_GPU is defined and MFEM_HYPRE_VERSION >= 23100
|
||||
if (!HYPRE_Initialized()) { return HYPRE_MEMORY_HOST; }
|
||||
HYPRE_MemoryLocation loc;
|
||||
HYPRE_GetMemoryLocation(&loc);
|
||||
return loc;
|
||||
@@ -1057,7 +1058,8 @@ inline void Memory<T>::MakeAlias(const Memory &base, int offset, int size)
|
||||
// register the 'base' if the MemoryManager::Exists():
|
||||
MemoryManager::Exists()
|
||||
#else // HYPRE_USING_GPU is defined and MFEM_HYPRE_VERSION >= 23100
|
||||
MemoryManager::Exists() && HypreUsingGPU()
|
||||
IsDeviceMemory(MemoryManager::GetDeviceMemoryType()) ||
|
||||
(MemoryManager::Exists() && HypreUsingGPU())
|
||||
#endif
|
||||
)
|
||||
{
|
||||
|
||||
@@ -537,7 +537,7 @@ void reduce(int N, T &res, B &&body, const R &reducer, bool use_dev,
|
||||
return;
|
||||
}
|
||||
|
||||
#if defined(MFEM_USE_HIP) || defined(MFEM_USE_CUDA)
|
||||
#if defined(MFEM_USE_CUDA_OR_HIP)
|
||||
if (use_dev &&
|
||||
mfem::Device::Allows(Backend::CUDA | Backend::HIP | Backend::RAJA_CUDA |
|
||||
Backend::RAJA_HIP))
|
||||
|
||||
+99
-2
@@ -403,8 +403,105 @@ template <typename value_type, typename gradient_type> MFEM_HOST_DEVICE
|
||||
dual<value_type, gradient_type> pow(dual<value_type, gradient_type> a, real_t b)
|
||||
{
|
||||
using std::pow;
|
||||
value_type value = pow(a.value, b);
|
||||
return {value, value * a.gradient * b / a.value};
|
||||
return {pow(a.value, b), b*pow(a.value, b-1) * a.gradient };
|
||||
}
|
||||
|
||||
/** @brief implementation of max of two dual numbers */
|
||||
template <typename value_type, typename gradient_type> MFEM_HOST_DEVICE
|
||||
dual<value_type, gradient_type> max(dual<value_type, gradient_type> a,
|
||||
dual<value_type, gradient_type> b)
|
||||
{
|
||||
using std::max;
|
||||
if (a.value > b.value)
|
||||
{
|
||||
return a;
|
||||
}
|
||||
else if (a.value < b.value)
|
||||
{
|
||||
return b;
|
||||
}
|
||||
else { return (a+b)*0.5; } // subgradient at the kink
|
||||
}
|
||||
|
||||
/** @brief implementation of max of a dual number and a non-dual number */
|
||||
template <typename value_type, typename gradient_type> MFEM_HOST_DEVICE
|
||||
dual<value_type, gradient_type> max(real_t a, dual<value_type, gradient_type> b)
|
||||
{
|
||||
using std::max;
|
||||
if (a > b.value)
|
||||
{
|
||||
return {a, {}};
|
||||
}
|
||||
else if (a < b.value)
|
||||
{
|
||||
return b;
|
||||
}
|
||||
else { return {a, b.gradient*0.5}; } // subgradient at the kink
|
||||
}
|
||||
|
||||
/** @brief implementation of max of two non-dual numbers */
|
||||
template <typename value_type > MFEM_HOST_DEVICE
|
||||
value_type max(value_type a, value_type b)
|
||||
{
|
||||
using std::pow;
|
||||
return max(a, b);
|
||||
}
|
||||
|
||||
/** @brief implementation of max of a dual number and a non-dual number */
|
||||
template <typename value_type, typename gradient_type> MFEM_HOST_DEVICE
|
||||
dual<value_type, gradient_type> max(dual<value_type, gradient_type> a, real_t b)
|
||||
{
|
||||
using std::max;
|
||||
return max(b, a);
|
||||
}
|
||||
|
||||
/** @brief implementation of min of two dual numbers */
|
||||
template <typename value_type, typename gradient_type> MFEM_HOST_DEVICE
|
||||
dual<value_type, gradient_type> min(dual<value_type, gradient_type> a,
|
||||
dual<value_type, gradient_type> b)
|
||||
{
|
||||
using std::max;
|
||||
if (a.value < b.value)
|
||||
{
|
||||
return a;
|
||||
}
|
||||
else if (a.value > b.value)
|
||||
{
|
||||
return b;
|
||||
}
|
||||
else { return (a+b)*0.5; } // subgradient at the kink
|
||||
}
|
||||
|
||||
/** @brief implementation of min of a dual number and a non-dual number */
|
||||
template <typename value_type, typename gradient_type> MFEM_HOST_DEVICE
|
||||
dual<value_type, gradient_type> min(real_t a, dual<value_type, gradient_type> b)
|
||||
{
|
||||
using std::max;
|
||||
if (a < b.value)
|
||||
{
|
||||
return {a, {}};
|
||||
}
|
||||
else if (a > b.value)
|
||||
{
|
||||
return b;
|
||||
}
|
||||
else { return {a, b.gradient*0.5}; } // subgradient at the kink
|
||||
}
|
||||
|
||||
/** @brief implementation of min of two non-dual numbers */
|
||||
template <typename value_type > MFEM_HOST_DEVICE
|
||||
value_type min(value_type a, value_type b)
|
||||
{
|
||||
using std::pow;
|
||||
return min(a, b);
|
||||
}
|
||||
|
||||
/** @brief implementation of min of a dual number and a non-dual number */
|
||||
template <typename value_type, typename gradient_type> MFEM_HOST_DEVICE
|
||||
dual<value_type, gradient_type> min(dual<value_type, gradient_type> a, real_t b)
|
||||
{
|
||||
using std::max;
|
||||
return min(b, a);
|
||||
}
|
||||
|
||||
/** @brief overload of operator<< for `dual` to work with work with standard output streams */
|
||||
|
||||
+1
-1
@@ -117,7 +117,7 @@ Vector::Vector(const Vector &v)
|
||||
UseDevice(v.UseDevice());
|
||||
}
|
||||
|
||||
Vector::Vector(Vector &&v)
|
||||
Vector::Vector(Vector &&v) : Vector()
|
||||
{
|
||||
*this = std::move(v);
|
||||
}
|
||||
|
||||
+111
-25
@@ -32,6 +32,7 @@
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <functional>
|
||||
#include <set>
|
||||
#include <numeric>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
@@ -921,6 +922,63 @@ const FaceGeometricFactors* Mesh::GetFaceGeometricFactors(
|
||||
return gf;
|
||||
}
|
||||
|
||||
const Array<int>& Mesh::GetBdrFaceAttributes() const
|
||||
{
|
||||
if (bdr_face_attrs_cache.Size() == 0)
|
||||
{
|
||||
std::unordered_map<int, int> f_to_be;
|
||||
for (int i = 0; i < GetNBE(); ++i)
|
||||
{
|
||||
const int f = GetBdrElementFaceIndex(i);
|
||||
f_to_be[f] = i;
|
||||
}
|
||||
const int nf_bdr = GetNFbyType(FaceType::Boundary);
|
||||
// MFEM_VERIFY(size_t(nf_bdr) == f_to_be.size(), "Incompatible sizes");
|
||||
bdr_face_attrs_cache.SetSize(nf_bdr);
|
||||
int f_ind = 0;
|
||||
const int nf = GetNumFaces();
|
||||
for (int f = 0; f < nf; ++f)
|
||||
{
|
||||
if (!GetFaceInformation(f).IsOfFaceType(FaceType::Boundary))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
int attribute = -1; // default value
|
||||
auto iter = f_to_be.find(f);
|
||||
if (iter != f_to_be.end())
|
||||
{
|
||||
const int be = iter->second;
|
||||
attribute = GetBdrAttribute(be);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If a boundary face does not correspond to the a boundary element,
|
||||
// we assign it the default attribute of -1.
|
||||
}
|
||||
bdr_face_attrs_cache[f_ind] = attribute;
|
||||
++f_ind;
|
||||
}
|
||||
}
|
||||
return bdr_face_attrs_cache;
|
||||
}
|
||||
|
||||
const Array<int>& Mesh::GetElementAttributes() const
|
||||
{
|
||||
if (elem_attrs_cache.Size() == 0)
|
||||
{
|
||||
// re-compute cache
|
||||
elem_attrs_cache.SetSize(GetNE());
|
||||
elem_attrs_cache.HostWrite();
|
||||
for (int i = 0; i < GetNE(); ++i)
|
||||
{
|
||||
elem_attrs_cache[i] = GetAttribute(i);
|
||||
MFEM_ASSERT(elem_attrs_cache[i] > 0,
|
||||
"Negative attribute on element " << i);
|
||||
}
|
||||
}
|
||||
return elem_attrs_cache;
|
||||
}
|
||||
|
||||
void Mesh::DeleteGeometricFactors()
|
||||
{
|
||||
for (int i = 0; i < geom_factors.Size(); i++)
|
||||
@@ -1804,6 +1862,8 @@ void Mesh::Destroy()
|
||||
TetMemory.Clear();
|
||||
#endif
|
||||
|
||||
elem_attrs_cache.DeleteAll();
|
||||
bdr_face_attrs_cache.DeleteAll();
|
||||
attributes.DeleteAll();
|
||||
bdr_attributes.DeleteAll();
|
||||
}
|
||||
@@ -1817,36 +1877,48 @@ void Mesh::ResetLazyData()
|
||||
DeleteGeometricFactors();
|
||||
nbInteriorFaces = -1;
|
||||
nbBoundaryFaces = -1;
|
||||
// set size to 0 so re-computations can potentially avoid a new allocation
|
||||
bdr_face_attrs_cache.SetSize(0);
|
||||
elem_attrs_cache.SetSize(0);
|
||||
}
|
||||
|
||||
void Mesh::SetAttributes()
|
||||
void Mesh::SetAttributes(bool elem_attrs_changed, bool bdr_face_attrs_changed)
|
||||
{
|
||||
Array<int> attribs;
|
||||
if (bdr_face_attrs_changed)
|
||||
{
|
||||
bdr_face_attrs_cache.SetSize(0); // Invalidate the cache
|
||||
|
||||
attribs.SetSize(GetNBE());
|
||||
for (int i = 0; i < attribs.Size(); i++)
|
||||
{
|
||||
attribs[i] = GetBdrAttribute(i);
|
||||
}
|
||||
attribs.Sort();
|
||||
attribs.Unique();
|
||||
attribs.Copy(bdr_attributes);
|
||||
if (bdr_attributes.Size() > 0 && bdr_attributes[0] <= 0)
|
||||
{
|
||||
MFEM_WARNING("Non-positive attributes on the boundary!");
|
||||
// Get sorted list of unique boundary element attributes
|
||||
std::set<int> attribs;
|
||||
for (int i = 0; i < GetNBE(); i++)
|
||||
{
|
||||
attribs.emplace(GetBdrAttribute(i));
|
||||
}
|
||||
|
||||
bdr_attributes.SetSize(attribs.size());
|
||||
bdr_attributes.HostWrite();
|
||||
std::copy(attribs.begin(), attribs.end(), bdr_attributes.begin());
|
||||
if (bdr_attributes.Size() > 0 && bdr_attributes[0] <= 0)
|
||||
{
|
||||
MFEM_WARNING("Non-positive attributes on the boundary!");
|
||||
}
|
||||
}
|
||||
|
||||
attribs.SetSize(GetNE());
|
||||
for (int i = 0; i < attribs.Size(); i++)
|
||||
if (elem_attrs_changed)
|
||||
{
|
||||
attribs[i] = GetAttribute(i);
|
||||
}
|
||||
attribs.Sort();
|
||||
attribs.Unique();
|
||||
attribs.Copy(attributes);
|
||||
if (attributes.Size() > 0 && attributes[0] <= 0)
|
||||
{
|
||||
MFEM_WARNING("Non-positive attributes in the domain!");
|
||||
// Re-compute the attributes cache
|
||||
elem_attrs_cache.SetSize(0);
|
||||
GetElementAttributes();
|
||||
// Get sorted list of unique element attributes
|
||||
std::set<int> attribs(elem_attrs_cache.begin(), elem_attrs_cache.end());
|
||||
attributes.SetSize(attribs.size());
|
||||
attributes.HostWrite();
|
||||
std::copy(attribs.begin(), attribs.end(), attributes.begin());
|
||||
|
||||
if (attributes.Size() > 0 && attributes[0] <= 0)
|
||||
{
|
||||
MFEM_WARNING("Non-positive attributes in the domain!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4454,6 +4526,10 @@ Mesh::Mesh(const Mesh &mesh, bool copy_nodes)
|
||||
Nodes = mesh.Nodes;
|
||||
own_nodes = 0;
|
||||
}
|
||||
|
||||
// copy attribute caches
|
||||
elem_attrs_cache = mesh.elem_attrs_cache;
|
||||
bdr_face_attrs_cache = mesh.bdr_face_attrs_cache;
|
||||
}
|
||||
|
||||
Mesh::Mesh(Mesh &&mesh) : Mesh()
|
||||
@@ -7721,6 +7797,12 @@ void Mesh::GetBdrElementAdjacentElement2(
|
||||
void Mesh::SetAttribute(int i, int attr)
|
||||
{
|
||||
elements[i]->SetAttribute(attr);
|
||||
if (elem_attrs_cache.Size() == GetNE())
|
||||
{
|
||||
// update the existing cache instead of deleting it
|
||||
elem_attrs_cache.HostReadWrite();
|
||||
elem_attrs_cache[i] = attr;
|
||||
}
|
||||
if (ncmesh) ncmesh->SetAttribute(i, attr);
|
||||
}
|
||||
|
||||
@@ -10850,6 +10932,10 @@ void Mesh::Swap(Mesh& other, bool non_geometry)
|
||||
mfem::Swap(nodes_sequence, other.nodes_sequence);
|
||||
mfem::Swap(last_operation, other.last_operation);
|
||||
}
|
||||
|
||||
// copy attribute caches
|
||||
mfem::Swap(elem_attrs_cache, other.elem_attrs_cache);
|
||||
mfem::Swap(bdr_face_attrs_cache, other.bdr_face_attrs_cache);
|
||||
}
|
||||
|
||||
void Mesh::GetElementData(const Array<Element*> &elem_array, int geom,
|
||||
@@ -13257,7 +13343,7 @@ void Mesh::ScaleElements(real_t sf)
|
||||
delete [] vn;
|
||||
}
|
||||
|
||||
void Mesh::Transform(void (*f)(const Vector&, Vector&))
|
||||
void Mesh::Transform(std::function<void(const Vector &, Vector&)> f)
|
||||
{
|
||||
// TODO: support for different new spaceDim.
|
||||
if (Nodes == NULL)
|
||||
@@ -13270,7 +13356,7 @@ void Mesh::Transform(void (*f)(const Vector&, Vector&))
|
||||
vold(j) = vertices[i](j);
|
||||
}
|
||||
vnew.SetData(vertices[i]());
|
||||
(*f)(vold, vnew);
|
||||
f(vold, vnew);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
+39
-4
@@ -107,6 +107,11 @@ protected:
|
||||
Array<Element *> boundary;
|
||||
Array<Element *> faces;
|
||||
|
||||
/// internal cache for element attributes
|
||||
mutable Array<int> elem_attrs_cache;
|
||||
/// internal cache for boundary element attributes
|
||||
mutable Array<int> bdr_face_attrs_cache;
|
||||
|
||||
/** @brief This structure stores the low level information necessary to
|
||||
interpret the configuration of elements on a specific face. This
|
||||
information can be accessed using methods like GetFaceElements(),
|
||||
@@ -1122,13 +1127,14 @@ public:
|
||||
Mesh vertices or nodes are set. */
|
||||
virtual void Finalize(bool refine = false, bool fix_orientation = false);
|
||||
|
||||
/// @brief Determine the sets of unique attribute values in domain and
|
||||
/// boundary elements.
|
||||
/// @brief Determine the sets of unique attribute values in domain if @a
|
||||
/// elem_attrs_changed and boundary elements if @a bdr_face_attrs_changed.
|
||||
///
|
||||
/// Separately scan the domain and boundary elements to generate unique,
|
||||
/// sorted sets of the element attribute values present in the mesh and
|
||||
/// store these in the Mesh::attributes and Mesh::bdr_attributes arrays.
|
||||
virtual void SetAttributes();
|
||||
virtual void SetAttributes(bool elem_attrs_changed = true,
|
||||
bool bdr_face_attrs_changed = true);
|
||||
|
||||
/// Check (and optionally attempt to fix) the orientation of the elements
|
||||
/** @param[in] fix_it If `true`, attempt to fix the orientations of some
|
||||
@@ -2254,7 +2260,7 @@ public:
|
||||
void ScaleSubdomains (real_t sf);
|
||||
void ScaleElements (real_t sf);
|
||||
|
||||
void Transform(void (*f)(const Vector&, Vector&));
|
||||
void Transform(std::function<void(const Vector &, Vector&)> f);
|
||||
void Transform(VectorCoefficient &deformation);
|
||||
|
||||
/** @brief This function should be called after the mesh node coordinates
|
||||
@@ -2267,6 +2273,35 @@ public:
|
||||
method does not modify the nodes. */
|
||||
void NodesUpdated() { DeleteGeometricFactors(); }
|
||||
|
||||
/// @brief Returns the attributes for all elements in this mesh. The i'th
|
||||
/// entry of the array is the attribute of the i'th element of the mesh.
|
||||
///
|
||||
/// The returned array points to an internal object that may be invalidated
|
||||
/// by mesh operations such as refinement or any element attributes are
|
||||
/// modified. Since not all such modifications can be tracked by the Mesh
|
||||
/// class (e.g. if a user calls GetElement() then changes the element
|
||||
/// attribute directly), one needs to account for such changes by calling the
|
||||
/// method SetAttributes().
|
||||
const Array<int>& GetElementAttributes() const;
|
||||
|
||||
/// @brief Returns the attributes for all boundary elements in this mesh.
|
||||
///
|
||||
/// The face restriction will give "face E-vectors" on the boundary that
|
||||
/// are numbered in the order of the faces of mesh. This numbering will be
|
||||
/// different than the numbering of the boundary elements. We compute
|
||||
/// mappings so that the array `bdr_attributes[i]` gives the boundary
|
||||
/// attribute of the `i`th boundary face in the mesh face order.
|
||||
/// Attributes <= 0 indicate there is no boundary element and should be
|
||||
/// skipped.
|
||||
///
|
||||
/// The returned array points to an internal object that may be invalidated
|
||||
/// by mesh operations such as refinement or any element attributes are
|
||||
/// modified. Since not all such modifications can be tracked by the Mesh
|
||||
/// class (e.g. if a user calls GetElement() then changes the element
|
||||
/// attribute directly), one needs to account for such changes by calling the
|
||||
/// method SetAttributes().
|
||||
const Array<int>& GetBdrFaceAttributes() const;
|
||||
|
||||
/// @}
|
||||
|
||||
/// @anchor mfem_Mesh_gf_nodes
|
||||
|
||||
+14
-8
@@ -1590,21 +1590,27 @@ void ParMesh::DistributeAttributes(Array<int> &attr)
|
||||
delete [] glb_attr_marker;
|
||||
}
|
||||
|
||||
void ParMesh::SetAttributes()
|
||||
void ParMesh::SetAttributes(bool elem_attrs_changed, bool bdr_attrs_changed)
|
||||
{
|
||||
// Determine the attributes occurring in local interior and boundary elements
|
||||
Mesh::SetAttributes();
|
||||
Mesh::SetAttributes(elem_attrs_changed, bdr_attrs_changed);
|
||||
|
||||
DistributeAttributes(bdr_attributes);
|
||||
if (bdr_attributes.Size() > 0 && bdr_attributes[0] <= 0)
|
||||
if (bdr_attrs_changed)
|
||||
{
|
||||
MFEM_WARNING("Non-positive boundary element attributes found!");
|
||||
DistributeAttributes(bdr_attributes);
|
||||
if (bdr_attributes.Size() > 0 && bdr_attributes[0] <= 0)
|
||||
{
|
||||
MFEM_WARNING("Non-positive boundary element attributes found!");
|
||||
}
|
||||
}
|
||||
|
||||
DistributeAttributes(attributes);
|
||||
if (attributes.Size() > 0 && attributes[0] <= 0)
|
||||
if (elem_attrs_changed)
|
||||
{
|
||||
MFEM_WARNING("Non-positive element attributes found!");
|
||||
DistributeAttributes(attributes);
|
||||
if (attributes.Size() > 0 && attributes[0] <= 0)
|
||||
{
|
||||
MFEM_WARNING("Non-positive element attributes found!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -394,7 +394,8 @@ public:
|
||||
|
||||
void Finalize(bool refine = false, bool fix_orientation = false) override;
|
||||
|
||||
void SetAttributes() override;
|
||||
void SetAttributes(bool elem_attrs_changed = true,
|
||||
bool bdr_attrs_changed = true) override;
|
||||
|
||||
/// Checks if any rank in the mesh has boundary elements
|
||||
bool HasBoundaryElements() const override;
|
||||
|
||||
@@ -9,12 +9,22 @@
|
||||
# terms of the BSD-3 license. We welcome feedback and contributions, see file
|
||||
# CONTRIBUTING.md for details.
|
||||
|
||||
list(APPEND SEQADIFF_COMMON_SOURCES)
|
||||
list(APPEND SEQADIFF_COMMON_SOURCES
|
||||
ad_native.cpp
|
||||
logger.cpp
|
||||
pg.cpp
|
||||
)
|
||||
|
||||
list(APPEND SEQADIFF_COMMON_HEADERS
|
||||
tadvector.hpp
|
||||
taddensemat.hpp
|
||||
admfem.hpp)
|
||||
admfem.hpp
|
||||
ad_intg.hpp
|
||||
ad_native.hpp
|
||||
logger.hpp
|
||||
pg.hpp
|
||||
tools.hpp
|
||||
)
|
||||
|
||||
convert_filenames_to_full_paths(SEQADIFF_COMMON_SOURCES)
|
||||
convert_filenames_to_full_paths(SEQADIFF_COMMON_HEADERS)
|
||||
@@ -23,34 +33,15 @@ set(SEQADIFF_COMMON_FILES
|
||||
EXTRA_SOURCES ${SEQADIFF_COMMON_SOURCES}
|
||||
EXTRA_HEADERS ${SEQADIFF_COMMON_HEADERS})
|
||||
|
||||
add_mfem_miniapp(seqadiff
|
||||
MAIN seq_example.cpp
|
||||
${SEQADIFF_COMMON_FILES}
|
||||
LIBRARIES mfem)
|
||||
|
||||
add_mfem_miniapp(seqtest
|
||||
MAIN seq_test.cpp
|
||||
${SEQADIFF_COMMON_FILES}
|
||||
LIBRARIES mfem)
|
||||
|
||||
if(MFEM_USE_MPI)
|
||||
|
||||
list(APPEND PARADIFF_COMMON_SOURCES)
|
||||
list(APPEND PARADIFF_COMMON_HEADERS)
|
||||
|
||||
convert_filenames_to_full_paths(PARADIFF_COMMON_SOURCES)
|
||||
convert_filenames_to_full_paths(PARADIFF_COMMON_HEADERS)
|
||||
|
||||
set(PARADIFF_COMMON_FILES
|
||||
EXTRA_SOURCES ${PARADIFF_COMMON_SOURCES} ${SEQADIFF_COMMON_SOURCES}
|
||||
EXTRA_HEADERS ${PARADIFF_COMMON_HEADERS} ${SEQADIFF_COMMON_HEADERS})
|
||||
|
||||
# message(STATUS "PARADIFF_COMMON_FILES: ${PARADIFF_COMMON_FILES}")
|
||||
# message(STATUS "SEQADIFF_COMMON_FILES: ${SEQADIFF_COMMON_FILES}")
|
||||
|
||||
add_mfem_miniapp(paradiff
|
||||
MAIN par_example.cpp
|
||||
${PARADIFF_COMMON_FILES}
|
||||
LIBRARIES mfem)
|
||||
|
||||
add_mfem_miniapp(ad_ex0 MAIN ad_ex0.cpp ${SEQADIFF_COMMON_FILES} LIBRARIES mfem)
|
||||
add_mfem_miniapp(ad_ex1 MAIN ad_ex1.cpp ${SEQADIFF_COMMON_FILES} LIBRARIES mfem)
|
||||
add_mfem_miniapp(ad_ex2 MAIN ad_ex2.cpp ${SEQADIFF_COMMON_FILES} LIBRARIES mfem)
|
||||
add_mfem_miniapp(ad_ex3 MAIN ad_ex3.cpp ${SEQADIFF_COMMON_FILES} LIBRARIES mfem)
|
||||
if (MFEM_USE_MUMPS)
|
||||
add_mfem_miniapp(ad_ex4 MAIN ad_ex4.cpp ${SEQADIFF_COMMON_FILES} LIBRARIES mfem)
|
||||
add_mfem_miniapp(ad_ex5 MAIN ad_ex5.cpp ${SEQADIFF_COMMON_FILES} LIBRARIES mfem)
|
||||
endif ()
|
||||
if (MFEM_USE_MPI)
|
||||
add_mfem_miniapp(ad_ex6 MAIN ad_ex6.cpp ${SEQADIFF_COMMON_FILES} LIBRARIES mfem)
|
||||
endif ()
|
||||
|
||||
|
||||
@@ -0,0 +1,697 @@
|
||||
// IWYU pragma: private, include "ad_intg.hpp"
|
||||
// -----------------------------------------
|
||||
/// Templated AD (block) nonlinear form integrators implementations
|
||||
#pragma once
|
||||
#include "ad_intg.hpp"
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
|
||||
template <ADEval mode>
|
||||
inline int ADNonlinearFormIntegrator<mode>::InitInputShapes(
|
||||
const FiniteElement &el,
|
||||
ElementTransformation &Tr,
|
||||
DenseMatrix &shapes)
|
||||
{
|
||||
const int sdim = Tr.GetSpaceDim();
|
||||
const int dim = el.GetDim();
|
||||
int idx[static_cast<int>(ADEval::NUMOPT)];
|
||||
idx[0] = 0;
|
||||
idx[1] = idx[0] + (hasFlag(mode, ADEval::QVALUE) ? 1 : 0);
|
||||
idx[2] = idx[1] + (hasFlag(mode, ADEval::VALUE)
|
||||
? hasFlag(mode, ADEval::VECFE)
|
||||
? dim // if vector-FE
|
||||
: 1 // if scalar-FE
|
||||
: 0); // no value
|
||||
idx[3] = idx[2] + (hasFlag(mode, ADEval::GRAD) ? sdim : 0);
|
||||
idx[4] = idx[3] + (hasFlag(mode, ADEval::DIV) ? 1 : 0);
|
||||
idx[5] = idx[4] + (hasFlag(mode, ADEval::CURL) ? el.GetCurlDim() : 0);
|
||||
const int shapedim = idx[5];
|
||||
const int dof = el.GetDof();
|
||||
shapes.SetSize(dof, shapedim);
|
||||
|
||||
if constexpr (hasFlag(mode, ADEval::QVALUE)) { shapes.SetCol(idx[0], 0.0); }
|
||||
|
||||
if constexpr (hasFlag(mode, ADEval::VALUE))
|
||||
{
|
||||
if constexpr (hasFlag(mode, ADEval::VECFE)) { vshape.UseExternalData(shapes.GetData() + dof*idx[1], dof, dim); }
|
||||
else { shapes.GetColumnReference(idx[1], shape); }
|
||||
}
|
||||
|
||||
if constexpr (hasFlag(mode, ADEval::GRAD))
|
||||
{
|
||||
gshape.UseExternalData(shapes.GetData() + dof*idx[2],
|
||||
dof, sdim);
|
||||
}
|
||||
if constexpr (hasFlag(mode, ADEval::DIV))
|
||||
{
|
||||
shapes.GetColumnReference(idx[3], divshape);
|
||||
}
|
||||
|
||||
if constexpr (hasFlag(mode, ADEval::CURL))
|
||||
{
|
||||
curlshape.UseExternalData(shapes.GetData() + dof*idx[4],
|
||||
dof, el.GetCurlDim());
|
||||
}
|
||||
|
||||
return shapedim;
|
||||
}
|
||||
|
||||
template <ADEval mode>
|
||||
inline void ADNonlinearFormIntegrator<mode>::CalcInputShapes(
|
||||
const FiniteElement &el,
|
||||
ElementTransformation &Tr,
|
||||
const IntegrationPoint &ip,
|
||||
DenseMatrix &allshapes)
|
||||
{
|
||||
// Get quadrature value
|
||||
// ip should be from the same integration rule with base quadrature
|
||||
if constexpr (hasFlag(mode, ADEval::QVALUE)) { allshapes.SetCol(0, 0.0); allshapes(ip.index, 0) = 1.0; }
|
||||
|
||||
// Get value shape
|
||||
if constexpr (hasFlag(mode, ADEval::VALUE))
|
||||
{
|
||||
if constexpr (hasFlag(mode, ADEval::VECFE)) { el.CalcPhysVShape(Tr, vshape); }
|
||||
else { el.CalcPhysShape(Tr, shape); }
|
||||
}
|
||||
|
||||
// Get gradient shape
|
||||
if constexpr (hasFlag(mode, ADEval::GRAD)) { el.CalcPhysDShape(Tr, gshape); }
|
||||
|
||||
// Get divergence shape
|
||||
if constexpr (hasFlag(mode, ADEval::DIV))
|
||||
{
|
||||
if constexpr (hasFlag(mode, ADEval::GRAD))
|
||||
{
|
||||
gshape.GetRowSums(divshape);
|
||||
}
|
||||
else
|
||||
{
|
||||
el.CalcPhysDivShape(Tr, divshape);
|
||||
}
|
||||
}
|
||||
|
||||
// Get divergence shape
|
||||
if constexpr (hasFlag(mode, ADEval::CURL)) { el.CalcPhysCurlShape(Tr, curlshape); }
|
||||
}
|
||||
|
||||
/// Perform the local action of the NonlinearFormIntegrator
|
||||
template <ADEval mode>
|
||||
real_t ADNonlinearFormIntegrator<mode>::GetElementEnergy(
|
||||
const FiniteElement &el,
|
||||
ElementTransformation &Tr,
|
||||
const Vector &elfun)
|
||||
{
|
||||
const int dof = el.GetDof();
|
||||
const int vdim = elfun.Size() / dof;
|
||||
MFEM_ASSERT(vdim == 1 ? true : hasFlag(mode, ADEval::VECTOR),
|
||||
"ADNonlinearFormIntegrator: "
|
||||
"vdim must be 1 or the mode must be VECTOR");
|
||||
|
||||
real_t energy = 0.0;
|
||||
|
||||
int shapedim = InitInputShapes(el, Tr, allshapes);
|
||||
x.SetSize(f.n_input);
|
||||
if constexpr (hasFlag(mode, ADEval::VECTOR))
|
||||
{
|
||||
elfun_matview.UseExternalData(const_cast<real_t*>(elfun.GetData()),
|
||||
dof, vdim);
|
||||
xmat.UseExternalData(x.GetData(), shapedim, vdim);
|
||||
}
|
||||
|
||||
const IntegrationRule * ir = GetIntegrationRule(el, Tr);
|
||||
for (int i = 0; i < ir->GetNPoints(); i++)
|
||||
{
|
||||
const IntegrationPoint &ip = ir->IntPoint(i);
|
||||
Tr.SetIntPoint(&ip);
|
||||
|
||||
CalcInputShapes(el, Tr, ip, allshapes);
|
||||
|
||||
if constexpr (hasFlag(mode, ADEval::VECTOR))
|
||||
{
|
||||
MultAtB(allshapes, elfun_matview, xmat);
|
||||
}
|
||||
else
|
||||
{
|
||||
allshapes.MultTranspose(elfun, x);
|
||||
}
|
||||
energy += f(x, Tr, ip)*Tr.Weight()*ip.weight;
|
||||
}
|
||||
return energy;
|
||||
}
|
||||
|
||||
/// Compute the local <grad f, v>
|
||||
template <ADEval mode>
|
||||
void ADNonlinearFormIntegrator<mode>::AssembleElementVector(
|
||||
const FiniteElement &el,
|
||||
ElementTransformation &Tr,
|
||||
const Vector &elfun, Vector &elvect)
|
||||
{
|
||||
const int dof = el.GetDof();
|
||||
const int vdim = elfun.Size() / dof;
|
||||
MFEM_ASSERT(vdim == 1 ? true : hasFlag(mode, ADEval::VECTOR),
|
||||
"ADNonlinearFormIntegrator: "
|
||||
"vdim must be 1 or the mode must be VECTOR");
|
||||
|
||||
real_t w;
|
||||
elvect.SetSize(dof*vdim);
|
||||
elvect = 0.0;
|
||||
|
||||
x.SetSize(f.n_input);
|
||||
jac.SetSize(f.n_input);
|
||||
|
||||
int shapedim = InitInputShapes(el, Tr, allshapes);
|
||||
|
||||
if constexpr (hasFlag(mode, ADEval::VECTOR))
|
||||
{
|
||||
elfun_matview.UseExternalData(const_cast<real_t*>(elfun.GetData()),
|
||||
dof, vdim);
|
||||
elvectmat.UseExternalData(elvect.GetData(), dof, vdim);
|
||||
xmat.UseExternalData(x.GetData(), shapedim, vdim);
|
||||
jacMat.UseExternalData(jac.GetData(), shapedim, vdim);
|
||||
}
|
||||
|
||||
const IntegrationRule * ir = GetIntegrationRule(el, Tr);
|
||||
for (int i = 0; i < ir->GetNPoints(); i++)
|
||||
{
|
||||
const IntegrationPoint &ip = ir->IntPoint(i);
|
||||
Tr.SetIntPoint(&ip);
|
||||
w = ip.weight * Tr.Weight();
|
||||
|
||||
CalcInputShapes(el, Tr, ip, allshapes);
|
||||
|
||||
// Convert dof to x = [[value, grad], [value, grad], ...]
|
||||
if constexpr (hasFlag(mode, ADEval::VECTOR)) { MultAtB(allshapes, elfun_matview, xmat); }
|
||||
else { allshapes.MultTranspose(elfun, x); }
|
||||
|
||||
f.Gradient(x, Tr, ip, jac);
|
||||
jac *= w;
|
||||
|
||||
if constexpr (hasFlag(mode, ADEval::VECTOR))
|
||||
{
|
||||
AddMult(allshapes, jacMat, elvectmat);
|
||||
}
|
||||
else
|
||||
{
|
||||
allshapes.AddMult(jac, elvect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Assemble the local <H_f(x)(u), v>
|
||||
template <ADEval mode>
|
||||
void ADNonlinearFormIntegrator<mode>::AssembleElementGrad(
|
||||
const FiniteElement &el,
|
||||
ElementTransformation &Tr,
|
||||
const Vector &elfun, DenseMatrix &elmat)
|
||||
{
|
||||
const int dof = el.GetDof();
|
||||
const int vdim = elfun.Size() / dof;
|
||||
MFEM_ASSERT(vdim == 1 ? true : hasFlag(mode, ADEval::VECTOR),
|
||||
"ADNonlinearFormIntegrator: "
|
||||
"vdim must be 1 or the mode must be VECTOR");
|
||||
|
||||
real_t w;
|
||||
elmat.SetSize(dof*vdim);
|
||||
elmat = 0.0;
|
||||
|
||||
int shapedim = InitInputShapes(el, Tr, allshapes);
|
||||
MFEM_ASSERT(shapedim*vdim == f.n_input,
|
||||
"ADNonlinearFormIntegrator: "
|
||||
"shapedim*vdim must match n_input");
|
||||
|
||||
x.SetSize(f.n_input);
|
||||
H.SetSize(f.n_input);
|
||||
Hx.SetSize(dof, shapedim*vdim*vdim);
|
||||
|
||||
|
||||
if constexpr (hasFlag(mode, ADEval::VECTOR))
|
||||
{
|
||||
elfun_matview.UseExternalData(const_cast<real_t*>(elfun.GetData()),
|
||||
dof, vdim);
|
||||
xmat.UseExternalData(x.GetData(), shapedim, vdim);
|
||||
partelmat.SetSize(dof, dof);
|
||||
Hs.UseExternalData(H.GetData(), shapedim, vdim*shapedim*vdim);
|
||||
}
|
||||
|
||||
const IntegrationRule * ir = GetIntegrationRule(el, Tr);
|
||||
for (int i = 0; i < ir->GetNPoints(); i++)
|
||||
{
|
||||
const IntegrationPoint &ip = ir->IntPoint(i);
|
||||
Tr.SetIntPoint(&ip);
|
||||
w = ip.weight * Tr.Weight();
|
||||
CalcInputShapes(el, Tr, ip, allshapes);
|
||||
|
||||
// Convert dof to x = [[value, grad], [value, grad], ...]
|
||||
if constexpr (hasFlag(mode, ADEval::VECTOR)) { MultAtB(allshapes, elfun_matview, xmat); }
|
||||
else { allshapes.MultTranspose(elfun, x); }
|
||||
|
||||
f.Hessian(x, Tr, ip, H);
|
||||
H *= w;
|
||||
|
||||
if constexpr (hasFlag(mode, ADEval::VECTOR))
|
||||
{
|
||||
Mult(allshapes, Hs, Hx);
|
||||
const int nel = shapedim*dof;
|
||||
for (int c=0; c<vdim; c++)
|
||||
{
|
||||
for (int r=0; r<=c; r++)
|
||||
{
|
||||
Hxsub.UseExternalData(Hx.GetData() + (c*vdim + r)*nel, dof, shapedim);
|
||||
MultABt(allshapes, Hxsub, partelmat);
|
||||
elmat.AddSubMatrix(c*dof, r*dof, partelmat);
|
||||
if (c != r)
|
||||
{
|
||||
elmat.AddSubMatrix(r*dof, c*dof, partelmat);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Mult(allshapes, H, Hx);
|
||||
AddMultABt(allshapes, Hx, elmat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Perform the local action of the NonlinearFormIntegrator resulting
|
||||
/// from a face integral term.
|
||||
template <ADEval mode>
|
||||
void ADNonlinearFormIntegrator<mode>::AssembleFaceVector(
|
||||
const FiniteElement &el1,
|
||||
const FiniteElement &el2,
|
||||
FaceElementTransformations &Tr,
|
||||
const Vector &elfun, Vector &elvect)
|
||||
{
|
||||
MFEM_ABORT("ADNonlinearFormIntegrator::AssembleFaceVector: "
|
||||
"This method is not implemented.");
|
||||
}
|
||||
|
||||
|
||||
/// @brief Assemble the local action of the gradient of the
|
||||
/// NonlinearFormIntegrator resulting from a face integral term.
|
||||
template <ADEval mode>
|
||||
void ADNonlinearFormIntegrator<mode>::AssembleFaceGrad(
|
||||
const FiniteElement &el1,
|
||||
const FiniteElement &el2,
|
||||
FaceElementTransformations &Tr,
|
||||
const Vector &elfun, DenseMatrix &elmat)
|
||||
{
|
||||
MFEM_ABORT("ADNonlinearFormIntegrator::AssembleFaceVector: "
|
||||
"This method is not implemented.");
|
||||
}
|
||||
|
||||
template <ADEval... modes>
|
||||
inline std::array<int, sizeof...(modes)>
|
||||
ADBlockNonlinearFormIntegrator<modes...>::InitInputShapes(
|
||||
const Array<const FiniteElement *>& els,
|
||||
ElementTransformation &Tr,
|
||||
std::vector<DenseMatrix> &shapes)
|
||||
{
|
||||
MFEM_ASSERT(els.Size() == numSpaces,
|
||||
"ADBlockNonlinearFormIntegrator: "
|
||||
"el.Size()=" << els.Size() << " must match numSpaces=" << numSpaces);
|
||||
const int sdim = Tr.GetSpaceDim();
|
||||
std::array<int, sizeof...(modes)> shapedims{};
|
||||
|
||||
_constexpr_for([&](auto i)
|
||||
{
|
||||
constexpr auto mode = modes_arr[i];
|
||||
const FiniteElement &el = *els[i];
|
||||
const int sdim = Tr.GetSpaceDim();
|
||||
const int dim = el.GetDim();
|
||||
int idx[static_cast<int>(ADEval::NUMOPT)];
|
||||
idx[0] = 0;
|
||||
idx[1] = idx[0] + (hasFlag(modes_arr[i], ADEval::QVALUE) ? 1 : 0);
|
||||
idx[2] = idx[1] + (hasFlag(modes_arr[i], ADEval::VALUE)
|
||||
? hasFlag(modes_arr[i], ADEval::VECFE)
|
||||
? dim // if vector-FE
|
||||
: 1 // if scalar-FE
|
||||
: 0); // no value
|
||||
idx[3] = idx[2] + (hasFlag(modes_arr[i], ADEval::GRAD) ? sdim : 0);
|
||||
idx[4] = idx[3] + (hasFlag(modes_arr[i], ADEval::DIV) ? 1 : 0);
|
||||
idx[5] = idx[4] + (hasFlag(modes_arr[i], ADEval::CURL) ? el.GetCurlDim() : 0);
|
||||
const int shapedim = idx[5];
|
||||
const int dof = el.GetDof();
|
||||
shapes[i].SetSize(dof, shapedim);
|
||||
|
||||
if constexpr (hasFlag(mode, ADEval::QVALUE)) { shapes[i].SetCol(idx[0], 0.0); }
|
||||
|
||||
if constexpr (hasFlag(mode, ADEval::VALUE))
|
||||
{
|
||||
if constexpr (hasFlag(mode, ADEval::VECFE)) { vshape[i].UseExternalData(shapes[i].GetData() + dof*idx[1], dof, dim); }
|
||||
else { shapes[i].GetColumnReference(idx[1], shape[i]); }
|
||||
}
|
||||
|
||||
if constexpr (hasFlag(mode, ADEval::GRAD))
|
||||
{
|
||||
gshape[i].UseExternalData(shapes[i].GetData() + dof*idx[2],
|
||||
dof, sdim);
|
||||
}
|
||||
if constexpr (hasFlag(mode, ADEval::DIV))
|
||||
{
|
||||
shapes[i].GetColumnReference(idx[3], divshape[i]);
|
||||
}
|
||||
|
||||
if constexpr (hasFlag(mode, ADEval::CURL))
|
||||
{
|
||||
curlshape[i].UseExternalData(shapes[i].GetData() + dof*idx[4],
|
||||
dof, el.GetCurlDim());
|
||||
}
|
||||
shapedims[i] = shapedim;
|
||||
}, std::make_index_sequence<sizeof...(modes)> {});
|
||||
return shapedims;
|
||||
}
|
||||
template <ADEval... modes>
|
||||
inline void
|
||||
ADBlockNonlinearFormIntegrator<modes...>::CalcInputShapes(
|
||||
const Array<const FiniteElement *>& els,
|
||||
ElementTransformation &Tr,
|
||||
const IntegrationPoint &ip,
|
||||
std::vector<DenseMatrix> &allshapes)
|
||||
{
|
||||
_constexpr_for([&](auto i)
|
||||
{
|
||||
const auto&el = *els[i];
|
||||
constexpr auto mode = modes_arr[i];
|
||||
// Get quadrature value
|
||||
// ip should be from the same integration rule with base quadrature
|
||||
if constexpr (hasFlag(mode, ADEval::QVALUE)) { allshapes[i].SetCol(0, 0.0); allshapes[i](ip.index, 0) = 1.0; }
|
||||
|
||||
// Get value shape
|
||||
if constexpr (hasFlag(mode, ADEval::VALUE))
|
||||
{
|
||||
if constexpr (hasFlag(mode, ADEval::VECFE)) { el.CalcPhysVShape(Tr, vshape[i]); }
|
||||
else { el.CalcPhysShape(Tr, shape[i]); }
|
||||
}
|
||||
|
||||
// Get gradient shape
|
||||
if constexpr (hasFlag(mode, ADEval::GRAD)) { el.CalcPhysDShape(Tr, gshape[i]); }
|
||||
|
||||
// Get divergence shape
|
||||
if constexpr (hasFlag(mode, ADEval::DIV))
|
||||
{
|
||||
if constexpr (hasFlag(mode, ADEval::GRAD))
|
||||
{
|
||||
gshape[i].GetRowSums(divshape[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
el.CalcPhysDivShape(Tr, divshape[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Get divergence shape
|
||||
if constexpr (hasFlag(mode, ADEval::CURL)) { el.CalcPhysCurlShape(Tr, curlshape[i]); }
|
||||
}, std::make_index_sequence<sizeof...(modes)> {});
|
||||
}
|
||||
|
||||
/// Compute the local energy
|
||||
template <ADEval... modes>
|
||||
real_t ADBlockNonlinearFormIntegrator<modes...>::GetElementEnergy(
|
||||
const Array<const FiniteElement *> &el,
|
||||
ElementTransformation &Tr,
|
||||
const Array<const Vector*> &elfun)
|
||||
{
|
||||
MFEM_ASSERT(el.Size() == numSpaces,
|
||||
"ADBlockNonlinearFormIntegrator: "
|
||||
"el.Size()=" << el.Size() << " must match numSpaces=" << numSpaces);
|
||||
std::array<int, numSpaces> dof{};
|
||||
std::array<int, numSpaces> order{};
|
||||
for (int i=0; i<numSpaces; i++)
|
||||
{
|
||||
dof[i] = el[i]->GetDof();
|
||||
order[i] = el[i]->GetOrder();
|
||||
vdim[i] = elfun[i]->Size() / dof[i];
|
||||
|
||||
MFEM_ASSERT(vdim[i] == 1 ? true : hasFlag(modes_arr[i], ADEval::VECTOR),
|
||||
"ADNonlinearFormIntegrator: "
|
||||
"vdim must be 1 or the mode must be VECTOR");
|
||||
}
|
||||
|
||||
real_t energy = 0.0;
|
||||
|
||||
std::array<int, numSpaces> shapedim(InitInputShapes(el, Tr, allshapes));
|
||||
x.SetSize(f.n_input);
|
||||
int x_idx = 0;
|
||||
_constexpr_for([&](auto vi)
|
||||
{
|
||||
xvar[vi].MakeRef(x, x_idx, shapedim[vi]*vdim[vi]);
|
||||
x_idx += shapedim[vi]*vdim[vi];
|
||||
if constexpr (hasFlag(modes_arr[vi], ADEval::VECTOR))
|
||||
{
|
||||
elfun_matview[vi].UseExternalData(const_cast<real_t*>(elfun[vi]->GetData()),
|
||||
dof[vi], vdim[vi]);
|
||||
xmat[vi].UseExternalData(xvar[vi].GetData(), shapedim[vi], vdim[vi]);
|
||||
}
|
||||
}, std::make_index_sequence<sizeof...(modes)> {});
|
||||
|
||||
const IntegrationRule * ir = GetIntegrationRule(el, Tr);
|
||||
for (int i = 0; i < ir->GetNPoints(); i++)
|
||||
{
|
||||
const IntegrationPoint &ip = ir->IntPoint(i);
|
||||
Tr.SetIntPoint(&ip);
|
||||
|
||||
CalcInputShapes(el, Tr, ip, allshapes);
|
||||
|
||||
_constexpr_for([&](auto vi)
|
||||
{
|
||||
if constexpr (hasFlag(modes_arr[vi], ADEval::VECTOR))
|
||||
{
|
||||
MultAtB(allshapes[vi], elfun_matview[vi], xmat[vi]);
|
||||
}
|
||||
else
|
||||
{
|
||||
allshapes[vi].MultTranspose(*elfun[vi], xvar[vi]);
|
||||
}
|
||||
}, std::make_index_sequence<sizeof...(modes)> {});
|
||||
energy += f(x, Tr, ip)*Tr.Weight()*ip.weight;
|
||||
}
|
||||
return energy;
|
||||
}
|
||||
|
||||
/// Perform the local action of the NonlinearFormIntegrator
|
||||
template <ADEval... modes>
|
||||
void ADBlockNonlinearFormIntegrator<modes...>::AssembleElementVector(
|
||||
const Array<const FiniteElement *>&el,
|
||||
ElementTransformation &Tr,
|
||||
const Array<const Vector *>&elfun,
|
||||
const Array<Vector *>&elvect)
|
||||
{
|
||||
MFEM_ASSERT(el.Size() == numSpaces,
|
||||
"ADBlockNonlinearFormIntegrator: "
|
||||
"el.Size()=" << el.Size() << " must match numSpaces=" << numSpaces);
|
||||
std::array<int, numSpaces> dof{};
|
||||
std::array<int, numSpaces> order{};
|
||||
for (int i=0; i<numSpaces; i++)
|
||||
{
|
||||
dof[i] = el[i]->GetDof();
|
||||
order[i] = el[i]->GetOrder();
|
||||
vdim[i] = elfun[i]->Size() / dof[i];
|
||||
|
||||
MFEM_ASSERT(vdim[i] == 1 ? true : hasFlag(modes_arr[i], ADEval::VECTOR),
|
||||
"ADNonlinearFormIntegrator: "
|
||||
"vdim must be 1 or the mode must be VECTOR");
|
||||
|
||||
elvect[i]->SetSize(elfun[i]->Size());
|
||||
*elvect[i] = 0.0;
|
||||
}
|
||||
|
||||
std::array<int, numSpaces> shapedim(InitInputShapes(el, Tr, allshapes));
|
||||
Array<int> x_idx(numSpaces+1);
|
||||
x_idx[0] = 0;
|
||||
for (int i=0; i<numSpaces; i++)
|
||||
{
|
||||
x_idx[i+1] = shapedim[i]*vdim[i];
|
||||
}
|
||||
x_idx.PartialSum();
|
||||
x.SetSize(f.n_input);
|
||||
jac.SetSize(f.n_input);
|
||||
_constexpr_for([&](auto vi)
|
||||
{
|
||||
xvar[vi].MakeRef(x, x_idx[vi], shapedim[vi]*vdim[vi]);
|
||||
jacVar[vi].MakeRef(jac, x_idx[vi], shapedim[vi]*vdim[vi]);
|
||||
if constexpr (hasFlag(modes_arr[vi], ADEval::VECTOR))
|
||||
{
|
||||
elfun_matview[vi].UseExternalData(const_cast<real_t*>(elfun[vi]->GetData()),
|
||||
dof[vi], vdim[vi]);
|
||||
xmat[vi].UseExternalData(xvar[vi].GetData(), shapedim[vi], vdim[vi]);
|
||||
jacVarMat[vi].UseExternalData(jacVar[vi].GetData(), shapedim[vi], vdim[vi]);
|
||||
}
|
||||
}, std::make_index_sequence<sizeof...(modes)> {});
|
||||
|
||||
const IntegrationRule * ir = GetIntegrationRule(el, Tr);
|
||||
real_t w;
|
||||
for (int i = 0; i < ir->GetNPoints(); i++)
|
||||
{
|
||||
const IntegrationPoint &ip = ir->IntPoint(i);
|
||||
Tr.SetIntPoint(&ip);
|
||||
w = Tr.Weight()*ip.weight;
|
||||
|
||||
CalcInputShapes(el, Tr, ip, allshapes);
|
||||
|
||||
_constexpr_for([&](auto vi)
|
||||
{
|
||||
if constexpr (hasFlag(modes_arr[vi], ADEval::VECTOR))
|
||||
{
|
||||
MultAtB(allshapes[vi], elfun_matview[vi], xmat[vi]);
|
||||
}
|
||||
else
|
||||
{
|
||||
allshapes[vi].MultTranspose(*elfun[vi], xvar[vi]);
|
||||
}
|
||||
}, std::make_index_sequence<sizeof...(modes)> {});
|
||||
f.Gradient(x, Tr, ip, jac);
|
||||
jac *= w;
|
||||
|
||||
_constexpr_for([&](auto vi)
|
||||
{
|
||||
if constexpr (hasFlag(modes_arr[vi], ADEval::VECTOR))
|
||||
{
|
||||
elvectmat[vi].UseExternalData(elvect[vi]->GetData(), dof[vi], vdim[vi]);
|
||||
AddMult(allshapes[vi], jacVarMat[vi], elvectmat[vi]);
|
||||
}
|
||||
else
|
||||
{
|
||||
allshapes[vi].AddMult(jacVar[vi], *elvect[vi]);
|
||||
}
|
||||
}, std::make_index_sequence<sizeof...(modes)> {});
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform the local action of the NonlinearFormIntegrator
|
||||
template <ADEval... modes>
|
||||
void ADBlockNonlinearFormIntegrator<modes...>::AssembleElementGrad(
|
||||
const Array<const FiniteElement *>&el,
|
||||
ElementTransformation &Tr,
|
||||
const Array<const Vector *>&elfun,
|
||||
const Array2D<DenseMatrix *>&elmat)
|
||||
{
|
||||
MFEM_ASSERT(el.Size() == numSpaces,
|
||||
"ADBlockNonlinearFormIntegrator: "
|
||||
"el.Size()=" << el.Size() << " must match numSpaces=" << numSpaces);
|
||||
Array<int> dof(numSpaces);
|
||||
Array<int> order(numSpaces);
|
||||
for (int i=0; i<numSpaces; i++)
|
||||
{
|
||||
dof[i] = el[i]->GetDof();
|
||||
order[i] = el[i]->GetOrder();
|
||||
vdim[i] = elfun[i]->Size() / dof[i];
|
||||
|
||||
MFEM_ASSERT(vdim[i] == 1 ? true : hasFlag(modes_arr[i], ADEval::VECTOR),
|
||||
"ADNonlinearFormIntegrator: "
|
||||
"vdim must be 1 or the mode must be VECTOR");
|
||||
}
|
||||
|
||||
for (int j=0; j<numSpaces; j++)
|
||||
{
|
||||
for (int i=0; i<numSpaces; i++)
|
||||
{
|
||||
elmat(i,j)->SetSize(elfun[i]->Size(),
|
||||
elfun[j]->Size());
|
||||
*elmat(i,j) = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
std::array<int, numSpaces> shapedim(InitInputShapes(el, Tr, allshapes));
|
||||
Array<int> x_idx(numSpaces+1);
|
||||
x_idx[0] = 0;
|
||||
for (int i=0; i<numSpaces; i++)
|
||||
{
|
||||
x_idx[i+1] = shapedim[i]*vdim[i];
|
||||
}
|
||||
x_idx.PartialSum();
|
||||
x.SetSize(f.n_input);
|
||||
H.SetSize(f.n_input);
|
||||
|
||||
_constexpr_for([&](auto vi)
|
||||
{
|
||||
xvar[vi].MakeRef(x, x_idx[vi], shapedim[vi]*vdim[vi]);
|
||||
if constexpr (hasFlag(modes_arr[vi], ADEval::VECTOR))
|
||||
{
|
||||
elfun_matview[vi].UseExternalData(const_cast<real_t*>(elfun[vi]->GetData()),
|
||||
dof[vi], vdim[vi]);
|
||||
xmat[vi].UseExternalData(xvar[vi].GetData(), shapedim[vi], vdim[vi]);
|
||||
}
|
||||
}, std::make_index_sequence<sizeof...(modes)> {});
|
||||
|
||||
const IntegrationRule * ir = GetIntegrationRule(el, Tr);
|
||||
real_t w;
|
||||
for (int i = 0; i < ir->GetNPoints(); i++)
|
||||
{
|
||||
const IntegrationPoint &ip = ir->IntPoint(i);
|
||||
Tr.SetIntPoint(&ip);
|
||||
w = Tr.Weight()*ip.weight;
|
||||
|
||||
CalcInputShapes(el, Tr, ip, allshapes);
|
||||
|
||||
_constexpr_for([&](auto vi)
|
||||
{
|
||||
if constexpr (hasFlag(modes_arr[vi], ADEval::VECTOR))
|
||||
{
|
||||
MultAtB(allshapes[vi], elfun_matview[vi], xmat[vi]);
|
||||
}
|
||||
else
|
||||
{
|
||||
allshapes[vi].MultTranspose(*elfun[vi], xvar[vi]);
|
||||
}
|
||||
}, std::make_index_sequence<sizeof...(modes)> {});
|
||||
f.Hessian(x, Tr, ip, H);
|
||||
H *= w;
|
||||
_constexpr_for([&](auto trial_i)
|
||||
{
|
||||
_constexpr_for([&](auto test_i)
|
||||
{
|
||||
const int tr_vdim = vdim[trial_i];
|
||||
const int ts_vdim = vdim[test_i];
|
||||
H.GetSubMatrix(x_idx[test_i], x_idx[test_i+1], x_idx[trial_i], x_idx[trial_i+1],
|
||||
Hsub);
|
||||
Hsub.SetSize(shapedim[test_i], ts_vdim*tr_vdim*shapedim[trial_i]);
|
||||
Hx.SetSize(dof[test_i], ts_vdim*tr_vdim*shapedim[trial_i]);
|
||||
Mult(allshapes[test_i], Hsub, Hx);
|
||||
Hx.SetSize(dof[test_i]*ts_vdim, tr_vdim*shapedim[trial_i]);
|
||||
const int h = dof[test_i]*ts_vdim;
|
||||
const int w = shapedim[trial_i];
|
||||
const int wout = dof[trial_i];
|
||||
for (int d=0; d<tr_vdim; d++)
|
||||
{
|
||||
Hxsub.UseExternalData(Hx.GetData() + d*(w*h), h, w);
|
||||
partelmat[trial_i].UseExternalData(elmat(test_i, trial_i)->GetData() + d*wout*h,
|
||||
h, wout);
|
||||
AddMultABt(Hxsub, allshapes[trial_i], partelmat[trial_i]);
|
||||
}
|
||||
}, std::make_index_sequence<sizeof...(modes)> {});
|
||||
}, std::make_index_sequence<sizeof...(modes)> {});
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Perform the local action of the NonlinearFormIntegrator resulting
|
||||
/// from a face integral term.
|
||||
template <ADEval... modes>
|
||||
void ADBlockNonlinearFormIntegrator<modes...>::AssembleFaceVector(
|
||||
const Array<const FiniteElement *>&el1,
|
||||
const Array<const FiniteElement *>&el2,
|
||||
FaceElementTransformations &Tr,
|
||||
const Array<const Vector *>&elfun,
|
||||
const Array<Vector *>&elvect)
|
||||
{
|
||||
MFEM_ABORT("ADBlockNonlinearFormIntegrator::AssembleFaceVector: "
|
||||
"This method is not implemented.");
|
||||
}
|
||||
|
||||
|
||||
/// @brief Assemble the local action of the gradient of the
|
||||
/// NonlinearFormIntegrator resulting from a face integral term.
|
||||
template <ADEval... modes>
|
||||
void ADBlockNonlinearFormIntegrator<modes...>::AssembleFaceGrad(
|
||||
const Array<const FiniteElement *>&el1,
|
||||
const Array<const FiniteElement *>&el2,
|
||||
FaceElementTransformations &Tr,
|
||||
const Array<const Vector *>&elfun,
|
||||
const Array2D<DenseMatrix *>&elmat)
|
||||
{
|
||||
MFEM_ABORT("ADBlockNonlinearFormIntegrator::AssembleFaceGrad: "
|
||||
"This method is not implemented.");
|
||||
}
|
||||
} // namespace mfem
|
||||
@@ -0,0 +1,187 @@
|
||||
/// Example 0: AD Function Example
|
||||
#include "mfem.hpp"
|
||||
#include "ad_native.hpp"
|
||||
|
||||
using namespace std;
|
||||
using namespace mfem;
|
||||
|
||||
void Print(DenseMatrix &H, bool last_linebreak=true);
|
||||
void Print(DenseTensor &H);
|
||||
|
||||
struct MyADFunction : public ADFunction
|
||||
{
|
||||
public:
|
||||
MyADFunction(int n_input): ADFunction(n_input) { }
|
||||
|
||||
AD_IMPL(T, V, M, x, return sin(x(0))*exp(x(1)) + pow(x(2), 3.0);)
|
||||
};
|
||||
|
||||
struct MyADVecFunction : public ADVectorFunction
|
||||
{
|
||||
public:
|
||||
MyADVecFunction()
|
||||
: ADVectorFunction(3, 2) { }
|
||||
|
||||
AD_VEC_IMPL(T, V, M, x, result,
|
||||
{
|
||||
result[0]=sin(x[0]*x[1]);
|
||||
result[1]=cos(x[0]*x[1]*x[2]);
|
||||
});
|
||||
};
|
||||
|
||||
void jacobian(const Vector &x, Vector &J)
|
||||
{
|
||||
J.SetSize(x.Size());
|
||||
J[0] = std::cos(x(0)) * std::exp(x(1));
|
||||
J[1] = std::sin(x(0)) * std::exp(x(1));
|
||||
J[2] = 3.0 * std::pow(x(2), 2.0);
|
||||
}
|
||||
|
||||
void hessian(const Vector &x, DenseMatrix &H)
|
||||
{
|
||||
// J[0] = cos(x(0)) * exp(x(1));
|
||||
H.SetSize(x.Size(), x.Size());
|
||||
H(0, 0) = -std::sin(x(0)) * std::exp(x(1));
|
||||
H(0, 1) = std::cos(x(0)) *std::exp(x(1));
|
||||
H(0, 2) = 0.0;
|
||||
|
||||
// J[1] = sin(x(0)) * exp(x(1));
|
||||
H(1, 0) = std::cos(x(0)) * std::exp(x(1));
|
||||
H(1, 1) = std::sin(x(0)) * std::exp(x(1));
|
||||
H(1, 2) = 0.0;
|
||||
|
||||
// J[2] = 3.0 * pow(x(2), 2.0);
|
||||
H(2, 0) = 0.0;
|
||||
H(2, 1) = 0.0;
|
||||
H(2, 2) = 6.0 * std::pow(x(2), 1.0);
|
||||
}
|
||||
|
||||
void jacobian(const Vector &x, DenseMatrix &J)
|
||||
{
|
||||
// result[0]=sin(x[0]*x[1]);
|
||||
// result[1]=cos(x[0]*x[1]*x[2]);)
|
||||
J.SetSize(2,3);
|
||||
J(0,0) = x(1) * std::cos(x(0) * x(1));
|
||||
J(0,1) = x(0) * std::cos(x(0) * x(1));
|
||||
J(0,2) = 0.0;
|
||||
J(1,0) = -x(1) * x(2) * std::sin(x(0) * x(1) * x(2));
|
||||
J(1,1) = -x(0) * x(2) * std::sin(x(0) * x(1) * x(2));
|
||||
J(1,2) = -x(0) * x(1) * std::sin(x(0) * x(1) * x(2));
|
||||
}
|
||||
void hessian(const Vector &X, DenseTensor &H)
|
||||
{
|
||||
real_t x(X(0)), y(X(1)), z(X(2));
|
||||
H.SetSize(3, 3, 2);
|
||||
H = 0.0;
|
||||
using std::sin;
|
||||
using std::cos;
|
||||
|
||||
// result[0]=sin(x[0]*x[1]);
|
||||
H(0,0,0) = -y*y*sin(x*y);
|
||||
H(0,1,0) = cos(x*y) - x*y*sin(x*y);
|
||||
H(1,0,0) = cos(x*y) - x*y*sin(x*y);
|
||||
H(1,1,0) = -x*x*sin(x*y);
|
||||
// result[1]=cos(x[0]*x[1]*x[2]);)
|
||||
H(0,0,1) = -y*y*z*z*cos(x*y*z);
|
||||
H(1,0,1) = -x*y*z*z*cos(x*y*z) - z*sin(x*y*z);
|
||||
H(2,0,1) = -x*y*y*z*cos(x*y*z) - y*sin(x*y*z);
|
||||
H(0,1,1) = -x*y*z*z*cos(x*y*z) - z*sin(x*y*z);
|
||||
H(1,1,1) = -x*x*z*z*cos(x*y*z);
|
||||
H(2,1,1) = -x*x*y*z*cos(x*y*z) - x*sin(x*y*z);
|
||||
H(0,2,1) = -x*y*y*z*cos(x*y*z) - y*sin(x*y*z);
|
||||
H(1,2,1) = -x*x*y*z*cos(x*y*z) - x*sin(x*y*z);
|
||||
H(2,2,1) = -x*x*y*y*cos(x*y*z);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
Vector x({0.5, 1.0, -1.0});
|
||||
|
||||
MyADFunction f(3);
|
||||
|
||||
Vector jac, jac_ref;
|
||||
f.Gradient(x, jac);
|
||||
jacobian(x, jac_ref);
|
||||
|
||||
DenseMatrix hess, hess_ref;
|
||||
f.Hessian(x, hess);
|
||||
hessian(x, hess_ref);
|
||||
|
||||
MyADVecFunction f2;
|
||||
|
||||
DenseMatrix jac2, jac2_ref;
|
||||
f2.Gradient(x, jac2);
|
||||
jacobian(x, jac2_ref);
|
||||
|
||||
DenseTensor hess2, hess2_ref;
|
||||
f2.Hessian(x, hess2);
|
||||
hessian(x, hess2_ref);
|
||||
|
||||
|
||||
out << "Value : " << f(x) << std::endl;
|
||||
|
||||
out << "Jacobian : ";
|
||||
jac.Print();
|
||||
out << "Reference : ";
|
||||
jac_ref.Print();
|
||||
jac -= jac_ref;
|
||||
|
||||
out << "Hessian : " << std::endl;
|
||||
Print(hess);
|
||||
out << "Reference: " << std::endl;
|
||||
Print(hess_ref);
|
||||
hess -= hess_ref;
|
||||
|
||||
out << std::endl;
|
||||
out << "Jacobian error: " << jac.DistanceTo(jac_ref) << std::endl;
|
||||
out << "Hessian error: " << hess.MaxMaxNorm() << std::endl;
|
||||
out << "-------------------------" << std::endl;
|
||||
|
||||
out << "Jacobian2 : " << std::endl;
|
||||
Print(jac2);
|
||||
out << "Reference : " << std::endl;
|
||||
Print(jac2_ref);
|
||||
jac2 -= jac2_ref;
|
||||
|
||||
out << "Hess2 : " << std::endl;
|
||||
Print(hess2);
|
||||
out << "Reference : " << std::endl;
|
||||
Print(hess2_ref);
|
||||
out << std::endl;
|
||||
out << "Jacobian2 error: " << jac2.MaxMaxNorm() << std::endl;
|
||||
for (int k=0; k<hess2.SizeK(); k++)
|
||||
{
|
||||
hess2(k) -= hess2_ref(k);
|
||||
out << "Hessian[" << k << "] error: " << hess2(k).MaxMaxNorm() << std::endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Print(DenseMatrix &H, bool last_linebreak)
|
||||
{
|
||||
for (int i = 0; i < H.Height(); i++)
|
||||
{
|
||||
for (int j = 0; j < H.Width(); j++)
|
||||
{
|
||||
cout << H(i, j) << " ";
|
||||
}
|
||||
cout << ";";
|
||||
if (i < H.Height() - 1 || last_linebreak)
|
||||
{
|
||||
cout << "\n";
|
||||
}
|
||||
}
|
||||
cout << std::flush;
|
||||
}
|
||||
|
||||
void Print(DenseTensor &H)
|
||||
{
|
||||
for (int k=0; k<H.SizeK(); k++)
|
||||
{
|
||||
out << "{ ";
|
||||
Print(H(k), false);
|
||||
out << " }\n";
|
||||
}
|
||||
cout << std::flush;
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/// Example 1: AD Diffusion
|
||||
#include "mfem.hpp"
|
||||
#include "logger.hpp"
|
||||
#include "ad_intg.hpp"
|
||||
|
||||
using namespace std;
|
||||
using namespace mfem;
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
// file name to be saved
|
||||
std::stringstream filename;
|
||||
filename << "ad-diffusion";
|
||||
|
||||
int order = 1;
|
||||
int ref_levels = 1;
|
||||
bool visualization = false;
|
||||
bool paraview = false;
|
||||
|
||||
OptionsParser args(argc, argv);
|
||||
args.AddOption(&order, "-o", "--order",
|
||||
"Finite element polynomial degree");
|
||||
args.AddOption(&ref_levels, "-r", "--ref", "Refinement levels");
|
||||
args.AddOption(&visualization, "-vis", "--visualization",
|
||||
"-no-vis", "--no-visualization",
|
||||
"Enable visualization, default is false");
|
||||
args.AddOption(¶view, "-pv", "--paraview",
|
||||
"-no-pv", "--no-paraview",
|
||||
"Enable Paraview Export. Default is false");
|
||||
args.ParseCheck();
|
||||
|
||||
Mesh mesh = Mesh::MakeCartesian2D(10, 10,
|
||||
Element::QUADRILATERAL);
|
||||
const int dim = mesh.Dimension();
|
||||
for (int i = 0; i < ref_levels; i++)
|
||||
{
|
||||
mesh.UniformRefinement();
|
||||
}
|
||||
FunctionCoefficient load_cf([](const Vector &x)
|
||||
{
|
||||
return 2*M_PI * M_PI * std::sin(M_PI * x(0)) * std::sin(M_PI * x(1));
|
||||
});
|
||||
|
||||
H1_FECollection fec(order, dim);
|
||||
FiniteElementSpace fes(&mesh, &fec);
|
||||
Array<int> ess_tdof_list;
|
||||
fes.GetBoundaryTrueDofs(ess_tdof_list);
|
||||
|
||||
DiffusionEnergy energy(dim);
|
||||
|
||||
NonlinearForm nlf(&fes);
|
||||
nlf.AddDomainIntegrator(new ADNonlinearFormIntegrator<ADEval::GRAD>(energy));
|
||||
nlf.SetEssentialTrueDofs(ess_tdof_list);
|
||||
LinearForm load(&fes);
|
||||
load.AddDomainIntegrator(new DomainLFIntegrator(load_cf));
|
||||
load.Assemble();
|
||||
load.SetSubVector(ess_tdof_list, 0.0);
|
||||
|
||||
GridFunction x(&fes);
|
||||
x = 0.0;
|
||||
SparseMatrix &op = static_cast<SparseMatrix&>(nlf.GetGradient(x));
|
||||
CGSolver lin_solver;
|
||||
GSSmoother prec;
|
||||
lin_solver.SetPreconditioner(prec);
|
||||
lin_solver.SetOperator(op);
|
||||
lin_solver.SetRelTol(1e-12);
|
||||
lin_solver.SetAbsTol(0.0);
|
||||
lin_solver.SetMaxIter(1e04);
|
||||
lin_solver.Mult(load, x);
|
||||
if (visualization)
|
||||
{
|
||||
GLVis glvis("localhost", 19916);
|
||||
glvis.Append(x, "x", "Rjc");
|
||||
}
|
||||
if (paraview)
|
||||
{
|
||||
std::stringstream pvloc;
|
||||
pvloc << "ParaView/" << filename.str();
|
||||
ParaViewDataCollection paraview_dc(pvloc.str(), &mesh);
|
||||
paraview_dc.SetLevelsOfDetail(order);
|
||||
paraview_dc.SetDataFormat(VTKFormat::BINARY);
|
||||
paraview_dc.SetHighOrderOutput(true);
|
||||
paraview_dc.RegisterField("solution", &x);
|
||||
paraview_dc.SetCycle(0);
|
||||
paraview_dc.SetTime(0.0);
|
||||
paraview_dc.Save();
|
||||
}
|
||||
|
||||
FunctionCoefficient exact_sol([](const Vector &x)
|
||||
{
|
||||
return std::sin(M_PI * x(0)) * std::sin(M_PI * x(1));
|
||||
});
|
||||
real_t err = x.ComputeL2Error(exact_sol);
|
||||
out << "Error: " << err << std::endl;
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/// Example 2: AD Minimal Surface
|
||||
#include "mfem.hpp"
|
||||
#include "logger.hpp"
|
||||
#include "ad_intg.hpp"
|
||||
|
||||
using namespace std;
|
||||
using namespace mfem;
|
||||
|
||||
struct MinimalSurfaceEnergy : public ADFunction
|
||||
{
|
||||
public:
|
||||
real_t eps=0.5; // regularization
|
||||
MinimalSurfaceEnergy(int dim): ADFunction(dim) {}
|
||||
AD_IMPL(T, V, M, gradu,
|
||||
{
|
||||
T h1_norm(gradu*gradu);
|
||||
// sqrt(1+ ||grad u||^2)
|
||||
// dJ/du = 0 -> minimal surface
|
||||
return sqrt(h1_norm + 1.0) + eps*h1_norm;
|
||||
});
|
||||
};
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
// file name to be saved
|
||||
std::stringstream filename;
|
||||
filename << "ad-minimalsurface";
|
||||
|
||||
int order = 1;
|
||||
int ref_levels = 3;
|
||||
bool visualization = false;
|
||||
bool paraview = false;
|
||||
|
||||
OptionsParser args(argc, argv);
|
||||
args.AddOption(&order, "-o", "--order",
|
||||
"Finite element polynomial degree");
|
||||
args.AddOption(&ref_levels, "-r", "--ref", "Refinement levels");
|
||||
args.AddOption(&visualization, "-vis", "--visualization",
|
||||
"-no-vis", "--no-visualization",
|
||||
"Enable visualization, default is false");
|
||||
args.AddOption(¶view, "-pv", "--paraview",
|
||||
"-no-pv", "--no-paraview",
|
||||
"Enable Paraview Export. Default is false");
|
||||
args.ParseCheck();
|
||||
|
||||
// Mesh mesh = rhs_fun_circle
|
||||
Mesh mesh = Mesh::MakeCartesian2D(10, 10,
|
||||
Element::QUADRILATERAL);
|
||||
const int dim = mesh.Dimension();
|
||||
for (int i = 0; i < ref_levels; i++)
|
||||
{
|
||||
mesh.UniformRefinement();
|
||||
}
|
||||
FunctionCoefficient bdry_cf([](const Vector &x)
|
||||
{
|
||||
real_t theta = std::atan2(x(1)-0.5, x(0)-0.5);
|
||||
real_t r = std::sqrt(std::pow(x(0)-0.5, 2.0) + std::pow(x(1)-0.5, 2.0));
|
||||
return r*std::cos(2*theta);
|
||||
});
|
||||
|
||||
H1_FECollection fec(order, dim);
|
||||
FiniteElementSpace fes(&mesh, &fec);
|
||||
Array<int> is_bdr_ess(mesh.bdr_attributes.Max());
|
||||
is_bdr_ess = 1;
|
||||
|
||||
MinimalSurfaceEnergy energy(dim);
|
||||
|
||||
NonlinearForm nlf(&fes);
|
||||
nlf.AddDomainIntegrator(new ADNonlinearFormIntegrator<ADEval::GRAD>
|
||||
(energy));
|
||||
nlf.SetEssentialBC(is_bdr_ess);
|
||||
|
||||
GridFunction x(&fes);
|
||||
x = 0.0;
|
||||
x.ProjectBdrCoefficient(bdry_cf, is_bdr_ess);
|
||||
CGSolver lin_solver;
|
||||
GSSmoother prec;
|
||||
lin_solver.SetPreconditioner(prec);
|
||||
lin_solver.SetRelTol(1e-12);
|
||||
lin_solver.SetAbsTol(0.0);
|
||||
lin_solver.SetMaxIter(1e04);
|
||||
NewtonSolver solver;
|
||||
solver.SetSolver(lin_solver);
|
||||
solver.SetOperator(nlf);
|
||||
solver.SetAbsTol(1e-10);
|
||||
solver.SetRelTol(1e-10);
|
||||
IterativeSolver::PrintLevel print_level;
|
||||
print_level.iterations = 1;
|
||||
solver.SetPrintLevel(print_level);
|
||||
solver.SetMaxIter(100);
|
||||
solver.iterative_mode = true;
|
||||
Vector dummy(0);
|
||||
std::unique_ptr<GLVis> glvis;
|
||||
if (visualization)
|
||||
{
|
||||
glvis = std::make_unique<GLVis>("localhost", 19916);
|
||||
glvis->Append(x, "x", "Rjc");
|
||||
}
|
||||
for (int i=0; i<30; i++)
|
||||
{
|
||||
solver.Mult(dummy, x);
|
||||
if (glvis) { glvis->Update(); }
|
||||
energy.eps *= 0.5;
|
||||
}
|
||||
if (paraview)
|
||||
{
|
||||
std::stringstream pvloc;
|
||||
pvloc << "ParaView/" << filename.str();
|
||||
ParaViewDataCollection paraview_dc(pvloc.str(), &mesh);
|
||||
paraview_dc.SetLevelsOfDetail(order);
|
||||
paraview_dc.SetDataFormat(VTKFormat::BINARY);
|
||||
paraview_dc.SetHighOrderOutput(true);
|
||||
paraview_dc.RegisterField("solution", &x);
|
||||
paraview_dc.SetCycle(0);
|
||||
paraview_dc.SetTime(0.0);
|
||||
paraview_dc.Save();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/// Example 3: AD Linear Elasticity with Vector FE
|
||||
#include "mfem.hpp"
|
||||
#include "logger.hpp"
|
||||
#include "ad_intg.hpp"
|
||||
|
||||
using namespace std;
|
||||
using namespace mfem;
|
||||
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
// file name to be saved
|
||||
std::stringstream filename;
|
||||
filename << "ad-elasticity";
|
||||
|
||||
int order = 1;
|
||||
int ref_levels = 3;
|
||||
bool visualization = false;
|
||||
bool paraview = false;
|
||||
|
||||
OptionsParser args(argc, argv);
|
||||
args.AddOption(&order, "-o", "--order",
|
||||
"Finite element polynomial degree");
|
||||
args.AddOption(&ref_levels, "-r", "--ref", "Refinement levels");
|
||||
args.AddOption(&visualization, "-vis", "--visualization",
|
||||
"-no-vis", "--no-visualization",
|
||||
"Enable visualization, default is false");
|
||||
args.AddOption(¶view, "-pv", "--paraview",
|
||||
"-no-pv", "--no-paraview",
|
||||
"Enable Paraview Export. Default is false");
|
||||
args.ParseCheck();
|
||||
|
||||
// Mesh mesh = rhs_fun_circle
|
||||
Mesh mesh = Mesh::MakeCartesian2D(10, 10,
|
||||
Element::QUADRILATERAL);
|
||||
const int dim = mesh.Dimension();
|
||||
for (int i = 0; i < ref_levels; i++)
|
||||
{
|
||||
mesh.UniformRefinement();
|
||||
}
|
||||
VectorFunctionCoefficient load_cf(dim, [dim](const Vector &x, Vector &y)
|
||||
{
|
||||
y.SetSize(dim);
|
||||
y = 1.0;
|
||||
});
|
||||
|
||||
H1_FECollection fec(order, dim);
|
||||
FiniteElementSpace fes(&mesh, &fec, dim);
|
||||
Array<int> is_bdr_ess(mesh.bdr_attributes.Max());
|
||||
is_bdr_ess = 0;
|
||||
is_bdr_ess[3] = 1;
|
||||
Array<int> ess_tdof_list;
|
||||
fes.GetEssentialTrueDofs(is_bdr_ess, ess_tdof_list);
|
||||
|
||||
real_t lambda(1.0), mu(1.0);
|
||||
LinearElasticityEnergy energy(dim, lambda, mu);
|
||||
|
||||
NonlinearForm nlf(&fes);
|
||||
nlf.AddDomainIntegrator(
|
||||
new ADNonlinearFormIntegrator<ADEval::GRAD | ADEval::VECTOR>(energy));
|
||||
nlf.SetEssentialBC(is_bdr_ess);
|
||||
LinearForm load(&fes);
|
||||
load.AddDomainIntegrator(new VectorDomainLFIntegrator(load_cf));
|
||||
load.Assemble();
|
||||
load.SetSubVector(ess_tdof_list, 0.0);
|
||||
|
||||
GridFunction x(&fes);
|
||||
x = 0.0;
|
||||
SparseMatrix &op = static_cast<SparseMatrix&>(nlf.GetGradient(x));
|
||||
CGSolver lin_solver;
|
||||
GSSmoother prec;
|
||||
lin_solver.SetPreconditioner(prec);
|
||||
lin_solver.SetOperator(op);
|
||||
lin_solver.SetRelTol(1e-12);
|
||||
lin_solver.SetAbsTol(0.0);
|
||||
lin_solver.SetMaxIter(1e04);
|
||||
lin_solver.Mult(load, x);
|
||||
|
||||
if (visualization)
|
||||
{
|
||||
GLVis glvis("localhost", 19916);
|
||||
glvis.Append(x, "x", "Rjc");
|
||||
}
|
||||
if (paraview)
|
||||
{
|
||||
std::stringstream pvloc;
|
||||
pvloc << "ParaView/" << filename.str();
|
||||
ParaViewDataCollection paraview_dc(pvloc.str(), &mesh);
|
||||
paraview_dc.SetLevelsOfDetail(order);
|
||||
paraview_dc.SetDataFormat(VTKFormat::BINARY);
|
||||
paraview_dc.SetHighOrderOutput(true);
|
||||
paraview_dc.RegisterField("solution", &x);
|
||||
paraview_dc.SetCycle(0);
|
||||
paraview_dc.SetTime(0.0);
|
||||
paraview_dc.Save();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
/// Example 4: AD Obstacle Problem with PG
|
||||
#include "mfem.hpp"
|
||||
#include "logger.hpp"
|
||||
#include "ad_intg.hpp"
|
||||
#include "tools.hpp"
|
||||
#include "pg.hpp"
|
||||
|
||||
using namespace std;
|
||||
using namespace mfem;
|
||||
|
||||
|
||||
struct ObstacleEnergy : public ADFunction
|
||||
{
|
||||
ObstacleEnergy(int dim) : ADFunction(dim+1) {}
|
||||
AD_IMPL(T, V, M, x,
|
||||
{
|
||||
T result = {};
|
||||
// First component is u. Others are grad u
|
||||
for (int i=1; i<x.Size(); i++)
|
||||
{
|
||||
result += x[i]*x[i];
|
||||
}
|
||||
return result*0.5;
|
||||
});
|
||||
};
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
Mpi::Init();
|
||||
int num_procs = Mpi::WorldSize();
|
||||
int myid = Mpi::WorldRank();
|
||||
Hypre::Init();
|
||||
MPI_Comm comm = MPI_COMM_WORLD;
|
||||
// file name to be saved
|
||||
std::stringstream filename;
|
||||
filename << "ad-obstacle";
|
||||
int rule_type = PGStepSizeRule::RuleType::CONSTANT;
|
||||
real_t max_alpha = 1e04;
|
||||
real_t alpha0 = 1.0;
|
||||
real_t ratio = 1.0;
|
||||
real_t ratio2 = 1.0;
|
||||
|
||||
int order = 2;
|
||||
int ref_levels = 3;
|
||||
bool visualization = false;
|
||||
bool paraview = false;
|
||||
|
||||
OptionsParser args(argc, argv);
|
||||
args.AddOption(&order, "-o", "--order",
|
||||
"Finite element polynomial degree");
|
||||
args.AddOption(&ref_levels, "-r", "--ref", "Refinement levels");
|
||||
args.AddOption(&rule_type, "-rule", "--rule",
|
||||
"Step size rule type: 0=CONSTANT, 1=POLY, 2=EXP, 3=DOUBLE_EXP");
|
||||
args.AddOption(&max_alpha, "-ma", "--max-alpha",
|
||||
"Maximum step size for PG method");
|
||||
args.AddOption(&alpha0, "-a0", "--alpha0",
|
||||
"Initial step size for PG method");
|
||||
args.AddOption(&ratio, "-ar", "--alpha-ratio",
|
||||
"Ratio for step size rule (POLY, EXP, DOUBLE_EXP)");
|
||||
args.AddOption(&ratio2, "-ar2", "--alpha-ratio2",
|
||||
"Second ratio for DOUBLE_EXP step size rule");
|
||||
args.AddOption(&visualization, "-vis", "--visualization",
|
||||
"-no-vis", "--no-visualization",
|
||||
"Enable visualization, default is false");
|
||||
args.AddOption(¶view, "-pv", "--paraview",
|
||||
"-no-pv", "--no-paraview",
|
||||
"Enable Paraview Export. Default is false");
|
||||
args.ParseCheck();
|
||||
if (myid != 0) { out.Disable(); }
|
||||
|
||||
PGStepSizeRule alpha_rule(rule_type, alpha0, max_alpha, ratio, ratio2);
|
||||
|
||||
// Mesh mesh = rhs_fun_circle
|
||||
Mesh ser_mesh = Mesh::MakeCartesian2D(2, 2,
|
||||
Element::QUADRILATERAL);
|
||||
const int dim = ser_mesh.Dimension();
|
||||
for (int i = 0; i < ref_levels; i++)
|
||||
{
|
||||
ser_mesh.UniformRefinement();
|
||||
}
|
||||
ParMesh mesh(comm, ser_mesh);
|
||||
|
||||
const int numBdrAttr = mesh.bdr_attributes.Max();
|
||||
Array<int> is_bdr_ess1(numBdrAttr);
|
||||
is_bdr_ess1 = 1;
|
||||
Array<int> is_bdr_ess2(numBdrAttr);
|
||||
is_bdr_ess2 = 0;
|
||||
Array<Array<int>*> is_bdr_ess{&is_bdr_ess1, &is_bdr_ess2};
|
||||
FunctionCoefficient load_cf([](const Vector &x)
|
||||
{
|
||||
return 2*M_PI * M_PI * std::sin(M_PI * x(0)) * std::sin(M_PI * x(1));
|
||||
});
|
||||
ObstacleEnergy obj_energy(dim);
|
||||
|
||||
H1_FECollection primal_fec(order+1, dim);
|
||||
L2_FECollection latent_fec(order-1, dim);
|
||||
ParFiniteElementSpace primal_fes(&mesh, &primal_fec);
|
||||
ParFiniteElementSpace latent_fes(&mesh, &latent_fec);
|
||||
QuadratureSpace visspace(&mesh, order+3);
|
||||
const IntegrationRule &ir = IntRules.Get(Geometry::Type::SQUARE, 3*order + 3);
|
||||
|
||||
Array<int> ess_tdof_list;
|
||||
primal_fes.GetEssentialTrueDofs(is_bdr_ess1, ess_tdof_list);
|
||||
|
||||
Array<int> offsets(3);
|
||||
offsets[0] = 0;
|
||||
offsets[1] = primal_fes.GetTrueVSize();
|
||||
offsets[2] = latent_fes.GetTrueVSize();
|
||||
offsets.PartialSum();
|
||||
BlockVector x_and_latent(offsets);
|
||||
|
||||
ParGridFunction x(&primal_fes), latent(&latent_fes);
|
||||
ParGridFunction latent_k(latent);
|
||||
|
||||
x = 0.0; x.ParallelAssemble(x_and_latent.GetBlock(0));
|
||||
latent = 0.0; latent.ParallelAssemble(x_and_latent.GetBlock(1));
|
||||
latent_k = 0.0; latent_k.SetTrueVector();
|
||||
|
||||
FermiDiracEntropy entropy(0.0, 0.5);
|
||||
|
||||
DifferentiableCoefficient entropy_cf(entropy);
|
||||
entropy_cf.AddInput(&latent);
|
||||
VectorCoefficient &u_cf = entropy_cf.Gradient();
|
||||
|
||||
real_t alpha;
|
||||
ADPGFunctional pg_functional(obj_energy, entropy, &alpha, latent_k);
|
||||
|
||||
ParGridFunction lambda(latent), lambda_prev(latent);
|
||||
lambda = 0.0;
|
||||
GridFunctionCoefficient lambda_prev_cf(&lambda_prev);
|
||||
|
||||
Array<ParFiniteElementSpace*> fespaces{&primal_fes, &latent_fes};
|
||||
ParBlockNonlinearForm bnlf(fespaces);
|
||||
constexpr ADEval u_mode = ADEval::VALUE | ADEval::GRAD;
|
||||
constexpr ADEval latent_mode = ADEval::VALUE;
|
||||
bnlf.AddDomainIntegrator(
|
||||
new ADBlockNonlinearFormIntegrator<u_mode, latent_mode>(
|
||||
pg_functional, &ir)
|
||||
);
|
||||
|
||||
BlockVector rhs(offsets);
|
||||
ParLinearForm b(&primal_fes);
|
||||
b.AddDomainIntegrator(new DomainLFIntegrator(load_cf));
|
||||
b.Assemble();
|
||||
b.ParallelAssemble(rhs.GetBlock(0));
|
||||
rhs.GetBlock(0).SetSubVector(ess_tdof_list, 0.0);
|
||||
rhs.GetBlock(1) = 0.0;
|
||||
|
||||
Array<Vector*> rhs_list{&rhs.GetBlock(0), &rhs.GetBlock(1)};
|
||||
bnlf.SetEssentialBC(is_bdr_ess, rhs_list);
|
||||
|
||||
MUMPSMonoSolver lin_solver(comm);
|
||||
NewtonSolver solver(comm);
|
||||
solver.SetSolver(lin_solver);
|
||||
solver.SetOperator(bnlf);
|
||||
IterativeSolver::PrintLevel print_level;
|
||||
solver.SetPrintLevel(print_level);
|
||||
solver.SetAbsTol(1e-09);
|
||||
solver.SetRelTol(0.0);
|
||||
solver.SetMaxIter(20);
|
||||
solver.iterative_mode = true;
|
||||
|
||||
std::unique_ptr<GLVis> glvis;
|
||||
if (visualization)
|
||||
{
|
||||
glvis = std::make_unique<GLVis>("localhost", 19916, 400, 350, 3);
|
||||
glvis->Append(x, "u", "Rjclmm");
|
||||
glvis->Append(u_cf, visspace, "U(psi)", "RjclQmm");
|
||||
glvis->Append(lambda, "lambda", "Rjclmm");
|
||||
}
|
||||
std::unique_ptr<ParaViewDataCollection> paraview_dc;
|
||||
if (paraview)
|
||||
{
|
||||
filename << "r" << ref_levels << "-o" << order;
|
||||
paraview_dc = std::make_unique<ParaViewDataCollection>(filename.str(), &mesh);
|
||||
paraview_dc->SetLevelsOfDetail(order);
|
||||
paraview_dc->SetDataFormat(VTKFormat::BINARY);
|
||||
paraview_dc->SetHighOrderOutput(true);
|
||||
paraview_dc->RegisterField("solution", &x);
|
||||
paraview_dc->SetCycle(0);
|
||||
paraview_dc->SetTime(0.0);
|
||||
paraview_dc->Save();
|
||||
}
|
||||
|
||||
|
||||
real_t lambda_diff = infinity();
|
||||
for (int i=0; i<100; i++)
|
||||
{
|
||||
alpha = alpha_rule.Get(i);
|
||||
out << "PG iteration " << i + 1 << " with alpha=" << alpha << std::endl;
|
||||
latent_k = latent;
|
||||
latent_k.SetTrueVector();
|
||||
|
||||
solver.Mult(rhs, x_and_latent);
|
||||
|
||||
if (!solver.GetConverged())
|
||||
{
|
||||
out << "Newton Failed to converge in " << solver.GetNumIterations() <<
|
||||
std::endl;
|
||||
}
|
||||
x.SetFromTrueDofs(x_and_latent.GetBlock(0));
|
||||
latent.SetFromTrueDofs(x_and_latent.GetBlock(1));
|
||||
|
||||
if (glvis) { glvis->Update(); }
|
||||
if (paraview_dc)
|
||||
{
|
||||
paraview_dc->SetCycle(i+1);
|
||||
paraview_dc->SetTime(i+1);
|
||||
paraview_dc->Save();
|
||||
}
|
||||
|
||||
subtract(latent, latent_k, lambda);
|
||||
lambda *= 1.0 / pg_functional.GetAlpha();
|
||||
|
||||
if ((lambda_diff = lambda.ComputeL1Error(lambda_prev_cf)) < 1e-8)
|
||||
{
|
||||
out << " The dual variable, (psi - psi_k)/alpha, converged" << std::endl;
|
||||
out << "PG Converged in " << i + 1
|
||||
<< " with final Lambda difference: " << lambda_diff << std::endl;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
out << " Newton converged in " << solver.GetNumIterations()
|
||||
<< " with residual " << solver.GetFinalNorm() << std::endl;
|
||||
out << " Lambda difference: " << lambda_diff << std::endl;
|
||||
}
|
||||
|
||||
lambda_prev = lambda;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
/// Example 5: AD Gradeint Obstacle Problem with PG
|
||||
#include "mfem.hpp"
|
||||
#include "logger.hpp"
|
||||
#include "ad_intg.hpp"
|
||||
#include "tools.hpp"
|
||||
#include "pg.hpp"
|
||||
|
||||
using namespace std;
|
||||
using namespace mfem;
|
||||
|
||||
|
||||
struct ObstacleEnergy : public ADFunction
|
||||
{
|
||||
ObstacleEnergy(int dim) : ADFunction(dim) {}
|
||||
AD_IMPL(T, V, M, x,
|
||||
{
|
||||
return x*x*0.5;
|
||||
});
|
||||
};
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
Mpi::Init();
|
||||
int num_procs = Mpi::WorldSize();
|
||||
int myid = Mpi::WorldRank();
|
||||
Hypre::Init();
|
||||
MPI_Comm comm = MPI_COMM_WORLD;
|
||||
// file name to be saved
|
||||
std::stringstream filename;
|
||||
filename << "ad-grad-obstacle";
|
||||
int rule_type = PGStepSizeRule::RuleType::CONSTANT;
|
||||
real_t max_alpha = 1e06;
|
||||
real_t alpha0 = 1.0;
|
||||
real_t ratio = 1.0;
|
||||
real_t ratio2 = 1.0;
|
||||
bool use_iterative = false;
|
||||
|
||||
int order = 2;
|
||||
int ref_levels = 3;
|
||||
bool visualization = false;
|
||||
bool paraview = false;
|
||||
|
||||
OptionsParser args(argc, argv);
|
||||
args.AddOption(&order, "-o", "--order",
|
||||
"Finite element polynomial degree");
|
||||
args.AddOption(&ref_levels, "-r", "--ref", "Refinement levels");
|
||||
args.AddOption(&rule_type, "-rule", "--rule",
|
||||
"Step size rule type: 0=CONSTANT, 1=POLY, 2=EXP, 3=DOUBLE_EXP");
|
||||
args.AddOption(&max_alpha, "-ma", "--max-alpha",
|
||||
"Maximum step size for PG method");
|
||||
args.AddOption(&alpha0, "-a0", "--alpha0",
|
||||
"Initial step size for PG method");
|
||||
args.AddOption(&ratio, "-ar", "--alpha-ratio",
|
||||
"Ratio for step size rule (POLY, EXP, DOUBLE_EXP)");
|
||||
args.AddOption(&ratio2, "-ar2", "--alpha-ratio2",
|
||||
"Second ratio for DOUBLE_EXP step size rule");
|
||||
args.AddOption(&visualization, "-vis", "--visualization",
|
||||
"-no-vis", "--no-visualization",
|
||||
"Enable visualization, default is false");
|
||||
args.AddOption(¶view, "-pv", "--paraview",
|
||||
"-no-pv", "--no-paraview",
|
||||
"Enable Paraview Export. Default is false");
|
||||
args.AddOption(&use_iterative, "-gmres", "--preconditioned-gmres",
|
||||
"-mumps", "--MUMPS",
|
||||
"Use preconditioned GMRES or MUMPS as linear solver. Default is MUMPS");
|
||||
args.ParseCheck();
|
||||
if (myid != 0) { out.Disable(); }
|
||||
MFEMInitializePetsc(NULL,NULL,"../src/pgpetsc",NULL);
|
||||
|
||||
PGStepSizeRule alpha_rule(rule_type, alpha0, max_alpha, ratio, ratio2);
|
||||
|
||||
// Mesh mesh = rhs_fun_circle
|
||||
Mesh ser_mesh = Mesh::MakeCartesian2D(2, 2,
|
||||
Element::QUADRILATERAL);
|
||||
const int dim = ser_mesh.Dimension();
|
||||
for (int i = 0; i < ref_levels; i++)
|
||||
{
|
||||
ser_mesh.UniformRefinement();
|
||||
}
|
||||
ParMesh mesh(comm, ser_mesh);
|
||||
|
||||
const int numBdrAttr = mesh.bdr_attributes.Max();
|
||||
Array<int> is_bdr_ess1(numBdrAttr);
|
||||
is_bdr_ess1 = 1;
|
||||
Array<int> is_bdr_ess2(numBdrAttr);
|
||||
is_bdr_ess2 = 0;
|
||||
Array<Array<int>*> is_bdr_ess{&is_bdr_ess1, &is_bdr_ess2};
|
||||
FunctionCoefficient load_cf([](const Vector &x)
|
||||
{
|
||||
return 2*M_PI * M_PI * std::sin(M_PI * x(0)) * std::sin(M_PI * x(1));
|
||||
});
|
||||
ObstacleEnergy obj_energy(dim);
|
||||
|
||||
H1_FECollection primal_fec(order, dim);
|
||||
H1_FECollection latent_fec(order-1, dim);
|
||||
ParFiniteElementSpace primal_fes(&mesh, &primal_fec);
|
||||
ParFiniteElementSpace latent_fes(&mesh, &latent_fec, dim);
|
||||
QuadratureSpace visspace(&mesh, order+3);
|
||||
const IntegrationRule &ir = IntRules.Get(Geometry::Type::SQUARE, 3*order + 3);
|
||||
|
||||
Array<int> ess_tdof_list;
|
||||
primal_fes.GetEssentialTrueDofs(is_bdr_ess1, ess_tdof_list);
|
||||
|
||||
Array<int> offsets(3);
|
||||
offsets[0] = 0;
|
||||
offsets[1] = primal_fes.GetTrueVSize();
|
||||
offsets[2] = latent_fes.GetTrueVSize();
|
||||
offsets.PartialSum();
|
||||
BlockVector x_and_latent(offsets);
|
||||
|
||||
ParGridFunction x(&primal_fes), latent(&latent_fes);
|
||||
ParGridFunction latent_k(latent);
|
||||
|
||||
x = 0.0; x.ParallelAssemble(x_and_latent.GetBlock(0));
|
||||
latent = 0.0; latent.ParallelAssemble(x_and_latent.GetBlock(1));
|
||||
latent_k = 0.0; latent_k.SetTrueVector();
|
||||
|
||||
FunctionCoefficient bound([](const Vector &x)
|
||||
{ return 0.1 + 0.2*x[0] + 0.4*x[1]; });
|
||||
HellingerEntropy entropy(dim, &bound);
|
||||
|
||||
DifferentiableCoefficient entropy_cf(entropy);
|
||||
entropy_cf.AddInput(&latent);
|
||||
VectorCoefficient &u_cf = entropy_cf.Gradient();
|
||||
|
||||
real_t alpha;
|
||||
ADPGFunctional pg_functional(obj_energy, entropy, &alpha, latent_k);
|
||||
|
||||
ParGridFunction lambda(latent), lambda_prev(latent);
|
||||
lambda = 0.0;
|
||||
VectorGridFunctionCoefficient lambda_prev_cf(&lambda_prev);
|
||||
|
||||
Array<ParFiniteElementSpace*> fespaces{&primal_fes, &latent_fes};
|
||||
ParBlockNonlinearForm bnlf(fespaces);
|
||||
constexpr ADEval u_mode = ADEval::GRAD;
|
||||
constexpr ADEval latent_mode = ADEval::VALUE | ADEval::VECTOR;
|
||||
bnlf.AddDomainIntegrator(
|
||||
new ADBlockNonlinearFormIntegrator<u_mode, latent_mode>(
|
||||
pg_functional, &ir)
|
||||
);
|
||||
|
||||
BlockVector rhs(offsets);
|
||||
ParLinearForm b(&primal_fes);
|
||||
b.AddDomainIntegrator(new DomainLFIntegrator(load_cf));
|
||||
b.Assemble();
|
||||
b.ParallelAssemble(rhs.GetBlock(0));
|
||||
rhs.GetBlock(0).SetSubVector(ess_tdof_list, 0.0);
|
||||
rhs.GetBlock(1) = 0.0;
|
||||
|
||||
Array<Vector*> rhs_list{&rhs.GetBlock(0), &rhs.GetBlock(1)};
|
||||
bnlf.SetEssentialBC(is_bdr_ess, rhs_list);
|
||||
|
||||
MUMPSMonoSolver lin_solver(comm);
|
||||
NewtonSolver solver(comm);
|
||||
solver.SetSolver(lin_solver);
|
||||
solver.SetOperator(bnlf);
|
||||
IterativeSolver::PrintLevel print_level;
|
||||
solver.SetPrintLevel(print_level);
|
||||
solver.SetAbsTol(1e-09);
|
||||
solver.SetRelTol(0.0);
|
||||
solver.SetMaxIter(20);
|
||||
solver.iterative_mode = true;
|
||||
|
||||
std::unique_ptr<GLVis> glvis;
|
||||
if (visualization)
|
||||
{
|
||||
glvis = std::make_unique<GLVis>("localhost", 19916, 400, 350, 3);
|
||||
glvis->Append(x, "u", "Rjclmm");
|
||||
glvis->Append(u_cf, visspace, "U(psi)", "RjclQmm");
|
||||
glvis->Append(lambda, "lambda", "Rjclmm");
|
||||
}
|
||||
std::unique_ptr<ParaViewDataCollection> paraview_dc;
|
||||
if (paraview)
|
||||
{
|
||||
filename << "r" << ref_levels << "-o" << order;
|
||||
paraview_dc = std::make_unique<ParaViewDataCollection>(filename.str(), &mesh);
|
||||
paraview_dc->SetLevelsOfDetail(order);
|
||||
paraview_dc->SetDataFormat(VTKFormat::BINARY);
|
||||
paraview_dc->SetHighOrderOutput(true);
|
||||
paraview_dc->RegisterField("solution", &x);
|
||||
paraview_dc->SetCycle(0);
|
||||
paraview_dc->SetTime(0.0);
|
||||
paraview_dc->Save();
|
||||
}
|
||||
|
||||
real_t lambda_diff = infinity();
|
||||
for (int i=0; i<100; i++)
|
||||
{
|
||||
alpha = alpha_rule.Get(i);
|
||||
out << "PG iteration " << i + 1 << " with alpha=" << alpha << std::endl;
|
||||
latent_k = latent;
|
||||
latent_k.SetTrueVector();
|
||||
|
||||
solver.Mult(rhs, x_and_latent);
|
||||
|
||||
if (!solver.GetConverged())
|
||||
{
|
||||
out << "Newton Failed to converge in " << solver.GetNumIterations() <<
|
||||
std::endl;
|
||||
}
|
||||
x.SetFromTrueDofs(x_and_latent.GetBlock(0));
|
||||
latent.SetFromTrueDofs(x_and_latent.GetBlock(1));
|
||||
|
||||
if (glvis) { glvis->Update(); }
|
||||
if (paraview_dc)
|
||||
{
|
||||
paraview_dc->SetCycle(i+1);
|
||||
paraview_dc->SetTime(i+1);
|
||||
paraview_dc->Save();
|
||||
}
|
||||
|
||||
subtract(latent, latent_k, lambda);
|
||||
lambda *= 1.0 / pg_functional.GetAlpha();
|
||||
|
||||
if ((lambda_diff = lambda.ComputeL1Error(lambda_prev_cf)) < 1e-8)
|
||||
{
|
||||
out << " The dual variable, (psi - psi_k)/alpha, converged" << std::endl;
|
||||
out << "PG Converged in " << i + 1
|
||||
<< " with final Lambda difference: " << lambda_diff << std::endl;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
out << " Newton converged in " << solver.GetNumIterations()
|
||||
<< " with residual " << solver.GetFinalNorm() << std::endl;
|
||||
out << " Lambda difference: " << lambda_diff << std::endl;
|
||||
}
|
||||
|
||||
lambda_prev = lambda;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
/// Example 6: Darcy
|
||||
#include "mfem.hpp"
|
||||
#include "logger.hpp"
|
||||
#include "ad_intg.hpp"
|
||||
#include "tools.hpp"
|
||||
|
||||
using namespace std;
|
||||
using namespace mfem;
|
||||
|
||||
|
||||
struct DarcyFunctional : public ADVectorFunction
|
||||
{
|
||||
int dim;
|
||||
// input: q (vector), divq (scalar), u (scalar) -> dim + 1 + 1
|
||||
// output: coefficient for w, divw, v -> dim + 1 + 1 (w, v are test functions)
|
||||
DarcyFunctional(int dim) : ADVectorFunction(dim + 1 + 1, dim + 1 + 1),
|
||||
dim(dim) {}
|
||||
// (q, w) - (div w, u) -> res[w] = q, res[divw] = -u
|
||||
// (div q, v) -> res[v] = div q
|
||||
AD_VEC_IMPL(T, V, M, q_divq_u, res,
|
||||
{
|
||||
res.SetSize(dim + 1 + 1);
|
||||
const V q(q_divq_u.GetData(), dim);
|
||||
const T divq = q_divq_u[dim];
|
||||
const T u = q_divq_u[dim+1];
|
||||
|
||||
V w_cf(res.GetData(), dim);
|
||||
T &divw_cf = res[dim];
|
||||
T &v_cf = res[dim+1];
|
||||
w_cf = q;
|
||||
divw_cf = -u;
|
||||
v_cf = divq;
|
||||
});
|
||||
};
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
Mpi::Init();
|
||||
int num_procs = Mpi::WorldSize();
|
||||
int myid = Mpi::WorldRank();
|
||||
Hypre::Init();
|
||||
MPI_Comm comm = MPI_COMM_WORLD;
|
||||
// file name to be saved
|
||||
std::stringstream filename;
|
||||
filename << "ad-darcy";
|
||||
|
||||
int order = 2;
|
||||
int ref_levels = 3;
|
||||
bool visualization = false;
|
||||
bool paraview = false;
|
||||
|
||||
OptionsParser args(argc, argv);
|
||||
args.AddOption(&order, "-o", "--order",
|
||||
"Finite element polynomial degree");
|
||||
args.AddOption(&ref_levels, "-r", "--ref", "Refinement levels");
|
||||
args.AddOption(&visualization, "-vis", "--visualization",
|
||||
"-no-vis", "--no-visualization",
|
||||
"Enable visualization, default is false");
|
||||
args.AddOption(¶view, "-pv", "--paraview",
|
||||
"-no-pv", "--no-paraview",
|
||||
"Enable Paraview Export. Default is false");
|
||||
args.ParseCheck();
|
||||
if (myid != 0) { out.Disable(); }
|
||||
|
||||
// Mesh mesh = rhs_fun_circle
|
||||
Mesh ser_mesh = Mesh::MakeCartesian2D(2, 2,
|
||||
Element::QUADRILATERAL);
|
||||
const int dim = ser_mesh.Dimension();
|
||||
for (int i = 0; i < ref_levels; i++)
|
||||
{
|
||||
ser_mesh.UniformRefinement();
|
||||
}
|
||||
ParMesh mesh(comm, ser_mesh);
|
||||
|
||||
FunctionCoefficient load_cf([](const Vector &x)
|
||||
{
|
||||
return 2*M_PI * M_PI * std::sin(M_PI * x(0)) * std::sin(M_PI * x(1));
|
||||
});
|
||||
DarcyFunctional darcy_functional(dim);
|
||||
|
||||
RT_FECollection flux_fec(order, dim);
|
||||
L2_FECollection potential_fec(order, dim);
|
||||
ParFiniteElementSpace flux_fes(&mesh, &flux_fec);
|
||||
ParFiniteElementSpace potential_fes(&mesh, &potential_fec);
|
||||
QuadratureSpace visspace(&mesh, order+3);
|
||||
const IntegrationRule &ir = IntRules.Get(Geometry::Type::SQUARE, 3*order + 3);
|
||||
|
||||
Array<int> offsets(3);
|
||||
offsets[0] = 0;
|
||||
offsets[1] = flux_fes.GetTrueVSize();
|
||||
offsets[2] = potential_fes.GetTrueVSize();
|
||||
offsets.PartialSum();
|
||||
BlockVector flux_and_potential(offsets);
|
||||
BlockVector rhs(offsets);
|
||||
|
||||
ParGridFunction flux(&flux_fes), potential(&potential_fes);
|
||||
|
||||
flux = 0.0; flux.GetTrueDofs(flux_and_potential.GetBlock(0));
|
||||
potential = 0.0; potential.GetTrueDofs(flux_and_potential.GetBlock(1));
|
||||
|
||||
Array<ParFiniteElementSpace*> fespaces{&flux_fes, &potential_fes};
|
||||
ParBlockNonlinearForm bnlf(fespaces);
|
||||
constexpr ADEval flux_mode = ADEval::VECFE | ADEval::VALUE | ADEval::DIV;
|
||||
constexpr ADEval potential_mode = ADEval::VALUE;
|
||||
bnlf.AddDomainIntegrator(
|
||||
new ADBlockNonlinearFormIntegrator<flux_mode, potential_mode>
|
||||
(darcy_functional, &ir)
|
||||
);
|
||||
|
||||
ParLinearForm b(&potential_fes);
|
||||
b.AddDomainIntegrator(new DomainLFIntegrator(load_cf));
|
||||
b.Assemble();
|
||||
b.ParallelAssemble(rhs.GetBlock(1));
|
||||
rhs.GetBlock(0) = 0.0;
|
||||
|
||||
GMRESSolver lin_solver(comm);
|
||||
lin_solver.SetRelTol(1e-08);
|
||||
lin_solver.SetAbsTol(0.0);
|
||||
lin_solver.SetMaxIter(1e04);
|
||||
lin_solver.SetKDim(100);
|
||||
|
||||
BlockOperator &darcy_op = bnlf.GetGradient(flux_and_potential);
|
||||
Vector Md(flux_fes.GetTrueVSize());
|
||||
HypreParMatrix &M = static_cast<HypreParMatrix&>(darcy_op.GetBlock(0,0));
|
||||
HypreParMatrix &B = static_cast<HypreParMatrix&>(darcy_op.GetBlock(1,0));
|
||||
M.GetDiag(Md);
|
||||
HypreParMatrix invMBt(static_cast<HypreParMatrix&>(darcy_op.GetBlock(0,1)));
|
||||
invMBt.InvScaleRows(Md);
|
||||
std::unique_ptr<HypreParMatrix> S(ParMult(&B, &invMBt));
|
||||
BlockDiagonalPreconditioner prec(offsets);
|
||||
HypreDiagScale invM(M);
|
||||
HypreBoomerAMG invS(*S);
|
||||
invS.SetPrintLevel(0);
|
||||
invM.iterative_mode = false;
|
||||
invS.iterative_mode = false;
|
||||
invS.SetMaxIter(1);
|
||||
prec.SetDiagonalBlock(0, &invM);
|
||||
prec.SetDiagonalBlock(1, &invS);
|
||||
prec.owns_blocks = false;
|
||||
|
||||
lin_solver.SetPreconditioner(prec);
|
||||
lin_solver.SetOperator(darcy_op);
|
||||
lin_solver.Mult(rhs, flux_and_potential);
|
||||
flux.SetFromTrueDofs(flux_and_potential.GetBlock(0));
|
||||
potential.SetFromTrueDofs(flux_and_potential.GetBlock(1));
|
||||
|
||||
if (visualization)
|
||||
{
|
||||
GLVis glvis("localhost", 19916, 400, 350, 3);
|
||||
glvis.Append(flux, "flux", "RjclQmm");
|
||||
glvis.Append(potential, "potential", "Rjclmm");
|
||||
}
|
||||
if (paraview)
|
||||
{
|
||||
std::stringstream pvloc;
|
||||
pvloc << "ParaView/" << filename.str();
|
||||
ParaViewDataCollection paraview_dc(pvloc.str(), &mesh);
|
||||
paraview_dc.SetLevelsOfDetail(order);
|
||||
paraview_dc.SetDataFormat(VTKFormat::BINARY);
|
||||
paraview_dc.SetHighOrderOutput(true);
|
||||
paraview_dc.RegisterField("flux", &flux);
|
||||
paraview_dc.RegisterField("potential", &potential);
|
||||
paraview_dc.SetCycle(0);
|
||||
paraview_dc.SetTime(0.0);
|
||||
paraview_dc.Save();
|
||||
}
|
||||
|
||||
FunctionCoefficient exact_potential([](const Vector &x)
|
||||
{
|
||||
return std::sin(M_PI * x(0)) * std::sin(M_PI * x(1));
|
||||
});
|
||||
VectorFunctionCoefficient exact_flux(dim, [](const Vector &x, Vector &q)
|
||||
{
|
||||
// flux = - grad u
|
||||
q.SetSize(x.Size());
|
||||
q[0] = -M_PI*std::cos(M_PI*x[0])*std::sin(M_PI*x[1]);
|
||||
q[1] = -M_PI*std::sin(M_PI*x[0])*std::cos(M_PI*x[1]);
|
||||
});
|
||||
out << "L2 Error in Potential: "
|
||||
<< potential.ComputeL2Error(exact_potential) << std::endl;
|
||||
out << "L2 Error in Flux: "
|
||||
<< flux.ComputeL2Error(exact_flux) << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
/// Templated AD (block) nonlinear form integrators definitions
|
||||
#pragma once
|
||||
#include "mfem.hpp"
|
||||
#include "ad_native.hpp"
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
// A helper struct to pass the loop index as a template parameter
|
||||
template<std::size_t N>
|
||||
struct __loop_index
|
||||
{
|
||||
static const constexpr size_t value = N;
|
||||
constexpr operator std::size_t() const { return N; }
|
||||
};
|
||||
// loop over indeces at compile time
|
||||
template <class F, std::size_t... Is>
|
||||
void _constexpr_for(F func, std::index_sequence<Is...>)
|
||||
{
|
||||
(func(__loop_index<Is> {}), ...);
|
||||
}
|
||||
// @brief ADQuadEvalMode is an enumeration for the evaluation modes of the ADEnergy class.
|
||||
// For example, if you want to evaluate the value and gradient of the function, you can use
|
||||
// constexpr auto mode = ADEval::VALUE | ADEval::GRAD;
|
||||
enum class ADEval
|
||||
{
|
||||
QVALUE = 1 << 0, // u(T, ip) (quadrature value)
|
||||
VALUE = 1 << 1, // u(T, ip)
|
||||
GRAD = 1 << 2, // grad u(T, ip)
|
||||
DIV = 1 << 3, // div u(T, ip) (not yet implemented)
|
||||
CURL = 1 << 4, // curl u(T, ip) (not yet implemented)
|
||||
Hessian = 1 << 5, // D^2 u(T, ip) (not yet implemented)
|
||||
|
||||
VECTOR = 1 << 6, // vector-valued scalar FE
|
||||
VECFE = 1 << 7, // vector-valued vector FE (not yet implemented)
|
||||
NUMOPT = 1 << 8, // number of options. If change options, change this value to last
|
||||
};
|
||||
|
||||
constexpr ADEval operator|(ADEval a, ADEval b)
|
||||
{
|
||||
return static_cast<ADEval>(static_cast<int>(a) | static_cast<int>(b));
|
||||
}
|
||||
constexpr ADEval operator&(ADEval a, ADEval b)
|
||||
{
|
||||
return static_cast<ADEval>(static_cast<int>(a) & static_cast<int>(b));
|
||||
}
|
||||
inline constexpr ADEval operator~(ADEval mode)
|
||||
{
|
||||
return static_cast<ADEval>(~static_cast<int>(mode));
|
||||
}
|
||||
inline constexpr bool hasFlag(ADEval mode, ADEval flag)
|
||||
{
|
||||
return (mode & flag) == flag;
|
||||
}
|
||||
|
||||
template <ADEval mode>
|
||||
constexpr bool isValidADEval()
|
||||
{
|
||||
constexpr auto INVALID = ADEval::Hessian;
|
||||
if constexpr (static_cast<int>(mode & INVALID) != 0) { return false; }
|
||||
if constexpr (hasFlag(mode, ADEval::QVALUE))
|
||||
{
|
||||
// QVALUE cannot be combined with other modes except VECTOR
|
||||
return static_cast<int>(mode & (~(ADEval::QVALUE | ADEval::VECTOR))) == 0;
|
||||
}
|
||||
if constexpr (hasFlag(mode, ADEval::VECFE))
|
||||
{
|
||||
return !hasFlag(mode,
|
||||
ADEval::VECTOR); // VECTOR is only for vector-valued scalar FE
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <ADEval... modes>
|
||||
class ADBlockNonlinearFormIntegrator;
|
||||
|
||||
template <ADEval mode>
|
||||
class ADNonlinearFormIntegrator : public NonlinearFormIntegrator
|
||||
{
|
||||
static_assert(isValidADEval<mode>(),
|
||||
"ADNonlinearFormIntegrator: Invalid ADEval mode");
|
||||
protected:
|
||||
ADFunction &f;
|
||||
|
||||
private:
|
||||
Vector x, jac;
|
||||
DenseMatrix H, Hx;
|
||||
|
||||
// only if ADEvalInput::VECTOR. Each column corresponds to a vector component
|
||||
DenseMatrix xmat, jacMat, Hs, Hxsub;
|
||||
DenseMatrix elfun_matview, elvectmat, partelmat;
|
||||
|
||||
DenseMatrix allshapes; // all shapes, [?shape, ?dshape]
|
||||
Vector shape, shape1, shape2;
|
||||
DenseMatrix vshape, vshape1, vshape2;
|
||||
DenseMatrix gshape, gshape1, gshape2;
|
||||
Vector divshape, divshape1, divshape2;
|
||||
DenseMatrix curlshape, curlshape1, curlshape2;
|
||||
Vector nor;
|
||||
// DenseMatrix d2shape, d2shape1, d2shape2; // for hessian. Not implemented yet.
|
||||
public:
|
||||
ADNonlinearFormIntegrator(ADFunction &f, IntegrationRule *ir = nullptr)
|
||||
: NonlinearFormIntegrator(ir), f(f) {}
|
||||
|
||||
const IntegrationRule* GetDefaultIntegrationRule(
|
||||
const FiniteElement& trial_fe, const FiniteElement& test_fe,
|
||||
const ElementTransformation& trans) const override
|
||||
{
|
||||
int order = std::max(trial_fe.GetOrder(), test_fe.GetOrder());
|
||||
return &IntRules.Get(trans.GetGeometryType(), order*2 + 2);
|
||||
}
|
||||
|
||||
/// Compute the local energy
|
||||
real_t GetElementEnergy(const FiniteElement &el,
|
||||
ElementTransformation &Tr,
|
||||
const Vector &elfun) override;
|
||||
|
||||
/// Perform the local action of the NonlinearFormIntegrator
|
||||
void AssembleElementVector(const FiniteElement &el,
|
||||
ElementTransformation &Tr,
|
||||
const Vector &elfun, Vector &elvect) override;
|
||||
|
||||
/// Assemble the local gradient matrix
|
||||
void AssembleElementGrad(const FiniteElement &el,
|
||||
ElementTransformation &Tr,
|
||||
const Vector &elfun, DenseMatrix &elmat) override;
|
||||
|
||||
/// @brief Perform the local action of the NonlinearFormIntegrator resulting
|
||||
/// from a face integral term.
|
||||
void AssembleFaceVector(const FiniteElement &el1,
|
||||
const FiniteElement &el2,
|
||||
FaceElementTransformations &Tr,
|
||||
const Vector &elfun, Vector &elvect) override;
|
||||
|
||||
|
||||
/// @brief Assemble the local action of the gradient of the
|
||||
/// NonlinearFormIntegrator resulting from a face integral term.
|
||||
void AssembleFaceGrad(const FiniteElement &el1,
|
||||
const FiniteElement &el2,
|
||||
FaceElementTransformations &Tr,
|
||||
const Vector &elfun, DenseMatrix &elmat) override;
|
||||
|
||||
|
||||
protected:
|
||||
|
||||
// Initialize shapes to [?value_shapes, ?grad_shapes]
|
||||
// and make value_shapes and grad_shapes reference to
|
||||
// allshapes.
|
||||
inline int InitInputShapes(const FiniteElement &el,
|
||||
ElementTransformation &Tr,
|
||||
DenseMatrix &shapes);
|
||||
|
||||
// Calculate parameter, shape, dshape at the given integration point
|
||||
inline void CalcInputShapes(const FiniteElement &el,
|
||||
ElementTransformation &Tr,
|
||||
const IntegrationPoint &ip,
|
||||
DenseMatrix &shapes);
|
||||
template <ADEval... modes>
|
||||
friend class ADBlockNonlinearFormIntegrator;
|
||||
private:
|
||||
};
|
||||
|
||||
template <ADEval... modes>
|
||||
class ADBlockNonlinearFormIntegrator : public BlockNonlinearFormIntegrator
|
||||
{
|
||||
public:
|
||||
const IntegrationRule *IntRule = nullptr;
|
||||
|
||||
protected:
|
||||
constexpr static size_t numSpaces = sizeof...(modes);
|
||||
static constexpr std::array<ADEval, sizeof...(modes)> modes_arr = {modes...};
|
||||
ADFunction &f;
|
||||
const IntegrationRule* GetIntegrationRule(
|
||||
const FiniteElement& trial_fe, const FiniteElement& test_fe,
|
||||
const ElementTransformation& trans) const;
|
||||
|
||||
/** @brief Returns an integration rule based on the arguments and
|
||||
internal state. (Version for identical trial_fe and test_fe)
|
||||
|
||||
@see GetIntegrationRule(const FiniteElement*, const FiniteElement*,
|
||||
const ElementTransformation*)
|
||||
*/
|
||||
const IntegrationRule* GetIntegrationRule(
|
||||
const FiniteElement& el,
|
||||
const ElementTransformation& trans) const;
|
||||
|
||||
private:
|
||||
Array<int> vdim;
|
||||
Vector x, jac;
|
||||
std::vector<Vector> xvar, jacVar;
|
||||
DenseMatrix H;
|
||||
DenseMatrix Hsub;
|
||||
DenseMatrix Hx;
|
||||
DenseMatrix Hxsub;
|
||||
|
||||
// only if ADEvalInput::VECTOR. Each column corresponds to a vector component
|
||||
std::vector<DenseMatrix> xmat, jacVarMat, Hs;
|
||||
std::vector<DenseMatrix> elfun_matview, elvectmat, partelmat;
|
||||
|
||||
std::vector<DenseMatrix> allshapes; // all shapes, [?shape, ?dshape]
|
||||
std::vector<Vector> shape, shape1, shape2;
|
||||
std::vector<DenseMatrix> vshape, vshape1, vshape2;
|
||||
std::vector<DenseMatrix> gshape, gshape1, gshape2;
|
||||
std::vector<Vector> divshape, divshape1, divshape2;
|
||||
std::vector<DenseMatrix> curlshape, curlgshape1, curlgshape2;
|
||||
Vector nor;
|
||||
// DenseMatrix d2shape, d2shape1, d2shape2; // for hessian. Not implemented yet.
|
||||
public:
|
||||
ADBlockNonlinearFormIntegrator(ADFunction &f,
|
||||
const IntegrationRule *ir = nullptr)
|
||||
: IntRule(ir), f(f), vdim(numSpaces)
|
||||
, allshapes(numSpaces)
|
||||
, xvar(numSpaces), jacVar(numSpaces)
|
||||
, Hx(numSpaces)
|
||||
, xmat(numSpaces), jacVarMat(numSpaces)
|
||||
, Hs(numSpaces), Hxsub(numSpaces)
|
||||
, elfun_matview(numSpaces), elvectmat(numSpaces)
|
||||
, partelmat(numSpaces)
|
||||
, shape(numSpaces), shape1(numSpaces), shape2(numSpaces)
|
||||
, vshape(numSpaces), vshape1(numSpaces), vshape2(numSpaces)
|
||||
, gshape(numSpaces), gshape1(numSpaces), gshape2(numSpaces)
|
||||
, divshape(numSpaces), divshape1(numSpaces), divshape2(numSpaces)
|
||||
, curlshape(numSpaces), curlgshape1(numSpaces), curlgshape2(numSpaces)
|
||||
{ vdim = 1; }
|
||||
|
||||
ADBlockNonlinearFormIntegrator(ADFunction &f, std::initializer_list<int> vdim,
|
||||
const IntegrationRule *ir = nullptr)
|
||||
: ADBlockNonlinearFormIntegrator(f, ir), vdim(vdim)
|
||||
{}
|
||||
|
||||
virtual void SetIntRule(const IntegrationRule *ir)
|
||||
{ IntRule = ir; }
|
||||
|
||||
/** @brief Prescribe a fixed IntegrationRule to use. Sets the NURBS patch
|
||||
integration rule to null.
|
||||
|
||||
@see SetIntRule(const IntegrationRule*)
|
||||
*/
|
||||
void SetIntegrationRule(const IntegrationRule &ir) { SetIntRule(&ir); }
|
||||
|
||||
/** @brief Directly return the IntRule pointer (possibly null) without
|
||||
checking for NURBS patch rules or falling back on a default. */
|
||||
const IntegrationRule *GetIntRule() const { return IntRule; }
|
||||
|
||||
/** @brief Equivalent to GetIntRule, but retained for backward
|
||||
compatibility with applications. */
|
||||
const IntegrationRule *GetIntegrationRule() const { return GetIntRule(); }
|
||||
|
||||
|
||||
/// Compute the local energy
|
||||
real_t GetElementEnergy(const Array<const FiniteElement *> &el,
|
||||
ElementTransformation &Tr,
|
||||
const Array<const Vector*> &elfun) override;
|
||||
|
||||
/// Perform the local action of the NonlinearFormIntegrator
|
||||
void AssembleElementVector(const Array<const FiniteElement *>&el,
|
||||
ElementTransformation &Tr,
|
||||
const Array<const Vector *>&elfun,
|
||||
const Array<Vector *>&elvect) override;
|
||||
|
||||
/// Assemble the local gradient matrix
|
||||
void AssembleElementGrad(const Array<const FiniteElement *>&el,
|
||||
ElementTransformation &Tr,
|
||||
const Array<const Vector *>&elfun,
|
||||
const Array2D<DenseMatrix *>&elmat) override;
|
||||
|
||||
/// @brief Perform the local action of the NonlinearFormIntegrator resulting
|
||||
/// from a face integral term.
|
||||
void AssembleFaceVector(const Array<const FiniteElement *>&el1,
|
||||
const Array<const FiniteElement *>&el2,
|
||||
FaceElementTransformations &Tr,
|
||||
const Array<const Vector *>&elfun,
|
||||
const Array<Vector *>&elvect) override;
|
||||
|
||||
|
||||
/// @brief Assemble the local action of the gradient of the
|
||||
/// NonlinearFormIntegrator resulting from a face integral term.
|
||||
void AssembleFaceGrad(const Array<const FiniteElement *>&el1,
|
||||
const Array<const FiniteElement *>&el2,
|
||||
FaceElementTransformations &Tr,
|
||||
const Array<const Vector *>&elfun,
|
||||
const Array2D<DenseMatrix *>&elmat) override;
|
||||
|
||||
|
||||
protected:
|
||||
|
||||
const IntegrationRule* GetIntegrationRule(
|
||||
const Array<const FiniteElement *>& trial_fe,
|
||||
const Array<const FiniteElement *>& test_fe,
|
||||
const ElementTransformation& trans) const
|
||||
{
|
||||
if (IntRule) { return IntRule; }
|
||||
return GetDefaultIntegrationRule(trial_fe, test_fe, trans);
|
||||
}
|
||||
|
||||
const IntegrationRule* GetIntegrationRule(
|
||||
const Array<const FiniteElement *>& el,
|
||||
const ElementTransformation& trans) const
|
||||
{
|
||||
if (IntRule) { return IntRule; }
|
||||
return GetDefaultIntegrationRule(el, el, trans);
|
||||
}
|
||||
|
||||
virtual const IntegrationRule* GetDefaultIntegrationRule(
|
||||
const Array<const FiniteElement *>& trial_fe,
|
||||
const Array<const FiniteElement *>& test_fe,
|
||||
const ElementTransformation& trans) const
|
||||
{
|
||||
int order = 0;
|
||||
for (int i=0; i<trial_fe.Size(); i++)
|
||||
{
|
||||
order = std::max(order, trial_fe[i]->GetOrder());
|
||||
}
|
||||
for (int i=0; i<test_fe.Size(); i++)
|
||||
{
|
||||
order = std::max(order, test_fe[i]->GetOrder());
|
||||
}
|
||||
return &IntRules.Get(trans.GetGeometryType(), order*2 + 2);
|
||||
}
|
||||
|
||||
std::array<int, sizeof...(modes)> InitInputShapes(
|
||||
const Array<const FiniteElement *>& el,
|
||||
ElementTransformation &Tr,
|
||||
std::vector<DenseMatrix> &shapes);
|
||||
|
||||
void CalcInputShapes(
|
||||
const Array<const FiniteElement *>& el,
|
||||
ElementTransformation &Tr,
|
||||
const IntegrationPoint &ip,
|
||||
std::vector<DenseMatrix> &allshapes);
|
||||
|
||||
private:
|
||||
};
|
||||
} // namespace mfem
|
||||
#include "_ad_intg.hpp"
|
||||
@@ -0,0 +1,327 @@
|
||||
#include "ad_native.hpp"
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
int Evaluator::GetSize(const param_t ¶m)
|
||||
{
|
||||
return std::visit([](auto arg)
|
||||
{
|
||||
using T = std::decay_t<decltype(arg)>;
|
||||
if constexpr (std::is_same_v<T, real_t>)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
if constexpr (std::is_same_v<T, Vector>)
|
||||
{
|
||||
return arg.Size();
|
||||
}
|
||||
if constexpr (std::is_same_v<T, DenseMatrix>)
|
||||
{
|
||||
return arg.TotalSize();
|
||||
}
|
||||
if constexpr (std::is_same_v<T, const real_t*>)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
if constexpr (std::is_same_v<T, const Vector*>)
|
||||
{
|
||||
return arg->Size();
|
||||
}
|
||||
if constexpr (std::is_same_v<T, const DenseMatrix*>)
|
||||
{
|
||||
return arg->Height()*arg->Width();
|
||||
}
|
||||
if constexpr (std::is_same_v<T, Coefficient*>)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
if constexpr (std::is_same_v<T, VectorCoefficient*>)
|
||||
{
|
||||
return arg->GetVDim();
|
||||
}
|
||||
if constexpr (std::is_same_v<T, MatrixCoefficient*>)
|
||||
{
|
||||
return arg->GetHeight() * arg->GetWidth();
|
||||
}
|
||||
if constexpr (std::is_same_v<T, const GridFunction*>)
|
||||
{
|
||||
return arg->FESpace()->GetVDim();
|
||||
}
|
||||
if constexpr (std::is_same_v<T, const QuadratureFunction*>)
|
||||
{
|
||||
return arg->GetVDim();
|
||||
}
|
||||
MFEM_ABORT("Evaluator: Unsupported parameter type");
|
||||
return 0;
|
||||
}, param);
|
||||
}
|
||||
|
||||
Evaluator::~Evaluator()
|
||||
{
|
||||
for (int i=0; i<params.size(); i++)
|
||||
{
|
||||
if (owns[i])
|
||||
{
|
||||
std::visit([](auto &arg)
|
||||
{
|
||||
using T = std::decay_t<decltype(arg)>;
|
||||
if constexpr (std::is_pointer_v<T>)
|
||||
{
|
||||
delete arg;
|
||||
}
|
||||
}, params[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
int Evaluator::Add(param_t param, bool eval_owns)
|
||||
{
|
||||
int idx = params.size();
|
||||
params.push_back(param);
|
||||
offsets.Append(offsets.Last() + GetSize(param));
|
||||
val.Update(offsets);
|
||||
owns.Append(eval_owns);
|
||||
std::visit([&](auto arg)
|
||||
{
|
||||
using T = std::decay_t<decltype(arg)>;
|
||||
if constexpr (std::is_same_v<T, real_t>)
|
||||
{
|
||||
MFEM_VERIFY(eval_owns==false,
|
||||
"Evaluator::Add: real_t parameter cannot own the value");
|
||||
val.GetBlock(idx) = arg;
|
||||
}
|
||||
if constexpr (std::is_same_v<T, Vector>)
|
||||
{
|
||||
MFEM_VERIFY(eval_owns==false,
|
||||
"Evaluator::Add: real_t parameter cannot own the value");
|
||||
val.GetBlock(idx) = arg;
|
||||
}
|
||||
if constexpr (std::is_same_v<T, DenseMatrix>)
|
||||
{
|
||||
MFEM_VERIFY(eval_owns==false,
|
||||
"Evaluator::Add: real_t parameter cannot own the value");
|
||||
Vector v(arg.GetData(), arg.TotalSize());
|
||||
val.GetBlock(idx) = v;
|
||||
}
|
||||
}, param);
|
||||
return idx;
|
||||
}
|
||||
void Evaluator::Replace(size_t i, param_t param)
|
||||
{
|
||||
MFEM_VERIFY(i < params.size(),
|
||||
"Evaluator::Set: index out of range");
|
||||
params[i] = param;
|
||||
int size = GetSize(param);
|
||||
MFEM_VERIFY(size == offsets[i+1] - offsets[i],
|
||||
"Evaluator::Set: size mismatch for parameter at index " << i
|
||||
<< ": expected " << (offsets[i+1] - offsets[i]) << ", got " << size);
|
||||
}
|
||||
|
||||
const Vector& Evaluator::Eval(int i, ElementTransformation &Tr,
|
||||
const IntegrationPoint &ip) const
|
||||
{
|
||||
std::visit([&](auto arg)
|
||||
{
|
||||
Vector &v = this->val.GetBlock(i);
|
||||
using T = std::decay_t<decltype(arg)>;
|
||||
if constexpr (std::is_same_v<T, real_t> ||
|
||||
std::is_same_v<T, Vector> ||
|
||||
std::is_same_v<T, DenseMatrix>)
|
||||
{
|
||||
// Already stored, do nothing
|
||||
return;
|
||||
}
|
||||
if constexpr (std::is_same_v<T, const real_t*>)
|
||||
{
|
||||
v = *arg;
|
||||
return;
|
||||
}
|
||||
if constexpr (std::is_same_v<T, const Vector*>)
|
||||
{
|
||||
v = *arg;
|
||||
return;
|
||||
}
|
||||
if constexpr (std::is_same_v<T, const DenseMatrix*>)
|
||||
{
|
||||
DenseMatrix m(v.GetData(), arg->Height(), arg->Width());
|
||||
m = *arg;
|
||||
return;
|
||||
}
|
||||
if constexpr (std::is_same_v<T, Coefficient*>)
|
||||
{
|
||||
v(0) = arg->Eval(Tr, ip);
|
||||
return;
|
||||
}
|
||||
if constexpr (std::is_same_v<T, VectorCoefficient*>)
|
||||
{
|
||||
arg->Eval(v, Tr, ip);
|
||||
return;
|
||||
}
|
||||
if constexpr (std::is_same_v<T, MatrixCoefficient*>)
|
||||
{
|
||||
DenseMatrix m(v.GetData(), arg->GetHeight(), arg->GetWidth());
|
||||
arg->Eval(m, Tr, ip);
|
||||
return;
|
||||
}
|
||||
if constexpr (std::is_same_v<T, const GridFunction*>)
|
||||
{
|
||||
arg->GetVectorValue(Tr, ip, v);
|
||||
return;
|
||||
}
|
||||
if constexpr (std::is_same_v<T, const QuadratureFunction*>)
|
||||
{
|
||||
arg->GetValues(Tr.ElementNo, ip.index, v);
|
||||
return;
|
||||
}
|
||||
}, params[i]);
|
||||
return this->val.GetBlock(i);
|
||||
}
|
||||
|
||||
void ADFunction::Gradient(const Vector &x, ElementTransformation &Tr,
|
||||
const IntegrationPoint &ip,
|
||||
Vector &J) const
|
||||
{
|
||||
ProcessParameters(Tr, ip);
|
||||
Gradient(x, J);
|
||||
}
|
||||
void ADFunction::Gradient(const Vector &x, Vector &J) const
|
||||
{
|
||||
MFEM_ASSERT(x.Size() == n_input,
|
||||
"ADFunction::Gradient: x.Size() must match n_input");
|
||||
J.SetSize(x.Size());
|
||||
ADVector x_ad(x);
|
||||
for (int i=0; i < n_input; i++)
|
||||
{
|
||||
x_ad[i].gradient = 1.0;
|
||||
ADReal_t result = (*this)(x_ad);
|
||||
J[i] = result.gradient;
|
||||
x_ad[i].gradient = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
void ADFunction::Hessian(const Vector &x, ElementTransformation &Tr,
|
||||
const IntegrationPoint &ip,
|
||||
DenseMatrix &H) const
|
||||
{
|
||||
ProcessParameters(Tr, ip);
|
||||
Hessian(x, H);
|
||||
}
|
||||
|
||||
void ADFunction::Hessian(const Vector &x, DenseMatrix &H) const
|
||||
{
|
||||
MFEM_ASSERT(x.Size() == n_input,
|
||||
"ADFunction::Hessian: x.Size() must match n_input");
|
||||
H.SetSize(x.Size(), x.Size());
|
||||
AD2Vector x_ad(x);
|
||||
for (int i=0; i<n_input; i++) // Loop for the first derivative
|
||||
{
|
||||
x_ad[i].value.gradient = 1.0;
|
||||
for (int j=0; j<=i; j++)
|
||||
{
|
||||
x_ad[j].gradient.value = 1.0;
|
||||
AD2Real_t result = (*this)(x_ad);
|
||||
H(j, i) = result.gradient.gradient;
|
||||
H(i, j) = result.gradient.gradient;
|
||||
x_ad[j].gradient.value = 0.0; // Reset gradient for next iteration
|
||||
}
|
||||
x_ad[i].value.gradient = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
void ADVectorFunction::Gradient(const Vector &x, DenseMatrix &J) const
|
||||
{
|
||||
MFEM_ASSERT(x.Size() == n_input,
|
||||
"ADVectorFunction::Gradient: x.Size() must match n_input");
|
||||
ADVector x_ad(x);
|
||||
ADVector Fx(n_output);
|
||||
J.SetSize(n_output, n_input);
|
||||
for (int i=0; i<n_input; i++)
|
||||
{
|
||||
x_ad[i].gradient = 1.0;
|
||||
Fx = ADReal_t();
|
||||
(*this)(x_ad, Fx);
|
||||
for (int j=0; j<n_output; j++)
|
||||
{
|
||||
J(j,i) = Fx[j].gradient;
|
||||
}
|
||||
x_ad[i].gradient = 0.0; // Reset gradient for next iteration
|
||||
}
|
||||
}
|
||||
|
||||
void ADVectorFunction::Hessian(const Vector &x, DenseTensor &H) const
|
||||
{
|
||||
MFEM_ASSERT(x.Size() == n_input,
|
||||
"ADVectorFunction::Gradient: x.Size() must match n_input");
|
||||
AD2Vector x_ad(x);
|
||||
AD2Vector Fx(n_output);
|
||||
H.SetSize(n_input, n_input, n_output);
|
||||
for (int i=0; i<n_input; i++) // Loop for the first derivative
|
||||
{
|
||||
x_ad[i].value.gradient = 1.0;
|
||||
for (int j=0; j<=i; j++)
|
||||
{
|
||||
x_ad[j].gradient.value = 1.0;
|
||||
Fx = AD2Real_t();
|
||||
(*this)(x_ad, Fx);
|
||||
for (int k=0; k<n_output; k++)
|
||||
{
|
||||
H(j, i, k) = Fx[k].gradient.gradient;
|
||||
H(i, j, k) = Fx[k].gradient.gradient;
|
||||
}
|
||||
x_ad[j].gradient.value = 0.0; // Reset gradient for next iteration
|
||||
}
|
||||
x_ad[i].value.gradient = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Lagrangian Lagrangian::AddEqConstraint(ADFunction &constraint,
|
||||
real_t target)
|
||||
{
|
||||
eq_con.push_back(&constraint);
|
||||
int numCon = eq_con.size();
|
||||
eq_rhs.SetSize(numCon);
|
||||
eq_rhs[numCon - 1] = target;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Lagrangian::ProcessParameters(ElementTransformation &Tr,
|
||||
const IntegrationPoint &ip) const
|
||||
{
|
||||
objective.ProcessParameters(Tr, ip);
|
||||
for (auto *con : eq_con) { con->ProcessParameters(Tr, ip); }
|
||||
}
|
||||
|
||||
ALFunctional ALFunctional::AddEqConstraint(ADFunction &constraint,
|
||||
real_t target)
|
||||
{
|
||||
eq_con.push_back(&constraint);
|
||||
int numCon = eq_con.size();
|
||||
eq_rhs.SetSize(numCon);
|
||||
lambda.SetSize(numCon);
|
||||
|
||||
eq_rhs[numCon - 1] = target;
|
||||
lambda[numCon - 1] = 0.0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void ALFunctional::SetLambda(const Vector &lambda)
|
||||
{
|
||||
MFEM_VERIFY(lambda.Size() == this->lambda.Size(),
|
||||
"ALFunctional: lambda size mismatch");
|
||||
this->lambda = lambda;
|
||||
}
|
||||
|
||||
void ALFunctional::SetPenalty(real_t mu)
|
||||
{
|
||||
MFEM_VERIFY(mu >= 0.0, "ALFunctional: mu must be non-negative");
|
||||
this->penalty = mu;
|
||||
}
|
||||
|
||||
void ALFunctional::ProcessParameters(ElementTransformation &Tr,
|
||||
const IntegrationPoint &ip) const
|
||||
{
|
||||
objective.ProcessParameters(Tr, ip);
|
||||
for (auto *con : eq_con) { con->ProcessParameters(Tr, ip); }
|
||||
}
|
||||
|
||||
} // namespace mfem
|
||||
@@ -0,0 +1,829 @@
|
||||
#pragma once
|
||||
|
||||
#include "mfem.hpp"
|
||||
#include "tadvector.hpp"
|
||||
#include "taddensemat.hpp"
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
template <typename T>
|
||||
constexpr auto type_name()
|
||||
{
|
||||
#if defined(__clang__)
|
||||
return std::string_view(__PRETTY_FUNCTION__);
|
||||
#elif defined(__GNUC__)
|
||||
return std::string_view(__PRETTY_FUNCTION__);
|
||||
#elif defined(_MSC_VER)
|
||||
return std::string_view(__FUNCSIG__);
|
||||
#else
|
||||
return std::string_view("unknown");
|
||||
#endif
|
||||
}
|
||||
template <typename value_type, typename gradient_type, typename other_type>
|
||||
MFEM_HOST_DEVICE
|
||||
inline future::dual<value_type, gradient_type> max(
|
||||
future::dual<value_type, gradient_type> a, other_type b);
|
||||
|
||||
inline real_t max(const real_t a, const real_t b) { return std::max(a,b); }
|
||||
|
||||
template <typename value_type, typename gradient_type, typename other_type>
|
||||
MFEM_HOST_DEVICE
|
||||
inline future::dual<value_type, gradient_type> min(
|
||||
future::dual<value_type, gradient_type> a, other_type b);
|
||||
|
||||
MFEM_HOST_DEVICE
|
||||
inline real_t min(const real_t a, const real_t b) { return std::min(a,b); }
|
||||
|
||||
// Use mfem-native autodiff types
|
||||
// If other autodiff libraries are used,
|
||||
// define ADReal_t, ADVector, ADMatrix, ... types accordingly.
|
||||
|
||||
// First order dual
|
||||
typedef future::dual<real_t, real_t> ADReal_t;
|
||||
typedef TAutoDiffVector<ADReal_t> ADVector;
|
||||
typedef TAutoDiffDenseMatrix<ADReal_t> ADMatrix;
|
||||
|
||||
// second order dual (nested dual)
|
||||
typedef future::dual<ADReal_t, ADReal_t> AD2Real_t;
|
||||
typedef TAutoDiffVector<AD2Real_t> AD2Vector;
|
||||
typedef TAutoDiffDenseMatrix<AD2Real_t> AD2Matrix;
|
||||
|
||||
class Evaluator
|
||||
{
|
||||
// To add a new parameter type,
|
||||
// implement GetSize() and Eval() method
|
||||
public:
|
||||
using param_t = std::variant<
|
||||
real_t, Vector, DenseMatrix, // pass by value
|
||||
const real_t*, const Vector*, const DenseMatrix*, // pass by pointer
|
||||
Coefficient*, VectorCoefficient*, MatrixCoefficient*,
|
||||
const GridFunction*,
|
||||
const QuadratureFunction*>;
|
||||
private:
|
||||
Array<int> offsets;
|
||||
std::vector<param_t> params;
|
||||
|
||||
mutable Vector loc_vec_val;
|
||||
mutable DenseMatrix loc_mat_val;
|
||||
|
||||
public:
|
||||
mutable BlockVector val;
|
||||
mutable Array<bool> owns;
|
||||
Evaluator(): offsets{0} {}
|
||||
Evaluator(int capacity)
|
||||
: offsets{0}
|
||||
{
|
||||
val.SetSize(capacity);
|
||||
val.SetSize(0);
|
||||
}
|
||||
virtual ~Evaluator();
|
||||
// Add a parameter to the evaluator
|
||||
int Add(param_t param, bool eval_owns = false);
|
||||
int Add(Vector &v)
|
||||
{
|
||||
if (dynamic_cast<GridFunction*>(&v))
|
||||
{
|
||||
MFEM_WARNING("Adding GridFunction by value, instead of its pointer. "
|
||||
"This result in the whole GridFunction value will be used at each quadrature point, "
|
||||
"which is likely not what you want. "
|
||||
"Use Add(const GridFunction*) instead.");
|
||||
}
|
||||
return Add((param_t)v);
|
||||
}
|
||||
int Add(const Vector &v)
|
||||
{
|
||||
if (dynamic_cast<const GridFunction*>(&v))
|
||||
{
|
||||
MFEM_WARNING("Adding GridFunction by value, instead of its pointer. "
|
||||
"This result in the whole GridFunction value will be used at each quadrature point, "
|
||||
"which is likely not what you want. "
|
||||
"Use Add(const GridFunction*) instead.");
|
||||
}
|
||||
return Add((param_t)v);
|
||||
}
|
||||
// Replace a parameter at index i with a new parameter
|
||||
// The output size of param should match the size of the old parameter
|
||||
void Replace(size_t i, param_t param);
|
||||
param_t Get(size_t i) const
|
||||
{
|
||||
MFEM_VERIFY(i >= 0 && i < params.size(),
|
||||
"Evaluator::Get: index out of range");
|
||||
return params[i];
|
||||
}
|
||||
|
||||
// Evaluate all parameters at once
|
||||
// and return the block vector
|
||||
const BlockVector &Eval(ElementTransformation &Tr,
|
||||
const IntegrationPoint &ip) const
|
||||
{
|
||||
for (int i=0; i<params.size(); i++)
|
||||
{ Eval(i, Tr, ip); }
|
||||
return val;
|
||||
}
|
||||
|
||||
// Evaluate the parameter at index i
|
||||
// this will update the val block vector, and return the corresponding block
|
||||
const Vector& Eval(int i, ElementTransformation &Tr,
|
||||
const IntegrationPoint &ip) const;
|
||||
static int GetSize(const param_t ¶m);
|
||||
int GetSize(size_t i) const
|
||||
{
|
||||
MFEM_VERIFY(i >= 0 && i < offsets.Size() - 1,
|
||||
"Evaluator::GetSize: index out of range");
|
||||
return offsets[i+1] - offsets[i];
|
||||
}
|
||||
void Project(QuadratureFunction &qf)
|
||||
{
|
||||
const int vdim = offsets.Last();
|
||||
qf.SetVDim(vdim);
|
||||
|
||||
QuadratureSpaceBase &qspace = *qf.GetSpace();
|
||||
Vector qf_view(qf.GetData(), vdim);
|
||||
for (int i=0; i<qspace.GetNE(); i++)
|
||||
{
|
||||
ElementTransformation &Tr = *qspace.GetTransformation(i);
|
||||
const IntegrationRule &ir = qspace.GetIntRule(i);
|
||||
for (int j=0; j<ir.GetNPoints(); j++)
|
||||
{
|
||||
const IntegrationPoint &ip = ir.IntPoint(j);
|
||||
qf_view = Eval(Tr, ip);
|
||||
qf_view.SetData(qf.GetData() + vdim);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
int GetVDim() const { return offsets.Last(); }
|
||||
};
|
||||
|
||||
class EvaluatorCF : public Coefficient
|
||||
{
|
||||
Evaluator &evaluator;
|
||||
int idx;
|
||||
const real_t &val;
|
||||
public:
|
||||
EvaluatorCF(Evaluator &evaluator_, int outer_idx=0, int inner_idx=0)
|
||||
: evaluator(evaluator_)
|
||||
, idx(outer_idx)
|
||||
, val(evaluator.val.GetBlock(outer_idx)(inner_idx)) {}
|
||||
real_t Eval(ElementTransformation &Tr, const IntegrationPoint &ip) override
|
||||
{
|
||||
evaluator.Eval(idx, Tr, ip);
|
||||
return val;
|
||||
}
|
||||
};
|
||||
class EvaluatorVCF : public VectorCoefficient
|
||||
{
|
||||
Evaluator &evaluator;
|
||||
int idx;
|
||||
public:
|
||||
EvaluatorVCF(Evaluator &evaluator, int idx=-1)
|
||||
: VectorCoefficient(idx == -1 ? evaluator.GetVDim() :
|
||||
evaluator.val.GetBlock(idx).Size())
|
||||
, evaluator(evaluator)
|
||||
, idx(idx)
|
||||
{ }
|
||||
|
||||
void Eval(Vector &V, ElementTransformation &Tr,
|
||||
const IntegrationPoint &ip) override
|
||||
{
|
||||
if (idx == -1) { V = evaluator.Eval(Tr, ip); }
|
||||
else { V = evaluator.Eval(idx, Tr, ip); }
|
||||
}
|
||||
};
|
||||
class EvaluatorMCF : public MatrixCoefficient
|
||||
{
|
||||
Evaluator &evaluator;
|
||||
int idx;
|
||||
const DenseMatrix val;
|
||||
public:
|
||||
EvaluatorMCF(Evaluator &evaluator, int h, int w, int idx=0)
|
||||
: MatrixCoefficient(h, w)
|
||||
, evaluator(evaluator)
|
||||
, idx(idx)
|
||||
, val(evaluator.val.GetBlock(idx).GetData(), h, w)
|
||||
{
|
||||
MFEM_VERIFY(evaluator.val.GetBlock(idx).Size() == h*w,
|
||||
"EvaluatorMCF: size mismatch");
|
||||
}
|
||||
void Eval(DenseMatrix &M, ElementTransformation &Tr,
|
||||
const IntegrationPoint &ip) override
|
||||
{
|
||||
evaluator.Eval(idx, Tr, ip);
|
||||
M = val;
|
||||
}
|
||||
};
|
||||
|
||||
class ADFunction
|
||||
{
|
||||
protected:
|
||||
|
||||
int AddParameter(Evaluator::param_t param)
|
||||
{ return evaluator.Add(param); }
|
||||
|
||||
void ReplaceParameter(int i, Evaluator::param_t param)
|
||||
{ evaluator.Replace(i, param); }
|
||||
|
||||
Evaluator evaluator;
|
||||
public:
|
||||
virtual void ProcessParameters(ElementTransformation &Tr,
|
||||
const IntegrationPoint &ip) const
|
||||
{ ProcessParameters(evaluator.Eval(Tr, ip)); }
|
||||
virtual void ProcessParameters(const BlockVector ¶m_val) const
|
||||
{ }
|
||||
|
||||
const int n_input;
|
||||
ADFunction(int n_input): n_input(n_input) {}
|
||||
// Constructor with capacity for evaluator.
|
||||
// This is useful when the parameter size is known in advance,
|
||||
// so that we can get references to the parameters at construction time.
|
||||
ADFunction(int n_input, int capacity)
|
||||
: n_input(n_input), evaluator(capacity)
|
||||
{
|
||||
MFEM_ASSERT(n_input > 0, "ADFunction: n_input must be positive");
|
||||
}
|
||||
// default evaluator
|
||||
virtual real_t operator()(const Vector &x) const
|
||||
{ MFEM_ABORT("Not implemented. Use AD_IMPL macro to implement all path"); }
|
||||
virtual real_t operator()(const Vector &x, ElementTransformation &Tr,
|
||||
const IntegrationPoint &ip) const
|
||||
{ ProcessParameters(Tr, ip); return (*this)(x); }
|
||||
|
||||
// default Jacobian evaluator
|
||||
virtual ADReal_t operator()(const ADVector &x) const
|
||||
{ MFEM_ABORT("Not implemented. Use MAKE_AD_FUNCTOR macro to create derived structure"); }
|
||||
|
||||
// default Hessian evaluator
|
||||
virtual AD2Real_t operator()(const AD2Vector &x) const
|
||||
{ MFEM_ABORT("Not implemented. Use MAKE_AD_FUNCTOR macro to create derived structure"); }
|
||||
|
||||
// Evaluate the gradient, using forward mode autodiff
|
||||
virtual void Gradient(const Vector &x, ElementTransformation &Tr,
|
||||
const IntegrationPoint &ip, Vector &J) const;
|
||||
virtual void Gradient(const Vector &x, Vector &J) const;
|
||||
// Evaluate the Hessian, using forward over forward autodiff
|
||||
// The Hessian assumed to be symmetric.
|
||||
virtual void Hessian(const Vector &x, ElementTransformation &Tr,
|
||||
const IntegrationPoint &ip,
|
||||
DenseMatrix &H) const;
|
||||
virtual void Hessian(const Vector &x, DenseMatrix &H) const;
|
||||
};
|
||||
|
||||
// We currently only support Jacobian.
|
||||
// To consistent with ADFunction, which returns
|
||||
// evaluate: scalar, Gradient: vector, Hessian: matrix,
|
||||
// we overrode the Gradient for evaulation, and Hessian for Jacobian
|
||||
// To be used with ADNonlinearFormIntegrator or ADBlockNonlinearFormIntegrator,
|
||||
// n_input and n_output must be the same.
|
||||
struct ADVectorFunction : public ADFunction
|
||||
{
|
||||
|
||||
int n_output;
|
||||
ADVectorFunction(int n_input, int n_output)
|
||||
: ADFunction(n_input), n_output(n_output)
|
||||
{
|
||||
MFEM_ASSERT(n_input > 0 && n_output > 0,
|
||||
"ADVectorFunction: n_input and n_output must be positive");
|
||||
}
|
||||
|
||||
void operator()(const Vector &x, ElementTransformation &Tr,
|
||||
const IntegrationPoint &ip,
|
||||
Vector &F) const
|
||||
{ ProcessParameters(Tr, ip); (*this)(x, F); }
|
||||
|
||||
// Derived struct should implement the following methods.
|
||||
// Use AD_VEC_IMPL macro to implement them.
|
||||
virtual void operator()(const Vector &x, Vector &F) const = 0;
|
||||
virtual void operator()(const ADVector &x, ADVector &F) const = 0;
|
||||
virtual void operator()(const AD2Vector &x, AD2Vector &F) const = 0;
|
||||
|
||||
void Gradient(const Vector &x, ElementTransformation &Tr,
|
||||
const IntegrationPoint &ip, DenseMatrix &J) const
|
||||
{ ProcessParameters(Tr, ip); Gradient(x, J); }
|
||||
|
||||
void Gradient(const Vector &x, DenseMatrix &J) const;
|
||||
|
||||
void Hessian(const Vector &x, ElementTransformation &Tr,
|
||||
const IntegrationPoint &ip,
|
||||
DenseTensor &H) const
|
||||
{ ProcessParameters(Tr, ip); Hessian(x, H); }
|
||||
|
||||
void Hessian(const Vector &x, DenseTensor &H) const;
|
||||
|
||||
// To support ADNonlinearFormIntegrator and ADVectorNonlinearFormIntegrator
|
||||
void Gradient(const Vector &x, ElementTransformation &Tr,
|
||||
const IntegrationPoint &ip, Vector &F) const override final
|
||||
{ (*this)(x, Tr, ip, F); }
|
||||
|
||||
void Gradient(const Vector &x, Vector &F) const override final
|
||||
{ (*this)(x, F); }
|
||||
|
||||
// To support ADNonlinearFormIntegrator and ADVectorNonlinearFormIntegrator
|
||||
void Hessian(const Vector &x, ElementTransformation &Tr,
|
||||
const IntegrationPoint &ip,
|
||||
DenseMatrix &J) const override final
|
||||
{ this->Gradient(x, Tr, ip, J); }
|
||||
|
||||
void Hessian(const Vector &x, DenseMatrix &J) const override final
|
||||
{ this->Gradient(x, J); }
|
||||
|
||||
real_t operator()(const Vector &x) const override final
|
||||
{
|
||||
MFEM_ABORT("ADVectorFunction::operator(): This method should not be called. "
|
||||
"Use ADVectorFunction::operator(const Vector &x, Vector &F) instead.");
|
||||
}
|
||||
ADReal_t operator()(const ADVector &x) const override final
|
||||
{
|
||||
MFEM_ABORT("ADVectorFunction::operator(): This method should not be called. "
|
||||
"Use ADVectorFunction::operator(const ADVector &x, ADVector &F) instead.");
|
||||
}
|
||||
AD2Real_t operator()(const AD2Vector &x) const override final
|
||||
{
|
||||
MFEM_ABORT("ADVectorFunction::operator(): This method should not be called. "
|
||||
"Use ADVectorFunction::operator(const AD2Vector &x, AD2Vector &F) instead.");
|
||||
}
|
||||
};
|
||||
|
||||
class DifferentiableCoefficient : public Coefficient
|
||||
{
|
||||
private:
|
||||
int idx; // index of the next input variable
|
||||
|
||||
class GradientCoefficient : public VectorCoefficient
|
||||
{
|
||||
DifferentiableCoefficient &c;
|
||||
public:
|
||||
GradientCoefficient(int dim, DifferentiableCoefficient &c)
|
||||
: VectorCoefficient(dim), c(c) { }
|
||||
void Eval(Vector &J, ElementTransformation &T,
|
||||
const IntegrationPoint &ip) override
|
||||
{
|
||||
return c.f.Gradient(c.evaluator.Eval(T, ip), T, ip, J);
|
||||
}
|
||||
};
|
||||
|
||||
friend class GradientCoefficient;
|
||||
GradientCoefficient grad_cf;
|
||||
|
||||
class HessianCoefficient : public MatrixCoefficient
|
||||
{
|
||||
DifferentiableCoefficient &c;
|
||||
public:
|
||||
HessianCoefficient(int dim, DifferentiableCoefficient &c)
|
||||
: MatrixCoefficient(dim), c(c) { }
|
||||
void Eval(DenseMatrix &H, ElementTransformation &T,
|
||||
const IntegrationPoint &ip) override
|
||||
{ return c.f.Hessian(c.evaluator.Eval(T, ip), T, ip, H); }
|
||||
};
|
||||
|
||||
friend class HessianCoefficient;
|
||||
HessianCoefficient hess_cf;
|
||||
|
||||
protected:
|
||||
Evaluator evaluator;
|
||||
|
||||
ADFunction &f;
|
||||
public:
|
||||
DifferentiableCoefficient(ADFunction &f)
|
||||
: f(f), idx(0)
|
||||
, grad_cf(f.n_input, *this)
|
||||
, hess_cf(f.n_input, *this)
|
||||
{}
|
||||
DifferentiableCoefficient &AddInput(Evaluator::param_t param)
|
||||
{ evaluator.Add(param); return *this; }
|
||||
|
||||
real_t Eval(ElementTransformation &T,
|
||||
const IntegrationPoint &ip) override
|
||||
{ return f(evaluator.Eval(T, ip), T, ip); }
|
||||
|
||||
GradientCoefficient& Gradient() { return grad_cf; }
|
||||
HessianCoefficient& Hessian() { return hess_cf; }
|
||||
|
||||
protected:
|
||||
};
|
||||
|
||||
// Macro to generate type-varying implementation for ADFunction.
|
||||
// See, DiffusionEnergy, ..., for example of usage.
|
||||
// @param SCALAR is the name of templated scalar type
|
||||
// @param VEC is the name of templated vector type
|
||||
// @param MAT is the name of templated matrix type
|
||||
// @param var is the input variable name
|
||||
// @param body is the main function body. Use T() to create T-typed 0.
|
||||
#define AD_IMPL(SCALAR, VEC, MAT, var, body) \
|
||||
using ADFunction::operator(); \
|
||||
real_t operator()(const Vector &var) const override \
|
||||
{ \
|
||||
MFEM_ASSERT(var.Size() == n_input, \
|
||||
"ADFunction::operator(): var.Size()=" << var.Size() \
|
||||
<< " must match n_input=" << n_input) \
|
||||
using SCALAR = real_t; \
|
||||
using VEC = Vector; \
|
||||
using MAT = DenseMatrix; \
|
||||
body \
|
||||
} \
|
||||
\
|
||||
ADReal_t operator()(const ADVector &var) const override \
|
||||
{ \
|
||||
MFEM_ASSERT(var.Size() == n_input, \
|
||||
"ADFunction::operator(): var.Size()=" << var.Size() \
|
||||
<< " must match n_input=" << n_input) \
|
||||
using SCALAR = ADReal_t; \
|
||||
using VEC = ADVector; \
|
||||
using MAT = ADMatrix; \
|
||||
body \
|
||||
} \
|
||||
\
|
||||
AD2Real_t operator()(const AD2Vector &var) const override \
|
||||
{ \
|
||||
MFEM_ASSERT(var.Size() == n_input, \
|
||||
"ADFunction::operator(): var.Size()=" << var.Size() \
|
||||
<< " must match n_input=" << n_input) \
|
||||
using SCALAR = AD2Real_t; \
|
||||
using VEC = AD2Vector; \
|
||||
using MAT = AD2Matrix; \
|
||||
body \
|
||||
}
|
||||
|
||||
|
||||
// Macro to generate type-varying implementation for ADVectorFunction.
|
||||
// @param SCALAR is the name of templated scalar type
|
||||
// @param VEC is the name of templated vector type
|
||||
// @param MAT is the name of templated matrix type
|
||||
// @param var is the input variable name
|
||||
// @param result is the output variable name
|
||||
// @param body is the main function body. Use T() to create T-typed 0.
|
||||
#define AD_VEC_IMPL(SCALAR, VEC, MAT, var, result, body) \
|
||||
using ADVectorFunction::operator(); \
|
||||
using ADVectorFunction::Gradient; \
|
||||
using ADVectorFunction::Hessian; \
|
||||
\
|
||||
void operator()(const Vector &var, Vector &result) const override \
|
||||
{ \
|
||||
MFEM_ASSERT(var.Size() == n_input, \
|
||||
"ADFunction::operator(): var.Size()=" << var.Size() \
|
||||
<< " must match n_input=" << n_input) \
|
||||
using SCALAR = real_t; \
|
||||
using VEC = Vector; \
|
||||
using MAT = DenseMatrix; \
|
||||
body \
|
||||
} \
|
||||
\
|
||||
void operator()(const ADVector &var, ADVector &result) const override \
|
||||
{ \
|
||||
MFEM_ASSERT(var.Size() == n_input, \
|
||||
"ADFunction::operator(): var.Size()=" << var.Size() \
|
||||
<< " must match n_input=" << n_input) \
|
||||
using SCALAR = ADReal_t; \
|
||||
using VEC = ADVector; \
|
||||
using MAT = ADMatrix; \
|
||||
body \
|
||||
} \
|
||||
\
|
||||
void operator()(const AD2Vector &var, AD2Vector &result) const override \
|
||||
{ \
|
||||
MFEM_ASSERT(var.Size() == n_input, \
|
||||
"ADFunction::operator(): var.Size()=" << var.Size() \
|
||||
<< " must match n_input=" << n_input) \
|
||||
using SCALAR = AD2Real_t; \
|
||||
using VEC = AD2Vector; \
|
||||
using MAT = AD2Matrix; \
|
||||
body \
|
||||
}
|
||||
|
||||
class MassEnergy : public ADFunction
|
||||
{
|
||||
public:
|
||||
MassEnergy(int n_var)
|
||||
: ADFunction(n_var)
|
||||
{}
|
||||
AD_IMPL(T, V, M, x, return 0.5*(x*x););
|
||||
};
|
||||
class DiffusionEnergy : public ADFunction
|
||||
{
|
||||
const int dim;
|
||||
mutable const Vector *K;
|
||||
public:
|
||||
DiffusionEnergy(int dim)
|
||||
: ADFunction(dim), dim(dim)
|
||||
{}
|
||||
DiffusionEnergy(int dim, Evaluator::param_t K)
|
||||
: DiffusionEnergy(dim)
|
||||
{ SetK(K); }
|
||||
|
||||
void ProcessParameters(ElementTransformation &Tr,
|
||||
const IntegrationPoint &ip) const override
|
||||
{ K = &evaluator.Eval(Tr, ip); }
|
||||
|
||||
void SetK(Evaluator::param_t param)
|
||||
{
|
||||
int i = AddParameter(param);
|
||||
int size = evaluator.val.GetBlock(i).Size();
|
||||
MFEM_VERIFY(size == 1 || size == n_input || size == n_input*n_input,
|
||||
"Incorrect size for K. Dimension is " << n_input << "but K has size " << size);
|
||||
}
|
||||
|
||||
AD_IMPL(T, V, M, gradu,
|
||||
{
|
||||
const int dim = gradu.Size();
|
||||
const int Kdim = K->Size();
|
||||
// No diffusion coefficient, ||grad u||^2
|
||||
if (Kdim == 0) { return 0.5*(gradu*gradu); }
|
||||
// Scalar diffusion coefficient, ||K^{1/2} grad u||^2
|
||||
if (Kdim == 1) { return 0.5*(*K)[0]*(gradu*gradu); }
|
||||
// Vector diffusion coefficient, ||diag(K)^{1/2} grad u||^2
|
||||
if (Kdim == dim)
|
||||
{
|
||||
T result = T();
|
||||
for (int i=0; i<dim; i++)
|
||||
{
|
||||
result += (*K)[i]*gradu[i]*gradu[i];
|
||||
}
|
||||
return 0.5*result;
|
||||
}
|
||||
// Matrix diffusion coefficient, ||K^{1/2} grad u||^2
|
||||
if (Kdim == dim*dim)
|
||||
{
|
||||
DenseMatrix Kmat(K->GetData(), dim, dim);
|
||||
T result = T();
|
||||
for (int j=0; j<dim; j++)
|
||||
{
|
||||
for (int i=0; i<dim; i++)
|
||||
{
|
||||
result += Kmat(i,j)*gradu[i]*gradu[j];
|
||||
}
|
||||
}
|
||||
return 0.5*result;
|
||||
}
|
||||
MFEM_ABORT("DiffusionEnergy: K must be a scalar, vector of size dim, "
|
||||
"or matrix of size dim x dim");
|
||||
return T();
|
||||
});
|
||||
};
|
||||
|
||||
class DiffEnergy : public ADFunction
|
||||
{
|
||||
const ADFunction &energy;
|
||||
mutable const Vector *target;
|
||||
public:
|
||||
DiffEnergy(const ADFunction &energy)
|
||||
: ADFunction(energy.n_input)
|
||||
, energy(energy)
|
||||
{ }
|
||||
|
||||
DiffEnergy(const ADFunction &energy, Evaluator::param_t other)
|
||||
: DiffEnergy(energy)
|
||||
{
|
||||
int i = AddParameter(other);
|
||||
MFEM_VERIFY(evaluator.val.GetBlock(0).Size() == n_input,
|
||||
"DiffEnergy: The provided target has the wrong size. "
|
||||
"Expected " << n_input << ", got " << evaluator.val.GetBlock(0).Size());
|
||||
}
|
||||
|
||||
void SetTarget(Evaluator::param_t &target)
|
||||
{
|
||||
if (evaluator.val.NumBlocks() == 1)
|
||||
{ evaluator.Replace(0, target); }
|
||||
else
|
||||
{ evaluator.Add(target); }
|
||||
MFEM_VERIFY(evaluator.val.GetBlock(0).Size() == n_input,
|
||||
"DiffEnergy: The provided target has the wrong size. "
|
||||
"Expected " << n_input << ", got " << evaluator.val.GetBlock(0).Size());
|
||||
}
|
||||
|
||||
void ProcessParameters(const BlockVector &x) const override
|
||||
{
|
||||
target = &x.GetBlock(0);
|
||||
}
|
||||
|
||||
AD_IMPL(T, V, M, x,
|
||||
{
|
||||
V diff(x);
|
||||
for (int i=0; i<n_input; i++)
|
||||
{ diff[i] -= (*target)[i]; }
|
||||
return energy(diff);
|
||||
});
|
||||
};
|
||||
|
||||
class LinearElasticityEnergy : public ADFunction
|
||||
{
|
||||
const int dim;
|
||||
real_t λ
|
||||
real_t μ
|
||||
public:
|
||||
void ProcessParameters(ElementTransformation &Tr,
|
||||
const IntegrationPoint &ip) const override
|
||||
{
|
||||
evaluator.Eval(Tr, ip);
|
||||
}
|
||||
LinearElasticityEnergy(int dim, Evaluator::param_t lambda,
|
||||
Evaluator::param_t mu, int offset=0)
|
||||
: ADFunction(dim*dim, 2)
|
||||
, dim(dim)
|
||||
, lambda(*(evaluator.val.GetData() + offset))
|
||||
, mu(*(evaluator.val.GetData() + evaluator.GetSize(mu) + offset))
|
||||
{
|
||||
int lambda_idx = evaluator.Add(lambda);
|
||||
int mu_idx = evaluator.Add(mu);
|
||||
MFEM_VERIFY(lambda_idx == 0,
|
||||
"LinearElasticityEnergy: lambda must be the first parameter");
|
||||
}
|
||||
AD_IMPL(T, V, M, gradu,
|
||||
{
|
||||
T divnorm = T();
|
||||
for (int i=0; i<dim; i++) { divnorm += gradu[i*dim + i]; }
|
||||
divnorm = divnorm*divnorm;
|
||||
T h1_norm = T();
|
||||
for (int i=0; i<dim; i++)
|
||||
{
|
||||
for (int j=0; j<dim; j++)
|
||||
{
|
||||
T symm = 0.5*(gradu[i*dim + j] + gradu[j*dim + i]);
|
||||
h1_norm += symm*symm;
|
||||
}
|
||||
}
|
||||
return 0.5*lambda*divnorm + mu*h1_norm;
|
||||
});
|
||||
};
|
||||
|
||||
// Lagrangian functional
|
||||
// f(x) + sum lambda[i]*c[i](x)
|
||||
class Lagrangian : public ADFunction
|
||||
{
|
||||
private:
|
||||
enum { OBJONLY=-2, FULL=-1, CON=0};
|
||||
int eval_mode =
|
||||
FULL; // -2: objective, -1: full Lagrangian, >=0: constraint comp
|
||||
|
||||
ADFunction &objective; // f(x)
|
||||
|
||||
std::vector<ADFunction*> eq_con; // c[i](x)
|
||||
Vector eq_rhs; // c[i](x) = con_target[i]
|
||||
public:
|
||||
|
||||
Lagrangian(ADFunction &objective, const int n_eq_con)
|
||||
: ADFunction(objective.n_input+n_eq_con)
|
||||
, objective(objective)
|
||||
{}
|
||||
|
||||
Lagrangian AddEqConstraint(ADFunction &constraint,
|
||||
real_t target = 0.0);
|
||||
Lagrangian SetEqRHS(int idx, real_t target) { eq_rhs[idx] = target; return *this; }
|
||||
|
||||
// return f(x) + sum lambda[i]*c[i](x)
|
||||
void FullMode() { this->eval_mode = FULL; }
|
||||
// return f(x)
|
||||
void ObjectiveMode() { this->eval_mode = OBJONLY; }
|
||||
// return c[i](x)
|
||||
void EqConstraintMode(int comp)
|
||||
{
|
||||
MFEM_VERIFY(comp >= 0 && comp < eq_con.size(),
|
||||
"ALFunctional: comp must be in [0, n_input)");
|
||||
this->eval_mode = comp;
|
||||
}
|
||||
|
||||
void ProcessParameters(ElementTransformation &Tr,
|
||||
const IntegrationPoint &ip) const override;
|
||||
|
||||
AD_IMPL(T, V, M, x_and_lambda,
|
||||
{
|
||||
const V x(x_and_lambda.GetData(), objective.n_input);
|
||||
const V lambda(x_and_lambda.GetData() + objective.n_input,
|
||||
eq_con.size());
|
||||
if (eval_mode >= 0) { return (*eq_con[eval_mode])(x); }
|
||||
|
||||
T result = objective(x);
|
||||
if (eval_mode == OBJONLY) { return result; } // only objective
|
||||
for (int i=0; i<eq_con.size(); i++) { result += (*eq_con[i])(x)*lambda[i]; }
|
||||
return result;
|
||||
});
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
// Augmented Lagrangian functional
|
||||
class ALFunctional : public ADFunction
|
||||
{
|
||||
private:
|
||||
enum { OBJONLY=-2, FULLAL=-1, CON=0};
|
||||
int al_eval_mode = FULLAL; // -2: objective, -1: full AL, >=0: constraint comp
|
||||
|
||||
ADFunction &objective; // f(x)
|
||||
|
||||
std::vector<ADFunction*> eq_con; // c[i](x)
|
||||
Vector eq_rhs; // c[i](x) = con_target[i]
|
||||
Vector lambda; // Lagrange multipliers
|
||||
real_t penalty=1.0; // penalty
|
||||
public:
|
||||
|
||||
ALFunctional(ADFunction &objective)
|
||||
: ADFunction(objective.n_input)
|
||||
, objective(objective)
|
||||
{}
|
||||
|
||||
ALFunctional AddEqConstraint(ADFunction &constraint,
|
||||
real_t target = 0.0);
|
||||
ALFunctional SetEqRHS(int idx, real_t target) { eq_rhs[idx] = target; return *this; }
|
||||
|
||||
void SetLambda(const Vector &lambda);
|
||||
const Vector &GetLambda() const { return lambda; }
|
||||
Vector &GetLambda() { return lambda; }
|
||||
|
||||
void SetPenalty(real_t mu);
|
||||
real_t GetPenalty() const {return penalty; }
|
||||
real_t &GetPenalty() { return penalty; }
|
||||
|
||||
// Full AL mode: f(x) + sum lambda[i]*c[i](x) + mu/2 * sum c[i](x)^2
|
||||
void ALMode() { this->al_eval_mode = FULLAL; }
|
||||
// Objective mode: f(x)
|
||||
void ObjectiveMode() { this->al_eval_mode = OBJONLY; }
|
||||
// Constraint mode: c[i](x)
|
||||
void EqConstraintMode(int comp)
|
||||
{
|
||||
MFEM_VERIFY(comp >= 0 && comp < eq_con.size(),
|
||||
"ALFunctional: comp must be in [0, n_input)");
|
||||
this->al_eval_mode = comp;
|
||||
}
|
||||
|
||||
void ProcessParameters(ElementTransformation &Tr,
|
||||
const IntegrationPoint &ip) const override;
|
||||
|
||||
AD_IMPL(T, V, M, x,
|
||||
{
|
||||
if (al_eval_mode >= 0) { return evalAL<T>(x, al_eval_mode); }
|
||||
|
||||
T result = objective(x);
|
||||
if (al_eval_mode == OBJONLY) { return result; } // only objective
|
||||
|
||||
for (int i=0; i<eq_con.size(); i++) { result += evalAL<T>(x, i); }
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
private:
|
||||
// Evaluate lambda*c(x) + (mu/2)*c(x)^2
|
||||
template <typename T, typename V>
|
||||
T evalAL(V &x, int idx) const
|
||||
{
|
||||
T cx = (*eq_con[idx])(x) - eq_rhs[idx];
|
||||
if (al_eval_mode >= 0) { return cx; } // if non-negative, only c(x)
|
||||
return cx*(lambda[idx] + penalty*0.5*cx);
|
||||
}
|
||||
};
|
||||
// ------------------------------------------------------------------------------
|
||||
// Implement dual max/min
|
||||
// ------------------------------------------------------------------------------
|
||||
template <typename value_type, typename gradient_type, typename other_type>
|
||||
MFEM_HOST_DEVICE
|
||||
inline future::dual<value_type, gradient_type> max(
|
||||
future::dual<value_type, gradient_type> a,
|
||||
other_type b)
|
||||
{
|
||||
if (a > b)
|
||||
{
|
||||
return a;
|
||||
}
|
||||
else if (a < b)
|
||||
{
|
||||
if constexpr (std::is_same<other_type, real_t>::value)
|
||||
{
|
||||
return future::dual<value_type, gradient_type> {b};
|
||||
}
|
||||
else
|
||||
{
|
||||
return b;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If values are equal, return the average (subgradient)
|
||||
return 0.5*(a + b);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename value_type, typename gradient_type, typename other_type>
|
||||
MFEM_HOST_DEVICE
|
||||
inline future::dual<value_type, gradient_type> min(
|
||||
future::dual<value_type, gradient_type> a,
|
||||
other_type b)
|
||||
{
|
||||
if (a < b)
|
||||
{
|
||||
return a;
|
||||
}
|
||||
else if (a > b)
|
||||
{
|
||||
if constexpr (std::is_same<other_type, real_t>::value)
|
||||
{
|
||||
return future::dual<value_type, gradient_type> {b};
|
||||
}
|
||||
else
|
||||
{
|
||||
return b;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If values are equal, return the average (subgradient)
|
||||
return 0.5*(a + b);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,876 +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.
|
||||
|
||||
#ifndef ADEXAMPLE_HPP
|
||||
#define ADEXAMPLE_HPP
|
||||
|
||||
#include "mfem.hpp"
|
||||
#include "admfem.hpp"
|
||||
#include <memory>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
|
||||
/// Example: Implementation of the residual evaluation for p-Laplacian
|
||||
/// problem. The residual is evaluated at the integration points for PDE
|
||||
/// parameters vparam and state fields (derivatives with respect to x,y,z and
|
||||
/// primal field) stored in vector uu.
|
||||
template<typename TDataType, typename TParamVector, typename TStateVector,
|
||||
int residual_size, int state_size, int param_size>
|
||||
class MyResidualFunctor
|
||||
{
|
||||
public:
|
||||
/// The operator returns the first derivative of the energy with respect to
|
||||
/// all state variables. These are set in vector uu and consist of the
|
||||
/// derivatives with respect to x,y,z and the primal field. The derivative is
|
||||
/// stored in vector rr with length equal to the length of vector uu.
|
||||
void operator()(TParamVector &vparam, TStateVector &uu, TStateVector &rr)
|
||||
{
|
||||
MFEM_ASSERT(residual_size==4,
|
||||
"PLaplacianResidual residual_size should be equal to 4!");
|
||||
real_t pp = vparam[0];
|
||||
real_t ee = vparam[1];
|
||||
real_t ff = vparam[2];
|
||||
|
||||
// The vector rr holds the gradients of the following expression:
|
||||
// (u_x^2+u_y^2+u_z^2+\varepsilon^2)^(p/2)-f.u,
|
||||
// where u_x,u_y,u_z are the gradients of the scalar field u.
|
||||
// The state vector is defined as uu=[u_x,u_y,u_z,u].
|
||||
|
||||
TDataType norm2 = uu[0] * uu[0] + uu[1] * uu[1] + uu[2] * uu[2];
|
||||
TDataType tvar = pow(ee * ee + norm2, (pp - 2.0) / 2.0);
|
||||
|
||||
rr[0] = tvar * uu[0];
|
||||
rr[1] = tvar * uu[1];
|
||||
rr[2] = tvar * uu[2];
|
||||
rr[3] = -ff;
|
||||
}
|
||||
};
|
||||
|
||||
/// Defines template class (functor) for evaluating the energy of the
|
||||
/// p-Laplacian problem. The input parameters vparam are: vparam[0] - the
|
||||
/// p-Laplacian power, vparam[1] small value ensuring exciting of an unique
|
||||
/// solution, and vparam[2] - the distributed external input to the PDE. The
|
||||
/// template parameter TDataType will be replaced by the compiler with the
|
||||
/// appropriate AD type for automatic differentiation. The TParamVector
|
||||
/// represents the vector type used for the parameter vector, and TStateVector
|
||||
/// the vector type used for the state vector. The template parameters
|
||||
/// state_size and param_size provide information for the size of the state and
|
||||
/// the parameters vectors.
|
||||
template<typename TDataType, typename TParamVector, typename TStateVector
|
||||
, int state_size, int param_size>
|
||||
class MyEnergyFunctor
|
||||
{
|
||||
public:
|
||||
/// Returns the energy of a p-Laplacian for state field input provided in
|
||||
/// vector uu and parameters provided in vector vparam.
|
||||
TDataType operator()(TParamVector &vparam, TStateVector &uu)
|
||||
{
|
||||
MFEM_ASSERT(state_size==4,"MyEnergyFunctor state_size should be equal to 4!");
|
||||
MFEM_ASSERT(param_size==3,"MyEnergyFunctor param_size should be equal to 3!");
|
||||
real_t pp = vparam[0];
|
||||
real_t ee = vparam[1];
|
||||
real_t ff = vparam[2];
|
||||
|
||||
TDataType u = uu[3];
|
||||
TDataType norm2 = uu[0] * uu[0] + uu[1] * uu[1] + uu[2] * uu[2];
|
||||
|
||||
TDataType rez = pow(ee * ee + norm2, pp / 2.0) / pp - ff * u;
|
||||
return rez;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/// Implements integrator for a p-Laplacian problem. The integrator is based on
|
||||
/// a class QFunction utilized for evaluating the energy, the first derivative
|
||||
/// (residual) and the Hessian of the energy (the Jacobian of the residual).
|
||||
/// The template parameter CQVectAutoDiff represents the automatically
|
||||
/// differentiated energy or residual implemented by the user.
|
||||
/// CQVectAutoDiff::VectorFunc(Vector parameters, Vector state,Vector residual)
|
||||
/// evaluates the residual at an integration point.
|
||||
/// CQVectAutoDiff::Jacobian(Vector parameters, Vector state, Matrix hessian)
|
||||
/// evaluates the Hessian of the energy(the Jacobian of the residual).
|
||||
template<class CQVectAutoDiff>
|
||||
class pLaplaceAD : public NonlinearFormIntegrator
|
||||
{
|
||||
protected:
|
||||
Coefficient *pp;
|
||||
Coefficient *coeff;
|
||||
Coefficient *load;
|
||||
|
||||
CQVectAutoDiff rdf;
|
||||
|
||||
public:
|
||||
pLaplaceAD()
|
||||
{
|
||||
coeff = nullptr;
|
||||
pp = nullptr;
|
||||
load = nullptr;
|
||||
|
||||
vparam.SetSize(3);
|
||||
vparam[0] = 2.0; // default power
|
||||
vparam[1] = 1e-8; // default epsilon
|
||||
vparam[2] = 1.0; // default load
|
||||
}
|
||||
|
||||
pLaplaceAD(Coefficient &pp_) : pp(&pp_), coeff(nullptr), load(nullptr)
|
||||
{
|
||||
vparam.SetSize(3);
|
||||
vparam[0] = 2.0; // default power
|
||||
vparam[1] = 1e-8; // default epsilon
|
||||
vparam[2] = 1.0; // default load
|
||||
|
||||
}
|
||||
|
||||
pLaplaceAD(Coefficient &pp_, Coefficient &q, Coefficient &ld_)
|
||||
: pp(&pp_), coeff(&q), load(&ld_)
|
||||
{
|
||||
vparam.SetSize(3);
|
||||
vparam[0] = 2.0; // default power
|
||||
vparam[1] = 1e-8; // default epsilon
|
||||
vparam[2] = 1.0; // default load
|
||||
}
|
||||
|
||||
virtual ~pLaplaceAD() {}
|
||||
|
||||
real_t GetElementEnergy(const FiniteElement &el,
|
||||
ElementTransformation &trans,
|
||||
const Vector &elfun) override
|
||||
{
|
||||
real_t energy = 0.0;
|
||||
const int ndof = el.GetDof();
|
||||
const int ndim = el.GetDim();
|
||||
const int spaceDim = trans.GetSpaceDim();
|
||||
bool square = (ndim == spaceDim);
|
||||
int order = 2 * el.GetOrder() + trans.OrderGrad(&el);
|
||||
const IntegrationRule &ir(IntRules.Get(el.GetGeomType(), order));
|
||||
|
||||
Vector shapef(ndof);
|
||||
// derivatives in isoparametric coordinates
|
||||
DenseMatrix dshape_iso(ndof, ndim);
|
||||
// derivatives in physical space
|
||||
DenseMatrix dshape_xyz(ndof, spaceDim);
|
||||
Vector grad(spaceDim);
|
||||
|
||||
Vector uu(4); //[diff_x,diff_y,diff_z,u]
|
||||
|
||||
uu = 0.0;
|
||||
|
||||
// Calculates the functional/energy at an integration point.
|
||||
MyEnergyFunctor<real_t,Vector,Vector,4,3> qfunc;
|
||||
|
||||
real_t w;
|
||||
real_t detJ;
|
||||
|
||||
for (int i = 0; i < ir.GetNPoints(); i++)
|
||||
{
|
||||
const IntegrationPoint &ip = ir.IntPoint(i);
|
||||
trans.SetIntPoint(&ip);
|
||||
w = trans.Weight();
|
||||
detJ = (square ? w : w * w);
|
||||
w = ip.weight * w;
|
||||
|
||||
el.CalcDShape(ip, dshape_iso);
|
||||
el.CalcShape(ip, shapef);
|
||||
// AdjugateJacobian = / adj(J), if J is square
|
||||
// \ adj(J^t.J).J^t, otherwise
|
||||
Mult(dshape_iso, trans.AdjugateJacobian(), dshape_xyz);
|
||||
// dshape_xyz should be divided by detJ for obtaining the real value
|
||||
// calculate the gradient
|
||||
dshape_xyz.MultTranspose(elfun, grad);
|
||||
|
||||
// set the power
|
||||
if (pp != nullptr)
|
||||
{
|
||||
vparam[0] = pp->Eval(trans, ip);
|
||||
}
|
||||
|
||||
// set the coefficient ensuring positiveness of the tangent matrix
|
||||
if (coeff != nullptr)
|
||||
{
|
||||
vparam[1] = coeff->Eval(trans, ip);
|
||||
}
|
||||
// add the contribution from the load
|
||||
if (load != nullptr)
|
||||
{
|
||||
vparam[2] = load->Eval(trans, ip);
|
||||
}
|
||||
// fill the values of vector uu
|
||||
for (int jj = 0; jj < spaceDim; jj++)
|
||||
{
|
||||
uu[jj] = grad[jj] / detJ;
|
||||
}
|
||||
uu[3] = shapef * elfun;
|
||||
// the energy is taken directly from the templated function
|
||||
energy = energy + w * qfunc(vparam,uu);
|
||||
}
|
||||
return energy;
|
||||
}
|
||||
|
||||
void AssembleElementVector(const FiniteElement &el,
|
||||
ElementTransformation &trans,
|
||||
const Vector &elfun,
|
||||
Vector &elvect) override
|
||||
{
|
||||
MFEM_PERF_BEGIN("AssembleElementVector");
|
||||
const int ndof = el.GetDof();
|
||||
const int ndim = el.GetDim();
|
||||
const int spaceDim = trans.GetSpaceDim();
|
||||
int order = 2 * el.GetOrder() + trans.OrderGrad(&el);
|
||||
const IntegrationRule &ir(IntRules.Get(el.GetGeomType(), order));
|
||||
|
||||
Vector shapef(ndof);
|
||||
DenseMatrix dshape_iso(ndof, ndim);
|
||||
DenseMatrix dshape_xyz(ndof, spaceDim);
|
||||
Vector lvec(ndof);
|
||||
elvect.SetSize(ndof);
|
||||
elvect = 0.0;
|
||||
|
||||
DenseMatrix B(ndof, 4); // [diff_x,diff_y,diff_z, shape]
|
||||
Vector uu(4); // [diff_x,diff_y,diff_z,u]
|
||||
Vector du(4);
|
||||
B = 0.0;
|
||||
uu = 0.0;
|
||||
real_t w;
|
||||
|
||||
for (int i = 0; i < ir.GetNPoints(); i++)
|
||||
{
|
||||
const IntegrationPoint &ip = ir.IntPoint(i);
|
||||
trans.SetIntPoint(&ip);
|
||||
w = trans.Weight();
|
||||
w = ip.weight * w;
|
||||
|
||||
el.CalcDShape(ip, dshape_iso);
|
||||
el.CalcShape(ip, shapef);
|
||||
Mult(dshape_iso, trans.InverseJacobian(), dshape_xyz);
|
||||
|
||||
// set the matrix B
|
||||
for (int jj = 0; jj < spaceDim; jj++)
|
||||
{
|
||||
B.SetCol(jj, dshape_xyz.GetColumn(jj));
|
||||
}
|
||||
B.SetCol(3, shapef);
|
||||
|
||||
// set the power
|
||||
if (pp != nullptr)
|
||||
{
|
||||
vparam[0] = pp->Eval(trans, ip);
|
||||
}
|
||||
// set the coefficient ensuring positiveness of the tangent matrix
|
||||
if (coeff != nullptr)
|
||||
{
|
||||
vparam[1] = coeff->Eval(trans, ip);
|
||||
}
|
||||
// add the contribution from the load
|
||||
if (load != nullptr)
|
||||
{
|
||||
vparam[2] = load->Eval(trans, ip);
|
||||
}
|
||||
|
||||
// calculate uu
|
||||
B.MultTranspose(elfun, uu);
|
||||
// calculate derivative of the energy with respect to uu
|
||||
rdf.VectorFunc(vparam,uu,du);
|
||||
B.Mult(du, lvec);
|
||||
elvect.Add(w, lvec);
|
||||
} // end integration loop
|
||||
MFEM_PERF_END("AssembleElementVector");
|
||||
}
|
||||
|
||||
void AssembleElementGrad(const FiniteElement &el,
|
||||
ElementTransformation &trans,
|
||||
const Vector &elfun,
|
||||
DenseMatrix &elmat) override
|
||||
{
|
||||
MFEM_PERF_BEGIN("AssembleElementGrad");
|
||||
const int ndof = el.GetDof();
|
||||
const int ndim = el.GetDim();
|
||||
const int spaceDim = trans.GetSpaceDim();
|
||||
int order = 2 * el.GetOrder() + trans.OrderGrad(&el);
|
||||
const IntegrationRule &ir(IntRules.Get(el.GetGeomType(), order));
|
||||
|
||||
Vector shapef(ndof);
|
||||
DenseMatrix dshape_iso(ndof, ndim);
|
||||
DenseMatrix dshape_xyz(ndof, spaceDim);
|
||||
elmat.SetSize(ndof, ndof);
|
||||
elmat = 0.0;
|
||||
|
||||
DenseMatrix B(ndof, 4); // [diff_x,diff_y,diff_z, shape]
|
||||
DenseMatrix A(ndof, 4);
|
||||
Vector uu(4); // [diff_x,diff_y,diff_z,u]
|
||||
DenseMatrix duu(4, 4);
|
||||
B = 0.0;
|
||||
uu = 0.0;
|
||||
|
||||
real_t w;
|
||||
|
||||
for (int i = 0; i < ir.GetNPoints(); i++)
|
||||
{
|
||||
const IntegrationPoint &ip = ir.IntPoint(i);
|
||||
trans.SetIntPoint(&ip);
|
||||
w = trans.Weight();
|
||||
w = ip.weight * w;
|
||||
|
||||
el.CalcDShape(ip, dshape_iso);
|
||||
el.CalcShape(ip, shapef);
|
||||
Mult(dshape_iso, trans.InverseJacobian(), dshape_xyz);
|
||||
|
||||
// set the matrix B
|
||||
for (int jj = 0; jj < spaceDim; jj++)
|
||||
{
|
||||
B.SetCol(jj, dshape_xyz.GetColumn(jj));
|
||||
}
|
||||
B.SetCol(3, shapef);
|
||||
|
||||
// set the power
|
||||
if (pp != nullptr)
|
||||
{
|
||||
vparam[0] = pp->Eval(trans, ip);
|
||||
}
|
||||
// set the coefficient ensuring positiveness of the tangent matrix
|
||||
if (coeff != nullptr)
|
||||
{
|
||||
vparam[1] = coeff->Eval(trans, ip);
|
||||
}
|
||||
// add the contribution from the load
|
||||
if (load != nullptr)
|
||||
{
|
||||
vparam[2] = load->Eval(trans, ip);
|
||||
}
|
||||
|
||||
// calculate uu
|
||||
B.MultTranspose(elfun, uu);
|
||||
// calculate derivative of the energy with respect to uu
|
||||
rdf.Jacobian(vparam,uu,duu);
|
||||
Mult(B, duu, A);
|
||||
AddMult_a_ABt(w, A, B, elmat);
|
||||
|
||||
} // end integration loop
|
||||
MFEM_PERF_END("AssembleElementGrad");
|
||||
}
|
||||
|
||||
private:
|
||||
Vector vparam; // [power, epsilon, load]
|
||||
|
||||
};
|
||||
|
||||
/// Implements hand-coded integrator for a p-Laplacian problem. Utilized as
|
||||
/// alternative for the pLaplaceAD class based on automatic differentiation.
|
||||
class pLaplace : public NonlinearFormIntegrator
|
||||
{
|
||||
protected:
|
||||
Coefficient *pp;
|
||||
Coefficient *coeff;
|
||||
Coefficient *load;
|
||||
|
||||
public:
|
||||
pLaplace()
|
||||
{
|
||||
coeff = nullptr;
|
||||
pp = nullptr;
|
||||
load = nullptr;
|
||||
}
|
||||
|
||||
pLaplace(Coefficient &pp_) : pp(&pp_), coeff(nullptr), load(nullptr) {}
|
||||
|
||||
pLaplace(Coefficient &pp_, Coefficient &q, Coefficient &ld_)
|
||||
: pp(&pp_), coeff(&q), load(&ld_)
|
||||
{}
|
||||
|
||||
virtual ~pLaplace() {}
|
||||
|
||||
real_t GetElementEnergy(const FiniteElement &el,
|
||||
ElementTransformation &trans,
|
||||
const Vector &elfun) override
|
||||
{
|
||||
real_t energy = 0.0;
|
||||
const int ndof = el.GetDof();
|
||||
const int ndim = el.GetDim();
|
||||
const int spaceDim = trans.GetSpaceDim();
|
||||
bool square = (ndim == spaceDim);
|
||||
int order = 2 * el.GetOrder() + trans.OrderGrad(&el);
|
||||
const IntegrationRule &ir(IntRules.Get(el.GetGeomType(), order));
|
||||
|
||||
Vector shapef(ndof);
|
||||
DenseMatrix dshape_iso(ndof, ndim);
|
||||
DenseMatrix dshape_xyz(ndof, spaceDim);
|
||||
Vector grad(spaceDim);
|
||||
|
||||
real_t w;
|
||||
real_t detJ;
|
||||
real_t nrgrad2;
|
||||
real_t ppp = 2.0;
|
||||
real_t eee = 0.0;
|
||||
|
||||
for (int i = 0; i < ir.GetNPoints(); i++)
|
||||
{
|
||||
const IntegrationPoint &ip = ir.IntPoint(i);
|
||||
trans.SetIntPoint(&ip);
|
||||
w = trans.Weight();
|
||||
detJ = (square ? w : w * w);
|
||||
w = ip.weight * w;
|
||||
|
||||
el.CalcDShape(ip, dshape_iso);
|
||||
el.CalcShape(ip, shapef);
|
||||
// AdjugateJacobian = / adj(J), if J is square
|
||||
// \ adj(J^t.J).J^t, otherwise
|
||||
Mult(dshape_iso, trans.AdjugateJacobian(), dshape_xyz);
|
||||
// dshape_xyz should be divided by detJ for obtaining the real value
|
||||
// calculate the gradient
|
||||
dshape_xyz.MultTranspose(elfun, grad);
|
||||
nrgrad2 = grad * grad / (detJ * detJ);
|
||||
|
||||
// set the power
|
||||
if (pp != nullptr)
|
||||
{
|
||||
ppp = pp->Eval(trans, ip);
|
||||
}
|
||||
|
||||
// set the coefficient ensuring positiveness of the tangent matrix
|
||||
if (coeff != nullptr)
|
||||
{
|
||||
eee = coeff->Eval(trans, ip);
|
||||
}
|
||||
|
||||
energy = energy + w * std::pow(nrgrad2 + eee * eee, ppp / 2.0) / ppp;
|
||||
|
||||
// add the contribution from the load
|
||||
if (load != nullptr)
|
||||
{
|
||||
energy = energy - w * (shapef * elfun) * load->Eval(trans, ip);
|
||||
}
|
||||
}
|
||||
return energy;
|
||||
}
|
||||
|
||||
void AssembleElementVector(const FiniteElement &el,
|
||||
ElementTransformation &trans,
|
||||
const Vector &elfun,
|
||||
Vector &elvect) override
|
||||
{
|
||||
MFEM_PERF_BEGIN("AssembleElementVector");
|
||||
const int ndof = el.GetDof();
|
||||
const int ndim = el.GetDim();
|
||||
const int spaceDim = trans.GetSpaceDim();
|
||||
bool square = (ndim == spaceDim);
|
||||
int order = 2 * el.GetOrder() + trans.OrderGrad(&el);
|
||||
const IntegrationRule &ir(IntRules.Get(el.GetGeomType(), order));
|
||||
|
||||
Vector shapef(ndof);
|
||||
DenseMatrix dshape_iso(ndof, ndim);
|
||||
DenseMatrix dshape_xyz(ndof, spaceDim);
|
||||
Vector grad(spaceDim);
|
||||
Vector lvec(ndof);
|
||||
elvect.SetSize(ndof);
|
||||
elvect = 0.0;
|
||||
|
||||
real_t w;
|
||||
real_t detJ;
|
||||
real_t nrgrad;
|
||||
real_t aa;
|
||||
real_t ppp = 2.0;
|
||||
real_t eee = 0.0;
|
||||
|
||||
for (int i = 0; i < ir.GetNPoints(); i++)
|
||||
{
|
||||
const IntegrationPoint &ip = ir.IntPoint(i);
|
||||
trans.SetIntPoint(&ip);
|
||||
w = trans.Weight();
|
||||
detJ = (square ? w : w * w);
|
||||
w = ip.weight * w;
|
||||
|
||||
el.CalcDShape(ip, dshape_iso);
|
||||
el.CalcShape(ip, shapef);
|
||||
// AdjugateJacobian = / adj(J), if J is square
|
||||
// \ adj(J^t.J).J^t, otherwise
|
||||
Mult(dshape_iso, trans.AdjugateJacobian(), dshape_xyz);
|
||||
// dshape_xyz should be divided by detJ for obtaining the real value
|
||||
|
||||
// calculate the gradient
|
||||
dshape_xyz.MultTranspose(elfun, grad);
|
||||
nrgrad = grad.Norml2() / detJ;
|
||||
// grad is not scaled so far, i.e., grad=grad/detJ
|
||||
|
||||
// set the power
|
||||
if (pp != nullptr)
|
||||
{
|
||||
ppp = pp->Eval(trans, ip);
|
||||
}
|
||||
|
||||
// set the coefficient ensuring positiveness of the tangent matrix
|
||||
if (coeff != nullptr)
|
||||
{
|
||||
eee = coeff->Eval(trans, ip);
|
||||
}
|
||||
// compute (norm of the gradient)^2 + epsilon^2
|
||||
aa = nrgrad * nrgrad + eee * eee;
|
||||
aa = std::pow(aa, (ppp - 2.0) / 2.0);
|
||||
dshape_xyz.Mult(grad, lvec);
|
||||
elvect.Add(w * aa / (detJ * detJ), lvec);
|
||||
|
||||
// add loading
|
||||
if (load != nullptr)
|
||||
{
|
||||
elvect.Add(-w * load->Eval(trans, ip), shapef);
|
||||
}
|
||||
} // end integration loop
|
||||
MFEM_PERF_END("AssembleElementVector");
|
||||
}
|
||||
|
||||
void AssembleElementGrad(const FiniteElement &el,
|
||||
ElementTransformation &trans,
|
||||
const Vector &elfun,
|
||||
DenseMatrix &elmat) override
|
||||
{
|
||||
MFEM_PERF_BEGIN("AssembleElementGrad");
|
||||
const int ndof = el.GetDof();
|
||||
const int ndim = el.GetDim();
|
||||
const int spaceDim = trans.GetSpaceDim();
|
||||
bool square = (ndim == spaceDim);
|
||||
int order = 2 * el.GetOrder() + trans.OrderGrad(&el);
|
||||
const IntegrationRule &ir(IntRules.Get(el.GetGeomType(), order));
|
||||
|
||||
DenseMatrix dshape_iso(ndof, ndim);
|
||||
DenseMatrix dshape_xyz(ndof, spaceDim);
|
||||
Vector grad(spaceDim);
|
||||
Vector lvec(ndof);
|
||||
// set the size of the element matrix
|
||||
elmat.SetSize(ndof, ndof);
|
||||
elmat = 0.0;
|
||||
|
||||
real_t w; // integration weight
|
||||
real_t detJ;
|
||||
real_t nrgrad; // norm of the gradient
|
||||
real_t aa0; // original nonlinear diffusion coefficient
|
||||
real_t aa1; // gradient of the above
|
||||
real_t ppp = 2.0; // power in the P-Laplacian
|
||||
real_t eee = 0.0; // regularization parameter
|
||||
|
||||
for (int i = 0; i < ir.GetNPoints(); i++)
|
||||
{
|
||||
const IntegrationPoint &ip = ir.IntPoint(i);
|
||||
trans.SetIntPoint(&ip);
|
||||
w = trans.Weight();
|
||||
detJ = (square ? w : w * w);
|
||||
w = ip.weight * w;
|
||||
|
||||
el.CalcDShape(ip, dshape_iso);
|
||||
// AdjugateJacobian = / adj(J), if J is square
|
||||
// \ adj(J^t.J).J^t, otherwise
|
||||
Mult(dshape_iso, trans.AdjugateJacobian(), dshape_xyz);
|
||||
// dshape_xyz should be divided by detJ for obtaining the real value
|
||||
// grad is not scaled so far,i.e., grad=grad/detJ
|
||||
|
||||
// set the power
|
||||
if (pp != nullptr)
|
||||
{
|
||||
ppp = pp->Eval(trans, ip);
|
||||
}
|
||||
// set the coefficient ensuring positiveness of the tangent matrix
|
||||
if (coeff != nullptr)
|
||||
{
|
||||
eee = coeff->Eval(trans, ip);
|
||||
}
|
||||
|
||||
// calculate the gradient
|
||||
dshape_xyz.MultTranspose(elfun, grad);
|
||||
nrgrad = grad.Norml2() / detJ;
|
||||
// (u_x^2+u_y^2+u_z^2+\varepsilon^2)
|
||||
aa0 = nrgrad * nrgrad + eee * eee;
|
||||
aa1 = std::pow(aa0, (ppp - 2.0) / 2.0);
|
||||
aa0 = (ppp - 2.0) * std::pow(aa0, (ppp - 4.0) / 2.0);
|
||||
dshape_xyz.Mult(grad, lvec);
|
||||
w = w / (detJ * detJ);
|
||||
AddMult_a_VVt(w * aa0 / (detJ * detJ), lvec, elmat);
|
||||
AddMult_a_AAt(w * aa1, dshape_xyz, elmat);
|
||||
|
||||
} // end integration loop
|
||||
MFEM_PERF_END("AssembleElementGrad");
|
||||
}
|
||||
};
|
||||
|
||||
/// Implements AD enabled integrator for a p-Laplacian problem. The tangent
|
||||
/// matrix is computed using the residual of the element. The template argument
|
||||
/// should be equal to the size of the residual vector (element vector), i.e.,
|
||||
/// the user should specify the size to match the exact vector size for the
|
||||
/// considered order of the shape functions.
|
||||
|
||||
template<int sizeres=10>
|
||||
class pLaplaceSL : public NonlinearFormIntegrator
|
||||
{
|
||||
protected:
|
||||
Coefficient *pp;
|
||||
Coefficient *coeff;
|
||||
Coefficient *load;
|
||||
|
||||
public:
|
||||
pLaplaceSL()
|
||||
{
|
||||
coeff = nullptr;
|
||||
pp = nullptr;
|
||||
load = nullptr;
|
||||
}
|
||||
|
||||
pLaplaceSL(Coefficient &pp_) : pp(&pp_), coeff(nullptr), load(nullptr) {}
|
||||
|
||||
pLaplaceSL(Coefficient &pp_, Coefficient &q, Coefficient &ld_)
|
||||
: pp(&pp_), coeff(&q), load(&ld_)
|
||||
{}
|
||||
|
||||
virtual ~pLaplaceSL() {}
|
||||
|
||||
real_t GetElementEnergy(const FiniteElement &el,
|
||||
ElementTransformation &trans,
|
||||
const Vector &elfun) override
|
||||
{
|
||||
real_t energy = 0.0;
|
||||
const int ndof = el.GetDof();
|
||||
const int ndim = el.GetDim();
|
||||
const int spaceDim = trans.GetSpaceDim();
|
||||
bool square = (ndim == spaceDim);
|
||||
int order = 2 * el.GetOrder() + trans.OrderGrad(&el);
|
||||
const IntegrationRule &ir(IntRules.Get(el.GetGeomType(), order));
|
||||
|
||||
Vector shapef(ndof);
|
||||
DenseMatrix dshape_iso(ndof, ndim);
|
||||
DenseMatrix dshape_xyz(ndof, spaceDim);
|
||||
Vector grad(spaceDim);
|
||||
|
||||
real_t w;
|
||||
real_t detJ;
|
||||
real_t nrgrad2;
|
||||
real_t ppp = 2.0;
|
||||
real_t eee = 0.0;
|
||||
|
||||
for (int i = 0; i < ir.GetNPoints(); i++)
|
||||
{
|
||||
const IntegrationPoint &ip = ir.IntPoint(i);
|
||||
trans.SetIntPoint(&ip);
|
||||
w = trans.Weight();
|
||||
detJ = (square ? w : w * w);
|
||||
w = ip.weight * w;
|
||||
|
||||
el.CalcDShape(ip, dshape_iso);
|
||||
el.CalcShape(ip, shapef);
|
||||
// AdjugateJacobian = / adj(J), if J is square
|
||||
// \ adj(J^t.J).J^t, otherwise
|
||||
Mult(dshape_iso, trans.AdjugateJacobian(), dshape_xyz);
|
||||
// dshape_xyz should be divided by detJ for obtaining the real value
|
||||
// calculate the gradient
|
||||
dshape_xyz.MultTranspose(elfun, grad);
|
||||
nrgrad2 = grad * grad / (detJ * detJ);
|
||||
|
||||
// set the power
|
||||
if (pp != nullptr)
|
||||
{
|
||||
ppp = pp->Eval(trans, ip);
|
||||
}
|
||||
|
||||
// set the coefficient ensuring positiveness of the tangent matrix
|
||||
if (coeff != nullptr)
|
||||
{
|
||||
eee = coeff->Eval(trans, ip);
|
||||
}
|
||||
|
||||
energy = energy + w * std::pow(nrgrad2 + eee * eee, ppp / 2.0) / ppp;
|
||||
|
||||
// add the contribution from the load
|
||||
if (load != nullptr)
|
||||
{
|
||||
energy = energy - w * (shapef * elfun) * load->Eval(trans, ip);
|
||||
}
|
||||
}
|
||||
return energy;
|
||||
}
|
||||
|
||||
void AssembleElementVector(const FiniteElement &el,
|
||||
ElementTransformation &trans,
|
||||
const Vector &elfun,
|
||||
Vector &elvect) override
|
||||
{
|
||||
MFEM_PERF_BEGIN("AssembleElementVector");
|
||||
const int ndof = el.GetDof();
|
||||
const int ndim = el.GetDim();
|
||||
const int spaceDim = trans.GetSpaceDim();
|
||||
bool square = (ndim == spaceDim);
|
||||
int order = 2 * el.GetOrder() + trans.OrderGrad(&el);
|
||||
const IntegrationRule &ir(IntRules.Get(el.GetGeomType(), order));
|
||||
|
||||
Vector shapef(ndof);
|
||||
DenseMatrix dshape_iso(ndof, ndim);
|
||||
DenseMatrix dshape_xyz(ndof, spaceDim);
|
||||
Vector grad(spaceDim);
|
||||
Vector lvec(ndof);
|
||||
elvect.SetSize(ndof);
|
||||
elvect = 0.0;
|
||||
|
||||
real_t w;
|
||||
real_t detJ;
|
||||
real_t nrgrad;
|
||||
real_t aa;
|
||||
real_t ppp = 2.0;
|
||||
real_t eee = 0.0;
|
||||
|
||||
for (int i = 0; i < ir.GetNPoints(); i++)
|
||||
{
|
||||
const IntegrationPoint &ip = ir.IntPoint(i);
|
||||
trans.SetIntPoint(&ip);
|
||||
w = trans.Weight();
|
||||
detJ = (square ? w : w * w);
|
||||
w = ip.weight * w; //w;
|
||||
|
||||
el.CalcDShape(ip, dshape_iso);
|
||||
el.CalcShape(ip, shapef);
|
||||
// AdjugateJacobian = / adj(J), if J is square
|
||||
// \ adj(J^t.J).J^t, otherwise
|
||||
Mult(dshape_iso, trans.AdjugateJacobian(), dshape_xyz);
|
||||
// dshape_xyz should be divided by detJ for obtaining the real value
|
||||
|
||||
// calculate the gradient
|
||||
dshape_xyz.MultTranspose(elfun, grad);
|
||||
nrgrad = grad.Norml2() / detJ;
|
||||
// grad is not scaled so far, i.e., grad=grad/detJ
|
||||
|
||||
// set the power
|
||||
if (pp != nullptr)
|
||||
{
|
||||
ppp = pp->Eval(trans, ip);
|
||||
}
|
||||
|
||||
// set the coefficient ensuring positiveness of the tangent matrix
|
||||
if (coeff != nullptr)
|
||||
{
|
||||
eee = coeff->Eval(trans, ip);
|
||||
}
|
||||
|
||||
aa = nrgrad * nrgrad + eee * eee;
|
||||
aa = std::pow(aa, (ppp - 2.0) / 2.0);
|
||||
dshape_xyz.Mult(grad, lvec);
|
||||
elvect.Add(w * aa / (detJ * detJ), lvec);
|
||||
|
||||
// add loading
|
||||
if (load != nullptr)
|
||||
{
|
||||
elvect.Add(-w * load->Eval(trans, ip), shapef);
|
||||
}
|
||||
} // end integration loop
|
||||
MFEM_PERF_END("AssembleElementVector");
|
||||
}
|
||||
|
||||
void AssembleElementGrad(const FiniteElement &el,
|
||||
ElementTransformation &trans,
|
||||
const Vector &elfun,
|
||||
DenseMatrix &elmat) override
|
||||
{
|
||||
MFEM_PERF_BEGIN("AssembleElementGrad");
|
||||
const int ndof = el.GetDof();
|
||||
const int ndim = el.GetDim();
|
||||
const int spaceDim = trans.GetSpaceDim();
|
||||
bool square = (ndim == spaceDim);
|
||||
int order = 2 * el.GetOrder() + trans.OrderGrad(&el);
|
||||
const IntegrationRule &ir(IntRules.Get(el.GetGeomType(), order));
|
||||
|
||||
DenseMatrix dshape_iso(ndof, ndim);
|
||||
DenseMatrix dshape_xyz(ndof, spaceDim);
|
||||
elmat.SetSize(ndof, ndof);
|
||||
elmat = 0.0;
|
||||
|
||||
real_t w;
|
||||
real_t detJ;
|
||||
real_t ppp = 2.0;
|
||||
real_t eee = 0.0;
|
||||
|
||||
mfem::Vector param(3); param=0.0;
|
||||
|
||||
// Computes the residual at an integration point. The implementation is a
|
||||
// copy of the integration loop in AssembleElementVector.
|
||||
auto resfun = [&](mfem::Vector& vparam, mfem::ad::ADVectorType& uu,
|
||||
mfem::ad::ADVectorType& vres)
|
||||
{
|
||||
|
||||
vres.SetSize(uu.Size()); vres=0.0;
|
||||
mfem::ad::ADVectorType grad(spaceDim);
|
||||
mfem::ad::ADFloatType nrgrad;
|
||||
mfem::ad::ADFloatType aa;
|
||||
mfem::ad::ADVectorType lvec(ndof);
|
||||
|
||||
for (int q = 0; q < ir.GetNPoints(); q++)
|
||||
{
|
||||
lvec=0.0;
|
||||
|
||||
const IntegrationPoint &ip = ir.IntPoint(q);
|
||||
trans.SetIntPoint(&ip);
|
||||
w = trans.Weight();
|
||||
detJ = (square ? w : w * w);
|
||||
w = ip.weight * w;
|
||||
|
||||
el.CalcDShape(ip, dshape_iso);
|
||||
// AdjugateJacobian = / adj(J), if J is square
|
||||
// \ adj(J^t.J).J^t, otherwise
|
||||
Mult(dshape_iso, trans.AdjugateJacobian(), dshape_xyz);
|
||||
// dshape_xyz should be divided by detJ for obtaining the real value
|
||||
// grad is not scaled so far,i.e., grad=grad/detJ
|
||||
|
||||
// set the power
|
||||
if (pp != nullptr)
|
||||
{
|
||||
ppp = pp->Eval(trans, ip);
|
||||
}
|
||||
// set the coefficient ensuring positiveness of the tangent matrix
|
||||
if (coeff != nullptr)
|
||||
{
|
||||
eee = coeff->Eval(trans, ip);
|
||||
}
|
||||
|
||||
grad=0.0;
|
||||
// calculate the gradient
|
||||
for (int i=0; i<spaceDim; i++)
|
||||
{
|
||||
for (int j=0; j<ndof; j++)
|
||||
{
|
||||
grad[i]= grad[i]+ dshape_xyz(j,i)*uu[j];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
nrgrad= (grad*grad)/(detJ*detJ);
|
||||
|
||||
aa = nrgrad + eee * eee;
|
||||
aa = pow(aa, (ppp - 2.0) / 2.0);
|
||||
|
||||
for (int i=0; i<spaceDim; i++)
|
||||
{
|
||||
for (int j=0; j<ndof; j++)
|
||||
{
|
||||
lvec[j] = lvec[j] + dshape_xyz(j,i) * grad[i];
|
||||
}
|
||||
}
|
||||
|
||||
for (int j=0; j<ndof; j++)
|
||||
{
|
||||
vres[j]=vres[j] + lvec[j] * (w*aa/(detJ*detJ));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
mfem::Vector bla(elfun);
|
||||
// calculate the gradient - only for a fixed ndof
|
||||
mfem::VectorFuncAutoDiff<sizeres,sizeres,3> fdr(resfun);
|
||||
fdr.Jacobian(param, bla, elmat);
|
||||
MFEM_PERF_END("AssembleElementGrad");
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mfem
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,261 @@
|
||||
#include "logger.hpp"
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
|
||||
TableLogger::TableLogger(std::ostream &os)
|
||||
: os(os), w(14), var_name_printed(false)
|
||||
{
|
||||
#ifdef MFEM_USE_MPI
|
||||
isRoot = mfem::Mpi::IsInitialized() ? mfem::Mpi::Root() : true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void TableLogger::Append(const std::string name, double &val)
|
||||
{
|
||||
names.push_back(name);
|
||||
data_double.push_back(&val);
|
||||
data_order.push_back(dtype::DOUBLE);
|
||||
}
|
||||
|
||||
void TableLogger::Append(const std::string name, int &val)
|
||||
{
|
||||
names.push_back(name);
|
||||
data_int.push_back(&val);
|
||||
data_order.push_back(dtype::INT);
|
||||
}
|
||||
|
||||
void TableLogger::Print(bool print_varname)
|
||||
{
|
||||
if (isRoot)
|
||||
{
|
||||
if (!var_name_printed || print_varname)
|
||||
{
|
||||
for (auto &name : names)
|
||||
{
|
||||
os << std::setw(w) << std::setfill(' ') << name << ",\t";
|
||||
}
|
||||
os << "\b\b";
|
||||
os << std::endl;
|
||||
if (!var_name_printed && file && file->is_open())
|
||||
{
|
||||
for (int i=0; i<names.size() - 1; i++)
|
||||
{
|
||||
*file << std::setw(w) << std::setfill(' ') << names[i] << ",\t";
|
||||
}
|
||||
*file << std::setw(w) << std::setfill(' ') << names.back() << std::endl;
|
||||
}
|
||||
var_name_printed = true;
|
||||
}
|
||||
int i(0), i_double(0), i_int(0);
|
||||
for (int i=0; i<data_order.size(); i++)
|
||||
{
|
||||
auto d = data_order[i];
|
||||
switch (d)
|
||||
{
|
||||
case dtype::DOUBLE:
|
||||
{
|
||||
os << std::setw(w) << *data_double[i_double];
|
||||
if (file && file->is_open())
|
||||
{
|
||||
*file << std::setprecision(8) << std::scientific << std::setw(w)
|
||||
<< std::setfill(' ') << *data_double[i_double];
|
||||
}
|
||||
i_double++;
|
||||
break;
|
||||
}
|
||||
case dtype::INT:
|
||||
{
|
||||
os << std::setw(w) << *data_int[i_int];
|
||||
if (file && file->is_open())
|
||||
{
|
||||
*file << std::setw(w) << std::setfill(' ') << *data_int[i_int];
|
||||
}
|
||||
i_int++;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
MFEM_ABORT("Unknown data type. See, TableLogger::dtype");
|
||||
}
|
||||
}
|
||||
if (i < data_order.size() - 1)
|
||||
{
|
||||
os << ",\t";
|
||||
*file << ",\t";
|
||||
}
|
||||
}
|
||||
os << std::endl;
|
||||
if (file)
|
||||
{
|
||||
*file << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TableLogger::SaveWhenPrint(std::string filename, std::ios::openmode mode)
|
||||
{
|
||||
if (isRoot)
|
||||
{
|
||||
filename = filename.append(".csv");
|
||||
file.reset(new std::fstream);
|
||||
file->open(filename, mode);
|
||||
if (!file->is_open())
|
||||
{
|
||||
std::string msg("");
|
||||
msg += "Cannot open file ";
|
||||
msg += filename;
|
||||
MFEM_ABORT(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool GLVis::Append(GridFunction *gf, QuadratureFunction *qf,
|
||||
std::string_view window_title, std::string_view keys)
|
||||
{
|
||||
MFEM_VERIFY((gf == nullptr && qf != nullptr)
|
||||
|| (gf != nullptr && qf == nullptr),
|
||||
"Either GridFunction or QuadratureFunction must be provided, "
|
||||
"but not both.");
|
||||
bool is_gf = gf != nullptr;
|
||||
sockets.push_back(std::make_unique<socketstream>(hostname, port, secure));
|
||||
socketstream &socket = *sockets.back();
|
||||
if (!socket.is_open() || !socket.good())
|
||||
{
|
||||
MFEM_WARNING("GLVis: Cannot connect to " << hostname << ":" << port);
|
||||
sockets.back().reset();
|
||||
sockets.pop_back();
|
||||
return false;
|
||||
}
|
||||
socket.precision(8);
|
||||
gfs.Append(gf);
|
||||
qfs.Append(qf);
|
||||
|
||||
Mesh *mesh;
|
||||
if (is_gf) { mesh = gf->FESpace()->GetMesh(); }
|
||||
else { mesh = qf->GetSpace()->GetMesh(); }
|
||||
meshes.Append(mesh);
|
||||
|
||||
cfs.Append(nullptr);
|
||||
vcfs.Append(nullptr);
|
||||
|
||||
#ifdef MFEM_USE_MPI
|
||||
parallel.Append(false);
|
||||
myrank.Append(0);
|
||||
nrrank.Append(1);
|
||||
ParMesh *pmesh = dynamic_cast<ParMesh*>(mesh);
|
||||
if (pmesh != nullptr)
|
||||
{
|
||||
parallel.Last() = true;
|
||||
nrrank.Last() = pmesh->GetNRanks();
|
||||
myrank.Last() = pmesh->GetMyRank();
|
||||
socket << "parallel " << nrrank.Last() << " " << myrank.Last() <<
|
||||
"\n";
|
||||
}
|
||||
#endif
|
||||
if (is_gf)
|
||||
{
|
||||
socket << "solution\n" << *mesh << *gf;
|
||||
}
|
||||
else
|
||||
{
|
||||
socket << "quadrature\n" << *mesh << *qf << "\n";
|
||||
}
|
||||
|
||||
|
||||
if (!keys.empty())
|
||||
{
|
||||
socket << "keys " << keys << "\n";
|
||||
bool hasQ=false;
|
||||
if (!is_gf)
|
||||
{
|
||||
auto end_pos = std::min(keys.find(' '), keys.find('\n'));
|
||||
std::string_view actual_keys = keys.substr(0, end_pos);
|
||||
if (actual_keys.find('Q') != std::string_view::npos) { hasQ = true; }
|
||||
}
|
||||
qfkey_has_Q.Append(hasQ);
|
||||
}
|
||||
if (!window_title.empty())
|
||||
{
|
||||
socket << "window_title '" << window_title <<"'\n";
|
||||
}
|
||||
int row = (sockets.size() - 1) / nrWinPerRow;
|
||||
int col = (sockets.size() - 1) % nrWinPerRow;
|
||||
socket << " window_geometry "
|
||||
<< w*col << " " << h*row << " "
|
||||
<< w << " " << h << "\n";
|
||||
socket << std::flush;
|
||||
#ifdef MFEM_USE_MPI
|
||||
if (parallel.Last())
|
||||
{
|
||||
MPI_Comm comm = static_cast<ParMesh*>(meshes.Last())->GetComm();
|
||||
MPI_Barrier(comm);
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
void GLVis::Append(Coefficient &cf, QuadratureSpace &qs,
|
||||
std::string_view window_title,
|
||||
std::string_view keys)
|
||||
{
|
||||
owned_qfs.push_back(std::make_unique<QuadratureFunction>(qs));
|
||||
cf.Project(*owned_qfs.back());
|
||||
if (Append(nullptr, owned_qfs.back().get(), window_title, keys))
|
||||
{
|
||||
cfs.Last() = &cf;
|
||||
}
|
||||
}
|
||||
|
||||
void GLVis::Append(VectorCoefficient &cf, QuadratureSpace &qs,
|
||||
std::string_view window_title,
|
||||
std::string_view keys)
|
||||
{
|
||||
owned_qfs.push_back(std::make_unique<QuadratureFunction>(qs, cf.GetVDim()));
|
||||
cf.Project(*owned_qfs.back());
|
||||
if (Append(nullptr, owned_qfs.back().get(), window_title, keys))
|
||||
{
|
||||
vcfs.Last() = &cf;
|
||||
}
|
||||
}
|
||||
|
||||
void GLVis::Update()
|
||||
{
|
||||
for (int i=0; i<sockets.size(); i++)
|
||||
{
|
||||
if (!sockets[i]->is_open() || !sockets[i]->good())
|
||||
{
|
||||
MFEM_WARNING("GLVis: Connection to " << hostname << ":" << port
|
||||
<< " for window " << i+1 << " lost.");
|
||||
continue;
|
||||
}
|
||||
#ifdef MFEM_USE_MPI
|
||||
if (parallel[i])
|
||||
{
|
||||
*sockets[i] << "parallel " << nrrank[i] << " " << myrank[i] <<
|
||||
"\n";
|
||||
}
|
||||
#endif
|
||||
if (gfs[i] != nullptr)
|
||||
{
|
||||
*sockets[i] << "solution\n" << *meshes[i] << *gfs[i];
|
||||
}
|
||||
else if (qfs[i] != nullptr)
|
||||
{
|
||||
if (cfs[i] != nullptr) { cfs[i]->Project(*qfs[i]); }
|
||||
else if (vcfs[i] != nullptr) { vcfs[i]->Project(*qfs[i]); }
|
||||
*sockets[i] << "quadrature\n" << *meshes[i] << *qfs[i];
|
||||
if (qfkey_has_Q[i]) { *sockets[i] << "keys QQQ\n"; }
|
||||
}
|
||||
*sockets[i] << std::flush;
|
||||
#ifdef MFEM_USE_MPI
|
||||
if (parallel[i])
|
||||
{
|
||||
MPI_Comm comm = static_cast<ParMesh*>(meshes[i])->GetComm();
|
||||
MPI_Barrier(comm);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mfem
|
||||
@@ -0,0 +1,122 @@
|
||||
#pragma once
|
||||
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include "mfem.hpp"
|
||||
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
|
||||
class TableLogger
|
||||
{
|
||||
public:
|
||||
enum dtype { DOUBLE, INT };
|
||||
|
||||
protected:
|
||||
// Double data to be printed.
|
||||
std::vector<double *> data_double;
|
||||
// Int data to be printed
|
||||
std::vector<int *> data_int;
|
||||
// Data type for each column
|
||||
std::vector<dtype> data_order;
|
||||
// Name of each monitored data
|
||||
std::vector<std::string> names;
|
||||
// Output stream
|
||||
std::ostream &os;
|
||||
// Column width
|
||||
int w;
|
||||
// Whether the variable name row has been printed or not
|
||||
bool var_name_printed;
|
||||
#ifdef MFEM_USE_MPI
|
||||
bool isRoot; // true if serial or root in parallel
|
||||
#else
|
||||
static constexpr bool isRoot = true;
|
||||
#endif
|
||||
std::unique_ptr<std::fstream> file;
|
||||
|
||||
private:
|
||||
public:
|
||||
// Create a logger that prints a row of variables for each call of Print
|
||||
TableLogger(std::ostream &os = std::cout);
|
||||
// Set column width of the table to be printed
|
||||
void setw(const int column_width) { w = column_width; }
|
||||
// Add double data to be monitored
|
||||
void Append(const std::string name, double &val);
|
||||
// Add double data to be monitored
|
||||
void Append(const std::string name, int &val);
|
||||
// Print a row of currently monitored data. If it is called
|
||||
void Print(bool print_valname=false);
|
||||
// Save data to a file whenever Print is called.
|
||||
void SaveWhenPrint(std::string filename,
|
||||
std::ios::openmode mode = std::ios::out);
|
||||
// Close file manually.
|
||||
void CloseFile() { if (file) { file.reset(nullptr); } }
|
||||
};
|
||||
|
||||
class GLVis
|
||||
{
|
||||
std::vector<std::unique_ptr<socketstream>> sockets;
|
||||
// Array<mfem::socketstream *> sockets;
|
||||
Array<mfem::GridFunction *> gfs;
|
||||
Array<mfem::QuadratureFunction *> qfs;
|
||||
Array<bool> qfkey_has_Q;
|
||||
Array<bool> qfhas_cf;
|
||||
Array<Coefficient*> cfs;
|
||||
Array<VectorCoefficient*> vcfs;
|
||||
std::vector<std::unique_ptr<QuadratureFunction>> owned_qfs;
|
||||
Array<Mesh *> meshes;
|
||||
Array<bool> parallel;
|
||||
Array<int> myrank;
|
||||
Array<int> nrrank;
|
||||
const char *hostname;
|
||||
const int port;
|
||||
int w, h, nrWinPerRow;
|
||||
bool secure;
|
||||
bool Append(GridFunction *gf, QuadratureFunction *qf,
|
||||
std::string_view window_title, std::string_view keys);
|
||||
|
||||
public:
|
||||
#ifdef MFEM_USE_GNUTLS
|
||||
static const bool secure_default = true;
|
||||
#else
|
||||
static const bool secure_default = false;
|
||||
#endif
|
||||
GLVis(const char hostname[], int port, int w=400, int h=350,
|
||||
int nrWinPerRow=1,
|
||||
bool secure = secure_default)
|
||||
: sockets(0), gfs(0), meshes(0), parallel(0), hostname(hostname),
|
||||
port(port), w(w), h(h), nrWinPerRow(nrWinPerRow),
|
||||
secure(secure_default) {}
|
||||
|
||||
void Append(GridFunction &gf,
|
||||
std::string_view window_title= {},
|
||||
std::string_view keys= {})
|
||||
{ Append(&gf, nullptr, window_title, keys); }
|
||||
void Append(QuadratureFunction &qf,
|
||||
std::string_view window_title= {},
|
||||
std::string_view keys= {})
|
||||
{ Append(nullptr, &qf, window_title, keys); }
|
||||
void Append(Coefficient &cf, QuadratureSpace &qs,
|
||||
std::string_view window_title= {},
|
||||
std::string_view keys= {});
|
||||
void Append(VectorCoefficient &cf, QuadratureSpace &qs,
|
||||
std::string_view window_title= {},
|
||||
std::string_view keys= {});
|
||||
void Update();
|
||||
|
||||
GridFunction& GetGridFunction(int i)
|
||||
{
|
||||
MFEM_VERIFY(i < gfs.Size(), "Index out of range");
|
||||
return *gfs[i];
|
||||
}
|
||||
|
||||
socketstream &GetSocket(int i)
|
||||
{
|
||||
MFEM_VERIFY(i < sockets.size(), "Index out of range");
|
||||
return *sockets[i];
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mfem
|
||||
+15
-25
@@ -13,7 +13,7 @@
|
||||
MFEM_DIR ?= ../..
|
||||
MFEM_BUILD_DIR ?= ../..
|
||||
MFEM_INSTALL_DIR ?= ../../mfem
|
||||
SRC = $(if $(MFEM_DIR:../..=),$(MFEM_DIR)/miniapps/autodiff/,)
|
||||
SRC = $(if $(MFEM_DIR:../..=),$(MFEM_DIR)/miniapps/electromagnetics/,)
|
||||
CONFIG_MK = $(or $(wildcard $(MFEM_BUILD_DIR)/config/config.mk),\
|
||||
$(wildcard $(MFEM_INSTALL_DIR)/share/mfem/config.mk))
|
||||
|
||||
@@ -21,49 +21,41 @@ CONFIG_MK = $(or $(wildcard $(MFEM_BUILD_DIR)/config/config.mk),\
|
||||
DEFAULTS_MK = $(MFEM_DIR)/config/defaults.mk
|
||||
include $(DEFAULTS_MK)
|
||||
|
||||
AD_COMMON_SRC = logger.cpp ad_native.cpp pg.cpp
|
||||
AD_COMMON_OBJ = $(AD_COMMON_SRC:.cpp=.o)
|
||||
|
||||
MFEM_LIB_FILE = mfem_is_not_built
|
||||
-include $(CONFIG_MK)
|
||||
|
||||
ADIFF_COMMON_SRC =
|
||||
ADIFF_COMMON_OBJ = $(ADIFF_COMMON_SRC:.cpp=.o)
|
||||
SEQ_MINIAPPS = ad_ex0 ad_ex1 ad_ex2 ad_ex3
|
||||
PAR_MINIAPPS = ad_ex6
|
||||
ifeq ($(MFEM_USE_PETSC),YES)
|
||||
PAR_MINIAPPS += ad_ex4 ad_ex5
|
||||
endif
|
||||
|
||||
SEQ_MINIAPPS = seq_example seq_test
|
||||
PAR_MINIAPPS = par_example
|
||||
ifeq ($(MFEM_USE_MPI),NO)
|
||||
MINIAPPS = $(SEQ_MINIAPPS)
|
||||
else
|
||||
MINIAPPS = $(PAR_MINIAPPS) $(SEQ_MINIAPPS)
|
||||
MINIAPPS = $(SEQ_MINIAPPS) $(PAR_MINIAPPS)
|
||||
endif
|
||||
|
||||
.SUFFIXES:
|
||||
.SUFFIXES: .o .cpp .mk
|
||||
.PHONY: all clean clean-build clean-exec
|
||||
.PRECIOUS: %.o
|
||||
|
||||
|
||||
# Remove built-in rules
|
||||
%: %.cpp
|
||||
%.o: %.cpp
|
||||
|
||||
%: %.o $(ADIFF_COMMON_OBJ)
|
||||
%: %.o $(AD_COMMON_OBJ)
|
||||
$(MFEM_CXX) $(MFEM_LINK_FLAGS) $^ -o $@ $(MFEM_LIBS)
|
||||
|
||||
%.o: $(SRC)%.cpp $(MFEM_LIB_FILE) $(CONFIG_MK)
|
||||
$(MFEM_CXX) $(MFEM_FLAGS) -c $< -o $@
|
||||
$(MFEM_CXX) $(MFEM_FLAGS) -I$(MFEM_DIR)/miniapps/autodiff -c $< -o $@
|
||||
|
||||
all: $(MINIAPPS)
|
||||
|
||||
MFEM_TESTS = MINIAPPS
|
||||
include $(MFEM_TEST_MK)
|
||||
|
||||
# Testing: Parallel vs. serial runs
|
||||
RUN_MPI = $(MFEM_MPIEXEC) $(MFEM_MPIEXEC_NP) $(MFEM_MPI_NP)
|
||||
TEST_NAME := ADIFF miniapp
|
||||
%-test-par: %
|
||||
@$(call mfem-test,$<, $(RUN_MPI), $(TEST_NAME))
|
||||
%-test-seq: %
|
||||
@$(call mfem-test,$<,, $(TEST_NAME))
|
||||
|
||||
# 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)
|
||||
@@ -72,7 +64,5 @@ clean: clean-build clean-exec
|
||||
|
||||
clean-build:
|
||||
rm -f *.o *~ $(SEQ_MINIAPPS) $(PAR_MINIAPPS)
|
||||
rm -rf ParaView
|
||||
rm -rf *.dSYM *.TVD.*breakpoints
|
||||
|
||||
clean-exec:
|
||||
@rm -rf Example*
|
||||
|
||||
@@ -1,550 +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.
|
||||
//
|
||||
// MFEM AD Example - Parallel Version
|
||||
//
|
||||
// Compile with: make par_example
|
||||
//
|
||||
// Sample runs: mpirun -np 2 par_example -m ../../data/beam-quad.mesh -pp 3.8
|
||||
// mpirun -np 2 par_example -m ../../data/beam-tri.mesh -pp 7.2
|
||||
// mpirun -np 2 par_example -m ../../data/beam-hex.mesh
|
||||
// mpirun -np 2 par_example -m ../../data/beam-tet.mesh
|
||||
// mpirun -np 2 par_example -m ../../data/beam-wedge.mesh
|
||||
//
|
||||
// Description: This examples solves a quasi-static nonlinear p-Laplacian
|
||||
// problem with zero Dirichlet boundary conditions applied on all
|
||||
// defined boundaries
|
||||
//
|
||||
// The example demonstrates the use of nonlinear operators
|
||||
// combined with automatic differentiation (AD). The integrators
|
||||
// are defined in example.hpp. Selecting integrator = 0 will use
|
||||
// the manually implemented integrator. Selecting integrator = 1
|
||||
// or 2 will utilize one of the AD integrators.
|
||||
//
|
||||
// We recommend viewing examples 1 and 19, before viewing this
|
||||
// example.
|
||||
|
||||
#include "example.hpp"
|
||||
|
||||
using namespace mfem;
|
||||
|
||||
enum IntegratorType
|
||||
{
|
||||
HandCodedIntegrator = 0,
|
||||
ADJacobianIntegrator = 1,
|
||||
ADHessianIntegrator = 2
|
||||
};
|
||||
|
||||
/// Non-linear solver for the p-Laplacian problem.
|
||||
class ParNLSolverPLaplacian
|
||||
{
|
||||
public:
|
||||
/// Constructor Input: imesh - FE mesh, finite element space, power for the
|
||||
/// p-Laplacian, external load (source, input), regularization parameter
|
||||
ParNLSolverPLaplacian(MPI_Comm comm, ParMesh& imesh,
|
||||
ParFiniteElementSpace& ifespace,
|
||||
real_t powerp=2,
|
||||
Coefficient* load=nullptr,
|
||||
real_t regularizationp=1e-7)
|
||||
{
|
||||
lcomm = comm;
|
||||
|
||||
// default parameters for the Newton solver
|
||||
newton_rtol = 1e-4;
|
||||
newton_atol = 1e-8;
|
||||
newton_iter = 10;
|
||||
|
||||
// linear solver
|
||||
linear_rtol = 1e-7;
|
||||
linear_atol = 1e-15;
|
||||
linear_iter = 500;
|
||||
|
||||
print_level = 0;
|
||||
|
||||
// set the mesh
|
||||
mesh=&imesh;
|
||||
|
||||
// set the fespace
|
||||
fespace=&ifespace;
|
||||
|
||||
// set the parameters
|
||||
plap_epsilon=new ConstantCoefficient(regularizationp);
|
||||
plap_power=new ConstantCoefficient(powerp);
|
||||
if (load==nullptr)
|
||||
{
|
||||
plap_input=new ConstantCoefficient(1.0);
|
||||
input_ownership=true;
|
||||
}
|
||||
else
|
||||
{
|
||||
plap_input=load;
|
||||
input_ownership=false;
|
||||
}
|
||||
|
||||
nlform=nullptr;
|
||||
nsolver=nullptr;
|
||||
gmres=nullptr;
|
||||
prec=nullptr;
|
||||
|
||||
// set the default integrator
|
||||
integ=IntegratorType::HandCodedIntegrator;
|
||||
}
|
||||
|
||||
~ParNLSolverPLaplacian()
|
||||
{
|
||||
delete nlform;
|
||||
delete nsolver;
|
||||
delete prec;
|
||||
delete gmres;
|
||||
if (input_ownership) { delete plap_input;}
|
||||
delete plap_epsilon;
|
||||
delete plap_power;
|
||||
}
|
||||
|
||||
/// Set the integrator.
|
||||
/// 0 - hand coded, 1 - AD based (compute only Hessian by AD),
|
||||
/// 2 - AD based (compute residual and Hessian by AD)
|
||||
void SetIntegrator(IntegratorType intr)
|
||||
{
|
||||
integ=intr;
|
||||
}
|
||||
|
||||
// set relative tolerance for the Newton solver
|
||||
void SetNRRTol(real_t rtol)
|
||||
{
|
||||
newton_rtol=rtol;
|
||||
}
|
||||
|
||||
// set absolute tolerance for the Newton solver
|
||||
void SetNRATol(real_t atol)
|
||||
{
|
||||
newton_atol=atol;
|
||||
}
|
||||
|
||||
// set max iterations for the NR solver
|
||||
void SetMaxNRIter(int miter)
|
||||
{
|
||||
newton_iter=miter;
|
||||
}
|
||||
|
||||
void SetLSRTol(real_t rtol)
|
||||
{
|
||||
linear_rtol=rtol;
|
||||
}
|
||||
|
||||
void SetLSATol(real_t atol)
|
||||
{
|
||||
linear_atol=atol;
|
||||
}
|
||||
|
||||
// set max iterations for the linear solver
|
||||
void SetMaxLSIter(int miter)
|
||||
{
|
||||
linear_iter=miter;
|
||||
}
|
||||
|
||||
// set the print level
|
||||
void SetPrintLevel(int plev)
|
||||
{
|
||||
print_level=plev;
|
||||
}
|
||||
|
||||
/// The state vector is used as initial condition for the NR solver. On
|
||||
/// return the statev holds the solution to the problem.
|
||||
void Solve(Vector& statev)
|
||||
{
|
||||
if (nlform==nullptr)
|
||||
{
|
||||
AllocSolvers();
|
||||
}
|
||||
Vector b; // RHS is zero
|
||||
nsolver->Mult(b, statev);
|
||||
}
|
||||
|
||||
/// Compute the energy
|
||||
real_t GetEnergy(Vector& statev)
|
||||
{
|
||||
if (nlform==nullptr)
|
||||
{
|
||||
// allocate the solvers
|
||||
AllocSolvers();
|
||||
}
|
||||
return nlform->GetEnergy(statev);
|
||||
}
|
||||
|
||||
private:
|
||||
void AllocSolvers()
|
||||
{
|
||||
if (nlform!=nullptr) { delete nlform;}
|
||||
if (nsolver!=nullptr) { delete nsolver;}
|
||||
if (gmres!=nullptr) { delete gmres;}
|
||||
if (prec!=nullptr) { delete prec;}
|
||||
|
||||
// Define the essential boundary attributes
|
||||
Array<int> ess_bdr(mesh->bdr_attributes.Max());
|
||||
ess_bdr = 1;
|
||||
|
||||
nlform = new ParNonlinearForm(fespace);
|
||||
if (integ==IntegratorType::HandCodedIntegrator)
|
||||
{
|
||||
nlform->AddDomainIntegrator(new pLaplace(*plap_power,*plap_epsilon,
|
||||
*plap_input));
|
||||
}
|
||||
else if (integ==IntegratorType::ADJacobianIntegrator)
|
||||
{
|
||||
// The template integrator is based on automatic differentiation. For
|
||||
// ADJacobianIntegrator the residual (vector function) at an
|
||||
// integration point is implemented as a functor by MyResidualFunctor.
|
||||
// The vector function has a return size of four(4), four state
|
||||
// arguments, and three(3) parameters. MyResidualFunctor is a template
|
||||
// argument to the actual template class performing the differentiation
|
||||
// - in this case, QVectorFuncAutoDiff. The derivatives are used in the
|
||||
// integration loop in the integrator pLaplaceAD.
|
||||
nlform->AddDomainIntegrator(new
|
||||
pLaplaceAD<mfem::QVectorFuncAutoDiff<MyResidualFunctor,4,4,3>>(*plap_power,
|
||||
*plap_epsilon,*plap_input));
|
||||
}
|
||||
else if (integ==IntegratorType::ADHessianIntegrator)
|
||||
{
|
||||
// The main difference from the previous case is that the user has to
|
||||
// implement only a functional evaluation at an integration point. The
|
||||
// implementation is in MyEnergyFunctor, which takes four state
|
||||
// arguments and three parameters. The residual vector is the first
|
||||
// derivative of the energy/functional with respect to the state
|
||||
// variables, and the Hessian is the second derivative. Automatic
|
||||
// differentiation is used for evaluating both of them.
|
||||
nlform->AddDomainIntegrator(new
|
||||
pLaplaceAD<mfem::QFunctionAutoDiff<MyEnergyFunctor,4,3>>(*plap_power,
|
||||
*plap_epsilon,*plap_input));
|
||||
}
|
||||
|
||||
nlform->SetEssentialBC(ess_bdr);
|
||||
|
||||
prec = new HypreBoomerAMG();
|
||||
prec->SetPrintLevel(print_level);
|
||||
|
||||
gmres = new GMRESSolver(lcomm);
|
||||
gmres->SetAbsTol(linear_atol);
|
||||
gmres->SetRelTol(linear_rtol);
|
||||
gmres->SetMaxIter(linear_iter);
|
||||
gmres->SetPrintLevel(print_level);
|
||||
gmres->SetPreconditioner(*prec);
|
||||
|
||||
nsolver = new NewtonSolver(lcomm);
|
||||
|
||||
nsolver->iterative_mode = true;
|
||||
nsolver->SetSolver(*gmres);
|
||||
nsolver->SetOperator(*nlform);
|
||||
nsolver->SetPrintLevel(print_level);
|
||||
nsolver->SetRelTol(newton_rtol);
|
||||
nsolver->SetAbsTol(newton_atol);
|
||||
nsolver->SetMaxIter(newton_iter);
|
||||
}
|
||||
|
||||
real_t newton_rtol;
|
||||
real_t newton_atol;
|
||||
int newton_iter;
|
||||
|
||||
real_t linear_rtol;
|
||||
real_t linear_atol;
|
||||
int linear_iter;
|
||||
|
||||
int print_level;
|
||||
|
||||
// power of the p-laplacian
|
||||
Coefficient* plap_power;
|
||||
// regularization parameter
|
||||
Coefficient* plap_epsilon;
|
||||
// load(input) parameter
|
||||
Coefficient* plap_input;
|
||||
// flag indicating the ownership of plap_input
|
||||
bool input_ownership;
|
||||
|
||||
MPI_Comm lcomm;
|
||||
|
||||
ParMesh *mesh;
|
||||
ParFiniteElementSpace *fespace;
|
||||
|
||||
ParNonlinearForm *nlform;
|
||||
|
||||
HypreBoomerAMG *prec;
|
||||
GMRESSolver *gmres;
|
||||
NewtonSolver *nsolver;
|
||||
IntegratorType integ;
|
||||
|
||||
};
|
||||
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
// 1. Initialize MPI and HYPRE.
|
||||
Mpi::Init(argc, argv);
|
||||
int myrank = Mpi::WorldRank();
|
||||
Hypre::Init();
|
||||
// Define Caliper ConfigManager
|
||||
#ifdef MFEM_USE_CALIPER
|
||||
cali::ConfigManager mgr;
|
||||
#endif
|
||||
// Caliper instrumentation
|
||||
MFEM_PERF_FUNCTION;
|
||||
|
||||
// 2. Parse command-line options
|
||||
const char *mesh_file = "../../data/beam-tet.mesh";
|
||||
int ser_ref_levels = 3;
|
||||
int par_ref_levels = 1;
|
||||
int order = 1;
|
||||
bool visualization = true;
|
||||
real_t newton_rel_tol = 1e-4;
|
||||
real_t newton_abs_tol = 1e-6;
|
||||
int newton_iter = 10;
|
||||
int print_level = 0;
|
||||
|
||||
real_t pp = 2.0; // p-Laplacian power
|
||||
|
||||
IntegratorType integrator = IntegratorType::ADHessianIntegrator;
|
||||
int int_integrator = integrator;
|
||||
// HandCodedIntegrator = 0 - do not use AD (hand coded)
|
||||
// ADJacobianIntegrator = 1 - use AD for Hessian only
|
||||
// ADHessianIntegrator = 2 - use AD for Residual and Hessian
|
||||
|
||||
const char* cali_config = "runtime-report";
|
||||
|
||||
OptionsParser args(argc, argv);
|
||||
args.AddOption(&mesh_file, "-m", "--mesh", "Mesh file to use.");
|
||||
args.AddOption(&ser_ref_levels,
|
||||
"-rs",
|
||||
"--refine-serial",
|
||||
"Number of times to refine the mesh uniformly in serial.");
|
||||
args.AddOption(&par_ref_levels,
|
||||
"-rp",
|
||||
"--refine-parallel",
|
||||
"Number of times to refine the mesh uniformly in parallel.");
|
||||
args.AddOption(&order,
|
||||
"-o",
|
||||
"--order",
|
||||
"Order (degree) of the finite elements.");
|
||||
args.AddOption(&visualization,
|
||||
"-vis",
|
||||
"--visualization",
|
||||
"-no-vis",
|
||||
"--no-visualization",
|
||||
"Enable or disable GLVis visualization.");
|
||||
args.AddOption(&newton_rel_tol,
|
||||
"-rel",
|
||||
"--relative-tolerance",
|
||||
"Relative tolerance for the Newton solve.");
|
||||
args.AddOption(&newton_abs_tol,
|
||||
"-abs",
|
||||
"--absolute-tolerance",
|
||||
"Absolute tolerance for the Newton solve.");
|
||||
args.AddOption(&newton_iter,
|
||||
"-it",
|
||||
"--newton-iterations",
|
||||
"Maximum iterations for the Newton solve.");
|
||||
args.AddOption(&pp,
|
||||
"-pp",
|
||||
"--power-parameter",
|
||||
"Power parameter (>=2.0) for the p-Laplacian.");
|
||||
args.AddOption((&print_level), "-prt", "--print-level", "Print level.");
|
||||
args.AddOption(&int_integrator,
|
||||
"-int",
|
||||
"--integrator",
|
||||
"Integrator 0: standard; 1: AD for Hessian; 2: AD for residual and Hessian");
|
||||
args.AddOption(&cali_config, "-p", "--caliper",
|
||||
"Caliper configuration string.");
|
||||
args.Parse();
|
||||
if (!args.Good())
|
||||
{
|
||||
if (myrank == 0)
|
||||
{
|
||||
args.PrintUsage(std::cout);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
if (myrank == 0)
|
||||
{
|
||||
args.PrintOptions(std::cout);
|
||||
}
|
||||
integrator = static_cast<IntegratorType>(int_integrator);
|
||||
|
||||
StopWatch *timer = new StopWatch();
|
||||
|
||||
// Caliper configuration
|
||||
#ifdef MFEM_USE_CALIPER
|
||||
mgr.add(cali_config);
|
||||
mgr.start();
|
||||
#endif
|
||||
// 3. Read the (serial) mesh from the given mesh file on all processors. We
|
||||
// can handle triangular, quadrilateral, tetrahedral and hexahedral meshes
|
||||
// with the same code.
|
||||
Mesh *mesh = new Mesh(mesh_file, 1, 1);
|
||||
int dim = mesh->Dimension();
|
||||
|
||||
// 4. Refine the mesh in serial to increase the resolution. In this example
|
||||
// we do 'ser_ref_levels' of uniform refinement, where 'ser_ref_levels' is
|
||||
// a command-line parameter.
|
||||
for (int lev = 0; lev < ser_ref_levels; lev++)
|
||||
{
|
||||
mesh->UniformRefinement();
|
||||
}
|
||||
|
||||
// 5. Define a parallel mesh by a partitioning of the serial mesh. Refine
|
||||
// this mesh further in parallel to increase the resolution. Once the
|
||||
// parallel mesh is defined, the serial mesh can be deleted.
|
||||
ParMesh *pmesh = new ParMesh(MPI_COMM_WORLD, *mesh);
|
||||
delete mesh;
|
||||
for (int lev = 0; lev < par_ref_levels; lev++)
|
||||
{
|
||||
pmesh->UniformRefinement();
|
||||
}
|
||||
|
||||
// 6. Define the load for the p-Laplacian
|
||||
ConstantCoefficient load(1.00);
|
||||
|
||||
// 7. Define the finite element spaces for the solution
|
||||
H1_FECollection fec(order, dim);
|
||||
ParFiniteElementSpace fespace(pmesh, &fec, 1, Ordering::byVDIM);
|
||||
HYPRE_Int glob_size = fespace.GlobalTrueVSize();
|
||||
if (myrank == 0)
|
||||
{
|
||||
std::cout << "Number of finite element unknowns: " << glob_size
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// 8. Define the solution vector x as a parallel finite element grid function
|
||||
// corresponding to fespace. Initialize x with initial guess of zero,
|
||||
// which satisfies the boundary conditions.
|
||||
ParGridFunction x(&fespace);
|
||||
x = 0.0;
|
||||
HypreParVector *sv = x.GetTrueDofs();
|
||||
|
||||
// 9. Define ParaView DataCollection
|
||||
ParaViewDataCollection *dacol = new ParaViewDataCollection("Example",
|
||||
pmesh);
|
||||
dacol->SetLevelsOfDetail(order);
|
||||
dacol->RegisterField("sol", &x);
|
||||
|
||||
// 10. Define the NR solver
|
||||
ParNLSolverPLaplacian* nr;
|
||||
|
||||
// 11. Start with linear diffusion - solvable for any initial guess
|
||||
nr=new ParNLSolverPLaplacian(MPI_COMM_WORLD,*pmesh, fespace, 2.0, &load);
|
||||
nr->SetIntegrator(integrator);
|
||||
nr->SetMaxNRIter(newton_iter);
|
||||
nr->SetNRATol(newton_abs_tol);
|
||||
nr->SetNRRTol(newton_rel_tol);
|
||||
nr->SetPrintLevel(print_level);
|
||||
timer->Clear();
|
||||
timer->Start();
|
||||
nr->Solve(*sv);
|
||||
timer->Stop();
|
||||
if (myrank==0)
|
||||
{
|
||||
std::cout << "[pp=2] The solution time is: " << timer->RealTime()
|
||||
<< std::endl;
|
||||
}
|
||||
// Compute the energy
|
||||
real_t energy = nr->GetEnergy(*sv);
|
||||
if (myrank==0)
|
||||
{
|
||||
std::cout << "[pp=2] The total energy of the system is E=" << energy
|
||||
<< std::endl;
|
||||
}
|
||||
delete nr;
|
||||
x.SetFromTrueDofs(*sv);
|
||||
dacol->SetTime(2.0);
|
||||
dacol->SetCycle(2);
|
||||
dacol->Save();
|
||||
|
||||
// 12. Continue with powers higher than 2
|
||||
for (int i = 3; i < pp; i++)
|
||||
{
|
||||
nr=new ParNLSolverPLaplacian(MPI_COMM_WORLD,*pmesh, fespace, (real_t)i, &load);
|
||||
nr->SetIntegrator(integrator);
|
||||
nr->SetMaxNRIter(newton_iter);
|
||||
nr->SetNRATol(newton_abs_tol);
|
||||
nr->SetNRRTol(newton_rel_tol);
|
||||
nr->SetPrintLevel(print_level);
|
||||
timer->Clear();
|
||||
timer->Start();
|
||||
nr->Solve(*sv);
|
||||
timer->Stop();
|
||||
if (myrank==0)
|
||||
{
|
||||
std::cout << "[pp="<<i<<"] The solution time is: " << timer->RealTime()
|
||||
<< std::endl;
|
||||
}
|
||||
// Compute the energy
|
||||
energy = nr->GetEnergy(*sv);
|
||||
if (myrank==0)
|
||||
{
|
||||
std::cout << "[pp="<<i<<"] The total energy of the system is E=" << energy
|
||||
<< std::endl;
|
||||
}
|
||||
delete nr;
|
||||
x.SetFromTrueDofs(*sv);
|
||||
dacol->SetTime((real_t)i);
|
||||
dacol->SetCycle(i);
|
||||
dacol->Save();
|
||||
}
|
||||
|
||||
// 13. Continue with the final power
|
||||
if (std::abs(pp - 2.0) > std::numeric_limits<real_t>::epsilon())
|
||||
{
|
||||
nr=new ParNLSolverPLaplacian(MPI_COMM_WORLD,*pmesh, fespace, pp, &load);
|
||||
nr->SetIntegrator(integrator);
|
||||
nr->SetMaxNRIter(newton_iter);
|
||||
nr->SetNRATol(newton_abs_tol);
|
||||
nr->SetNRRTol(newton_rel_tol);
|
||||
nr->SetPrintLevel(print_level);
|
||||
timer->Clear();
|
||||
timer->Start();
|
||||
nr->Solve(*sv);
|
||||
timer->Stop();
|
||||
if (myrank==0)
|
||||
{
|
||||
std::cout << "[pp="<<pp<<"] The solution time is: " << timer->RealTime()
|
||||
<< std::endl;
|
||||
}
|
||||
// Compute the energy
|
||||
energy = nr->GetEnergy(*sv);
|
||||
if (myrank==0)
|
||||
{
|
||||
std::cout << "[pp="<<pp<<"] The total energy of the system is E=" << energy
|
||||
<< std::endl;
|
||||
}
|
||||
delete nr;
|
||||
x.SetFromTrueDofs(*sv);
|
||||
dacol->SetTime(pp);
|
||||
if (pp < 2.0)
|
||||
{
|
||||
dacol->SetCycle(static_cast<int>(std::floor(pp)));
|
||||
}
|
||||
else
|
||||
{
|
||||
dacol->SetCycle(static_cast<int>(std::ceil(pp)));
|
||||
}
|
||||
dacol->Save();
|
||||
}
|
||||
|
||||
// 14. Free the used memory
|
||||
delete dacol;
|
||||
delete sv;
|
||||
delete pmesh;
|
||||
delete timer;
|
||||
|
||||
// Flush output before MPI_finalize
|
||||
#ifdef MFEM_USE_CALIPER
|
||||
mgr.flush();
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
#include "pg.hpp"
|
||||
namespace mfem
|
||||
{
|
||||
PGStepSizeRule::PGStepSizeRule(int rule_type,
|
||||
real_t alpha0, real_t max_alpha,
|
||||
real_t ratio, real_t ratio2)
|
||||
: rule_type(static_cast<RuleType>(rule_type))
|
||||
, max_alpha(max_alpha), alpha0(alpha0), ratio(ratio), ratio2(ratio2)
|
||||
{
|
||||
MFEM_VERIFY(rule_type < RuleType::INVALID,
|
||||
"PGStepSizeRule: Invalid rule type");
|
||||
MFEM_VERIFY(alpha0 > 0, "PGStepSizeRule: alpha0 must be positive");
|
||||
MFEM_VERIFY(max_alpha >= alpha0,
|
||||
"PGStepSizeRule: max_alpha must be greater than or equal to alpha0");
|
||||
if (rule_type == RuleType::CONSTANT)
|
||||
{
|
||||
}
|
||||
else if (rule_type == RuleType::POLY)
|
||||
{
|
||||
MFEM_VERIFY(ratio > 0, "PGStepSizeRule: ratio must be positive for POLY rule");
|
||||
}
|
||||
else if (rule_type == RuleType::EXP)
|
||||
{
|
||||
MFEM_VERIFY(ratio > 1,
|
||||
"PGStepSizeRule: ratio must be greater than 1 for EXP rule");
|
||||
}
|
||||
else if (rule_type == RuleType::DOUBLE_EXP)
|
||||
{
|
||||
MFEM_VERIFY(ratio > 1 && ratio2 > 1,
|
||||
"PGStepSizeRule: ratio and ratio2 must be greater than 1 for DOUBLE_EXP rule");
|
||||
}
|
||||
}
|
||||
|
||||
real_t PGStepSizeRule::Get(int iter) const
|
||||
{
|
||||
real_t alpha = alpha0;
|
||||
switch (rule_type)
|
||||
{
|
||||
case RuleType::CONSTANT:
|
||||
break;
|
||||
case RuleType::POLY:
|
||||
alpha *= std::pow(iter+1, ratio);
|
||||
break;
|
||||
case RuleType::EXP:
|
||||
alpha *= std::pow(ratio, iter);
|
||||
break;
|
||||
case RuleType::DOUBLE_EXP:
|
||||
alpha *= std::pow(ratio, std::pow(ratio2, iter));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return std::min(alpha, max_alpha);
|
||||
}
|
||||
|
||||
const GridFunction& ADPGFunctional::GetPrevLatent(int i) const
|
||||
{
|
||||
Evaluator::param_t param = evaluator.Get(i);
|
||||
const GridFunction* gf = std::visit([&](auto arg)
|
||||
{
|
||||
using T = std::decay_t<decltype(arg)>;
|
||||
if constexpr (std::is_same_v<T, const GridFunction*>)
|
||||
{
|
||||
return (const GridFunction*)arg;
|
||||
}
|
||||
else
|
||||
{
|
||||
MFEM_ABORT("Parameter at index " << i
|
||||
<< " is not a GridFunction or ParGridFunction");
|
||||
return (const GridFunction*)nullptr;
|
||||
}
|
||||
}, param);
|
||||
MFEM_VERIFY(gf != nullptr,
|
||||
"ADPGFunctional: GetPrevLatent(" << i << ") is null");
|
||||
return *gf;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,472 @@
|
||||
#pragma once
|
||||
#include "mfem.hpp"
|
||||
#include "ad_native.hpp"
|
||||
#include "tools.hpp"
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
// PGStepSizeRule defines the step size rule for the Proximal Galerkin (PG) method.
|
||||
// See RuleType for the available rules
|
||||
struct PGStepSizeRule
|
||||
{
|
||||
enum RuleType
|
||||
{
|
||||
CONSTANT, // alpha0
|
||||
POLY, // alpha0 * (iter+1)^ratio
|
||||
EXP, // alpha0 * ratio^iter
|
||||
DOUBLE_EXP, // alpha0 * ratio^(ratio2^iter)
|
||||
// ... add more rules as needed
|
||||
INVALID // used to check for valid rule types
|
||||
};
|
||||
RuleType rule_type;
|
||||
|
||||
real_t max_alpha;
|
||||
real_t alpha0; // initial step size
|
||||
real_t ratio; // poly degree (POLY), exponential base (EXP, DOUBLE_EXP)
|
||||
real_t ratio2; // nested exponential base (DOUBLE_EXP)
|
||||
|
||||
PGStepSizeRule(int rule_type,
|
||||
real_t alpha0 = 1.0, real_t max_alpha = 1e06,
|
||||
real_t ratio = -1.0, real_t ratio2 = -1.0);
|
||||
|
||||
/// Get the step size for the given iteration
|
||||
real_t Get(int iter) const;
|
||||
};
|
||||
|
||||
// Base struct for dual entropy functions
|
||||
class ADEntropy : public ADFunction
|
||||
{
|
||||
public:
|
||||
ADEntropy(int n_input)
|
||||
: ADFunction(n_input) { }
|
||||
ADEntropy(int n_input, int capacity)
|
||||
: ADFunction(n_input, capacity) { }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
std::vector<T*> uniquevec2ptrvec(std::vector<std::unique_ptr<T>> &vec)
|
||||
{
|
||||
std::vector<T*> ptrs(vec.size());
|
||||
for (int i=0; i<vec.size(); i++)
|
||||
{
|
||||
ptrs[i] = vec[i].get();
|
||||
}
|
||||
return ptrs;
|
||||
}
|
||||
|
||||
|
||||
// Construct augmented energy for proximal Galerkin
|
||||
// psi =
|
||||
// L(u, psi) = f(u) + (1/alpha)(u*(psi-psi_k) - E^*(psi))
|
||||
// Equivalently, L(u, lambda) = f(u) + (u*lambda - E^*(alpha*lambda + psi_k))
|
||||
// so that
|
||||
// dL/du = df/du + (1/alpha)(psi-psi_k)
|
||||
// dL/dpsi = (1/alpha)(u - dE^*(psi))
|
||||
// When primal is not full vector, set primal_begin
|
||||
// The parameter should be [org_param, entropy_param, alpha, psi_k]
|
||||
class ADPGFunctional : public ADFunction
|
||||
{
|
||||
protected:
|
||||
ADFunction &f;
|
||||
std::vector<ADEntropy*> dual_entropy;
|
||||
std::vector<int> primal_idx;
|
||||
std::vector<int> dual_idx;
|
||||
std::vector<int> entropy_size;
|
||||
mutable const BlockVector *latent_k;
|
||||
mutable Vector jac;
|
||||
mutable DenseMatrix hess;
|
||||
mutable real_t alpha;
|
||||
std::unique_ptr<VectorCoefficient> owned_cf;
|
||||
static int GetEntropySize(const std::vector<ADEntropy*> &dual_entropy)
|
||||
{
|
||||
int size = 0;
|
||||
for (const auto &entropy : dual_entropy)
|
||||
{
|
||||
size += entropy->n_input;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
public:
|
||||
ADPGFunctional(ADFunction &f, ADEntropy &dual_entropy, Evaluator::param_t alpha,
|
||||
int idx=0)
|
||||
: ADFunction(f.n_input + dual_entropy.n_input, 1)
|
||||
, f(f), dual_entropy{&dual_entropy}
|
||||
, primal_idx(1)
|
||||
, dual_idx(1)
|
||||
, entropy_size(1)
|
||||
{
|
||||
evaluator.Add(alpha);
|
||||
this->primal_idx[0] = idx;
|
||||
entropy_size[0] = dual_entropy.n_input;
|
||||
MFEM_VERIFY(f.n_input >= this->primal_idx[0] + entropy_size[0],
|
||||
"ADPGFunctional: f.n_input must not exceed "
|
||||
"primal_begin + dual_entropy.n_input:"
|
||||
<< f.n_input << " >= " << n_input);
|
||||
dual_idx[0] = f.n_input;
|
||||
}
|
||||
ADPGFunctional(ADFunction &f, ADEntropy &dual_entropy,
|
||||
Evaluator::param_t alpha,
|
||||
GridFunction &latent_k, int idx=0)
|
||||
: ADPGFunctional(f, dual_entropy, alpha, idx)
|
||||
{
|
||||
evaluator.Add(&latent_k);
|
||||
}
|
||||
// Multiple entropies
|
||||
ADPGFunctional(ADFunction &f, std::vector<ADEntropy*> dual_entropy_,
|
||||
std::vector<int> &primal_begin, Evaluator::param_t alpha)
|
||||
: ADFunction(f.n_input + GetEntropySize(dual_entropy_), 1)
|
||||
, f(f), dual_entropy(std::move(dual_entropy_))
|
||||
, primal_idx(primal_begin)
|
||||
, dual_idx(dual_entropy.size())
|
||||
, entropy_size(dual_entropy.size())
|
||||
, alpha(*evaluator.val.GetBlock(0).GetData())
|
||||
{
|
||||
evaluator.Add(alpha);
|
||||
int dual_entropy_size = 0;
|
||||
int max_primal_index = 0;
|
||||
for (int i=0; i<dual_entropy.size(); i++)
|
||||
{
|
||||
dual_entropy_size += dual_entropy[i]->n_input;
|
||||
max_primal_index = std::max(max_primal_index,
|
||||
primal_begin[i] + dual_entropy[i]->n_input);
|
||||
}
|
||||
MFEM_VERIFY(f.n_input >= max_primal_index,
|
||||
"ADPGFunctional: f.n_input must be larger than "
|
||||
"primal_begin[i] + dual_entropy.n_input[i] for all i");
|
||||
}
|
||||
|
||||
ADPGFunctional(ADFunction &f, std::vector<ADEntropy*> dual_entropy,
|
||||
std::vector<GridFunction*> latent_k_gf, std::vector<int> &primal_begin,
|
||||
Evaluator::param_t alpha)
|
||||
: ADPGFunctional(f, std::move(dual_entropy), primal_begin, alpha)
|
||||
{
|
||||
MFEM_VERIFY(latent_k_gf.size() == this->dual_entropy.size(),
|
||||
"ADPGFunctional: latent_k must have the same size as dual_entropy: "
|
||||
<< latent_k_gf.size() << " != " << dual_entropy.size());
|
||||
MFEM_VERIFY(latent_k_gf.size() == primal_begin.size(),
|
||||
"ADPGFunctional: latent_k must have the same size as primal_begin"
|
||||
<< latent_k_gf.size() << " != " << primal_begin.size());
|
||||
for (int i=0; i<latent_k_gf.size(); i++)
|
||||
{
|
||||
MFEM_VERIFY(latent_k_gf[i] != nullptr,
|
||||
"ADPGFunctional: latent_k_gf[" << i << "] is null");
|
||||
evaluator.Add(latent_k_gf[i]);
|
||||
}
|
||||
}
|
||||
// Multiple entropies
|
||||
ADPGFunctional(ADFunction &f,
|
||||
std::vector<std::unique_ptr<ADEntropy>> &dual_entropy,
|
||||
std::vector<int> &primal_begin, Evaluator::param_t alpha)
|
||||
: ADPGFunctional(f, uniquevec2ptrvec(dual_entropy), primal_begin, alpha)
|
||||
{}
|
||||
ADPGFunctional(ADFunction &f,
|
||||
std::vector<std::unique_ptr<ADEntropy>> &dual_entropy,
|
||||
std::vector<std::unique_ptr<GridFunction>> &latent_k_gf,
|
||||
std::vector<int> primal_begin, Evaluator::param_t alpha)
|
||||
: ADPGFunctional(f, uniquevec2ptrvec(dual_entropy),
|
||||
uniquevec2ptrvec(latent_k_gf), primal_begin, alpha)
|
||||
{}
|
||||
|
||||
const GridFunction& GetPrevLatent(int i) const;
|
||||
|
||||
ADFunction &GetObjective() const
|
||||
{ return f; }
|
||||
|
||||
ADEntropy &GetEntropy() const
|
||||
{
|
||||
MFEM_VERIFY(dual_entropy.size() == 1,
|
||||
"ADPGFunctional: GetEntropy() can only be called when there is a single entropy");
|
||||
return *dual_entropy[0];
|
||||
}
|
||||
const std::vector<ADEntropy*> &GetEntropies() const
|
||||
{ return dual_entropy; }
|
||||
|
||||
real_t GetAlpha() const { return alpha; }
|
||||
|
||||
void ProcessParameters(ElementTransformation &Tr,
|
||||
const IntegrationPoint &ip) const override
|
||||
{
|
||||
for (int i=0; i<dual_entropy.size(); i++)
|
||||
{
|
||||
dual_entropy[i]->ProcessParameters(Tr, ip);
|
||||
}
|
||||
f.ProcessParameters(Tr, ip);
|
||||
latent_k = &evaluator.Eval(Tr, ip);
|
||||
alpha = evaluator.val[0];
|
||||
}
|
||||
|
||||
AD_IMPL(T, V, M, x_psi,
|
||||
{
|
||||
// variables
|
||||
const V x(x_psi.GetData(), f.n_input);
|
||||
V psi;
|
||||
|
||||
// evaluate mixed value
|
||||
T cross_entropy = T();
|
||||
T dual_entropy_sum = T();
|
||||
for (int i=0; i<entropy_size.size(); i++)
|
||||
{
|
||||
psi.SetDataAndSize(x_psi.GetData() + dual_idx[i], entropy_size[i]);
|
||||
const Vector &psi_k = latent_k->GetBlock(i+1);
|
||||
for (int j=0; j<entropy_size[i]; j++)
|
||||
{
|
||||
cross_entropy += x[primal_idx[i] + j]*(psi[j] - psi_k[j]);
|
||||
}
|
||||
dual_entropy_sum += (*dual_entropy[i])(psi);
|
||||
}
|
||||
return f(x) + (cross_entropy - dual_entropy_sum)/alpha;
|
||||
});
|
||||
};
|
||||
|
||||
class ADLambdaPGFunctional : public ADPGFunctional
|
||||
{
|
||||
using ADPGFunctional::ADPGFunctional;
|
||||
|
||||
AD_IMPL(T, V, M, x_lambda,
|
||||
{
|
||||
// variables
|
||||
const V x(x_lambda.GetData(), f.n_input);
|
||||
V lambda;
|
||||
V latent;
|
||||
|
||||
// evaluate mixed value
|
||||
T cross_entropy = T();
|
||||
T dual_entropy_sum = T();
|
||||
for (int i=0; i<entropy_size.size(); i++)
|
||||
{
|
||||
lambda.SetDataAndSize(x_lambda.GetData() + dual_idx[i], entropy_size[i]);
|
||||
for (int j=0; j<entropy_size[i]; j++)
|
||||
{
|
||||
cross_entropy += x[primal_idx[i] + j]*lambda[j];
|
||||
}
|
||||
latent = latent_k->GetBlock(i+1);
|
||||
latent.Add(alpha, lambda);
|
||||
dual_entropy_sum += (*dual_entropy[i])(latent);
|
||||
}
|
||||
return f(x) + cross_entropy - dual_entropy_sum/alpha;
|
||||
});
|
||||
};
|
||||
|
||||
enum LatentType
|
||||
{
|
||||
COEFFICIENT,
|
||||
GF,
|
||||
QF
|
||||
};
|
||||
|
||||
|
||||
// Dual entropy for (negative) Shannon entropy (xlogx - x) with half bound
|
||||
// when bound[1] = 1, [lower, inf[
|
||||
// when bound[1] = -1, ]-inf, upper]
|
||||
//
|
||||
// The resulting dual is (f(pm1*(x - shift)))^*
|
||||
// = f^*(pm1*x^*) + shift*pm1*x^*
|
||||
class ShannonEntropy : public ADEntropy
|
||||
{
|
||||
protected:
|
||||
const real_t &bound;
|
||||
int sign;
|
||||
public:
|
||||
ShannonEntropy(Evaluator::param_t bound, int sign=1)
|
||||
: ADEntropy(1, 1)
|
||||
, bound(*evaluator.val.GetData())
|
||||
, sign(sign)
|
||||
{
|
||||
evaluator.Add(bound);
|
||||
MFEM_VERIFY(sign == 1 || sign == -1,
|
||||
"ShannonEntropy: sign must be 1 or -1");
|
||||
MFEM_VERIFY(evaluator.val.GetBlock(0).Size() == 1,
|
||||
"ShannonEntropy: The provided bound has the wrong size. "
|
||||
"Expected 1, got " << evaluator.val.GetBlock(0).Size());
|
||||
}
|
||||
AD_IMPL(T, V, M, x, return sign*(exp(x[0]*sign)) + bound*x[0]; );
|
||||
};
|
||||
|
||||
// Dual entropy for (negative) Fermi-Dirac with [lower, upper] bounds
|
||||
class FermiDiracEntropy : public ADEntropy
|
||||
{
|
||||
protected:
|
||||
const real_t &upper_bound;
|
||||
const real_t &lower_bound;
|
||||
mutable real_t shift;
|
||||
mutable real_t scale;
|
||||
public:
|
||||
FermiDiracEntropy(Evaluator::param_t lower_bound,
|
||||
Evaluator::param_t upper_bound)
|
||||
: ADEntropy(1, 2)
|
||||
, upper_bound(*evaluator.val.GetData())
|
||||
, lower_bound(*(evaluator.val.GetData()+1))
|
||||
{
|
||||
evaluator.Add(lower_bound);
|
||||
evaluator.Add(upper_bound);
|
||||
MFEM_VERIFY(evaluator.val.GetBlock(0).Size() == 1,
|
||||
"FermiDiracEntropy: The provided bound has the wrong size. "
|
||||
"Expected 1, got " << evaluator.val.GetBlock(0).Size());
|
||||
MFEM_VERIFY(evaluator.val.GetBlock(1).Size() == 1,
|
||||
"FermiDiracEntropy: The provided bound has the wrong size. "
|
||||
"Expected 1, got " << evaluator.val.GetBlock(1).Size());
|
||||
}
|
||||
void ProcessParameters(const BlockVector &x) const override
|
||||
{
|
||||
shift = lower_bound;
|
||||
scale = upper_bound - shift;
|
||||
}
|
||||
AD_IMPL(T, V, M, x,
|
||||
{
|
||||
T z = x[0]*scale;
|
||||
|
||||
// Use a numerically stable implementation of log(1+exp(z))
|
||||
if (z > 0)
|
||||
{
|
||||
return z + log(1.0 + exp(-z)) + shift*x[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
return log(1.0 + exp(z)) + shift*x[0];
|
||||
}
|
||||
});
|
||||
};
|
||||
// Dual entropy for (negative) Hellinger entropy with bound > 0
|
||||
class HellingerEntropy : public ADEntropy
|
||||
{
|
||||
const real_t &scale;
|
||||
public:
|
||||
HellingerEntropy(int dim, Evaluator::param_t bound)
|
||||
: ADEntropy(dim, 1)
|
||||
, scale(*evaluator.val.GetData())
|
||||
{
|
||||
evaluator.Add(bound);
|
||||
MFEM_VERIFY(evaluator.val.GetBlock(0).Size() == 1,
|
||||
"HellingerEntropy: The provided bound has the wrong size. "
|
||||
"Expected 1, got " << evaluator.val.GetBlock(0).Size());
|
||||
}
|
||||
void ProcessParameters(const BlockVector &x) const override
|
||||
{
|
||||
MFEM_ASSERT(scale > 0, "HellingerEntropy: bound must be positive");
|
||||
}
|
||||
AD_IMPL(T, V, M, x, return sqrt(1 + (x*x)*(scale*scale)););
|
||||
};
|
||||
|
||||
// Dual entropy for (negative) Simplex entropy with
|
||||
// x_i >= 0 sum_i x_i = bound
|
||||
// Also known as cateborical entropy or multinomial Shannon entropy
|
||||
class SimplexEntropy : public ADEntropy
|
||||
{
|
||||
const real_t &scale;
|
||||
public:
|
||||
SimplexEntropy(int n_input, Evaluator::param_t bound)
|
||||
: ADEntropy(n_input, 1), scale(*evaluator.val.GetData())
|
||||
{
|
||||
evaluator.Add(bound);
|
||||
MFEM_VERIFY(evaluator.val.GetBlock(0).Size() == 1,
|
||||
"SimplexEntropy: The provided bound has the wrong size. "
|
||||
"Expected 1, got " << evaluator.val.GetBlock(0).Size());
|
||||
}
|
||||
|
||||
void ProcessParameters(const BlockVector &x) const override
|
||||
{
|
||||
MFEM_ASSERT(scale >= 0, "SimplexEntropy: bound must be non-negative");
|
||||
}
|
||||
AD_IMPL(T, V, M, x,
|
||||
{
|
||||
T maxval = x[0];
|
||||
for (int i=1; i<x.Size(); i++) { maxval = max(maxval, x[i]); }
|
||||
|
||||
T sum_exp = T();
|
||||
for (int i=0; i<x.Size(); i++)
|
||||
{
|
||||
sum_exp += exp(x[i]-maxval);
|
||||
}
|
||||
return scale*(maxval + log(sum_exp));
|
||||
});
|
||||
};
|
||||
|
||||
#ifdef MFEM_USE_PETSC
|
||||
class PetscOperatorWrapper : public Operator
|
||||
{
|
||||
protected:
|
||||
MPI_Comm comm;
|
||||
Operator &op;
|
||||
Operator::Type mtype;
|
||||
mutable std::unique_ptr<PetscParMatrix> petsc_matrix;
|
||||
public:
|
||||
PetscOperatorWrapper(MPI_Comm comm, Operator &op,
|
||||
Operator::Type mtype = Operator::Type::PETSC_MATAIJ)
|
||||
: Operator(op.Height(), op.Width()), comm(comm), op(op), mtype(mtype)
|
||||
{ }
|
||||
|
||||
void Mult(const Vector &x, Vector &y) const override
|
||||
{
|
||||
op.Mult(x, y);
|
||||
}
|
||||
|
||||
Operator &GetGradient(const Vector &x) const override
|
||||
{
|
||||
auto &grad = op.GetGradient(x);
|
||||
petsc_matrix = std::make_unique<PetscParMatrix>(comm, &grad, mtype);
|
||||
return *petsc_matrix;
|
||||
}
|
||||
};
|
||||
|
||||
class NewtonLinearSolverMonitor : public IterativeSolverController
|
||||
{
|
||||
protected:
|
||||
/// The last IterativeSolver to which this controller was attached.
|
||||
const class IterativeSolver *iter_solver;
|
||||
#ifdef MFEM_USE_PETSC
|
||||
PetscLinearSolver *petsc_solver;
|
||||
#endif
|
||||
IterativeSolver *mfem_solver;
|
||||
|
||||
int numIterations=0;
|
||||
int prefix=0;
|
||||
bool is_root = true;
|
||||
bool converged = false;
|
||||
|
||||
public:
|
||||
#ifdef MFEM_USE_PETSC
|
||||
NewtonLinearSolverMonitor(PetscLinearSolver &linear_solver)
|
||||
: petsc_solver(&linear_solver)
|
||||
{
|
||||
is_root = Mpi::Root();
|
||||
}
|
||||
#endif
|
||||
NewtonLinearSolverMonitor(IterativeSolver &linear_solver)
|
||||
: mfem_solver(&linear_solver)
|
||||
{
|
||||
#ifdef MFEM_USE_MPI
|
||||
is_root = Mpi::Root();
|
||||
#endif
|
||||
}
|
||||
|
||||
void SetPrefix(size_t i) { prefix = i; }
|
||||
|
||||
virtual void Reset()
|
||||
{
|
||||
converged = false;
|
||||
numIterations = 0;
|
||||
}
|
||||
|
||||
/// Monitor the solution vector r
|
||||
virtual void MonitorResidual(int it, real_t norm, const Vector &r,
|
||||
bool final)
|
||||
{
|
||||
if (final && is_root)
|
||||
{
|
||||
for (int i=0; i<prefix; i++) { out << " "; }
|
||||
out << "Average Linear Solver Iterations: " << (numIterations /
|
||||
(it + 1.)) << std::endl;
|
||||
numIterations = 0;
|
||||
return;
|
||||
}
|
||||
#ifdef MFEM_USE_PETSC
|
||||
if (petsc_solver) { numIterations += petsc_solver->GetNumIterations(); }
|
||||
#endif
|
||||
if (mfem_solver) { numIterations += mfem_solver->GetNumIterations(); }
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
} // namespace mfem
|
||||
@@ -1,478 +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.
|
||||
//
|
||||
// MFEM AD Example - Serial Version
|
||||
//
|
||||
// Compile with: make seq_example
|
||||
//
|
||||
// Sample runs: seq_example -m ../../data/beam-quad.mesh -pp 3.5
|
||||
// seq_example -m ../../data/beam-tri.mesh -pp 4.6
|
||||
// seq_example -m ../../data/beam-hex.mesh
|
||||
// seq_example -m ../../data/beam-tet.mesh
|
||||
// seq_example -m ../../data/beam-wedge.mesh
|
||||
//
|
||||
// Description: This examples solves a quasi-static nonlinear p-Laplacian
|
||||
// problem with zero Dirichlet boundary conditions applied on all
|
||||
// defined boundaries
|
||||
//
|
||||
// The example demonstrates the use of nonlinear operators
|
||||
// combined with automatic differentiation (AD). The integrators
|
||||
// are defined in example.hpp. Selecting integrator = 0 will use
|
||||
// the manually implemented integrator. Selecting integrator = 1
|
||||
// or 2 will utilize one of the AD integrators.
|
||||
//
|
||||
// We recommend viewing examples 1 and 19, before viewing this
|
||||
// example.
|
||||
|
||||
#include "example.hpp"
|
||||
|
||||
using namespace mfem;
|
||||
|
||||
enum IntegratorType
|
||||
{
|
||||
HandCodedIntegrator = 0,
|
||||
ADJacobianIntegrator = 1,
|
||||
ADHessianIntegrator = 2
|
||||
};
|
||||
|
||||
/// Non-linear solver for the p-Laplacian problem.
|
||||
class NLSolverPLaplacian
|
||||
{
|
||||
public:
|
||||
/// Constructor Input: imesh - FE mesh, finite element space, power for the
|
||||
/// p-Laplacian, external load (source, input), regularization parameter
|
||||
NLSolverPLaplacian(Mesh& imesh, FiniteElementSpace& ifespace,
|
||||
real_t powerp=2,
|
||||
Coefficient* load=nullptr,
|
||||
real_t regularizationp=1e-7)
|
||||
{
|
||||
// default parameters for the Newton solver
|
||||
newton_rtol = 1e-4;
|
||||
newton_atol = 1e-8;
|
||||
newton_iter = 10;
|
||||
|
||||
// linear solver
|
||||
linear_rtol = 1e-7;
|
||||
linear_atol = 1e-15;
|
||||
linear_iter = 500;
|
||||
|
||||
print_level = 0;
|
||||
|
||||
// set the mesh
|
||||
mesh=&imesh;
|
||||
|
||||
// set the fespace
|
||||
fespace=&ifespace;
|
||||
|
||||
// set the parameters
|
||||
plap_epsilon=new ConstantCoefficient(regularizationp);
|
||||
plap_power=new ConstantCoefficient(powerp);
|
||||
if (load==nullptr)
|
||||
{
|
||||
plap_input=new ConstantCoefficient(1.0);
|
||||
input_ownership=true;
|
||||
}
|
||||
else
|
||||
{
|
||||
plap_input=load;
|
||||
input_ownership=false;
|
||||
}
|
||||
|
||||
// set the nonlinear form
|
||||
nlform=nullptr;
|
||||
lsolver=nullptr;
|
||||
prec=nullptr;
|
||||
nsolver=nullptr;
|
||||
|
||||
// set the default integrator
|
||||
integ=IntegratorType::HandCodedIntegrator; // hand coded
|
||||
}
|
||||
|
||||
~NLSolverPLaplacian()
|
||||
{
|
||||
if (nlform!=nullptr) { delete nlform;}
|
||||
if (nsolver!=nullptr) { delete nsolver;}
|
||||
if (prec!=nullptr) { delete prec;}
|
||||
if (lsolver!=nullptr) { delete lsolver;}
|
||||
if (input_ownership) { delete plap_input;}
|
||||
delete plap_epsilon;
|
||||
delete plap_power;
|
||||
}
|
||||
|
||||
/// Set the integrator.
|
||||
/// 0 - hand coded, 1 - AD based (compute only Hessian by AD),
|
||||
/// 2 - AD based (compute residual and Hessian by AD)
|
||||
void SetIntegrator(IntegratorType intr)
|
||||
{
|
||||
integ=intr;
|
||||
}
|
||||
|
||||
|
||||
// set relative tolerance for the Newton solver
|
||||
void SetNRRTol(real_t rtol)
|
||||
{
|
||||
newton_rtol=rtol;
|
||||
}
|
||||
|
||||
// set absolute tolerance for the Newton solver
|
||||
void SetNRATol(real_t atol)
|
||||
{
|
||||
newton_atol=atol;
|
||||
}
|
||||
|
||||
// set max iterations for the NR solver
|
||||
void SetMaxNRIter(int miter)
|
||||
{
|
||||
newton_iter=miter;
|
||||
}
|
||||
|
||||
void SetLSRTol(real_t rtol)
|
||||
{
|
||||
linear_rtol=rtol;
|
||||
}
|
||||
|
||||
void SetLSATol(real_t atol)
|
||||
{
|
||||
linear_atol=atol;
|
||||
}
|
||||
|
||||
// set max iterations for the linear solver
|
||||
void SetMaxLSIter(int miter)
|
||||
{
|
||||
linear_iter=miter;
|
||||
}
|
||||
|
||||
// set the print level
|
||||
void SetPrintLevel(int plev)
|
||||
{
|
||||
print_level=plev;
|
||||
}
|
||||
|
||||
/// The state vector is used as initial condition for the NR solver. On
|
||||
/// return the statev holds the solution to the problem.
|
||||
void Solve(Vector& statev)
|
||||
{
|
||||
if (nlform==nullptr)
|
||||
{
|
||||
AllocSolvers();
|
||||
}
|
||||
Vector b; // RHS is zero
|
||||
nsolver->Mult(b, statev);
|
||||
}
|
||||
|
||||
/// Compute the energy
|
||||
real_t GetEnergy(Vector& statev)
|
||||
{
|
||||
if (nlform==nullptr)
|
||||
{
|
||||
// allocate the solvers
|
||||
AllocSolvers();
|
||||
}
|
||||
return nlform->GetEnergy(statev);
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
|
||||
void AllocSolvers()
|
||||
{
|
||||
if (nlform!=nullptr) { delete nlform;}
|
||||
if (nsolver!=nullptr) {delete nsolver;}
|
||||
if (prec!=nullptr) {delete prec;}
|
||||
if (lsolver!=nullptr) { delete lsolver;}
|
||||
|
||||
// Define the essential boundary attributes
|
||||
Array<int> ess_bdr(mesh->bdr_attributes.Max());
|
||||
ess_bdr = 1;
|
||||
|
||||
nlform = new NonlinearForm(fespace);
|
||||
|
||||
if (integ==IntegratorType::HandCodedIntegrator)
|
||||
{
|
||||
// standard hand coded integrator
|
||||
nlform->AddDomainIntegrator(new pLaplace(*plap_power,*plap_epsilon,
|
||||
*plap_input));
|
||||
}
|
||||
else if (integ==IntegratorType::ADJacobianIntegrator)
|
||||
{
|
||||
// The template integrator is based on automatic differentiation. For
|
||||
// ADJacobianIntegrator the residual (vector function) at an
|
||||
// integration point is implemented as a functor by MyVFunctor. The
|
||||
// vector function has a return size of four(4), four state arguments,
|
||||
// and three(3) parameters. MyVFunctor is a template argument to the
|
||||
// actual template class performing the differentiation - in this case,
|
||||
// QVectorFuncAutoDiff. The derivatives are used in the integration
|
||||
// loop in the integrator pLaplaceAD.
|
||||
nlform->AddDomainIntegrator(new
|
||||
pLaplaceAD<mfem::QVectorFuncAutoDiff<MyResidualFunctor,4,4,3>>(*plap_power,
|
||||
*plap_epsilon,*plap_input));
|
||||
}
|
||||
else // IntegratorType::ADHessianIntegrator
|
||||
{
|
||||
// The main difference from the previous case is that the user has to
|
||||
// implement only a functional evaluation at an integration point. The
|
||||
// implementation is in MyQFunctor, which takes four state arguments
|
||||
// and three parameters. The residual vector is the first derivative of
|
||||
// the energy/functional with respect to the state variables, and the
|
||||
// Hessian is the second derivative. Automatic differentiation is used
|
||||
// for evaluating both of them.
|
||||
nlform->AddDomainIntegrator(new
|
||||
pLaplaceAD<mfem::QFunctionAutoDiff<MyEnergyFunctor,4,3>>(*plap_power,
|
||||
*plap_epsilon,*plap_input));
|
||||
}
|
||||
|
||||
nlform->SetEssentialBC(ess_bdr);
|
||||
|
||||
#ifdef MFEM_USE_SUITESPARSE
|
||||
prec = new UMFPackSolver();
|
||||
#else
|
||||
prec = new GSSmoother();
|
||||
#endif
|
||||
|
||||
// allocate the linear solver
|
||||
lsolver=new CGSolver();
|
||||
lsolver->SetRelTol(linear_rtol);
|
||||
lsolver->SetAbsTol(linear_atol);
|
||||
lsolver->SetMaxIter(linear_iter);
|
||||
lsolver->SetPrintLevel(print_level);
|
||||
lsolver->SetPreconditioner(*prec);
|
||||
|
||||
// allocate the NR solver
|
||||
nsolver = new NewtonSolver();
|
||||
nsolver->iterative_mode = true;
|
||||
nsolver->SetSolver(*lsolver);
|
||||
nsolver->SetOperator(*nlform);
|
||||
nsolver->SetPrintLevel(print_level);
|
||||
nsolver->SetRelTol(newton_rtol);
|
||||
nsolver->SetAbsTol(newton_atol);
|
||||
nsolver->SetMaxIter(newton_iter);
|
||||
}
|
||||
|
||||
real_t newton_rtol;
|
||||
real_t newton_atol;
|
||||
int newton_iter;
|
||||
|
||||
real_t linear_rtol;
|
||||
real_t linear_atol;
|
||||
int linear_iter;
|
||||
|
||||
int print_level;
|
||||
|
||||
// reference to the mesh
|
||||
Mesh* mesh;
|
||||
// reference to the fespace
|
||||
FiniteElementSpace *fespace;
|
||||
|
||||
// nonlinear form for the p-laplacian
|
||||
NonlinearForm *nlform;
|
||||
CGSolver *lsolver; // linear solver
|
||||
Solver *prec; // preconditioner for the linear solver
|
||||
NewtonSolver *nsolver; // NR solver
|
||||
IntegratorType integ;
|
||||
|
||||
// power of the p-laplacian
|
||||
Coefficient* plap_power;
|
||||
// regularization parameter
|
||||
Coefficient* plap_epsilon;
|
||||
// load(input) parameter
|
||||
Coefficient* plap_input;
|
||||
// flag indicating the ownership of plap_input
|
||||
bool input_ownership;
|
||||
};
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
// 1. Parse command-line options
|
||||
const char *mesh_file = "../../data/beam-tet.mesh";
|
||||
int ser_ref_levels = 3;
|
||||
int order = 1;
|
||||
bool visualization = true;
|
||||
real_t newton_rel_tol = 1e-4;
|
||||
real_t newton_abs_tol = 1e-6;
|
||||
int newton_iter = 10;
|
||||
int print_level = 0;
|
||||
|
||||
real_t pp = 2.0; // p-Laplacian power
|
||||
|
||||
IntegratorType integrator = IntegratorType::ADHessianIntegrator;
|
||||
int int_integrator = integrator;
|
||||
// HandCodedIntegrator = 0 - do not use AD (hand coded)
|
||||
// ADJacobianIntegrator = 1 - use AD for Hessian only
|
||||
// ADHessianIntegrator = 2 - use AD for Residual and Hessian
|
||||
StopWatch *timer = new StopWatch();
|
||||
OptionsParser args(argc, argv);
|
||||
args.AddOption(&mesh_file, "-m", "--mesh", "Mesh file to use.");
|
||||
args.AddOption(&ser_ref_levels,
|
||||
"-rs",
|
||||
"--refine-serial",
|
||||
"Number of times to refine the mesh uniformly in serial.");
|
||||
args.AddOption(&order,
|
||||
"-o",
|
||||
"--order",
|
||||
"Order (degree) of the finite elements.");
|
||||
args.AddOption(&visualization,
|
||||
"-vis",
|
||||
"--visualization",
|
||||
"-no-vis",
|
||||
"--no-visualization",
|
||||
"Enable or disable GLVis visualization.");
|
||||
args.AddOption(&newton_rel_tol,
|
||||
"-rel",
|
||||
"--relative-tolerance",
|
||||
"Relative tolerance for the Newton solve.");
|
||||
args.AddOption(&newton_abs_tol,
|
||||
"-abs",
|
||||
"--absolute-tolerance",
|
||||
"Absolute tolerance for the Newton solve.");
|
||||
args.AddOption(&newton_iter,
|
||||
"-it",
|
||||
"--newton-iterations",
|
||||
"Maximum iterations for the Newton solve.");
|
||||
args.AddOption(&pp,
|
||||
"-pp",
|
||||
"--power-parameter",
|
||||
"Power parameter (>=2.0) for the p-Laplacian.");
|
||||
args.AddOption((&print_level), "-prt", "--print-level", "Print level.");
|
||||
args.AddOption(&int_integrator,
|
||||
"-int",
|
||||
"--integrator",
|
||||
"Integrator 0: standard; 1: AD for Hessian; 2: AD for residual and Hessian");
|
||||
args.Parse();
|
||||
if (!args.Good())
|
||||
{
|
||||
args.PrintUsage(std::cout);
|
||||
return 1;
|
||||
}
|
||||
args.PrintOptions(std::cout);
|
||||
integrator = static_cast<IntegratorType>(int_integrator);
|
||||
|
||||
// 2. Read the (serial) mesh from the given mesh file.
|
||||
Mesh *mesh = new Mesh(mesh_file, 1, 1);
|
||||
int dim = mesh->Dimension();
|
||||
|
||||
// 3. Refine the mesh in serial to increase the resolution. In this example
|
||||
// we do 'ser_ref_levels' of uniform refinement, where 'ser_ref_levels' is
|
||||
// a command-line parameter.
|
||||
for (int lev = 0; lev < ser_ref_levels; lev++)
|
||||
{
|
||||
mesh->UniformRefinement();
|
||||
}
|
||||
|
||||
// 4. Define the load parameter for the p-Laplacian
|
||||
ConstantCoefficient load(1.00);
|
||||
|
||||
// 5. Define the finite element spaces for the solution
|
||||
H1_FECollection fec(order, dim);
|
||||
FiniteElementSpace fespace(mesh, &fec, 1, Ordering::byVDIM);
|
||||
int glob_size = fespace.GetTrueVSize();
|
||||
|
||||
std::cout << "Number of finite element unknowns: " << glob_size << std::endl;
|
||||
|
||||
// 6. Define the solution grid function
|
||||
GridFunction x(&fespace);
|
||||
x = 0.0;
|
||||
|
||||
// 7. Define the solution true vector
|
||||
Vector sv(fespace.GetTrueVSize());
|
||||
sv = 0.0;
|
||||
|
||||
// 8. Define ParaView DataCollection
|
||||
ParaViewDataCollection *dacol = new ParaViewDataCollection("Example", mesh);
|
||||
dacol->SetLevelsOfDetail(order);
|
||||
dacol->RegisterField("sol", &x);
|
||||
|
||||
// 9. Define the nonlinear p-Laplacian solver
|
||||
NLSolverPLaplacian* nr;
|
||||
|
||||
// 10. Start with linear diffusion - solvable for any initial guess
|
||||
nr=new NLSolverPLaplacian(*mesh, fespace, 2.0, &load);
|
||||
nr->SetIntegrator(integrator);
|
||||
nr->SetMaxNRIter(newton_iter);
|
||||
nr->SetNRATol(newton_abs_tol);
|
||||
nr->SetNRRTol(newton_rel_tol);
|
||||
timer->Clear();
|
||||
timer->Start();
|
||||
nr->Solve(sv);
|
||||
timer->Stop();
|
||||
std::cout << "[pp=2] The solution time is: " << timer->RealTime()
|
||||
<< std::endl;
|
||||
// Compute the energy
|
||||
real_t energy = nr->GetEnergy(sv);
|
||||
std::cout << "[pp=2] The total energy of the system is E=" << energy
|
||||
<< std::endl;
|
||||
delete nr;
|
||||
x.SetFromTrueDofs(sv);
|
||||
dacol->SetTime(2.0);
|
||||
dacol->SetCycle(2);
|
||||
dacol->Save();
|
||||
|
||||
|
||||
// 11. Continue with powers higher than 2
|
||||
for (int i = 3; i < pp; i++)
|
||||
{
|
||||
nr=new NLSolverPLaplacian(*mesh, fespace, (real_t)i, &load);
|
||||
nr->SetIntegrator(integrator);
|
||||
nr->SetMaxNRIter(newton_iter);
|
||||
nr->SetNRATol(newton_abs_tol);
|
||||
nr->SetNRRTol(newton_rel_tol);
|
||||
timer->Clear();
|
||||
timer->Start();
|
||||
nr->Solve(sv);
|
||||
timer->Stop();
|
||||
std::cout << "[pp=" << i
|
||||
<< "] The solution time is: " << timer->RealTime() << std::endl;
|
||||
energy = nr->GetEnergy(sv);
|
||||
std::cout << "[pp="<< i<<"] The total energy of the system is E=" << energy
|
||||
<< std::endl;
|
||||
delete nr;
|
||||
x.SetFromTrueDofs(sv);
|
||||
dacol->SetTime(i);
|
||||
dacol->SetCycle(i);
|
||||
dacol->Save();
|
||||
}
|
||||
|
||||
// 12. Continue with the final power
|
||||
if (std::abs(pp - 2.0) > std::numeric_limits<real_t>::epsilon())
|
||||
{
|
||||
nr=new NLSolverPLaplacian(*mesh, fespace, pp, &load);
|
||||
nr->SetIntegrator(integrator);
|
||||
nr->SetMaxNRIter(newton_iter);
|
||||
nr->SetNRATol(newton_abs_tol);
|
||||
nr->SetNRRTol(newton_rel_tol);
|
||||
timer->Clear();
|
||||
timer->Start();
|
||||
nr->Solve(sv);
|
||||
timer->Stop();
|
||||
std::cout << "[pp=" << pp
|
||||
<< "] The solution time is: " << timer->RealTime() << std::endl;
|
||||
energy = nr->GetEnergy(sv);
|
||||
std::cout << "[pp="<<pp<<"] The total energy of the system is E=" << energy
|
||||
<< std::endl;
|
||||
delete nr;
|
||||
x.SetFromTrueDofs(sv);
|
||||
dacol->SetTime(pp);
|
||||
if (pp < 2.0)
|
||||
{
|
||||
dacol->SetCycle(static_cast<int>(std::floor(pp)));
|
||||
}
|
||||
else
|
||||
{
|
||||
dacol->SetCycle(static_cast<int>(std::ceil(pp)));
|
||||
}
|
||||
dacol->Save();
|
||||
}
|
||||
|
||||
// 13. Free the memory
|
||||
delete dacol;
|
||||
delete mesh;
|
||||
delete timer;
|
||||
return 0;
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
|
||||
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
|
||||
// LICENSE and NOTICE for details. LLNL-CODE-806117.
|
||||
//
|
||||
// This file is part of the MFEM library. For more information and source code
|
||||
// availability visit https://mfem.org.
|
||||
//
|
||||
// MFEM is free software; you can redistribute it and/or modify it under the
|
||||
// terms of the BSD-3 license. We welcome feedback and contributions, see file
|
||||
// CONTRIBUTING.md for details.
|
||||
|
||||
#include "admfem.hpp"
|
||||
#include "mfem.hpp"
|
||||
|
||||
template<typename TDataType, typename TParamVector, typename TStateVector
|
||||
, int state_size, int param_size>
|
||||
class DiffusionFunctional
|
||||
{
|
||||
public:
|
||||
TDataType operator() (TParamVector& vparam, TStateVector& uu)
|
||||
{
|
||||
MFEM_ASSERT(state_size==4,"ExampleFunctor state_size should be equal to 4!");
|
||||
MFEM_ASSERT(param_size==2,"ExampleFunctor param_size should be equal to 2!");
|
||||
auto kappa = vparam[0]; // diffusion coefficient
|
||||
auto load = vparam[1]; // volumetric influx
|
||||
TDataType rez = kappa*(uu[0]*uu[0]+uu[1]*uu[1]+uu[2]*uu[2])/2.0 - load*uu[3];
|
||||
return rez;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
template<typename TDataType, typename TParamVector, typename TStateVector,
|
||||
int residual_size, int state_size, int param_size>
|
||||
class DiffusionResidual
|
||||
{
|
||||
public:
|
||||
void operator ()(TParamVector& vparam, TStateVector& uu, TStateVector& rr)
|
||||
{
|
||||
MFEM_ASSERT(residual_size==4,
|
||||
"DiffusionResidual residual_size should be equal to 4!");
|
||||
MFEM_ASSERT(state_size==4,"ExampleFunctor state_size should be equal to 4!");
|
||||
MFEM_ASSERT(param_size==2,"ExampleFunctor param_size should be equal to 2!");
|
||||
auto kappa = vparam[0]; // diffusion coefficient
|
||||
auto load = vparam[1]; // volumetric influx
|
||||
|
||||
rr[0] = kappa * uu[0];
|
||||
rr[1] = kappa * uu[1];
|
||||
rr[2] = kappa * uu[2];
|
||||
rr[3] = -load;
|
||||
}
|
||||
};
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
|
||||
#ifdef MFEM_USE_ADFORWARD
|
||||
std::cout<<"MFEM_USE_ADFORWARD == true"<<std::endl;
|
||||
#else
|
||||
std::cout<<"MFEM_USE_ADFORWARD == false"<<std::endl;
|
||||
#endif
|
||||
|
||||
#ifdef MFEM_USE_CALIPER
|
||||
cali::ConfigManager mgr;
|
||||
#endif
|
||||
// Caliper instrumentation
|
||||
MFEM_PERF_FUNCTION;
|
||||
#ifdef MFEM_USE_CALIPER
|
||||
const char* cali_config = "runtime-report";
|
||||
mgr.add(cali_config);
|
||||
mgr.start();
|
||||
#endif
|
||||
mfem::Vector param(2);
|
||||
param[0]=3.0; // diffusion coefficient
|
||||
param[1]=2.0; // volumetric influx
|
||||
|
||||
mfem::Vector state(4);
|
||||
state[0]=1.0; // grad_x
|
||||
state[1]=2.0; // grad_y
|
||||
state[2]=3.0; // grad_z
|
||||
state[3]=4.0; // state value
|
||||
|
||||
mfem::QFunctionAutoDiff<DiffusionFunctional,4,2> adf;
|
||||
|
||||
mfem::Vector rr0(4);
|
||||
mfem::DenseMatrix hh0(4,4);
|
||||
|
||||
mfem::Vector rr1(4);
|
||||
mfem::DenseMatrix hh1(4,4);
|
||||
MFEM_PERF_BEGIN("Grad");
|
||||
adf.Grad(param,state,rr0);
|
||||
MFEM_PERF_END("Grad");
|
||||
MFEM_PERF_BEGIN("Hessian");
|
||||
adf.Hessian(param, state, hh0);
|
||||
MFEM_PERF_END("Hessian");
|
||||
// dump out the results
|
||||
std::cout<<"FunctionAutoDiff"<<std::endl;
|
||||
std::cout<< adf.Eval(param,state)<<std::endl;
|
||||
rr0.Print(std::cout);
|
||||
hh0.Print(std::cout);
|
||||
|
||||
mfem::QVectorFuncAutoDiff<DiffusionResidual,4,4,2> rdf;
|
||||
MFEM_PERF_BEGIN("Jacobian");
|
||||
rdf.Jacobian(param, state, hh1);
|
||||
MFEM_PERF_END("Jacobian");
|
||||
|
||||
std::cout<<"ResidualAutoDiff"<<std::endl;
|
||||
hh1.Print(std::cout);
|
||||
|
||||
// using lambda expression
|
||||
auto func = [](mfem::Vector& vparam,
|
||||
mfem::ad::ADVectorType& uu,
|
||||
mfem::ad::ADVectorType& vres)
|
||||
{
|
||||
// auto func = [](auto& vparam, auto& uu, auto& vres) { //c++14
|
||||
auto kappa = vparam[0]; // diffusion coefficient
|
||||
auto load = vparam[1]; // volumetric influx
|
||||
|
||||
vres[0] = kappa * uu[0];
|
||||
vres[1] = kappa * uu[1];
|
||||
vres[2] = kappa * uu[2];
|
||||
vres[3] = -load;
|
||||
};
|
||||
|
||||
mfem::VectorFuncAutoDiff<4,4,2> fdr(func);
|
||||
MFEM_PERF_BEGIN("JacobianV");
|
||||
fdr.Jacobian(param,state,
|
||||
hh1); // computes the gradient of func and stores the result in hh1
|
||||
MFEM_PERF_END("JacobianV");
|
||||
std::cout<<"LambdaAutoDiff"<<std::endl;
|
||||
hh1.Print(std::cout);
|
||||
|
||||
|
||||
mfem::real_t kappa = param[0];
|
||||
mfem::real_t load = param[1];
|
||||
// using lambda expression
|
||||
auto func01 = [&kappa,&load](mfem::Vector& vparam,
|
||||
mfem::ad::ADVectorType& uu,
|
||||
mfem::ad::ADVectorType& vres)
|
||||
{
|
||||
// auto func = [](auto& vparam, auto& uu, auto& vres) { //c++14
|
||||
|
||||
vres[0] = kappa * uu[0];
|
||||
vres[1] = kappa * uu[1];
|
||||
vres[2] = kappa * uu[2];
|
||||
vres[3] = -load;
|
||||
};
|
||||
|
||||
mfem::VectorFuncAutoDiff<4,4,2> fdr01(func01);
|
||||
MFEM_PERF_BEGIN("Jacobian1");
|
||||
fdr01.Jacobian(param,state,hh1);
|
||||
MFEM_PERF_END("Jacobian1");
|
||||
std::cout<<"LambdaAutoDiff 01"<<std::endl;
|
||||
hh1.Print(std::cout);
|
||||
|
||||
#ifdef MFEM_USE_CALIPER
|
||||
mgr.flush();
|
||||
#endif
|
||||
}
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#if defined(_MSC_VER) && (_MSC_VER < 1800)
|
||||
#include <float.h>
|
||||
#define isfinite _finite
|
||||
@@ -119,11 +118,7 @@ public:
|
||||
with SetData(). */
|
||||
TAutoDiffVector(dtype *_data, int _size)
|
||||
{
|
||||
if (capacity > 0)
|
||||
{
|
||||
delete[] data;
|
||||
capacity = 0;
|
||||
}
|
||||
capacity = 0;
|
||||
size = _size;
|
||||
data = _data;
|
||||
}
|
||||
@@ -315,7 +310,7 @@ public:
|
||||
/// Dot product with a `dtype *` array.
|
||||
dtype operator*(const dtype *v) const
|
||||
{
|
||||
dtype dot = 0.0;
|
||||
dtype dot = {};
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
dot += data[i] * v[i];
|
||||
@@ -327,7 +322,7 @@ public:
|
||||
dtype operator*(const TAutoDiffVector<dtype> &v) const
|
||||
{
|
||||
MFEM_ASSERT(size == v.Size(), "incompatible Vectors!");
|
||||
dtype dot = 0.0;
|
||||
dtype dot = {};
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
dot += data[i] * v[i];
|
||||
@@ -338,7 +333,7 @@ public:
|
||||
dtype operator*(const Vector &v) const
|
||||
{
|
||||
MFEM_ASSERT(size == v.Size(), "incompatible Vectors!");
|
||||
dtype dot = 0.0;
|
||||
dtype dot = {};
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
dot += data[i] * v[i];
|
||||
@@ -586,7 +581,7 @@ public:
|
||||
}
|
||||
|
||||
/// Destroys vector.
|
||||
~TAutoDiffVector() { delete[] data; }
|
||||
~TAutoDiffVector() { if (OwnsData()) { delete[] data; } }
|
||||
|
||||
/// Prints vector to stream @a os with @a width entries per line.
|
||||
void Print(std::ostream &os = mfem::out, int width = 8) const
|
||||
@@ -649,8 +644,8 @@ public:
|
||||
return abs(data[0]);
|
||||
} // end if 1 == size
|
||||
|
||||
dtype scale = 0.0;
|
||||
dtype sum = 0.0;
|
||||
dtype scale = {};
|
||||
dtype sum = {};
|
||||
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
@@ -674,7 +669,7 @@ public:
|
||||
/// Returns the l_infinity norm of the vector.
|
||||
dtype Normlinf() const
|
||||
{
|
||||
dtype max = 0.0;
|
||||
dtype max = {};
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
max = max(abs(data[i]), max);
|
||||
@@ -684,7 +679,7 @@ public:
|
||||
/// Returns the l_1 norm of the vector.
|
||||
dtype Norml1() const
|
||||
{
|
||||
dtype sum = 0.0;
|
||||
dtype sum = {};
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
sum += abs(data[i]);
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
#pragma once
|
||||
#include "mfem.hpp"
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
class MappedGridFunctionCoefficient : public Coefficient
|
||||
{
|
||||
private:
|
||||
GridFunction *gf;
|
||||
std::function<real_t(const real_t)> map_func;
|
||||
public:
|
||||
MappedGridFunctionCoefficient(GridFunction *gf_,
|
||||
std::function<real_t(const real_t)> map_func_)
|
||||
: gf(gf_), map_func(map_func_) { }
|
||||
virtual real_t Eval(ElementTransformation &T, const IntegrationPoint &ip)
|
||||
{
|
||||
return map_func(gf->GetValue(T.ElementNo, T.GetIntPoint()));
|
||||
}
|
||||
};
|
||||
class VectorGradientGridFunction : public MatrixCoefficient
|
||||
{
|
||||
private:
|
||||
GridFunction &gf;
|
||||
public:
|
||||
VectorGradientGridFunction(GridFunction &gf)
|
||||
: MatrixCoefficient(gf.FESpace()->GetVDim(),
|
||||
gf.FESpace()->GetMesh()->SpaceDimension()), gf(gf)
|
||||
{}
|
||||
|
||||
void Eval(DenseMatrix &grad, ElementTransformation &T,
|
||||
const IntegrationPoint &ip) override
|
||||
{ gf.GetVectorGradient(T, grad); }
|
||||
};
|
||||
|
||||
inline std::unique_ptr<GridFunction>
|
||||
NewGridFunction(FiniteElementSpace &fes)
|
||||
{
|
||||
#ifdef MFEM_USE_MPI
|
||||
if (ParFiniteElementSpace *pfes =
|
||||
dynamic_cast<ParFiniteElementSpace*>(&fes))
|
||||
{
|
||||
return std::make_unique<ParGridFunction>(pfes);
|
||||
}
|
||||
#endif
|
||||
return std::make_unique<GridFunction>(&fes);
|
||||
}
|
||||
|
||||
inline std::unique_ptr<LinearForm>
|
||||
NewLinearForm(FiniteElementSpace &fes)
|
||||
{
|
||||
#ifdef MFEM_USE_MPI
|
||||
if (ParFiniteElementSpace *pfes =
|
||||
dynamic_cast<ParFiniteElementSpace*>(&fes))
|
||||
{
|
||||
return std::make_unique<ParLinearForm>(pfes);
|
||||
}
|
||||
#endif
|
||||
return std::make_unique<LinearForm>(&fes);
|
||||
}
|
||||
|
||||
inline std::unique_ptr<BilinearForm>
|
||||
NewBilinearForm(FiniteElementSpace &fes)
|
||||
{
|
||||
#ifdef MFEM_USE_MPI
|
||||
if (ParFiniteElementSpace *pfes =
|
||||
dynamic_cast<ParFiniteElementSpace*>(&fes))
|
||||
{
|
||||
return std::make_unique<ParBilinearForm>(pfes);
|
||||
}
|
||||
#endif
|
||||
return std::make_unique<BilinearForm>(&fes);
|
||||
}
|
||||
|
||||
inline std::unique_ptr<MixedBilinearForm>
|
||||
NewMixedBilinearForm(FiniteElementSpace &trial_fes,
|
||||
FiniteElementSpace &test_fes)
|
||||
{
|
||||
#ifdef MFEM_USE_MPI
|
||||
if (ParFiniteElementSpace *trial_pfes =
|
||||
dynamic_cast<ParFiniteElementSpace*>(&trial_fes))
|
||||
{
|
||||
ParFiniteElementSpace *test_pfes = dynamic_cast<ParFiniteElementSpace*>
|
||||
(&test_fes);
|
||||
MFEM_VERIFY(test_pfes != nullptr,
|
||||
"NewMixedBilinearForm: Trial is parallel, but test is not.");
|
||||
return std::make_unique<ParMixedBilinearForm>(trial_pfes, test_pfes);
|
||||
}
|
||||
MFEM_VERIFY(dynamic_cast<ParFiniteElementSpace*>(&test_fes) == nullptr,
|
||||
"NewMixedBilinearForm: Trial is not parallel, but test is.");
|
||||
#endif
|
||||
return std::make_unique<MixedBilinearForm>(&trial_fes, &test_fes);
|
||||
}
|
||||
inline std::unique_ptr<NonlinearForm>
|
||||
NewNonlinearForm(FiniteElementSpace &fes)
|
||||
{
|
||||
#ifdef MFEM_USE_MPI
|
||||
if (ParFiniteElementSpace *pfes =
|
||||
dynamic_cast<ParFiniteElementSpace*>(&fes))
|
||||
{
|
||||
return std::make_unique<ParNonlinearForm>(pfes);
|
||||
}
|
||||
#endif
|
||||
return std::make_unique<NonlinearForm>(&fes);
|
||||
}
|
||||
inline std::unique_ptr<BlockNonlinearForm>
|
||||
NewBlockNonlinearForm(Array<FiniteElementSpace*> &fes)
|
||||
{
|
||||
#ifdef MFEM_USE_MPI
|
||||
int numParallel = 0;
|
||||
|
||||
Array<ParFiniteElementSpace*> pfes;
|
||||
for (auto *space : fes)
|
||||
{
|
||||
pfes.Append(dynamic_cast<ParFiniteElementSpace*>(space));
|
||||
numParallel += pfes.Last() != nullptr;
|
||||
}
|
||||
MFEM_VERIFY(numParallel == 0 || numParallel == fes.Size(),
|
||||
"NewBlockNonlinearForm: either all or none of the spaces must be parallel");
|
||||
if (numParallel == fes.Size())
|
||||
{
|
||||
return std::make_unique<ParBlockNonlinearForm>(pfes);
|
||||
}
|
||||
#endif
|
||||
return std::make_unique<BlockNonlinearForm>(fes);
|
||||
}
|
||||
|
||||
#ifdef MFEM_USE_PETSC
|
||||
// Monolithic direct solver for block system
|
||||
class MUMPSMonoSolver : public MUMPSSolver
|
||||
{
|
||||
private:
|
||||
std::unique_ptr<HypreParMatrix> mono;
|
||||
public:
|
||||
MUMPSMonoSolver(MPI_Comm comm) : MUMPSSolver(comm) {}
|
||||
|
||||
void SetOperator(const Operator &op)
|
||||
{
|
||||
const BlockOperator *bop = dynamic_cast<const BlockOperator*>(&op);
|
||||
MFEM_VERIFY(bop != nullptr, "Not a BlockOperator");
|
||||
Array2D<const HypreParMatrix*> blocks(bop->NumRowBlocks(), bop->NumColBlocks());
|
||||
for (int j=0; j<bop->NumColBlocks(); j++)
|
||||
{
|
||||
for (int i=0; i<bop->NumRowBlocks(); i++)
|
||||
{
|
||||
if (bop->IsZeroBlock(i,j)) { continue; }
|
||||
const HypreParMatrix *m =
|
||||
dynamic_cast<const HypreParMatrix*>(&bop->GetBlock(i,j));
|
||||
MFEM_VERIFY(m != nullptr, "Not a HypreParMatrix");
|
||||
blocks(i,j) = m;
|
||||
}
|
||||
}
|
||||
mono.reset(HypreParMatrixFromBlocks(blocks));
|
||||
MUMPSSolver::SetOperator(*mono);
|
||||
};
|
||||
};
|
||||
#endif
|
||||
|
||||
inline std::tuple<std::unique_ptr<FiniteElementSpace>, std::unique_ptr<L2_FECollection>>
|
||||
QSpaceToFESpace(QuadratureSpace &qs)
|
||||
{
|
||||
Mesh *mesh = qs.GetMesh();
|
||||
const int dim = mesh->Dimension();
|
||||
Geometry::Type geom = mesh->GetTypicalElementGeometry();
|
||||
MFEM_VERIFY(geom != Geometry::TRIANGLE &&
|
||||
geom != Geometry::TETRAHEDRON &&
|
||||
geom != Geometry::PRISM &&
|
||||
geom != Geometry::PYRAMID,
|
||||
"QSpaceToFESpace: only support tensor product elements");
|
||||
std::unique_ptr<L2_FECollection> fec
|
||||
= std::make_unique<L2_FECollection> (qs.GetOrder()/2, dim);
|
||||
|
||||
std::unique_ptr<FiniteElementSpace> fes;
|
||||
#ifdef MFEM_USE_MPI
|
||||
ParMesh *pmesh = dynamic_cast<ParMesh*>(qs.GetMesh());
|
||||
if (pmesh) { fes = std::make_unique<ParFiniteElementSpace>(pmesh, fec.get()); }
|
||||
#endif
|
||||
if (!fes) { fes = std::make_unique<FiniteElementSpace>(mesh, fec.get()); }
|
||||
return std::make_tuple(std::move(fes), std::move(fec));
|
||||
}
|
||||
|
||||
inline Array<int> GetOffsets(const Array<FiniteElementSpace*> &fespaces)
|
||||
{
|
||||
Array<int> offsets(fespaces.Size() + 1);
|
||||
offsets[0] = 0;
|
||||
for (int i=0; i<fespaces.Size(); i++)
|
||||
{
|
||||
offsets[i+1] = offsets[i] + fespaces[i]->GetVSize();
|
||||
}
|
||||
return std::move(offsets);
|
||||
}
|
||||
inline Array<int> GetTrueOffsets(const Array<FiniteElementSpace*> &fespaces)
|
||||
{
|
||||
Array<int> offsets(fespaces.Size() + 1);
|
||||
offsets[0] = 0;
|
||||
for (int i=0; i<fespaces.Size(); i++)
|
||||
{
|
||||
offsets[i+1] = offsets[i] + fespaces[i]->GetTrueVSize();
|
||||
}
|
||||
return std::move(offsets);
|
||||
}
|
||||
|
||||
class VectorNormCoefficient : public Coefficient
|
||||
{
|
||||
private:
|
||||
VectorCoefficient &vc;
|
||||
Vector v;
|
||||
public:
|
||||
VectorNormCoefficient(VectorCoefficient &vc): vc(vc), v(vc.GetVDim()) {}
|
||||
real_t Eval(ElementTransformation &T, const IntegrationPoint &ip) override
|
||||
{
|
||||
vc.Eval(v, T, ip);
|
||||
return std::sqrt(v*v);
|
||||
}
|
||||
};
|
||||
|
||||
class BooleanCoefficient : public Coefficient
|
||||
{
|
||||
private:
|
||||
Coefficient &cf;
|
||||
std::function<bool(real_t)> func;
|
||||
public:
|
||||
BooleanCoefficient(Coefficient &cf, std::function<bool(real_t)> func)
|
||||
: cf(cf), func(func) {}
|
||||
real_t Eval(ElementTransformation &T, const IntegrationPoint &ip) override
|
||||
{
|
||||
return func(cf.Eval(T, ip));
|
||||
}
|
||||
};
|
||||
|
||||
};
|
||||
@@ -105,8 +105,11 @@ MFEM_PERF_CXXFLAGS_xlc = -mcpu=native
|
||||
# - Clang extra options:
|
||||
ifeq ($(MFEM_MACHINE),riscv64)
|
||||
MFEM_PERF_CXXFLAGS_clang += -march=rv64gc
|
||||
else ifneq ($(MFEM_MACHINE),arm64)
|
||||
# -march=native is unavailable on clang/ARM64 as of 05/2021: support could be added later.
|
||||
else ifneq (,$(findstring ppc,$(MFEM_MACHINE)))
|
||||
MFEM_PERF_CXXFLAGS_clang += -mcpu=native -mtune=native
|
||||
else ifeq ($(MFEM_MACHINE),arm64)
|
||||
MFEM_PERF_CXXFLAGS_clang += -mcpu=native -mtune=native
|
||||
else
|
||||
MFEM_PERF_CXXFLAGS_clang += -march=native
|
||||
endif
|
||||
MFEM_PERF_CXXFLAGS_clang += $(PEDANTIC_FLAG) -Wall
|
||||
|
||||
@@ -235,7 +235,7 @@ void test_derefine_L2_element(int order, Element::Type el_type, int basis_type)
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("AMR Coarsen L2 Element","[AMR][Coarsen]")
|
||||
TEST_CASE("AMR Coarsen L2 Element","[AMR][Coarsen][CUDA]")
|
||||
{
|
||||
std::vector<int> orders_1d{0,1,2,3};
|
||||
std::vector<int> orders_2d{0,1,2,3};
|
||||
@@ -420,7 +420,7 @@ void stress_parallel_coarsen(int order, Element::Type el_type, int basis_type)
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST_CASE("Parallel AMR Coarsen Stress Test", "[AMR][Coarsen][Parallel]")
|
||||
TEST_CASE("Parallel AMR Coarsen Stress Test", "[AMR][Coarsen][Parallel][CUDA]")
|
||||
{
|
||||
std::vector<int> orders_1d{0,1,2,3};
|
||||
std::vector<int> orders_2d{0,1,2,3};
|
||||
@@ -482,7 +482,7 @@ TEST_CASE("Parallel AMR Coarsen Stress Test", "[AMR][Coarsen][Parallel]")
|
||||
|
||||
}
|
||||
|
||||
TEST_CASE("ParDerefine", "[Parallel]")
|
||||
TEST_CASE("ParDerefine", "[Parallel][CUDA]")
|
||||
{
|
||||
for (dimension = 2; dimension <= 3; ++dimension)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user