Compare commits

...
Author SHA1 Message Date
Veselin Dobrev ce870630dc Fix a small issue in miniapps/shifted/distance.cpp 2025-08-18 19:06:08 -07:00
Veselin Dobrev f3b862a75a In GridFunction::ComputeLpError, use the device implementation only
in supported cases.
2025-08-18 16:31:56 -07:00
Veselin Dobrev 2c6d1d6e71 Fix warnings 2025-08-18 13:52:10 -07:00
Veselin Dobrev 68c5deb199 Merge branch 'master' into ceed-solver-bps 2025-08-18 13:39:23 -07:00
Veselin Dobrev f40afd320f Add more annotations and some small optimizations 2025-08-18 13:37:17 -07:00
Tzanio Kolev 20e77be88a Merge pull request #4987 from mfem/gitlab-ci--ruby-to-dane
Move Gitlab CI from Ruby to Dane
2025-08-16 16:01:21 -07:00
Veselin Dobrev 97a568af13 Move Gitlab CI from Ruby to Dane 2025-08-15 21:27:43 -07:00
Tzanio Kolev cd2715680f Merge pull request #4725 from mfem/qspace-device
Bilinear Operator Assembly optimizations
2025-08-15 07:22:53 -07:00
Tzanio Kolev 62997e9dc7 Merge pull request #4928 from mfem/mpi-dfem-fix
change MPI_INT to MFEM_MPI_SIZE_T
2025-08-15 07:22:37 -07:00
Andrew Ho b2a0f48001 fixed comment 2025-08-06 09:19:47 -07:00
Veselin Dobrev fb200e4b98 In the 3D version of 'forall', fix (Raja)OmpWrap2D calls to be 3D.
In INSTALL, add version requirement: OpenMP >= 3.1.

Fix a warning in the Vector move constructor about use of uninitialized
values by default initializing the Vector vefore calling its move
assignment operator.
2025-08-06 00:36:29 -07:00
Andrew Ho da852fd0e4 Merge remote-tracking branch 'base/qspace-device' into qspace-device 2025-08-05 18:10:35 -07:00
Andrew Ho c2657d20a8 Check for negative element attributes in debug mode when computing elem_attrs_cache 2025-08-05 18:08:42 -07:00
Andrew Ho cbcae09fe8 Merge branch 'master' into qspace-device 2025-08-05 14:30:11 -07:00
Andrew Ho b75c6e96e6 fix doxygen 2025-08-05 14:19:51 -07:00
Andrew Ho a54c5d2105 changed to use MPITypeMap
extend MPITypeMap to all fundamental C++ types except long double
2025-08-05 14:09:11 -07:00
Andrew Ho cd5d098c95 Merge branch 'master' into mpi-dfem-fix 2025-08-05 13:56:30 -07:00
Andrew Ho 30506b52d9 Documentation 2025-08-05 12:29:02 -07:00
Andrew Ho 3f78374d2a ignore attributes <= 0 2025-08-05 12:12:54 -07:00
Andrew Ho 1299d05d21 Merge branch 'master' into mpi-dfem-fix 2025-08-05 11:11:22 -07:00
Andrew Ho a8e251c28e 2D and 3D forall for OpenMP 2025-08-02 14:39:24 -07:00
Andrew Ho 9df402b565 suggestions from Veselin 2025-08-02 13:34:13 -07:00
Andrew Ho 5ddbaed1f4 Merge branch 'master' into qspace-device 2025-08-02 12:52:12 -07:00
Andrew Ho 2d6cb3f0e5 Merge remote-tracking branch 'base/qspace-device' into qspace-device 2025-07-29 17:09:37 -07:00
Andrew Ho d06f52461e missing bdr_attrs_cache 2025-07-29 17:09:10 -07:00
Andrew Ho 3853cb6860 fixed typo 2025-07-29 17:07:45 -07:00
Andrew HoandVeselin Dobrev f5d0db2a33 Update mesh/mesh.cpp
Co-authored-by: Veselin Dobrev <v-dobrev@users.noreply.github.com>
2025-07-29 17:07:04 -07:00
Andrew Ho a102daf621 Merge branch 'master' into qspace-device 2025-07-29 17:04:33 -07:00
Andrew Ho 4cc9b5c6f6 Merge branch 'master' into qspace-device 2025-07-19 17:07:43 -07:00
Veselin Dobrev 4107456b01 In solver-bp.cpp, fix for new hypre versions 2025-07-14 02:15:31 -07:00
Veselin Dobrev 64f5904119 In miniapps/benchmarks/ceed-solver-bps, add an option to set the number
of ranks per node which is only used for CSV output. Also, adjust run.sh
so that the number of ranks per node can be set in the script.
2025-07-14 01:19:42 -07:00
Veselin Dobrev 1ca666157e In solver-bp.cpp, use double instead of real_t for time measurements 2025-07-13 23:59:35 -07:00
Veselin Dobrev 0cf5da85e2 In solver-bp.cpp, add specialization for DiffusionIntegrator for
dim=3, p=6 (i.e. d1d=7), q1d=7.
2025-07-13 23:43:08 -07:00
Andrew Ho 267082d6f2 Merge remote-tracking branch 'base/master' into qspace-device 2025-07-12 23:07:56 -07:00
Andrew Ho dd6c207534 formatting 2025-07-11 10:02:11 -07:00
Andrew Ho 71af00ab6d change MPI_INT to MFEM_MPI_SIZE_T
this detects the underlying size of size_t
2025-07-11 09:57:25 -07:00
Will Pazner b20f91232b Add some Caliper annotations 2025-07-10 15:28:22 -07:00
Will Pazner 6e68d4640f Add a couple more perf function annotations 2025-07-10 15:27:46 -07:00
Veselin Dobrev 539f958a9b Fix merge issue 2025-07-09 01:01:59 -07:00
Veselin Dobrev a0b2a77b69 Merge branch 'master' into ceed-solver-bps 2025-07-09 00:50:01 -07:00
Veselin Dobrev e2292a93b8 MFEM_PERF_* annotations: added options to enable GPU-stream- and
MPI-synchronizations at the start and at the end of annotation regions. These
synchronizations can be enabled or disabled (default) in code via the new
macros: MFEM_PERF_SYNC, MFEM_PERF_SYNC_STREAM, and MFEM_PERF_SYNC_MPI; the
environment variables with the same names can be set to 0/1 to control the
synchronization as well.
2025-07-08 19:23:09 -07:00
Veselin Dobrev fce5095ba0 Merge branch 'master' into ceed-solver-bps
Resolved conflicts:
   .gitignore
   makefile
2025-07-05 14:00:06 -07:00
Veselin Dobrev 408c8e9468 Redefine MFEM_MPI_CXX_BOOL as MPI_UNSIGNED_CHAR because in some
implementations MFEM_MPI_CXX_BOOL does not work correctly.
2025-07-05 13:56:13 -07:00
Andrew Ho 1ac7837dc0 Merge branch 'master' into qspace-device 2025-06-16 12:20:48 -07:00
Andrew Ho 056152aaa6 Merge branch 'master' into qspace-device 2025-06-02 11:38:14 -07:00
Andrew Ho 02424a41a9 Merge branch 'master' into qspace-device 2025-05-27 12:05:07 -07:00
Andrew Ho b289def58a Merge branch 'master' into qspace-device 2025-05-12 14:04:22 -07:00
Andrew Ho 932508ad1b fixed bugs 2025-05-07 18:49:36 -07:00
Andrew Ho 0e30ee1e15 Added forall wrappers which flatten 2D and 3D ranges
achieves good performance on CPU and GPU
2025-05-07 18:25:27 -07:00
Andrew Ho dd236b8796 Improved the performance of the 1D forall modulo assembly for CPUs 2025-05-06 22:44:39 -07:00
Andrew Ho 9a47f4391d make sure CPU is also fast for mass matrix assembly 2025-05-06 01:15:25 -07:00
Andrew Ho ccc006febf Merge branch 'master' into qspace-device 2025-05-05 13:13:57 -06:00
Andrew Ho f42334cb4d Merge branch 'master' into qspace-device 2025-05-02 10:29:15 -06:00
Andrew Ho cc86e59167 Made getting offsets public, added accessor to internal held offsets 2025-04-23 13:25:28 -07:00
Andrew Ho 52eaf0deb7 Merge branch 'master' into qspace-device 2025-04-23 13:16:59 -07:00
Veselin Dobrev 602e889600 Add miniapps/benchmarks/ceed-solver-bps/solver-bp to .gitignore 2025-04-20 14:48:42 -07:00
Veselin Dobrev 4a82e0112f Fix the single precision build in miniapps/benchmarks/ceed-solver-bps 2025-04-19 16:33:46 -07:00
Veselin Dobrev 198c35a314 Merge branch 'master' into ceed-solver-bps 2025-04-19 16:20:57 -07:00
Veselin Dobrev bd50b287aa Fix style 2025-04-19 16:04:55 -07:00
Veselin Dobrev 4244d97e1f Fix GPU and out-of-source builds for miniapps/benchmarks/ceed-solver-bps 2025-04-19 15:59:00 -07:00
Veselin Dobrev 75a4018d9c Initial version of the ceed-solver-bps benchmark in miniapps/benchmarks 2025-04-17 16:10:20 -07:00
Veselin Dobrev 72323aad24 Merge branch 'master' into ceed-solver-bps
Resolved conflicts:
  fem/transfer.hpp
2025-04-17 15:45:51 -07:00
Veselin Dobrev ce9f969f16 Some tweaks in class MultigridBase to avoid implicit conversions of
call parameters.

Some small optimizations in class OperatorChebyshevSmoother.
2025-04-17 15:37:54 -07:00
Andrew Ho 44f58a169c Merge branch 'master' into qspace-device 2025-04-15 11:01:58 -07:00
Andrew Ho 4a09b7a1fc fix merge compile errors 2025-04-04 16:28:42 -07:00
Andrew Ho 1ea8e59f69 Merge branch 'master' into qspace-device 2025-04-04 16:02:17 -07:00
Andrew Ho b6aa248fc9 Merge branch 'master' into qspace-device 2025-03-24 06:27:44 -07:00
Andrew Ho 6857e4b277 use set instead of unordered_set to get ordered attributes 2025-02-26 07:14:55 -08:00
Andrew Ho 639a0347fb Merge branch 'master' into qspace-device 2025-02-25 19:43:38 -08:00
Andrew Ho a02f7165d8 Cache bdr_attributes in mesh (used by linearform_ext and bilinearform_ext) 2025-02-25 19:41:36 -08:00
Andrew Ho 4fb48b4e41 Get rid of the ElementAttributesUpdated function
Have users call SetAttributes() instead
2025-02-25 17:16:43 -08:00
Andrew Ho 751dbd0ed9 Merge remote-tracking branch 'base/qspace-device' into qspace-device 2025-02-25 17:05:04 -08:00
Andrew Ho c2c6a94d42 Merge remote-tracking branch 'base/qspace-device' into qspace-device 2025-02-25 17:04:49 -08:00
Andrew HoandWill Pazner 088ec0f461 Update fem/qspace.hpp
Co-authored-by: Will Pazner <11493037+pazner@users.noreply.github.com>
2025-02-25 17:03:59 -08:00
Andrew Ho 7e1be9da75 Use unordered_set to get unique attributes 2025-02-25 17:00:05 -08:00
Andrew HoandWill Pazner be29d4a3d4 Update mesh/mesh.hpp
Co-authored-by: Will Pazner <11493037+pazner@users.noreply.github.com>
2025-02-25 16:58:32 -08:00
Andrew HoandWill Pazner be34c5a6bd Update mesh/mesh.cpp
Co-authored-by: Will Pazner <11493037+pazner@users.noreply.github.com>
2025-02-25 16:57:35 -08:00
Andrew Ho 8bd3569f6d unused variable 2025-02-25 02:31:54 -08:00
Andrew Ho e8908ad646 cache element attributes in mesh 2025-02-24 19:38:29 -08:00
Andrew Ho 6c7673df1c code style 2025-02-24 19:18:40 -08:00
Andrew Ho 1f7f394f4e Merge remote-tracking branch 'base/master' into qspace-device 2025-02-24 18:47:58 -08:00
Veselin Dobrev 1fe1e50598 Merge branch 'mg-smem-prolongation' into ceed-solver-bps 2025-02-20 23:26:31 -08:00
Veselin Dobrev f0e542787b Fix memory leaks in miniapps/solvers/block-solvers.cpp 2025-02-20 23:24:35 -08:00
Veselin Dobrev 9627443feb Fix uninitialized memory usage due to incorrect setting of the
iterative mode of the MINRESSolver in BDPMinresSolver in
miniapps/solvers/darcy_solver.?pp. This became an issue due to the way
MultigridBase::SmoothingStep now calls the smoothers (including the
coarse solver) -- it sets the iterative mode of the smoother to false
and calls its Mult() method without initializing the second argument.
2025-02-20 21:48:23 -08:00
Veselin Dobrev ac2252ff20 In class MultigridBase, added support for coarse solver that is
used in addition to the coarse smoother, if given.

In class OperatorChebyshevSmoother, added support for changing the
order after construction with a new method: SetOrder().

Added a few minor tweaks.
2025-02-20 17:17:03 -08:00
Veselin Dobrev 3dc1217b30 Merge branch 'solver-monitor-controller' into ceed-solver-bps 2025-02-20 13:49:14 -08:00
Veselin Dobrev 32c5af1151 Merge branch 'mg-smem-prolongation' into ceed-solver-bps 2025-02-20 13:47:10 -08:00
Veselin Dobrev f573b345ae double -> real_t 2025-02-19 18:16:53 -08:00
Veselin Dobrev 80aaf8bb0d Merge branch 'master' into mg-smem-prolongation
Resolved conflicts:
   examples/ex26.cpp
   examples/ex26p.cpp
   fem/transfer.cpp
   linalg/solvers.cpp
2025-02-19 18:11:09 -08:00
Andrew Ho 7794c291be Expose more parallelism in mass integrator assemblePA 2024-11-22 13:01:58 -08:00
Andrew Ho 7ca49cb200 FaceQuadratureSpace also needs to save ne 2024-11-22 12:01:20 -08:00
Andrew Ho e48d41c9dc return correct NE for compressed qspace 2024-11-22 11:51:20 -08:00
Andrew Ho 0ead1d9c77 Implemented offset compression in qspace 2024-11-22 11:40:33 -08:00
Andrew Ho e2168f9348 code style 2024-11-20 15:56:17 -08:00
Andrew Ho 65516eca23 offsets need to be accessible to the host 2024-11-20 15:47:43 -08:00
Andrew Ho 79d9314560 Fix issue with using CUDA lambda functions in private/protected member functions 2024-11-19 17:06:07 -08:00
Will Pazner d91d39bc00 Construct quadrature space offsets on device (when possible)
This can be done easily when there is only one geometry type in the mesh
2024-11-19 12:52:22 -08:00
Veselin Dobrev a8ea2cf89a In MultigridBase::Cycle, fix for the case of no pre-smoothing 2024-02-09 22:28:42 -08:00
Veselin Dobrev 5768645171 In class MultigridBase:
* add support for iterative mode
* avoid unnecessary initializations with zero and residual computations
  when using W-cycle
* ensure smoothers (including the level 0 coarse solver) are used only
  with iterative mode turned off
* when using W-cycle, do not call the level 0  coarse solver twice to
  avoid calling "exact" solvers twice.
2024-02-09 21:13:02 -08:00
Veselin Dobrev c2a721cabe Bugfix: add missing allocation of OperatorChebyshevSmoother::z 2024-02-02 06:45:00 -08:00
Veselin Dobrev 343ce0c26d Fix the arguments in calls to SmemProlongationTranspose3D 2024-02-02 06:17:53 -08:00
Veselin Dobrev 5c4d341621 Fix a typo from the previous commit: 'R_[j]' -> '*R_[j]'.
Rename Operator::z to Operator::z_am to avoid confusion with the
Vectors z defined in a number of derived classes, and make it
private for reasons explained in the added doxygen comment.

