Compare commits

..
Author SHA1 Message Date
Will Pazner 653a610455 Vector::DeleteAt on device using InclusiveScan 2025-08-20 19:16:30 -07:00
Will Pazner 304dac15c2 Merge branch 'qfspace-device' into array-vector-improvements-dev
# Conflicts:
#	mesh/mesh.cpp
2025-08-20 17:33:20 -07:00
Will Pazner 061a92067f Merge branch 'master' into array-vector-improvements-dev 2025-08-19 16:33:02 -07:00
Will Pazner f0cb31088c Merge pull request #4989 from mfem/update-ci-mac
Update Xcode version in macos CI from 15.3 -> 16.4
2025-08-19 16:32:39 -07:00
Joseph SignorelliandWill Pazner 0ddb02c7e7 fix type
Co-authored-by: Will Pazner <11493037+pazner@users.noreply.github.com>
2025-08-19 15:57:45 -07:00
Justin Laughlin f407ca7756 Xcode 16.4 2025-08-19 13:58:39 -07:00
Justin Laughlin ab00472c5d Try removing xcode version specification 2025-08-19 13:46:38 -07:00
Justin Laughlin c4a3d31289 Update Xcode version in macos CI from 15.3 -> 16.4 2025-08-19 13:38:07 -07:00
Joseph SignorelliandWill Pazner 427406d1b8 Update Vector::Reserve
Co-authored-by: Will Pazner <11493037+pazner@users.noreply.github.com>
2025-08-19 13:36:42 -07:00
Joseph Signorelli 5026d6ca8a Merge branch 'master' into array-vector-improvements-dev 2025-08-19 12:14:21 -07:00
Joseph Signorelli 04d7e8a62f add newlines to bottom of test files 2025-08-19 12:03:34 -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
Joseph Signorelli 915853cee0 style 2025-08-13 14:51:59 -07:00
Joseph Signorelli a52599d4cc Add Vector::Reserve 2025-08-13 14:49:39 -07:00
Joseph Signorelli 0d5b13c4aa Add Vector::DeleteAt w/ unit test 2025-08-13 14:44:55 -07:00
Joseph Signorelli dda6b0dbe1 Add Array::DeleteAt w/ unit test. 2025-08-13 14:39:29 -07:00
Veselin Dobrev 91cdbeee7d Merge pull request #4919 from mfem/revert-hip-memcpy-async-for-d2d
Use `HipMemcpyDtoD` instead of `HipMemcpyDtoDAsync`
2025-08-12 13:58:36 -07:00
Tzanio Kolev 0466ee74f2 Merge pull request #4622 from mfem/hypre-gpu-tweaks
HYPRE and GPU related tweaks
2025-08-09 10:28:25 -07:00
Tzanio Kolev 50b8873f20 Merge pull request #4923 from mfem/hughcars/cuda-hip-compiler-checks
Check for CUDA or HIP compilers
2025-08-09 10:27:54 -07:00
Veselin Dobrev cd7dfe2188 Merge pull request #4973 from mfem/macos-llvm-ci
switch to llvm 20 for CI
2025-08-07 17:55:50 -07:00
Andrew Ho 1bf00d3761 switch to llvm20 for CI 2025-08-07 15:18:36 -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
Veselin Dobrev 47e9b29c0f Restore the MFEM_UNROLL definition -- it is only used in CUDA device code.
Tweak a comment and restore some formatting.
2025-08-05 16:27:15 -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
Veselin Dobrev bd7281bdfa Merge pull request #4960 from mfem/fix-warnings-algoim
Algoim compilation warnings
2025-08-05 12:05:40 -07:00
Andrew Ho 1299d05d21 Merge branch 'master' into mpi-dfem-fix 2025-08-05 11:11:22 -07:00
Veselin Dobrev 39944d8dfb In the INSTALL file, add requirement for ROCm >= 5.6.1 due to the fix
in that version that made HipMemcpyDtoD asynchronous w.r.t. host.
2025-08-05 09:54:53 -07:00
Hugh Carson 6df8ae80cb PR feedback 2025-08-05 11:17:12 -04:00
John Camier 0730fa304a Merge branch 'master' into revert-hip-memcpy-async-for-d2d 2025-08-05 06:06:02 -07:00
Tzanio Kolev 6dfbe25c07 Merge pull request #4955 from mfem/clang-perf-flags
In `miniapps/performance/makefile`, adjust the flags for Clang
2025-08-04 07:44:47 -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
Tzanio Kolev 02edb95a31 Merge pull request #4957 from farscape-project/conduit
Guarantee data on host when writing conduit files
2025-08-02 09:27:45 -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
Will Pazner 597cba8d37 Merge pull request #4954 from mfem/ruby-60
Extend time allocation for testing on ruby from 45 to 60 minutes
2025-07-29 12:49:00 -07:00
Veselin Dobrev 075a160187 Merge pull request #4827 from mfem/feature/guthrey1/transform_lambda
Allow capturing lambdas for mesh::Transform
2025-07-29 09:34:22 -07:00
Veselin Dobrev 6d8a96a8fb Merge branch 'master' into hypre-gpu-tweaks 2025-07-29 09:08:02 -07:00
Vladimir Z Tomov 8d9579057f Simplified to std:vector. 2025-07-28 15:55:21 -07:00
Vladimir Z Tomov 76d65f5866 Fixed compilation warnings when Algoim is on. 2025-07-28 15:39:50 -07:00
Tzanio Kolev 720fb72372 Merge pull request #4884 from adam-sim-dev/gitignore
Fix gitignore for Windows
2025-07-26 10:45:44 -07:00
Nuno Nobre bbde976378 Guarantee data on host when writing conduit files 2025-07-26 01:31:22 +01:00
Andrew Ho 1efc5e78e5 Added lazy offset construction and optional qspace compression 2025-07-25 12:10:17 -07:00
Andrew Ho a553c2dba8 update doc since CUB implementation by design requires commutative operators 2025-07-25 10:32:57 -07:00
Tzanio Kolev 58ad1c79cd Merge pull request #4673 from mfem/cmake-replace-library-files-with-targets
Support fetching of third party libraries using CMake's ExternalProject
2025-07-24 12:23:03 -07:00
Will Pazner b6f755925c Compress offsets in FaceQuadratureSpace 2025-07-23 16:44:47 -07:00
Veselin Dobrev 447cd0f500 In miniapps/performance/makefile, adjust the flags for Clang 2025-07-22 22:14:35 -07:00
adam-sim-dev 2d68b877f2 Merge branch 'master' into gitignore 2025-07-23 10:01:26 +08:00
Tzanio Kolev cce301f92b Extend time allocation for testing on ruby from 45 to 60 minutes 2025-07-22 12:45:09 -07:00
Veselin Dobrev 46ccaee493 Merge branch 'master' into feature/guthrey1/transform_lambda 2025-07-21 19:36:55 -07:00
Andrew Ho 25bd2f9596 Merge remote-tracking branch 'base/qfspace-device' into qfspace-device 2025-07-21 12:08:55 -07:00
Andrew Ho 516f709061 remove old comments 2025-07-21 12:06:08 -07:00
Andrew Ho fa89692e57 Use O(1) way to find number of faces of given type
GetNFbyType is O(n) in number of faces
2025-07-21 11:54:45 -07:00
Andrew Ho 1b6d878189 Added a way to indicate to the bilinear integrators that the mesh/fespace has been updated 2025-07-21 11:31:05 -07:00
Andrew Ho 4cc9b5c6f6 Merge branch 'master' into qspace-device 2025-07-19 17:07:43 -07:00
Andrew Ho f73f41fc82 Merge branch 'master' into qfspace-device 2025-07-19 17:07:24 -07:00
Andrew Ho 9b1b56a155 avoid overflow in test
found bug for non-commutative scan in cub
2025-07-19 14:20:31 -07:00
Andrew Ho f95b18b457 move face_indices and face_indices_inv into mesh
this allows them to only be re-computed on mesh face info update and
shared between FaceQuadratureSpace objects
2025-07-19 11:24:50 -07:00
Andrew Ho af6d0d7479 Added GPU-accelerated parallel scan 2025-07-18 23:40:16 -07:00
Tzanio Kolev 7aa327174a Merge pull request #4811 from mfem/dev-derefine
AMR Matrix-free derefinement operator
2025-07-15 12:22:19 -07:00
adam-sim-dev 8c861b4602 Merge branch 'master' into gitignore 2025-07-15 09:20:33 +08: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
Tzanio Kolev a901754de5 Merge pull request #4841 from mfem/nurbs-surf
NURBS surface interpolation minapp
2025-07-11 09:18:02 -07:00
Tzanio Kolev 03da41c0f5 Merge pull request #4924 from mfem/dfem-elem-restriction-fix
Multiple actions with one dfem `DifferentiableOperator`
2025-07-11 09:17:25 -07:00
Andrew Ho 2d5f00635f fixed derefinement for ordering byVDIM 2025-07-10 17:00:49 -07:00
Tzanio Kolev c6ec74db41 Merge pull request #4921 from mfem/submesh-small-simplification
Small simplification in `SubMeshUtils::AddBoundaryElements`
2025-07-10 08:55:28 -07:00
Eric B. Chin 8854247f86 Merge branch 'master' into dfem-elem-restriction-fix 2025-07-09 11:17:01 -07:00
E. B. Chin 3a15fe3d96 sum into residual_l 2025-07-08 15:31:36 -07:00
Hugh Carson 3f447cb11c Check if a cuda or hip compiler is being used before accessing cuda or hip specific variable 2025-07-07 15:47:32 -04:00
Arturo Vargas e629502dca Merge branch 'master' into dev-derefine 2025-07-07 09:55:58 -07:00
Tzanio Kolev e0fbc5e3aa Merge branch 'master' into nurbs-surf 2025-07-06 10:50:17 -07:00
Veselin Dobrev 03da9d7789 Small simplification in SubMeshUtils::AddBoundaryElements 2025-07-05 13:46:56 -07:00
Tom Stitt ce434a9c82 HipMemcpyDtoDAsync -> HipMemcpyDtoD since hipMemcpy for d2d is async wip to the host as of 5.6.1 2025-07-03 09:39:58 -07:00
Arturo Vargas 48dd78b154 Merge branch 'master' into dev-derefine 2025-07-03 09:23:21 -07:00
Andrew Ho dc08fc65ce updated changelog 2025-07-01 16:01:38 -07:00
Andrew Ho 3276cb955f Merge branch 'master' into dev-derefine 2025-07-01 15:33:44 -07:00
Tzanio Kolev 25056defeb Merge branch 'master' into nurbs-surf 2025-07-01 14:27:00 -07:00
Andrew Ho bcdbda1597 Merge branch 'master' into dev-derefine 2025-07-01 11:24:42 -07:00
adam-sim-dev 0239b86304 Merge branch 'master' into gitignore 2025-06-30 12:15:37 +08:00
Andrew Ho aca8d51f95 Merge branch 'master' into dev-derefine 2025-06-27 09:25:25 -07:00
Andrew Ho 368724d456 fix missing climit header 2025-06-26 09:30:45 -07:00
Tzanio Kolev a99efcdb63 Merge branch 'master' into dev-derefine 2025-06-26 08:25:48 -07:00
Dylan Copeland 7dded1fdcf Merge branch 'master' of github.com:mfem/mfem into nurbs-surf 2025-06-24 13:28:06 -07:00
adam-sim-dev b5a7cf6470 Merge branch 'master' into gitignore 2025-06-24 12:11:35 +08:00
Dylan Copeland d8b9c7881b Transpose argument for banded factorization. 2025-06-20 10:22:47 -07:00
Dylan Copeland 58f0e28453 Merge branch 'master' of github.com:mfem/mfem into nurbs-surf 2025-06-20 10:13:14 -07:00
Andrew Ho 1ac7837dc0 Merge branch 'master' into qspace-device 2025-06-16 12:20:48 -07:00
Andrew Ho e6fc7893be Merge branch 'master' into dev-derefine 2025-06-16 12:20:13 -07:00
Dylan Copeland ea593def25 Merge branch 'master' of github.com:mfem/mfem into nurbs-surf 2025-06-13 14:15:39 -07:00
Dylan Copeland f316ec7d5e Merge branch 'master' of github.com:mfem/mfem into nurbs-surf 2025-06-13 11:08:05 -07:00
adam-sim-devandJan Nikl 78df8dffb2 Update
Co-authored-by: Jan Nikl <nikl1@llnl.gov>
2025-06-10 05:04:44 +08:00
dylan-copeland ff11a6b572 Merge branch 'master' of github.com:mfem/mfem into nurbs-surf 2025-06-09 13:03:05 -07:00
adam-sim-dev d0f5cb73e7 Fix for Windows 2025-06-09 12:17:58 +08:00
Andrew Ho 056152aaa6 Merge branch 'master' into qspace-device 2025-06-02 11:38:14 -07:00
Andrew Ho db55be84ad Merge branch 'master' into dev-derefine 2025-06-02 11:37:29 -07:00
Andrew Ho 02424a41a9 Merge branch 'master' into qspace-device 2025-05-27 12:05:07 -07:00
Andrew Ho 091c6272fc Merge branch 'master' into dev-derefine 2025-05-27 11:57:28 -07:00
Andrew Ho 101af72047 Merge branch 'master' into dev-derefine 2025-05-19 12:17:39 -07:00
Dylan Copeland 35aeecb5c0 Minor fixes. 2025-05-16 19:15:26 -07:00
Dylan Copeland 44f2a63f16 Merge branch 'master' of github.com:mfem/mfem into nurbs-surf 2025-05-16 19:07:45 -07:00
Andrew Ho 01990d4605 don't need _VM 2025-05-13 10:32:01 -07:00
Andrew Ho 4a3cd44695 move tag from VarMessageTag to MessageTag 2025-05-13 04:46:00 -07:00
Andrew Ho 3b059c3995 struct instead of class 2025-05-12 17:21:05 -07:00
Andrew Ho 89dc5ce5c0 parallel version needs to compile to nothing if not building with MPI 2025-05-12 17:19:16 -07:00
Andrew Ho eb205caaa2 fixed collision in ifdef guard 2025-05-12 17:08:39 -07:00
Andrew Ho f50601f1aa fixed using struct vs. class 2025-05-12 16:52:32 -07:00
Andrew Ho 529d6e3bc2 Split deref matrix ops into separate files 2025-05-12 16:34:40 -07:00
Andrew Ho b289def58a Merge branch 'master' into qspace-device 2025-05-12 14:04:22 -07:00
Andrew Ho ef29b1dd30 Merge remote-tracking branch 'base/dev-derefine' into dev-derefine 2025-05-12 11:58:50 -07:00
Andrew Ho 33b3ef6045 Enable GPU testing of fespace/pfespace derefinement 2025-05-12 11:40:26 -07:00
Andrew Ho 657f6ef5fe Merge branch 'master' into dev-derefine 2025-05-12 11:19:22 -07:00
Andrew Ho 96cfef783c Review suggestions
- const on some local variables
- use VarMessageTag instead of hard-coded integers for MPI messages
- comment updates
2025-05-12 10:59:23 -07:00
Andrew Ho 603f43e775 updated fes_kernels member variable documentation 2025-05-12 10:47:13 -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
Tzanio Kolev 726b5f99ff Added jittering option (off by default) 2025-05-03 23:27:20 -07:00
Dylan Copeland a943683063 Removed optional nodes argument to Mesh::Print. Refactored miniapp. 2025-05-03 19:47:54 -07:00
Tzanio Kolev b6fb45f384 CI fixes 2025-05-03 18:22:51 -07:00
Tzanio Kolev d119fa7636 CI fixes 2025-05-03 17:43:50 -07:00
Tzanio Kolev 4a7c643f99 Fixed, hacks and improvements in the NURBS Surface miniapp 2025-05-03 17:34:45 -07:00
Tzanio Kolev ae9a8b2897 Merge branch 'master' into nurbs-surf 2025-05-02 14:02:23 -07:00
Andrew Ho a2e33109eb Merge branch 'master' into dev-derefine 2025-05-02 13:04:12 -06:00
Dylan Copeland 2d3aba5d87 Generalized machine epsilon in KnotVector::FindMaxima. 2025-05-02 10:05:58 -07:00
Andrew Ho b277a0cb2d Merge branch 'master' into dev-derefine 2025-05-02 10:29:36 -06:00
Andrew Ho f42334cb4d Merge branch 'master' into qspace-device 2025-05-02 10:29:15 -06:00
dylan-copeland 762551da72 Mac fix. 2025-05-01 21:08:35 -07:00
Dylan Copeland 293a374a74 Minor fixes. 2025-05-01 21:00:07 -07:00
Dylan Copeland 39be93547e Refactoring to simplify the API. 2025-05-01 20:49:50 -07:00
Dylan Copeland 7559d37c58 Label glvis windows. 2025-05-01 20:13:23 -07:00
dylan-copeland 90eed63144 Remove unused variables. 2025-05-01 18:23:39 -07:00
Dylan Copeland e6bc4e5a0e Remove no-vis in tests. 2025-05-01 17:25:08 -07:00
Dylan Copeland 03ec8d78e2 Documentation. New miniapp checklist. 2025-05-01 16:46:32 -07:00
Dylan Copeland 3fbeff1db7 Fix visualization. 2025-05-01 11:50:07 -07:00
Dylan Copeland 3d7ac596da Revert a previous change. Reduce output to 3 meshes. 2025-05-01 11:32:34 -07:00
Dylan Copeland 57e1693fc7 CHANGELOG and some minor edits. 2025-05-01 10:54:42 -07:00
Dylan Copeland 67ca28a501 Merge branch 'nurbs-surf' of github.com:mfem/mfem into nurbs-surf 2025-04-30 12:40:46 -07:00
Dylan Copeland 6d334a925a Merge branch 'master' of github.com:mfem/mfem into nurbs-surf 2025-04-30 12:40:31 -07:00
Dylan Copeland 8183e1729d More optimization by reusing banded matrix factorization. 2025-04-30 12:40:16 -07:00
Tzanio Kolev ef1e0caed1 Merge branch 'master' into nurbs-surf 2025-04-30 09:01:34 -07:00
Dylan Copeland f28cd12995 ifdef lapack for banded solver 2025-04-29 22:32:36 -07:00
Dylan Copeland e0aba0647d Banded solver for 1D KnotVector interpolation. 2025-04-29 22:28:49 -07:00
Dylan Copeland 0cc5280e34 New miniapp to fit a NURBS surface to a structured grid of 3D point data. 2025-04-29 11:14:28 -07:00
Tzanio Kolev ac26026ff1 Merge branch 'master' into cmake-replace-library-files-with-targets 2025-04-26 12:19:30 -07:00
Pierson Guthrey d439781751 resolve ambiguity, thanks @v-dobrev 2025-04-23 18:08:06 -07:00
Pierson Guthrey 16c9123f98 allow lambdas for mesh Transformation 2025-04-23 17:21:16 -07: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
Andrew Ho e73ea1fb77 documentation for parallel version 2025-04-23 12:11:54 -07:00
Andrew Ho ea6c104475 added const and documentation 2025-04-23 12:05:21 -07:00
Andrew Ho e7762d126e Merge branch 'master' into dev-derefine 2025-04-23 09:02:36 -07:00
Andrew Ho c61d0e07d3 switch to using INT_MAX for rows to skip 2025-04-22 16:51:04 -07:00
Andrew Ho 720051d641 unused testing var 2025-04-22 08:55:41 -07:00
Andrew Ho 5c25fa49a7 graceful fallback for HOST_PINNED 2025-04-22 08:50:23 -07:00
Andrew Ho d9ef117ee9 Merge branch 'master' into dev-derefine 2025-04-21 23:21:29 -07:00
Andrew Ho b7a8cf6290 CI fixes 2025-04-21 23:20:43 -07:00
Andrew Ho bed1823d72 unused variables 2025-04-21 23:12:54 -07:00
Andrew Ho 93a3648712 fixed a few performance bugs 2025-04-21 20:12:35 -07:00
Christopher vogl 01eeccee7f updated hypre fetch version; removed unnecessary dependencies on install step; leverage METIS_FETCH_VERSION variable 2025-04-21 15:31:52 -07:00
Christopher vogl 39b6112e69 reverted use of MPI target instead of files 2025-04-21 13:49:18 -07:00
Andrew Ho c70d2b7309 off-diagonals should be implemented now 2025-04-21 00:34:35 -07:00
Andrew Ho c98e8bfe07 Think I have ghost comm data being transferred correctly now 2025-04-20 23:03:10 -07:00
Andrew Ho ef9137bd1f const correctness fix 2025-04-20 22:59:08 -07:00
Andrew Ho 8e0c42a132 fixed a few bugs
still need to finish implementing off-diagonals
2025-04-19 23:04:11 -07:00
Andrew Ho d755037904 Re-arranged DerefineMatrixOpFunctor
Now it's a common implementation with the indexing order separated.

Still need to make it work for off-diagonals.
2025-04-19 20:42:17 -07:00
Andrew Ho b577d2322e diagonals for pfespace derefop working
still need to work out the off-diagonals
2025-04-19 17:46:10 -07:00
Andrew Ho 6f99204d37 code cleanup 2025-04-19 01:32:52 -07:00
Andrew Ho c6e93f5474 Coalesce ParallelDerefineMatrix MPI messages
Not strictly required for correctness, but fewer MPI messages should
always be good
2025-04-18 22:17:24 -07:00
Andrew Ho 21b6ade630 removed derefine multT
I don't think this is ever used or has any meaning
2025-04-17 14:31:44 -07:00
Andrew Ho 2e8685d80a started working on ParDerefineMatrixOp 2025-04-17 11:59:01 -07:00
Andrew Ho 6d8c32db80 only need atomic for mult with DG 2025-04-17 09:01:07 -07:00
Andrew Ho 4adb51df50 fixed documentation and p derefinement oob read 2025-04-16 14:37:47 -07:00
Andrew Ho 6cec49e220 fix compiler warnings 2025-04-16 14:23:35 -07:00
Andrew Ho 9006171327 Merge remote-tracking branch 'base/master' into dev-derefine 2025-04-16 13:18:46 -07:00
Andrew Ho 9e10cd87b1 fixed verify for vdim > 1 2025-04-15 23:15:23 -07:00
Andrew Ho 3f7c551d66 implemented MultTranspose 2025-04-15 22:45:35 -07:00
Andrew Ho a7a5ce5cb1 implemented Mult for VDim ordering 2025-04-15 21:48:53 -07:00
Andrew Ho 4218c11b41 have at least one kernel working 2025-04-15 21:31:55 -07:00
Andrew Ho 44f58a169c Merge branch 'master' into qspace-device 2025-04-15 11:01:58 -07:00
Andrew Ho 3c4a860f3a re-arranged the run operation so functor is responsible
need to change parallelization so vdim can be allocated into shared
memory as well
2025-04-15 10:55:39 -07:00
Andrew Ho e64300f84b Re-arranged DerefineMatrixOp to use kernel specialization infrastructure
all that's left is to implement Mult and MultT for specializations
2025-04-10 13:31:26 -07:00
Andrew Ho 3854184620 have setup for prototype derefinematrixop implemented
still not sure if DerefinementOperator is acceptable/modifiable or not
for this
2025-04-09 16:22:13 -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
Christopher vogl bfc6b0708d corrected some unncecessary whitespace added 2025-02-20 16:48:54 -08:00
Christopher vogl 3d5c6d58e8 added options to defaults.cmake and updated INSTALL document 2025-02-20 16:45:40 -08:00
Christopher vogl 6efbd4cc27 applied change suggested by nmnobre to use patched metis to support ARM macs 2025-02-20 16:18:35 -08:00
Christopher vogl 369b9365ad added check for hypre target to top of FindHYPRE 2025-02-20 12:35:19 -08:00
Christopher vogl b35ff2270a applying nmnobre's suggestion to clean up fetched metis directory 2025-02-20 10:24:42 -08:00
Chris VoglandNuno Nobre 7a54a543b0 Apply update_disconnect fix
Co-authored-by: Nuno Nobre <nuno.nobre@stfc.ac.uk>
2025-02-20 10:06:24 -08:00
Christopher vogl 15420f7049 restored support for FETCH_TPLS 2025-02-20 09:56:32 -08:00
Chris VoglandNuno Nobre dd20722733 Applying suggestions for individual fetching and dynamic hypre versioning
Co-authored-by: Nuno Nobre <nuno.nobre@stfc.ac.uk>
2025-02-20 09:42:50 -08:00
Christopher vogl 5577edf446 Added status messages suggested by nmnobre 2025-02-19 09:08:40 -08:00
Chris VoglandNuno Nobre 230a345ef3 Apply suggestions from nmnobre
1. use git tag instead of commit
2. explicitly specify hypre library install directory