Note: this commit does not seem to pass some of the unit tests.
2024-02-02 05:14:57 -08:00
Veselin Dobrev 35c9c43012 In OperatorChebyshevSmoother::Mult, for k = 0, use a single kernel
instead of 3 separate kernels.
2024-02-02 04:38:37 -08:00
Veselin Dobrev 371b091fd1 Use 'subtract' in a few places instead of the more general 'add' 2024-02-02 03:58:35 -08:00
Veselin Dobrev 1ed1ba8a71 Add optimization for OperatorJacobiSmoother::Mult from PR #1852 2024-02-02 03:41:58 -08:00
Will Pazner 2bd73092d4 Eliminate two more copies in multigrid 2024-02-01 15:26:38 -08:00
Will Pazner aeb4d508bb Remove temporaries from MultigridBase
Add temporary vector z in Operator
2024-02-01 15:26:24 -08:00
Will Pazner a58d941918 Small optimization in MultigridBase::Cycle (requires coarse solver not in iterative mode) 2024-02-01 15:16:45 -08:00
Will Pazner 60a7a9e673 Reduce copies in MultigridBase::Cycle and MultigridBase::SmoothingStep 2024-02-01 15:16:45 -08:00
Will Pazner a1d643f287 Small improvements to OperatorChebyshevSmoother
- Fuse two kernels in OperatorChebyshevSmoother::Mult
- Remove unnecessary copy
- Improve unit test
2024-02-01 14:47:06 -08:00
Will Pazner 04f303fd09 Add multigrid prolongation specializations 2024-02-01 12:04:17 -08:00
Will Pazner 1c4390c0f1 Add optimized p-multigrid prolongation kernels
Also rename Restriction to ProlongationTranspose
2024-02-01 10:49:07 -08:00
Will Pazner 35121d7297 Remove TensorProductPRefinementTransferOperator::~TensorProductPRefinementTransferOperator 2024-02-01 10:48:09 -08:00
82 changed files with 3505 additions and 523 deletions
+2
View File
@@ -411,6 +411,8 @@ miniapps/tribol/contact-patch-test
miniapps/diag-smoothers/abs-l1-jacobi
miniapps/diag-smoothers/mg-abs-l1-jacobi
miniapps/benchmarks/ceed-solver-bps/solver-bp
# Unit test binary and outputs
tests/unit/output_meshes
tests/unit/unit_tests
+5 -5
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -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,13 +40,14 @@ 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}
+1 -1
View File
@@ -18,7 +18,7 @@
setup_baseline:
tags:
- shell
- ruby
- dane
stage: setup
variables:
GIT_STRATEGY: none
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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}"
+2 -2
View File
@@ -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
+7
View File
@@ -80,6 +80,13 @@ Miscellaneous
variable is an alternative to calling 'Device::SetGPUAwareMPI(true)'.
- Added parallel Address Sanitizer, serial and parallel Undefined Behavior
Sanitizer and serial Memory Sanitizer GitHub actions tests on Ubuntu.
- MFEM_PERF_* annotations: added options to enable GPU-stream- and
MPI-synchronizations at the start and at the end of annotation regions. These
synchronizations can be enabled or disabled (default) in code via the new
macros: MFEM_PERF_SYNC, MFEM_PERF_SYNC_STREAM, and MFEM_PERF_SYNC_MPI; the
environment variables with the same names can be set to 0/1 to control the
synchronization as well.
Version 4.8, released on Apr 9, 2025
====================================
+1
View File
@@ -668,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).
+3
View File
@@ -23,6 +23,9 @@
#include "_config.hpp"
#endif
#include <cstdint>
#include <climits>
namespace mfem
{
+3 -1
View File
@@ -78,6 +78,7 @@ private:
opr.SetOperatorOwner(false);
CGSolver* pcg = new CGSolver();
// pcg->iterative_mode = false; // the multigrid algorithm does this
pcg->SetPrintLevel(-1);
pcg->SetMaxIter(200);
pcg->SetRelTol(sqrt(1e-4));
@@ -100,7 +101,8 @@ private:
Vector diag(fespace.GetTrueVSize());
bfs[level]->AssembleDiagonal(diag);
Solver* smoother = new OperatorChebyshevSmoother(*opr, diag, ess_tdof_list, 2);
Solver *smoother = new OperatorChebyshevSmoother(
*opr, diag, ess_tdof_list, 2);
AddLevel(opr.Ptr(), smoother, true, true);
}
};
+1
View File
@@ -88,6 +88,7 @@ private:
amg->SetPrintLevel(-1);
CGSolver* pcg = new CGSolver(MPI_COMM_WORLD);
// pcg->iterative_mode = false; // the multigrid algorithm does this
pcg->SetPrintLevel(-1);
pcg->SetMaxIter(10);
pcg->SetRelTol(sqrt(1e-4));
+34 -64
View File
@@ -255,6 +255,8 @@ PABilinearFormExtension::PABilinearFormExtension(BilinearForm *form)
void PABilinearFormExtension::SetupRestrictionOperators(const L2FaceValues m)
{
MFEM_PERF_FUNCTION;
if ( Device::Allows(Backend::CEED_MASK) ) { return; }
ElementDofOrdering ordering = GetEVectorOrdering(*a->FESpace());
elem_restrict = trial_fes->GetElementRestriction(ordering);
@@ -266,11 +268,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,50 +327,14 @@ 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();
}
}
void PABilinearFormExtension::Assemble()
{
MFEM_PERF_FUNCTION;
SetupRestrictionOperators(L2FaceValues::DoubleValued);
Array<BilinearFormIntegrator*> &integrators = *a->GetDBFI();
@@ -429,7 +391,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 +412,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 +438,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 +451,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);
}
@@ -529,6 +491,8 @@ void PABilinearFormExtension::FormLinearSystem(const Array<int> &ess_tdof_list,
void PABilinearFormExtension::MultInternal(const Vector &x, Vector &y,
const bool useAbs) const
{
MFEM_PERF_FUNCTION;
Array<BilinearFormIntegrator*> &integrators = *a->GetDBFI();
const int iSz = integrators.Size();
@@ -588,7 +552,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 +654,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 +663,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 +693,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 +740,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 +771,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);
@@ -898,6 +865,7 @@ EABilinearFormExtension::EABilinearFormExtension(BilinearForm *form)
void EABilinearFormExtension::Assemble()
{
MFEM_PERF_FUNCTION;
SetupRestrictionOperators(L2FaceValues::SingleValued);
ne = trial_fes->GetMesh()->GetNE();
@@ -920,7 +888,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 +922,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 +951,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 +1000,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);
}
}
}
@@ -1445,6 +1414,7 @@ FABilinearFormExtension::FABilinearFormExtension(BilinearForm *form)
void FABilinearFormExtension::Assemble()
{
MFEM_PERF_FUNCTION;
EABilinearFormExtension::Assemble();
FiniteElementSpace &fes = *a->FESpace();
int width = fes.GetVSize();
+2 -1
View File
@@ -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;
+1
View File
@@ -50,6 +50,7 @@ ElementTransformation *RefinedToCoarse(
void Coefficient::Project(QuadratureFunction &qf)
{
MFEM_PERF_FUNCTION;
QuadratureSpaceBase &qspace = *qf.GetSpace();
const int ne = qspace.GetNE();
Vector values;
+2 -2
View File
@@ -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)
+8 -1
View File
@@ -101,7 +101,10 @@ FiniteElementSpace::FiniteElementSpace(const FiniteElementSpace &orig,
FiniteElementSpace::FiniteElementSpace(Mesh *mesh,
const FiniteElementCollection *fec,
int vdim, int ordering)
{ Constructor(mesh, NULL, fec, vdim, ordering); }
{
MFEM_PERF_FUNCTION;
Constructor(mesh, NULL, fec, vdim, ordering);
}
FiniteElementSpace::FiniteElementSpace(Mesh *mesh, NURBSExtension *ext,
const FiniteElementCollection *fec,
@@ -393,6 +396,8 @@ void FiniteElementSpace::BuildElementToDofTable() const
{
if (elem_dof) { return; }
MFEM_PERF_FUNCTION;
// TODO: can we call GetElementDofs only once per element?
Table *el_dof = new Table;
Table *el_fos = (mesh->Dimension() > 2) ? (new Table) : NULL;
@@ -2748,6 +2753,8 @@ void FiniteElementSpace::BuildNURBSFaceToDofTable() const
void FiniteElementSpace::Construct()
{
MFEM_PERF_FUNCTION;
// This method should be used only for non-NURBS spaces.
MFEM_VERIFY(!NURBSext, "internal error");
+120 -1
View File
@@ -19,6 +19,7 @@
#include "../mesh/nurbs.hpp"
#include "../mesh/vtkhdf.hpp"
#include "../general/text.hpp"
#include "../general/reducers.hpp"
#ifdef MFEM_USE_MPI
#include "pfespace.hpp"
@@ -3326,8 +3327,126 @@ real_t GridFunction::ComputeLpError(const real_t p, Coefficient &exsol,
const IntegrationRule *irs[],
const Array<int> *elems) const
{
MFEM_PERF_FUNCTION;
MFEM_VERIFY(fes->GetVDim() == 1, "invalid vector dimension!");
real_t error = 0.0;
const FiniteElement *fe;
bool device_eval = true;
// TODO: check for cases that are not supported on device:
// * mixed meshes
// * meshes with non-tensor-product elements can have negative weights
// * variable orders
// * weight is not NULL
// * elems is not NULL
// * map type is not VALUE
// * ...
Mesh *mesh = fes->GetMesh();
const FiniteElement *fe = fes->GetTypicalFE();
if (mesh->GetNumGeometries(mesh->Dimension()) > 1 ||
(mesh->Dimension() > 1 && mesh->MeshGenerator() != 2) ||
fes->IsVariableOrder() ||
weight != nullptr ||
elems != nullptr ||
fe->GetMapType() != FiniteElement::MapType::VALUE)
{
device_eval = false;
}
if (device_eval)
{
Geometry::Type geom = mesh->GetTypicalElementGeometry();
const IntegrationRule *ir_p;
if (irs)
{
ir_p = irs[geom];
}
else
{
int intorder = 2*fe->GetOrder() + 3; // <----------
ir_p = &(IntRules.Get(geom, intorder));
}
const IntegrationRule &ir = *ir_p;
QuadratureSpace qs(*mesh, ir);
CoefficientVector coeff(exsol, qs, CoefficientStorage::FULL);
const QVectorLayout ql = QVectorLayout::byNODES;
const MemoryType d_mt = MemoryType::DEFAULT;
Vector q_vals;
// TODO: make this a method
{
// const FiniteElement *fe = fes->GetTypicalFE();
const int vdim = fes->GetVDim();
const int NE = fes->GetNE();
const int ND = fe->GetDof();
const int NQ = ir.GetNPoints();
MemoryType my_d_mt = (d_mt != MemoryType::DEFAULT) ? d_mt :
Device::GetDeviceMemoryType();
// byNODES : NQPT x VDIM x NE
// byVDIM : VDIM x NQPT x NE
q_vals.SetSize(vdim*NQ*NE, my_d_mt);
const QuadratureInterpolator &qi = *fes->GetQuadratureInterpolator(ir);
qi.SetOutputLayout(ql);
const bool use_tensor_products = UsesTensorBasis(*fes);
qi.DisableTensorProducts(!use_tensor_products);
const ElementDofOrdering e_ordering =
use_tensor_products ?
ElementDofOrdering::LEXICOGRAPHIC :
ElementDofOrdering::NATIVE;
const Operator *elem_restr = fes->GetElementRestriction(e_ordering);
if (fe->GetMapType() == FiniteElement::MapType::INTEGRAL)
{
// Pre-compute the geometric factors in order to set the desired
// MemoryType they use:
fes->GetMesh()->GetGeometricFactors(
ir, GeometricFactors::DETERMINANTS, my_d_mt);
}
if (elem_restr)
{
Vector f_e(vdim*ND*NE, my_d_mt);
elem_restr->Mult(*this, f_e);
qi.PhysValues(f_e, q_vals);
}
else
{
qi.PhysValues(*this, q_vals);
}
}
const real_t *exact_d = coeff.Read();
const real_t *gridf_d = q_vals.Read();
// FIXME: reuse the workspace vector from vector.cpp?
static Array<real_t> workspace;
if (p < infinity())
{
MemoryType my_d_mt = (d_mt != MemoryType::DEFAULT) ? d_mt :
Device::GetDeviceMemoryType();
const GeometricFactors *geom_factors =
fes->GetMesh()->GetGeometricFactors(
ir, GeometricFactors::DETERMINANTS, my_d_mt);
const real_t *detJ_d = geom_factors->detJ.Read();
const real_t *w_d = ir.GetWeights().Read();
const int NQ = ir.GetNPoints();
mfem::reduce(q_vals.Size(), error,
[=] MFEM_HOST_DEVICE(int i, real_t &r)
{
const real_t diff = fabs(exact_d[i] - gridf_d[i]);
r += w_d[i%NQ] * detJ_d[i] * pow(diff, p);
}, SumReducer<real_t> {}, true, workspace);
error = pow(error, 1./p);
}
else
{
mfem::reduce(q_vals.Size(), error,
[=] MFEM_HOST_DEVICE(int i, real_t &r)
{
const real_t diff = fabs(exact_d[i] - gridf_d[i]);
r = fmax(r, diff);
}, MaxReducer<real_t> {}, true, workspace);
}
return error;
}
ElementTransformation *T;
Vector vals;
+34 -31
View File
@@ -161,7 +161,8 @@ static void EADiffusionAssemble3D(const int NE,
auto B = Reshape(b.Read(), Q1D, D1D);
auto G = Reshape(g.Read(), Q1D, D1D);
auto D = Reshape(padata.Read(), Q1D, Q1D, Q1D, 6, NE);
auto A = Reshape(eadata.ReadWrite(), D1D, D1D, D1D, D1D, D1D, D1D, NE);
auto A = Reshape(add ? eadata.ReadWrite() : eadata.Write(),
D1D, D1D, D1D, D1D, D1D, D1D, NE);
mfem::forall_3D(NE, D1D, D1D, D1D, [=] MFEM_HOST_DEVICE (int e)
{
const int D1D = T_D1D ? T_D1D : d1d;
@@ -246,58 +247,60 @@ void DiffusionIntegrator::AssembleEA(const FiniteElementSpace &fes,
Vector &ea_data,
const bool add)
{
MFEM_PERF_FUNCTION;
AssemblePA(fes);
ne = fes.GetMesh()->GetNE();
const Array<real_t> &B = maps->B;
const Array<real_t> &G = maps->G;
decltype(&EADiffusionAssemble1D<>) kernel = nullptr;
if (dim == 1)
{
switch ((dofs1D << 4 ) | quad1D)
{
case 0x22: return EADiffusionAssemble1D<2,2>(ne,B,G,pa_data,ea_data,add);
case 0x33: return EADiffusionAssemble1D<3,3>(ne,B,G,pa_data,ea_data,add);
case 0x44: return EADiffusionAssemble1D<4,4>(ne,B,G,pa_data,ea_data,add);
case 0x55: return EADiffusionAssemble1D<5,5>(ne,B,G,pa_data,ea_data,add);
case 0x66: return EADiffusionAssemble1D<6,6>(ne,B,G,pa_data,ea_data,add);
case 0x77: return EADiffusionAssemble1D<7,7>(ne,B,G,pa_data,ea_data,add);
case 0x88: return EADiffusionAssemble1D<8,8>(ne,B,G,pa_data,ea_data,add);
case 0x99: return EADiffusionAssemble1D<9,9>(ne,B,G,pa_data,ea_data,add);
default: return EADiffusionAssemble1D(ne,B,G,pa_data,ea_data,add,
dofs1D,quad1D);
case 0x22: kernel = EADiffusionAssemble1D<2,2>;
case 0x33: kernel = EADiffusionAssemble1D<3,3>;
case 0x44: kernel = EADiffusionAssemble1D<4,4>;
case 0x55: kernel = EADiffusionAssemble1D<5,5>;
case 0x66: kernel = EADiffusionAssemble1D<6,6>;
case 0x77: kernel = EADiffusionAssemble1D<7,7>;
case 0x88: kernel = EADiffusionAssemble1D<8,8>;
case 0x99: kernel = EADiffusionAssemble1D<9,9>;
default: kernel = EADiffusionAssemble1D<>;
}
}
else if (dim == 2)
{
switch ((dofs1D << 4 ) | quad1D)
{
case 0x22: return EADiffusionAssemble2D<2,2>(ne,B,G,pa_data,ea_data,add);
case 0x33: return EADiffusionAssemble2D<3,3>(ne,B,G,pa_data,ea_data,add);
case 0x44: return EADiffusionAssemble2D<4,4>(ne,B,G,pa_data,ea_data,add);
case 0x55: return EADiffusionAssemble2D<5,5>(ne,B,G,pa_data,ea_data,add);
case 0x66: return EADiffusionAssemble2D<6,6>(ne,B,G,pa_data,ea_data,add);
case 0x77: return EADiffusionAssemble2D<7,7>(ne,B,G,pa_data,ea_data,add);
case 0x88: return EADiffusionAssemble2D<8,8>(ne,B,G,pa_data,ea_data,add);
case 0x99: return EADiffusionAssemble2D<9,9>(ne,B,G,pa_data,ea_data,add);
default: return EADiffusionAssemble2D(ne,B,G,pa_data,ea_data,add,
dofs1D,quad1D);
case 0x22: kernel = EADiffusionAssemble2D<2,2>;
case 0x33: kernel = EADiffusionAssemble2D<3,3>;
case 0x44: kernel = EADiffusionAssemble2D<4,4>;
case 0x55: kernel = EADiffusionAssemble2D<5,5>;
case 0x66: kernel = EADiffusionAssemble2D<6,6>;
case 0x77: kernel = EADiffusionAssemble2D<7,7>;
case 0x88: kernel = EADiffusionAssemble2D<8,8>;
case 0x99: kernel = EADiffusionAssemble2D<9,9>;
default: kernel = EADiffusionAssemble2D<>;
}
}
else if (dim == 3)
{
switch ((dofs1D << 4 ) | quad1D)
{
case 0x23: return EADiffusionAssemble3D<2,3>(ne,B,G,pa_data,ea_data,add);
case 0x34: return EADiffusionAssemble3D<3,4>(ne,B,G,pa_data,ea_data,add);
case 0x45: return EADiffusionAssemble3D<4,5>(ne,B,G,pa_data,ea_data,add);
case 0x56: return EADiffusionAssemble3D<5,6>(ne,B,G,pa_data,ea_data,add);
case 0x67: return EADiffusionAssemble3D<6,7>(ne,B,G,pa_data,ea_data,add);
case 0x78: return EADiffusionAssemble3D<7,8>(ne,B,G,pa_data,ea_data,add);
case 0x89: return EADiffusionAssemble3D<8,9>(ne,B,G,pa_data,ea_data,add);
default: return EADiffusionAssemble3D(ne,B,G,pa_data,ea_data,add,
dofs1D,quad1D);
case 0x23: kernel = EADiffusionAssemble3D<2,3>;
case 0x34: kernel = EADiffusionAssemble3D<3,4>;
case 0x45: kernel = EADiffusionAssemble3D<4,5>;
case 0x56: kernel = EADiffusionAssemble3D<5,6>;
case 0x67: kernel = EADiffusionAssemble3D<6,7>;
case 0x78: kernel = EADiffusionAssemble3D<7,8>;
case 0x89: kernel = EADiffusionAssemble3D<8,9>;
default: kernel = EADiffusionAssemble3D<>;
}
}
MFEM_ABORT("Unknown kernel.");
MFEM_VERIFY(kernel != nullptr, "Unknown kernel.");
kernel(ne,B,G,pa_data,ea_data,add,dofs1D,quad1D);
// Free the PA data:
pa_data.Destroy();
}
}
+4
View File
@@ -39,6 +39,8 @@ void DiffusionIntegrator::AssembleDiagonalPA(Vector &diag)
// PA Diffusion Apply kernel
void DiffusionIntegrator::AddMultPA(const Vector &x, Vector &y) const
{
MFEM_PERF_FUNCTION;
if (DeviceCanUseCeed())
{
ceedOp->AddMult(x, y);
@@ -88,6 +90,8 @@ void DiffusionIntegrator::AddMultTransposePA(const Vector &x, Vector &y) const
void DiffusionIntegrator::AssemblePA(const FiniteElementSpace &fes)
{
MFEM_PERF_FUNCTION;
const MemoryType mt = (pa_mt == MemoryType::DEFAULT) ?
Device::GetDeviceMemoryType() : pa_mt;
// Assuming the same element type
+29 -56
View File
@@ -23,6 +23,8 @@ namespace mfem
void MassIntegrator::AssemblePA(const FiniteElementSpace &fes)
{
MFEM_PERF_FUNCTION;
const MemoryType mt = (pa_mt == MemoryType::DEFAULT) ?
Device::GetDeviceMemoryType() : pa_mt;
@@ -59,26 +61,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 +108,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)
@@ -170,6 +141,8 @@ void MassIntegrator::AssembleDiagonalPA(Vector &diag)
void MassIntegrator::AddMultPA(const Vector &x, Vector &y) const
{
MFEM_PERF_FUNCTION;
if (DeviceCanUseCeed())
{
ceedOp->AddMult(x, y);
+2
View File
@@ -242,6 +242,8 @@ void DomainLFIntegrator::AssembleDevice(const FiniteElementSpace &fes,
const Array<int> &markers,
Vector &b)
{
MFEM_PERF_FUNCTION;
const FiniteElement &fe = *fes.GetTypicalFE();
const int qorder = oa * fe.GetOrder() + ob;
const Geometry::Type gtype = fe.GetGeomType();
+3
View File
@@ -161,6 +161,7 @@ bool LinearForm::SupportsDevice() const
void LinearForm::UseFastAssembly(bool use_fa)
{
MFEM_PERF_FUNCTION;
fast_assembly = use_fa;
if (fast_assembly && SupportsDevice() && !ext)
@@ -171,6 +172,8 @@ void LinearForm::UseFastAssembly(bool use_fa)
void LinearForm::Assemble()
{
MFEM_PERF_FUNCTION;
Array<int> vdofs;
ElementTransformation *eltrans;
Vector elemvect;
+19 -33
View File
@@ -15,10 +15,16 @@
namespace mfem
{
LinearFormExtension::LinearFormExtension(LinearForm *lf): lf(lf) { Update(); }
LinearFormExtension::LinearFormExtension(LinearForm *lf): lf(lf)
{
MFEM_PERF_FUNCTION;
Update();
}
void LinearFormExtension::Assemble()
{
MFEM_PERF_FUNCTION;
const FiniteElementSpace &fes = *lf->FESpace();
MFEM_VERIFY(lf->SupportsDevice(), "Not supported.");
MFEM_VERIFY(lf->Size() == fes.GetVSize(), "LinearForm size does not "
@@ -51,7 +57,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 +99,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;
});
}
@@ -112,6 +119,8 @@ void LinearFormExtension::Assemble()
void LinearFormExtension::Update()
{
MFEM_PERF_FUNCTION;
const FiniteElementSpace &fes = *lf->FESpace();
const Mesh &mesh = *fes.GetMesh();
constexpr ElementDofOrdering ordering = ElementDofOrdering::LEXICOGRAPHIC;
@@ -125,8 +134,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 +144,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,
+3 -1
View File
@@ -14,6 +14,7 @@
#include "../general/array.hpp"
#include "../linalg/vector.hpp"
#include "fespace.hpp"
namespace mfem
{
@@ -25,7 +26,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;
+2
View File
@@ -365,6 +365,8 @@ FiniteElementSpace &LORBase::GetFESpace() const
void LORBase::AssembleSystem(BilinearForm &a_ho, const Array<int> &ess_dofs)
{
MFEM_PERF_FUNCTION;
A.Clear();
delete a;
if (BatchedLORAssembly::FormIsSupported(a_ho))
+4
View File
@@ -360,6 +360,8 @@ void BatchedLORAssembly::FillJAndData(SparseMatrix &A) const
void BatchedLORAssembly::SparseIJToCSR(OperatorHandle &A) const
{
MFEM_PERF_FUNCTION;
const int nvdof = fes_ho.GetVSize();
// If A contains an existing SparseMatrix, reuse it (and try to reuse its
@@ -417,6 +419,8 @@ static void Assemble_(LOR_KERNEL &kernel, int dim, int sdim, int order)
template <typename LOR_KERNEL>
void BatchedLORAssembly::AssemblyKernel(BilinearForm &a)
{
MFEM_PERF_FUNCTION;
LOR_KERNEL kernel(a, fes_ho, X_vert, sparse_ij, sparse_mapping);
const int dim = fes_ho.GetMesh()->Dimension();
+2
View File
@@ -184,6 +184,8 @@ void BatchedLOR_H1::Assemble2D()
template <int ORDER>
void BatchedLOR_H1::Assemble3D()
{
MFEM_PERF_FUNCTION;
const int nel_ho = fes_ho.GetNE();
static constexpr int nv = 8;
static constexpr int dim = 3;
+143 -58
View File
@@ -10,6 +10,7 @@
// CONTRIBUTING.md for details.
#include "multigrid.hpp"
#include "../general/annotation.hpp"
namespace mfem
{
@@ -17,7 +18,10 @@ namespace mfem
MultigridBase::MultigridBase()
: cycleType(CycleType::VCYCLE), preSmoothingSteps(1), postSmoothingSteps(1),
nrhs(0)
{}
{
coarse_solver = nullptr;
own_coarse_solver = false;
}
MultigridBase::MultigridBase(const Array<Operator*>& operators_,
const Array<Solver*>& smoothers_,
@@ -29,12 +33,18 @@ MultigridBase::MultigridBase(const Array<Operator*>& operators_,
{
operators_.Copy(operators);
smoothers_.Copy(smoothers);
coarse_solver = nullptr;
ownedOperators_.Copy(ownedOperators);
ownedSmoothers_.Copy(ownedSmoothers);
own_coarse_solver = false;
}
MultigridBase::~MultigridBase()
{
if (own_coarse_solver)
{
delete coarse_solver;
}
for (int i = 0; i < operators.Size(); ++i)
{
if (ownedOperators[i])
@@ -56,16 +66,17 @@ void MultigridBase::InitVectors() const
X.SetSize(M, nrhs);
Y.SetSize(M, nrhs);
R.SetSize(M, nrhs);
Z.SetSize(M, nrhs);
for (int i = 0; i < X.NumRows(); ++i)
for (int i = 0; i < M; ++i)
{
const int n = operators[i]->Height();
for (int j = 0; j < X.NumCols(); ++j)
for (int j = 0; j < nrhs; ++j)
{
X(i, j) = new Vector(n);
Y(i, j) = new Vector(n);
if (i < M - 1)
{
X(i, j) = new Vector(n);
Y(i, j) = new Vector(n);
}
R(i, j) = new Vector(n);
Z(i, j) = new Vector(n);
}
}
}
@@ -76,10 +87,12 @@ void MultigridBase::EraseVectors() const
{
for (int j = 0; j < X.NumCols(); ++j)
{
delete X(i, j);
delete Y(i, j);
if (i < X.NumRows() - 1)
{
delete X(i, j);
delete Y(i, j);
}
delete R(i, j);
delete Z(i, j);
}
}
}
@@ -95,6 +108,12 @@ void MultigridBase::AddLevel(Operator* op, Solver* smoother,
ownedSmoothers.Append(ownSmoother);
}
void MultigridBase::AddCoarseSolver(Solver *c_solver, bool own_c_solver)
{
coarse_solver = c_solver;
own_coarse_solver = own_c_solver;
}
void MultigridBase::SetCycleType(CycleType cycleType_, int preSmoothingSteps_,
int postSmoothingSteps_)
{
@@ -105,25 +124,24 @@ void MultigridBase::SetCycleType(CycleType cycleType_, int preSmoothingSteps_,
void MultigridBase::Mult(const Vector& x, Vector& y) const
{
Array<const Vector*> X_(1);
Array<Vector*> Y_(1);
X_[0] = &x;
Y_[0] = &y;
const Vector *x_array[1] = { &x };
Array<const Vector*> X_(x_array, 1); // no heap allocation
Vector *y_array[1] = { &y };
Array<Vector*> Y_(y_array, 1); // no heap allocation
ArrayMult(X_, Y_);
}
void MultigridBase::ArrayMult(const Array<const Vector*>& X_,
Array<Vector*>& Y_) const
{
MFEM_PERF_FUNCTION;
MFEM_ASSERT(operators.Size() > 0,
"Multigrid solver does not have operators set!");
MFEM_ASSERT(X_.Size() == Y_.Size(),
"Number of columns mismatch in MultigridBase::Mult!");
if (iterative_mode)
{
MFEM_WARNING("Multigrid solver does not use iterative_mode and ignores "
"the initial guess!");
}
// Add capacity as necessary
nrhs = X_.Size();
@@ -134,96 +152,163 @@ void MultigridBase::ArrayMult(const Array<const Vector*>& X_,
for (int j = 0; j < nrhs; ++j)
{
MFEM_ASSERT(X_[j] && Y_[j], "Missing Vector in MultigridBase::Mult!");
*X(M - 1, j) = *X_[j];
*Y(M - 1, j) = 0.0;
}
Cycle(M - 1);
for (int j = 0; j < nrhs; ++j)
{
*Y_[j] = *Y(M - 1, j);
X(M - 1, j) = const_cast<Vector*>(X_[j]);
Y(M - 1, j) = Y_[j];
}
const bool zero = !iterative_mode;
Cycle(M - 1, zero);
}
void MultigridBase::SmoothingStep(int level, bool zero, bool transpose) const
{
MFEM_PERF_FUNCTION;
// y = y + S (x - A y) or y = y + S^T (x - A y)
// Note: 'zero' == true means that Y(level,*) are not initialized and we
// should assume that the input they typically provide to this call is zeros.
// We can't use the smoothers' iterative mode since we don't know if they
// actually support it, so we always turn the iterative mode off to properly
// use smoothers that do support it.
smoothers[level]->iterative_mode = false;
if (zero)
{
Array<Vector *> X_(X[level], nrhs), Y_(Y[level], nrhs);
GetSmootherAtLevel(level)->ArrayMult(X_, Y_);
MFEM_ASSERT(!transpose, "internal error!");
const Array<const Vector *> cX_((const Vector **)(X[level]), nrhs);
Array<Vector *> Y_(Y[level], nrhs);
GetSmootherAtLevel(level)->ArrayMult(cX_, Y_);
}
else
{
Array<Vector *> Y_(Y[level], nrhs), R_(R[level], nrhs),
Z_(Z[level], nrhs);
const Array<const Vector *> cY_((const Vector **)(Y[level]), nrhs),
cR_((const Vector **)(R[level]), nrhs);
Array<Vector *> Y_(Y[level], nrhs), R_(R[level], nrhs);
GetOperatorAtLevel(level)->ArrayMult(cY_, R_);
for (int j = 0; j < nrhs; ++j)
{
*R_[j] = *X(level, j);
// *R_[j] = *X(level, j) - *R_[j]
subtract(*X(level, j), *R_[j], *R_[j]);
}
GetOperatorAtLevel(level)->ArrayAddMult(Y_, R_, -1.0);
if (transpose)
{
GetSmootherAtLevel(level)->ArrayMultTranspose(R_, Z_);
GetSmootherAtLevel(level)->ArrayAddMultTranspose(cR_, Y_);
}
else
{
GetSmootherAtLevel(level)->ArrayMult(R_, Z_);
}
for (int j = 0; j < nrhs; ++j)
{
*Y_[j] += *Z_[j];
GetSmootherAtLevel(level)->ArrayAddMult(cR_, Y_);
}
}
}
void MultigridBase::Cycle(int level) const
void MultigridBase::CoarseSolve(bool zero) const
{
// Coarse solve
if (level == 0)
MFEM_PERF_FUNCTION;
// See the comment about iterative mode in SmoothingStep()
coarse_solver->iterative_mode = false;
if (zero)
{
SmoothingStep(0, true, false);
const Array<const Vector *> cX_((const Vector **)(X[0]), nrhs);
Array<Vector *> Y_(Y[0], nrhs);
coarse_solver->ArrayMult(cX_, Y_);
}
else
{
const Array<const Vector *> cY_((const Vector **)(Y[0]), nrhs),
cR_((const Vector **)(R[0]), nrhs);
Array<Vector *> Y_(Y[0], nrhs), R_(R[0], nrhs);
GetOperatorAtLevel(0)->ArrayMult(cY_, R_);
for (int j = 0; j < nrhs; ++j)
{
// *R_[j] = *X(0, j) - *R_[j]
subtract(*X(0, j), *R_[j], *R_[j]);
}
coarse_solver->ArrayAddMult(cR_, Y_);
}
}
void MultigridBase::Cycle(int level, bool zero) const
{
// Note: 'zero' == true means that Y(level,*) are not initialized and we
// should assume that the input they typically provide to this call is zeros.
// Coarse solve
if (level == 0 && !coarse_solver)
{
SmoothingStep(0, zero, false);
return;
}
// Pre-smooth
for (int i = 0; i < preSmoothingSteps; ++i)
{
SmoothingStep(level, (cycleType == CycleType::VCYCLE && i == 0), false);
SmoothingStep(level, zero && (i == 0), false);
}
// Coarse solve with 'coarse_solver'
if (level == 0)
{
CoarseSolve(preSmoothingSteps == 0 && zero);
goto mg_post_smooth;
}
// Compute residual and restrict
if (preSmoothingSteps == 0 && zero)
{
Array<Vector *> Y_(Y[level], nrhs), R_(R[level], nrhs),
X_(X[level - 1], nrhs);
const Array<const Vector *> cX_l((const Vector **)(X[level]), nrhs);
Array<Vector *> X_lm1(X[level - 1], nrhs);
GetProlongationAtLevel(level - 1)->ArrayMultTranspose(cX_l, X_lm1);
}
else
{
const Array<const Vector *> cY_((const Vector **)(Y[level]), nrhs),
cR_((const Vector **)(R[level]), nrhs);
Array<Vector *> R_(R[level], nrhs), X_(X[level - 1], nrhs);
GetOperatorAtLevel(level)->ArrayMult(cY_, R_);
for (int j = 0; j < nrhs; ++j)
{
*R_[j] = *X(level, j);
}
GetOperatorAtLevel(level)->ArrayAddMult(Y_, R_, -1.0);
GetProlongationAtLevel(level - 1)->ArrayMultTranspose(R_, X_);
for (int j = 0; j < nrhs; ++j)
{
*Y(level - 1, j) = 0.0;
// *R_[j] = *X(level, j) - *R_[j]
subtract(*X(level, j), *R_[j], *R_[j]);
}
GetProlongationAtLevel(level - 1)->ArrayMultTranspose(cR_, X_);
}
// Corrections
Cycle(level - 1);
Cycle(level - 1, true);
if (cycleType == CycleType::WCYCLE)
{
Cycle(level - 1);
// If the coarse solve at level 0 is "exact" solve, then we don't want to
// repeat it.
// To support multiple level 0 coarse-grid corrections, one can wrap that
// smoother in an SLI solver and use that instead.
if (level > 1) { Cycle(level - 1, false); }
}
// Prolongate and add
{
Array<Vector *> Y_(Y[level - 1], nrhs), Z_(Z[level], nrhs);
GetProlongationAtLevel(level - 1)->ArrayMult(Y_, Z_);
for (int j = 0; j < nrhs; ++j)
const Array<const Vector *> cY_lm1((const Vector **)(Y[level - 1]), nrhs);
Array<Vector *> Y_l(Y[level], nrhs);
if (preSmoothingSteps == 0 && zero)
{
*Y(level, j) += *Z_[j];
GetProlongationAtLevel(level - 1)->ArrayMult(cY_lm1, Y_l);
}
else
{
GetProlongationAtLevel(level - 1)->ArrayAddMult(cY_lm1, Y_l);
}
}
mg_post_smooth:
// Post-smooth
for (int i = 0; i < postSmoothingSteps; ++i)
{
+20 -2
View File
@@ -36,12 +36,14 @@ protected:
Array<Solver*> smoothers;
Array<bool> ownedOperators;
Array<bool> ownedSmoothers;
Solver *coarse_solver; /// can be NULL, see AddCoarseSolver()
bool own_coarse_solver;
CycleType cycleType;
int preSmoothingSteps;
int postSmoothingSteps;
mutable Array2D<Vector*> X, Y, R, Z;
mutable Array2D<Vector*> X, Y, R;
mutable int nrhs;
public:
@@ -65,6 +67,16 @@ public:
void AddLevel(Operator* op, Solver* smoother, bool ownOperator,
bool ownSmoother);
/// Adds a coarse solver for level 0 to work in tandem with the smoother
/** If this coarse solver is not given, the smoother at level 0 is used as
the coarse solver. When this coarse solver is given, the smoother at
level 0 is used similar to the smoothers at other levels. Thus, the
action at level 0 consists of:
- pre-smoothing steps with smoother 0,
- solve step with @a c_solver,
- post-smoothing steps with smoother 0. */
void AddCoarseSolver(Solver *c_solver, bool own_c_solver);
/// Returns the number of levels
int NumLevels() const { return operators.Size(); }
@@ -118,11 +130,14 @@ public:
private:
/// Application of a multigrid cycle at particular level
void Cycle(int level) const;
void Cycle(int level, bool zero) const;
/// Application of a pre-/post-smoothing step at particular level
void SmoothingStep(int level, bool zero, bool transpose) const;
/// Perform a coarse solve with 'coarse_solve' (must be non-NULL)
void CoarseSolve(bool zero) const;
/// Allocate or destroy temporary storage
void InitVectors() const;
void EraseVectors() const;
@@ -202,6 +217,9 @@ public:
/// Recover the solution of a linear system formed with FormFineLinearSystem()
void RecoverFineFEMSolution(const Vector& X, const Vector& b, Vector& x);
const Array<int> &GetFineEssentialTrueDofs() const
{ return *essentialTrueDofs.Last(); }
};
} // namespace mfem
+2
View File
@@ -124,6 +124,8 @@ void ParBilinearForm::pAllocMat()
void ParBilinearForm::ParallelRAP(SparseMatrix &loc_A, OperatorHandle &A,
bool steal_loc_A)
{
MFEM_PERF_FUNCTION;
ParFiniteElementSpace &pfespace = *ParFESpace();
// Create a block diagonal parallel matrix
+9 -2
View File
@@ -63,9 +63,11 @@ ParFiniteElementSpace::ParFiniteElementSpace(
ParFiniteElementSpace::ParFiniteElementSpace(
ParMesh *pm, const FiniteElementCollection *f, int dim, int ordering)
: FiniteElementSpace(pm, f, dim, ordering)
: FiniteElementSpace((MFEM_PERF_BEGIN(_MFEM_FUNC_NAME), pm),
f, dim, ordering)
{
ParInit(pm);
MFEM_PERF_END(_MFEM_FUNC_NAME);
}
ParFiniteElementSpace::ParFiniteElementSpace(
@@ -92,6 +94,7 @@ ParNURBSExtension *ParFiniteElementSpace::MakeLocalNURBSext(
void ParFiniteElementSpace::ParInit(ParMesh *pm)
{
MFEM_PERF_FUNCTION;
pmesh = pm;
pncmesh = nullptr;
@@ -181,6 +184,7 @@ void ParFiniteElementSpace::CommunicateGhostOrder()
void ParFiniteElementSpace::Construct()
{
MFEM_PERF_FUNCTION;
if (NURBSext)
{
ConstructTrueNURBSDofs();
@@ -839,6 +843,8 @@ void ParFiniteElementSpace::Build_Dof_TrueDof_Matrix() const // matrix P
if (P) { return; }
MFEM_PERF_FUNCTION;
if (!nd_strias)
{
// Safe to assume 1-1 correspondence between shared dofs
@@ -1424,6 +1430,7 @@ const Operator *ParFiniteElementSpace::GetRestrictionOperator() const
if (NRanks == 1)
{
R_transpose.reset(new IdentityOperator(GetTrueVSize()));
Rconf = new IdentityOperator(GetTrueVSize());
}
else
{
@@ -1436,8 +1443,8 @@ const Operator *ParFiniteElementSpace::GetRestrictionOperator() const
R_transpose.reset(
new DeviceConformingProlongationOperator(*this, true));
}
Rconf = new TransposeOperator(*R_transpose);
}
Rconf = new TransposeOperator(*R_transpose);
return Rconf;
}
else
+2
View File
@@ -45,6 +45,8 @@ void ParLinearForm::MakeRef(ParFiniteElementSpace *pf, Vector &v, int v_offset)
void ParLinearForm::Assemble()
{
MFEM_PERF_FUNCTION;
LinearForm::Assemble();
if (interior_face_integs.Size())
+10 -10
View File
@@ -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;
+25 -8
View File
@@ -96,17 +96,33 @@ void QuadratureSpaceBase::Integrate(VectorCoefficient &coeff,
void QuadratureSpace::ConstructOffsets()
{
MFEM_PERF_FUNCTION;
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 +204,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
View File
@@ -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; }
+1
View File
@@ -503,6 +503,7 @@ void QuadratureInterpolator::Mult(const Vector &e_vec,
Vector &q_der,
Vector &q_det) const
{
MFEM_PERF_FUNCTION;
using namespace internal::quadrature_interpolator;
const int ne = fespace->GetNE();
+6 -1
View File
@@ -25,7 +25,7 @@ namespace mfem
ElementRestriction::ElementRestriction(const FiniteElementSpace &f,
ElementDofOrdering e_ordering)
: fes(f),
: fes((MFEM_PERF_BEGIN(_MFEM_FUNC_NAME), f)),
ne(fes.GetNE()),
vdim(fes.GetVDim()),
byvdim(fes.GetOrdering() == Ordering::byVDIM),
@@ -104,10 +104,13 @@ ElementRestriction::ElementRestriction(const FiniteElementSpace &f,
offsets[i] = offsets[i - 1];
}
offsets[0] = 0;
MFEM_PERF_END(_MFEM_FUNC_NAME);
}
void ElementRestriction::Mult(const Vector& x, Vector& y) const
{
MFEM_PERF_FUNCTION;
// Assumes all elements have the same number of dofs
const int nd = dof;
const int vd = vdim;
@@ -152,6 +155,8 @@ void ElementRestriction::AbsMult(const Vector& x, Vector& y) const
template <bool ADD>
void ElementRestriction::TAddMultTranspose(const Vector& x, Vector& y) const
{
MFEM_PERF_FUNCTION;
// Assumes all elements have the same number of dofs
const int nd = dof;
const int vd = vdim;
+196 -17
View File
@@ -13,6 +13,7 @@
#include "bilinearform.hpp"
#include "pbilinearform.hpp"
#include "../general/forall.hpp"
#include "kernels.hpp"
namespace mfem
{
@@ -2322,6 +2323,76 @@ void Prolongation2D(const int NE, const int D1D, const int Q1D,
});
}
template <int DLO, int DHI>
static void SmemProlongation3D(const int NE,
const Vector& localL, Vector& localH,
const Array<real_t> &b, const Vector& mask)
{
MFEM_PERF_FUNCTION;
auto u_lo = Reshape(localL.Read(), DLO, DLO, DLO, NE);
auto u_hi = Reshape(localH.Write(), DHI, DHI, DHI, NE);
auto d_b = Reshape(b.Read(), DHI, DLO);
auto m_ = Reshape(mask.Read(), DHI, DHI, DHI, NE);
mfem::forall_2D(NE, DHI, DHI, [=] MFEM_HOST_DEVICE (int e)
{
// Load B into shared memory
MFEM_SHARED real_t s_B[DHI*DLO];
kernels::internal::LoadBt<DLO,DHI>(DLO,DHI,d_b,s_B);
const DeviceMatrix B(s_B, DHI, DLO);
MFEM_SHARED real_t s_u[DHI*DHI*DLO];
const DeviceCube u(s_u, DHI, DHI, DLO);
real_t v[DHI];
MFEM_FOREACH_THREAD(lx,x,DLO)
{
MFEM_FOREACH_THREAD(ly,y,DLO)
{
for (int hz = 0; hz < DHI; ++hz) { v[hz] = 0.0; }
for (int lz = 0; lz < DLO; ++lz)
{
const real_t XYZ = u_lo(lx,ly,lz,e);
for (int hz = 0; hz < DHI; ++hz) { v[hz] += XYZ * B(hz,lz); }
}
for (int hz = 0; hz < DHI; ++hz) { u(hz,ly,lx) = v[hz]; }
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(hz,y,DHI)
{
MFEM_FOREACH_THREAD(lx,x,DLO)
{
for (int hy = 0; hy < DHI; ++hy) { v[hy] = 0.0; }
for (int ly = 0; ly < DLO; ++ly)
{
const real_t zYX = u(hz,ly,lx);
for (int hy = 0; hy < DHI; ++hy) { v[hy] += zYX * B(hy,ly); }
}
for (int hy = 0; hy < DHI; ++hy) { u(hz,hy,lx) = v[hy]; }
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(hz,y,DHI)
{
MFEM_FOREACH_THREAD(hy,x,DHI)
{
for (int hx = 0; hx < DHI; ++hx) { v[hx] = 0.0; }
for (int lx = 0; lx < DLO; ++lx)
{
const real_t zyX = u(hz,hy,lx);
for (int hx = 0; hx < DHI; ++hx) { v[hx] += zyX * B(hx,lx); }
}
for (int hx = 0; hx < DHI; ++hx)
{
u_hi(hx,hy,hz,e) = m_(hx,hy,hz,e)*v[hx];
}
}
}
});
}
void Prolongation3D(const int NE, const int D1D, const int Q1D,
const Vector& localL, Vector& localH,
const Array<real_t>& B, const Vector& mask)
@@ -2403,9 +2474,9 @@ void Prolongation3D(const int NE, const int D1D, const int Q1D,
});
}
void Restriction2D(const int NE, const int D1D, const int Q1D,
const Vector& localH, Vector& localL,
const Array<real_t>& Bt, const Vector& mask)
void ProlongationTranspose2D(const int NE, const int D1D, const int Q1D,
const Vector& localH, Vector& localL,
const Array<real_t>& Bt, const Vector& mask)
{
auto x_ = Reshape(localH.Read(), Q1D, Q1D, NE);
auto y_ = Reshape(localL.Write(), D1D, D1D, NE);
@@ -2448,9 +2519,80 @@ void Restriction2D(const int NE, const int D1D, const int Q1D,
}
});
}
void Restriction3D(const int NE, const int D1D, const int Q1D,
const Vector& localH, Vector& localL,
const Array<real_t>& Bt, const Vector& mask)
template <int DLO, int DHI>
static void SmemProlongationTranspose3D(
const int NE, const Vector& localH, Vector& localL,
const Array<real_t>& bt, const Vector& mask)
{
MFEM_PERF_FUNCTION;
auto u_h = Reshape(localH.Read(), DHI, DHI, DHI, NE);
auto u_l = Reshape(localL.Write(), DLO, DLO, DLO, NE);
auto d_bt = Reshape(bt.Read(), DLO, DHI);
auto m_ = Reshape(mask.Read(), DHI, DHI, DHI, NE);
mfem::forall_2D(NE, DHI, DHI, [=] MFEM_HOST_DEVICE (int e)
{
// Load Bt into shared memory
MFEM_SHARED real_t s_Bt[DHI*DLO];
kernels::internal::LoadBt<DHI,DLO>(DHI,DLO,d_bt,s_Bt);
const DeviceMatrix Bt(s_Bt, DLO, DHI);
MFEM_SHARED real_t s_u[DLO*DHI*DHI];
const DeviceCube u(s_u, DLO, DHI, DHI);
real_t v[DLO];
MFEM_FOREACH_THREAD(hx,x,DHI)
{
MFEM_FOREACH_THREAD(hy,y,DHI)
{
for (int lz = 0; lz < DLO; ++lz) { v[lz] = 0.0; }
for (int hz = 0; hz < DHI; ++hz)
{
const real_t XYZ = m_(hx,hy,hz,e)*u_h(hx,hy,hz,e);
for (int lz = 0; lz < DLO; ++lz) { v[lz] += XYZ * Bt(lz,hz); }
}
for (int lz = 0; lz < DLO; ++lz) { u(lz,hy,hx) = v[lz]; }
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(lz,y,DLO)
{
MFEM_FOREACH_THREAD(hx,x,DHI)
{
for (int ly = 0; ly < DLO; ++ly) { v[ly] = 0.0; }
for (int hy = 0; hy < DHI; ++hy)
{
const real_t zYX = u(lz,hy,hx);
for (int ly = 0; ly < DLO; ++ly) { v[ly] += zYX * Bt(ly,hy); }
}
for (int ly = 0; ly < DLO; ++ly) { u(lz,ly,hx) = v[ly]; }
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(lz,y,DLO)
{
MFEM_FOREACH_THREAD(ly,x,DLO)
{
for (int lx = 0; lx < DLO; ++lx) { v[lx] = 0.0; }
for (int hx = 0; hx < DHI; ++hx)
{
const real_t zyX = u(lz,ly,hx);
for (int lx = 0; lx < DLO; ++lx) { v[lx] += zyX * Bt(lx,hx); }
}
for (int lx = 0; lx < DLO; ++lx)
{
u_l(lx,ly,lz,e) = v[lx];
}
}
}
});
}
void ProlongationTranspose3D(const int NE, const int D1D, const int Q1D,
const Vector& localH, Vector& localL,
const Array<real_t>& Bt, const Vector& mask)
{
auto x_ = Reshape(localH.Read(), Q1D, Q1D, Q1D, NE);
auto y_ = Reshape(localL.Write(), D1D, D1D, D1D, NE);
@@ -2518,11 +2660,15 @@ void Restriction3D(const int NE, const int D1D, const int Q1D,
}
});
}
} // namespace TransferKernels
void TensorProductPRefinementTransferOperator::Mult(const Vector& x,
Vector& y) const
{
MFEM_PERF_FUNCTION;
using namespace TransferKernels;
if (lFESpace.GetMesh()->GetNE() == 0)
{
return;
@@ -2531,11 +2677,25 @@ void TensorProductPRefinementTransferOperator::Mult(const Vector& x,
elem_restrict_lex_l->Mult(x, localL);
if (dim == 2)
{
TransferKernels::Prolongation2D(NE, D1D, Q1D, localL, localH, B, mask);
Prolongation2D(NE, D1D, Q1D, localL, localH, B, mask);
}
else if (dim == 3)
{
TransferKernels::Prolongation3D(NE, D1D, Q1D, localL, localH, B, mask);
switch ((D1D << 4 ) | Q1D)
{
case 0x23:
SmemProlongation3D<2,3>(NE, localL, localH, B, mask); break;
case 0x24:
SmemProlongation3D<2,4>(NE, localL, localH, B, mask); break;
case 0x35:
SmemProlongation3D<3,5>(NE, localL, localH, B, mask); break;
case 0x46:
SmemProlongation3D<4,6>(NE, localL, localH, B, mask); break;
case 0x47:
SmemProlongation3D<4,7>(NE, localL, localH, B, mask); break;
default:
Prolongation3D(NE, D1D, Q1D, localL, localH, B, mask); break;
}
}
else
{
@@ -2549,6 +2709,9 @@ void TensorProductPRefinementTransferOperator::Mult(const Vector& x,
void TensorProductPRefinementTransferOperator::MultTranspose(const Vector& x,
Vector& y) const
{
MFEM_PERF_FUNCTION;
using namespace TransferKernels;
if (lFESpace.GetMesh()->GetNE() == 0)
{
return;
@@ -2557,11 +2720,25 @@ void TensorProductPRefinementTransferOperator::MultTranspose(const Vector& x,
elem_restrict_lex_h->Mult(x, localH);
if (dim == 2)
{
TransferKernels::Restriction2D(NE, D1D, Q1D, localH, localL, Bt, mask);
ProlongationTranspose2D(NE, D1D, Q1D, localH, localL, Bt, mask);
}
else if (dim == 3)
{
TransferKernels::Restriction3D(NE, D1D, Q1D, localH, localL, Bt, mask);
switch ((D1D << 4 ) | Q1D)
{
case 0x23:
SmemProlongationTranspose3D<2,3>(NE, localH, localL, Bt, mask); break;
case 0x24:
SmemProlongationTranspose3D<2,4>(NE, localH, localL, Bt, mask); break;
case 0x35:
SmemProlongationTranspose3D<3,5>(NE, localH, localL, Bt, mask); break;
case 0x46:
SmemProlongationTranspose3D<4,6>(NE, localH, localL, Bt, mask); break;
case 0x47:
SmemProlongationTranspose3D<4,7>(NE, localH, localL, Bt, mask); break;
default:
ProlongationTranspose3D(NE, D1D, Q1D, localH, localL, Bt, mask); break;
}
}
else
{
@@ -2583,20 +2760,20 @@ TrueTransferOperator::TrueTransferOperator(const FiniteElementSpace& lFESpace_,
P = lFESpace.GetProlongationMatrix();
R = hFESpace.IsVariableOrder() ? hFESpace.GetHpRestrictionMatrix() :
hFESpace.GetRestrictionMatrix();
hFESpace.GetRestrictionOperator();
// P and R can be both null
// P can be null and R not null
// If P is not null it is assumed that R is not null as well
if (P) { MFEM_VERIFY(R, "Both P and R have to be not NULL") }
if (P)
if (!IsIdentityProlongation(P))
{
tmpL.SetSize(lFESpace_.GetVSize());
tmpH.SetSize(hFESpace_.GetVSize());
}
// P can be null and R not null
else if (R)
else if (!IsIdentityProlongation(R))
{
tmpH.SetSize(hFESpace_.GetVSize());
}
@@ -2609,13 +2786,14 @@ TrueTransferOperator::~TrueTransferOperator()
void TrueTransferOperator::Mult(const Vector& x, Vector& y) const
{
if (P)
MFEM_PERF_FUNCTION;
if (!IsIdentityProlongation(P))
{
P->Mult(x, tmpL);
localTransferOperator->Mult(tmpL, tmpH);
R->Mult(tmpH, y);
}
else if (R)
else if (!IsIdentityProlongation(R))
{
localTransferOperator->Mult(x, tmpH);
R->Mult(tmpH, y);
@@ -2628,13 +2806,14 @@ void TrueTransferOperator::Mult(const Vector& x, Vector& y) const
void TrueTransferOperator::MultTranspose(const Vector& x, Vector& y) const
{
if (P)
MFEM_PERF_FUNCTION;
if (!IsIdentityProlongation(P))
{
R->MultTranspose(x, tmpH);
localTransferOperator->MultTranspose(tmpH, tmpL);
P->MultTranspose(tmpL, y);
}
else if (R)
else if (!IsIdentityProlongation(R))
{
R->MultTranspose(x, tmpH);
localTransferOperator->MultTranspose(tmpH, y);
+1 -4
View File
@@ -621,9 +621,6 @@ public:
const FiniteElementSpace& lFESpace_,
const FiniteElementSpace& hFESpace_);
/// Destructor
virtual ~TensorProductPRefinementTransferOperator() { }
/// @brief Interpolation or prolongation of a vector \p x corresponding to
/// the coarse space to the vector \p y corresponding to the fine space.
void Mult(const Vector& x, Vector& y) const override;
@@ -642,7 +639,7 @@ private:
const FiniteElementSpace& lFESpace;
const FiniteElementSpace& hFESpace;
const Operator * P = nullptr;
const SparseMatrix * R = nullptr;
const Operator * R = nullptr;
TransferOperator* localTransferOperator;
mutable Vector tmpL;
mutable Vector tmpH;
+83 -9
View File
@@ -14,24 +14,98 @@
#include "../config/config.hpp"
#ifdef MFEM_USE_CALIPER
#define MFEM_CONCAT_(X,Y) X##Y
#define MFEM_CONCAT(X,Y) MFEM_CONCAT_(X,Y)
#ifdef MFEM_USE_CALIPER
#include "device.hpp"
#include "backends.hpp"
#ifdef MFEM_USE_MPI
#include "communication.hpp"
#endif
#include <caliper/cali.h>
#include <caliper/cali-manager.h>
#define MFEM_PERF_FUNCTION CALI_CXX_MARK_FUNCTION
#define MFEM_PERF_BEGIN(s) CALI_MARK_BEGIN(s)
#define MFEM_PERF_END(s) CALI_MARK_END(s)
#endif
namespace mfem
{
namespace internal
{
extern int annotation_sync_stream; // defined in globals.cpp
extern int annotation_sync_mpi; // defined in globals.cpp
#ifdef MFEM_USE_CALIPER
inline void AnnotationSync()
{
if (annotation_sync_stream && Device::Allows(Backend::DEVICE_MASK))
{
MFEM_STREAM_SYNC;
}
#ifdef MFEM_USE_MPI
if (annotation_sync_mpi && Mpi::IsInitialized() && !Mpi::IsFinalized())
{
MPI_Barrier(GetGlobalMPI_Comm());
}
#endif
}
struct FunctionAnnotation
{
::cali::Function cali_func;
FunctionAnnotation(const char *fname)
: cali_func((AnnotationSync(), fname)) { }
~FunctionAnnotation() { AnnotationSync(); }
};
struct ScopeAnnotation
{
::cali::ScopeAnnotation cali_scope;
ScopeAnnotation(const char *name)
: cali_scope((AnnotationSync(), name)) { }
~ScopeAnnotation() { AnnotationSync(); }
};
#endif // #ifdef MFEM_USE_CALIPER
} // namespace internal
} // namespace mfem
#ifdef MFEM_USE_CALIPER
#define MFEM_PERF_FUNCTION \
mfem::internal::FunctionAnnotation mfem_func_annotation_(_MFEM_FUNC_NAME)
#define MFEM_PERF_BEGIN(s) \
(mfem::internal::AnnotationSync(), CALI_MARK_BEGIN(s))
#define MFEM_PERF_END(s) \
(mfem::internal::AnnotationSync(), CALI_MARK_END(s))
#define MFEM_PERF_SCOPE(name) \
cali::Annotation::Guard cali_autogenerated_guard_name(cali::Annotation("function").begin(std::string(name).c_str()))
mfem::internal::ScopeAnnotation \
MFEM_CONCAT(mfem_scope_annotation_,__LINE__)(name)
#define MFEM_PERF_SYNC_STREAM(b) (mfem::internal::annotation_sync_stream = (b))
#define MFEM_PERF_SYNC_MPI(b) (mfem::internal::annotation_sync_mpi = (b))
#define MFEM_PERF_SYNC(b) (MFEM_PERF_SYNC_STREAM(b), MFEM_PERF_SYNC_MPI(b))
#else
#else // #ifdef MFEM_USE_CALIPER
#define MFEM_PERF_FUNCTION
#define MFEM_PERF_BEGIN(s)
#define MFEM_PERF_BEGIN(s) ((void)(0))
#define MFEM_PERF_END(s)
#define MFEM_PERF_SCOPE(name)
#endif
#define MFEM_PERF_SYNC_STREAM(b)
#define MFEM_PERF_SYNC_MPI(b)
#define MFEM_PERF_SYNC(b)
#endif
#endif // #ifdef MFEM_USE_CALIPER
#endif // MFEM_ANNOTATION_HPP
+13 -3
View File
@@ -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 &gt, Mode m)
: gtopo(gt), mode(m)
+43 -16
View File
@@ -23,13 +23,9 @@
#include <mpi.h>
#include <cstdint>
// can't directly use MPI_CXX_BOOL because Microsoft's MPI implementation
// doesn't include MPI_CXX_BOOL. Fallback to MPI_C_BOOL if unavailable.
#ifdef MPI_CXX_BOOL
#define MFEM_MPI_CXX_BOOL MPI_CXX_BOOL
#else
#define MFEM_MPI_CXX_BOOL MPI_C_BOOL
#endif
// Some MPI implementations do not have MPI_CXX_BOOL or do not handle it
// correctly, so we use MPI_UNSIGNED_CHAR as the MPI type for 'bool':
#define MFEM_MPI_CXX_BOOL MPI_UNSIGNED_CHAR
namespace mfem
{
@@ -607,10 +603,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;
@@ -619,15 +655,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).
+16
View File
@@ -151,6 +151,22 @@ Device::Device()
{
SetGPUAwareMPI(true);
}
if (const char *mfem_perf_sync = GetEnv("MFEM_PERF_SYNC"))
{
MFEM_PERF_SYNC(std::atoi(mfem_perf_sync));
MFEM_CONTRACT_VAR(mfem_perf_sync);
}
if (const char *mfem_perf_sync_stream = GetEnv("MFEM_PERF_SYNC_STREAM"))
{
MFEM_PERF_SYNC_STREAM(std::atoi(mfem_perf_sync_stream));
MFEM_CONTRACT_VAR(mfem_perf_sync_stream);
}
if (const char *mfem_perf_sync_mpi = GetEnv("MFEM_PERF_SYNC_MPI"))
{
MFEM_PERF_SYNC_MPI(std::atoi(mfem_perf_sync_mpi));
MFEM_CONTRACT_VAR(mfem_perf_sync_mpi);
}
}
Device::~Device()
+1 -1
View File
@@ -193,4 +193,4 @@ void mfem_warning(const char *msg)
}
}
}
} // namespace mfem
+1 -1
View File
@@ -208,4 +208,4 @@ __device__ void abort_msg(T & msg)
#define MFEM_ASSERT_KERNEL(x,...)
#endif
#endif
#endif // MFEM_ERROR_HPP
+159
View File
@@ -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
@@ -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)
{
+3
View File
@@ -31,6 +31,9 @@ namespace internal
{
bool mfem_out_initialized = false;
bool mfem_err_initialized = false;
int annotation_sync_stream = 0; // declared in annotation.hpp
int annotation_sync_mpi = 0; // declared in annotation.hpp
}
void OutStream::Init()
+6 -4
View File
@@ -657,7 +657,8 @@ private: // Static methods used by the Memory<T> class
/// Return the host pointer.
MFEM_ENZYME_INACTIVE static void *Register_(void *ptr, void *h_ptr,
size_t bytes, MemoryType mt,
bool own, bool alias, unsigned &flags);
bool own, bool alias,
unsigned &flags);
/// Register a pair of external host and device pointers
static void Register2_(void *h_ptr, void *d_ptr, size_t bytes,
@@ -741,7 +742,7 @@ private:
/// Insert a host address @a h_ptr and size *a bytes in the memory map to be
/// managed.
void Insert(void *h_ptr, size_t bytes, MemoryType h_mt, MemoryType d_mt);
void Insert(void *h_ptr, size_t bytes, MemoryType h_mt, MemoryType d_mt);
/// Insert a device and the host addresses in the memory map
void InsertDevice(void *d_ptr, void *h_ptr, size_t bytes,
@@ -981,7 +982,7 @@ inline void Memory<T>::Wrap(T *ptr, int size, bool own)
#ifdef MFEM_DEBUG
if (own && MemoryManager::Exists())
{
MemoryType h_ptr_mt = MemoryManager::GetHostMemoryType_(h_ptr);
MemoryType h_ptr_mt = MemoryManager::GetHostMemoryType_((void*)h_ptr);
MFEM_VERIFY(h_mt == h_ptr_mt,
"h_mt = " << (int)h_mt << ", h_ptr_mt = " << (int)h_ptr_mt);
}
@@ -989,7 +990,8 @@ inline void Memory<T>::Wrap(T *ptr, int size, bool own)
if (own && h_mt != MemoryType::HOST)
{
const size_t bytes = size*sizeof(T);
MemoryManager::Register_(ptr, ptr, bytes, h_mt, own, false, flags);
MemoryManager::Register_((void*)ptr, (void*)ptr, bytes, h_mt, own, false,
flags);
}
}
+4 -4
View File
@@ -20,13 +20,13 @@
#define MFEM_CU_or_HIP(stub) HIP##stub
#endif
#define MFEM_CONCAT(x, y, z) MFEM_CONCAT_(x, y, z)
#define MFEM_CONCAT_(x, y, z) x ## y ## z
#define MFEM_CONCAT3(x, y, z) MFEM_CONCAT3_(x, y, z)
#define MFEM_CONCAT3_(x, y, z) x ## y ## z
#ifdef MFEM_USE_SINGLE
#define MFEM_GPUBLAS_PREFIX(stub) MFEM_CONCAT(MFEM_cu_or_hip(blas), S, stub)
#define MFEM_GPUBLAS_PREFIX(stub) MFEM_CONCAT3(MFEM_cu_or_hip(blas), S, stub)
#elif defined(MFEM_USE_DOUBLE)
#define MFEM_GPUBLAS_PREFIX(stub) MFEM_CONCAT(MFEM_cu_or_hip(blas), D, stub)
#define MFEM_GPUBLAS_PREFIX(stub) MFEM_CONCAT3(MFEM_cu_or_hip(blas), D, stub)
#endif
#define MFEM_BLAS_SUCCESS MFEM_CU_or_HIP(BLAS_STATUS_SUCCESS)
+8
View File
@@ -1868,6 +1868,8 @@ HYPRE_Int HypreParMatrix::Mult(HypreParVector &x, HypreParVector &y,
void HypreParMatrix::Mult(real_t a, const Vector &x, real_t b, Vector &y) const
{
MFEM_PERF_FUNCTION;
MFEM_ASSERT(x.Size() == Width(), "invalid x.Size() = " << x.Size()
<< ", expected size = " << Width());
MFEM_ASSERT(y.Size() == Height(), "invalid y.Size() = " << y.Size()
@@ -1926,6 +1928,8 @@ void HypreParMatrix::Mult(real_t a, const Vector &x, real_t b, Vector &y) const
void HypreParMatrix::MultTranspose(real_t a, const Vector &x,
real_t b, Vector &y) const
{
MFEM_PERF_FUNCTION;
MFEM_ASSERT(x.Size() == Height(), "invalid x.Size() = " << x.Size()
<< ", expected size = " << Height());
MFEM_ASSERT(y.Size() == Width(), "invalid y.Size() = " << y.Size()
@@ -4091,6 +4095,8 @@ void HypreSolver::Setup(const HypreParVector &b, HypreParVector &x) const
{
if (setup_called) { return; }
MFEM_PERF_FUNCTION;
MFEM_VERIFY(A != NULL, "HypreParMatrix A is missing");
HYPRE_Int err_flag = SetupFcn()(*this, *A, b, x);
@@ -4116,6 +4122,8 @@ void HypreSolver::Setup(const Vector &b, Vector &x) const
void HypreSolver::Mult(const HypreParVector &b, HypreParVector &x) const
{
MFEM_PERF_FUNCTION;
HYPRE_Int err_flag;
if (A == NULL)
{
+14 -6
View File
@@ -50,17 +50,19 @@ void Operator::InitTVectors(const Operator *Po, const Operator *Ri,
void Operator::AddMult(const Vector &x, Vector &y, const real_t a) const
{
mfem::Vector z(y.Size());
Mult(x, z);
y.Add(a, z);
z_am.SetSize(y.Size());
z_am.UseDevice(true);
Mult(x, z_am);
y.Add(a, z_am);
}
void Operator::AddMultTranspose(const Vector &x, Vector &y,
const real_t a) const
{
mfem::Vector z(y.Size());
MultTranspose(x, z);
y.Add(a, z);
z_am.SetSize(y.Size());
z_am.UseDevice(true);
MultTranspose(x, z_am);
y.Add(a, z_am);
}
void Operator::ArrayMult(const Array<const Vector *> &X,
@@ -586,6 +588,8 @@ void ConstrainedOperator::EliminateRHS(const Vector &x, Vector &b) const
void ConstrainedOperator::ConstrainedMult(const Vector &x, Vector &y,
const bool transpose) const
{
MFEM_PERF_FUNCTION;
const int csz = constraint_list.Size();
if (csz == 0)
{
@@ -785,6 +789,8 @@ void RectangularConstrainedOperator::EliminateRHS(const Vector &x,
void RectangularConstrainedOperator::Mult(const Vector &x, Vector &y) const
{
MFEM_PERF_FUNCTION;
const int trial_csz = trial_constraints.Size();
const int test_csz = test_constraints.Size();
if (trial_csz == 0)
@@ -820,6 +826,8 @@ void RectangularConstrainedOperator::Mult(const Vector &x, Vector &y) const
void RectangularConstrainedOperator::MultTranspose(const Vector &x,
Vector &y) const
{
MFEM_PERF_FUNCTION;
const int trial_csz = trial_constraints.Size();
const int test_csz = test_constraints.Size();
if (test_csz == 0)
+12 -2
View File
@@ -13,6 +13,7 @@
#define MFEM_OPERATOR
#include "vector.hpp"
#include "../general/annotation.hpp"
namespace mfem
{
@@ -23,6 +24,13 @@ class RectangularConstrainedOperator;
/// Abstract operator
class Operator
{
private:
/// Auxiliary Vector used by the methods AddMult() and AddMultTranspose().
/** @note This Vector is private to prevent derived classes from accidentaly
using it in their implementation of Mult() or MultTranspose() which may
lead to hard-to-find bugs. */
mutable Vector z_am;
protected:
int height; ///< Dimension of the output / number of rows in the matrix.
int width; ///< Dimension of the input / number of columns in the matrix.
@@ -818,10 +826,12 @@ public:
explicit IdentityOperator(int n) : Operator(n) { }
/// Operator application
void Mult(const Vector &x, Vector &y) const override { y = x; }
void Mult(const Vector &x, Vector &y) const override
{ MFEM_PERF_FUNCTION; y = x; }
/// Application of the transpose
void MultTranspose(const Vector &x, Vector &y) const override { y = x; }
void MultTranspose(const Vector &x, Vector &y) const override
{ MFEM_PERF_FUNCTION; y = x; }
};
/// Returns true if P is the identity prolongation, i.e. if it is either NULL or
+85 -37
View File
@@ -55,6 +55,8 @@ IterativeSolver::IterativeSolver(MPI_Comm comm_)
real_t IterativeSolver::Dot(const Vector &x, const Vector &y) const
{
MFEM_PERF_FUNCTION;
#ifndef MFEM_USE_MPI
return (x * y);
#else
@@ -314,25 +316,29 @@ void OperatorJacobiSmoother::Mult(const Vector &x, Vector &y) const
MFEM_VERIFY(x.Size() == Width(), "invalid input vector");
MFEM_VERIFY(y.Size() == Height(), "invalid output vector");
auto DI = dinv.Read();
auto X = x.Read();
if (iterative_mode)
{
MFEM_VERIFY(oper, "iterative_mode == true requires the forward operator");
oper->Mult(y, residual); // r = A y
subtract(x, residual, residual); // r = x - A y
auto R = residual.Read();
auto Y = y.ReadWrite();
// y += D^{-1} (x - A y)
mfem::forall(height, [=] MFEM_HOST_DEVICE (int i)
{
Y[i] += DI[i] * (X[i] - R[i]);
});
}
else
{
residual = x;
y.UseDevice(true);
y = 0.0;
auto Y = y.Write();
// y = D^{-1} x
mfem::forall(height, [=] MFEM_HOST_DEVICE (int i)
{
Y[i] = DI[i] * X[i];
});
}
auto DI = dinv.Read();
auto R = residual.Read();
auto Y = y.ReadWrite();
mfem::forall(height, [=] MFEM_HOST_DEVICE (int i)
{
Y[i] += DI[i] * R[i];
});
}
OperatorChebyshevSmoother::OperatorChebyshevSmoother(const Operator &oper_,
@@ -348,7 +354,8 @@ OperatorChebyshevSmoother::OperatorChebyshevSmoother(const Operator &oper_,
diag(d),
coeffs(order),
ess_tdof_list(ess_tdofs),
residual(N),
residual(order > 1 ? N : 0),
z(order > 1 ? N : 0),
oper(&oper_) { Setup(); }
#ifdef MFEM_USE_MPI
@@ -368,14 +375,15 @@ OperatorChebyshevSmoother::OperatorChebyshevSmoother(const Operator &oper_,
real_t power_tolerance,
int power_seed)
#endif
: Solver(d.Size()),
: Solver((MFEM_PERF_BEGIN(_MFEM_FUNC_NAME), d.Size())),
order(order_),
N(d.Size()),
dinv(N),
diag(d),
coeffs(order),
ess_tdof_list(ess_tdofs),
residual(N),
residual(order > 1 ? N : 0),
z(order > 1 ? N : 0),
oper(&oper_)
{
OperatorJacobiSmoother invDiagOperator(diag, ess_tdofs, 1.0);
@@ -394,6 +402,7 @@ OperatorChebyshevSmoother::OperatorChebyshevSmoother(const Operator &oper_,
power_seed);
Setup();
MFEM_PERF_END(_MFEM_FUNC_NAME);
}
OperatorChebyshevSmoother::OperatorChebyshevSmoother(const Operator* oper_,
@@ -422,7 +431,7 @@ void OperatorChebyshevSmoother::Setup()
{
// Invert diagonal
residual.UseDevice(true);
helperVector.UseDevice(true);
z.UseDevice(true);
auto D = diag.Read();
auto X = dinv.Write();
mfem::forall(N, [=] MFEM_HOST_DEVICE (int i) { X[i] = 1.0 / D[i]; });
@@ -432,6 +441,20 @@ void OperatorChebyshevSmoother::Setup()
X[I[i]] = 1.0;
});
const int order_save = order;
order = -1; // avoid early exit in SetOrder() when 'new_order' == 'order'
SetOrder(order_save);
}
void OperatorChebyshevSmoother::SetOrder(int new_order)
{
if (new_order == order) { return; }
order = new_order;
coeffs.SetSize(order);
residual.SetSize(order > 1 ? N : 0);
z.SetSize(order > 1 ? N : 0);
// Set up Chebyshev coefficients
// For reference, see e.g., Parallel multigrid smoothing: polynomial versus
// Gauss-Seidel by Adams et al.
@@ -501,6 +524,8 @@ void OperatorChebyshevSmoother::Setup()
void OperatorChebyshevSmoother::Mult(const Vector& x, Vector &y) const
{
MFEM_PERF_FUNCTION;
if (iterative_mode)
{
MFEM_ABORT("Chebyshev smoother not implemented for iterative mode");
@@ -511,32 +536,55 @@ void OperatorChebyshevSmoother::Mult(const Vector& x, Vector &y) const
MFEM_ABORT("Chebyshev smoother requires operator");
}
residual = x;
helperVector.SetSize(x.Size());
helperVector.UseDevice(true);
y.UseDevice(true);
y = 0.0;
for (int k = 0; k < order; ++k)
// for k = 0, perform:
// r = D^{-1} x
// y = C_0 r
const real_t C_0 = coeffs[0];
auto Dinv = dinv.Read();
auto X = x.Read();
auto Y0 = y.Write();
if (order == 1)
{
// Apply
if (k > 0)
mfem::forall(N, [=] MFEM_HOST_DEVICE (int i)
{
oper->Mult(residual, helperVector);
residual = helperVector;
}
Y0[i] = C_0 * Dinv[i] * X[i];
});
}
else
{
auto R0 = residual.Write();
mfem::forall(N, [=] MFEM_HOST_DEVICE (int i)
{
Y0[i] = C_0 * (R0[i] = Dinv[i] * X[i]);
});
}
// Scale residual by inverse diagonal
const int n = N;
auto Dinv = dinv.Read();
auto R = residual.ReadWrite();
mfem::forall(n, [=] MFEM_HOST_DEVICE (int i) { R[i] *= Dinv[i]; });
for (int k = 1; k < order; ++k)
{
// Apply: z = A r
oper->Mult(residual, z);
// Add weighted contribution to y
// Scale residual by inverse diagonal and add weighted contribution to y:
// r = D^{-1} z
// y += C_k r
const real_t C_k = coeffs[k];
auto Z = z.Read();
auto Y = y.ReadWrite();
auto C = coeffs.Read();
mfem::forall(n, [=] MFEM_HOST_DEVICE (int i) { Y[i] += C[k] * R[i]; });
if (k < order-1)
{
auto R = residual.Write();
mfem::forall(N, [=] MFEM_HOST_DEVICE (int i)
{
Y[i] += C_k * (R[i] = Dinv[i] * Z[i]);
});
}
else
{
mfem::forall(N, [=] MFEM_HOST_DEVICE (int i)
{
Y[i] += C_k * Dinv[i] * Z[i];
});
}
}
}
@@ -3213,7 +3261,7 @@ void ResidualBCMonitor::MonitorResidual(
MPI_Comm comm = iter_solver->GetComm();
if (comm != MPI_COMM_NULL)
{
double glob_bc_norm_squared = 0.0;
real_t glob_bc_norm_squared = 0.0;
MPI_Reduce(&bc_norm_squared, &glob_bc_norm_squared, 1,
MPITypeMap<real_t>::mpi_type,
MPI_SUM, 0, comm);
+9 -8
View File
@@ -380,11 +380,11 @@ public:
void SetPositiveDiagonal(bool pos_diag = true) { use_abs_diag = pos_diag; }
/// Approach the solution of the linear system by applying Jacobi smoothing.
void Mult(const Vector &x, Vector &y) const;
void Mult(const Vector &x, Vector &y) const override;
/** @brief Approach the solution of the transposed linear system by applying
Jacobi smoothing. */
void MultTranspose(const Vector &x, Vector &y) const { Mult(x, y); }
void MultTranspose(const Vector &x, Vector &y) const override { Mult(x, y); }
/** @brief Recompute the diagonal using the method AssembleDiagonal of the
given new Operator, @a op. */
@@ -397,7 +397,7 @@ public:
When the new Operator, @a op, is not a (Par)BilinearForm, any previously
set array of essential true-dofs will be thrown away because in this case
any essential b.c. will be handled by the AssembleDiagonal method. */
void SetOperator(const Operator &op);
void SetOperator(const Operator &op) override;
private:
Vector dinv;
@@ -481,21 +481,22 @@ public:
/** @brief Approach the solution of the linear system by applying Chebyshev
smoothing. */
void Mult(const Vector &x, Vector &y) const;
void Mult(const Vector &x, Vector &y) const override;
/** @brief Approach the solution of the transposed linear system by applying
Chebyshev smoothing. */
void MultTranspose(const Vector &x, Vector &y) const { Mult(x, y); }
void MultTranspose(const Vector &x, Vector &y) const override { Mult(x, y); }
void SetOperator(const Operator &op_)
void SetOperator(const Operator &op_) override
{
oper = &op_;
}
void Setup();
void SetOrder(int new_order);
private:
const int order;
int order;
real_t max_eig_estimate;
const int N;
Vector dinv;
@@ -503,7 +504,7 @@ private:
Array<real_t> coeffs;
const Array<int>& ess_tdof_list;
mutable Vector residual;
mutable Vector helperVector;
mutable Vector z;
const Operator* oper;
};
+4
View File
@@ -764,6 +764,8 @@ void SparseMatrix::Mult(const Vector &x, Vector &y) const
void SparseMatrix::AddMult(const Vector &x, Vector &y, const real_t a) const
{
MFEM_PERF_FUNCTION;
MFEM_ASSERT(width == x.Size(), "Input vector size (" << x.Size()
<< ") must match matrix width (" << width << ")");
MFEM_ASSERT(height == y.Size(), "Output vector size (" << y.Size()
@@ -964,6 +966,8 @@ void SparseMatrix::MultTranspose(const Vector &x, Vector &y) const
void SparseMatrix::AddMultTranspose(const Vector &x, Vector &y,
const real_t a) const
{
MFEM_PERF_FUNCTION;
MFEM_ASSERT(height == x.Size(), "Input vector size (" << x.Size()
<< ") must match matrix height (" << height << ")");
MFEM_ASSERT(width == y.Size(), "Output vector size (" << y.Size()
+16 -2
View File
@@ -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);
}
@@ -205,14 +205,16 @@ Vector &Vector::operator=(const Vector &v)
data.CopyFrom(v.data, v.Size());
UseDevice(v.UseDevice());
#else
SetSize(v.Size());
const bool vuse = v.UseDevice();
const bool use_dev = UseDevice() || vuse;
if (use_dev) { MFEM_PERF_BEGIN(_MFEM_FUNC_NAME); }
SetSize(v.Size());
v.UseDevice(use_dev);
// keep 'data' where it is, unless 'use_dev' is true
if (use_dev) { Write(); }
data.CopyFrom(v.data, v.Size());
v.UseDevice(vuse);
if (use_dev) { MFEM_PERF_END(_MFEM_FUNC_NAME); }
#endif
return *this;
}
@@ -227,9 +229,11 @@ Vector &Vector::operator=(Vector &&v)
Vector &Vector::operator=(real_t value)
{
const bool use_dev = UseDevice();
if (use_dev) { MFEM_PERF_BEGIN(_MFEM_FUNC_NAME); }
const int N = size;
auto y = Write(use_dev);
mfem::forall_switch(use_dev, N, [=] MFEM_HOST_DEVICE (int i) { y[i] = value; });
if (use_dev) { MFEM_PERF_END(_MFEM_FUNC_NAME); }
return *this;
}
@@ -290,10 +294,12 @@ Vector &Vector::operator-=(const Vector &v)
MFEM_ASSERT(size == v.size, "incompatible Vectors!");
const bool use_dev = UseDevice() || v.UseDevice();
if (use_dev) { MFEM_PERF_BEGIN(_MFEM_FUNC_NAME); }
const int N = size;
const auto x = v.Read(use_dev);
auto y = ReadWrite(use_dev);
mfem::forall_switch(use_dev, N, [=] MFEM_HOST_DEVICE (int i) { y[i] -= x[i]; });
if (use_dev) { MFEM_PERF_END(_MFEM_FUNC_NAME); }
return *this;
}
@@ -311,10 +317,12 @@ Vector &Vector::operator+=(const Vector &v)
MFEM_ASSERT(size == v.size, "incompatible Vectors!");
const bool use_dev = UseDevice() || v.UseDevice();
if (use_dev) { MFEM_PERF_BEGIN(_MFEM_FUNC_NAME); }
const int N = size;
const auto x = v.Read(use_dev);
auto y = ReadWrite(use_dev);
mfem::forall_switch(use_dev, N, [=] MFEM_HOST_DEVICE (int i) { y[i] += x[i]; });
if (use_dev) { MFEM_PERF_END(_MFEM_FUNC_NAME); }
return *this;
}
@@ -326,9 +334,11 @@ Vector &Vector::Add(const real_t a, const Vector &Va)
{
const int N = size;
const bool use_dev = UseDevice() || Va.UseDevice();
if (use_dev) { MFEM_PERF_BEGIN(_MFEM_FUNC_NAME); }
const auto x = Va.Read(use_dev);
auto y = ReadWrite(use_dev);
mfem::forall_switch(use_dev, N, [=] MFEM_HOST_DEVICE (int i) { y[i] += a * x[i]; });
if (use_dev) { MFEM_PERF_END(_MFEM_FUNC_NAME); }
}
return *this;
}
@@ -445,6 +455,7 @@ void add(const Vector &v1, real_t alpha, const Vector &v2, Vector &v)
{
#if !defined(MFEM_USE_LEGACY_OPENMP)
const bool use_dev = v1.UseDevice() || v2.UseDevice() || v.UseDevice();
if (use_dev) { MFEM_PERF_BEGIN(_MFEM_FUNC_NAME); }
const int N = v.size;
// Note: get read access first, in case v is the same as v1/v2.
const auto d_x = v1.Read(use_dev);
@@ -454,6 +465,7 @@ void add(const Vector &v1, real_t alpha, const Vector &v2, Vector &v)
{
d_z[i] = d_x[i] + alpha * d_y[i];
});
if (use_dev) { MFEM_PERF_END(_MFEM_FUNC_NAME); }
#else
const real_t *v1p = v1.data, *v2p = v2.data;
real_t *vp = v.data;
@@ -569,6 +581,7 @@ void subtract(const Vector &x, const Vector &y, Vector &z)
#if !defined(MFEM_USE_LEGACY_OPENMP)
const bool use_dev = x.UseDevice() || y.UseDevice() || z.UseDevice();
if (use_dev) { MFEM_PERF_BEGIN(_MFEM_FUNC_NAME); }
const int N = x.size;
// Note: get read access first, in case z is the same as x/y.
const auto xd = x.Read(use_dev);
@@ -578,6 +591,7 @@ void subtract(const Vector &x, const Vector &y, Vector &z)
{
zd[i] = xd[i] - yd[i];
});
if (use_dev) { MFEM_PERF_END(_MFEM_FUNC_NAME); }
#else
const real_t *xp = x.data;
const real_t *yp = y.data;
+2 -1
View File
@@ -125,7 +125,8 @@ EXAMPLE_TEST_DIRS := examples
MINIAPP_SUBDIRS = common electromagnetics meshing navier performance tools \
toys nurbs gslib adjoint solvers shifted mtop parelag tribol autodiff dfem \
hooke multidomain dpg hdiv-linear-solver spde diag-smoothers
hooke multidomain dpg hdiv-linear-solver spde diag-smoothers \
benchmarks/ceed-solver-bps
MINIAPP_DIRS := $(addprefix miniapps/,$(MINIAPP_SUBDIRS))
MINIAPP_TEST_DIRS := $(filter-out %/common,$(MINIAPP_DIRS))
MINIAPP_USE_COMMON := $(addprefix miniapps/,electromagnetics meshing tools \
+114 -24
View File
@@ -32,6 +32,7 @@
#include <cstring>
#include <ctime>
#include <functional>
#include <set>
#include <numeric>
#include <unordered_map>
#include <unordered_set>
@@ -921,8 +922,66 @@ 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()
{
MFEM_PERF_FUNCTION;
for (int i = 0; i < geom_factors.Size(); i++)
{
delete geom_factors[i];
@@ -1804,6 +1863,8 @@ void Mesh::Destroy()
TetMemory.Clear();
#endif
elem_attrs_cache.DeleteAll();
bdr_face_attrs_cache.DeleteAll();
attributes.DeleteAll();
bdr_attributes.DeleteAll();
}
@@ -1817,36 +1878,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 +4527,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()
@@ -6503,6 +6580,7 @@ void XYZ_VectorFunction(const Vector &p, Vector &v)
void Mesh::GetNodes(GridFunction &nodes) const
{
MFEM_PERF_FUNCTION;
if (Nodes == NULL || Nodes->FESpace() != nodes.FESpace())
{
const int newSpaceDim = nodes.FESpace()->GetVDim();
@@ -6523,6 +6601,7 @@ void Mesh::SetNodalFESpace(FiniteElementSpace *nfes)
void Mesh::EnsureNodes()
{
MFEM_PERF_FUNCTION;
if (Nodes)
{
const FiniteElementCollection *fec = GetNodalFESpace()->FEColl();
@@ -6575,6 +6654,7 @@ const FiniteElementSpace *Mesh::GetNodalFESpace() const
void Mesh::SetCurvature(int order, bool discont, int space_dim, int ordering)
{
MFEM_PERF_FUNCTION;
if (order <= 0)
{
delete Nodes;
@@ -7721,6 +7801,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 +10936,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,
@@ -14649,7 +14739,7 @@ GeometricFactors::GeometricFactors(const GridFunction &nodes,
void GeometricFactors::Compute(const GridFunction &nodes,
MemoryType d_mt)
{
MFEM_PERF_FUNCTION;
const FiniteElementSpace *fespace = nodes.FESpace();
const FiniteElement *fe = fespace->GetTypicalFE();
const int dim = fe->GetDim();
+38 -3
View File
@@ -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
@@ -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
+15 -8
View File
@@ -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!");
}
}
}
@@ -2010,6 +2016,7 @@ void ParMesh::DeleteFaceNbrData()
void ParMesh::SetCurvature(int order, bool discont, int space_dim, int ordering)
{
MFEM_PERF_FUNCTION;
DeleteFaceNbrData();
space_dim = (space_dim == -1) ? spaceDim : space_dim;
FiniteElementCollection* nfec;
+2 -1
View File
@@ -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;
@@ -0,0 +1,156 @@
// 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 __KERSHAW_HPP__
#define __KERSHAW_HPP__
#include "mfem.hpp"
namespace mfem
{
// 1D transformation at the right boundary.
real_t right(const real_t eps, const real_t x)
{
return (x <= 0.5) ? (2-eps) * x : 1 + eps*(x-1);
}
// 1D transformation at the left boundary
real_t left(const real_t eps, const real_t x)
{
return 1-right(eps,1-x);
}
// Transition from a value of "a" for x=0, to a value of "b" for x=1. Smoothness
// is controlled by the parameter "s", taking values 0, 1, or 2.
real_t step(const real_t a, const real_t b, real_t x, int s)
{
if (x <= 0) { return a; }
if (x >= 1) { return b; }
switch (s)
{
case 0:
default:
return a + (b-a) * (x);
case 1: return a + (b-a) * (x*x*(3-2*x));
case 2: return a + (b-a) * (x*x*x*(x*(6*x-15)+10));
}
}
// 3D version of a generalized Kershaw mesh transformation, see D. Kershaw,
// "Differencing of the diffusion equation in Lagrangian hydrodynamic codes",
// JCP, 39:375395, 1981.
//
// The input mesh should be Cartesian nx x ny x nz with nx divisible by 6 and
// ny, nz divisible by 2.
//
// The eps parameters are in (0, 1]. Uniform mesh is recovered for epsy=epsz=1.
void kershaw(const real_t epsy, const real_t epsz, const int smoothness,
const real_t x, const real_t y, const real_t z,
real_t &X, real_t &Y, real_t &Z)
{
X = x;
int layer = x*6.0;
real_t lambda = (x-layer/6.0)*6;
// The x-range is split in 6 layers going from left-to-left, left-to-right,
// right-to-left (2 layers), left-to-right and right-to-right yz-faces.
switch (layer)
{
case 0:
Y = left(epsy, y);
Z = left(epsz, z);
break;
case 1:
case 4:
Y = step(left(epsy, y), right(epsy, y), lambda, smoothness);
Z = step(left(epsz, z), right(epsz, z), lambda, smoothness);
break;
case 2:
Y = step(right(epsy, y), left(epsy, y), lambda/2, smoothness);
Z = step(right(epsz, z), left(epsz, z), lambda/2, smoothness);
break;
case 3:
Y = step(right(epsy, y), left(epsy, y), (1+lambda)/2, smoothness);
Z = step(right(epsz, z), left(epsz, z), (1+lambda)/2, smoothness);
break;
default:
Y = right(epsy, y);
Z = right(epsz, z);
break;
}
}
struct KershawTransformation : VectorCoefficient
{
real_t epsy, epsz;
int dim, s;
KershawTransformation(int dim_, real_t epsy_, real_t epsz_, int s_=0)
: VectorCoefficient(dim_), epsy(epsy_), epsz(epsz_), dim(dim_), s(s_) { }
using VectorCoefficient::Eval;
void Eval(Vector &V, ElementTransformation &T,
const IntegrationPoint &ip) override
{
real_t xyz[3];
Vector transip(xyz, 3);
T.Transform(ip, transip);
if (dim == 1)
{
V[0] = xyz[0]; // no transformation in 1D
}
else if (dim == 2)
{
real_t z=0, zt;
kershaw(epsy, epsz, s, xyz[0], xyz[1], z, V[0], V[1], zt);
}
else // dim == 3
{
kershaw(epsy, epsz, s, xyz[0], xyz[1], xyz[2], V[0], V[1], V[2]);
}
}
};
ParMesh CreateKershawMesh(int nx, int ny, int nz, real_t epsy, real_t epsz)
{
const bool sfc_order = true;
Mesh serial_mesh;
if (nx > 0 && ny == 0 && nz == 0)
{
serial_mesh = Mesh::MakeCartesian1D(nx, 1.0);
}
else if (nx > 0 && ny > 0 && nz == 0)
{
serial_mesh = Mesh::MakeCartesian2D(nx, ny, Element::QUADRILATERAL,
false, 1, 1, sfc_order);
}
else if (nx > 0 && ny > 0 && nz > 0)
{
serial_mesh = Mesh::MakeCartesian3D(nx, ny, nz, Element::HEXAHEDRON,
1, 1, 1, sfc_order);
}
else
{
MFEM_ABORT("Bad grid size");
}
KershawTransformation kt(serial_mesh.Dimension(), epsy, epsz);
serial_mesh.Transform(kt);
return ParMesh(MPI_COMM_WORLD, serial_mesh);
}
ParMesh CreateKershawMesh(int n, real_t eps)
{
return CreateKershawMesh(n, n, n, eps, eps);
}
}
#endif
@@ -0,0 +1,77 @@
# Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
# at the Lawrence Livermore National Laboratory. All Rights reserved. See files
# LICENSE and NOTICE for details. LLNL-CODE-806117.
#
# This file is part of the MFEM library. For more information and source code
# availability visit https://mfem.org.
#
# MFEM is free software; you can redistribute it and/or modify it under the
# terms of the BSD-3 license. We welcome feedback and contributions, see file
# CONTRIBUTING.md for details.
# Use the MFEM build directory
MFEM_DIR ?= ../../..
MFEM_BUILD_DIR ?= ../../..
MFEM_INSTALL_DIR ?= ../../../mfem
SRC = $(if $(MFEM_DIR:../../..=),$(MFEM_DIR)/miniapps/benchmarks/ceed-solver-bps/,)
CONFIG_MK = $(or $(wildcard $(MFEM_BUILD_DIR)/config/config.mk),\
$(wildcard $(MFEM_INSTALL_DIR)/share/mfem/config.mk))
MFEM_LIB_FILE = mfem_is_not_built
-include $(CONFIG_MK)
SEQ_MINIAPPS =
PAR_MINIAPPS = solver-bp
ifeq ($(MFEM_USE_MPI),NO)
MINIAPPS = $(SEQ_MINIAPPS)
else
MINIAPPS = $(PAR_MINIAPPS) $(SEQ_MINIAPPS)
endif
EXTRA_SOURCES = preconditioners.cpp
EXTRA_HEADERS = kershaw.hpp rhs.hpp preconditioners.hpp
EXTRA_OBJECTS = $(EXTRA_SOURCES:.cpp=.o)
.SUFFIXES:
.SUFFIXES: .o .cpp .mk
.PHONY: all clean clean-build clean-exec
.PRECIOUS: %.o
# Remove built-in rules
%: %.cpp
%.o: %.cpp
all: $(MINIAPPS)
# Rule for building solver-bp
solver-bp: solver-bp.o $(addprefix $(SRC),$(EXTRA_HEADERS)) \
$(EXTRA_OBJECTS) $(MFEM_LIB_FILE) $(CONFIG_MK)
$(MFEM_CXX) $(MFEM_LINK_FLAGS) $< -o $@ $(EXTRA_OBJECTS) $(MFEM_LIBS)
# Rules for compiling *.o files
# -I$(MFEM_DIR) is needed for "general/forall.hpp" for out-of-source builds
%.o: $(SRC)%.cpp $(wildcard $(SRC)%.hpp) $(CONFIG_MK)
$(MFEM_CXX) $(MFEM_FLAGS) -I$(MFEM_DIR) -c $(<) -o $(@)
MFEM_TESTS = MINIAPPS
include $(MFEM_TEST_MK)
# Testing: Specific execution options
RUN_MPI = $(MFEM_MPIEXEC) $(MFEM_MPIEXEC_NP) $(MFEM_MPI_NP)
solver-bp-test-par: solver-bp
@$(call mfem-test,$<, $(RUN_MPI), CEED Solver BP,,SKIP-NO-VIS)
# Testing: "test" target and mfem-test* variables are defined in config/test.mk
# Generate an error message if the MFEM library is not built and exit
$(MFEM_LIB_FILE):
$(error The MFEM library is not built)
clean: clean-build clean-exec
clean-build:
rm -f *.o *~ $(SEQ_MINIAPPS) $(PAR_MINIAPPS) $(EXTRA_OBJECTS)
rm -rf *.dSYM *.TVD.*breakpoints
clean-exec:
@true
@@ -0,0 +1,129 @@
import csv
from pylab import *
fields=[
['code ID', 'str'],
['preconditioner ID', 'str'],
['machine ID', 'str'],
['number of nodes', 'int'],
['number of MPI ranks', 'int'],
['n_x', 'int'], ['n_y', 'int'], ['n_z', 'int'],
['solution polynomial degree', 'int'],
['number of 1D quadrature points', 'float'],
['eps_y', 'float'], ['eps_z', 'float'],
['ndofs (including Dirichlet boundary)', 'int'],
['niter', 'int'],
['initial residual', 'float'], ['final residual', 'float'],
['error', 'float'],
['t_setup (preconditioner setup)', 'float'],
['t_solve (total iter time)', 'float']]
fields_dict=dict(fields)
def convert(obj, type_str):
ctor=getattr(__builtins__, type_str)
return ctor(obj)
input_csv='run-001.csv'
print('reading %s ...' % input_csv)
runs = []
with open(input_csv) as csvfile:
csvreader = csv.DictReader(csvfile, fieldnames=[f[0] for f in fields],
restkey='additional notes')
for row in csvreader:
for i in fields_dict:
row[i]=convert(row[i], fields_dict[i])
runs.append(row)
orders=[r['solution polynomial degree'] for r in runs]
orders=unique(orders) # numpy function
# orders=[1]
nps=[r['number of MPI ranks'] for r in runs]
nps=unique(nps)
if len(nps) > 1:
print('multiple num-ranks present: %s' % nps)
quit()
np=nps[0]
# plot fx (or fx/fn) vs fy, (or fx/fn/fy, etc) for all orders
fn='number of MPI ranks'
fx='ndofs (including Dirichlet boundary)'
fy='t_solve (total iter time)'
# fy='niter'
# fy='error'
fz='niter'
figure()
for p in orders:
rr=[r for r in runs if (r['solution polynomial degree']==p and
r['niter']>0)]
if len(rr)==0:
continue
# pl_data=asarray([[r[fx],r[fx]/r[fy]] for r in rr])
# pl_data=asarray([[r[fx],r[fy]] for r in rr])
# pl_data=asarray([[r[fx],r[fx]/(r[fy]/r[fz])] for r in rr])
pl_data=asarray([[r[fx]/r[fn],r[fx]/r[fn]/r[fy]] for r in rr])
# pl_data=asarray([[r[fx]/r[fn],r[fy]] for r in rr])
plot(pl_data[:,0],pl_data[:,1], 'o-', label='p=%i'%p)
rnx=asarray([r['n_x'] for r in rr])
rerr=asarray([r['error'] for r in rr])
rate=arange(1.0,len(rnx))
for l in range(1,len(rnx)):
rate[l-1]=log(rerr[l-1]/rerr[l])/log(rnx[l]/rnx[l-1])
set_printoptions(formatter={'float':"{:6.2f}".format},linewidth=120)
print(f"p={p} rate:{rate}")
# xscale('log', basex=10) # older matplotlib
xscale('log', base=10)
# xlim(4e4,3.1e7)
xlim(4e4,5e6)
# yscale('log', basey=10) # older matplotlib
# yscale('log', base=10)
# ylim(1e5,2e7)
# ylim(0,2.55e7)
# ylim(0,3.25e7)
# ylim(0,5e6)
ymin,ymax=ylim()
ylim(0,ymax)
# ylim(1e-2,2e1)
# ylim(3e-3,6e-2)
# xlabel(fx)
# xlabel('# DOFs')
xlabel('# DOFs / # Ranks')
# ylabel(fx + ' / ' + fy)
# ylabel(fy)
# ylabel('# DOFs / t_solve')
ylabel('# DOFs / # Ranks / t_solve')
# ylabel('t_solve')
# ylabel('# DOFs / (t_solve / # Iter)')
# ylabel('# Iter')
# ylabel('L2 error')
# ylabel('Grad L2 error')
grid('on', color='gray', ls='dotted')
grid('on', axis='both', which='minor', color='gray', ls='dotted')
legend(ncol=2, loc='best')
ranks='1 MPI rank'
if np > 1:
ranks='%s MPI ranks' % (np,np)
hypre='hypre CPU'
# hypre='hypre HIP'
# prec=hypre+', p-MG(1,1)'
prec=hypre+', LOR'
# prec='Jacobi'
# eps='1'
eps='0.3'
mfem='MFEM CPU'
# mfem='MFEM HIP'
title(mfem + ', ' + prec + ', $\\varepsilon = ' + eps + '$, ' + ranks)
if 1: # write .pdf file?
pdf_file='plot.pdf'
print('saving figure --> %s'%pdf_file)
savefig(pdf_file, format='pdf', bbox_inches='tight')
if 0: # show the figures?
print('\nshowing figures ...')
show()
@@ -0,0 +1,241 @@
// 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 "preconditioners.hpp"
namespace mfem
{
AssemblyLevel GetCoarseAssemblyLevel(SolverConfig config)
{
switch (config.type)
{
case SolverConfig::JACOBI:
case SolverConfig::LOR_HYPRE:
case SolverConfig::LOR_AMGX:
return AssemblyLevel::PARTIAL;
default:
return AssemblyLevel::FULL;
// return AssemblyLevel::LEGACYFULL;
}
}
bool NeedsLOR(SolverConfig config)
{
switch (config.type)
{
case SolverConfig::LOR_HYPRE:
case SolverConfig::LOR_AMGX:
return true;
default:
return false;
}
}
DiffusionMultigrid::DiffusionMultigrid(
ParFiniteElementSpaceHierarchy& hierarchy,
Coefficient &coeff_,
Array<int>& ess_bdr,
SolverConfig coarse_solver_config,
int q1d_inc_,
int smoothers_cheby_order_)
: GeometricMultigrid(hierarchy, ess_bdr),
coeff(coeff_),
q1d_inc(q1d_inc_),
irs(0, Quadrature1D::GaussLegendre),
smoothers_cheby_order(smoothers_cheby_order_)
{
ConstructCoarseOperatorAndSolver(
coarse_solver_config, hierarchy.GetFESpaceAtLevel(0), ess_bdr);
int nlevels = hierarchy.GetNumLevels();
for (int i=1; i<nlevels; ++i)
{
ConstructOperatorAndSmoother(hierarchy.GetFESpaceAtLevel(i), ess_bdr);
}
}
void DiffusionMultigrid::ConstructBilinearForm(
ParFiniteElementSpace &fespace, Array<int> &ess_bdr, AssemblyLevel asm_lvl)
{
ParBilinearForm *form = new ParBilinearForm(&fespace);
form->SetAssemblyLevel(asm_lvl);
DiffusionIntegrator *integ = new DiffusionIntegrator(coeff);
int p = fespace.GetOrder(0);
int dim = fespace.GetMesh()->Dimension();
// Integration rule for high-order problem: (p+1+q1d_inc)^d Gauss-Legendre
// points
int int_order = 2*(p+1+q1d_inc) - 1;
Geometry::Type geom = fespace.GetMesh()->GetElementBaseGeometry(0);
const IntegrationRule &ir = irs.Get(geom, int_order);
MFEM_VERIFY(ir.Size() == pow(p+1+q1d_inc,dim), "Wrong quadrature");
integ->SetIntegrationRule(ir);
form->AddDomainIntegrator(integ);
form->Assemble();
bfs.Append(form);
essentialTrueDofs.Append(new Array<int>());
fespace.GetEssentialTrueDofs(ess_bdr, *essentialTrueDofs.Last());
}
void DiffusionMultigrid::ConstructOperatorAndSmoother(
ParFiniteElementSpace& fespace, Array<int>& ess_bdr)
{
ConstructBilinearForm(fespace, ess_bdr, AssemblyLevel::PARTIAL);
OperatorPtr opr;
bfs.Last()->FormSystemMatrix(*essentialTrueDofs.Last(), opr);
opr.SetOperatorOwner(false);
Vector diag(fespace.GetTrueVSize());
bfs.Last()->AssembleDiagonal(diag);
Solver* smoother = new OperatorChebyshevSmoother(
*opr, diag, *essentialTrueDofs.Last(), smoothers_cheby_order,
fespace.GetParMesh()->GetComm());
AddLevel(opr.Ptr(), smoother, true, true);
}
void DiffusionMultigrid::ConstructCoarseOperatorAndSolver(
SolverConfig config, ParFiniteElementSpace& fespace, Array<int>& ess_bdr)
{
ConstructBilinearForm(fespace, ess_bdr, GetCoarseAssemblyLevel(config));
ParBilinearForm &a = static_cast<ParBilinearForm&>(*bfs.Last());
Array<int> &ess_dofs = *essentialTrueDofs.Last();
a.FormSystemMatrix(ess_dofs, A_coarse);
OperatorPtr A_prec;
if (NeedsLOR(config))
{
if (Mpi::Root())
{
std::cout << "Forming LOR discretization..." << std::endl;
}
lor.reset(new ParLORDiscretization(a, ess_dofs));
A_prec = lor->GetAssembledSystem();
if (Mpi::Root())
{
std::cout << "Forming LOR discretization... Done." << std::endl;
}
}
else
{
A_prec = A_coarse;
}
if (Mpi::Root()) { std::cout << "Forming preconditioner... " << std::endl; }
switch (config.type)
{
case SolverConfig::JACOBI:
coarse_precond.reset(new OperatorJacobiSmoother(a, ess_dofs));
break;
case SolverConfig::FA_HYPRE:
case SolverConfig::LOR_HYPRE:
{
HypreBoomerAMG *amg = new HypreBoomerAMG(*A_prec.As<HypreParMatrix>());
amg->SetPrintLevel(1);
Vector b(amg->Height());
Vector x(amg->Height());
b = 0.0;
x = 0.0;
amg->Setup(b, x); // Force setup;
coarse_precond.reset(amg);
break;
}
#ifdef MFEM_USE_AMGX
case SolverConfig::FA_AMGX:
case SolverConfig::LOR_AMGX:
{
AmgXSolver *amg = new AmgXSolver;
amg->ReadParameters(config.amgx_config_file, AmgXSolver::EXTERNAL);
amg->InitExclusiveGPU(MPI_COMM_WORLD);
amg->SetOperator(*A_prec.As<HypreParMatrix>());
coarse_precond.reset(amg);
break;
}
#endif
default:
MFEM_ABORT("Not available.")
}
if (config.inner_sli) // coarse_solver = SLI
{
SLISolver *sli = new SLISolver(fespace.GetComm());
sli->SetPrintLevel(0);
sli->SetAbsTol(0.0);
sli->SetRelTol(0.0);
sli->SetMaxIter(config.inner_sli_iter);
sli->SetOperator(*A_coarse);
sli->SetPreconditioner(*coarse_precond);
coarse_solver.reset(sli);
}
else if (config.inner_cg)
{
CGSolver *cg = new CGSolver(MPI_COMM_WORLD);
cg->SetPrintLevel(2);
cg->SetMaxIter(100);
cg->SetRelTol(1e-8);
cg->SetAbsTol(0.0);
cg->SetOperator(*A_coarse);
cg->SetPreconditioner(*coarse_precond);
cg->iterative_mode = false;
coarse_solver.reset(cg);
}
else
{
coarse_solver = coarse_precond;
}
if (Mpi::Root())
{
std::cout << "Forming preconditioner... Done.\n" << std::endl;
}
if (config.coarse_smooth)
{
Vector diag(fespace.GetTrueVSize());
a.AssembleDiagonal(diag);
Solver *smoother = new OperatorChebyshevSmoother(
*A_coarse, diag, ess_dofs, smoothers_cheby_order,
fespace.GetParMesh()->GetComm());
AddLevel(A_coarse.Ptr(), smoother, false, true);
AddCoarseSolver(coarse_solver.get(), false);
}
else
{
AddLevel(A_coarse.Ptr(), coarse_solver.get(), false, false);
}
}
void DiffusionMultigrid::SetSmoothersChebyshevOrder(int new_cheby_order)
{
for (int level = MultigridBase::coarse_solver ? 0 : 1;
level < NumLevels(); level++)
{
OperatorChebyshevSmoother *cheby =
dynamic_cast<OperatorChebyshevSmoother*>(GetSmootherAtLevel(level));
if (cheby) { cheby->SetOrder(new_cheby_order); }
}
smoothers_cheby_order = new_cheby_order;
}
void DiffusionMultigrid::SetInnerSLINumIter(int inner_sli_iter)
{
SLISolver *sli = dynamic_cast<SLISolver*>(coarse_solver.get());
if (sli) { sli->SetMaxIter(inner_sli_iter); }
}
} // namespace mfem
@@ -0,0 +1,100 @@
// 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 __SOLVER_BP_HPP__
#define __SOLVER_BP_HPP__
#include "mfem.hpp"
#include <memory>
namespace mfem
{
struct SolverConfig
{
enum SolverType
{
JACOBI = 0,
FA_HYPRE = 1,
LOR_HYPRE = 2,
FA_AMGX = 3,
LOR_AMGX = 4
};
SolverType type;
const char *amgx_config_file = "amgx/amgx.json";
bool inner_cg = false; //<-- use inner CG iteration for coarse solver
bool inner_sli = false; //<-- use inner SLI iteration for coarse solver
int inner_sli_iter = 1; //<- number of iterations for the inner SLI solver
bool coarse_smooth = false; //<- enable level 0 smoothing
SolverConfig(SolverType type_) : type(type_) { }
void Print()
{
mfem::out << "Coarse solver: ";
switch (type)
{
case JACOBI: mfem::out << "Jacobi"; break;
case FA_HYPRE: mfem::out << "Hypre (full)"; break;
case LOR_HYPRE: mfem::out << "Hypre (LOR)"; break;
case FA_AMGX: mfem::out << "AmgX (full)"; break;
case LOR_AMGX: mfem::out << "AmgX (LOR)"; break;
}
mfem::out << std::endl;
// If inner_sli is true inner_cg is not used, see
// DiffusionMultigrid::ConstructCoarseOperatorAndSolver():
if (inner_sli) { inner_cg = false; }
mfem::out << "Inner CG: "
<< (inner_cg ? "On" : "Off")
<< std::endl;
mfem::out << "Inner SLI: " << (inner_sli ? "On" : "Off") << '\n';
mfem::out << "Coarse smooth: " << (coarse_smooth ? "On" : "Off") << '\n';
}
};
struct DiffusionMultigrid : GeometricMultigrid
{
Coefficient &coeff;
int q1d_inc;
IntegrationRules irs;
std::unique_ptr<ParLORDiscretization> lor;
OperatorPtr A_coarse;
std::shared_ptr<Solver> coarse_solver, coarse_precond;
int smoothers_cheby_order;
DiffusionMultigrid(
ParFiniteElementSpaceHierarchy& hierarchy,
Coefficient &coeff_,
Array<int>& ess_bdr,
SolverConfig coarse_solver_config,
int q1d_inc_ = 0,
int smoothers_cheby_order_ = 1);
void ConstructBilinearForm(
ParFiniteElementSpace &fespace,
Array<int> &ess_bdr,
AssemblyLevel asm_lvl);
void ConstructOperatorAndSmoother(
ParFiniteElementSpace &fespace,
Array<int> &ess_bdr);
void ConstructCoarseOperatorAndSolver(
SolverConfig config,
ParFiniteElementSpace &fespace,
Array<int> &ess_bdr);
void SetSmoothersChebyshevOrder(int new_cheby_order);
void SetInnerSLINumIter(int inner_sli_iter);
};
} // namespace mfem
#endif
+364
View File
@@ -0,0 +1,364 @@
// 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 __RHS_HPP__
#define __RHS_HPP__
#include "mfem.hpp"
#include "general/forall.hpp"
// 0 - Solution described in the CEED MS 36 report
// 1 - Solution from the "ecp_special_2023" paper (option with cosine):
// w(n,x) = \sum_{k=0}^n a^k \cos(b^k \pi (x - 1/2)), x \in [0,1]
// with a = 1/2, b = 3.
// 2 - Solution from the "ecp_special_2023" paper (option with sine):
// w(n,x) = \sum_{k=0}^n a^k \sin(b^k \pi x), x \in [0,1]
// with a = 1/2, b = 3.
#define CEED_SOLVER_BP_SOLUTION_OPTION 1
namespace mfem
{
constexpr real_t pi = real_t(M_PI);
#if (CEED_SOLVER_BP_SOLUTION_OPTION == 0)
MFEM_HOST_DEVICE inline
real_t s(int k, real_t x)
{
return sin(2*pi*k*x);
}
MFEM_HOST_DEVICE inline
real_t u(int k, real_t x)
{
real_t skx = s(k,x);
real_t sgn = skx < 0 ? -1.0 : 1.0;
return exp(-1/skx/skx)*sgn;
}
MFEM_HOST_DEVICE inline
real_t u_xx(int k, real_t x)
{
real_t kpix = k*pi*x;
real_t csc_2kpix = 1.0/sin(2*kpix);
real_t sgn = sin(2*kpix) < 0 ? -1.0 : 1.0;
return 2*exp(-csc_2kpix*csc_2kpix)*k*k*pi*pi
*(1 + 6*cos(4*kpix) + cos(8*kpix))
*pow(csc_2kpix,6)
*sgn;
}
MFEM_HOST_DEVICE inline
real_t w(int n, real_t x)
{
real_t wkx = 0.0;
real_t xx = 2*x - 1; // transform from [0,1] to [-1,1]
for (int j=0; j<n; ++j)
{
int k = pow(3, j);
wkx += u(k, xx);
}
return wkx;
}
MFEM_HOST_DEVICE inline
real_t w_xx(int n, real_t x)
{
real_t wkx = 0.0;
real_t xx = 2*x - 1; // transform from [0,1] to [-1,1]
if (xx == 0.0) { return 0.0; }
for (int j=0; j<n; ++j)
{
int k = pow(3, j);
wkx += 4*u_xx(k, xx); // factor of four from reference interval transf.
}
return wkx;
}
#elif (CEED_SOLVER_BP_SOLUTION_OPTION == 1)
MFEM_HOST_DEVICE inline
real_t w(int n, real_t x)
{
// w(n,x) = \sum_{k=0}^n a^k \cos(b^k \pi (x - 1/2))
const real_t a = 0.5, b = 3.;
real_t ak = 1.0;
real_t xk = pi * (x - 0.5);
real_t w_ = ak * cos(xk);
for (int k = 1; k <= n; k++)
{
ak *= a;
xk *= b;
w_ += ak * cos(xk);
}
return w_;
}
MFEM_HOST_DEVICE inline
real_t w_x(int n, real_t x)
{
// w'(n,x) = -\pi \sum_{k=0}^n a^k b^k \sin(b^k \pi (x - 1/2))
const real_t a = 0.5, b = 3.;
real_t ck = -pi;
real_t xk = pi * (x - 0.5);
real_t w_x_ = ck * sin(xk);
for (int k = 1; k <= n; k++)
{
ck *= a * b;
xk *= b;
w_x_ += ck * sin(xk);
}
return w_x_;
}
MFEM_HOST_DEVICE inline
real_t w_xx(int n, real_t x)
{
// w''(n,x) = -\pi^2 \sum_{k=0}^n a^k b^{2 k} \cos(b^k \pi (x - 1/2))
const real_t a = 0.5, b = 3.;
real_t ck = -(pi * pi);
real_t xk = pi * (x - 0.5);
real_t w_xx_ = ck * cos(xk);
for (int k = 1; k <= n; k++)
{
ck *= a * b*b;
xk *= b;
w_xx_ += ck * cos(xk);
}
return w_xx_;
}
#elif (CEED_SOLVER_BP_SOLUTION_OPTION == 2)
MFEM_HOST_DEVICE inline
real_t w(int n, real_t x)
{
// w(n,x) = \sum_{k=0}^n a^k \sin(b^k \pi x)
const real_t a = 0.5, b = 3.;
real_t ak = 1.0;
real_t xk = pi * x;
real_t w_ = ak * sin(xk);
for (int k = 1; k <= n; k++)
{
ak *= a;
xk *= b;
w_ += ak * sin(xk);
}
return w_;
}
MFEM_HOST_DEVICE inline
real_t w_xx(int n, real_t x)
{
// w''(n,x) = -\pi^2 \sum_{k=0}^n a^k b^{2 k} \sin(b^k \pi x)
const real_t a = 0.5, b = 3.;
real_t ck = -(pi * pi);
real_t xk = pi * x;
real_t w_xx_ = ck * sin(xk);
for (int k = 1; k <= n; k++)
{
ck *= a * b*b;
xk *= b;
w_xx_ += ck * sin(xk);
}
return w_xx_;
}
#endif // CEED_SOLVER_BP_SOLUTION_OPTION
using BPSFunctionType = real_t(*)(int n, const real_t *xyz);
template <BPSFunctionType F>
void ProjectBPSFunction(int n, QuadratureFunction &qf)
{
MFEM_PERF_FUNCTION;
QuadratureSpaceBase &qs = *qf.GetSpace();
Mesh &mesh = *qs.GetMesh();
const IntegrationRule &ir = qs.GetIntRule(0);
auto *geom = mesh.GetGeometricFactors(ir, GeometricFactors::COORDINATES);
const int dim = qs.GetMesh()->Dimension();
const int nq = ir.Size();
const int N = qf.Size();
const real_t *d_x = geom->X.Read();
real_t *d_q = qf.Write();
mfem::forall(N, [=] MFEM_HOST_DEVICE (int ii)
{
const int i = ii / nq;
const int j = ii % nq;
real_t xvec[3];
for (int d = 0; d < dim; ++d)
{
xvec[d] = d_x[j + d*nq + i*dim*nq];
}
d_q[ii] = F(n, xvec);
});
}
MFEM_HOST_DEVICE inline
real_t sol_1d(const int n, const real_t *xyz)
{
return w(n, xyz[0]);
}
MFEM_HOST_DEVICE inline
real_t sol_2d(const int n, const real_t *xyz)
{
return w(n, xyz[0])*w(n, xyz[1]);
}
MFEM_HOST_DEVICE inline
real_t sol_3d(const int n, const real_t *xyz)
{
return w(n, xyz[0])*w(n, xyz[1])*w(n, xyz[2]);
}
struct ExactSolution : Coefficient
{
int dim, n;
ExactSolution(int dim_, int n_=0) : dim(dim_), n(n_) { }
using Coefficient::Eval;
real_t Eval(ElementTransformation &T, const IntegrationPoint &ip) override
{
real_t xyz[3];
Vector transip(xyz, 3);
T.Transform(ip, transip);
if (dim == 1)
{
return w(n, xyz[0]);
}
if (dim == 2)
{
return w(n, xyz[0])*w(n, xyz[1]);
}
else // dim == 3
{
return w(n, xyz[0])*w(n, xyz[1])*w(n, xyz[2]);
}
}
void Project(QuadratureFunction &qf) override
{
switch (dim)
{
case 1: ProjectBPSFunction<sol_1d>(n, qf); break;
case 2: ProjectBPSFunction<sol_2d>(n, qf); break;
case 3: ProjectBPSFunction<sol_3d>(n, qf); break;
default: MFEM_ABORT("Unsupported dimension.");
}
}
};
struct ExactGrad : VectorCoefficient
{
int dim, n;
ExactGrad(int dim_, int n_)
: VectorCoefficient(dim_), dim(dim_), n(n_) { }
using VectorCoefficient::Eval;
void Eval(Vector &V, ElementTransformation &T,
const IntegrationPoint &ip) override
{
real_t xyz[3];
Vector transip(xyz, 3);
T.Transform(ip, transip);
V.SetSize(dim);
if (dim == 1)
{
V(0) = w_x(n, xyz[0]);
}
if (dim == 2)
{
V(0) = w_x(n, xyz[0])* w(n, xyz[1]);
V(1) = w(n, xyz[0])*w_x(n, xyz[1]);
}
else // dim == 3
{
const real_t wnx = w(n, xyz[0]);
const real_t wny = w(n, xyz[1]);
const real_t wnz = w(n, xyz[2]);
V(0) = w_x(n, xyz[0])*wny *wnz;
V(1) = wnx *w_x(n, xyz[1])*wnz;
V(2) = wnx *wny *w_x(n, xyz[2]);
}
}
};
MFEM_HOST_DEVICE inline
real_t rhs_1d(const int n, const real_t *xyz)
{
return -w_xx(n, xyz[0]);
}
MFEM_HOST_DEVICE inline
real_t rhs_2d(const int n, const real_t *xyz)
{
return -w_xx(n, xyz[0])*w(n, xyz[1]) - w(n, xyz[0])*w_xx(n, xyz[1]);
}
MFEM_HOST_DEVICE inline
real_t rhs_3d(const int n, const real_t *xyz)
{
return -w_xx(n, xyz[0])*w(n, xyz[1])*w(n, xyz[2])
- w(n, xyz[0])*w_xx(n, xyz[1])*w(n, xyz[2])
- w(n, xyz[0])*w(n, xyz[1])*w_xx(n, xyz[2]);
}
void ProjectRHS(int n, QuadratureFunction &qf)
{
const int dim = qf.GetSpace()->GetMesh()->Dimension();
switch (dim)
{
case 1: ProjectBPSFunction<rhs_1d>(n, qf); break;
case 2: ProjectBPSFunction<rhs_2d>(n, qf); break;
case 3: ProjectBPSFunction<rhs_3d>(n, qf); break;
default: MFEM_ABORT("Unsupported dimension.");
}
}
struct RHS : Coefficient
{
int dim, n;
RHS(int dim_, int n_=0) : dim(dim_), n(n_) { }
using Coefficient::Eval;
real_t Eval(ElementTransformation &T, const IntegrationPoint &ip) override
{
real_t xyz[3];
Vector transip(xyz, 3);
T.Transform(ip, transip);
if (dim == 1)
{
return -w_xx(n, xyz[0]);
}
if (dim == 2)
{
return -w_xx(n, xyz[0])*w(n, xyz[1]) - w(n, xyz[0])*w_xx(n, xyz[1]);
}
else // dim == 3
{
return -w_xx(n, xyz[0])*w(n, xyz[1])*w(n, xyz[2])
- w(n, xyz[0])*w_xx(n, xyz[1])*w(n, xyz[2])
- w(n, xyz[0])*w(n, xyz[1])*w_xx(n, xyz[2]);
}
}
void Project(QuadratureFunction &qf) override
{
ProjectRHS(n,qf);
}
};
}
#endif
+125
View File
@@ -0,0 +1,125 @@
bsep="============================================================"
ssep="----------------------------------------"
# Enable GPU-aware MPI:
# gpu_aware_mpi_env_cmd="env MPICH_GPU_SUPPORT_ENABLED=1"
# gpu_aware_mpi="-g"
# number of MPI ranks, number of ranks per node, number of nodes:
np=1
nrnode=4
((nnodes = (np+nrnode-1)/nrnode))
# dev="-d gpu ${gpu_aware_mpi}"
eps="0.3"
# mpirun_np="mpirun -np"
mpirun_np="env MFEM_REPORT_KERNELS=1 mpirun -np"
# mpirun_np="${gpu_aware_mpi_env_cmd} flux run -x -N ${nnodes} -n"
# dry run:
# mpirun_np="echo ${mpirun_np}"
# p-MG/LOR + FA-hypre, or diagonal (Jacobi smoother)
# prec_type: "p-mg", "lor", or "diag"
prec_type="lor"
p_mg_opts="-cb 1"
# p_mg_opts="-cb 5 -sli -sli-it 6"
# lor_opts="-cls -cb 5 -sli -sli-it 6"
# lor_opts="-cls -cb 2 -sli -sli-it 2"
lor_opts="-cb 2 -sli -sli-it 2"
mg_set=("1" "1 2" "1 3" "1 2 4" "1 3 5" "1 3 6")
# mg_set=("1 2")
# p=7 and p=8 fail at the moment: "1 3 5 7" "1 3 5 8"
# per-rank limits on the number of LOR elements for different p, in 2^20 units:
# (bigger sizes run out of GPU memory, at least with LOR prec.)
lor_ne_max_all=(4 4 4 4 4 4 4 4)
# lor_ne_max_all=(18 22 24 24 27 24 8 8) # MI250X
((lor_ne_min = 40*2**10))
((np_ = np))
((mm = 1))
while ((np_ > 8)); do
((mm++))
((np_ = (np_-1)/8+1))
done
((mf = 2**mm))
((mff = 3*mf))
echo " *** np = ${np}, mf = ${mf}, mff = ${mff}"
for mg in "${mg_set[@]}"; do
echo "${bsep}"
p=(${mg})
# p=${p[-1]}
p="${p[$((${#p[@]}-1))]}"
lor_ne_max="${lor_ne_max_all[$((p-1))]}"
((lor_ne_max *= 2**20))
# n_max = floor(lor_ne_max^(1/3))
n_max=$(echo "a=e((1/3)*l(${np}*${lor_ne_max}));scale=0;a/1" | bc -l)
# for np*lor_ne_max=256^3, the above gives 255, so we adjust the result:
while (( (n_max+1)**3 <= np*lor_ne_max )); do
((n_max++))
done
echo " *** p = ${p}, n_max = ${n_max}"
if (( n_max**3 > np*lor_ne_max )); then
echo "error: n_max^3 > np*lor_ne_max"
exit 1
fi
echo "${bsep}"
nx_set=()
for ((nx = (n_max/p/mff)*mff, last_nx = 2*nx; nx >= 6; nx -= mff)); do
((last_ne = last_nx**3))
((ne = nx**3))
((lor_ne = (p*nx)**3))
if ((np*lor_ne_min > lor_ne)); then break; fi
if ((last_ne < ne*4/3)); then continue; fi
nx_set=("${nx}" "${nx_set[@]}")
((ndofs = (p*nx+1)**3))
((rhs_n=0))
while ((2*3**(rhs_n+1) <= p*nx)); do
((rhs_n++))
done
# 2*3**rhs_n <= p*nx < 2*3**(rhs_n+1)
printf "np = ${np}, p = ${p}, nx = ${nx}, ndofs = ${ndofs}"
# rhs_n for eps = 1:
# printf ", rhs_n = ${rhs_n}"
printf "\n"
((last_nx = nx))
done
for nx in "${nx_set[@]}"; do
# break;
if ((nx % mf != 0)); then
echo " *** internal error!"
exit 1
fi
((rp = mm))
((nx /= mf))
if false; then
# 0, 1, or 2 additional parallel refinements for 1, 8, or 64 ranks
((np_=np))
while ((np_%8 == 0)); do
((np_=np_/8))
((rp++))
done
fi
((ndofs = (p*nx*2**rp+1)**3))
echo "${bsep}"
echo "np = ${np}, p = ${p}, ndofs = ${ndofs}"
if [[ "$prec_type" == "p-mg" ]]; then
# p-MG
printf "$mpirun_np ${np} ./solver-bp ${dev} -nrn ${nrnode}"
printf " -ey ${eps} -mg \"${mg}\" -cs 1 ${p_mg_opts}"
printf " -nx ${nx} -rp ${rp}\n"
echo "${ssep}"
$mpirun_np "${np}" ./solver-bp ${dev} -nrn ${nrnode} \
-ey ${eps} -mg "${mg}" -cs 1 ${p_mg_opts} -nx "${nx}" -rp "${rp}"
elif [[ "$prec_type" == "lor" ]]; then
# LOR
printf "$mpirun_np ${np} ./solver-bp ${dev} -nrn ${nrnode}"
printf " -ey ${eps} -mg \"${p}\" -cs 2 ${lor_opts}"
printf " -nx ${nx} -rp ${rp}\n"
echo "${ssep}"
$mpirun_np "${np}" ./solver-bp ${dev} -nrn ${nrnode} \
-ey ${eps} -mg "${p}" -cs 2 ${lor_opts} -nx "${nx}" -rp "${rp}"
elif [[ "$prec_type" == "diag" ]]; then
# Diag
printf "$mpirun_np ${np} ./solver-bp ${dev} -nrn ${nrnode}"
printf " -ey ${eps} -mg \"${p}\" -cs 0 -nx ${nx} -rp ${rp}\n"
echo "${ssep}"
$mpirun_np "${np}" ./solver-bp ${dev} -nrn ${nrnode} \
-ey ${eps} -mg "${p}" -cs 0 -nx "${nx}" -rp "${rp}"
fi
done
done
@@ -0,0 +1,845 @@
// 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 Implementation of the CEED Solver Bake-off Problems
// --------------------------------------------------------------
//
// Run a suite of benchmarks and view the results:
//
// 1. Edit 'run.sh' to adjust machine and size parameters.
// 2. Run 'run.sh' redirecting output to a file, e.g.:
// bash run.sh > run-001.out
// 3. Extract the CSV output:
// sed -n -e 's/^= CSV:\(.*\)$/\1/p' run-001.out > run-001.csv
// 3. Edit the script 'plot_csv.py' set the name of your CSV file and,
// optionally, customize the plot it generates.
// 4. Process the CSV file:
// python3 plot_csv.py
//
// Sample runs:
//
// solver-bp -nx 6
// solver-bp -nx 6 -mg "1 2 3"
// solver-bp -nx 6 -mg "1 r r 2 3"
// solver-bp -nx 6 -rp 2 -mg 3 -cs 1
// solver-bp -nx 6 -rp 2 -mg 3 -cs 2
//
// Device sample runs:
//
// solver-bp -d cuda -nx 6 -mg "1 r r 2 3" -cs 0
// solver-bp -d cuda -nx 6 -rp 2 -mg 3 -cs 3
// solver-bp -d cuda -nx 6 -rp 2 -mg 3 -cs 4
//
#include "mfem.hpp"
#include "kershaw.hpp"
#include "rhs.hpp"
#include "preconditioners.hpp"
#include <regex>
#include <fem/integ/bilininteg_diffusion_kernels.hpp>
using namespace std;
using namespace mfem;
struct MGRefinement
{
enum Type { P_MG, H_MG };
const Type type;
const int order;
MGRefinement(Type type_, int order_) : type(type_), order(order_) { }
static MGRefinement p(int order_) { return MGRefinement(P_MG, order_); }
static MGRefinement h() { return MGRefinement(H_MG, 0); }
};
struct CGMonitor : IterativeSolverMonitor
{
const real_t tol;
real_t initial_nrm, final_nrm, saved_nrm;
int final_it, saved_it;
CGMonitor(real_t tol_) : tol(tol_) { }
void MonitorResidual(int it, real_t norm, const Vector &r, bool final)
override
{
MFEM_PERF_FUNCTION;
MFEM_CONTRACT_VAR(norm);
// Avoid recomputing the norm if it was already computed -- this method
// is called two times for the final iteration: once with final = false
// (possibly triggering the monitor convergence criterion) and a second
// time with final = true.
bool init_call = (it == 0 && !final);
const real_t nrm =
(!init_call && it == saved_it) ?
saved_nrm :
sqrt(InnerProduct(iter_solver->GetComm(), r, r));
if ((it == 0 || final) && Mpi::Root())
{
mfem::out << (final ? "Final" : " Initial")
<< " l2 norm of residual: " << nrm << '\n';
}
if (init_call)
{
initial_nrm = nrm;
converged = false;
final_nrm = -1.0;
final_it = -1;
}
saved_nrm = nrm;
saved_it = it;
// Check for monitor-triggered convergence
converged = (nrm <= tol*initial_nrm);
if (final)
{
final_nrm = nrm;
final_it = it;
}
if (final && Mpi::Root())
{
mfem::out << "Final relative l2 residual: ";
if (initial_nrm == 0.0)
{
mfem::out << "N/A (initial norm is 0)" << endl;
}
else
{
const real_t rel_nrm = nrm/initial_nrm;
mfem::out << rel_nrm << '\n';
mfem::out << "Average l2 reduction factor: ";
if (it == 0) { mfem::out << "N/A"; }
else { mfem::out << pow(rel_nrm, 1.0/it); }
mfem::out << " [" << it << " iterations]" << endl;
}
}
}
};
void report_hypre_gpu_status(bool gpu_aware_mpi_requested);
void report_env_vars();
real_t verify_ess_bdr(const Vector &b, const Vector &x,
const Array<int> &ess_tdof_list);
template <typename T> void PrintPair(const string &name, T val)
{
cout << setw(14) << left << name << val << '\n';
}
int main(int argc, char *argv[])
{
DiffusionIntegrator::AddSpecialization<3,3,3>();
DiffusionIntegrator::AddSpecialization<3,4,4>();
DiffusionIntegrator::AddSpecialization<3,5,5>();
DiffusionIntegrator::AddSpecialization<3,6,6>();
DiffusionIntegrator::AddSpecialization<3,7,7>();
Mpi::Init(argc, argv);
Hypre::Init();
const char *device_config = "cpu";
int nrnode = 4; // number of ranks per node, used for CSV output only
bool gpu_aware_mpi = false;
int nx = 6, ny = -1, nz = -1;
int rhs_n = -1;
const char *mg_spec = "1";
int q1d_inc = 0; // num 1D qpts = p + 1 + q1d_inc
int smoothers_cheby_order = 1;
real_t epsy = 1.0, epsz = -1;
int ref_par = 0;
bool glvis = false;
bool paraview = false;
SolverConfig coarse_solver(SolverConfig::JACOBI);
OptionsParser args(argc, argv);
args.AddOption(&device_config, "-d", "--device",
"Device configuration string, see Device::Configure().");
args.AddOption(&nrnode, "-nrn", "--num-ranks-per-node",
"Number of ranks per compute node. Used to compute the number"
" of nodes written in CSV output.");
args.AddOption(&gpu_aware_mpi, "-g", "--gpu-aware-mpi", "-no-g",
"--no-gpu-aware-mpi", "Enable GPU-aware MPI.");
args.AddOption(&mg_spec, "-mg", "--multigrid-spec",
"Multigrid specification. See README for description.");
args.AddOption(&q1d_inc, "-qi", "--quadrature-points-increment",
"Increment for the 1D quadrature points relative to p + 1");
args.AddOption(&smoothers_cheby_order, "-cb",
"--smoothers-chebyshev-order",
"Order of the Chebyshev smoothers for the multigrid.");
args.AddOption((int*)&coarse_solver.type, "-cs", "--coarse-solver-config",
"Coarse solver configuration. 0: Jacobi, 1: FA-HYPRE, "
"2: LOR-HYPRE, 3: FA-AMGX, 4: LOR-AMGX.");
args.AddOption(&coarse_solver.inner_cg, "-cg", "--inner-cg",
"-no-cg", "--no-inner-cg",
"Use inner CG iteration for the coarse solver.");
args.AddOption(&coarse_solver.inner_sli, "-sli", "--inner-sli",
"-no-sli", "--no-inner-sli",
"Use inner SLI iteration for the coarse solver.");
args.AddOption(&coarse_solver.inner_sli_iter, "-sli-it",
"--inner-sli-iterations",
"Number of iterations for the inner SLI solver.");
args.AddOption(&coarse_solver.coarse_smooth, "-cls", "--coarse-level-smooth",
"-no-cls", "--no-coarse-level-smooth",
"Use coarse smoothing in addition to the coarse solver.");
args.AddOption(&coarse_solver.amgx_config_file, "-amgx", "--amgx-config",
"AmgX config JSON file.");
args.AddOption(&nx, "-nx", "--nx", "Number of elements in x direction.");
args.AddOption(&ny, "-ny", "--ny", "Number of elements in y direction.");
args.AddOption(&nz, "-nz", "--nz", "Number of elements in z direction.");
args.AddOption(&epsy, "-ey", "--epsy", "Kershaw parameter epsilon y.");
args.AddOption(&epsz, "-ez", "--epsz", "Kershaw parameter epsilon z.");
args.AddOption(&rhs_n, "-rn", "--rhs-n",
"Parameter n in the RHS function; -1 for default.");
args.AddOption(&ref_par, "-rp", "--ref-par",
"Number of uniform parallel refinements to perform.");
args.AddOption(&glvis, "-gv", "--glvis", "-no-gv", "--no-glvis",
"Save the mesh and solution for GLVis visualization.");
args.AddOption(&paraview, "-pv", "--paraview", "-no-pv", "--no-paraview",
"Save data files for ParaView visualization.");
args.ParseCheck();
if (ny < 0) { ny = nx; }
if (nz < 0) { nz = nx; }
if (epsz < 0) { epsz = epsy; }
// rhs_n default is handled later
Device device(device_config);
device.SetGPUAwareMPI(gpu_aware_mpi);
if (Mpi::Root()) { device.Print(); }
// Report HYPRE's GPU config and GPU-aware MPI config. Terminates if
// GPU-aware MPI is requested but HYPRE's GPU-aware MPI support is disabled.
report_hypre_gpu_status(gpu_aware_mpi);
// Report environment variables like {CUDA,ROCR}_VISIBLE_DEVICES:
report_env_vars();
// Generate mesh
MFEM_PERF_BEGIN("CreateKershawMesh");
ParMesh mesh_coarse = CreateKershawMesh(nx, ny, nz, epsy, epsz);
MFEM_PERF_END("CreateKershawMesh");
const int dim = mesh_coarse.Dimension();
for (int i=0; i<ref_par; ++i)
{
MFEM_PERF_SCOPE("Mesh UniformRefinement");
mesh_coarse.UniformRefinement();
}
int coarse_order = 0, order = 0, h_ref = ref_par;
// Parse order specification
vector<MGRefinement> mg_refinements;
{
istringstream mg_stream(mg_spec);
string ref;
mg_stream >> coarse_order;
int prev_order = order = coarse_order;
if (Mpi::Root()) { cout << "\nCoarse order " << coarse_order << '\n'; }
while (mg_stream >> ref)
{
if (ref == "r")
{
if (Mpi::Root()) { cout << "h-MG uniform refinement\n"; }
mg_refinements.push_back(MGRefinement::h());
++h_ref;
}
else
{
try { order = stoi(ref); }
catch (...)
{
MFEM_ABORT("Multigrid refinement must either be an integer or "
"the character `r`");
}
if (Mpi::Root()) { cout << "p-MG order " << order << '\n'; }
MFEM_VERIFY(order > 0, "Orders must be positive");
MFEM_VERIFY(order > prev_order, "Orders must be increasing");
mg_refinements.push_back(MGRefinement::p(order));
prev_order = order;
}
}
}
if (order == 1 && coarse_solver.type == SolverConfig::LOR_HYPRE)
{
// Using ~10^7 elements with p=1 overflows a Vector in the LOR setup.
// The Vector has size (3D): (p+1)^3 * 27 * num_elem_ho.
// In 3D, for p > 1, the overflow will happen around:
// - p=2: ~23.6 million dofs or 2,945,794 elements
// - p=3: ~33.6 million dofs or 1,242,757 elements
// - p=4: ~40.7 million dofs or 636,292 elements
// - p=5: ~46.0 million dofs or 368,225 elements
// - p=6: ~50.1 million dofs or 231,885 elements
//
// Note: the size of the Jacobians at quadrature points (with q1d=p+1) in
// 3D is: (p+1)^3 * 9 * num_elem, so 3x smaller than the above Vector.
//
// For q1d=p+2, the overflow happens around:
// - p=1: 8,837,382 elements or ~8.8 million dofs
// - p=2: 3,728,271 elements or ~29.8 million dofs
// - p=3: 1,908,875 elements or ~51.5 million dofs
// - p=4: 1,104,673 elements or ~70.7 million dofs
// - p=5: 695,654 elements or ~87.0 million dofs
// - p=6: 466,034 elements or ~100.7 million dofs
coarse_solver.type = SolverConfig::FA_HYPRE;
if (Mpi::Root())
{
cout << "\nOrder is 1: switching from LOR-HYPRE to FA-HYPRE.\n";
}
}
#if 0
if (order == 1 && coarse_solver.type == SolverConfig::FA_HYPRE &&
coarse_solver.inner_sli)
{
coarse_solver.inner_sli = false;
if (Mpi::Root())
{
cout << "\nOrder is 1: turning off the inner SLI.\n";
}
}
#endif
MFEM_PERF_BEGIN("Setup [hierarchy]");
vector<unique_ptr<FiniteElementCollection>> fe_collections;
fe_collections.emplace_back(new H1_FECollection(coarse_order, dim));
ParFiniteElementSpace fes_coarse(&mesh_coarse, fe_collections.back().get());
ParFiniteElementSpaceHierarchy hierarchy(&mesh_coarse, &fes_coarse,
false, false);
for (MGRefinement ref : mg_refinements)
{
if (ref.type == MGRefinement::H_MG)
{
hierarchy.AddUniformlyRefinedLevel();
}
else // P_MG
{
fe_collections.emplace_back(new H1_FECollection(ref.order, dim));
hierarchy.AddOrderRefinedLevel(fe_collections.back().get());
}
}
MFEM_PERF_END("Setup [hierarchy]");
const int nlevels = hierarchy.GetNumLevels();
if (Mpi::Root())
{
if (nlevels == 1)
{
cout << "1 level in MG hierarchy. Using coarse solver only." << endl;
}
else
{
cout << nlevels << " levels in MG hierarchy." << endl;
}
coarse_solver.Print();
cout << endl;
}
// Determine final nx, ny, nz and use them to determine the default rhs_n.
const int ref_factor = pow(2, h_ref);
nx *= ref_factor;
ny *= ref_factor;
nz *= ref_factor;
if (rhs_n < 0)
{
int n_min = min(nx, ny);
if (nz > 0) { n_min = min(n_min, nz); }
// Find rhs_n such that 2*3^rhs_n <= (order*n_min) < 2*3^{rhs_n+1}
rhs_n = 0;
for (int l = 2*3; l <= order*n_min; l *= 3) { rhs_n++; }
if (epsy < 0.8) { rhs_n--; }
if (Mpi::Root()) { cout << "Using rhs_n = " << rhs_n << '\n' << endl; }
}
ParFiniteElementSpace &fes = hierarchy.GetFinestFESpace();
ParMesh &mesh = *fes.GetParMesh();
MFEM_PERF_BEGIN("ParMesh PrintInfo");
mesh.PrintInfo(cout);
MFEM_PERF_END("ParMesh PrintInfo");
HYPRE_Int ndof = fes.GlobalTrueVSize();
if (Mpi::Root())
{
cout << "\nTotal number of DOFs: " << ndof << endl << endl;
}
// All Dirichlet boundaries
Array<int> ess_bdr;
if (mesh.bdr_attributes.Size())
{
ess_bdr.SetSize(mesh.bdr_attributes.Max());
ess_bdr = 1;
}
ConstantCoefficient one(1.0);
ConstantCoefficient coeff(1.0); // Diffusion coefficient
// Set up RHS
if (Mpi::Root()) { cout << "Assembling right-hand side..." << endl; }
MFEM_PERF_BEGIN("Setup [RHS]");
RHS rhs_coeff(dim, rhs_n);
ParLinearForm b(&fes);
const int rhs_ir_inc = 2*q1d_inc+1;
// --> ir_order = 2*(p+1+q1d_inc)-1 --> q1d = p+1+q1d_inc
b.AddDomainIntegrator(new DomainLFIntegrator(rhs_coeff, 2, rhs_ir_inc));
b.UseFastAssembly(true);
b.Assemble();
MFEM_PERF_END("Setup [RHS]");
if (Mpi::Root()) { cout << "Assembling right-hand side... Done." << endl; }
// Free device memory: the geometric facros computed so far are:
// * the coordinates, for the rhs coefficient evaluation, and
// * the detJ, for the DomainLFIntegrator.
// These are no-longer needed (?), so we can free the memory.
mesh.DeleteGeometricFactors();
// make sure the GPU is done with any previous tasks:
if (Device::Allows(Backend::DEVICE_MASK)) { MFEM_STREAM_SYNC; }
// make sure all ranks are done with any previous tasks:
MPI_Barrier(MPI_COMM_WORLD);
MFEM_PERF_BEGIN("Setup [DiffusionMultigrid]");
tic();
// Set up operators in the multigrid hierarchy
DiffusionMultigrid MG(hierarchy, coeff, ess_bdr, coarse_solver, q1d_inc,
smoothers_cheby_order);
MG.SetCycleType(Multigrid::CycleType::VCYCLE, 1, 1);
// make sure the GPU is done with all setup tasks:
if (Device::Allows(Backend::DEVICE_MASK)) { MFEM_STREAM_SYNC; }
// make sure all ranks are done with all setup tasks:
MPI_Barrier(MPI_COMM_WORLD);
const double t_setup = tic_toc.RealTime();
MFEM_PERF_END("Setup [DiffusionMultigrid]");
ParGridFunction x(&fes);
x = 0.0;
OperatorPtr A;
Vector X, B;
MFEM_PERF_BEGIN("Setup [MG.FormFineLinearSystem]");
MG.FormFineLinearSystem(x, b, A, X, B);
MFEM_PERF_END("Setup [MG.FormFineLinearSystem]");
const real_t l2_tol = 1e-8;
CGMonitor monitor(l2_tol);
CGSolver cg(MPI_COMM_WORLD);
cg.SetRelTol(0.0); // use the 'monitor' for convergence
cg.SetPrintLevel(3);
cg.SetOperator(*A);
cg.SetPreconditioner(MG);
cg.SetMonitor(monitor);
// Run 2 CG iterations to ensure everything is allocated and initialized for
// the full CG solve:
if (Mpi::Root()) { cout << "Running 2 warm-up CG iterations ...\n"; }
MFEM_PERF_BEGIN("Warm-up");
cg.SetMaxIter(2);
{
Vector X_save(X);
cg.Mult(B, X);
X = X_save;
}
MFEM_PERF_END("Warm-up");
if (coarse_solver.inner_sli &&
((coarse_solver.type == SolverConfig::FA_HYPRE /* && order > 1 */) ||
coarse_solver.type == SolverConfig::LOR_HYPRE))
{
MFEM_PERF_SCOPE("Auto-tuning");
// timing data: (t-solve,sli-iter,cheby-order,pcg-iter)
std::vector<std::tuple<double,int,int,int>> timings;
Vector X_save(X);
if (Mpi::Root()) { cout << "\nFinding optimal MG parameters ...\n"; }
cg.SetMaxIter(500);
for (int sli_it = 1; sli_it <= coarse_solver.inner_sli_iter; sli_it++)
{
MG.SetInnerSLINumIter(sli_it);
for (int cheby_order = 1; cheby_order <= smoothers_cheby_order;
cheby_order++)
{
MFEM_PERF_SCOPE(("Timing [" + to_string(sli_it) + "," +
to_string(cheby_order) + "]").c_str());
MG.SetSmoothersChebyshevOrder(cheby_order);
if (Mpi::Root())
{
cout << "\nRunning and timing parameters (sli iter, cheby order)"
<< " = (" << sli_it << ',' << cheby_order << ") ...\n";
}
// make sure the GPU is done with any previous tasks:
if (Device::Allows(Backend::DEVICE_MASK)) { MFEM_STREAM_SYNC; }
// make sure all ranks are done with any previous tasks:
MPI_Barrier(MPI_COMM_WORLD);
tic();
cg.Mult(B, X);
// make sure the GPU is done with all solve tasks:
if (Device::Allows(Backend::DEVICE_MASK)) { MFEM_STREAM_SYNC; }
// make sure all ranks are done with all solve tasks:
MPI_Barrier(MPI_COMM_WORLD);
const double t_solve = tic_toc.RealTime();
if (cg.GetConverged())
{
timings.emplace_back(t_solve, sli_it, cheby_order,
cg.GetNumIterations());
}
X = X_save;
}
}
std::sort(timings.begin(), timings.end());
if (Mpi::Root())
{
cout << "\nSorted timings from rank 0:\n";
const auto old_prec = cout.precision(6);
const auto old_fmtflags = cout.flags();
cout << std::fixed;
for (size_t i = 0; i < timings.size(); i++)
{
cout << setw(2) << i << ": "
<< 1e3*std::get<0>(timings[i]) << " ms: ("
<< std::get<1>(timings[i]) << ','
<< std::get<2>(timings[i]) << "): "
<< setw(3) << std::get<3>(timings[i]) << " iter\n";
}
cout.flags(old_fmtflags);
cout.precision(old_prec);
}
if (timings.size() > 0)
{
// Use the fastest parameters (as timed on rank 0) for the full solve:
int si = std::get<1>(timings[0]);
int co = std::get<2>(timings[0]);
MPI_Bcast(&si, 1, MPI_INT, 0, MPI_COMM_WORLD);
MPI_Bcast(&co, 1, MPI_INT, 0, MPI_COMM_WORLD);
MG.SetInnerSLINumIter(si);
MG.SetSmoothersChebyshevOrder(co);
coarse_solver.inner_sli_iter = si;
smoothers_cheby_order = co;
if (Mpi::Root())
{
cout << "\nUsing the fastest option (sli iter, cheby order) = ("
<< si << ',' << co << ")\n";
}
}
else
{
MG.SetInnerSLINumIter(1);
MG.SetSmoothersChebyshevOrder(1);
coarse_solver.inner_sli_iter = 1;
smoothers_cheby_order = 1;
if (Mpi::Root())
{
cout << "\nAll options failed to converge!"
<< " Using (sli iter, cheby order) = (1,1)\n";
}
}
}
if (Mpi::Root()) { cout << "\nRunning and timing the full CG solve ...\n"; }
cg.SetMaxIter(500);
// make sure the GPU is done with any previous tasks:
if (Device::Allows(Backend::DEVICE_MASK)) { MFEM_STREAM_SYNC; }
// make sure all ranks are done with any previous tasks:
MPI_Barrier(MPI_COMM_WORLD);
MFEM_PERF_BEGIN("Final CG Solve");
tic();
cg.Mult(B, X);
// make sure the GPU is done with all solve tasks:
if (Device::Allows(Backend::DEVICE_MASK)) { MFEM_STREAM_SYNC; }
// make sure all ranks are done with all solve tasks:
MPI_Barrier(MPI_COMM_WORLD);
const double t_solve = tic_toc.RealTime();
MFEM_PERF_END("Final CG Solve");
const int niter = cg.GetConverged() ? cg.GetNumIterations() : -1;
const real_t bdr_err = verify_ess_bdr(B, X, MG.GetFineEssentialTrueDofs());
if (Mpi::Root())
{
MFEM_VERIFY(bdr_err == 0.0, "Incorrect boundary values in solution!"
" bdr_err = " << bdr_err);
}
MG.RecoverFineFEMSolution(X, b, x);
MFEM_PERF_BEGIN("Compute L2 Error");
ExactSolution exact_coeff(dim, rhs_n);
// ExactGrad exact_grad_coeff(dim, rhs_n);
real_t L2_err = x.ComputeL2Error(exact_coeff);
// real_t grad_err = x.ComputeGradError(&exact_grad_coeff);
MFEM_PERF_END("Compute L2 Error");
if (Mpi::Root())
{
cout << "\nL2 Error: " << setprecision(10) << scientific
<< L2_err << '\n';
// cout << "\nGrad Error: " << setprecision(10) << scientific
// << grad_err << '\n';
}
if (glvis)
{
ofstream mesh_ofs(MakeParFilename("mesh.", Mpi::WorldRank()));
mesh_ofs.precision(8);
mesh.Print(mesh_ofs);
ofstream sol_ofs(MakeParFilename("sol.", Mpi::WorldRank()));
sol_ofs.precision(8);
x.Save(sol_ofs);
}
if (paraview)
{
ParGridFunction rhs_gf(&fes), exact_gf(&fes), error_gf(&fes);
rhs_gf.ProjectCoefficient(rhs_coeff);
exact_gf.ProjectCoefficient(exact_coeff);
subtract(exact_gf, x, error_gf);
ParaViewDataCollection dc("SolverBP", &mesh);
dc.RegisterField("u", &x);
dc.RegisterField("rhs", &rhs_gf);
dc.RegisterField("exact", &exact_gf);
dc.RegisterField("error", &error_gf);
dc.SetPrefixPath("ParaView");
dc.SetLevelsOfDetail(order);
dc.SetHighOrderOutput(true);
dc.SetCycle(0);
dc.SetTime(0.0);
dc.Save();
}
const long long nel = mesh.GetGlobalNE();
if (nz == 0) { MFEM_VERIFY(nel == nx*ny, "Wrong number of elements"); }
else { MFEM_VERIFY(nel == nx*ny*nz, "Wrong number of elements"); }
if (Mpi::Root())
{
cout << "\n= Results\n";
PrintPair("nranks", Mpi::WorldSize());
PrintPair("nx", nx);
PrintPair("ny", ny);
PrintPair("nz", nz);
PrintPair("degree", order);
PrintPair("rhs_n", rhs_n);
PrintPair("epsy", epsy);
PrintPair("epsz", epsz);
PrintPair("ndof", ndof);
PrintPair("niter", niter);
// Should also output:
// code id
// prec id
// machine id
// number of supercomputer nodes
// number of 1d quadrature points
// initial and final residuals
// error
// Timings
PrintPair("t_setup", t_setup);
PrintPair("t_solve", t_solve);
cout << "\nSolve MDOFs/rank/sec: "
<< ndof/1e6/Mpi::WorldSize()/t_solve << '\n';
// CSV fields:
// 1. code ID
// 2. preconditioner ID
// 3. machine ID
// 4. number of nodes
// 5. number of MPI ranks
// 6,7,8. n_x, n_y, n_z
// 9. solution polynomial degree
// 10. number of 1D quadrature points
// 11,12. eps_y, eps_z
// 13. ndofs (including Dirichlet boundary)
// 14. niter
// 15,16. initial and final residuals
// 17. error
// 18. t_setup (preconditioner setup)
// 19. t_solve (total iter time)
//
// extract the CSV lines from the output with:
// grep "= CSV:" out.txt | sed -e 's/^= CSV://' > out.csv
cout << "\n= CSV:"
<< "MFEM-" + string(device_config); // 1
string hypre_str =
#if defined(HYPRE_USING_HIP)
"hypre-hip"
#elif defined(HYPRE_USING_CUDA)
"hypre-cuda"
#else
"hypre-cpu"
#endif
;
auto cs = coarse_solver.type;
string prec_id;
if (cs == SolverConfig::FA_HYPRE) // p-MG, add (sli-iter,cheby-order)
{
prec_id = hypre_str + "-pMG(";
}
else if (cs == SolverConfig::LOR_HYPRE) // LOR, add (sli-iter,cheby-order)
{
prec_id = hypre_str + "-LOR(";
}
else if (cs == SolverConfig::JACOBI)
{
prec_id = "diag(";
}
else
{
prec_id = "(unknown)(";
}
if (coarse_solver.inner_cg)
{
prec_id += "cg;";
}
if (coarse_solver.inner_sli)
{
prec_id += to_string(coarse_solver.inner_sli_iter) + ";";
}
prec_id += to_string(smoothers_cheby_order) +
(coarse_solver.coarse_smooth ? "c" : "") + ")";
prec_id += "-" + regex_replace(mg_spec, regex(" "), "-");
cout << ',' << prec_id; // 2
const char *hostname = getenv("HOSTNAME");
if (!hostname) { hostname = getenv("HOST"); }
string host_id = regex_replace(hostname ? hostname : "(unknown)",
regex("[0-9]*$"), "");
cout << ',' << host_id; // 3
cout << ',' << (fes.GetNRanks() + (nrnode-1))/nrnode; // 4
cout << ',' << fes.GetNRanks(); // 5
cout << ',' << nx << ',' << ny << ',' << nz; // 6,7,8
cout << ',' << order; // 9
// DiffusionMultigrid::ConstructBilinearForm p+1+q1d_inc 1D points
real_t Q1D = order + 1 + q1d_inc;
cout << ',' << defaultfloat << Q1D; // 10 (note: written as real_t)
cout << ',' << scientific << epsy << ',' << epsz; // 11,12
cout << ',' << ndof; // 13
cout << ',' << niter; // 14
cout << ',' << monitor.initial_nrm << ',' << monitor.final_nrm; // 15,16
cout << ',' << L2_err; // 17
// cout << ',' << grad_err; // 17 *** for testing ***
cout << ',' << t_setup << ',' << t_solve; // 18,19
cout << endl;
}
return 0;
}
void report_hypre_gpu_status(bool gpu_aware_mpi_requested)
{
#if defined(HYPRE_WITH_GPU_AWARE_MPI) || defined(HYPRE_USING_GPU_AWARE_MPI)
bool hypre_gpu_aware_mpi = true;
#else
bool hypre_gpu_aware_mpi = false;
#endif
#if (MFEM_HYPRE_VERSION > 23000)
hypre_gpu_aware_mpi = hypre_gpu_aware_mpi && hypre_GetGpuAwareMPI();
#endif
if (Mpi::Root())
{
MFEM_VERIFY(!gpu_aware_mpi_requested || hypre_gpu_aware_mpi,
"GPU-aware MPI requested but HYPRE's GPU-aware MPI support"
" is not enabled");
cout << "\nHYPRE GPU support: "
<< (HypreUsingGPU() ? "enabled" : "disabled");
cout << "\nHYPRE GPU-aware MPI support: "
<< (hypre_gpu_aware_mpi ? "enabled" : "disabled") << endl;
}
}
void report_env_vars()
{
const int myid = Mpi::WorldRank();
// const int lastid = min(Mpi::WorldSize(),4)-1; // show up to 4 ranks
const int lastid = Mpi::WorldSize()-1;
if (myid > lastid) { return; }
Array<char> recv_buf;
int buflen = -1, tag = 42;
const char *env_vars[] =
{
"HOST", "HOSTNAME", "MPICH_GPU_SUPPORT_ENABLED", "CUDA_VISIBLE_DEVICES",
"ROCR_VISIBLE_DEVICES"
};
const int num_env_vars = sizeof(env_vars)/sizeof(env_vars[0]);
// Send strings to rank 0, so that they can be printed in order, guaranteed.
// Every rank > 0 sends to rank 0:
if (myid > 0)
{
for (int ev = 0; ev < num_env_vars; ev++)
{
const char *env_var_val = getenv(env_vars[ev]);
buflen = env_var_val ? int(strlen(env_var_val)+1) : -1;
MPI_Send(&buflen, 1, MPI_INT, 0, tag, MPI_COMM_WORLD);
if (env_var_val)
{
MPI_Send(env_var_val, buflen, MPI_CHAR, 0, tag, MPI_COMM_WORLD);
}
}
}
else // myid == 0
{
cout << "\nDefined environment variables:\n";
for (int id = 0; id <= lastid; id++)
{
cout << "[rank " << id << "]:";
for (int ev = 0, vars_shown = 0; ev < num_env_vars; ev++)
{
const char *env_var_val = nullptr;
if (id == 0)
{
env_var_val = getenv(env_vars[ev]);
buflen = env_var_val ? 0 : -1;
}
else
{
MPI_Recv(&buflen, 1, MPI_INT, id, tag, MPI_COMM_WORLD,
MPI_STATUS_IGNORE);
}
if (buflen != -1)
{
if (id > 0)
{
recv_buf.SetSize(buflen);
MPI_Recv(recv_buf.begin(), buflen, MPI_CHAR, id, tag,
MPI_COMM_WORLD, MPI_STATUS_IGNORE);
env_var_val = recv_buf.begin();
}
if (vars_shown)
{
cout << "\n[rank " << id << "]:";
}
cout << ' ' << env_vars[ev] << '=' << env_var_val;
vars_shown++;
}
}
cout << '\n';
}
if (lastid < Mpi::WorldSize()-1)
{
cout << "... [only " << lastid+1 << '/' << Mpi::WorldSize()
<< " ranks shown]\n";
}
cout << flush;
}
}
real_t verify_ess_bdr(const Vector &b, const Vector &x,
const Array<int> &ess_tdof_list)
{
Vector d(ess_tdof_list.Size());
auto d_b = b.Read();
auto d_x = x.Read();
auto d_d = d.Write();
auto d_ess_ind = ess_tdof_list.Read();
mfem::forall(ess_tdof_list.Size(), [=] MFEM_HOST_DEVICE (int i)
{
const int ind = d_ess_ind[i];
d_d[i] = -fabs(d_b[ind] - d_x[ind]);
});
real_t d_max = -d.Min(); // max is not implemented on device
MPI_Allreduce(MPI_IN_PLACE, &d_max, 1, MFEM_MPI_REAL_T, MPI_MAX,
MPI_COMM_WORLD);
return d_max;
}
+3 -1
View File
@@ -384,8 +384,10 @@ int main(int argc, char *argv[])
dacol.Save();
ConstantCoefficient zero(0.0);
Vector zero_vec(dim); zero_vec = 0_r;
VectorConstantCoefficient vzero(zero_vec);
const real_t s_norm = distance_s.ComputeL2Error(zero),
v_norm = distance_v.ComputeL2Error(zero);
v_norm = distance_v.ComputeL2Error(vzero);
if (myid == 0)
{
cout << fixed << setprecision(10) << "Norms: "
+1
View File
@@ -365,6 +365,7 @@ int main(int argc, char *argv[])
std::map<const DarcySolver*, real_t> setup_time;
chrono.Restart();
BDPMinresSolver bdp(M, B, param);
bdp.iterative_mode = true;
setup_time[&bdp] = chrono.RealTime();
chrono.Restart();
+1
View File
@@ -32,6 +32,7 @@ BramblePasciakSolver::BramblePasciakSolver(ParBilinearForm &mVarf,
std::unique_ptr<HypreParMatrix> invDBt(B_->Transpose());
invDBt->InvScaleRows(diagM);
S_.reset(ParMult(B_.get(), invDBt.get(), true));
invDBt.reset();
M0_.Reset(new HypreDiagScale(*M_));
M1_.Reset(new HypreBoomerAMG(*S_));
M1_.As<HypreBoomerAMG>()->SetPrintLevel(0);
+1
View File
@@ -57,6 +57,7 @@ BDPMinresSolver::BDPMinresSolver(const HypreParMatrix& M,
void BDPMinresSolver::Mult(const Vector & x, Vector & y) const
{
solver_.iterative_mode = this->iterative_mode;
solver_.Mult(x, y);
for (int dof : ess_zero_dofs_) { y[dof] = 0.0; }
}
+1 -1
View File
@@ -52,7 +52,7 @@ class BDPMinresSolver : public DarcySolver
BlockDiagonalPreconditioner prec_;
OperatorPtr BT_;
OperatorPtr S_; // S_ = B diag(M)^{-1} B^T
MINRESSolver solver_;
mutable MINRESSolver solver_;
Array<int> ess_zero_dofs_;
public:
BDPMinresSolver(const HypreParMatrix& M,
+3 -4
View File
@@ -84,7 +84,6 @@ DFSSpaces::DFSSpaces(int order, int num_refine, ParMesh *mesh,
data_.Q_l2.resize(num_refine);
hdiv_fes_->GetEssentialTrueDofs(ess_attr, data_.coarsest_ess_hdivdofs);
data_.C.resize(num_refine+1);
data_.Ae.resize(num_refine+1);
hcurl_fes_ = std::make_unique<ParFiniteElementSpace>(mesh, hcurl_fec_.get());
coarse_hcurl_fes_ = std::make_unique<ParFiniteElementSpace>(*hcurl_fes_);
@@ -174,9 +173,9 @@ void DFSSpaces::CollectDFSData()
data_.C[level_+1].Reset(curl.ParallelAssemble());
mfem::Array<int> ess_hcurl_tdof;
hcurl_fes_->GetEssentialTrueDofs(ess_bdr_attr_, ess_hcurl_tdof);
data_.Ae[level_+1].reset(
data_.C[level_+1].As<HypreParMatrix>()
->EliminateCols(ess_hcurl_tdof));
HypreParMatrix *res =
data_.C[level_+1].As<HypreParMatrix>()->EliminateCols(ess_hcurl_tdof);
delete res;
++level_;
-2
View File
@@ -36,7 +36,6 @@ struct DFSParameters : IterSolveParameters
struct DFSData
{
using UniqueOperatorPtr = std::unique_ptr<OperatorPtr>;
using UniqueHypreParMatrix = std::unique_ptr<HypreParMatrix>;
std::vector<OperatorPtr> agg_hdivdof; // agglomerates to H(div) dofs table
std::vector<OperatorPtr> agg_l2dof; // agglomerates to L2 dofs table
@@ -46,7 +45,6 @@ struct DFSData
std::vector<OperatorPtr> Q_l2; // Q_l2[l] = (W_{l+1})^{-1} P_l2[l]^T W_l
Array<int> coarsest_ess_hdivdofs; // coarsest level essential H(div) dofs
std::vector<OperatorPtr> C; // discrete curl: ND -> RT, map to Null(B)
std::vector<UniqueHypreParMatrix> Ae;
DFSParameters param;
};
+35 -41
View File
@@ -14,53 +14,47 @@
using namespace mfem;
TEST_CASE("OperatorChebyshevSmoother", "[Chebyshev symmetry]")
TEST_CASE("Chebyshev symmetry", "[OperatorChebyshevSmoother]")
{
for (int order = 2; order < 5; ++order)
{
const int cheb_order = 2;
const int order = GENERATE(2, 3, 4);
const int cheb_order = GENERATE(2, 3);
Mesh mesh = Mesh::MakeCartesian3D(4, 4, 4, Element::HEXAHEDRON);
FiniteElementCollection *fec = new H1_FECollection(order, 3);
FiniteElementSpace fespace(&mesh, fec);
Array<int> ess_bdr(mesh.bdr_attributes.Max());
ess_bdr = 1;
Array<int> ess_tdof_list;
fespace.GetEssentialTrueDofs(ess_bdr, ess_tdof_list);
Mesh mesh = Mesh::MakeCartesian3D(4, 4, 4, Element::HEXAHEDRON);
H1_FECollection fec(order, 3);
FiniteElementSpace fespace(&mesh, &fec);
BilinearForm aform(&fespace);
aform.SetAssemblyLevel(AssemblyLevel::PARTIAL);
aform.AddDomainIntegrator(new DiffusionIntegrator);
aform.Assemble();
OperatorPtr opr;
opr.SetType(Operator::ANY_TYPE);
aform.FormSystemMatrix(ess_tdof_list, opr);
Vector diag(fespace.GetTrueVSize());
aform.AssembleDiagonal(diag);
Array<int> ess_tdof_list;
fespace.GetBoundaryTrueDofs(ess_tdof_list);
Solver* smoother = new OperatorChebyshevSmoother(*opr, diag, ess_tdof_list,
cheb_order);
BilinearForm aform(&fespace);
aform.SetAssemblyLevel(AssemblyLevel::PARTIAL);
aform.AddDomainIntegrator(new DiffusionIntegrator);
aform.Assemble();
int n = smoother->Width();
Vector left(n);
Vector right(n);
int seed = (int) time(0);
left.Randomize(seed);
right.Randomize(seed + 2);
OperatorPtr opr;
opr.SetType(Operator::ANY_TYPE);
aform.FormSystemMatrix(ess_tdof_list, opr);
// test that x^T S y = y^T S x
Vector smooth(n);
smooth = 0.0;
smoother->Mult(right, smooth);
double forward_val = left * smooth;
smoother->Mult(left, smooth);
double transpose_val = right * smooth;
Vector diag(fespace.GetTrueVSize());
aform.AssembleDiagonal(diag);
double error = fabs(forward_val - transpose_val) / fabs(forward_val);
CAPTURE(order, error);
REQUIRE(error < 1.e-13);
OperatorChebyshevSmoother smoother(*opr, diag, ess_tdof_list, cheb_order);
delete smoother;
delete fec;
}
const int n = smoother.Width();
Vector left(n);
Vector right(n);
left.Randomize(1);
right.Randomize(2);
// test that x^T S y = y^T S x
Vector smooth(n);
smoother.Mult(right, smooth);
real_t forward_val = left * smooth;
smoother.Mult(left, smooth);
real_t transpose_val = right * smooth;
real_t error = std::abs(forward_val - transpose_val) / std::abs(forward_val);
CAPTURE(order, error);
REQUIRE(error == MFEM_Approx(0.0));
}