Co-authored-by: Nuno Nobre <nuno.nobre@stfc.ac.uk>
2025-02-19 08:42:45 -08:00
Christopher vogl 89f1dce705 added support for fetching hypre with CUDA, HIP, and floats 2025-01-23 10:26:20 -08:00
Christopher vogl 7f17c1f049 see prior message (clearly I need more coffee) 2025-01-22 17:48:30 -08:00
Christopher vogl 7e6edcddec changes that should have been part of prior commit (more commenting) 2025-01-22 17:47:38 -08:00
Christopher vogl d89b01e24c updated commenting of new code 2025-01-22 17:46:04 -08:00
Christopher vogl 33f1150c34 updated FindMETIS to also use ExternalProject 2025-01-22 16:14:09 -08:00
Christopher vogl c6a05d371b fixed capitalization and made fetch directory consistent 2025-01-22 16:13:41 -08:00
Christopher vogl f97a29f6e8 refactored FindHYPRE to use ExternalProject instead of FetchContent 2025-01-22 15:00:06 -08:00
Christopher vogl 85ae72fada changed lib to Lib to be consistent with METIS capitalization 2025-01-21 16:16:30 -08:00
Christopher vogl 27a5bd71ee whitespace 2025-01-21 15:27:14 -08:00
Christopher vogl 643db22bef minor tweaks, commenting, and cleanup 2025-01-21 15:25:18 -08:00
Christopher vogl 97fb6be029 Merge remote-tracking branch 'origin/master' into cmake-replace-library-files-with-targets 2025-01-21 13:41:24 -08:00
Christopher vogl 9587ef212d added fetching of HYPRE and METIS with FETCH_TPLS flag 2025-01-21 13:41:08 -08:00
Christopher vogl 35462b8645 refactored CMakeLists to support targets for TPLs 2025-01-15 13:13:18 -08:00
Veselin Dobrev 4f0f353720 Print error code (in addition to the error string) on CUDA/HIP errors 2024-12-09 18:20:32 -08:00
Veselin Dobrev a3156023d0 Avoid HYPRE initialization via calls to HypreUsingGPU() and
GetHypreMemoryLocation() when running serial codes.
2024-12-09 18:03:01 -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
126 changed files with 4063 additions and 4093 deletions
+8 -5
View File
@@ -168,10 +168,13 @@ jobs:
env
shell: bash
# For info on Xcode see:
# - https://github.com/actions/runner-images/issues/12541
# - https://github.com/actions/runner-images/blob/releases/macos-15-arm64/20250811/images/macos/macos-15-arm64-Readme.md#xcode
- name: Xcode version setup (MacOS)
if: matrix.os == 'macos-latest'
run: |
XCODE_PATH="/Applications/Xcode_15.3.app"
XCODE_PATH="/Applications/Xcode_16.4.app"
echo "> sudo xcode-select -s ${XCODE_PATH}"
sudo xcode-select -s ${XCODE_PATH}
echo "> g++ -v"
@@ -289,10 +292,10 @@ jobs:
run: |
export HOMEBREW_NO_INSTALL_CLEANUP=1
brew update
brew install llvm@19 enzyme
echo "LLVM_PREFIX=$(brew --prefix llvm@19)" >> $GITHUB_ENV
echo "OMPI_CC=$(brew --prefix llvm@19)/bin/clang" >> $GITHUB_ENV
echo "OMPI_CXX=$(brew --prefix llvm@19)/bin/clang++" >> $GITHUB_ENV
brew install llvm@20 enzyme
echo "LLVM_PREFIX=$(brew --prefix llvm@20)" >> $GITHUB_ENV
echo "OMPI_CC=$(brew --prefix llvm@20)/bin/clang" >> $GITHUB_ENV
echo "OMPI_CXX=$(brew --prefix llvm@20)/bin/clang++" >> $GITHUB_ENV
# MFEM build and test
- name: build
+6 -7
View File
@@ -211,7 +211,7 @@ miniapps/electromagnetics/joule
miniapps/electromagnetics/Volta-AMR*
miniapps/electromagnetics/Tesla-AMR*
miniapps/electromagnetics/Maxwell-Parallel*
miniapps/electromagnetics/Joule_*
miniapps/electromagnetics/Joule_[0-9]*
miniapps/gslib/field-diff
miniapps/gslib/field-interp
@@ -267,9 +267,9 @@ miniapps/meshing/bounding-box*
miniapps/meshing/jacobian-determinant*
miniapps/mtop/parheat
miniapps/mtop/ParHeat*
miniapps/mtop/ParHeat/*
miniapps/mtop/seqheat
miniapps/mtop/SeqHeat*
miniapps/mtop/SeqHeat/*
miniapps/autodiff/paradiff
miniapps/autodiff/seqadiff
@@ -277,7 +277,7 @@ miniapps/autodiff/seqtest
miniapps/autodiff/par_example
miniapps/autodiff/seq_example
miniapps/autodiff/seq_test
miniapps/autodiff/Exampl*
miniapps/autodiff/Example/*
miniapps/navier/navier_mms
miniapps/navier/navier_kovasznay
@@ -300,6 +300,7 @@ miniapps/nurbs/nurbs_solenoidal
miniapps/nurbs/nurbs_printfunc
miniapps/nurbs/nurbs_patch_ex1
miniapps/nurbs/nurbs_curveint
miniapps/nurbs/nurbs_surface
miniapps/nurbs/refined.mesh
miniapps/nurbs/mesh.*
miniapps/nurbs/sol_?.gf
@@ -318,6 +319,7 @@ miniapps/nurbs/nurbs_naca_cmesh
miniapps/nurbs/naca-cmesh.mesh
miniapps/nurbs/glvis_naca-cmesh.mesh
miniapps/nurbs/Naca_cmesh
miniapps/nurbs/*-Surface.mesh
miniapps/performance/ex1
miniapps/performance/ex1p
@@ -409,8 +411,6 @@ 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
@@ -431,7 +431,6 @@ tests/benchmarks/bench_ceed
tests/benchmarks/bench_tmop
tests/benchmarks/bench_vector
tests/benchmarks/bench_virtuals
tests/benchmarks/pbench_ceed
# Test script output
tests/scripts/*.err
+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,16 +40,17 @@ variables:
- when: on_success
# Spack helped builds
# Generic ruby build job, extending build script
.build_and_test_on_ruby:
extends: [.on_ruby]
# Generic dane build job, extending build script
.build_and_test_on_dane:
extends: [.on_dane]
stage: build_and_test
script:
# THREADS is used by 'tests/gitlab/build_and_test', run below
- export THREADS=16
# Dane has 224 threads/node and we run 7 separate jobs: 224=7*32
- export THREADS=28
- echo ${ALLOC_NAME}
- export JOBID=$(squeue -h --name=${ALLOC_NAME} --format=%A)
- echo ${JOBID}
- echo ${MFEM_DATA_DIR}
- echo ${SPEC}
- srun $( [[ -n "${JOBID}" ]] && echo "--jobid=${JOBID}" ) --reservation=ci -t 45 -N 1 tests/gitlab/build_and_test --spec "${SPEC}" --data-dir "${MFEM_DATA_DIR}" --data
- srun $( [[ -n "${JOBID}" ]] && echo "--jobid=${JOBID}" ) --reservation=ci -t 60 -N 1 tests/gitlab/build_and_test --spec "${SPEC}" --data-dir "${MFEM_DATA_DIR}" --data
+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
+10 -7
View File
@@ -29,9 +29,14 @@ Discretization improvements
Meshing improvements
--------------------
- Added support for higher order meshes in Mesh::MakeSimplicial and
ParMesh::MakeSimplicial.
- Added a new miniapp for interpolating a surface grid of points in 3D using a
smooth NURBS surface, that can then be sampled at arbitrary resolution while
staying close to the original geometry. See miniapps/nurbs/nurbs_surface.
GPU computing
-------------
- The function Vector::SetSubVector(const Array<int> &, const real_t) now
@@ -41,6 +46,11 @@ GPU computing
where host execution is always needed (e.g. when the DOFs array is small).
- Introduced MFEM_FOREACH_THREAD_DIRECT, which directly maps loop tasks to GPU
threads, assigning one task per thread.
- Implemented a GPU-accelerated matrix-free AMR derefinement `GridFunction`
update operator. This supports mixed geometry meshes and variable order
spaces, and is the default derefinement operator constructed by
`FiniteElementSpace::Update` and `ParFiniteElementSpace::Update`.
The operator requires `FiniteElementSpace::Nonconforming() == true`.
New and updated examples and miniapps
-------------------------------------
@@ -70,13 +80,6 @@ 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
====================================
+14 -5
View File
@@ -598,14 +598,20 @@ set(MFEM_TPLS OPENMP HYPRE LAPACK BLAS SuperLUDist STRUMPACK METIS SuiteSparse
BENCHMARK PARELAG TRIBOL MPI_CXX HIP HIPBLAS HIPSPARSE MOONOLITH BLITZ
ALGOIM ENZYME)
# Add all *_FOUND libraries in the variable TPL_LIBRARIES.
# Add all created targets and *_FOUND libraries in the variables TPL_TARGETS and
# TPL_LIBRARIES, respectively.
set(TPL_TARGETS)
set(TPL_LIBRARIES "")
set(TPL_INCLUDE_DIRS "")
foreach(TPL IN LISTS MFEM_TPLS)
if (${TPL}_FOUND)
if (${TPL}_FOUND OR TARGET ${TPL})
message(STATUS "MFEM: using package ${TPL}")
list(APPEND TPL_LIBRARIES ${${TPL}_LIBRARIES})
list(APPEND TPL_INCLUDE_DIRS ${${TPL}_INCLUDE_DIRS})
if (TARGET ${TPL})
list(APPEND TPL_TARGETS ${TPL})
else()
list(APPEND TPL_LIBRARIES ${${TPL}_LIBRARIES})
list(APPEND TPL_INCLUDE_DIRS ${${TPL}_INCLUDE_DIRS})
endif()
endif()
endforeach(TPL)
list(REVERSE TPL_LIBRARIES)
@@ -680,7 +686,10 @@ set(MFEM_INSTALL_DIR ${CMAKE_INSTALL_PREFIX})
# Declaring the library
mfem_add_library(mfem ${SOURCES} ${HEADERS} ${MASTER_HEADERS})
# message(STATUS "TPL_LIBRARIES = ${TPL_LIBRARIES}")
target_link_libraries(mfem PUBLIC ${TPL_LIBRARIES})
target_link_libraries(mfem PUBLIC ${TPL_LIBRARIES} ${TPL_TARGETS})
if (TPL_TARGETS)
add_dependencies(mfem ${TPL_TARGETS})
endif()
if (MINGW)
target_link_libraries(mfem PRIVATE ws2_32)
endif()
+16
View File
@@ -121,6 +121,11 @@ Parallel build:
make -j 4
(For METIS 5, see https://mfem.org/building/#parallel-build-using-metis-5)
Parallel build with fetching of hypre and METIS:
mkdir <mfem-buil-dir> ; cd <mfem-build-dir>
cmake <mfem-source-dir> -DMFEM_USE_MPI=YES -DFETCH_TPLS=YES
make -j 4
CUDA build:
(this build requires CMake 3.17 or newer)
mkdir <mfem-build-dir> ; cd <mfem-build-dir>
@@ -663,6 +668,7 @@ The specific libraries and their options are:
- OpenMP (optional), usually part of compiler, used when either MFEM_USE_OPENMP
or MFEM_USE_LEGACY_OPENMP is set to YES.
Options: OPENMP_OPT, OPENMP_LIB.
Versions: OpenMP >= 3.1 when MFEM_USE_OPENMP=YES.
- High-resolution POSIX clocks: when using MFEM_TIMER_TYPE = 2, it may be
necessary to link with a system library (e.g. librt.so).
@@ -842,6 +848,7 @@ The specific libraries and their options are:
- HIP (optional), used when MFEM_USE_HIP = YES.
URL: https://rocmdocs.amd.com
Options: HIP_CXX, HIP_ARCH, HIP_OPT, HIP_LIB.
Versions: ROCm >= 5.6.1.
- OCCA (optional), used when MFEM_USE_OCCA = YES.
URL: https://libocca.org
@@ -1074,6 +1081,9 @@ The following options are CMake specific:
MFEM_ENABLE_TESTING - Enable the ctest framework for testing.
MFEM_ENABLE_EXAMPLES - Build all of the examples by default.
MFEM_ENABLE_MINIAPPS - Build all of the miniapps by default.
FETCH_TPLS - Enable fetching of all supported third-party libraries.
HYPRE_FETCH - Enable fetching of hypre.
METIS_FETCH - Enable fetching of metis.
External libraries (CMake):
---------------------------
@@ -1135,6 +1145,12 @@ The following built-in CMake packages are also used:
set the <LIBNAME>_LIBRARIES option directly; the configuration option
<LIBNAME>_DIR is not supported.
The MFEM CMake build system also provides fetching (automated building) for the
packages/libraries listed below. Note that when fetching is enabled, any related
auto-detection functionality is disabled.
- HYPRE
- METIS
Building without GNU make or CMake
==================================
+54 -2
View File
@@ -9,15 +9,18 @@
# terms of the BSD-3 license. We welcome feedback and contributions, see file
# CONTRIBUTING.md for details.
# Defines the following variables:
# Defines the following variables if fetching of TPLs is disabled (default):
# - HYPRE_FOUND
# - HYPRE_LIBRARIES
# - HYPRE_INCLUDE_DIRS
# - HYPRE_VERSION
# - HYPRE_USING_CUDA (internal)
# - HYPRE_USING_HIP (internal)
# otherwise, the following are defined:
# - HYPRE (imported library target)
# - HYPRE_VERSION (cache variable)
if (HYPRE_FOUND)
if (HYPRE_FOUND OR TARGET HYPRE)
if (HYPRE_USING_CUDA)
find_package(CUDAToolkit REQUIRED)
endif()
@@ -33,6 +36,55 @@ if (HYPRE_FOUND)
endif()
endif()
if (HYPRE_FETCH OR FETCH_TPLS)
set(HYPRE_FETCH_VERSION 2.33.0)
add_library(HYPRE STATIC IMPORTED)
# set options and associated dependencies
set(CMAKE_OPTIONS)
list(APPEND CMAKE_OPTIONS -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE})
if (MFEM_USE_CUDA)
list(APPEND CMAKE_OPTIONS -DHYPRE_WITH_CUDA:BOOL=ON)
find_package(CUDAToolkit REQUIRED)
target_link_libraries(HYPRE INTERFACE CUDA::cusparse CUDA::curand CUDA::cublas)
elseif (MFEM_USE_HIP)
list(APPEND CMAKE_OPTIONS -DHYPRE_WITH_HIP:BOOL=ON)
find_package(rocsparse REQUIRED)
find_package(rocrand REQUIRED)
target_link_libraries(HYPRE INTERFACE rocsparse rocrand)
endif()
if (MFEM_USE_SINGLE)
list(APPEND CMAKE_OPTIONS -DHYPRE_ENABLE_SINGLE:BOOL=ON)
endif()
# define external project and create future include directory so it is present
# to pass CMake checks at end of MFEM configuration step
message(STATUS "Will fetch HYPRE ${HYPRE_FETCH_VERSION} to be built with ${CMAKE_OPTIONS}")
set(PREFIX ${CMAKE_BINARY_DIR}/fetch/hypre)
include(ExternalProject)
ExternalProject_Add(hypre
GIT_REPOSITORY https://github.com/hypre-space/hypre.git
GIT_TAG v${HYPRE_FETCH_VERSION}
GIT_SHALLOW TRUE
UPDATE_DISCONNECTED TRUE
SOURCE_SUBDIR src
PREFIX ${PREFIX}
CMAKE_CACHE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${PREFIX} -DCMAKE_INSTALL_LIBDIR:PATH=lib ${CMAKE_OPTIONS})
file(MAKE_DIRECTORY ${PREFIX}/include)
# set imported library target properties
add_dependencies(HYPRE hypre)
set_target_properties(HYPRE PROPERTIES
IMPORTED_LOCATION ${PREFIX}/lib/libHYPRE.a
INTERFACE_INCLUDE_DIRECTORIES ${PREFIX}/include)
# convert HYPRE version to integer
string(REGEX MATCHALL "[0-9]+" HYPRE_SPLIT_VERSION ${HYPRE_FETCH_VERSION})
list(GET HYPRE_SPLIT_VERSION 0 HYPRE_MAJOR_VERSION)
list(GET HYPRE_SPLIT_VERSION 1 HYPRE_MINOR_VERSION)
list(GET HYPRE_SPLIT_VERSION 2 HYPRE_PATCH_VERSION)
math(EXPR HYPRE_VERSION "10000*${HYPRE_MAJOR_VERSION} + 100*${HYPRE_MINOR_VERSION} + ${HYPRE_PATCH_VERSION}")
# set cache variables that would otherwise be set after mfem_find_package call
set(HYPRE_VERSION ${HYPRE_VERSION} CACHE STRING "HYPRE version." FORCE)
return()
endif()
include(MfemCmakeUtilities)
mfem_find_package(HYPRE HYPRE HYPRE_DIR "include" "HYPRE.h" "lib" "HYPRE"
"Paths to headers required by HYPRE." "Libraries required by HYPRE."
+29 -1
View File
@@ -9,10 +9,38 @@
# terms of the BSD-3 license. We welcome feedback and contributions, see file
# CONTRIBUTING.md for details.
# Defines the following variables:
# Defines the following variables if fetching of TPLs is disabled (default):
# - METIS_FOUND
# - METIS_LIBRARIES
# - METIS_INCLUDE_DIRS
# - METIS_VERSION_5
# otherwise, the following are defined:
# - METIS (imported library target)
# - METIS_VERSION_5 (cache variable)
if (METIS_FETCH OR FETCH_TPLS)
set(METIS_FETCH_VERSION 4.0.3)
add_library(METIS STATIC IMPORTED)
# define external project
message(STATUS "Will fetch METIS ${METIS_FETCH_VERSION} to be built with default options")
set(PREFIX ${CMAKE_BINARY_DIR}/fetch/metis)
include(ExternalProject)
ExternalProject_Add(metis
GIT_REPOSITORY https://github.com/mfem/tpls
GIT_TAG b60352fbe9675d374b00828055e55be4584c7995 # tag from 1/16/25
GIT_SHALLOW TRUE
UPDATE_DISCONNECTED TRUE
PREFIX ${PREFIX}
CONFIGURE_COMMAND tar -xzf ../metis/metis-${METIS_FETCH_VERSION}-mac.tgz --strip=1
INSTALL_COMMAND mkdir -p ${PREFIX}/lib && cp libmetis.a ${PREFIX}/lib/)
# set imported library target properties
add_dependencies(METIS metis)
set_target_properties(METIS PROPERTIES
IMPORTED_LOCATION ${PREFIX}/lib/libmetis.a)
# set cache variables that would otherwise be set after mfem_find_package call
set(METIS_VERSION_5 FALSE CACHE BOOL "Is METIS version 5?")
return()
endif()
include(MfemCmakeUtilities)
mfem_find_package(METIS METIS METIS_DIR "include;Lib" "metis.h"
+4 -1
View File
@@ -23,11 +23,14 @@
#include "_config.hpp"
#endif
#include <cstdint>
#include <climits>
namespace mfem
{
#if (defined(MFEM_USE_CUDA) && defined(__CUDACC__)) || \
(defined(MFEM_USE_HIP) && defined(__HIPCC__))
(defined(MFEM_USE_HIP) && defined(__HIP__))
#define MFEM_HOST_DEVICE __host__ __device__
#else
#define MFEM_HOST_DEVICE
+6
View File
@@ -89,6 +89,12 @@ option(MFEM_ENABLE_EXAMPLES "Build all of the examples" OFF)
option(MFEM_ENABLE_MINIAPPS "Build all of the miniapps" OFF)
option(MFEM_ENABLE_BENCHMARKS "Build all of the benchmarks" OFF)
# Allow a user to specify fetching of certain third-party libraries instead of
# searching for existing installations.
option(FETCH_TPLS "Enable fetching of all supported third-party libraries" OFF)
option(HYPRE_FETCH "Enable fetching of hypre" OFF)
option(METIS_FETCH "Enable fetching of METIS" OFF)
# Setting CXX/MPICXX on the command line or in user.cmake will overwrite the
# autodetected C++ compiler.
# set(CXX g++)
+1 -3
View File
@@ -78,7 +78,6 @@ 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));
@@ -101,8 +100,7 @@ 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,7 +88,6 @@ 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));
+2
View File
@@ -82,6 +82,8 @@ set(SRCS
fe/fe_ser.cpp
fe_coll.cpp
fespace.cpp
derefmat_op.cpp
pderefmat_op.cpp
geom.cpp
gridfunc.cpp
hybridization.cpp
+41
View File
@@ -1275,6 +1275,22 @@ void BilinearForm::Update(FiniteElementSpace *nfes)
height = width = fes->GetVSize();
if (ext) { ext->Update(); }
for (int k = 0; k < domain_integs.Size(); ++k)
{
domain_integs[k]->Update();
}
for (int k = 0; k < boundary_integs.Size(); ++k)
{
boundary_integs[k]->Update();
}
for (int k = 0; k < interior_face_integs.Size(); ++k)
{
interior_face_integs[k]->Update();
}
for (int k = 0; k < boundary_integs.Size(); ++k)
{
boundary_face_integs[k]->Update();
}
}
void BilinearForm::SetDiagonalPolicy(DiagonalPolicy policy)
@@ -2337,6 +2353,31 @@ void MixedBilinearForm::Update()
height = test_fes->GetVSize();
width = trial_fes->GetVSize();
if (ext) { ext->Update(); }
for (int k = 0; k < domain_integs.Size(); ++k)
{
domain_integs[k]->Update();
}
for (int k = 0; k < boundary_integs.Size(); ++k)
{
boundary_integs[k]->Update();
}
for (int k = 0; k < interior_face_integs.Size(); ++k)
{
interior_face_integs[k]->Update();
}
for (int k = 0; k < boundary_integs.Size(); ++k)
{
boundary_face_integs[k]->Update();
}
for (int k = 0; k < trace_face_integs.Size(); ++k)
{
trace_face_integs[k]->Update();
}
for (int k = 0; k < boundary_trace_face_integs.Size(); ++k)
{
boundary_trace_face_integs[k]->Update();
}
}
MixedBilinearForm::~MixedBilinearForm()
+26 -70
View File
@@ -255,8 +255,6 @@ 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);
@@ -268,11 +266,7 @@ void PABilinearFormExtension::SetupRestrictionOperators(const L2FaceValues m)
// Gather the attributes on the host from all the elements
const Mesh &mesh = *trial_fes->GetMesh();
elem_attributes.SetSize(mesh.GetNE());
for (int i = 0; i < mesh.GetNE(); ++i)
{
elem_attributes[i] = mesh.GetAttribute(i);
}
elem_attributes = &mesh.GetElementAttributes();
}
// Construct face restriction operators only if the bilinear form has
@@ -331,52 +325,12 @@ 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();
@@ -433,7 +387,7 @@ void PABilinearFormExtension::AssembleDiagonal(Vector &y) const
mfem::forall(ne, [=] MFEM_HOST_DEVICE (int e)
{
const int attr = d_attr[e];
if (d_m[attr - 1] == 0)
if (attr <= 0 || d_m[attr - 1] == 0)
{
for (int i = 0; i < nd; ++i)
{
@@ -454,7 +408,7 @@ void PABilinearFormExtension::AssembleDiagonal(Vector &y) const
for (int i = 0; i < iSz; ++i)
{
assemble_diagonal_with_markers(*integrators[i], elem_markers[i],
elem_attributes, localY);
*elem_attributes, localY);
}
const ElementRestriction* H1elem_restrict =
dynamic_cast<const ElementRestriction*>(elem_restrict);
@@ -480,7 +434,7 @@ void PABilinearFormExtension::AssembleDiagonal(Vector &y) const
for (int i = 0; i < iSz; ++i)
{
assemble_diagonal_with_markers(*integrators[i], elem_markers[i],
elem_attributes, y);
*elem_attributes, y);
}
}
@@ -493,7 +447,7 @@ void PABilinearFormExtension::AssembleDiagonal(Vector &y) const
for (int i = 0; i < n_bdr_integs; ++i)
{
assemble_diagonal_with_markers(*bdr_integs[i], bdr_markers[i],
bdr_attributes, bdr_face_Y);
*bdr_face_attributes, bdr_face_Y);
}
bdr_face_restrict_lex->AddAbsMultTranspose(bdr_face_Y, y);
}
@@ -533,8 +487,6 @@ 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();
@@ -594,7 +546,7 @@ void PABilinearFormExtension::MultInternal(const Vector &x, Vector &y,
for (int i = 0; i < iSz; ++i)
{
AddMultWithMarkers(*integrators[i], localX, elem_markers[i],
elem_attributes, false, localY, useAbs);
*elem_attributes, false, localY, useAbs);
}
if (H1elem_restrict && useAbs)
{
@@ -696,8 +648,8 @@ void PABilinearFormExtension::MultInternal(const Vector &x, Vector &y,
}
for (int i = 0; i < n_bdr_integs; ++i)
{
AddMultWithMarkers(*bdr_integs[i], bdr_face_X, bdr_markers[i], bdr_attributes,
false, bdr_face_Y);
AddMultWithMarkers(*bdr_integs[i], bdr_face_X, bdr_markers[i],
*bdr_face_attributes, false, bdr_face_Y);
}
for (int i = 0; i < n_bdr_face_integs; ++i)
{
@@ -705,12 +657,14 @@ void PABilinearFormExtension::MultInternal(const Vector &x, Vector &y,
{
AddMultNormalDerivativesWithMarkers(
*bdr_face_integs[i], bdr_face_X, bdr_face_dXdn,
bdr_face_markers[i], bdr_attributes, bdr_face_Y, bdr_face_dYdn);
bdr_face_markers[i], *bdr_face_attributes, bdr_face_Y,
bdr_face_dYdn);
}
else
{
AddMultWithMarkers(*bdr_face_integs[i], bdr_face_X, bdr_face_markers[i],
bdr_attributes, false, bdr_face_Y);
AddMultWithMarkers(*bdr_face_integs[i], bdr_face_X,
bdr_face_markers[i], *bdr_face_attributes, false,
bdr_face_Y);
}
}
bdr_face_restrict_lex->AddMultTransposeInPlace(bdr_face_Y, y);
@@ -733,7 +687,7 @@ void PABilinearFormExtension::MultTranspose(const Vector &x, Vector &y) const
localY = 0.0;
for (int i = 0; i < iSz; ++i)
{
AddMultWithMarkers(*integrators[i], localX, elem_markers[i], elem_attributes,
AddMultWithMarkers(*integrators[i], localX, elem_markers[i], *elem_attributes,
true, localY);
}
elem_restrict->MultTranspose(localY, y);
@@ -780,13 +734,14 @@ void PABilinearFormExtension::MultTranspose(const Vector &x, Vector &y) const
bdr_face_Y = 0.0;
for (int i = 0; i < n_bdr_integs; ++i)
{
AddMultWithMarkers(*bdr_integs[i], bdr_face_X, bdr_markers[i], bdr_attributes,
true, bdr_face_Y);
AddMultWithMarkers(*bdr_integs[i], bdr_face_X, bdr_markers[i],
*bdr_face_attributes, true, bdr_face_Y);
}
for (int i = 0; i < n_bdr_face_integs; ++i)
{
AddMultWithMarkers(*bdr_face_integs[i], bdr_face_X, bdr_face_markers[i],
bdr_attributes, true, bdr_face_Y);
AddMultWithMarkers(*bdr_face_integs[i], bdr_face_X,
bdr_face_markers[i], *bdr_face_attributes, true,
bdr_face_Y);
}
bdr_face_restrict_lex->AddMultTransposeInPlace(bdr_face_Y, y);
}
@@ -810,7 +765,7 @@ static void AddWithMarkers_(
mfem::forall(ne, [=] MFEM_HOST_DEVICE (int e)
{
const int attr = d_attr[e];
if (d_m[attr - 1] == 0) { return; }
if (attr <= 0 || d_m[attr - 1] == 0) { return; }
for (int i = 0; i < nd; ++i)
{
d_y(i, e) += d_x(i, e);
@@ -926,7 +881,8 @@ void EABilinearFormExtension::Assemble()
{
const int i = idx % sz;
const int e = idx / sz;
const real_t val = d_m[d_a[e] - 1] ? d_ea_1(i, e) : 0.0;
const real_t val =
d_a[e] > 0 ? (d_m[d_a[e] - 1] ? d_ea_1(i, e) : 0) : 0;
if (add)
{
d_ea_2(i, e) += val;
@@ -959,7 +915,7 @@ void EABilinearFormExtension::Assemble()
ea_data_tmp.SetSize(ea_data.Size());
integrators[i]->AssembleEA(*a->FESpace(), ea_data_tmp, false);
add_with_markers(ea_data_tmp, ea_data, ne, *markers,
elem_attributes, add);
*elem_attributes, add);
}
}
}
@@ -988,7 +944,7 @@ void EABilinearFormExtension::Assemble()
ea_data_tmp.SetSize(ea_data_bdr.Size());
bdr_integs[i]->AssembleEABoundary(*a->FESpace(), ea_data_tmp, add);
add_with_markers(ea_data_tmp, ea_data_bdr, nf_bdr, *markers,
bdr_attributes, add);
*bdr_face_attributes, add);
}
}
}
@@ -1037,7 +993,7 @@ void EABilinearFormExtension::Assemble()
ea_data_tmp,
add);
add_with_markers(ea_data_tmp, ea_data_bdr, nf_bdr, *markers,
bdr_attributes, add);
*bdr_face_attributes, add);
}
}
}
+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;
+11
View File
@@ -21,6 +21,11 @@ using namespace std;
namespace mfem
{
void BilinearFormIntegrator::Update()
{
// default no-op
}
void BilinearFormIntegrator::AssemblePA(const FiniteElementSpace&)
{
MFEM_ABORT("BilinearFormIntegrator::AssemblePA(fes)\n"
@@ -3460,6 +3465,12 @@ real_t ElasticityIntegrator::ComputeFluxEnergy(const FiniteElement &fluxelem,
return energy;
}
void DGTraceIntegrator::Update()
{
qspace[0].reset();
qspace[1].reset();
}
void DGTraceIntegrator::AssembleFaceMatrix(const FiniteElement &el1,
const FiniteElement &el2,
FaceElementTransformations &Trans,
+19 -10
View File
@@ -23,6 +23,8 @@
namespace mfem
{
class QuadratureSpace;
class FaceQuadratureSpace;
/// Abstract base class BilinearFormIntegrator
class BilinearFormIntegrator : public NonlinearFormIntegrator
@@ -44,6 +46,10 @@ public:
// make sense for the action of the nonlinear operator (but they all make
// sense for its Jacobian).
/// Signal this integrator that something about either the trial or test space has changed.
virtual void Update();
/// Method defining partial assembly.
/** The result of the partial assembly is stored internally so that it can be
used later in the methods AddMultPA() and AddMultTransposePA(). */
@@ -812,7 +818,7 @@ protected:
const FiniteElement & test_fe) const
{
return (trial_fe.GetDim() == 1 && test_fe.GetDim() == 1 &&
trial_fe.GetDerivType() == mfem::FiniteElement::GRAD &&
trial_fe.GetDerivType() == mfem::FiniteElement::GRAD &&
test_fe.GetRangeType() == mfem::FiniteElement::SCALAR );
}
@@ -884,7 +890,7 @@ protected:
const FiniteElement & trial_fe,
const FiniteElement & test_fe) const
{
return (trial_fe.GetDerivType() == mfem::FiniteElement::DIV &&
return (trial_fe.GetDerivType() == mfem::FiniteElement::DIV &&
test_fe.GetRangeType() == mfem::FiniteElement::SCALAR );
}
@@ -919,7 +925,7 @@ protected:
const FiniteElement & trial_fe,
const FiniteElement & test_fe) const
{
return (trial_fe.GetDerivType() == mfem::FiniteElement::DIV &&
return (trial_fe.GetDerivType() == mfem::FiniteElement::DIV &&
test_fe.GetRangeType() == mfem::FiniteElement::VECTOR );
}
@@ -1600,7 +1606,7 @@ public:
{
return (trial_fe.GetCurlDim() == 3 && test_fe.GetRangeDim() == 3 &&
trial_fe.GetRangeType() == mfem::FiniteElement::VECTOR &&
trial_fe.GetDerivType() == mfem::FiniteElement::CURL &&
trial_fe.GetDerivType() == mfem::FiniteElement::CURL &&
test_fe.GetRangeType() == mfem::FiniteElement::VECTOR );
}
@@ -1635,7 +1641,7 @@ public:
{
return (trial_fe.GetDim() == 2 && test_fe.GetDim() == 2 &&
trial_fe.GetRangeType() == mfem::FiniteElement::VECTOR &&
trial_fe.GetDerivType() == mfem::FiniteElement::CURL &&
trial_fe.GetDerivType() == mfem::FiniteElement::CURL &&
test_fe.GetRangeType() == mfem::FiniteElement::VECTOR );
}
@@ -1669,7 +1675,7 @@ public:
{
return (trial_fe.GetDim() == 2 && test_fe.GetDim() == 2 &&
trial_fe.GetRangeType() == mfem::FiniteElement::SCALAR &&
trial_fe.GetDerivType() == mfem::FiniteElement::GRAD &&
trial_fe.GetDerivType() == mfem::FiniteElement::GRAD &&
test_fe.GetRangeType() == mfem::FiniteElement::SCALAR );
}
@@ -1760,7 +1766,7 @@ public:
const FiniteElement & test_fe) const
{
return (trial_fe.GetRangeType() == mfem::FiniteElement::SCALAR &&
trial_fe.GetDerivType() == mfem::FiniteElement::GRAD &&
trial_fe.GetDerivType() == mfem::FiniteElement::GRAD &&
test_fe.GetRangeType() == mfem::FiniteElement::SCALAR );
}
@@ -1793,7 +1799,7 @@ public:
const FiniteElement & test_fe) const
{
return (trial_fe.GetRangeType() == mfem::FiniteElement::SCALAR &&
trial_fe.GetDerivType() == mfem::FiniteElement::GRAD &&
trial_fe.GetDerivType() == mfem::FiniteElement::GRAD &&
test_fe.GetRangeType() == mfem::FiniteElement::VECTOR &&
test_fe.GetDerivType() == mfem::FiniteElement::DIV );
}
@@ -1832,7 +1838,7 @@ public:
const FiniteElement & test_fe) const
{
return (trial_fe.GetRangeType() == mfem::FiniteElement::VECTOR &&
trial_fe.GetDerivType() == mfem::FiniteElement::DIV &&
trial_fe.GetDerivType() == mfem::FiniteElement::DIV &&
test_fe.GetRangeType() == mfem::FiniteElement::SCALAR &&
test_fe.GetDerivType() == mfem::FiniteElement::GRAD
);
@@ -1973,7 +1979,7 @@ protected:
const FiniteElement & test_fe) const override
{
return (trial_fe.GetCurlDim() == 3 && test_fe.GetRangeDim() == 3 &&
trial_fe.GetDerivType() == mfem::FiniteElement::CURL &&
trial_fe.GetDerivType() == mfem::FiniteElement::CURL &&
test_fe.GetRangeType() == mfem::FiniteElement::VECTOR );
}
@@ -3311,6 +3317,7 @@ protected:
VectorCoefficient *u;
real_t alpha, beta;
// PA extension
std::unique_ptr<FaceQuadratureSpace> qspace[2];
Vector pa_data;
const DofToQuad *maps; ///< Not owned
const FaceGeometricFactors *geom; ///< Not owned
@@ -3333,6 +3340,8 @@ public:
real_t a, real_t b)
{ rho = &rho_; u = &u_; alpha = a; beta = b; }
void Update() override;
using BilinearFormIntegrator::AssembleFaceMatrix;
void AssembleFaceMatrix(const FiniteElement &el1,
const FiniteElement &el2,
+4 -4
View File
@@ -912,7 +912,7 @@ ConduitDataCollection::GridFunctionToBlueprintField(mfem::GridFunction *gf,
if (vdim == 1) // scalar case
{
n_field["values"].set_external(gf->GetData(),
n_field["values"].set_external(const_cast<real_t *>(gf->HostRead()),
ndofs);
}
else // vector case
@@ -925,18 +925,18 @@ ConduitDataCollection::GridFunctionToBlueprintField(mfem::GridFunction *gf,
int vdim_stride = (ordering == Ordering::byNODES ? ndofs : 1);
index_t offset = 0;
index_t stride = sizeof(double) * entry_stride;
index_t stride = sizeof(real_t) * entry_stride;
for (int d = 0; d < vdim; d++)
{
std::ostringstream oss;
oss << "v" << d;
std::string comp_name = oss.str();
n_field["values"][comp_name].set_external(gf->GetData(),
n_field["values"][comp_name].set_external(const_cast<real_t *>(gf->HostRead()),
ndofs,
offset,
stride);
offset += sizeof(double) * vdim_stride;
offset += sizeof(real_t) * vdim_stride;
}
}
+266
View File
@@ -0,0 +1,266 @@
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#include "derefmat_op.hpp"
#include "fes_kernels.hpp"
/// \cond DO_NOT_DOCUMENT
namespace mfem
{
namespace internal
{
template <Ordering::Type Order, bool Atomic>
static void DerefMultKernelImpl(const DerefineMatrixOp &op, const Vector &x,
Vector &y)
{
DerefineMatrixOpMultFunctor<Order, Atomic> func;
func.xptr = x.Read();
y.UseDevice();
y = 0.;
func.yptr = y.ReadWrite();
func.bsptr = op.block_storage.Read();
func.boptr = op.block_offsets.Read();
func.brptr = op.block_row_idcs_offsets.Read();
func.bcptr = op.block_col_idcs_offsets.Read();
func.rptr = op.row_idcs.Read();
func.cptr = op.col_idcs.Read();
func.vdims = op.fespace->GetVDim();
func.nblocks = op.block_offsets.Size();
func.width = op.Width() / func.vdims;
func.height = op.Height() / func.vdims;
func.Run(op.max_rows);
}
} // namespace internal
DerefineMatrixOp::DerefineMatrixOp(FiniteElementSpace &fespace_, int old_ndofs,
const Table *old_elem_dof,
const Table *old_elem_fos)
: Operator(fespace_.GetVSize(), old_ndofs * fespace_.GetVDim()),
fespace(&fespace_)
{
static Kernels kernels;
constexpr int max_team_size = 256;
/// TODO: Implement DofTransformation support
MFEM_VERIFY(fespace->Nonconforming(),
"Not implemented for conforming meshes.");
MFEM_VERIFY(old_ndofs, "Missing previous (finer) space.");
MFEM_VERIFY(fespace->GetNDofs() <= old_ndofs,
"Previous space is not finer.");
const CoarseFineTransformations &dtrans =
fespace->GetMesh()->ncmesh->GetDerefinementTransforms();
MFEM_ASSERT(dtrans.embeddings.Size() == old_elem_dof->Size(), "");
const bool is_dg = fespace->FEColl()->GetContType()
== FiniteElementCollection::DISCONTINUOUS;
DenseMatrix localRVO; // for variable-order only
DenseTensor localR[Geometry::NumGeom];
int total_rows = 0;
int total_cols = 0;
block_offsets.SetSize(dtrans.embeddings.Size());
block_offsets.HostWrite();
if (fespace->IsVariableOrder())
{
// TODO: any potential for some compression here?
// determine storage size and offsets
block_offsets[0] = 0;
int total_size = 0;
for (int k = 0; k < dtrans.embeddings.Size(); ++k)
{
const Embedding &emb = dtrans.embeddings[k];
const FiniteElement *fe = fespace->GetFE(emb.parent);
const int ldof = fe->GetDof();
if (k + 1 < dtrans.embeddings.Size())
{
block_offsets[k + 1] = block_offsets[k] + ldof * ldof;
}
total_rows += ldof;
total_cols += ldof;
total_size += ldof * ldof;
}
block_storage.SetSize(total_size);
}
else
{
// compression scheme:
// block_offsets is the start of each block, potentially repeated
// only need to store localR for used shapes
Mesh::GeometryList elem_geoms(*fespace->GetMesh());
int geom_offsets[Geometry::NumGeom];
{
int size = 0;
for (int i = 0; i < elem_geoms.Size(); ++i)
{
fespace->GetLocalDerefinementMatrices(elem_geoms[i],
localR[elem_geoms[i]]);
geom_offsets[elem_geoms[i]] = size;
size += localR[elem_geoms[i]].TotalSize();
}
block_storage.SetSize(size);
// copy blocks into block_storage
auto bs_ptr = block_storage.HostWrite();
for (int i = 0; i < elem_geoms.Size(); ++i)
{
std::copy(localR[elem_geoms[i]].Data(),
localR[elem_geoms[i]].Data()
+ localR[elem_geoms[i]].TotalSize(),
bs_ptr);
bs_ptr += localR[elem_geoms[i]].TotalSize();
}
}
for (int k = 0; k < dtrans.embeddings.Size(); ++k)
{
const Embedding &emb = dtrans.embeddings[k];
Geometry::Type geom =
fespace->GetMesh()->GetElementBaseGeometry(emb.parent);
auto size = localR[geom].SizeI() * localR[geom].SizeJ();
total_rows += localR[geom].SizeI();
total_cols += localR[geom].SizeJ();
// set block offsets and sizes
block_offsets[k] = geom_offsets[geom] + size * emb.matrix;
}
}
row_idcs.SetSize(total_rows);
row_idcs.HostWrite();
col_idcs.SetSize(total_cols);
col_idcs.HostWrite();
block_row_idcs_offsets.SetSize(dtrans.embeddings.Size() + 1);
block_row_idcs_offsets.HostWrite();
block_col_idcs_offsets.SetSize(dtrans.embeddings.Size() + 1);
block_col_idcs_offsets.HostWrite();
block_row_idcs_offsets[0] = 0;
block_col_idcs_offsets[0] = 0;
// compute index information
Array<int> dofs, old_dofs;
max_rows = 1;
{
Array<int> mark(fespace->GetNDofs());
mark = 0;
auto bs_ptr = block_storage.HostWrite();
int ridx = 0;
int cidx = 0;
int num_marked = 0;
for (int k = 0; k < dtrans.embeddings.Size(); k++)
{
const Embedding &emb = dtrans.embeddings[k];
Geometry::Type geom =
fespace->GetMesh()->GetElementBaseGeometry(emb.parent);
if (fespace->IsVariableOrder())
{
const FiniteElement *fe = fespace->GetFE(emb.parent);
const DenseTensor &pmats = dtrans.point_matrices[geom];
const int ldof = fe->GetDof();
IsoparametricTransformation isotr;
isotr.SetIdentityTransformation(geom);
localRVO.SetSize(ldof, ldof);
isotr.SetPointMat(pmats(emb.matrix));
// Local restriction is size ldofxldof assuming that the parent
// and child are of same polynomial order.
fe->GetLocalRestriction(isotr, localRVO);
// copy block
auto size = localRVO.Height() * localRVO.Width();
std::copy(localRVO.Data(), localRVO.Data() + size, bs_ptr);
bs_ptr += size;
}
DenseMatrix &lR =
fespace->IsVariableOrder() ? localRVO : localR[geom](emb.matrix);
block_row_idcs_offsets[k + 1] =
block_row_idcs_offsets[k] + lR.Height();
block_col_idcs_offsets[k + 1] = block_col_idcs_offsets[k] + lR.Width();
max_rows = std::max(lR.Height(), max_rows);
// index information
fespace->elem_dof->GetRow(emb.parent, dofs);
old_elem_dof->GetRow(k, old_dofs);
MFEM_VERIFY(old_dofs.Size() == dofs.Size(),
"Parent and child must have same #dofs.");
for (int i = 0; i < lR.Height(); ++i, ++ridx)
{
if (!std::isfinite(lR(i, 0)))
{
row_idcs[ridx] = INT_MAX;
continue;
}
int r = dofs[i];
int m = (r >= 0) ? r : (-1 - r);
if (is_dg || !mark[m])
{
row_idcs[ridx] = r;
mark[m] = 1;
++num_marked;
}
else
{
row_idcs[ridx] = INT_MAX;
}
}
for (int i = 0; i < lR.Width(); ++i, ++cidx)
{
col_idcs[cidx] = old_dofs[i];
}
}
if (!is_dg && !fespace->IsVariableOrder())
{
MFEM_VERIFY(num_marked * fespace->GetVDim() == Height(),
"internal error: not all rows were set.");
}
}
// if not using GPU, set max_rows/max_cols to zero
if (Device::Allows(Backend::DEVICE_MASK))
{
max_rows = std::min(max_rows, max_team_size);
}
else
{
max_rows = 1;
}
}
void DerefineMatrixOp::Mult(const Vector &x, Vector &y) const
{
const bool is_dg = fespace->FEColl()->GetContType()
== FiniteElementCollection::DISCONTINUOUS;
// DG needs atomic summation
MultKernel::Run(fespace->GetOrdering(), is_dg, *this, x, y);
}
DerefineMatrixOp::Kernels::Kernels()
{
MultKernel::Specialization<Ordering::byNODES, false>::Add();
MultKernel::Specialization<Ordering::byVDIM, false>::Add();
MultKernel::Specialization<Ordering::byNODES, true>::Add();
MultKernel::Specialization<Ordering::byVDIM, true>::Add();
}
template <Ordering::Type Order, bool Atomic>
DerefineMatrixOp::MultKernelType DerefineMatrixOp::MultKernel::Kernel()
{
return internal::DerefMultKernelImpl<Order, Atomic>;
}
DerefineMatrixOp::MultKernelType
DerefineMatrixOp::MultKernel::Fallback(Ordering::Type, bool)
{
MFEM_ABORT("invalid MultKernel parameters");
}
} // namespace mfem
/// \endcond DO_NOT_DOCUMENT
+65
View File
@@ -0,0 +1,65 @@
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#ifndef MFEM_DEREFMAT_OP
#define MFEM_DEREFMAT_OP
#include "fespace.hpp"
#include "kernel_dispatch.hpp"
namespace mfem
{
/// \cond DO_NOT_DOCUMENT
struct DerefineMatrixOp : public Operator
{
FiniteElementSpace *fespace;
/// offsets into block_storage
Array<int> block_offsets;
/// offsets into row_idcs
Array<int> block_row_idcs_offsets;
/// offsets into col_idcs
Array<int> block_col_idcs_offsets;
/// mapping for row dofs, INT_MAX indicates the block row should be ignored.
/// negative means the row data should be negated.
Array<int> row_idcs;
/// mapping for col dofs, negative means the col data should be negated.
Array<int> col_idcs;
/// dense block matrices which can be reused to construct the full matrix
/// operation. These are stored contiguously and blocks have no restrictions
/// on shape (can be rectangle and differ from block to block).
Vector block_storage;
/// maximum height of any block in block_storage for GPU
/// parallelization, or 1 for CPU runs.
int max_rows;
using MultKernelType = void (*)(const DerefineMatrixOp &, const Vector &,
Vector &);
/// template args: ordering, atomic
MFEM_REGISTER_KERNELS(MultKernel, MultKernelType, (Ordering::Type, bool));
struct Kernels
{
Kernels();
};
void Mult(const Vector &x, Vector &y) const;
DerefineMatrixOp(FiniteElementSpace &fespace_, int old_ndofs,
const Table *old_elem_dof, const Table *old_elem_fos);
};
/// \endcond DO_NOT_DOCUMENT
} // namespace mfem
#endif
+1
View File
@@ -241,6 +241,7 @@ public:
{
MFEM_ASSERT(!action_callbacks.empty(), "no integrators have been set");
prolongation(solutions, solutions_t, solutions_l);
residual_l = 0.0;
for (auto &action : action_callbacks)
{
action(solutions_l, parameters_l, residual_l);
+6 -6
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)
@@ -568,7 +568,7 @@ struct ThreadBlocks
int z = 1;
};
#if (defined(MFEM_USE_CUDA) || defined(MFEM_USE_HIP))
#if defined(MFEM_USE_CUDA_OR_HIP)
template <typename func_t>
__global__ void forall_kernel_shmem(func_t f, int n)
{
@@ -591,7 +591,7 @@ void forall(func_t f,
if (Device::Allows(Backend::CUDA_MASK) ||
Device::Allows(Backend::HIP_MASK))
{
#if (defined(MFEM_USE_CUDA) || defined(MFEM_USE_HIP))
#if defined(MFEM_USE_CUDA_OR_HIP)
// int gridsize = (N + Z - 1) / Z;
int num_bytes = num_shmem * sizeof(decltype(shmem));
dim3 block_size(blocks.x, blocks.y, blocks.z);
@@ -987,7 +987,7 @@ get_restriction_transpose(
{
auto RT = [=](const Vector &v_e, Vector &v_l)
{
v_l = v_e;
v_l += v_e;
};
return std::make_tuple(RT, 1);
}
@@ -996,7 +996,7 @@ get_restriction_transpose(
const Operator *R = get_restriction<entity_t>(f, o);
std::function<void(const Vector&, Vector&)> RT = [=](const Vector &x, Vector &y)
{
R->MultTranspose(x, y);
R->AddMultTranspose(x, y);
};
return std::make_tuple(RT, R->Height());
}
+249
View File
@@ -0,0 +1,249 @@
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#ifndef MFEM_FES_KERNELS_HPP
#define MFEM_FES_KERNELS_HPP
#include "../general/forall.hpp"
#include <climits>
namespace mfem
{
/// \cond DO_NOT_DOCUMENT
namespace internal
{
///
/// Implements matrix-vector multiply $y = A x$ for a sparse matrix composed of
/// a sum of smaller dense blocks. There is additional permutation/sign
/// information associated with each block. The base class only implements
/// helper routines such as computing block widths, index into x, index into y,
/// and column in A given sub-block information.
/// @sa DerefineMatrixOpMultFunctor
///
/// @tparam Order vdim ordering for x and y. Note that for Diag = false this is
/// ignored for x as x has a special interleaved order.
/// @tparam Base used for the curious recurring template pattern (CRTP) so the
/// base class can access child class fields without virtual functions
/// @tparam Diag true if this corresponds to the diagonal block (coarse element
/// and fine element are on our rank), false otherwise (coarse element is on our
/// rank, fine element is on a different rank).
///
template <Ordering::Type Order, class Base, bool Diag = true>
struct DerefineMatrixOpFunctorBase;
template <class Base>
struct DerefineMatrixOpFunctorBase<Ordering::byNODES, Base, true>
{
/// block column indices offsets
const int *bcptr;
/// column indices
const int *cptr;
int MFEM_HOST_DEVICE BlockWidth(int k) const
{
return bcptr[k + 1] - bcptr[k];
}
void MFEM_HOST_DEVICE Col(int j, int k, int &col, int &sign) const
{
col = cptr[bcptr[k] + j];
if (col < 0)
{
col = -1 - col;
sign = -sign;
}
}
int MFEM_HOST_DEVICE IndexX(int col, int vdim, int) const
{
return col + vdim * static_cast<const Base *>(this)->width;
}
int MFEM_HOST_DEVICE IndexY(int row, int vdim) const
{
return row + vdim * static_cast<const Base *>(this)->height;
}
};
template <class Base>
struct DerefineMatrixOpFunctorBase<Ordering::byVDIM, Base, true>
{
/// block column indices offsets
const int *bcptr;
/// column indices
const int *cptr;
int MFEM_HOST_DEVICE BlockWidth(int k) const
{
return bcptr[k + 1] - bcptr[k];
}
void MFEM_HOST_DEVICE Col(int j, int k, int &col, int &sign) const
{
col = cptr[bcptr[k] + j];
if (col < 0)
{
col = -1 - col;
sign = -sign;
}
}
int MFEM_HOST_DEVICE IndexX(int col, int vdim, int) const
{
return vdim + col * static_cast<const Base *>(this)->vdims;
}
int MFEM_HOST_DEVICE IndexY(int row, int vdim) const
{
return vdim + row * static_cast<const Base *>(this)->vdims;
}
};
template <class Base>
struct DerefineMatrixOpFunctorBase<Ordering::byNODES, Base, false>
{
/// receive segment offsets
const int *segptr;
/// receive segment index
const int *rsptr;
/// off-diagonal block column offsets
const int *coptr;
/// off-diagonal block widths
const int *bwptr;
int MFEM_HOST_DEVICE BlockWidth(int k) const { return bwptr[k]; }
void MFEM_HOST_DEVICE Col(int j, int k, int &col, int &sign) const
{
col = coptr[k] + j;
}
int MFEM_HOST_DEVICE IndexX(int col, int vdim, int k) const
{
int tmp = rsptr[k];
int segwidth = segptr[tmp + 1] - segptr[tmp];
return segptr[tmp] * static_cast<const Base *>(this)->vdims + col +
vdim * segwidth;
}
int MFEM_HOST_DEVICE IndexY(int row, int vdim) const
{
return row + vdim * static_cast<const Base *>(this)->height;
}
};
template <class Base>
struct DerefineMatrixOpFunctorBase<Ordering::byVDIM, Base, false>
{
/// receive segment offsets
const int *segptr;
/// receive segment index
const int *rsptr;
/// off-diagonal block column offsets
const int *coptr;
/// off-diagonal block widths
const int *bwptr;
int MFEM_HOST_DEVICE BlockWidth(int k) const { return bwptr[k]; }
void MFEM_HOST_DEVICE Col(int j, int k, int &col, int &sign) const
{
col = coptr[k] + j;
}
int MFEM_HOST_DEVICE IndexX(int col, int vdim, int k) const
{
int tmp = rsptr[k];
int segwidth = segptr[tmp + 1] - segptr[tmp];
return segptr[tmp] * static_cast<const Base *>(this)->vdims + col +
vdim * segwidth;
}
int MFEM_HOST_DEVICE IndexY(int row, int vdim) const
{
return vdim + row * static_cast<const Base *>(this)->vdims;
}
};
/// internally used to implement the derefinement operator Mult diagonal
/// block
template <Ordering::Type Order, bool Atomic, bool Diag = true>
struct DerefineMatrixOpMultFunctor
: public DerefineMatrixOpFunctorBase<
Order, DerefineMatrixOpMultFunctor<Order, Atomic, Diag>, Diag>
{
const real_t *xptr;
real_t *yptr;
/// block storage
const real_t *bsptr;
/// block offsets
const int *boptr;
/// block row index offsets
const int *brptr;
/// row indices
const int *rptr;
// number of blocks
int nblocks;
// number of components
int vdims;
/// overall operator height (for vdim = 1)
int height;
/// overall operator width (for vdim = 1)
int width;
void MFEM_HOST_DEVICE operator()(int kidx) const
{
int k = kidx % nblocks;
int vdim = kidx / nblocks;
int block_height = brptr[k + 1] - brptr[k];
int block_width = this->BlockWidth(k);
MFEM_FOREACH_THREAD(i, x, block_height)
{
int row = rptr[brptr[k] + i];
int rsign = 1;
if (row < 0)
{
row = -1 - row;
rsign = -1;
}
if (row < INT_MAX)
{
// row not marked as unused
real_t sum = 0;
for (int j = 0; j < block_width; ++j)
{
int col, sign = rsign;
this->Col(j, k, col, sign);
sum += sign * bsptr[boptr[k] + i + j * block_height] *
xptr[this->IndexX(col, vdim, k)];
}
#if defined(__CUDA_ARCH__) or defined(__HIP_DEVICE_COMPILE__)
if (Atomic)
{
atomicAdd(yptr + this->IndexY(row, vdim), sum);
}
else
#endif
{
yptr[this->IndexY(row, vdim)] += sum;
}
}
}
}
/// N is the max block row size (doesn't have to be a power of 2)
void Run(int N) const { forall_2D(nblocks * vdims, N, 1, *this); }
};
} // namespace internal
/// \endcond DO_NOT_DOCUMENT
} // namespace mfem
#endif
+13 -6
View File
@@ -17,6 +17,9 @@
#include "fem.hpp"
#include "ceed/interface/util.hpp"
#include "derefmat_op.hpp"
#include <algorithm>
#include <cmath>
#include <cstdarg>
@@ -24,9 +27,9 @@ using namespace std;
namespace mfem
{
template <> void Ordering::
DofsToVDofs<Ordering::byNODES>(int ndofs, int vdim, Array<int> &dofs)
template <>
void Ordering::DofsToVDofs<Ordering::byNODES>(int ndofs, int vdim,
Array<int> &dofs)
{
// static method
int size = dofs.Size();
@@ -40,8 +43,9 @@ DofsToVDofs<Ordering::byNODES>(int ndofs, int vdim, Array<int> &dofs)
}
}
template <> void Ordering::
DofsToVDofs<Ordering::byVDIM>(int ndofs, int vdim, Array<int> &dofs)
template <>
void Ordering::DofsToVDofs<Ordering::byVDIM>(int ndofs, int vdim,
Array<int> &dofs)
{
// static method
int size = dofs.Size();
@@ -55,7 +59,6 @@ DofsToVDofs<Ordering::byVDIM>(int ndofs, int vdim, Array<int> &dofs)
}
}
FiniteElementSpace::FiniteElementSpace()
: mesh(NULL), fec(NULL), vdim(0), ordering(Ordering::byNODES),
ndofs(0), nvdofs(0), nedofs(0), nfdofs(0), nbdofs(0),
@@ -4244,7 +4247,11 @@ void FiniteElementSpace::Update(bool want_transform)
case Mesh::DEREFINE:
{
BuildConformingInterpolation();
#if 0
Th.Reset(DerefinementMatrix(old_ndofs, old_elem_dof, old_elem_fos));
#else
Th.Reset(new DerefineMatrixOp(*this, old_ndofs, old_elem_dof, old_elem_fos));
#endif
if (IsVariableOrder())
{
if (cP && cR_hp)
+2 -1
View File
@@ -113,7 +113,7 @@ class QuadratureSpace;
class QuadratureInterpolator;
class FaceQuadratureInterpolator;
class PRefinementTransferOperator;
struct DerefineMatrixOp;
/** @brief Class FiniteElementSpace - responsible for providing FEM view of the
mesh, mainly managing the set of degrees of freedom.
@@ -246,6 +246,7 @@ class FiniteElementSpace
friend class PRefinementTransferOperator;
friend void Mesh::Swap(Mesh &, bool);
friend class LORBase;
friend struct DerefineMatrixOp;
protected:
/// The mesh that FE space lives on (not owned).
+11 -4
View File
@@ -139,8 +139,6 @@ void DGTraceIntegrator::SetupPA(const FiniteElementSpace &fes, FaceType type)
const MemoryType mt = (pa_mt == MemoryType::DEFAULT) ?
Device::GetDeviceMemoryType() : pa_mt;
nf = fes.GetNFbyType(type);
if (nf==0) { return; }
// Assumes tensor-product elements
Mesh *mesh = fes.GetMesh();
const FiniteElement &el = *fes.GetTypicalTraceElement();
@@ -148,6 +146,17 @@ void DGTraceIntegrator::SetupPA(const FiniteElementSpace &fes, FaceType type)
IntRule:
&GetRule(el.GetGeomType(), el.GetOrder(),
*mesh->GetTypicalElementTransformation());
if (!qspace[static_cast<int>(type)])
{
qspace[static_cast<int>(type)].reset(
new FaceQuadratureSpace(*mesh, *ir, type));
}
FaceQuadratureSpace& qs = *qspace[static_cast<int>(type)];
nf = qs.GetNumFaces();
if (nf==0) { return; }
const int symmDims = 4;
nq = ir->GetNPoints();
dim = mesh->Dimension();
@@ -159,8 +168,6 @@ void DGTraceIntegrator::SetupPA(const FiniteElementSpace &fes, FaceType type)
dofs1D = maps->ndof;
quad1D = maps->nqpt;
pa_data.SetSize(symmDims * nq * nf, Device::GetMemoryType());
FaceQuadratureSpace qs(*mesh, *ir, type);
CoefficientVector vel(*u, qs, CoefficientStorage::COMPRESSED);
CoefficientVector r(qs, CoefficientStorage::COMPRESSED);
+10 -7
View File
@@ -1212,18 +1212,22 @@ inline void SmemPADiffusionApply3D(const int NE,
} // namespace internal
namespace
{
using ApplyKernelType = DiffusionIntegrator::ApplyKernelType;
using DiagonalKernelType = DiffusionIntegrator::DiagonalKernelType;
}
template<int DIM, int T_D1D, int T_Q1D>
DiffusionIntegrator::ApplyKernelType
DiffusionIntegrator::ApplyPAKernels::Kernel()
ApplyKernelType DiffusionIntegrator::ApplyPAKernels::Kernel()
{
if (DIM == 2) { return internal::SmemPADiffusionApply2D<T_D1D,T_Q1D>; }
else if (DIM == 3) { return internal::SmemPADiffusionApply3D<T_D1D, T_Q1D>; }
else { MFEM_ABORT(""); }
}
inline DiffusionIntegrator::ApplyKernelType
DiffusionIntegrator::ApplyPAKernels::Fallback(int DIM, int, int)
inline
ApplyKernelType DiffusionIntegrator::ApplyPAKernels::Fallback(int DIM, int, int)
{
if (DIM == 2) { return internal::PADiffusionApply2D; }
else if (DIM == 3) { return internal::PADiffusionApply3D; }
@@ -1231,15 +1235,14 @@ DiffusionIntegrator::ApplyPAKernels::Fallback(int DIM, int, int)
}
template<int DIM, int D1D, int Q1D>
DiffusionIntegrator::DiagonalKernelType
DiffusionIntegrator::DiagonalPAKernels::Kernel()
DiagonalKernelType DiffusionIntegrator::DiagonalPAKernels::Kernel()
{
if (DIM == 2) { return internal::SmemPADiffusionDiagonal2D<D1D,Q1D>; }
else if (DIM == 3) { return internal::SmemPADiffusionDiagonal3D<D1D, Q1D>; }
else { MFEM_ABORT(""); }
}
inline DiffusionIntegrator::DiagonalKernelType
inline DiagonalKernelType
DiffusionIntegrator::DiagonalPAKernels::Fallback(int DIM, int, int)
{
if (DIM == 2) { return internal::PADiffusionDiagonal2D; }
-4
View File
@@ -39,8 +39,6 @@ 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);
@@ -90,8 +88,6 @@ 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
+11 -6
View File
@@ -1383,9 +1383,14 @@ inline void EAMassAssemble3D(const int NE,
} // namespace internal
namespace
{
using ApplyKernelType = MassIntegrator::ApplyKernelType;
using DiagonalKernelType = MassIntegrator::DiagonalKernelType;
}
template<int DIM, int T_D1D, int T_Q1D>
MassIntegrator::ApplyKernelType MassIntegrator::ApplyPAKernels::Kernel()
ApplyKernelType MassIntegrator::ApplyPAKernels::Kernel()
{
if (DIM == 1) { return internal::PAMassApply1D; }
else if (DIM == 2) { return internal::SmemPAMassApply2D<T_D1D,T_Q1D>; }
@@ -1393,8 +1398,8 @@ MassIntegrator::ApplyKernelType MassIntegrator::ApplyPAKernels::Kernel()
else { MFEM_ABORT(""); }
}
inline MassIntegrator::ApplyKernelType
MassIntegrator::ApplyPAKernels::Fallback(int DIM, int, int)
inline ApplyKernelType MassIntegrator::ApplyPAKernels::Fallback(
int DIM, int, int)
{
if (DIM == 1) { return internal::PAMassApply1D; }
else if (DIM == 2) { return internal::PAMassApply2D; }
@@ -1403,7 +1408,7 @@ MassIntegrator::ApplyPAKernels::Fallback(int DIM, int, int)
}
template<int DIM, int T_D1D, int T_Q1D>
MassIntegrator::DiagonalKernelType MassIntegrator::DiagonalPAKernels::Kernel()
DiagonalKernelType MassIntegrator::DiagonalPAKernels::Kernel()
{
if (DIM == 1) { return internal::PAMassAssembleDiagonal1D; }
else if (DIM == 2) { return internal::SmemPAMassAssembleDiagonal2D<T_D1D,T_Q1D>; }
@@ -1411,8 +1416,8 @@ MassIntegrator::DiagonalKernelType MassIntegrator::DiagonalPAKernels::Kernel()
else { MFEM_ABORT(""); }
}
inline MassIntegrator::DiagonalKernelType
MassIntegrator::DiagonalPAKernels::Fallback(int DIM, int, int)
inline DiagonalKernelType MassIntegrator::DiagonalPAKernels::Fallback(
int DIM, int, int)
{
if (DIM == 1) { return internal::PAMassAssembleDiagonal1D; }
else if (DIM == 2) { return internal::PAMassAssembleDiagonal2D; }
+25 -60
View File
@@ -23,8 +23,6 @@ namespace mfem
void MassIntegrator::AssemblePA(const FiniteElementSpace &fes)
{
MFEM_PERF_FUNCTION;
const MemoryType mt = (pa_mt == MemoryType::DEFAULT) ?
Device::GetDeviceMemoryType() : pa_mt;
@@ -61,26 +59,23 @@ void MassIntegrator::AssemblePA(const FiniteElementSpace &fes)
QuadratureSpace qs(*mesh, *ir);
CoefficientVector coeff(Q, qs, CoefficientStorage::COMPRESSED);
const int NE = ne;
const int Q1D = quad1D;
const int NQ = static_cast<int>(std::pow(Q1D, dim));
const bool const_c = coeff.Size() == 1;
const bool by_val = map_type == FiniteElement::VALUE;
const auto W = Reshape(ir->GetWeights().Read(), NQ);
const auto J = Reshape(geom->detJ.Read(), NQ, NE);
const auto C = const_c ? Reshape(coeff.Read(), 1, 1) :
Reshape(coeff.Read(), NQ,NE);
auto v = Reshape(pa_data.Write(), NQ, NE);
mfem::forall_2D(NE, NQ, 1, [=] MFEM_HOST_DEVICE (int e)
{
MFEM_FOREACH_THREAD(i, x, NQ)
const int NE = ne;
const int NQ = nq;
const bool const_c = coeff.Size() == 1;
const bool by_val = map_type == FiniteElement::VALUE;
const auto W = Reshape(ir->GetWeights().Read(), NQ);
const auto J = Reshape(geom->detJ.Read(), NQ, NE);
const auto C =
const_c ? Reshape(coeff.Read(), 1, 1) : Reshape(coeff.Read(), NQ, NE);
auto v = Reshape(pa_data.Write(), NQ, NE);
mfem::forall(NQ, NE, [=] MFEM_HOST_DEVICE(int q, int e)
{
const real_t detJ = J(i,e);
const real_t coeff = const_c ? C(0,0) : C(i,e);
v(i,e) = W(i) * coeff * (by_val ? detJ : 1.0/detJ);
}
});
const real_t detJ = J(q, e);
const real_t coeff = const_c ? C(0, 0) : C(q, e);
v(q, e) = W(q) * coeff * (by_val ? detJ : 1.0 / detJ);
});
}
}
void MassIntegrator::AssemblePABoundary(const FiniteElementSpace &fes)
@@ -111,50 +106,22 @@ void MassIntegrator::AssemblePABoundary(const FiniteElementSpace &fes)
CoefficientVector coeff(Q, qs, CoefficientStorage::COMPRESSED);
const int NE = ne;
const int Q1D = quad1D;
const int NQ = nq;
const bool const_c = coeff.Size() == 1;
const bool by_val = map_type == FiniteElement::VALUE;
if (dim==1)
{
const auto W = Reshape(ir->GetWeights().Read(), Q1D);
const auto J = Reshape(face_geom->detJ.Read(), Q1D, NE);
const auto C = const_c ? Reshape(coeff.Read(), 1, 1) :
Reshape(coeff.Read(), Q1D, NE);
auto v = Reshape(pa_data.Write(), Q1D, NE);
mfem::forall_2D(NE, Q1D, 1, [=] MFEM_HOST_DEVICE (int e)
const auto W = Reshape(ir->GetWeights().Read(), NQ);
const auto J = Reshape(face_geom->detJ.Read(), NQ, NE);
const auto C = const_c ? Reshape(coeff.Read(), 1, 1)
: Reshape(coeff.Read(), NQ, NE);
auto v = Reshape(pa_data.Write(), NQ, NE);
mfem::forall(NQ, NE, [=] MFEM_HOST_DEVICE(int q, int e)
{
MFEM_FOREACH_THREAD(qx,x,Q1D)
{
const real_t detJ = J(qx,e);
const real_t coeff = const_c ? C(0,0) : C(qx,e);
v(qx,e) = W(qx) * coeff * (by_val ? detJ : 1.0/detJ);
}
const real_t detJ = J(q, e);
const real_t coeff = const_c ? C(0, 0) : C(q, e);
v(q, e) = W(q) * coeff * (by_val ? detJ : 1.0 / detJ);
});
}
else if (dim==2)
{
const auto W = Reshape(ir->GetWeights().Read(), Q1D,Q1D);
const auto J = Reshape(face_geom->detJ.Read(), Q1D,Q1D,NE);
const auto C = const_c ? Reshape(coeff.Read(), 1,1,1) :
Reshape(coeff.Read(), Q1D,Q1D,NE);
auto v = Reshape(pa_data.Write(), Q1D,Q1D, NE);
mfem::forall_2D(NE, Q1D, Q1D, [=] MFEM_HOST_DEVICE (int e)
{
MFEM_FOREACH_THREAD(qx,x,Q1D)
{
MFEM_FOREACH_THREAD(qy,y,Q1D)
{
const real_t detJ = J(qx,qy,e);
const real_t coeff = const_c ? C(0,0,0) : C(qx,qy,e);
v(qx,qy,e) = W(qx,qy) * coeff * (by_val ? detJ : 1.0/detJ);
}
}
});
}
else
{
MFEM_ABORT("Not supported.");
}
}
void MassIntegrator::AssembleDiagonalPA(Vector &diag)
@@ -172,8 +139,6 @@ void MassIntegrator::AssembleDiagonalPA(Vector &diag)
void MassIntegrator::AddMultPA(const Vector &x, Vector &y) const
{
MFEM_PERF_FUNCTION;
if (DeviceCanUseCeed())
{
ceedOp->AddMult(x, y);
+29 -29
View File
@@ -346,13 +346,13 @@ private:
template<typename T>
T operator() (const blitz::TinyVector<T,3>& x) const
{
int el_order=el->GetOrder();
T u1[el_order+1];
T u2[el_order+1];
T u3[el_order+1];
TmplPoly_1D::CalcBernstein(el_order, x[0], u1);
TmplPoly_1D::CalcBernstein(el_order, x[1], u2);
TmplPoly_1D::CalcBernstein(el_order, x[2], u3);
const int el_order = el->GetOrder();
std::vector<T> u1(el_order+1);
std::vector<T> u2(el_order+1);
std::vector<T> u3(el_order+1);
TmplPoly_1D::CalcBernstein(el_order, x[0], u1.data());
TmplPoly_1D::CalcBernstein(el_order, x[1], u2.data());
TmplPoly_1D::CalcBernstein(el_order, x[2], u3.data());
const Array<int>& dof_map=el->GetDofMap();
@@ -370,17 +370,17 @@ private:
template<typename T>
blitz::TinyVector<T,3> grad(const blitz::TinyVector<T,3>& x) const
{
int el_order=el->GetOrder();
T u1[el_order+1];
T u2[el_order+1];
T u3[el_order+1];
T d1[el_order+1];
T d2[el_order+1];
T d3[el_order+1];
const int el_order = el->GetOrder();
std::vector<T> u1(el_order+1);
std::vector<T> u2(el_order+1);
std::vector<T> u3(el_order+1);
std::vector<T> d1(el_order+1);
std::vector<T> d2(el_order+1);
std::vector<T> d3(el_order+1);
TmplPoly_1D::CalcBernstein(el_order,x[0], u1, d1);
TmplPoly_1D::CalcBernstein(el_order,x[1], u2, d2);
TmplPoly_1D::CalcBernstein(el_order,x[2], u3, d3);
TmplPoly_1D::CalcBernstein(el_order,x[0], u1.data(), d1.data());
TmplPoly_1D::CalcBernstein(el_order,x[1], u2.data(), d2.data());
TmplPoly_1D::CalcBernstein(el_order,x[2], u3.data(), d3.data());
blitz::TinyVector<T,3> res(T(0.0),T(0.0),T(0.0));
@@ -415,11 +415,11 @@ private:
template<typename T>
T operator() (const blitz::TinyVector<T,2>& x) const
{
int el_order=el->GetOrder();
T u1[el_order+1];
T u2[el_order+1];
TmplPoly_1D::CalcBernstein(el_order, x[0], u1);
TmplPoly_1D::CalcBernstein(el_order, x[1], u2);
const int el_order = el->GetOrder();
std::vector<T> u1(el_order+1);
std::vector<T> u2(el_order+1);
TmplPoly_1D::CalcBernstein(el_order, x[0], u1.data());
TmplPoly_1D::CalcBernstein(el_order, x[1], u2.data());
const Array<int>& dof_map=el->GetDofMap();
@@ -437,14 +437,14 @@ private:
template<typename T>
blitz::TinyVector<T,2> grad(const blitz::TinyVector<T,2>& x) const
{
int el_order=el->GetOrder();
T u1[el_order+1];
T u2[el_order+1];
T d1[el_order+1];
T d2[el_order+1];
const int el_order = el->GetOrder();
std::vector<T> u1(el_order+1);
std::vector<T> u2(el_order+1);
std::vector<T> d1(el_order+1);
std::vector<T> d2(el_order+1);
TmplPoly_1D::CalcBernstein(el_order,x[0], u1, d1);
TmplPoly_1D::CalcBernstein(el_order,x[1], u2, d2);
TmplPoly_1D::CalcBernstein(el_order,x[0], u1.data(), d1.data());
TmplPoly_1D::CalcBernstein(el_order,x[1], u2.data(), d2.data());
blitz::TinyVector<T,2> res(T(0.0),T(0.0));
+10 -32
View File
@@ -51,7 +51,7 @@ void LinearFormExtension::Assemble()
{
// scan the attributes to set the markers to 0 or 1
const int NE = fes.GetNE();
const auto attr = attributes.Read();
const auto attr = attributes->Read();
const auto dimk = domain_integs_marker_k->Read();
auto markers_w = markers.Write();
mfem::forall(NE, [=] MFEM_HOST_DEVICE (int e)
@@ -93,13 +93,14 @@ void LinearFormExtension::Assemble()
else
{
// scan the attributes to set the markers to 0 or 1
const int NBE = bdr_attributes.Size();
const auto attr = bdr_attributes.Read();
const int NBE = bdr_face_attributes->Size();
const auto attr = bdr_face_attributes->Read();
const auto attr_markers = boundary_integs_marker_k->Read();
auto markers_w = bdr_markers.Write();
mfem::forall(NBE, [=] MFEM_HOST_DEVICE (int e)
mfem::forall(NBE, [=] MFEM_HOST_DEVICE(int e)
{
markers_w[e] = attr_markers[attr[e]-1] == 1;
markers_w[e] =
attr[e] > 0 ? (attr_markers[attr[e] - 1] == 1) : false;
});
}
@@ -125,8 +126,7 @@ void LinearFormExtension::Update()
//markers.UseDevice(true);
// Gather the attributes on the host from all the elements
attributes.SetSize(NE);
for (int i = 0; i < NE; ++i) { attributes[i] = mesh.GetAttribute(i); }
attributes = &mesh.GetElementAttributes();
elem_restrict_lex = fes.GetElementRestriction(ordering);
MFEM_VERIFY(elem_restrict_lex, "Element restriction not available");
@@ -136,34 +136,12 @@ void LinearFormExtension::Update()
if (lf->boundary_integs.Size() > 0)
{
const int nf_bdr = fes.GetNFbyType(FaceType::Boundary);
bdr_face_attributes = &mesh.GetBdrFaceAttributes();
const int nf_bdr = bdr_face_attributes->Size();
bdr_markers.SetSize(nf_bdr);
// bdr_markers.UseDevice(true);
// The face restriction will give us "face E-vectors" on the boundary that
// are numbered in the order of the faces of mesh. This numbering will be
// different than the numbering of the boundary elements. We compute
// mappings so that the array `bdr_attributes[i]` gives the boundary
// attribute of the `i`th boundary face in the mesh face order.
std::unordered_map<int,int> f_to_be;
for (int i = 0; i < mesh.GetNBE(); ++i)
{
const int f = mesh.GetBdrElementFaceIndex(i);
f_to_be[f] = i;
}
MFEM_VERIFY(size_t(nf_bdr) == f_to_be.size(), "Incompatible sizes");
bdr_attributes.SetSize(nf_bdr);
int f_ind = 0;
for (int f = 0; f < mesh.GetNumFaces(); ++f)
{
if (f_to_be.find(f) != f_to_be.end())
{
const int be = f_to_be[f];
bdr_attributes[f_ind] = mesh.GetBdrAttribute(be);
++f_ind;
}
}
bdr_restrict_lex =
dynamic_cast<const FaceRestriction*>(
fes.GetFaceRestriction(ordering, FaceType::Boundary,
+2 -2
View File
@@ -14,7 +14,6 @@
#include "../general/array.hpp"
#include "../linalg/vector.hpp"
#include "fespace.hpp"
namespace mfem
{
@@ -26,7 +25,8 @@ class LinearForm;
class LinearFormExtension
{
/// Attributes of all mesh elements.
Array<int> attributes, bdr_attributes;
const Array<int> *attributes; // Not owned
const Array<int> *bdr_face_attributes; // Not owned
/// Temporary markers for device kernels.
Array<int> markers, bdr_markers;
-2
View File
@@ -365,8 +365,6 @@ 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,8 +360,6 @@ 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
@@ -419,8 +417,6 @@ 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,8 +184,6 @@ 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;
+54 -132
View File
@@ -17,10 +17,7 @@ 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_,
@@ -32,18 +29,12 @@ 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])
@@ -65,17 +56,16 @@ void MultigridBase::InitVectors() const
X.SetSize(M, nrhs);
Y.SetSize(M, nrhs);
R.SetSize(M, nrhs);
for (int i = 0; i < M; ++i)
Z.SetSize(M, nrhs);
for (int i = 0; i < X.NumRows(); ++i)
{
const int n = operators[i]->Height();
for (int j = 0; j < nrhs; ++j)
for (int j = 0; j < X.NumCols(); ++j)
{
if (i < M - 1)
{
X(i, j) = new Vector(n);
Y(i, j) = new Vector(n);
}
X(i, j) = new Vector(n);
Y(i, j) = new Vector(n);
R(i, j) = new Vector(n);
Z(i, j) = new Vector(n);
}
}
}
@@ -86,12 +76,10 @@ void MultigridBase::EraseVectors() const
{
for (int j = 0; j < X.NumCols(); ++j)
{
if (i < X.NumRows() - 1)
{
delete X(i, j);
delete Y(i, j);
}
delete X(i, j);
delete Y(i, j);
delete R(i, j);
delete Z(i, j);
}
}
}
@@ -107,12 +95,6 @@ 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_)
{
@@ -123,12 +105,10 @@ void MultigridBase::SetCycleType(CycleType cycleType_, int preSmoothingSteps_,
void MultigridBase::Mult(const Vector& x, Vector& y) const
{
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
Array<const Vector*> X_(1);
Array<Vector*> Y_(1);
X_[0] = &x;
Y_[0] = &y;
ArrayMult(X_, Y_);
}
@@ -139,6 +119,11 @@ void MultigridBase::ArrayMult(const Array<const Vector*>& X_,
"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();
@@ -149,159 +134,96 @@ 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) = const_cast<Vector*>(X_[j]);
Y(M - 1, j) = Y_[j];
*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);
}
const bool zero = !iterative_mode;
Cycle(M - 1, zero);
}
void MultigridBase::SmoothingStep(int level, bool zero, bool transpose) const
{
// 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)
{
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_);
Array<Vector *> X_(X[level], nrhs), Y_(Y[level], nrhs);
GetSmootherAtLevel(level)->ArrayMult(X_, Y_);
}
else
{
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_);
Array<Vector *> Y_(Y[level], nrhs), R_(R[level], nrhs),
Z_(Z[level], nrhs);
for (int j = 0; j < nrhs; ++j)
{
// *R_[j] = *X(level, j) - *R_[j]
subtract(*X(level, j), *R_[j], *R_[j]);
*R_[j] = *X(level, j);
}
GetOperatorAtLevel(level)->ArrayAddMult(Y_, R_, -1.0);
if (transpose)
{
GetSmootherAtLevel(level)->ArrayAddMultTranspose(cR_, Y_);
GetSmootherAtLevel(level)->ArrayMultTranspose(R_, Z_);
}
else
{
GetSmootherAtLevel(level)->ArrayAddMult(cR_, Y_);
GetSmootherAtLevel(level)->ArrayMult(R_, Z_);
}
}
}
void MultigridBase::CoarseSolve(bool zero) const
{
// See the comment about iterative mode in SmoothingStep()
coarse_solver->iterative_mode = false;
if (zero)
{
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]);
*Y_[j] += *Z_[j];
}
coarse_solver->ArrayAddMult(cR_, Y_);
}
}
void MultigridBase::Cycle(int level, bool zero) const
void MultigridBase::Cycle(int level) 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)
if (level == 0)
{
SmoothingStep(0, zero, false);
SmoothingStep(0, true, false);
return;
}
// Pre-smooth
for (int i = 0; i < preSmoothingSteps; ++i)
{
SmoothingStep(level, zero && (i == 0), false);
}
// Coarse solve with 'coarse_solver'
if (level == 0)
{
CoarseSolve(preSmoothingSteps == 0 && zero);
goto mg_post_smooth;
SmoothingStep(level, (cycleType == CycleType::VCYCLE && i == 0), false);
}
// Compute residual and restrict
if (preSmoothingSteps == 0 && zero)
{
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_);
Array<Vector *> Y_(Y[level], nrhs), R_(R[level], nrhs),
X_(X[level - 1], nrhs);
for (int j = 0; j < nrhs; ++j)
{
// *R_[j] = *X(level, j) - *R_[j]
subtract(*X(level, j), *R_[j], *R_[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;
}
GetProlongationAtLevel(level - 1)->ArrayMultTranspose(cR_, X_);
}
// Corrections
Cycle(level - 1, true);
Cycle(level - 1);
if (cycleType == CycleType::WCYCLE)
{
// 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); }
Cycle(level - 1);
}
// Prolongate and add
{
const Array<const Vector *> cY_lm1((const Vector **)(Y[level - 1]), nrhs);
Array<Vector *> Y_l(Y[level], nrhs);
if (preSmoothingSteps == 0 && zero)
Array<Vector *> Y_(Y[level - 1], nrhs), Z_(Z[level], nrhs);
GetProlongationAtLevel(level - 1)->ArrayMult(Y_, Z_);
for (int j = 0; j < nrhs; ++j)
{
GetProlongationAtLevel(level - 1)->ArrayMult(cY_lm1, Y_l);
}
else
{
GetProlongationAtLevel(level - 1)->ArrayAddMult(cY_lm1, Y_l);
*Y(level, j) += *Z_[j];
}
}
mg_post_smooth:
// Post-smooth
for (int i = 0; i < postSmoothingSteps; ++i)
{
+2 -20
View File
@@ -36,14 +36,12 @@ 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;
mutable Array2D<Vector*> X, Y, R, Z;
mutable int nrhs;
public:
@@ -67,16 +65,6 @@ 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(); }
@@ -130,14 +118,11 @@ public:
private:
/// Application of a multigrid cycle at particular level
void Cycle(int level, bool zero) const;
void Cycle(int level) 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;
@@ -217,9 +202,6 @@ 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,8 +124,6 @@ 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
+591
View File
@@ -0,0 +1,591 @@
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#include "pderefmat_op.hpp"
#ifdef MFEM_USE_MPI
#include "fes_kernels.hpp"
/// \cond DO_NOT_DOCUMENT
namespace mfem
{
namespace internal
{
template <Ordering::Type Order, bool Atomic>
static void ParDerefMultKernelImpl(const ParDerefineMatrixOp &op,
const Vector &x, Vector &y)
{
// pack sends
if (op.xghost_send.Size())
{
auto src = x.Read();
auto idcs = op.send_permutations.Read();
auto dst = Device::GetGPUAwareMPI() ? op.xghost_send.Write()
: op.xghost_send.HostWrite();
auto vdims = op.fespace->GetVDim();
auto sptr = op.send_segment_idcs.Read();
auto lptr = op.send_segments.Read();
auto old_ndofs = x.Size() / vdims;
forall(op.send_permutations.Size(), [=] MFEM_HOST_DEVICE(int i)
{
int seg = sptr[i];
int width = lptr[seg + 1] - lptr[seg];
auto tdst = dst + i + lptr[seg] * vdims;
int sign = 1;
int col = idcs[i];
if (col < 0)
{
sign = -1;
col = -1 - col;
}
for (int vdim = 0; vdim < vdims; ++vdim)
{
tdst[vdim * width] =
sign
* src[Order == Ordering::byNODES ? (col + vdim * old_ndofs)
: (col * vdims + vdim)];
}
});
// TODO: is this needed so we can send the packed data correctly?
// unclear for GPU-aware MPI, definitely required otherwise
MFEM_DEVICE_SYNC;
}
// initialize off-diagonal receive and send
op.requests.clear();
if (op.xghost_recv.Size())
{
auto vdims = op.fespace->GetVDim();
auto rcv = Device::GetGPUAwareMPI() ? op.xghost_recv.Write()
: op.xghost_recv.HostWrite();
for (int i = 0; i < op.recv_ranks.Size(); ++i)
{
op.requests.emplace_back();
MPI_Irecv(rcv + op.recv_segments[i] * vdims,
(op.recv_segments[i + 1] - op.recv_segments[i]) * vdims,
MPITypeMap<real_t>::mpi_type, op.recv_ranks[i],
MessageTag::DEREFINEMENT_MATRIX_CONSTRUCTION_DATA,
op.fespace->GetComm(), &op.requests.back());
}
}
if (op.xghost_send.Size())
{
auto vdims = op.fespace->GetVDim();
// only is a GPU mem ptr if GPU-aware MPI is enabled
auto dst = Device::GetGPUAwareMPI() ? op.xghost_send.Write()
: op.xghost_send.HostWrite();
for (int i = 0; i < op.send_ranks.Size(); ++i)
{
op.requests.emplace_back();
MPI_Isend(dst + op.send_segments[i] * vdims,
(op.send_segments[i + 1] - op.send_segments[i]) * vdims,
MPITypeMap<real_t>::mpi_type, op.send_ranks[i],
MessageTag::DEREFINEMENT_MATRIX_CONSTRUCTION_DATA,
op.fespace->GetComm(), &op.requests.back());
}
}
{
// diagonal
DerefineMatrixOpMultFunctor<Order, Atomic, true> func;
func.xptr = x.Read();
y.UseDevice();
y = 0.;
func.yptr = y.ReadWrite();
func.bsptr = op.block_storage.Read();
func.boptr = op.block_offsets.Read();
func.brptr = op.block_row_idcs_offsets.Read();
func.bcptr = op.block_col_idcs_offsets.Read();
func.rptr = op.row_idcs.Read();
func.cptr = op.col_idcs.Read();
func.vdims = op.fespace->GetVDim();
func.nblocks = op.block_offsets.Size();
func.width = op.Width() / func.vdims;
func.height = op.Height() / func.vdims;
func.Run(op.max_rows);
}
// wait for comm to finish, if any
if (op.requests.size())
{
MPI_Waitall(op.requests.size(), op.requests.data(), MPI_STATUSES_IGNORE);
if (op.xghost_recv.Size())
{
// off-diagonal kernel
DerefineMatrixOpMultFunctor<Order, Atomic, false> func;
// directly read from host-pinned memory if not using GPU-aware MPI
func.xptr = Device::GetGPUAwareMPI() ? op.xghost_recv.Read()
: op.xghost_recv.HostRead();
func.yptr = y.ReadWrite();
func.bsptr = op.block_storage.Read();
func.boptr = op.off_diag_block_offsets.Read();
func.brptr = op.block_off_diag_row_idcs_offsets.Read();
func.rsptr = op.recv_segment_idcs.Read();
func.segptr = op.recv_segments.Read();
func.coptr = op.block_off_diag_col_offsets.Read();
func.bwptr = op.block_off_diag_widths.Read();
func.rptr = op.row_off_diag_idcs.Read();
func.vdims = op.fespace->GetVDim();
func.nblocks = op.off_diag_block_offsets.Size();
func.width = op.xghost_recv.Size() / func.vdims;
func.height = op.Height() / func.vdims;
func.Run(op.max_rows);
}
}
}
} // namespace internal
template <Ordering::Type Order, bool Atomic>
ParDerefineMatrixOp::MultKernelType ParDerefineMatrixOp::MultKernel::Kernel()
{
return internal::ParDerefMultKernelImpl<Order, Atomic>;
}
ParDerefineMatrixOp::MultKernelType
ParDerefineMatrixOp::MultKernel::Fallback(Ordering::Type, bool)
{
MFEM_ABORT("invalid MultKernel parameters");
}
ParDerefineMatrixOp::Kernels::Kernels()
{
MultKernel::Specialization<Ordering::byNODES, false>::Add();
MultKernel::Specialization<Ordering::byVDIM, false>::Add();
MultKernel::Specialization<Ordering::byNODES, true>::Add();
MultKernel::Specialization<Ordering::byVDIM, true>::Add();
}
void ParDerefineMatrixOp::Mult(const Vector &x, Vector &y) const
{
const bool is_dg = fespace->FEColl()->GetContType()
== FiniteElementCollection::DISCONTINUOUS;
// DG needs atomic summation
MultKernel::Run(fespace->GetOrdering(), is_dg, *this, x, y);
// use this to prevent xghost* from being re-purposed for subsequent Mult
// calls
MFEM_DEVICE_SYNC;
}
ParDerefineMatrixOp::ParDerefineMatrixOp(ParFiniteElementSpace &fespace_,
int old_ndofs,
const Table *old_elem_dof,
const Table *old_elem_fos)
: Operator(fespace_.GetVSize(), old_ndofs * fespace_.GetVDim()),
fespace(&fespace_)
{
static Kernels kernels;
constexpr int max_team_size = 256;
const int NRanks = fespace->GetNRanks();
const int nrk = HYPRE_AssumedPartitionCheck() ? 2 : NRanks;
MFEM_VERIFY(fespace->Nonconforming(),
"Not implemented for conforming meshes.");
MFEM_VERIFY(fespace->old_dof_offsets[nrk],
"Missing previous (finer) space.");
const int MyRank = fespace->GetMyRank();
ParNCMesh *old_pncmesh = fespace->GetParMesh()->pncmesh;
const CoarseFineTransformations &dtrans =
old_pncmesh->GetDerefinementTransforms();
const Array<int> &old_ranks = old_pncmesh->GetDerefineOldRanks();
const bool is_dg = fespace->FEColl()->GetContType()
== FiniteElementCollection::DISCONTINUOUS;
DenseMatrix localRVO; // for variable-order only
DenseTensor localR[Geometry::NumGeom];
int diag_rows = 0;
int off_diag_rows = 0;
int diag_cols = 0;
auto get_ldofs = [&](int k) -> int
{
const Embedding &emb = dtrans.embeddings[k];
if (fespace->IsVariableOrder())
{
const FiniteElement *fe = fespace->GetFE(emb.parent);
return fe->GetDof();
}
else
{
Geometry::Type geom =
fespace->GetParMesh()->GetElementBaseGeometry(emb.parent);
return fespace->FEColl()->FiniteElementForGeometry(geom)->GetDof();
}
};
Array<int> dofs, old_dofs;
max_rows = 1;
// first pass:
// - determine memory block lengths
// - identify dofs in x we need to send/receive
// don't need to send the indices, fine rank will re-arrange and sign
// change x before transmitting the ghost data
// key: coarse rank to send to
// value: old dofs to send (with sign)
std::map<int, std::vector<int>> to_send;
// key: fine rank
// value: indices into dtrans.embeddings
std::map<int, std::vector<int>> od_ks;
// key: fine rank
// value: recv segment length
std::map<int, int> od_seg_lens;
int send_len = 0;
int recv_len = 0;
// size of block_storage, if fespace->IsVariableOrder()
// otherwise unused
int total_size = 0;
int num_diagonal_blocks = 0;
int num_offdiagonal_blocks = 0;
for (int k = 0; k < dtrans.embeddings.Size(); ++k)
{
const Embedding &emb = dtrans.embeddings[k];
int fine_rank = old_ranks[k];
int coarse_rank = (emb.parent < 0) ? (-1 - emb.parent)
: old_pncmesh->ElementRank(emb.parent);
if (coarse_rank != MyRank && fine_rank == MyRank)
{
// this rank needs to send data in x to course_rank
old_elem_dof->GetRow(k, old_dofs);
auto &tmp = to_send[coarse_rank];
send_len += old_dofs.Size();
for (int i = 0; i < old_dofs.Size(); ++i)
{
tmp.emplace_back(old_dofs[i]);
}
}
else if (coarse_rank == MyRank && fine_rank != MyRank)
{
// this rank needs to receive data in x from fine_rank
MFEM_ASSERT(emb.parent >= 0, "");
auto ldofs = get_ldofs(k);
off_diag_rows += ldofs;
recv_len += ldofs;
od_ks[fine_rank].emplace_back(k);
od_seg_lens[fine_rank] += ldofs;
++num_offdiagonal_blocks;
if (fespace->IsVariableOrder())
{
total_size += ldofs * ldofs;
}
}
else if (coarse_rank == MyRank && fine_rank == MyRank)
{
MFEM_ASSERT(emb.parent >= 0, "");
// diagonal
++num_diagonal_blocks;
auto ldofs = get_ldofs(k);
diag_rows += ldofs;
diag_cols += ldofs;
if (fespace->IsVariableOrder())
{
total_size += ldofs * ldofs;
}
}
}
send_segments.SetSize(to_send.size() + 1);
send_segments.HostWrite();
send_ranks.SetSize(to_send.size());
send_ranks.HostWrite();
{
int idx = 0;
send_segments[0] = 0;
for (auto &tmp : to_send)
{
send_ranks[idx] = tmp.first;
send_segments[idx + 1] = send_segments[idx] + tmp.second.size();
++idx;
}
}
recv_segment_idcs.SetSize(off_diag_rows);
recv_segment_idcs.HostWrite();
recv_segments.SetSize(od_ks.size() + 1);
recv_segments.HostWrite();
recv_ranks.SetSize(od_ks.size());
recv_ranks.HostWrite();
// set sizes
row_idcs.SetSize(diag_rows);
row_idcs.HostWrite();
row_off_diag_idcs.SetSize(off_diag_rows);
row_off_diag_idcs.HostWrite();
col_idcs.SetSize(diag_cols);
col_idcs.HostWrite();
block_row_idcs_offsets.SetSize(num_diagonal_blocks + 1);
block_row_idcs_offsets.HostWrite();
block_col_idcs_offsets.SetSize(num_diagonal_blocks + 1);
block_col_idcs_offsets.HostWrite();
block_off_diag_row_idcs_offsets.SetSize(num_offdiagonal_blocks + 1);
block_off_diag_row_idcs_offsets.HostWrite();
block_off_diag_col_offsets.SetSize(num_offdiagonal_blocks);
block_off_diag_col_offsets.HostWrite();
block_off_diag_widths.SetSize(num_offdiagonal_blocks);
block_off_diag_widths.HostWrite();
pack_col_idcs.SetSize(send_len);
// memory manager doesn't appear to have a graceful fallback for
// HOST_PINNED if not built with CUDA or HIP
#if defined(MFEM_USE_CUDA) or defined(MFEM_USE_HIP)
xghost_send.SetSize(send_len * fespace->GetVDim(),
Device::GetGPUAwareMPI() ? MemoryType::DEFAULT
: MemoryType::HOST_PINNED);
xghost_recv.SetSize(recv_len * fespace->GetVDim(),
Device::GetGPUAwareMPI() ? MemoryType::DEFAULT
: MemoryType::HOST_PINNED);
#else
xghost_send.SetSize(send_len * fespace->GetVDim());
xghost_recv.SetSize(recv_len * fespace->GetVDim());
#endif
send_permutations.SetSize(send_len);
send_segment_idcs.SetSize(send_len);
block_offsets.SetSize(num_diagonal_blocks);
block_offsets.HostWrite();
off_diag_block_offsets.SetSize(num_offdiagonal_blocks);
off_diag_block_offsets.HostWrite();
int geom_offsets[Geometry::NumGeom];
real_t *bs_ptr;
if (fespace->IsVariableOrder())
{
block_storage.SetSize(total_size);
bs_ptr = block_storage.HostWrite();
// compute block data later
}
else
{
// compression scheme:
// block_offsets is the start of each block, potentially repeated
// only need to store localR for used shapes
Mesh::GeometryList elem_geoms(*fespace->GetMesh());
int size = 0;
for (int i = 0; i < elem_geoms.Size(); ++i)
{
fespace->GetLocalDerefinementMatrices(elem_geoms[i],
localR[elem_geoms[i]]);
geom_offsets[elem_geoms[i]] = size;
size += localR[elem_geoms[i]].TotalSize();
}
block_storage.SetSize(size);
bs_ptr = block_storage.HostWrite();
// copy blocks into block_storage
for (int i = 0; i < elem_geoms.Size(); ++i)
{
std::copy(localR[elem_geoms[i]].Data(),
localR[elem_geoms[i]].Data()
+ localR[elem_geoms[i]].TotalSize(),
bs_ptr);
bs_ptr += localR[elem_geoms[i]].TotalSize();
}
}
// second pass:
// - initialize buffers
{
auto ptr = send_permutations.HostWrite();
auto ptr2 = send_segment_idcs.HostWrite();
int i = 0;
for (auto &v : to_send)
{
ptr = std::copy(v.second.begin(), v.second.end(), ptr);
for (size_t idx = 0; idx < v.second.size(); ++idx)
{
*ptr2 = i;
++ptr2;
}
++i;
}
}
block_row_idcs_offsets[0] = 0;
block_col_idcs_offsets[0] = 0;
block_off_diag_row_idcs_offsets[0] = 0;
Array<int> mark(fespace->GetNDofs());
mark = 0;
{
int idx = 0;
recv_segments[0] = 0;
for (auto &v : od_seg_lens)
{
recv_ranks[idx] = v.first;
recv_segments[idx + 1] = recv_segments[idx] + v.second;
++idx;
}
}
// key: index into dtrans.embeddings
// value: off-diagonal block offset, od_ridx, seg id
std::unordered_map<int, std::array<int, 3>> ks_map;
{
int od_ridx = 0;
int seg_id = 0;
for (auto &v1 : od_ks)
{
for (auto k : v1.second)
{
auto &tmp = ks_map[k];
tmp[0] = ks_map.size() - 1;
tmp[1] = od_ridx;
tmp[2] = seg_id;
od_ridx += get_ldofs(k);
}
++seg_id;
}
}
int diag_idx = 0;
int var_offset = 0;
int ridx = 0;
int cidx = 0;
// can't break this up into separate diagonals/off-diagonals loops because
// of mark
for (int k = 0; k < dtrans.embeddings.Size(); ++k)
{
const Embedding &emb = dtrans.embeddings[k];
if (emb.parent < 0)
{
continue;
}
int fine_rank = old_ranks[k];
int coarse_rank = (emb.parent < 0) ? (-1 - emb.parent)
: old_pncmesh->ElementRank(emb.parent);
if (coarse_rank == MyRank)
{
// either diagonal or off-diagonal
Geometry::Type geom =
fespace->GetMesh()->GetElementBaseGeometry(emb.parent);
if (fespace->IsVariableOrder())
{
const FiniteElement *fe = fespace->GetFE(emb.parent);
const DenseTensor &pmats = dtrans.point_matrices[geom];
const int ldof = fe->GetDof();
IsoparametricTransformation isotr;
isotr.SetIdentityTransformation(geom);
localRVO.SetSize(ldof, ldof);
isotr.SetPointMat(pmats(emb.matrix));
// Local restriction is size ldofxldof assuming that the parent
// and child are of same polynomial order.
fe->GetLocalRestriction(isotr, localRVO);
// copy block
auto s = localRVO.Height() * localRVO.Width();
std::copy(localRVO.Data(), localRVO.Data() + s, bs_ptr);
bs_ptr += s;
}
DenseMatrix &lR =
fespace->IsVariableOrder() ? localRVO : localR[geom](emb.matrix);
max_rows = std::max(lR.Height(), max_rows);
auto size = lR.Height() * lR.Width();
fespace->elem_dof->GetRow(emb.parent, dofs);
if (fine_rank == MyRank)
{
// diagonal
old_elem_dof->GetRow(k, old_dofs);
MFEM_VERIFY(old_dofs.Size() == dofs.Size(),
"Parent and child must have same #dofs.");
block_row_idcs_offsets[diag_idx + 1] =
block_row_idcs_offsets[diag_idx] + lR.Height();
block_col_idcs_offsets[diag_idx + 1] =
block_col_idcs_offsets[diag_idx] + lR.Width();
if (fespace->IsVariableOrder())
{
block_offsets[diag_idx] = var_offset;
var_offset += size;
}
else
{
block_offsets[diag_idx] = geom_offsets[geom] + size * emb.matrix;
}
for (int i = 0; i < lR.Height(); ++i, ++ridx)
{
if (!std::isfinite(lR(i, 0)))
{
row_idcs[ridx] = INT_MAX;
continue;
}
int r = dofs[i];
int m = (r >= 0) ? r : (-1 - r);
if (is_dg || !mark[m])
{
row_idcs[ridx] = r;
mark[m] = 1;
}
else
{
row_idcs[ridx] = INT_MAX;
}
}
for (int i = 0; i < lR.Width(); ++i, ++cidx)
{
col_idcs[cidx] = old_dofs[i];
}
++diag_idx;
}
else
{
// off-diagonal
auto &tmp = ks_map.at(k);
auto od_idx = tmp[0];
auto od_ridx = tmp[1];
block_off_diag_row_idcs_offsets[od_idx + 1] =
block_off_diag_row_idcs_offsets[od_idx] + lR.Height();
block_off_diag_col_offsets[od_idx] = od_ridx;
block_off_diag_widths[od_idx] = lR.Width();
recv_segment_idcs[od_idx] = tmp[2];
if (fespace->IsVariableOrder())
{
off_diag_block_offsets[od_idx] = var_offset;
var_offset += size;
}
else
{
off_diag_block_offsets[od_idx] =
geom_offsets[geom] + size * emb.matrix;
}
for (int i = 0; i < lR.Height(); ++i, ++od_ridx)
{
if (!std::isfinite(lR(i, 0)))
{
row_off_diag_idcs[od_ridx] = INT_MAX;
continue;
}
int r = dofs[i];
int m = (r >= 0) ? r : (-1 - r);
if (is_dg || !mark[m])
{
row_off_diag_idcs[od_ridx] = r;
mark[m] = 1;
}
else
{
row_off_diag_idcs[od_ridx] = INT_MAX;
}
}
++od_idx;
}
}
}
// if not using GPU, set max_rows/max_cols to zero
if (Device::Allows(Backend::DEVICE_MASK))
{
max_rows = std::min(max_rows, max_team_size);
}
else
{
max_rows = 1;
}
requests.reserve(recv_ranks.Size() + send_ranks.Size());
}
} // namespace mfem
/// \endcond DO_NOT_DOCUMENT
#endif
+111
View File
@@ -0,0 +1,111 @@
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#ifndef MFEM_PDEREFMAT_OP
#define MFEM_PDEREFMAT_OP
#include "../config/config.hpp"
#ifdef MFEM_USE_MPI
#include "pfespace.hpp"
#include "kernel_dispatch.hpp"
#include <vector>
namespace mfem
{
/// \cond DO_NOT_DOCUMENT
struct ParDerefineMatrixOp : public Operator
{
ParFiniteElementSpace *fespace;
/// offsets into block_storage for diagonal
Array<int> block_offsets;
/// offsets into row_idcs for diagonal
Array<int> block_row_idcs_offsets;
/// offsets into col_idcs for diagonal
Array<int> block_col_idcs_offsets;
/// offsets into block_storage for off-diagonal
Array<int> off_diag_block_offsets;
/// offsets into row_idcs for off-diagonal
Array<int> block_off_diag_row_idcs_offsets;
Array<int> block_off_diag_col_offsets;
Array<int> block_off_diag_widths;
/// mapping for row dofs, INT_MAX indicates the block row should be ignored.
/// negative means the row data should be negated.
/// only for diagonal blocks
Array<int> row_idcs;
/// mapping for col dofs, negative means the col data should be negated.
/// only for diagonal blocks
Array<int> col_idcs;
Array<int> pack_col_idcs;
/// mapping for row dofs, INT_MAX indicates the block row should be ignored.
/// negative means the row data should be negated.
/// only for off-diagonal blocks
Array<int> row_off_diag_idcs;
/// dense block matrices which can be reused to construct the full matrix
/// operation. These are stored contiguously and blocks have no restrictions
/// on shape (can be rectangle and differ from block to block).
/// This is only for the diagonal block.
Vector block_storage;
/// maximum height of any block in block_storage for GPU
/// parallelization, or 1 for CPU runs.
int max_rows;
/// quasi Ordering::byNODES, broken into sections by ranks we need to send
/// the data to
mutable Vector xghost_send;
/// quasi Ordering::byNODES, broken into sections by ranks we received
/// the data from
mutable Vector xghost_recv;
/// maps off-diagonal k to segment
Array<int> recv_segment_idcs;
/// cumulative count of dofs which will be received from other ranks
Array<int> recv_segments;
/// Source rank of each recv segment
Array<int> recv_ranks;
/// What send segment each entry in send_permutations corresponds to
Array<int> send_segment_idcs;
/// cumulative count of dofs which will be sent to other ranks
Array<int> send_segments;
/// Destination rank of each send segment
Array<int> send_ranks;
/// how to permute/sign change values from our local x to send to other ranks
Array<int> send_permutations;
/// internal buffer for MPI requests
mutable std::vector<MPI_Request> requests;
using MultKernelType = void (*)(const ParDerefineMatrixOp &, const Vector &,
Vector &);
/// template args: ordering, atomic
MFEM_REGISTER_KERNELS(MultKernel, MultKernelType, (Ordering::Type, bool));
struct Kernels
{
Kernels();
};
void Mult(const Vector &x, Vector &y) const;
ParDerefineMatrixOp(ParFiniteElementSpace &fespace_, int old_ndofs,
const Table *old_elem_dof, const Table *old_elem_fos);
};
/// \endcond DO_NOT_DOCUMENT
} // namespace mfem
#endif
#endif
+111 -196
View File
@@ -22,12 +22,13 @@
#include "../mesh/mesh_headers.hpp"
#include "../general/binaryio.hpp"
#include "pderefmat_op.hpp"
#include <limits>
#include <list>
namespace mfem
{
ParFiniteElementSpace::ParFiniteElementSpace(
const ParFiniteElementSpace &orig, ParMesh *pmesh,
const FiniteElementCollection *fec)
@@ -4487,13 +4488,6 @@ ParFiniteElementSpace::RebalanceMatrix(int old_ndofs,
return M;
}
struct DerefDofMessage
{
std::vector<HYPRE_BigInt> dofs;
MPI_Request request;
};
HypreParMatrix*
ParFiniteElementSpace::ParallelDerefinementMatrix(int old_ndofs,
const Table* old_elem_dof,
@@ -4536,7 +4530,13 @@ ParFiniteElementSpace::ParallelDerefinementMatrix(int old_ndofs,
old_pncmesh->GetDerefinementTransforms();
const Array<int> &old_ranks = old_pncmesh->GetDerefineOldRanks();
std::map<int, DerefDofMessage> messages;
// key: other rank
// value: send or recieve buffer
std::map<int, std::vector<HYPRE_BigInt>> to_send;
std::map<int, std::vector<HYPRE_BigInt>> to_recv;
// key: index into dtrans.embeddings
// value: [start, stop]
std::unordered_map<int, std::array<size_t, 2>> recv_messages;
HYPRE_BigInt old_offset = HYPRE_AssumedPartitionCheck()
? old_dof_offsets[0] : old_dof_offsets[MyRank];
@@ -4556,30 +4556,46 @@ ParFiniteElementSpace::ParallelDerefinementMatrix(int old_ndofs,
old_elem_dof->GetRow(k, dofs);
DofsToVDofs(dofs, old_ndofs);
DerefDofMessage &msg = messages[k];
msg.dofs.resize(dofs.Size());
std::vector<HYPRE_BigInt>& send_buf = to_send[coarse_rank];
auto pos = send_buf.size();
send_buf.resize(pos + dofs.Size());
for (int i = 0; i < dofs.Size(); i++)
{
msg.dofs[i] = old_offset + dofs[i];
send_buf[pos + i] = old_offset + dofs[i];
}
MPI_Isend(&msg.dofs[0], static_cast<int>(msg.dofs.size()), HYPRE_MPI_BIG_INT,
coarse_rank, 291, MyComm, &msg.request);
}
else if (coarse_rank == MyRank && fine_rank != MyRank)
{
MFEM_ASSERT(emb.parent >= 0, "");
Geometry::Type geom = mesh->GetElementBaseGeometry(emb.parent);
DerefDofMessage &msg = messages[k];
msg.dofs.resize(ldof[geom]*vdim);
MPI_Irecv(&msg.dofs[0], ldof[geom]*vdim, HYPRE_MPI_BIG_INT,
fine_rank, 291, MyComm, &msg.request);
std::vector<HYPRE_BigInt>& recv_buf = to_recv[fine_rank];
auto& msg = recv_messages[k];
msg[0] = recv_buf.size();
recv_buf.resize(recv_buf.size() + ldof[geom] * vdim);
msg[1] = recv_buf.size();
}
// TODO: coalesce Isends/Irecvs to the same rank. Typically, on uniform
// derefinement, there should be just one send to MyRank-1 and one recv
// from MyRank+1
}
// assume embedding orders are consistent (i.e. what we expect to receive
// first from a given rank is sent first, etc.)
std::vector<MPI_Request> requests;
requests.reserve(to_send.size() + to_recv.size());
// enqueue recvs
for (auto &v : to_recv)
{
requests.emplace_back();
MPI_Irecv(v.second.data(), v.second.size(), HYPRE_MPI_BIG_INT, v.first,
MessageTag::DEREFINEMENT_MATRIX_CONSTRUCTION_DATA, MyComm,
&requests.back());
}
// enqueue sends
for (auto &v : to_send)
{
requests.emplace_back();
MPI_Isend(v.second.data(), v.second.size(), HYPRE_MPI_BIG_INT, v.first,
MessageTag::DEREFINEMENT_MATRIX_CONSTRUCTION_DATA, MyComm,
&requests.back());
}
DenseTensor localR[Geometry::NumGeom];
@@ -4637,10 +4653,7 @@ ParFiniteElementSpace::ParallelDerefinementMatrix(int old_ndofs,
diag->Finalize();
// wait for all sends/receives to complete
for (auto it = messages.begin(); it != messages.end(); ++it)
{
MPI_Wait(&it->second.request, MPI_STATUS_IGNORE);
}
MPI_Waitall(requests.size(), requests.data(), MPI_STATUSES_IGNORE);
// create the off-diagonal part of the derefinement matrix
SparseMatrix *offd = new SparseMatrix(ndofs*vdim, 1);
@@ -4661,13 +4674,14 @@ ParFiniteElementSpace::ParallelDerefinementMatrix(int old_ndofs,
elem_dof->GetRow(emb.parent, dofs);
DerefDofMessage &msg = messages[k];
MFEM_ASSERT(msg.dofs.size(), "");
auto& odofs = to_recv.at(fine_rank);
auto &msg = recv_messages[k];
MFEM_ASSERT(msg[1] > msg[0], "");
for (int vd = 0; vd < vdim; vd++)
{
MFEM_ASSERT(ldof[geom], "");
HYPRE_BigInt* remote_dofs = &msg.dofs[vd*ldof[geom]];
HYPRE_BigInt *remote_dofs = odofs.data() + msg[0] + vd * ldof[geom];
for (int i = 0; i < lR.Height(); i++)
{
@@ -4694,7 +4708,6 @@ ParFiniteElementSpace::ParallelDerefinementMatrix(int old_ndofs,
}
}
messages.clear();
offd->Finalize(0);
offd->SetWidth(static_cast<int>(col_map.size()));
@@ -4946,8 +4959,13 @@ void ParFiniteElementSpace::Update(bool want_transform)
case Mesh::DEREFINE:
{
#if 0
Th.Reset(ParallelDerefinementMatrix(old_ndofs, old_elem_dof,
old_elem_fos));
#else
Th.Reset(new ParDerefineMatrixOp(*this, old_ndofs, old_elem_dof,
old_elem_fos));
#endif
if (Nonconforming())
{
Th.SetOperatorOwner(false);
@@ -5309,18 +5327,6 @@ DeviceConformingProlongationOperator::DeviceConformingProlongationOperator(
if (recv_size > 0) { req_counter++; }
}
requests = new MPI_Request[req_counter];
if (mpi_gpu_aware)
{
#if defined(MFEM_USE_HIP)
MFEM_GPU_CHECK(
hipEventCreateWithFlags(&gpu_event, hipEventDisableTiming));
#elif defined(MFEM_USE_CUDA)
MFEM_GPU_CHECK(
cudaEventCreateWithFlags(&gpu_event, cudaEventDisableTiming));
#else
MFEM_ABORT("not implemented");
#endif
}
}
DeviceConformingProlongationOperator::DeviceConformingProlongationOperator(
@@ -5334,27 +5340,16 @@ DeviceConformingProlongationOperator::DeviceConformingProlongationOperator(
}
static void ExtractSubVector(const Array<int> &indices,
const Vector &vin, Vector &vout,
real_t a, real_t b)
const Vector &vin, Vector &vout)
{
MFEM_ASSERT(indices.Size() == vout.Size(), "incompatible sizes!");
auto y = (a == 0) ? vout.Write() : vout.ReadWrite();
auto y = vout.Write();
const auto x = vin.Read();
const auto I = indices.Read();
if (a == 0)
mfem::forall(indices.Size(), [=] MFEM_HOST_DEVICE (int i)
{
mfem::forall(indices.Size(), [=] MFEM_HOST_DEVICE (int i)
{
y[i] = b*x[I[i]];
}); // indices can be repeated
}
else
{
mfem::forall(indices.Size(), [=] MFEM_HOST_DEVICE (int i)
{
y[i] = a*y[i] + b*x[I[i]];
}); // indices can be repeated
}
y[i] = x[I[i]];
}); // indices can be repeated
}
void DeviceConformingProlongationOperator::BcastBeginCopy(
@@ -5362,7 +5357,10 @@ void DeviceConformingProlongationOperator::BcastBeginCopy(
{
// shr_buf[i] = src[shr_ltdof[i]]
if (shr_ltdof.Size() == 0) { return; }
ExtractSubVector(shr_ltdof, x, shr_buf, 0, 1);
ExtractSubVector(shr_ltdof, x, shr_buf);
// If the above kernel is executed asynchronously, we should wait for it to
// complete
if (mpi_gpu_aware) { MFEM_STREAM_SYNC; }
}
static void SetSubVector(const Array<int> &indices,
@@ -5399,7 +5397,7 @@ void DeviceConformingProlongationOperator::Mult(const Vector &x,
Vector &y) const
{
const GroupTopology &gtopo = gc.GetGroupTopology();
int req_counter = 0, num_recv_req = 0;
int req_counter = 0;
// Make sure 'y' is marked as valid on device and for use on device.
// This ensures that there is no unnecessary host to device copy when the
// input 'y' is valid on host (in 'y.SetSubVector(ext_ldof, 0.0)' when local
@@ -5409,89 +5407,42 @@ void DeviceConformingProlongationOperator::Mult(const Vector &x,
{
// done on device since we've marked ext_ldof for use on device:
y.SetSubVector(ext_ldof, 0.0);
BcastLocalCopy(x, y);
return;
}
BcastBeginCopy(x); // copy to 'shr_buf'
if (mpi_gpu_aware && shr_ltdof.Size() != 0)
else
{
/* record a stream event to wait for later */
#if defined(MFEM_USE_HIP)
MFEM_GPU_CHECK(hipEventRecord(gpu_event, 0));
#elif defined(MFEM_USE_CUDA)
MFEM_GPU_CHECK(cudaEventRecord(gpu_event, 0));
#endif
}
BcastLocalCopy(x, y);
// Queue all receive communications
if (ext_ldof.Size() != 0) // ext_ldof.Size() == ext_buf.Size()
{
auto recv_buf = mpi_gpu_aware ? ext_buf.Write() : ext_buf.HostWrite();
for (int nbr = 1; nbr < gtopo.GetNumNeighbors(); nbr++)
{
const int recv_offset = ext_buf_offsets[nbr];
const int recv_size = ext_buf_offsets[nbr+1] - recv_offset;
if (recv_size > 0)
{
MPI_Irecv(recv_buf + recv_offset, recv_size,
MPITypeMap<real_t>::mpi_type,
gtopo.GetNeighborRank(nbr), 41822,
gtopo.GetComm(), &requests[req_counter++]);
}
}
num_recv_req = req_counter;
}
// Queue all send communications
if (shr_ltdof.Size() != 0) // shr_ltdof.Size() == shr_buf.Size()
{
// The BcastBeginCopy kernel is executed asynchronously, we should wait
// for it to complete:
// - when mpi_gpu_aware == false, this is done implicily when we call
// shr_buf.HostRead()
// - when mpi_gpu_aware == true, we need to wait for BcastBeginCopy to
// complete by waiting for gpu_event.
if (mpi_gpu_aware)
{
/* wait for the stream event recorded above */
#if defined(MFEM_USE_HIP)
MFEM_GPU_CHECK(hipEventSynchronize(gpu_event));
#elif defined(MFEM_USE_CUDA)
MFEM_GPU_CHECK(cudaEventSynchronize(gpu_event));
#endif
}
auto send_buf = mpi_gpu_aware ? shr_buf.Read() : shr_buf.HostRead();
BcastBeginCopy(x); // copy to 'shr_buf'
for (int nbr = 1; nbr < gtopo.GetNumNeighbors(); nbr++)
{
const int send_offset = shr_buf_offsets[nbr];
const int send_size = shr_buf_offsets[nbr+1] - send_offset;
if (send_size > 0)
{
MPI_Isend(send_buf + send_offset, send_size,
MPITypeMap<real_t>::mpi_type,
auto send_buf = mpi_gpu_aware ? shr_buf.Read() : shr_buf.HostRead();
MPI_Isend(send_buf + send_offset, send_size, MPITypeMap<real_t>::mpi_type,
gtopo.GetNeighborRank(nbr), 41822,
gtopo.GetComm(), &requests[req_counter++]);
}
const int recv_offset = ext_buf_offsets[nbr];
const int recv_size = ext_buf_offsets[nbr+1] - recv_offset;
if (recv_size > 0)
{
auto recv_buf = mpi_gpu_aware ? ext_buf.Write() : ext_buf.HostWrite();
MPI_Irecv(recv_buf + recv_offset, recv_size, MPITypeMap<real_t>::mpi_type,
gtopo.GetNeighborRank(nbr), 41822,
gtopo.GetComm(), &requests[req_counter++]);
}
}
}
// Wait for all receive requests
MPI_Waitall(num_recv_req, requests, MPI_STATUSES_IGNORE);
BcastEndCopy(y); // copy from 'ext_buf'
// Wait for all send requests
MPI_Waitall(req_counter - num_recv_req, requests + num_recv_req,
MPI_STATUSES_IGNORE);
BcastLocalCopy(x, y);
if (!local)
{
MPI_Waitall(req_counter, requests, MPI_STATUSES_IGNORE);
BcastEndCopy(y); // copy from 'ext_buf'
}
}
DeviceConformingProlongationOperator::~DeviceConformingProlongationOperator()
{
if (mpi_gpu_aware)
{
#if defined(MFEM_USE_HIP)
MFEM_GPU_CHECK(hipEventDestroy(gpu_event));
#elif defined(MFEM_USE_CUDA)
MFEM_GPU_CHECK(cudaEventDestroy(gpu_event));
#endif
}
delete [] requests;
ext_buf_offsets.Delete();
shr_buf_offsets.Delete();
@@ -5502,23 +5453,25 @@ void DeviceConformingProlongationOperator::ReduceBeginCopy(
{
// ext_buf[i] = src[ext_ldof[i]]
if (ext_ldof.Size() == 0) { return; }
ExtractSubVector(ext_ldof, x, ext_buf, 0, 1);
ExtractSubVector(ext_ldof, x, ext_buf);
// If the above kernel is executed asynchronously, we should wait for it to
// complete
if (mpi_gpu_aware) { MFEM_STREAM_SYNC; }
}
void DeviceConformingProlongationOperator::ReduceLocalCopy(
const Vector &x, Vector &y, real_t a, real_t b) const
const Vector &x, Vector &y) const
{
// dst[i] = src[ltdof_ldof[i]]
if (ltdof_ldof.Size() == 0) { return; }
ExtractSubVector(ltdof_ldof, x, y, a, b);
ExtractSubVector(ltdof_ldof, x, y);
}
static void AddSubVector(const Array<int> &unique_dst_indices,
const Array<int> &unique_to_src_offsets,
const Array<int> &unique_to_src_indices,
const Vector &src,
Vector &dst,
real_t b)
Vector &dst)
{
auto y = dst.ReadWrite();
const auto x = src.Read();
@@ -5528,94 +5481,56 @@ static void AddSubVector(const Array<int> &unique_dst_indices,
mfem::forall(unique_dst_indices.Size(), [=] MFEM_HOST_DEVICE (int i)
{
const int dst_idx = DST_I[i];
real_t sum = 0;
real_t sum = y[dst_idx];
const int end = SRC_O[i+1];
for (int j = SRC_O[i]; j != end; ++j) { sum += x[SRC_I[j]]; }
y[dst_idx] += b*sum;
y[dst_idx] = sum;
});
}
void DeviceConformingProlongationOperator::ReduceEndAssemble(
Vector &y, real_t b) const
void DeviceConformingProlongationOperator::ReduceEndAssemble(Vector &y) const
{
// dst[shr_ltdof[i]] += shr_buf[i]
if (unq_ltdof.Size() == 0) { return; }
AddSubVector(unq_ltdof, unq_shr_i, unq_shr_j, shr_buf, y, b);
AddSubVector(unq_ltdof, unq_shr_i, unq_shr_j, shr_buf, y);
}
void DeviceConformingProlongationOperator::ApplyTranspose(
const Vector &x, Vector &y, real_t a, real_t b) const
void DeviceConformingProlongationOperator::MultTranspose(const Vector &x,
Vector &y) const
{
const GroupTopology &gtopo = gc.GetGroupTopology();
int req_counter = 0, num_recv_req = 0;
if (local)
int req_counter = 0;
if (!local)
{
ReduceLocalCopy(x, y, a, b);
return;
}
ReduceBeginCopy(x); // copy to 'ext_buf'
if (mpi_gpu_aware && ext_ldof.Size() != 0)
{
/* record a stream event to wait for later */
#if defined(MFEM_USE_HIP)
MFEM_GPU_CHECK(hipEventRecord(gpu_event, 0));
#elif defined(MFEM_USE_CUDA)
MFEM_GPU_CHECK(cudaEventRecord(gpu_event, 0));
#endif
}
ReduceLocalCopy(x, y, a, b);
// Queue all receive communications
if (unq_ltdof.Size() != 0)
{
auto recv_buf = mpi_gpu_aware ? shr_buf.Write() : shr_buf.HostWrite();
for (int nbr = 1; nbr < gtopo.GetNumNeighbors(); nbr++)
{
const int recv_offset = shr_buf_offsets[nbr];
const int recv_size = shr_buf_offsets[nbr+1] - recv_offset;
if (recv_size > 0)
{
MPI_Irecv(recv_buf + recv_offset, recv_size,
MPITypeMap<real_t>::mpi_type,
gtopo.GetNeighborRank(nbr), 41823,
gtopo.GetComm(), &requests[req_counter++]);
}
}
num_recv_req = req_counter;
}
// Queue all send communications
if (ext_ldof.Size() != 0)
{
if (mpi_gpu_aware)
{
/* wait for the stream event recorded above */
#if defined(MFEM_USE_HIP)
MFEM_GPU_CHECK(hipEventSynchronize(gpu_event));
#elif defined(MFEM_USE_CUDA)
MFEM_GPU_CHECK(cudaEventSynchronize(gpu_event));
#endif
}
auto send_buf = mpi_gpu_aware ? ext_buf.Read() : ext_buf.HostRead();
ReduceBeginCopy(x); // copy to 'ext_buf'
for (int nbr = 1; nbr < gtopo.GetNumNeighbors(); nbr++)
{
const int send_offset = ext_buf_offsets[nbr];
const int send_size = ext_buf_offsets[nbr+1] - send_offset;
if (send_size > 0)
{
MPI_Isend(send_buf + send_offset, send_size,
MPITypeMap<real_t>::mpi_type,
auto send_buf = mpi_gpu_aware ? ext_buf.Read() : ext_buf.HostRead();
MPI_Isend(send_buf + send_offset, send_size, MPITypeMap<real_t>::mpi_type,
gtopo.GetNeighborRank(nbr), 41823,
gtopo.GetComm(), &requests[req_counter++]);
}
const int recv_offset = shr_buf_offsets[nbr];
const int recv_size = shr_buf_offsets[nbr+1] - recv_offset;
if (recv_size > 0)
{
auto recv_buf = mpi_gpu_aware ? shr_buf.Write() : shr_buf.HostWrite();
MPI_Irecv(recv_buf + recv_offset, recv_size, MPITypeMap<real_t>::mpi_type,
gtopo.GetNeighborRank(nbr), 41823,
gtopo.GetComm(), &requests[req_counter++]);
}
}
}
// Wait for all receive requests
MPI_Waitall(num_recv_req, requests, MPI_STATUSES_IGNORE);
ReduceEndAssemble(y, b); // assemble from 'shr_buf'
// Wait for all send requests
MPI_Waitall(req_counter - num_recv_req, requests + num_recv_req,
MPI_STATUSES_IGNORE);
ReduceLocalCopy(x, y);
if (!local)
{
MPI_Waitall(req_counter, requests, MPI_STATUSES_IGNORE);
ReduceEndAssemble(y); // assemble from 'shr_buf'
}
}
} // namespace mfem
+6 -18
View File
@@ -24,9 +24,12 @@
namespace mfem
{
struct ParDerefineMatrixOp;
/// Abstract parallel finite element space.
class ParFiniteElementSpace : public FiniteElementSpace
{
friend struct ParDerefineMatrixOp;
private:
/// MPI data.
MPI_Comm MyComm;
@@ -586,7 +589,6 @@ public:
{ MultTranspose(x,y); }
};
/// Auxiliary device class used by ParFiniteElementSpace.
class DeviceConformingProlongationOperator: public
ConformingProlongationOperator
@@ -599,11 +601,6 @@ protected:
Array<int> ltdof_ldof, unq_ltdof;
Array<int> unq_shr_i, unq_shr_j;
MPI_Request *requests;
#if defined(MFEM_USE_HIP)
hipEvent_t gpu_event;
#elif defined(MFEM_USE_CUDA)
cudaEvent_t gpu_event;
#endif
// Kernel: copy ltdofs from 'src' to 'shr_buf' - prepare for send.
// shr_buf[i] = src[shr_ltdof[i]]
@@ -623,12 +620,11 @@ protected:
// Kernel: copy owned ldofs from 'src' to ltdofs in 'dst'.
// dst[i] = src[ltdof_ldof[i]]
void ReduceLocalCopy(const Vector &src, Vector &dst,
real_t a, real_t b) const;
void ReduceLocalCopy(const Vector &src, Vector &dst) const;
// Kernel: assemble dofs from 'shr_buf' into to 'dst' - after recv.
// dst[shr_ltdof[i]] += shr_buf[i]
void ReduceEndAssemble(Vector &dst, real_t b) const;
void ReduceEndAssemble(Vector &dst) const;
public:
DeviceConformingProlongationOperator(
@@ -639,20 +635,12 @@ public:
virtual ~DeviceConformingProlongationOperator();
void ApplyTranspose(const Vector &x, Vector &y,
real_t a, real_t b) const;
void Mult(const Vector &x, Vector &y) const override;
void AbsMult(const Vector &x, Vector &y) const override
{ Mult(x,y); }
void MultTranspose(const Vector &x, Vector &y) const override
{ ApplyTranspose(x, y, 0, 1); }
void AddMultTranspose(const Vector &x, Vector &y,
real_t a = 1) const override
{ ApplyTranspose(x, y, 1, a); }
void MultTranspose(const Vector &x, Vector &y) const override;
void AbsMultTranspose(const Vector &x, Vector &y) const override
{ MultTranspose(x,y); }
+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;
+77 -58
View File
@@ -17,8 +17,9 @@ namespace mfem
{
QuadratureSpaceBase::QuadratureSpaceBase(Mesh &mesh_, Geometry::Type geom,
const IntegrationRule &ir)
: mesh(mesh_), order(ir.GetOrder())
const IntegrationRule &ir,
QSpaceStorage storage)
: mesh(mesh_), order(ir.GetOrder()), storage(storage)
{
for (int g = 0; g < Geometry::NumGeom; g++)
{
@@ -96,27 +97,37 @@ void QuadratureSpaceBase::Integrate(VectorCoefficient &coeff,
void QuadratureSpace::ConstructOffsets()
{
const int num_elem = mesh.GetNE();
offsets.SetSize(num_elem + 1);
int offset = 0;
for (int i = 0; i < num_elem; i++)
const int num_elem = ne;
if (storage == QSpaceStorage::COMPRESSED &&
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()
{
ConstructIntRules(mesh.Dimension());
ConstructOffsets();
}
QuadratureSpace::QuadratureSpace(Mesh *mesh_, std::istream &in)
: QuadratureSpaceBase(*mesh_)
QuadratureSpace::QuadratureSpace(Mesh *mesh_, std::istream &in,
QSpaceStorage storage)
: QuadratureSpaceBase(*mesh_, 0, storage)
{
const char *msg = "invalid input stream";
std::string ident;
@@ -135,15 +146,24 @@ QuadratureSpace::QuadratureSpace(Mesh *mesh_, std::istream &in)
return;
}
Construct();
ne = mesh.GetNE();
ConstructIntRules(mesh.Dimension());
}
QuadratureSpace::QuadratureSpace(Mesh &mesh_, const IntegrationRule &ir)
: QuadratureSpaceBase(mesh_, mesh_.GetTypicalElementGeometry(), ir)
QuadratureSpace::QuadratureSpace(Mesh *mesh_, int order_, QSpaceStorage storage)
: QuadratureSpaceBase(*mesh_, order_, storage)
{
ne = mesh.GetNE();
ConstructIntRules(mesh.Dimension());
}
QuadratureSpace::QuadratureSpace(Mesh &mesh_, const IntegrationRule &ir,
QSpaceStorage storage)
: QuadratureSpaceBase(mesh_, mesh_.GetTypicalElementGeometry(), ir, storage)
{
MFEM_VERIFY(mesh.GetNumGeometries(mesh.Dimension()) <= 1,
"Constructor not valid for mixed meshes");
ConstructOffsets();
ne = mesh.GetNE();
}
void QuadratureSpace::Save(std::ostream &os) const
@@ -165,54 +185,53 @@ const Vector &QuadratureSpace::GetGeometricFactorWeights() const
}
FaceQuadratureSpace::FaceQuadratureSpace(Mesh &mesh_, int order_,
FaceType face_type_)
: QuadratureSpaceBase(mesh_, order_),
face_type(face_type_),
num_faces(mesh.GetNFbyType(face_type))
FaceType face_type_,
QSpaceStorage storage)
: QuadratureSpaceBase(mesh_, order_, storage), face_type(face_type_),
face_indices(mesh.GetFaceIndices(face_type_)),
face_indices_inv(mesh.GetInvFaceIndices(face_type_))
{
Construct();
ne = face_indices.Size();
ConstructIntRules(mesh.Dimension() - 1);
}
FaceQuadratureSpace::FaceQuadratureSpace(Mesh &mesh_, const IntegrationRule &ir,
FaceType face_type_)
: QuadratureSpaceBase(mesh_, mesh_.GetTypicalFaceGeometry(), ir),
face_type(face_type_),
num_faces(mesh.GetNFbyType(face_type))
FaceType face_type_,
QSpaceStorage storage)
: QuadratureSpaceBase(mesh_, mesh_.GetTypicalFaceGeometry(), ir, storage),
face_type(face_type_), face_indices(mesh.GetFaceIndices(face_type_)),
face_indices_inv(mesh.GetInvFaceIndices(face_type_))
{
MFEM_VERIFY(mesh.GetNumGeometries(mesh.Dimension() - 1) <= 1,
"Constructor not valid for mixed meshes");
ConstructOffsets();
ne = face_indices.Size();
}
void FaceQuadratureSpace::ConstructOffsets()
{
face_indices.SetSize(num_faces);
offsets.SetSize(num_faces + 1);
int offset = 0;
int f_idx = 0;
for (int i = 0; i < mesh.GetNumFacesWithGhost(); i++)
if (storage == QSpaceStorage::COMPRESSED &&
mesh.GetNumGeometries(mesh.Dimension() - 1) == 1)
{
const Mesh::FaceInformation face = mesh.GetFaceInformation(i);
if (face.IsNonconformingCoarse() || !face.IsOfFaceType(face_type))
{
continue;
}
face_indices[f_idx] = i;
face_indices_inv[i] = f_idx;
offsets[f_idx] = offset;
Geometry::Type geom = mesh.GetFaceGeometry(i);
MFEM_ASSERT(int_rule[geom] != NULL, "Missing integration rule");
offset += int_rule[geom]->GetNPoints();
f_idx++;
Array<Geometry::Type> geoms;
mesh.GetGeometries(mesh.Dimension() - 1, geoms);
offsets.SetSize(1);
offsets.HostWrite();
offsets[0] = int_rule[geoms[0]]->GetNPoints();
size = ne * offsets[0];
}
else
{
offsets.SetSize(face_indices.Size() + 1);
int offset = 0;
for (int i = 0; i < mesh.GetNFbyType(face_type); ++i)
{
offsets[i] = offset;
Geometry::Type geom = mesh.GetFaceGeometry(face_indices[i]);
MFEM_ASSERT(int_rule[geom] != NULL, "Missing integration rule");
offset += int_rule[geom]->GetNPoints();
}
offsets[face_indices.Size()] = size = offset;
}
offsets[num_faces] = size = offset;
}
void FaceQuadratureSpace::Construct()
{
ConstructIntRules(mesh.Dimension() - 1);
ConstructOffsets();
}
int FaceQuadratureSpace::GetPermutedIndex(int idx, int iq) const
+80 -25
View File
@@ -19,35 +19,49 @@
namespace mfem
{
enum class QSpaceStorage
{
FULL,
COMPRESSED
};
/// Abstract base class for QuadratureSpace and FaceQuadratureSpace.
/** This class represents the storage layout for QuadratureFunction%s, that may
be defined either on mesh elements or mesh faces. */
class QuadratureSpaceBase
{
protected:
friend class QuadratureFunction; // Uses the offsets.
Mesh &mesh; ///< The underlying mesh.
int order; ///< The order of integration rule.
int size; ///< Total number of quadrature points.
int size = -1; ///< Total number of quadrature points. -1 indicates
///< offsets/size not computed yet.
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.
QSpaceStorage storage;
/// @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. Lazily constructed.
///
/// 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];
/// Protected constructor. Used by derived classes.
QuadratureSpaceBase(Mesh &mesh_, int order_ = 0)
: mesh(mesh_), order(order_) { }
QuadratureSpaceBase(Mesh &mesh_, int order_ = 0,
QSpaceStorage storage = QSpaceStorage::COMPRESSED)
: mesh(mesh_), order(order_), storage(storage)
{}
/// Protected constructor. Used by derived classes.
QuadratureSpaceBase(Mesh &mesh_, Geometry::Type geom,
const IntegrationRule &ir);
const IntegrationRule &ir,
QSpaceStorage storage = QSpaceStorage::COMPRESSED);
/// Fill the @ref int_rule array for each geometry type using @ref order.
void ConstructIntRules(int dim);
@@ -58,15 +72,55 @@ protected:
/// Compute the integration weights.
void ConstructWeights() const;
virtual void ConstructOffsets() = 0;
public:
QSpaceStorage StorageType() const { return storage; }
/// @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
{
if (size < 0)
{
const_cast<QuadratureSpaceBase *>(this)->ConstructOffsets();
}
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
{
if (size < 0)
{
const_cast<QuadratureSpaceBase *>(this)->ConstructOffsets();
}
return offsets;
}
/// Return the total number of quadrature points.
int GetSize() const { return size; }
int GetSize() const
{
if (size < 0)
{
const_cast<QuadratureSpaceBase *>(this)->ConstructOffsets();
}
return size;
}
/// Return the order of the quadrature rule(s) used by all elements.
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; }
@@ -120,19 +174,20 @@ class QuadratureSpace : public QuadratureSpaceBase
{
protected:
const Vector &GetGeometricFactorWeights() const override;
void ConstructOffsets();
void Construct();
void ConstructOffsets() override;
public:
/// Create a QuadratureSpace based on the global rules from #IntRules.
QuadratureSpace(Mesh *mesh_, int order_)
: QuadratureSpaceBase(*mesh_, order_) { Construct(); }
QuadratureSpace(Mesh *mesh_, int order_,
QSpaceStorage storage = QSpaceStorage::COMPRESSED);
/// @brief Create a QuadratureSpace with an IntegrationRule, valid only when
/// the mesh has one element type.
QuadratureSpace(Mesh &mesh_, const IntegrationRule &ir);
QuadratureSpace(Mesh &mesh_, const IntegrationRule &ir,
QSpaceStorage storage = QSpaceStorage::COMPRESSED);
/// Read a QuadratureSpace from the stream @a in.
QuadratureSpace(Mesh *mesh_, std::istream &in);
QuadratureSpace(Mesh *mesh_, std::istream &in,
QSpaceStorage storage = QSpaceStorage::COMPRESSED);
/// Returns number of elements in the mesh.
inline int GetNE() const { return mesh.GetNE(); }
@@ -169,29 +224,29 @@ public:
class FaceQuadratureSpace : public QuadratureSpaceBase
{
FaceType face_type; ///< Is the space defined on interior or boundary faces?
const int num_faces; ///< Number of faces.
/// Map from boundary or interior face indices to mesh face indices.
Array<int> face_indices;
const Array<int> &face_indices;
/// Inverse of the map @a face_indices.
std::unordered_map<int,int> face_indices_inv;
const std::unordered_map<int,int> &face_indices_inv;
const Vector &GetGeometricFactorWeights() const override;
void ConstructOffsets();
void Construct();
void ConstructOffsets() override;
public:
/// Create a FaceQuadratureSpace based on the global rules from #IntRules.
FaceQuadratureSpace(Mesh &mesh_, int order_, FaceType face_type_);
FaceQuadratureSpace(Mesh &mesh_, int order_, FaceType face_type_,
QSpaceStorage storage = QSpaceStorage::COMPRESSED);
/// @brief Create a FaceQuadratureSpace with an IntegrationRule, valid only
/// when the mesh has one type of face geometry.
FaceQuadratureSpace(Mesh &mesh_, const IntegrationRule &ir,
FaceType face_type_);
FaceType face_type_,
QSpaceStorage storage = QSpaceStorage::COMPRESSED);
/// Returns number of faces in the mesh.
inline int GetNumFaces() const { return num_faces; }
inline int GetNumFaces() const { return face_indices.Size(); }
/// Returns the face type (boundary or interior).
FaceType GetFaceType() const { return face_type; }
-2
View File
@@ -503,8 +503,6 @@ 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();
+10 -181
View File
@@ -13,7 +13,6 @@
#include "bilinearform.hpp"
#include "pbilinearform.hpp"
#include "../general/forall.hpp"
#include "kernels.hpp"
namespace mfem
{
@@ -2323,74 +2322,6 @@ 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)
{
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)
@@ -2472,9 +2403,9 @@ void Prolongation3D(const int NE, const int D1D, const int Q1D,
});
}
void ProlongationTranspose2D(const int NE, const int D1D, const int Q1D,
const Vector& localH, Vector& localL,
const Array<real_t>& Bt, const Vector& mask)
void Restriction2D(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);
@@ -2517,78 +2448,9 @@ void ProlongationTranspose2D(const int NE, const int D1D, const int Q1D,
}
});
}
template <int DLO, int DHI>
static void SmemProlongationTranspose3D(
const int NE, const Vector& localH, Vector& localL,
const Array<real_t>& bt, const Vector& mask)
{
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)
void Restriction3D(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);
@@ -2656,14 +2518,11 @@ void ProlongationTranspose3D(const int NE, const int D1D, const int Q1D,
}
});
}
} // namespace TransferKernels
void TensorProductPRefinementTransferOperator::Mult(const Vector& x,
Vector& y) const
{
using namespace TransferKernels;
if (lFESpace.GetMesh()->GetNE() == 0)
{
return;
@@ -2672,25 +2531,11 @@ void TensorProductPRefinementTransferOperator::Mult(const Vector& x,
elem_restrict_lex_l->Mult(x, localL);
if (dim == 2)
{
Prolongation2D(NE, D1D, Q1D, localL, localH, B, mask);
TransferKernels::Prolongation2D(NE, D1D, Q1D, localL, localH, B, mask);
}
else if (dim == 3)
{
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;
}
TransferKernels::Prolongation3D(NE, D1D, Q1D, localL, localH, B, mask);
}
else
{
@@ -2704,8 +2549,6 @@ void TensorProductPRefinementTransferOperator::Mult(const Vector& x,
void TensorProductPRefinementTransferOperator::MultTranspose(const Vector& x,
Vector& y) const
{
using namespace TransferKernels;
if (lFESpace.GetMesh()->GetNE() == 0)
{
return;
@@ -2714,25 +2557,11 @@ void TensorProductPRefinementTransferOperator::MultTranspose(const Vector& x,
elem_restrict_lex_h->Mult(x, localH);
if (dim == 2)
{
ProlongationTranspose2D(NE, D1D, Q1D, localH, localL, Bt, mask);
TransferKernels::Restriction2D(NE, D1D, Q1D, localH, localL, Bt, mask);
}
else if (dim == 3)
{
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;
}
TransferKernels::Restriction3D(NE, D1D, Q1D, localH, localL, Bt, mask);
}
else
{
+3
View File
@@ -621,6 +621,9 @@ 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;
+8 -95
View File
@@ -14,111 +14,24 @@
#include "../config/config.hpp"
#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 <optional>
#include <caliper/cali.h>
#include <caliper/cali-manager.h>
#endif
namespace mfem
{
namespace internal
{
extern int annotation_sync_stream; // defined in globals.cpp
extern int annotation_sync_mpi; // defined in globals.cpp
extern int annotation_enabled; // defined in globals.cpp
#ifdef MFEM_USE_CALIPER
inline void AnnotationSync()
{
if (!annotation_enabled) { return; }
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
{
std::optional<cali::Function> cali_func;
FunctionAnnotation(const char *fname)
{
AnnotationSync();
if (annotation_enabled) { cali_func.emplace(fname); }
}
~FunctionAnnotation() { AnnotationSync(); }
};
struct ScopeAnnotation
{
std::optional<cali::ScopeAnnotation> cali_scope;
ScopeAnnotation(const char *name)
{
AnnotationSync();
if (annotation_enabled) { cali_scope.emplace(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_FUNCTION CALI_CXX_MARK_FUNCTION
#define MFEM_PERF_BEGIN(s) CALI_MARK_BEGIN(s)
#define MFEM_PERF_END(s) CALI_MARK_END(s)
#define MFEM_PERF_SCOPE(name) \
mfem::internal::ScopeAnnotation \
MFEM_CONCAT(mfem_scope_annotation_,__LINE__)(name)
cali::Annotation::Guard cali_autogenerated_guard_name(cali::Annotation("function").begin(std::string(name).c_str()))
#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))
#define MFEM_PERF_ENABLE (mfem::internal::annotation_enabled = 1)
#define MFEM_PERF_DISABLE (mfem::internal::annotation_enabled = 0)
#else // #ifdef MFEM_USE_CALIPER
#else
#define MFEM_PERF_FUNCTION
#define MFEM_PERF_BEGIN(s)
#define MFEM_PERF_END(s)
#define MFEM_PERF_SCOPE(name)
#define MFEM_PERF_SYNC_STREAM(b)
#define MFEM_PERF_SYNC_MPI(b)
#define MFEM_PERF_SYNC(b)
#define MFEM_PERF_ENABLE
#define MFEM_PERF_DISABLE
#endif
#endif // #ifdef MFEM_USE_CALIPER
#endif // MFEM_ANNOTATION_HPP
#endif
+27
View File
@@ -211,6 +211,9 @@ public:
/// Delete the first entry with value == 'el'.
inline void DeleteFirst(const T &el);
/// Delete entries at @a indices, and resize.
inline void DeleteAt(const Array<int> &indices);
/// Delete the whole array.
inline void DeleteAll();
@@ -935,6 +938,30 @@ inline void Array<T>::DeleteFirst(const T &el)
}
}
template <class T>
inline void Array<T>::DeleteAt(const Array<int> &indices)
{
// Make a copy of the indices, sorted.
Array<int> sorted_indices(indices);
sorted_indices.Sort();
int rm_count = 0;
for (int i = 0; i < size; i++)
{
if (rm_count < sorted_indices.Size() && i == sorted_indices[rm_count])
{
rm_count++;
}
else
{
data[i-rm_count] = data[i]; // shift data rm_count
}
}
// Resize to remove tail
size -= rm_count;
}
template <class T>
inline void Array<T>::DeleteAll()
{
+6 -6
View File
@@ -14,7 +14,7 @@
#include "../config/config.hpp"
#ifdef MFEM_USE_CUDA
#if defined(MFEM_USE_CUDA) && defined(__CUDACC__)
#include <cusparse.h>
#include <library_types.h>
#include <cuda_runtime.h>
@@ -22,7 +22,7 @@
#endif
#include "cuda.hpp"
#ifdef MFEM_USE_HIP
#if defined(MFEM_USE_HIP) && defined(__HIP__)
#include <hip/hip_runtime.h>
#endif
#include "hip.hpp"
@@ -43,7 +43,7 @@
#endif
#endif
#if !(defined(MFEM_USE_CUDA) || defined(MFEM_USE_HIP))
#if !defined(MFEM_USE_CUDA_OR_HIP)
#define MFEM_DEVICE
#define MFEM_HOST
#define MFEM_LAMBDA
@@ -55,7 +55,7 @@
#endif
#if !((defined(MFEM_USE_CUDA) && defined(__CUDA_ARCH__)) || \
(defined(MFEM_USE_HIP) && defined(__HIP_DEVICE_COMPILE__)))
(defined(MFEM_USE_HIP) && defined(__HIP_DEVICE_COMPILE__)))
#define MFEM_SHARED
#define MFEM_SYNC_THREAD
#define MFEM_BLOCK_ID(k) 0
@@ -66,7 +66,7 @@
#endif
// 'double' and 'float' atomicAdd implementation for previous versions of CUDA
#if defined(MFEM_USE_CUDA) && defined(__CUDA_ARCH__) && __CUDA_ARCH__ < 600
#if defined(MFEM_USE_CUDA) && defined(__CUDA_ARCH__) && (__CUDA_ARCH__ < 600)
MFEM_DEVICE inline mfem::real_t atomicAdd(mfem::real_t *add, mfem::real_t val)
{
unsigned long long int *ptr = (unsigned long long int *) add;
@@ -94,7 +94,7 @@ template <typename T>
MFEM_HOST_DEVICE T AtomicAdd(T &add, const T val)
{
#if ((defined(MFEM_USE_CUDA) && defined(__CUDA_ARCH__)) || \
(defined(MFEM_USE_HIP) && defined(__HIP_DEVICE_COMPILE__)))
(defined(MFEM_USE_HIP) && defined(__HIP_DEVICE_COMPILE__)))
return atomicAdd(&add,val);
#else
T old = add;
+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)
+63 -20
View File
@@ -23,9 +23,13 @@
#include <mpi.h>
#include <cstdint>
// 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
// 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
namespace mfem
{
@@ -421,16 +425,24 @@ public:
~GroupCommunicator();
};
/// General MPI message tags used by MFEM
enum MessageTag
{
DEREFINEMENT_MATRIX_CONSTRUCTION_DATA =
291, /// ParFiniteElementSpace ParallelDerefinementMatrix and
/// ParDerefineMatrixOp
};
enum VarMessageTag
{
NEIGHBOR_ELEMENT_RANK_VM, ///< NeighborElementRankMessage
NEIGHBOR_ORDER_VM, ///< NeighborOrderMessage
NEIGHBOR_DEREFINEMENT_VM, ///< NeighborDerefinementMessage
NEIGHBOR_REFINEMENT_VM, ///< NeighborRefinementMessage
NEIGHBOR_PREFINEMENT_VM, ///< NeighborPRefinementMessage
NEIGHBOR_ROW_VM, ///< NeighborRowMessage
REBALANCE_VM, ///< RebalanceMessage
REBALANCE_DOF_VM ///< RebalanceDofMessage
NEIGHBOR_ELEMENT_RANK_VM, ///< NeighborElementRankMessage
NEIGHBOR_ORDER_VM, ///< NeighborOrderMessage
NEIGHBOR_DEREFINEMENT_VM, ///< NeighborDerefinementMessage
NEIGHBOR_REFINEMENT_VM, ///< NeighborRefinementMessage
NEIGHBOR_PREFINEMENT_VM, ///< NeighborPRefinementMessage
NEIGHBOR_ROW_VM, ///< NeighborRowMessage
REBALANCE_VM, ///< RebalanceMessage
REBALANCE_DOF_VM, ///< RebalanceDofMessage
};
/// \brief Variable-length MPI message containing unspecific binary data.
@@ -595,10 +607,50 @@ protected:
template <typename Type> struct MPITypeMap;
// Specializations of MPITypeMap; mpi_type initialized in communication.cpp:
template<> struct MPITypeMap<bool>
{
static MFEM_EXPORT const MPI_Datatype mpi_type;
};
template<> struct MPITypeMap<char>
{
static MFEM_EXPORT const MPI_Datatype mpi_type;
};
template<> struct MPITypeMap<unsigned char>
{
static MFEM_EXPORT const MPI_Datatype mpi_type;
};
template<> struct MPITypeMap<short>
{
static MFEM_EXPORT const MPI_Datatype mpi_type;
};
template<> struct MPITypeMap<unsigned short>
{
static MFEM_EXPORT const MPI_Datatype mpi_type;
};
template<> struct MPITypeMap<int>
{
static MFEM_EXPORT const MPI_Datatype mpi_type;
};
template<> struct MPITypeMap<unsigned int>
{
static MFEM_EXPORT const MPI_Datatype mpi_type;
};
template<> struct MPITypeMap<long>
{
static MFEM_EXPORT const MPI_Datatype mpi_type;
};
template<> struct MPITypeMap<unsigned long>
{
static MFEM_EXPORT const MPI_Datatype mpi_type;
};
template<> struct MPITypeMap<long long>
{
static MFEM_EXPORT const MPI_Datatype mpi_type;
};
template<> struct MPITypeMap<unsigned long long>
{
static MFEM_EXPORT const MPI_Datatype mpi_type;
};
template<> struct MPITypeMap<double>
{
static MFEM_EXPORT const MPI_Datatype mpi_type;
@@ -607,15 +659,6 @@ template<> struct MPITypeMap<float>
{
static MFEM_EXPORT const MPI_Datatype mpi_type;
};
template<> struct MPITypeMap<int64_t>
{
static MFEM_EXPORT const MPI_Datatype mpi_type;
};
template<> struct MPITypeMap<uint64_t>
{
static MFEM_EXPORT const MPI_Datatype mpi_type;
};
/** Reorder MPI ranks to follow the Z-curve within the physical machine topology
(provided that functions to query physical node coordinates are available).
+1 -1
View File
@@ -24,7 +24,7 @@ void mfem_cuda_error(cudaError_t err, const char *expr, const char *func,
const char *file, int line)
{
mfem::err << "\n\nCUDA error: (" << expr << ") failed with error:\n --> "
<< cudaGetErrorString(err)
<< cudaGetErrorString(err) << " [code: " << (int)err << ']'
<< "\n ... in function: " << func
<< "\n ... in file: " << file << ':' << line << '\n';
mfem_error();
+5 -5
View File
@@ -18,7 +18,7 @@
// CUDA block size used by MFEM.
#define MFEM_CUDA_BLOCKS 256
#ifdef MFEM_USE_CUDA
#if defined(MFEM_USE_CUDA) && defined(__CUDACC__)
#define MFEM_USE_CUDA_OR_HIP
#define MFEM_DEVICE __device__
#define MFEM_HOST __host__
@@ -37,10 +37,9 @@
__FILE__, __LINE__); \
} \
} while (0)
#endif // MFEM_USE_CUDA
// Define the MFEM inner threading macros
#if defined(MFEM_USE_CUDA) && defined(__CUDA_ARCH__)
#if defined(__CUDA_ARCH__)
#define MFEM_SHARED __shared__
#define MFEM_SYNC_THREAD __syncthreads()
#define MFEM_BLOCK_ID(k) blockIdx.k
@@ -48,12 +47,13 @@
#define MFEM_THREAD_SIZE(k) blockDim.k
#define MFEM_FOREACH_THREAD(i,k,N) for(int i=threadIdx.k; i<N; i+=blockDim.k)
#define MFEM_FOREACH_THREAD_DIRECT(i,k,N) if(const int i=threadIdx.k; i<N)
#endif
#endif // defined(__CUDA_ARCH__)
#endif // defined(MFEM_USE_CUDA) && defined(__CUDACC__)
namespace mfem
{
#ifdef MFEM_USE_CUDA
#if defined(MFEM_USE_CUDA) && defined(__CUDACC__)
// Function used by the macro MFEM_GPU_CHECK.
void mfem_cuda_error(cudaError_t err, const char *expr, const char *func,
const char *file, int line);
+1 -17
View File
@@ -151,22 +151,6 @@ 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()
@@ -631,7 +615,7 @@ void Device::Setup(const std::string &device_option, const int device_id)
if (Allows(Backend::DEBUG_DEVICE)) { ngpu = 1; }
}
MemoryType Device::QueryMemoryType(void *ptr)
MemoryType Device::QueryMemoryType(const void* ptr)
{
// from HYPRE's hypre_GetPointerLocation
MemoryType res = MemoryType::HOST;
+3 -3
View File
@@ -297,9 +297,9 @@ public:
/// Get the status of GPU-aware MPI flag.
static bool GetGPUAwareMPI() { return Get().mpi_gpu_aware; }
/** @brief Query the device driver for what memory type a given @a ptr is
allocated with. */
static MemoryType QueryMemoryType(void *ptr);
/** Query the device driver for what memory type a given @a ptr is allocated
* with. */
static MemoryType QueryMemoryType(const void* ptr);
/** @brief The number of hardware compute units/streaming multiprocessors
available on a given compute device @a device_id. */
+1 -1
View File
@@ -193,4 +193,4 @@ void mfem_warning(const char *msg)
}
}
} // namespace mfem
}
+2 -2
View File
@@ -176,7 +176,7 @@ __device__ void abort_msg(T & msg)
printf(__VA_ARGS__); \
asm("trap;"); \
}
#elif defined(MFEM_USE_HIP)
#elif defined(__HIP_DEVICE_COMPILE__)
#define MFEM_ABORT_KERNEL(...) \
{ \
printf(__VA_ARGS__); \
@@ -208,4 +208,4 @@ __device__ void abort_msg(T & msg)
#define MFEM_ASSERT_KERNEL(x,...)
#endif
#endif // MFEM_ERROR_HPP
#endif
+171 -12
View File
@@ -158,8 +158,8 @@ private:
#define MFEM_PRAGMA(X) _Pragma(#X)
// MFEM_UNROLL pragma macro that can be used inside MFEM_FORALL macros.
#if defined(MFEM_USE_CUDA) && defined(__CUDA_ARCH__)
#ifdef __NVCC__
#if defined(MFEM_USE_CUDA) && defined(__CUDA_ARCH__) // Clang cuda or nvcc
#ifdef __NVCC__ // nvcc specifically
#define MFEM_UNROLL(N) MFEM_PRAGMA(unroll(N))
#else // Assuming Clang CUDA
#define MFEM_UNROLL(N) MFEM_PRAGMA(unroll N)
@@ -169,12 +169,12 @@ private:
#endif
// MFEM_GPU_FORALL: "parallel for" executed with CUDA or HIP based on the MFEM
// build-time configuration (MFEM_USE_CUDA or MFEM_USE_HIP). If neither CUDA nor
// HIP is enabled, this macro is a no-op.
#if defined(MFEM_USE_CUDA)
// build-time configuration (MFEM_USE_CUDA or MFEM_USE_HIP), and if compiling
// with CUDA/HIP language. Otherwise, this macro is a no-op.
#if defined(MFEM_USE_CUDA) && defined(__CUDACC__)
#define MFEM_GPU_FORALL(i, N,...) CuWrap1D(N, [=] MFEM_DEVICE \
(int i) {__VA_ARGS__})
#elif defined(MFEM_USE_HIP)
#elif defined(MFEM_USE_HIP) && defined(__HIP__)
#define MFEM_GPU_FORALL(i, N,...) HipWrap1D(N, [=] MFEM_DEVICE \
(int i) {__VA_ARGS__})
#else
@@ -225,6 +225,52 @@ void OmpWrap(const int N, HBODY &&h_body)
#endif
}
template <typename HBODY>
void OmpWrap2D(const int Nx, const int Ny, HBODY &&h_body)
{
#ifdef MFEM_USE_OPENMP
// requires OpenMP 3.1
#pragma omp parallel for collapse(2)
for (int j = 0; j < Ny; j++)
{
for (int i = 0; i < Nx; i++)
{
h_body(i, j);
}
}
#else
MFEM_CONTRACT_VAR(Nx);
MFEM_CONTRACT_VAR(Ny);
MFEM_CONTRACT_VAR(h_body);
MFEM_ABORT("OpenMP requested for MFEM but OpenMP is not enabled!");
#endif
}
template <typename HBODY>
void OmpWrap3D(const int Nx, const int Ny, const int Nz, HBODY &&h_body)
{
#ifdef MFEM_USE_OPENMP
// requires OpenMP 3.1
#pragma omp parallel for collapse(3)
for (int k = 0; k < Nz; k++)
{
for (int j = 0; j < Ny; j++)
{
for (int i = 0; i < Nx; i++)
{
h_body(i, j, k);
}
}
}
#else
MFEM_CONTRACT_VAR(Nx);
MFEM_CONTRACT_VAR(Ny);
MFEM_CONTRACT_VAR(Nz);
MFEM_CONTRACT_VAR(h_body);
MFEM_ABORT("OpenMP requested for MFEM but OpenMP is not enabled!");
#endif
}
/// RAJA Cuda and Hip backends
#if defined(MFEM_USE_RAJA) && defined(RAJA_ENABLE_CUDA)
@@ -454,6 +500,42 @@ void RajaOmpWrap(const int N, HBODY &&h_body)
RAJA::forall<RAJA::omp_parallel_for_exec>(RAJA::RangeSegment(0,N), h_body);
}
template <typename HBODY>
void RajaOmpWrap2D(const int Nx, const int Ny, HBODY &&h_body)
{
using omp_launch_policy = RAJA::LaunchPolicy<RAJA::omp_launch_t>;
using global_thread_xy = RAJA::LoopPolicy<RAJA::omp_for_exec>;
RAJA::RangeSegment xrange(0, Nx);
RAJA::RangeSegment yrange(0, Ny);
RAJA::launch<omp_launch_policy>(RAJA::ExecPlace::HOST, RAJA::LaunchParams(),
[=](RAJA::LaunchContext ctx)
{
// contiguous in x
RAJA::expt::loop<global_thread_xy>(ctx, xrange, yrange, [&](int i, int j)
{
h_body(i, j);
});
});
}
template <typename HBODY>
void RajaOmpWrap3D(const int Nx, const int Ny, const int Nz, HBODY &&h_body)
{
using omp_launch_policy = RAJA::LaunchPolicy<RAJA::omp_launch_t>;
using global_thread_xyz = RAJA::LoopPolicy<RAJA::omp_for_exec>;
RAJA::RangeSegment xrange(0, Nx);
RAJA::RangeSegment yrange(0, Ny);
RAJA::RangeSegment zrange(0, Nz);
RAJA::launch<omp_launch_policy>(RAJA::ExecPlace::HOST, RAJA::LaunchParams(),
[=](RAJA::LaunchContext ctx)
{
// contiguous in x
RAJA::expt::loop<global_thread_xyz>(ctx, xrange, yrange, zrange,
[&](int i, int j, int k)
{ h_body(i, j, k); });
});
}
#endif
@@ -481,7 +563,7 @@ void RajaSeqWrap(const int N, HBODY &&h_body)
/// CUDA backend
#ifdef MFEM_USE_CUDA
#if defined(MFEM_USE_CUDA) && defined(__CUDACC__)
template <typename BODY> __global__ static
void CuKernel1D(const int N, BODY body)
@@ -573,11 +655,11 @@ struct CuWrap<3>
}
};
#endif // MFEM_USE_CUDA
#endif // defined(MFEM_USE_CUDA) && defined(__CUDACC__)
/// HIP backend
#ifdef MFEM_USE_HIP
#if defined(MFEM_USE_HIP) && defined(__HIP__)
template <typename BODY> __global__ static
void HipKernel1D(const int N, BODY body)
@@ -668,7 +750,7 @@ struct HipWrap<3>
}
};
#endif // MFEM_USE_HIP
#endif // defined(MFEM_USE_HIP) && defined(__HIP__)
/// The forall kernel body wrapper
@@ -701,7 +783,7 @@ inline void ForallWrap(const bool use_dev, const int N,
}
#endif
#ifdef MFEM_USE_CUDA
#if defined(MFEM_USE_CUDA) && defined(__CUDACC__)
// If Backend::CUDA is allowed, use it
if (Device::Allows(Backend::CUDA))
{
@@ -709,7 +791,7 @@ inline void ForallWrap(const bool use_dev, const int N,
}
#endif
#ifdef MFEM_USE_HIP
#if defined(MFEM_USE_HIP) && defined(__HIP__)
// If Backend::HIP is allowed, use it
if (Device::Allows(Backend::HIP))
{
@@ -753,6 +835,83 @@ inline void ForallWrap(const bool use_dev, const int N, lambda &&body,
template<typename lambda>
inline void forall(int N, lambda &&body) { ForallWrap<1>(true, N, body); }
template<typename lambda>
inline void forall(int Nx, int Ny, lambda &&body)
{
if (Device::Allows(Backend::DEVICE_MASK))
{
forall(Nx * Ny, [=] MFEM_HOST_DEVICE(int idx)
{
int j = idx / Nx;
int i = idx % Nx;
body(i, j);
});
}
#if defined(MFEM_USE_RAJA) && defined(RAJA_ENABLE_OPENMP)
else if (Device::Allows(Backend::RAJA_OMP))
{
return RajaOmpWrap2D(Nx, Ny, body);
}
#endif
#ifdef MFEM_USE_OPENMP
else if (Device::Allows(Backend::OMP))
{
return OmpWrap2D(Nx, Ny, body);
}
#endif
else
{
for (int j = 0; j < Ny; ++j)
{
for (int i = 0; i < Nx; ++i)
{
body(i, j);
}
}
}
}
template<typename lambda>
inline void forall(int Nx, int Ny, int Nz, lambda &&body)
{
if (Device::Allows(Backend::DEVICE_MASK))
{
forall(Nx * Ny * Nz, [=] MFEM_HOST_DEVICE(int idx)
{
int i = idx % Nx;
int j = idx / Nx;
int k = j / Ny;
j = j % Ny;
body(i, j, k);
});
}
#if defined(MFEM_USE_RAJA) && defined(RAJA_ENABLE_OPENMP)
else if (Device::Allows(Backend::RAJA_OMP))
{
return RajaOmpWrap3D(Nx, Ny, Nz, body);
}
#endif
#ifdef MFEM_USE_OPENMP
else if (Device::Allows(Backend::OMP))
{
return OmpWrap3D(Nx, Ny, Nz, body);
}
#endif
else
{
for (int k = 0; k < Nz; ++k)
{
for (int j = 0; j < Ny; ++j)
{
for (int i = 0; i < Nx; ++i)
{
body(i, j, k);
}
}
}
}
}
template<typename lambda>
inline void forall_switch(bool use_dev, int N, lambda &&body)
{
-4
View File
@@ -31,10 +31,6 @@ 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
int annotation_enabled = 1; // declared in annotation.hpp
}
void OutStream::Init()
+1 -1
View File
@@ -24,7 +24,7 @@ void mfem_hip_error(hipError_t err, const char *expr, const char *func,
const char *file, int line)
{
mfem::err << "\n\nHIP error: (" << expr << ") failed with error:\n --> "
<< hipGetErrorString(err)
<< hipGetErrorString(err) << " [code: " << (int)err << ']'
<< "\n ... in function: " << func
<< "\n ... in file: " << file << ':' << line << '\n';
mfem_error();
+4 -4
View File
@@ -18,7 +18,7 @@
// HIP block size used by MFEM.
#define MFEM_HIP_BLOCKS 256
#ifdef MFEM_USE_HIP
#if defined(MFEM_USE_HIP) && defined(__HIP__)
#define MFEM_USE_CUDA_OR_HIP
#define MFEM_DEVICE __device__
#define MFEM_HOST __host__
@@ -37,10 +37,9 @@
__FILE__, __LINE__); \
} \
} while (0)
#endif // MFEM_USE_HIP
// Define the MFEM inner threading macros
#if defined(MFEM_USE_HIP) && defined(__HIP_DEVICE_COMPILE__)
#if defined(__HIP_DEVICE_COMPILE__)
#define MFEM_SHARED __shared__
#define MFEM_SYNC_THREAD __syncthreads()
#define MFEM_BLOCK_ID(k) hipBlockIdx_ ##k
@@ -50,7 +49,8 @@
for(int i=hipThreadIdx_ ##k; i<N; i+=hipBlockDim_ ##k)
#define MFEM_FOREACH_THREAD_DIRECT(i,k,N) \
if(const int i=hipThreadIdx_ ##k; i<N)
#endif
#endif // defined(__HIP_DEVICE_COMPILE__)
#endif // defined(MFEM_USE_HIP) && defined(__HIP__)
namespace mfem
{
+2 -8
View File
@@ -513,10 +513,7 @@ public:
void *HtoD(void *dst, const void *src, size_t bytes) override
{ return HipMemcpyHtoD(dst, src, bytes); }
void *DtoD(void* dst, const void* src, size_t bytes) override
// Unlike cudaMemcpy(DtoD), hipMemcpy(DtoD) causes a host-side synchronization so
// instead we use hipMemcpyAsync to get similar behavior.
// for more info see: https://github.com/mfem/mfem/pull/2780
{ return HipMemcpyDtoDAsync(dst, src, bytes); }
{ return HipMemcpyDtoD(dst, src, bytes); }
void *DtoH(void *dst, const void *src, size_t bytes) override
{ return HipMemcpyDtoH(dst, src, bytes); }
};
@@ -658,10 +655,7 @@ public:
return CuMemcpyDtoD(dst, src, bytes);
#endif
#ifdef MFEM_USE_HIP
// Unlike cudaMemcpy(DtoD), hipMemcpy(DtoD) causes a host-side synchronization so
// instead we use hipMemcpyAsync to get similar behavior.
// for more info see: https://github.com/mfem/mfem/pull/2780
return HipMemcpyDtoDAsync(dst, src, bytes);
return HipMemcpyDtoD(dst, src, bytes);
#endif
// rm.copy(dst, const_cast<void*>(src), bytes); return dst;
}
+7 -7
View File
@@ -657,8 +657,7 @@ 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,
@@ -742,7 +741,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,
@@ -897,6 +896,7 @@ inline HYPRE_MemoryLocation GetHypreMemoryLocation()
#elif MFEM_HYPRE_VERSION < 23100
return HYPRE_MEMORY_DEVICE;
#else // HYPRE_USING_GPU is defined and MFEM_HYPRE_VERSION >= 23100
if (!HYPRE_Initialized()) { return HYPRE_MEMORY_HOST; }
HYPRE_MemoryLocation loc;
HYPRE_GetMemoryLocation(&loc);
return loc;
@@ -981,7 +981,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_((void*)h_ptr);
MemoryType h_ptr_mt = MemoryManager::GetHostMemoryType_(h_ptr);
MFEM_VERIFY(h_mt == h_ptr_mt,
"h_mt = " << (int)h_mt << ", h_ptr_mt = " << (int)h_ptr_mt);
}
@@ -989,8 +989,7 @@ 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_((void*)ptr, (void*)ptr, bytes, h_mt, own, false,
flags);
MemoryManager::Register_(ptr, ptr, bytes, h_mt, own, false, flags);
}
}
@@ -1059,7 +1058,8 @@ inline void Memory<T>::MakeAlias(const Memory &base, int offset, int size)
// register the 'base' if the MemoryManager::Exists():
MemoryManager::Exists()
#else // HYPRE_USING_GPU is defined and MFEM_HYPRE_VERSION >= 23100
MemoryManager::Exists() && HypreUsingGPU()
IsDeviceMemory(MemoryManager::GetDeviceMemoryType()) ||
(MemoryManager::Exists() && HypreUsingGPU())
#endif
)
{
+1 -1
View File
@@ -537,7 +537,7 @@ void reduce(int N, T &res, B &&body, const R &reducer, bool use_dev,
return;
}
#if defined(MFEM_USE_HIP) || defined(MFEM_USE_CUDA)
#if defined(MFEM_USE_CUDA_OR_HIP)
if (use_dev &&
mfem::Device::Allows(Backend::CUDA | Backend::HIP | Backend::RAJA_CUDA |
Backend::RAJA_HIP))
+176
View File
@@ -0,0 +1,176 @@
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#ifndef MFEM_SCAN_HPP
#define MFEM_SCAN_HPP
#ifdef MFEM_USE_CUDA
#include <cub/device/device_scan.cuh>
#define MFEM_CUB_NAMESPACE cub
#elif MFEM_USE_HIP
#include <hipcub/device/device_scan.hpp>
#define MFEM_CUB_NAMESPACE hipcub
#endif
#include <functional>
#include <numeric>
namespace mfem
{
/// Equivalent to InclusiveScan(use_dev, d_in, d_out, num_items, workspace,
/// std::plus<>{})
template <class InputIt, class OutputIt>
void InclusiveScan(bool use_dev, InputIt d_in, OutputIt d_out, size_t num_items,
Array<char> &workspace)
{
// forward to InclusiveSum for potentially faster kernels
#if defined(MFEM_USE_CUDA) || defined(MFEM_USE_HIP)
if (use_dev && mfem::Device::Allows(Backend::CUDA_MASK | Backend::HIP_MASK))
{
size_t bytes = workspace.Size();
if (bytes)
{
auto err = MFEM_CUB_NAMESPACE::DeviceScan::InclusiveSum(
workspace.Write(), bytes, d_in, d_out, num_items);
#if defined(MFEM_USE_CUDA)
if (err == cudaSuccess)
{
return;
}
#elif defined(MFEM_USE_HIP)
if (err == hipSuccess)
{
return;
}
#endif
}
// try allocating a larger buffer
bytes = 0;
MFEM_GPU_CHECK(MFEM_CUB_NAMESPACE::DeviceScan::InclusiveSum(
nullptr, bytes, d_in, d_out, num_items));
workspace.SetSize(bytes);
MFEM_GPU_CHECK(MFEM_CUB_NAMESPACE::DeviceScan::InclusiveSum(
workspace.Write(), bytes, d_in, d_out, num_items));
return;
}
#endif
std::inclusive_scan(d_in, d_in + num_items, d_out);
}
/// Performs an inclusive scan of [d_in, d_in+num_items) -> [d_out,
/// d_out+num_items). This call is potentially asynchronous on the device.
/// @a d_in input start.
/// @a d_out output start. Can perform in-place scans with d_out = d_in
/// @a workspace temporary workspace used for device scans. TODO: replace with
/// internal temporary workspace once that's added to the memory manager.
/// @a scan_op binary scan functor. Must be associative. If only weakly
/// associative (i.e. floating point addition) results are not deterministic. On
/// device this must also be commutative.
template <class InputIt, class OutputIt, class ScanOp>
void InclusiveScan(bool use_dev, InputIt d_in, OutputIt d_out, size_t num_items,
Array<char> &workspace, ScanOp scan_op)
{
#if defined(MFEM_USE_CUDA) || defined(MFEM_USE_HIP)
if (use_dev && mfem::Device::Allows(Backend::CUDA_MASK | Backend::HIP_MASK))
{
size_t bytes = workspace.Size();
if (bytes)
{
auto err = MFEM_CUB_NAMESPACE::DeviceScan::InclusiveScan(
workspace.Write(), bytes, d_in, d_out, scan_op, num_items);
#if defined(MFEM_USE_CUDA)
if (err == cudaSuccess)
{
return;
}
#elif defined(MFEM_USE_HIP)
if (err == hipSuccess)
{
return;
}
#endif
}
// try allocating a larger buffer
bytes = 0;
MFEM_GPU_CHECK(MFEM_CUB_NAMESPACE::DeviceScan::InclusiveScan(
nullptr, bytes, d_in, d_out, scan_op, num_items));
workspace.SetSize(bytes);
MFEM_GPU_CHECK(MFEM_CUB_NAMESPACE::DeviceScan::InclusiveScan(
workspace.Write(), bytes, d_in, d_out, scan_op, num_items));
return;
}
#endif
std::inclusive_scan(d_in, d_in + num_items, d_out, scan_op);
}
/// Performs an exclusive scan of [d_in, d_in+num_items) -> [d_out,
/// d_out+num_items). This call is potentially asynchronous on the device.
/// @a d_in input start.
/// @a d_out output start. Can perform in-place scans with d_out = d_in
/// @a workspace temporary workspace used for device scans. TODO: replace with
/// internal temporary workspace once that's added to the memory manager.
/// @a scan_op binary scan functor. Must be associative. If only weakly
/// associative (i.e. floating point addition) results are not deterministic. On
/// device this must also be commutative.
template <class InputIt, class OutputIt, class T, class ScanOp>
void ExclusiveScan(bool use_dev, InputIt d_in, OutputIt d_out, size_t num_items,
T init_value, Array<char> &workspace, ScanOp scan_op)
{
#if defined(MFEM_USE_CUDA) || defined(MFEM_USE_HIP)
if (use_dev && mfem::Device::Allows(Backend::CUDA_MASK | Backend::HIP_MASK))
{
size_t bytes = workspace.Size();
if (bytes)
{
auto err = MFEM_CUB_NAMESPACE::DeviceScan::ExclusiveScan(
workspace.Write(), bytes, d_in, d_out, scan_op, init_value,
num_items);
#if defined(MFEM_USE_CUDA)
if (err == cudaSuccess)
{
return;
}
#elif defined(MFEM_USE_HIP)
if (err == hipSuccess)
{
return;
}
#endif
}
// try allocating a larger buffer
bytes = 0;
MFEM_GPU_CHECK(MFEM_CUB_NAMESPACE::DeviceScan::ExclusiveScan(
nullptr, bytes, d_in, d_out, scan_op, init_value, num_items));
workspace.SetSize(bytes);
MFEM_GPU_CHECK(MFEM_CUB_NAMESPACE::DeviceScan::ExclusiveScan(
workspace.Write(), bytes, d_in, d_out, scan_op, init_value,
num_items));
return;
}
#endif
std::exclusive_scan(d_in, d_in + num_items, d_out, init_value, scan_op);
}
/// Equivalent to ExclusiveScan(use_dev, d_in, d_out, num_items, init_value,
/// workspace, std::plus<>{})
template <class InputIt, class OutputIt, class T>
void ExclusiveScan(bool use_dev, InputIt d_in, OutputIt d_out, size_t num_items,
T init_value, Array<char> &workspace)
{
ExclusiveScan(use_dev, d_in, d_out, num_items, init_value, workspace,
std::plus<> {});
}
} // namespace mfem
#undef MFEM_CUB_NAMESPACE
#endif
+4 -4
View File
@@ -20,13 +20,13 @@
#define MFEM_CU_or_HIP(stub) HIP##stub
#endif
#define MFEM_CONCAT3(x, y, z) MFEM_CONCAT3_(x, y, z)
#define MFEM_CONCAT3_(x, y, z) x ## y ## z
#define MFEM_CONCAT(x, y, z) MFEM_CONCAT_(x, y, z)
#define MFEM_CONCAT_(x, y, z) x ## y ## z
#ifdef MFEM_USE_SINGLE
#define MFEM_GPUBLAS_PREFIX(stub) MFEM_CONCAT3(MFEM_cu_or_hip(blas), S, stub)
#define MFEM_GPUBLAS_PREFIX(stub) MFEM_CONCAT(MFEM_cu_or_hip(blas), S, stub)
#elif defined(MFEM_USE_DOUBLE)
#define MFEM_GPUBLAS_PREFIX(stub) MFEM_CONCAT3(MFEM_cu_or_hip(blas), D, stub)
#define MFEM_GPUBLAS_PREFIX(stub) MFEM_CONCAT(MFEM_cu_or_hip(blas), D, stub)
#endif
#define MFEM_BLAS_SUCCESS MFEM_CU_or_HIP(BLAS_STATUS_SUCCESS)
+28
View File
@@ -4405,4 +4405,32 @@ void BatchLUSolve(const DenseTensor &Mlu, const Array<int> &P, Vector &X)
BatchedLinAlg::LUSolve(Mlu, P, X);
}
#ifdef MFEM_USE_LAPACK
void BandedSolve(int KL, int KU, DenseMatrix &AB, DenseMatrix &B,
Array<int> &ipiv)
{
int LDAB = (2*KL) + KU + 1;
int N = AB.NumCols();
int NRHS = B.NumCols();
int info;
ipiv.SetSize(N);
MFEM_LAPACK_PREFIX(gbsv_)(&N, &KL, &KU, &NRHS, AB.GetData(), &LDAB,
ipiv.GetData(), B.GetData(), &N, &info);
MFEM_ASSERT(info == 0, "BandedSolve failed in LAPACK");
}
void BandedFactorizedSolve(int KL, int KU, DenseMatrix &AB, DenseMatrix &B,
bool transpose, Array<int> &ipiv)
{
int LDAB = (2*KL) + KU + 1;
int N = AB.NumCols();
int NRHS = B.NumCols();
char trans = transpose ? 'T' : 'N';
int info;
MFEM_LAPACK_PREFIX(gbtrs_)(&trans, &N, &KL, &KU, &NRHS, AB.GetData(), &LDAB,
ipiv.GetData(), B.GetData(), &N, &info);
MFEM_ASSERT(info == 0, "BandedFactorizedSolve failed in LAPACK");
}
#endif
} // namespace mfem
+7
View File
@@ -1329,6 +1329,13 @@ void BatchLUFactor(DenseTensor &Mlu, Array<int> &P, const real_t TOL = 0.0);
dimension m x n. */
void BatchLUSolve(const DenseTensor &Mlu, const Array<int> &P, Vector &X);
#ifdef MFEM_USE_LAPACK
void BandedSolve(int KL, int KU, DenseMatrix &AB, DenseMatrix &B,
Array<int> &ipiv);
void BandedFactorizedSolve(int KL, int KU, DenseMatrix &AB, DenseMatrix &B,
bool transpose, Array<int> &ipiv);
#endif
// Inline methods
inline real_t &DenseMatrix::operator()(int i, int j)
-4
View File
@@ -4091,8 +4091,6 @@ 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);
@@ -4118,8 +4116,6 @@ 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)
{
+7
View File
@@ -42,6 +42,13 @@ extern "C" void
MFEM_LAPACK_PREFIX(getri_)(int *N, real_t *A, int *LDA, int *IPIV, real_t *WORK,
int *LWORK, int *INFO);
extern "C" void
MFEM_LAPACK_PREFIX(gbsv_)(int *, int *, int *, int *, real_t *, int *, int *,
real_t *, int *, int *);
extern "C" void
MFEM_LAPACK_PREFIX(gbtrs_)(char *, int *, int *, int *, int *, real_t *, int *,
int *, real_t *, int *, int *);
extern "C" void
MFEM_LAPACK_PREFIX(syevr_)(char *JOBZ, char *RANGE, char *UPLO, int *N,
real_t *A, int *LDA, real_t *VL, real_t *VU, int *IL,
int *IU, real_t *ABSTOL, int *M, real_t *W,
+6 -8
View File
@@ -50,19 +50,17 @@ void Operator::InitTVectors(const Operator *Po, const Operator *Ri,
void Operator::AddMult(const Vector &x, Vector &y, const real_t a) const
{
z_am.SetSize(y.Size());
z_am.UseDevice(true);
Mult(x, z_am);
y.Add(a, z_am);
mfem::Vector z(y.Size());
Mult(x, z);
y.Add(a, z);
}
void Operator::AddMultTranspose(const Vector &x, Vector &y,
const real_t a) const
{
z_am.SetSize(y.Size());
z_am.UseDevice(true);
MultTranspose(x, z_am);
y.Add(a, z_am);
mfem::Vector z(y.Size());
MultTranspose(x, z);
y.Add(a, z);
}
void Operator::ArrayMult(const Array<const Vector *> &X,
-7
View File
@@ -23,13 +23,6 @@ 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.
+38 -81
View File
@@ -314,29 +314,25 @@ 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
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]);
});
subtract(x, residual, residual); // r = x - A y
}
else
{
auto Y = y.Write();
// y = D^{-1} x
mfem::forall(height, [=] MFEM_HOST_DEVICE (int i)
{
Y[i] = DI[i] * X[i];
});
residual = x;
y.UseDevice(true);
y = 0.0;
}
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_,
@@ -352,8 +348,7 @@ OperatorChebyshevSmoother::OperatorChebyshevSmoother(const Operator &oper_,
diag(d),
coeffs(order),
ess_tdof_list(ess_tdofs),
residual(order > 1 ? N : 0),
z(order > 1 ? N : 0),
residual(N),
oper(&oper_) { Setup(); }
#ifdef MFEM_USE_MPI
@@ -380,8 +375,7 @@ OperatorChebyshevSmoother::OperatorChebyshevSmoother(const Operator &oper_,
diag(d),
coeffs(order),
ess_tdof_list(ess_tdofs),
residual(order > 1 ? N : 0),
z(order > 1 ? N : 0),
residual(N),
oper(&oper_)
{
OperatorJacobiSmoother invDiagOperator(diag, ess_tdofs, 1.0);
@@ -428,7 +422,7 @@ void OperatorChebyshevSmoother::Setup()
{
// Invert diagonal
residual.UseDevice(true);
z.UseDevice(true);
helperVector.UseDevice(true);
auto D = diag.Read();
auto X = dinv.Write();
mfem::forall(N, [=] MFEM_HOST_DEVICE (int i) { X[i] = 1.0 / D[i]; });
@@ -438,20 +432,6 @@ 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.
@@ -531,55 +511,32 @@ void OperatorChebyshevSmoother::Mult(const Vector& x, Vector &y) const
MFEM_ABORT("Chebyshev smoother requires operator");
}
// 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)
{
mfem::forall(N, [=] MFEM_HOST_DEVICE (int i)
{
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]);
});
}
residual = x;
helperVector.SetSize(x.Size());
helperVector.UseDevice(true);
for (int k = 1; k < order; ++k)
{
// Apply: z = A r
oper->Mult(residual, z);
y.UseDevice(true);
y = 0.0;
// 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();
for (int k = 0; k < order; ++k)
{
// Apply
if (k > 0)
{
oper->Mult(residual, helperVector);
residual = helperVector;
}
// 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]; });
// Add weighted contribution to y
auto Y = y.ReadWrite();
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];
});
}
auto C = coeffs.Read();
mfem::forall(n, [=] MFEM_HOST_DEVICE (int i) { Y[i] += C[k] * R[i]; });
}
}
@@ -3256,7 +3213,7 @@ void ResidualBCMonitor::MonitorResidual(
MPI_Comm comm = iter_solver->GetComm();
if (comm != MPI_COMM_NULL)
{
real_t glob_bc_norm_squared = 0.0;
double 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);
+8 -9
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 override;
void Mult(const Vector &x, Vector &y) const;
/** @brief Approach the solution of the transposed linear system by applying
Jacobi smoothing. */
void MultTranspose(const Vector &x, Vector &y) const override { Mult(x, y); }
void MultTranspose(const Vector &x, Vector &y) const { 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) override;
void SetOperator(const Operator &op);
private:
Vector dinv;
@@ -481,22 +481,21 @@ public:
/** @brief Approach the solution of the linear system by applying Chebyshev
smoothing. */
void Mult(const Vector &x, Vector &y) const override;
void Mult(const Vector &x, Vector &y) const;
/** @brief Approach the solution of the transposed linear system by applying
Chebyshev smoothing. */
void MultTranspose(const Vector &x, Vector &y) const override { Mult(x, y); }
void MultTranspose(const Vector &x, Vector &y) const { Mult(x, y); }
void SetOperator(const Operator &op_) override
void SetOperator(const Operator &op_)
{
oper = &op_;
}
void Setup();
void SetOrder(int new_order);
private:
int order;
const int order;
real_t max_eig_estimate;
const int N;
Vector dinv;
@@ -504,7 +503,7 @@ private:
Array<real_t> coeffs;
const Array<int>& ess_tdof_list;
mutable Vector residual;
mutable Vector z;
mutable Vector helperVector;
const Operator* oper;
};
+42 -1
View File
@@ -14,6 +14,7 @@
#include "../general/forall.hpp"
#include "../general/reducers.hpp"
#include "../general/hash.hpp"
#include "../general/scan.hpp"
#include "vector.hpp"
#ifdef MFEM_USE_OPENMP
@@ -117,7 +118,7 @@ Vector::Vector(const Vector &v)
UseDevice(v.UseDevice());
}
Vector::Vector(Vector &&v)
Vector::Vector(Vector &&v) : Vector()
{
*this = std::move(v);
}
@@ -1252,4 +1253,44 @@ real_t Vector::Sum() const
return res;
}
void Vector::DeleteAt(const Array<int> &indices)
{
const bool use_dev = UseDevice();
Array<int> flag(size);
const auto d_flag = flag.Write(use_dev);
mfem::forall_switch(use_dev, size, [=] MFEM_HOST_DEVICE (int i)
{
d_flag[i] = true;
});
const auto d_indices = indices.Read(use_dev);
mfem::forall_switch(use_dev, indices.Size(), [=] MFEM_HOST_DEVICE (int i)
{
d_flag[d_indices[i]] = false;
});
Array<int> out_idx(size);
auto d_out_idx = out_idx.Write(use_dev);
Array<char> workspace;
// Perform inclusive scan so that the last entry is the new size.
InclusiveScan(use_dev, d_flag, d_out_idx, size, workspace);
Vector copy(*this);
auto d_in = copy.Read(use_dev);
auto d_out = Write(use_dev);
mfem::forall_switch(use_dev, size, [=] MFEM_HOST_DEVICE (int i)
{
if (d_flag[i])
{
// Transform inclusive scan to exclusive by shifting.
const int j = (i > 0) ? d_out_idx[i - 1] : 0;
d_out[j] = d_in[i];
}
});
// Get the new size of the vector. Copy only the last entry.
Memory<int> submem(out_idx.GetMemory(), out_idx.Size() - 1, 1);
size = submem.Read(MemoryClass::HOST, 1)[0];
}
} // namespace mfem
+18
View File
@@ -171,6 +171,12 @@ public:
/// Resize the vector to size @a s using the MemoryType of @a v.
void SetSize(int s, const Vector &v) { SetSize(s, v.GetMemory().GetMemoryType()); }
/// Update \ref Capacity() to @a res (if less than current), keeping existing entries.
void Reserve(int res);
/// Delete entries at @a indices and resize vector accordingly.
void DeleteAt(const Array<int> &indices);
/// Set the Vector data.
/// @warning This method should be called only when OwnsData() is false.
void SetData(real_t *d) { data.Wrap(d, data.Capacity(), false); }
@@ -621,6 +627,18 @@ inline void Vector::SetSize(int s, MemoryType mt)
data.UseDevice(use_dev);
}
inline void Vector::Reserve(int res)
{
if (res > Capacity())
{
Memory<real_t> p(res, data.GetMemoryType());
p.CopyFrom(data, size);
p.UseDevice(data.UseDevice());
data.Delete();
data = p;
}
}
inline void Vector::NewMemoryAndSize(const Memory<real_t> &mem, int s,
bool own_mem)
{
+5 -8
View File
@@ -125,8 +125,7 @@ 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 \
benchmarks/ceed-solver-bps
hooke multidomain dpg hdiv-linear-solver spde diag-smoothers
MINIAPP_DIRS := $(addprefix miniapps/,$(MINIAPP_SUBDIRS))
MINIAPP_TEST_DIRS := $(filter-out %/common,$(MINIAPP_DIRS))
MINIAPP_USE_COMMON := $(addprefix miniapps/,electromagnetics meshing tools \
@@ -146,7 +145,7 @@ MFEM_BUILD_DIR ?= .
BUILD_DIR := $(MFEM_BUILD_DIR)
BUILD_REAL_DIR := $(abspath $(BUILD_DIR))
ifneq ($(BUILD_REAL_DIR),$(MFEM_REAL_DIR))
BUILD_SUBDIRS = $(DIRS) config $(EM_DIRS) doc $(TEST_DIRS) tests/benchmarks
BUILD_SUBDIRS = $(DIRS) config $(EM_DIRS) doc $(TEST_DIRS)
CONFIG_FILE_DEF = -DMFEM_CONFIG_FILE='"$(BUILD_REAL_DIR)/config/_config.hpp"'
BLD := $(if $(BUILD_REAL_DIR:$(CURDIR)=),$(BUILD_DIR)/,)
$(if $(word 2,$(BLD)),$(error Spaces in BLD = "$(BLD)" are not supported))
@@ -583,14 +582,13 @@ test-print:
@for dir in $(ALL_TEST_DIRS); do \
$(MAKE) -j1 -C $(BLD)$${dir} test-print; done
ALL_CLEAN_SUBDIRS = $(addsuffix /clean,config $(EM_DIRS) doc $(TEST_DIRS) \
tests/benchmarks)
ALL_CLEAN_SUBDIRS = $(addsuffix /clean,config $(EM_DIRS) doc $(TEST_DIRS))
.PHONY: $(ALL_CLEAN_SUBDIRS) miniapps/clean
miniapps/clean: $(addsuffix /clean,$(MINIAPP_DIRS))
$(ALL_CLEAN_SUBDIRS):
$(MAKE) -C $(BLD)$(@D) $(@F)
clean: $(addsuffix /clean,$(EM_DIRS) $(TEST_DIRS) tests/benchmarks)
clean: $(addsuffix /clean,$(EM_DIRS) $(TEST_DIRS))
rm -f $(addprefix $(BLD),$(foreach d,$(DIRS),$(d)/*.o))
rm -f $(addprefix $(BLD),$(foreach d,$(DIRS),$(d)/*~))
rm -rf $(addprefix $(BLD),*~ libmfem.* deps.mk)
@@ -693,8 +691,7 @@ local-config:
.PHONY: build-config
build-config:
for d in $(BUILD_SUBDIRS); do mkdir -p $(BLD)$${d}; done
for dir in "" $(addsuffix /,config $(EM_DIRS) doc $(TEST_DIRS) \
tests/benchmarks); do \
for dir in "" $(addsuffix /,config $(EM_DIRS) doc $(TEST_DIRS)); do \
printf "# Auto-generated file.\n%s\n%s\n" \
"MFEM_DIR = $(MFEM_REAL_DIR)" \
"include \$$(MFEM_DIR)/$${dir}makefile" \
+167 -28
View File
@@ -32,6 +32,7 @@
#include <cstring>
#include <ctime>
#include <functional>
#include <set>
#include <numeric>
#include <unordered_map>
#include <unordered_set>
@@ -883,8 +884,6 @@ const GeometricFactors* Mesh::GetGeometricFactors(const IntegrationRule& ir,
const int flags,
MemoryType d_mt)
{
MFEM_PERF_FUNCTION;
for (int i = 0; i < geom_factors.Size(); i++)
{
GeometricFactors *gf = geom_factors[i];
@@ -923,6 +922,103 @@ 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::ComputeFaceInfo(FaceType ftype) const
{
auto &fidcs = face_indices[static_cast<int>(ftype)];
auto &ifidcs = inv_face_indices[static_cast<int>(ftype)];
fidcs.SetSize(GetNFbyType(ftype));
fidcs.HostWrite();
ifidcs.reserve(fidcs.Size());
int f_idx = 0;
for (int i = 0; i < GetNumFacesWithGhost(); ++i)
{
const FaceInformation face = GetFaceInformation(i);
if (face.IsNonconformingCoarse() || !face.IsOfFaceType(ftype))
{
continue;
}
fidcs[f_idx] = i;
ifidcs[i] = f_idx;
++f_idx;
}
}
const Array<int> &Mesh::GetFaceIndices(FaceType ftype) const
{
if (face_indices[static_cast<int>(ftype)].Size() == 0)
{
ComputeFaceInfo(ftype);
}
return face_indices[static_cast<int>(ftype)];
}
const std::unordered_map<int, int> &
Mesh::GetInvFaceIndices(FaceType ftype) const
{
if (inv_face_indices[static_cast<int>(ftype)].empty())
{
ComputeFaceInfo(ftype);
}
return inv_face_indices[static_cast<int>(ftype)];
}
void Mesh::DeleteGeometricFactors()
{
for (int i = 0; i < geom_factors.Size(); i++)
@@ -1806,8 +1902,15 @@ void Mesh::Destroy()
TetMemory.Clear();
#endif
elem_attrs_cache.DeleteAll();
bdr_face_attrs_cache.DeleteAll();
attributes.DeleteAll();
bdr_attributes.DeleteAll();
face_indices[0].DeleteAll();
face_indices[1].DeleteAll();
inv_face_indices[0] = std::unordered_map<int, int>();
inv_face_indices[1] = std::unordered_map<int, int>();
}
void Mesh::ResetLazyData()
@@ -1819,36 +1922,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!");
}
}
}
@@ -4456,6 +4571,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()
@@ -7723,6 +7842,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);
}
@@ -8057,6 +8182,12 @@ void Mesh::GenerateFaces()
FreeElement(f);
}
// delete caches
face_indices[0].SetSize(0);
face_indices[1].SetSize(0);
inv_face_indices[0].clear();
inv_face_indices[1].clear();
// (re)generate the interior faces and the info for them
faces.SetSize(nfaces);
faces_info.SetSize(nfaces);
@@ -10852,6 +10983,15 @@ 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);
mfem::Swap(face_indices[0], other.face_indices[0]);
mfem::Swap(face_indices[1], other.face_indices[1]);
inv_face_indices[0].swap(other.inv_face_indices[0]);
inv_face_indices[1].swap(other.inv_face_indices[1]);
}
void Mesh::GetElementData(const Array<Element*> &elem_array, int geom,
@@ -13259,7 +13399,7 @@ void Mesh::ScaleElements(real_t sf)
delete [] vn;
}
void Mesh::Transform(void (*f)(const Vector&, Vector&))
void Mesh::Transform(std::function<void(const Vector &, Vector&)> f)
{
// TODO: support for different new spaceDim.
if (Nodes == NULL)
@@ -13272,7 +13412,7 @@ void Mesh::Transform(void (*f)(const Vector&, Vector&))
vold(j) = vertices[i](j);
}
vnew.SetData(vertices[i]());
(*f)(vold, vnew);
f(vold, vnew);
}
}
else
@@ -14651,7 +14791,6 @@ 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();
+61 -11
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(),
@@ -273,6 +278,13 @@ protected:
// used during NC mesh initialization only
Array<Triple<int, int, int> > tmp_vertex_parents;
/// cache for FaceIndices(ftype)
mutable Array<int> face_indices[2];
/// cache for FaceIndices(ftype)
mutable std::unordered_map<int, int> inv_face_indices[2];
/// compute face_indices[ftype] and inv_face_indices[type]
void ComputeFaceInfo(FaceType ftype) const;
public:
typedef Geometry::Constants<Geometry::SEGMENT> seg_t;
@@ -307,6 +319,11 @@ public:
// (true) is set in mesh_readers.cpp.
static bool remove_unused_vertices;
/// Map from boundary or interior face indices to mesh face indices.
const Array<int>& GetFaceIndices(FaceType ftype) const;
/// Inverse of the map FaceIndices(ftype)
const std::unordered_map<int, int>& GetInvFaceIndices(FaceType ftype) const;
protected:
Operation last_operation;
@@ -588,9 +605,10 @@ protected:
void Loader(std::istream &input, int generate_edges = 0,
std::string parse_tag = "");
/** If NURBS mesh, write NURBS format. If NCMesh, write mfem v1.1 format.
If section_delimiter is empty, write mfem v1.0 format. Otherwise, write
mfem v1.2 format with the given section_delimiter at the end.
/** @brief If NURBS mesh, write NURBS format. If NCMesh, write mfem v1.1
format. If section_delimiter is empty, write mfem v1.0 format. Otherwise,
write mfem v1.2 format with the given section_delimiter at the end.
If @a comments is non-empty, it will be printed after the first line of
the file, and each line should begin with '#'. */
void Printer(std::ostream &os = mfem::out,
@@ -1121,13 +1139,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
@@ -2253,7 +2272,7 @@ public:
void ScaleSubdomains (real_t sf);
void ScaleElements (real_t sf);
void Transform(void (*f)(const Vector&, Vector&));
void Transform(std::function<void(const Vector &, Vector&)> f);
void Transform(VectorCoefficient &deformation);
/** @brief This function should be called after the mesh node coordinates
@@ -2266,6 +2285,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
@@ -2482,10 +2530,12 @@ public:
/// Print the mesh to the given stream using Netgen/Truegrid format.
virtual void PrintXG(std::ostream &os = mfem::out) const;
/// Print the mesh to the given stream using the default MFEM mesh format.
/// \see mfem::ofgzstream() for on-the-fly compression of ascii outputs. If
/// @a comments is non-empty, it will be printed after the first line of the
/// file, and each line should begin with '#'.
/** @brief Print the mesh to the given stream using the default MFEM mesh
format.
\see mfem::ofgzstream() for on-the-fly compression of ascii outputs. If
@a comments is non-empty, it will be printed after the first line of the
file, and each line should begin with '#'. */
virtual void Print(std::ostream &os = mfem::out,
const std::string &comments = "") const
{ Printer(os, "", comments); }
+85 -21
View File
@@ -53,7 +53,7 @@ KnotVector::KnotVector(int order, int NCP)
}
KnotVector::KnotVector(int order, const Vector& intervals,
const Array<int>& continuity )
const Array<int>& continuity)
{
// NOTE: This may need to be generalized to support periodicity
// in the future.
@@ -151,7 +151,7 @@ void KnotVector::UniformRefinement(Vector &newknots, int rf) const
{
for (int m = 1; m < rf; ++m)
{
newknots(j) = m * h * (knot(i) + knot(i+1));
newknots(j) = ((1.0 - (m * h)) * knot(i)) + (m * h * knot(i+1));
j++;
}
}
@@ -340,7 +340,7 @@ void KnotVector::PrintFunctions(std::ostream &os, int samples) const
}
}
// Routine from "The NURBS book" - 2nd ed - Piegl and Tiller
// Routine from "The NURBS Book" - 2nd ed - Piegl and Tiller
// Algorithm A2.2 p. 70
void KnotVector::CalcShape(Vector &shape, int i, real_t xi) const
{
@@ -367,7 +367,7 @@ void KnotVector::CalcShape(Vector &shape, int i, real_t xi) const
}
}
// Routine from "The NURBS book" - 2nd ed - Piegl and Tiller
// Routine from "The NURBS Book" - 2nd ed - Piegl and Tiller
// Algorithm A2.3 p. 72
void KnotVector::CalcDShape(Vector &grad, int i, real_t xi) const
{
@@ -425,7 +425,7 @@ void KnotVector::CalcDShape(Vector &grad, int i, real_t xi) const
}
}
// Routine from "The NURBS book" - 2nd ed - Piegl and Tiller
// Routine from "The NURBS Book" - 2nd ed - Piegl and Tiller
// Algorithm A2.3 p. 72
void KnotVector::CalcDnShape(Vector &gradn, int n, int i, real_t xi) const
{
@@ -545,11 +545,11 @@ void KnotVector::FindMaxima(Array<int> &ks, Vector &xi, Vector &u) const
int i = j - d;
if (isElement(i))
{
arg1 = 1e-16;
arg1 = std::numeric_limits<real_t>::epsilon() / 2_r;
CalcShape(shape, i, arg1);
max1 = shape[d];
arg2 = 1-(1e-16);
arg2 = 1_r - arg1;
CalcShape(shape, i, arg2);
max2 = shape[d];
@@ -587,9 +587,9 @@ void KnotVector::FindMaxima(Array<int> &ks, Vector &xi, Vector &u) const
}
}
// Routine from "The NURBS book" - 2nd ed - Piegl and Tiller
// Routine from "The NURBS Book" - 2nd ed - Piegl and Tiller
// Algorithm A9.1 p. 369
void KnotVector::FindInterpolant(Array<Vector*> &x)
void KnotVector::FindInterpolant(Array<Vector*> &x, bool reuse_inverse)
{
int order = GetOrder();
int ncp = GetNCP();
@@ -597,29 +597,93 @@ void KnotVector::FindInterpolant(Array<Vector*> &x)
// Find interpolation points
Vector xi_args, u_args;
Array<int> i_args;
FindMaxima(i_args,xi_args, u_args);
FindMaxima(i_args, xi_args, u_args);
// Assemble collocation matrix
Vector shape(order+1);
DenseMatrix A(ncp,ncp);
A = 0.0;
#ifdef MFEM_USE_LAPACK
// If using LAPACK, we use banded matrix storage (order + 1 nonzeros per row).
// Find banded structure of matrix.
int KL = 0; // Number of subdiagonals
int KU = 0; // Number of superdiagonals
for (int i = 0; i < ncp; i++)
{
CalcShape(shape, i_args[i], xi_args[i]);
for (int p = 0; p < order+1; p++)
{
A(i,i_args[i] + p) = shape[p];
const int col = i_args[i] + p;
if (col < i)
{
KL = std::max(KL, i - col);
}
else if (i < col)
{
KU = std::max(KU, col - i);
}
}
}
// Solve problems
A.Invert();
const int LDAB = (2*KL) + KU + 1;
const int N = ncp;
fact_AB.SetSize(LDAB, N);
#else
// Without LAPACK, we store and invert a DenseMatrix (inefficient).
if (!reuse_inverse)
{
A_coll_inv.SetSize(ncp, ncp);
A_coll_inv = 0.0;
}
#endif
Vector shape(order+1);
if (!reuse_inverse) // Set collocation matrix entries
{
for (int i = 0; i < ncp; i++)
{
CalcShape(shape, i_args[i], xi_args[i]);
for (int p = 0; p < order+1; p++)
{
const int j = i_args[i] + p;
#ifdef MFEM_USE_LAPACK
fact_AB(KL+KU+i-j,j) = shape[p];
#else
A_coll_inv(i,j) = shape[p];
#endif
}
}
}
// Solve the system
#ifdef MFEM_USE_LAPACK
const int NRHS = x.Size();
DenseMatrix B(N, NRHS);
for (int j=0; j<NRHS; ++j)
{
for (int i=0; i<N; ++i) { B(i, j) = (*x[j])[i]; }
}
if (reuse_inverse)
{
BandedFactorizedSolve(KL, KU, fact_AB, B, false, fact_ipiv);
}
else
{
BandedSolve(KL, KU, fact_AB, B, fact_ipiv);
}
for (int j=0; j<NRHS; ++j)
{
for (int i=0; i<N; ++i) { (*x[j])[i] = B(i, j); }
}
#else
if (!reuse_inverse) { A_coll_inv.Invert(); }
Vector tmp;
for (int i= 0; i < x.Size(); i++)
for (int i = 0; i < x.Size(); i++)
{
tmp = *x[i];
A.Mult(tmp,*x[i]);
A_coll_inv.Mult(tmp, *x[i]);
}
#endif
}
int KnotVector::findKnotSpan(real_t u) const
@@ -1421,7 +1485,7 @@ void NURBSPatch::DegreeElevate(int t)
}
}
// Routine from "The NURBS book" - 2nd ed - Piegl and Tiller
// Routine from "The NURBS Book" - 2nd ed - Piegl and Tiller
void NURBSPatch::DegreeElevate(int dir, int t)
{
if (dir >= kv.Size() || dir < 0)
@@ -2385,7 +2449,7 @@ NURBSExtension::NURBSExtension(Mesh *mesh_array[], int num_pieces)
}
NURBSExtension::NURBSExtension(const Mesh *patch_topology,
const Array<const NURBSPatch*> patches_)
const Array<const NURBSPatch*> &patches_)
{
// Basic topology checks
MFEM_VERIFY(patches_.Size() > 0, "Must have at least one patch");
+18 -4
View File
@@ -143,8 +143,13 @@ public:
/** @brief Global curve interpolation through the points @a x (overwritten).
@a x is an array with the length of the spatial dimension containing
vectors with spatial coordinates. The control points of the interpolated
curve are returned in @a x in the same form. */
void FindInterpolant(Array<Vector*> &x);
curve are returned in @a x in the same form.
The inverse of the collocation matrix, used in the interpolation, is
stored for repeated calls and used if @a reuse_inverse is true. Reuse is
valid only if this KnotVector has not changed since the initial call with
@a reuse_inverse false. */
void FindInterpolant(Array<Vector*> &x, bool reuse_inverse = false);
/** Set @a diff, comprised of knots in @a kv not contained in this KnotVector.
@a kv must be of the same order as this KnotVector. The current
@@ -202,6 +207,14 @@ public:
/** Flag to indicate whether the KnotVector has been coarsened, which means
it is ready for non-nested refinement. */
bool coarse;
#ifdef MFEM_USE_LAPACK
// Data for reusing banded matrix factorization in FindInterpolant().
DenseMatrix fact_AB; /// Banded matrix factorization
Array<int> fact_ipiv; /// Row pivot indices
#else
DenseMatrix A_coll_inv; /// Collocation matrix inverse
#endif
};
@@ -285,7 +298,7 @@ public:
includes the weight. The array of control point coordinates stores each
point's coordinates contiguously, and points are ordered in a standard
ijk grid ordering. */
NURBSPatch(Array<const KnotVector *> &kv_, int dim_,
NURBSPatch(Array<const KnotVector *> &kv_, int dim_,
const real_t* control_points);
/// Constructor for a patch of dimension equal to the size of @a kv.
@@ -700,7 +713,8 @@ public:
NURBSExtension(Mesh *mesh_array[], int num_pieces);
NURBSExtension(const Mesh *patch_topology, const Array<const NURBSPatch*> p);
NURBSExtension(const Mesh *patch_topology,
const Array<const NURBSPatch*> &patches_);
/// Copy assignment not supported.
NURBSExtension& operator=(const NURBSExtension&) = delete;
+14 -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!");
}
}
}
+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;
+1 -9
View File
@@ -257,15 +257,7 @@ template <typename SubMeshT>
void AddBoundaryElements(SubMeshT &mesh,
const std::unordered_map<int,int> &lface_to_boundary_attribute)
{
mesh.Dimension();
const int num_codim_1 = [&mesh]()
{
auto Dim = mesh.Dimension();
if (Dim == 1) { return mesh.GetNV(); }
else if (Dim == 2) { return mesh.GetNEdges(); }
else if (Dim == 3) { return mesh.GetNFaces(); }
else { MFEM_ABORT("Invalid dimension."); return -1; }
}();
const int num_codim_1 = mesh.GetNumFaces();
if (mesh.Dimension() == 3)
{
@@ -1,156 +0,0 @@
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#ifndef __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
@@ -1,77 +0,0 @@
# Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
# at the Lawrence Livermore National Laboratory. All Rights reserved. See files
# LICENSE and NOTICE for details. LLNL-CODE-806117.
#
# This file is part of the MFEM library. For more information and source code
# availability visit https://mfem.org.
#
# MFEM is free software; you can redistribute it and/or modify it under the
# terms of the BSD-3 license. We welcome feedback and contributions, see file
# CONTRIBUTING.md for details.
# 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
@@ -1,129 +0,0 @@
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()

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